IPv6 Unique Local Addresses (ULA) for the win

Problem statement

I was quite happy with my IPv6 on EdgeRouter + PiHole setup, especially after I fixed the IPv6 on UniFi guest wireless network issue.

That is, until my refreshed corp devices1 decided for some reason that IPv6 link local addresses are too cool for them. The symptom of that was that DNS resolution became miserably slow, likely falling back onto IPv4.

In this quick post I’m going to explain switching to my very own Unique Local Address (ULA) range and some issues I ran into during the reconfiguration.

What are Unique Local Addresses?

RFC4193 – Unique Local IPv6 Unicast Addresses explains best:

These addresses are called Unique Local IPv6 Unicast Addresses and are abbreviated in this document as Local IPv6 addresses. They are not expected to be routable on the global Internet. They are routable inside of a more limited area such as a site. They may also be routed between a limited set of sites.

Why are they useful?

As I explained in the original IPv6 on EdgeRouter + PiHole, I’m not fond of binding the DNS resolver address for my home network to an address derived from DHCP prefix delegation by my ISP.

Because that is a recipe for annoying outage should the IPv6 range assigned to me change in the future.

Which is why I originally went with the link-local address for the DNS resolver.

But since Apple devices decided not to play nice, a different stable address range is needed.

And since my local DNS server is expected to be local to the home network, an analog to RFC1918 IPv4 ranges (, etc) feels appropriate.

Thus, the Unique Local addresses are ideal:

Got a range in mind, now what?

It is rather simple, add a new address on the router (let’s assume I decided to use fd00:1234:5678::/48 as my ULA assignment):

set interfaces ethernet eth1 address fd00:1234:5678::1/64

and similarly e.g. fd00:1234:5678::2/64 on the Pi-Hole.

Then switch the RDNSS to use this new Pi-Hole address:

edit interfaces ethernet eth1 ipv6 router-advert
set radvd-options "RDNSS fd00::1234:5678::2 { AdvRDNSSLifetime 300; };"
# commit
# save
# exit

Originally I was freaked out a bit, because immediately I noticed a new router announcement for the fd00:1234:5678::/64 range. That is actually documented in the radvd.conf man page (emphasis mine):

Special prefix “::/64” is also supported on systems that implement getifaddrs() […]. When configured, radvd picks all non-link-local prefix assigned to the interface and starts advertising it.

Immediately I questioned the sanity of my decision. But as RFC6724, section 2.1 points out, no need to worry, because default policy table:

Prefix        Precedence Label
::1/128               50     0
::/0                  40     1
::ffff:0:0/96         35     4
2002::/16             30     2
2001::/32              5     5
fc00::/7               3    13
::/96                  1     3
fec0::/10              1    11
3ffe::/16              1    12

should not qualify the ULA prefix as a default address, as long as some other range from the usual pool is advertised.

And a quick check:

$ ip route get fd00:1234:5678::/128 | sed 's,from.*src,<-,;s,metric.*,,'
fd00:1234:5678:: <- fd00:1234:5678::xxxx
$ ip route get fd00:5678:1234::/128 | sed 's,from.*src,<-,;s,metric.*,,'
fd00:5678:1234:: <- fd00:1234:5678::xxxx
$ ip route get 2a02:168::/128 | sed 's,from.*src,<-,;s,metric.*,,'
2a02:168:: <- 2a02:168::xxxx metric

actually shows sane behavior. Not sure about Apple devices, but Linux seems to get it.

Still, it might be prudent to preemptively firewall out any outgoing traffic to the fc::/7 range at the network edge3:

# configure
# WAN ingress
edit firewall ipv6-name WAN6_IN
set rule 8 action reject
set rule 8 description "reject ULA ingress"
set rule 8 protocol all
set rule 8 source address fc::/7


# WAN egress
edit firewall ipv6-name WAN6_OUT
set default-action accept
set rule 8 action reject
set rule 8 description "reject ULA egress"
set rule 8 protocol all
set rule 8 destination address fc::/7


# Hook it up (if not already)
#set interfaces ethernet eth0 firewall in ipv6-name WAN6_IN
set interfaces ethernet eth0 firewall out ipv6-name WAN6_OUT

# commit
# save
# exit

This way, even if my provider drops the ball with IPv6 assignments, I won’t be “spilling” ULA-addressed traffic to them.

Closing words

This wasn’t particularly hard, the tl;dr is basically one ip addr add and some fussing around with firewall.

Maybe the interesting tidbit is the fact that the IPv6 stack will do the right thing out of the box, even when more than one range is advertised.

  1. Hello, new Mac laptop and new iPhone.

  2. Fully optional, but why not.

  3. The snippet assumes that the previous firewall rules from IPv6 on EdgeRouter + PiHole are in place; if not, adjust accordingly.