[How-To] NPTv6 with dynamic prefix delegation (and some thoughts)

Started by OPNenthu, May 02, 2026, 11:31:11 PM

Previous topic - Next topic
Just some notes on the configuration because I didn't find a tutorial here.  Let me know if it exists and I'll link to it.

I'll give some thoughts about this in post #2.

If you're looking to set up IPv6 the right way, this is not it.  Please go here instead.

---

(Tested on OPNsense 26.1.7_1-amd64)

The objective is to use static ULA /64 prefixes on internal interfaces (e.g. LAN) which are controlled by you and independent of the ISP, but which get translated on egress to a delegated prefix.  The first 64 bits of the ULA get overwritten but the lower 64 (host) bits remain intact.  This means if you use privacy extensions on a web client those temporary address host bits will be preserved.

This is how it might look in firewall logs:

You cannot view this attachment.

You need at least one internal interface tracking the delegated prefix from WAN.  This is a required input to the NPTv6 rule so that it knows which external prefix to translate to.  I think it's required that your ISP delegates at least a /60 so that one of the prefix IDs can be used for the tracking interface, but don't quote me on this.  I don't know how you would track a /64 delegation and you unfortunately cannot have the NPTv6 rule just use whatever is assigned on WAN already (see Note 3 below).

You can reuse the same tracking interface in multiple NPTv6 rules, however.

You can also use a dedicated loopback as the tracking interface in case you don't already have one.  In my testing the new 'Identity Association' (idassoc) mode works for this purpose though I guess you can also use 'Track Interface (legacy)' (track6).  I haven't tried it.  Take care to disable any automatic radvd configs that get created in that case.

1. Go to Interfaces->Devices->Loopback and add new device.
2. Go to Interfaces->Assignments and assign this device with a name.
3. Go to Interfaces->[name] and enable it.
- Leave the IPv4 config set to None.
- Change the IPv6 config to "Identity Association" and choose your WAN as the parent interface.
- Choose an available prefix ID and save.

Note 1: don't add any rules on this interface as it won't be passing traffic.  It's just there to hold the prefix.

You cannot view this attachment.

Finally the NPTv6 rule itself:

1. Go to Firewall->NAT->NPTv6 and click the "+" button to add a new rule.
- Set "Interface" to WAN (this is the interface that the rule applies to for outbound translation)
- Set "Internal IPv6 prefix" to one of your ULA prefixes for an internal network.
- Leave "External IPv6 prefix" blank.
- Set "Track Interface" to the internal interface tracking the prefix, or the loopback we set up earlier.
- Save.

Repeat for additional ULA prefixes you need to translate.

Note 2: you don't need to specify the internal interface anywhere.  NPTv6 cares only about prefixes.

Note 3: if you don't specify either a tracking interface or an external prefix for translation (which we don't use for dynamic prefixes), there is currently a validation bug that will not stop you from doing this and it will cause IPv6 services that you have listening on WAN (e.g. WireGuard tunnels) to break.

And that should be it. This is how it might look for several subnets getting translated to the same tracking interface's prefix:

You cannot view this attachment.
N5105 | 8/250GB | 4xi226-V | Community

https://www.youtube.com/watch?v=XI9NG068TwI

Some thoughts:

For me this is an experiment to see if it helps with some specific issues (and to learn something new) but I don't plan to keep this configuration long term.  The main problem is that if IPv4 is enabled then many web clients and browsers will prefer that over a ULA for egress, so IPv6 will almost never be used except as a fallback.  I think there are ways to configure that on a per-client basis but I'm not going to try.

(UPDATE: On a subnet with Android mobile clients I see much higher IPv6 usage, so those may already be de-prioritizing IPv4.)

I think this method becomes more attractive in an IPv6 only network where the ULA route is the only one, because then you can have true independence from the ISP's delegation for internal networks and there is nothing competing with the ULA for priority.  Still, it flies against the IPv6 design principle where everything is globally addressable and any kind of NAT (even just prefix translation) should not be needed.  As already pointed out, addressability != reachability.

There are however a few annoyances that this attempts to work around and they mainly have to do with challenges associated with dynamic prefixes.  Put bluntly, many ISPs are not following RIPE recommendations[1] for persistent /48 prefix assignments.  Most of the below reasons stem from that fateful decision.

I think the most obvious reason is if you don't have enough /64 prefixes for your subnetting.  If your ISP only delegates a /60 and you need more than 16 subnets (or 15+WAN, because in DHCPv6-PD the WAN interface consumes one) then you might consider using ULAs for additional subnets and translating them to one of the existing prefixes.

A second reason is because of firewalling challenges[2] related to dynamic GUAs.  In some cases it's difficult to enforce boundaries using only the network aliases that OPNsense maintains.  For example, you might be tempted to create an alias named "IPV6_INTERNET" and define it as 2000::/3, then use that in filters or policy routing.  The problem is that your GUAs are included in that and (at least as of 26.1) you have no way to exclude your dynamic prefixes.

