Part of the reason I self-host my own apps and services is that it’s a great way to learn new things. IPv6 has been a curiosity for quite a while, but I’ve never really dug into the details to understand it well.
Some folks might say IPv6 is a waste of time as you can get by with just using IPv4 + NAT. I agree and think this is a totally valid point - but I’m still interested in learning more about it.
New Concepts and Terminology
There are some key differences and things to understand when adding IPv6 to a home network.
Link Local and ULA Prefixes
One misconception I had was there is a single global address space in IPv6 where every device has one globally unique
IPv6 address. In reality, there are multiple addresses for every IPv6 interface. Every device will have a link-local
address in the range fe80::/10. This address is only used for communicating with other devices on the same subnet.
It is the first address on the interface and is used to bootstrap the rest of the network configuration.
IPv6 networks can also support unique local addresses similar to
how IPv4 supports private ranges like 192.168.0.0/16, 10.0.0.0/8, etc. These addresses are not globally unique or routable,
but they can be used for communication on a private network. Also, while they are not guaranteed to be unique, the probability
of a collision is extremely small (unlike the IPv4 equivalents) due to the fact they have a randomly generated prefix called
the ULA Prefix.
SLAAC
There is a good overview on Wikipedia. While IPv4 typically assigns addresses through DHCP, SLAAC also allows for auto-configuration of an IPv6 address inside the network’s IPv6 space. The router provides the network’s prefix and the host generates the suffix. This is one of the options for assigning client IPv6 addresses (and the primary mechanism for Android devices).
Improving on IPv4 Layout
In hindsight, there are some improvements I’d like to make on the current IPv4 implementation on my network.
- Better support for various subnets on my network. This includes setting up distinct subnets for:
- network devices - routers, switches, WiFi APs, etc
- IoT devices
- trusted clients - both with static IPs and DHCP/SLAAC assigned IPs
- guest devices
- etc
- Improved segmentation for “sites” such as my primary home location, a remote site, VPN segments, etc.
Getting Started
Given what I know so far, my general approach to getting started is similar to the current IPv4 configuration but adding improvements mentioned above.
- Generate a ULA prefix for my internal network using a tool like this.
- Segment the network to better allocate IP space for sites and subnets.
- Assign static IPv6 addresses to my DHCP/DNS servers.
- Ensure DHCP and DNS is working on IPv6.
Subnet Layout Approach
Let’s start with a random ULA prefix - say fd42:1111:1111::/48. Given that we want to allow for multiple
sites and then subnets in each site, we can use the format fd42:1111:1111:SSXX::/64 where SS is the site
and XX is the subnet. This will allow for 256 sites, 256 subnets in each site, and 64 bits of space in each subnet.
NOTE: The
/64space for a IPv6 subnet is a key design aspect and requried for SLAAC support.
We can then layout subnets along these lines:
fd42:1111:1111:SS00::/64- network hardware devices such as firewalls, switches, WiFi APs, etc.fd42:1111:1111:SS01::/64- DNS/DHCP serversfd42:1111:1111:SS02::/64- serversfd42:1111:1111:SS03::/64- trusted clientsfd42:1111:1111:SS04::/64- guest clientsfd42:1111:1111:SS05::/64- IoT devices- etc
Setup Static IPs on DHCP Servers
My current DHCP/DNS is served via dnsmasq on two redundant Raspberry Pis running Raspberry Pi OS 13. I configure static
IPs for these via an Ansible playbook. These devices run ifupdown as their network configuration service so
the interface configuration is defined in /etc/network/interfaces.d/eth0.
An example for a particular host after applying the change looks like:
# /etc/network/interfaces.d/eth0
auto eth0
# Current IPv4 address
iface eth0 inet static
address 192.168.0.14/23
gateway 192.168.0.1
dns-domain example.com
dns-nameservers 9.9.9.9 1.1.1.1
# New IPv6 address
iface eth0 inet6 static
address fd42:1111:1111:0101::14:1/56
gateway fd42:1111:1111:0100::1
dns-search example.com
dns-nameservers 2620:fe::fe 2606:4700:4700::1111
After restarting the network service, let’s check what IPs the host has.
➜ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether dc:a6:32:5b:da:4c brd ff:ff:ff:ff:ff:ff
inet 192.168.0.14/23 brd 192.168.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fd42:9ab1:6c0d:101::14:1/56 scope global
valid_lft forever preferred_lft forever
inet6 fe80::dea6:32ff:fe5b:da4c/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
This confirms the host now has a link local (fe80::*) and a unique network (fd42:1111:1111:101::14:1) address.
NOTE: Tools like
digandipwill abbreviate IPv6 addresses by removing leading zeros. The addressfd42:1111:1111:0101::14:1andfd42:1111:1111:101::14:1are equivalent. You can also use IPv6 notation to reduce redundant zeros with::- the full IPv6 address is reallyfd42:1111:1111:0101:0000:0000:0014:0001.
Testing connectivity with ping
We can also confirm IPv6 traffic is working on the network by pinging between our two IPv6 devices (14:1 and 15:1).
➜ ping -6 -c 1 fd42:1111:1111:101::14:1 # Ping host itself
PING fd42:1111:1111:101::14:1 (fd42:1111:1111:101::14:1) 56 data bytes
64 bytes from fd42:1111:1111:101::14:1: icmp_seq=1 ttl=64 time=0.069 ms
--- fd42:1111:1111:101::14:1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.069/0.069/0.069/0.000 ms
➜ ping -6 -c 1 fd42:1111:1111:101::15:1 # Ping other DHCP server with static IP
PING fd42:1111:1111:101::15:1 (fd42:1111:1111:101::15:1) 56 data bytes
64 bytes from fd42:1111:1111:101::15:1: icmp_seq=1 ttl=64 time=0.230 ms
--- fd42:1111:1111:101::15:1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.230/0.230/0.230/0.000 ms
DNS Configuration
Now that our servers have IPv6 addresses, we need to ensure that DNS is working with IPv6 as well. Let’s check
it by testing resolution of the servers themselves. We will use dig to query the A (IPv4) and AAAA (IPv6) IPs
of the host fuzzpi4.
➜ dig @fd42:1111:1111:101::14:1 fuzzpi4 A fuzzpi4 AAAA +short
192.168.0.14
As you can see we are only getting the IPv4 address. That is because we haven’t told dnsmasq what IPv6 address
fuzzpi4 is running at. The local network hosts are configured in dnsmasq via an addn-hosts file and we can confirm
what addresses are defined for fuzzpi4.
➜ cat /etc/dnsmasq.conf | grep addn-hosts
addn-hosts=/etc/dnsmasq-local-net.list
➜ cat /etc/dnsmasq-local-net.list | grep fuzzpi4
192.168.0.14 fuzzpi4
Let’s add the IPv6 addresses to the addn-hosts file, reload dnsmasq, and confirm we can then resolve both addresses.
➜ cat /etc/dnsmasq-local-net.list | grep fuzzpi4
192.168.0.14 fuzzpi4
fd42:1111:1111:0101::14:1 fuzzpi4 # Now we have IPv6 addresses as well!
➜ dig @fd42:1111:1111:101::14:1 fuzzpi4 A fuzzpi4 AAAA +short
192.168.0.14
fd42:1111:1111:101::14:1 # Nice - IPv6 too!
We can then confirm we can ping the host by name and not just IP.
➜ ping -c 1 -6 fuzzpi4
PING fuzzpi4 (fd42:1111:1111:101::14:1) 56 data bytes
64 bytes from fuzzpi4 (fd42:1111:1111:101::14:1): icmp_seq=1 ttl=64 time=0.169 ms
--- fuzzpi4 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.169/0.169/0.169/0.000 ms
What’s Next
This post covers the initial, foundational steps for introducing IPv6 to a home network. By generating a Unique Local Address (ULA) prefix, designing a logical subnetting scheme, and configuring static addresses for core services, we are off to a good start. We’ve verified that our DNS servers can resolve IPv6 addresses and that basic communication between hosts is working.
However, this is just the start. The current setup only covers static IP addresses on a local, non-routable network. The next steps will involve exploring dynamic address assignment with DHCPv6 and SLAAC for other clients, and investigating how this impacts network routing into our self-hosted containers.