A third reason is because some particular quirks may affect you.  My ISP gives me a long-lived prefix which persists even after modem reboots.  The ISP also reboots the modem weekly for updates and network maintenance.  This causes the prefix to become deprecated and clients should not use the same prefix again, by some unfortunate language[3] in section 3.5 of RFC 8981.  The effect is that until/unless clients reset their network interfaces, SLAAC fails to generate new addresses which is particularly insidious if you rely on temporary addresses with IPv6 privacy extensions.  If there's one thing I cannot abide, it's silent failure[4] of privacy related functions and in my recent testing NPTv6 does fix this.

All said however, I think NPTv6 for these use cases is still not the second, third, or even fourth best alternative.  Before this is considered at all I would look at something like ndp-proxy-go[5] or the trick of borrowing an unused prefix from somewhere[6], if you can.  Just beware that some ISPs (like mine) do Stupid ThingsTM with RAs (like advertising ULA routes) so the ndp-proxy is not recommended then.

I'll mention also this article[7] linked by @JavierĀ® in case you are interested in a true NAT option, although the described solution is centric to the OpenBSD flavor of pf and would need some adaptation for OPNsense.  This one still has the same problem of ULA priority as NPTv6, though.

NAT impressions:

Regarding outbound NAT, in my testing I see that NPTv6 does not seem to interfere with it.  I have a VPN interface with an attached gateway and traffic leaving that interface is NATed to the wireguard tunnel address.  I think because the Outbound NAT / SNAT rules have higher precedence than the NTPv6 / binat rules in the pf ruleset those are overriding the NPTv6 rules:

You cannot view this attachment.

root@firewall:~ # cat /tmp/rules.debug | grep nat
...
nat log on wg1 inet from any to any -> (wg1:0) port 1024:65535 # SNAT on WAN_VPN0
nat log on wg1 inet6 from any to any -> (wg1:0) port 1024:65535 # SNAT on WAN_VPN0 (IPv6)
...
binat log on igc1 inet6 from fd5a:xxxx:xxxx:1002::/64 -> (lo1:0)/64 # NPTv6 WAN<->VPN (/64)

So interactions with outbound NAT seem safe.

Finally, what about ingress?  Well, for hosts behind the firewall I would guess that NAT port forwards / DNAT should still work.  I don't presently host anything and haven't tested it.


[1]: https://www.ripe.net/publications/docs/ripe-690/
[2]: https://github.com/opnsense/core/issues/7000
[3]: https://www.rfc-editor.org/rfc/rfc8981.pdf
[4]: https://forum.opnsense.org/index.php?topic=44435.0
[5]: https://forum.opnsense.org/index.php?topic=44071.0
[6]: https://forum.opnsense.org/index.php?topic=31374.msg151647#msg151647
[7]: https://eradman.com/posts/ipv6-strategy.html
N5105 | 8/250GB | 4xi226-V | Community

https://www.youtube.com/watch?v=XI9NG068TwI

I use a very similar setup for special use cases and yes, this works just fine (with all the known limitations of ULAs of course).

What seems special about your setup is that you translate multiple /64 ULA subnets to the same /64 GUA subnet. While this may or may not work for outbound connections, it can't work for inbound connections since there is no way to determine which internal prefix to translate to. You need a true 1:1 mapping for this.

I instead use a single NPT rule for a larger subnet, for example:

Loopback interface prefix ID: 0x10
NPT internal IPv6 prefix: fd01:2345:6789:1000::/60

So you only need a single NPT rule for 16 LAN interfaces (fd01:2345:6789:1000::/64 ... fd01:2345:6789:100f::/64).

This assumes you get a /56 PD from your ISP, but of course you can adapt this to any PD size.

Cheers
Maurice
OPNsense virtual machine images
OPNsense aarch64 firmware repository

Commercial support & engineering available. PM for details (en / de).

Hi @Maurice,

I want to implement your approach but I only get a /60 from my ISP.  Is there a clean way I could use a larger internal NTPv6 prefix than the /64 I currently have?  I'm not certain but it might help with an issue I just noticed.

I have a UniFi OS controller on LAN at the fd5a:...:1000:...:f30 address.
I have a web client (trusted PC) on CLEAR at the fd5a:...:1003:...6ac2 address.

When I access the UniFi controller those two pass lots of UDP packets but at some point the UniFi host picks up the external prefix (2601:...:316f/64) and starts trying to send traffic to my web client on the translated GUA prefix.

You cannot view this attachment.

This is the only case I've seen so far where this happens.  I put an "out" block rule on the tracking interface (lo1) to try and prevent leaks to WAN though I'm not certain that's effective. This would be difficult to filter on the LAN interface itself.

Maybe has to do with the /64 -> /64 translation?
N5105 | 8/250GB | 4xi226-V | Community

https://www.youtube.com/watch?v=XI9NG068TwI

For eight /64 ULA subnets, you'll need a /61 NPT rule. That's half of the /60 you get from your ISP, so not a problem.

Loopback interface prefix ID: 8
NPT internal IPv6 prefix: fd5a:xxxx:xxxx:1000::/61

This translates ULA prefixes fd5a:xxxx:xxxx:1000::/64 .. fd5a:xxxx:xxxx:1007::/64 to GUA prefixes with IDs 8 .. f.
GUA prefixes with IDs 0 .. 7 remain available for other purposes.

Cheers
Maurice
OPNsense virtual machine images
OPNsense aarch64 firmware repository

Commercial support & engineering available. PM for details (en / de).