Setting up an IPv6 only home network with OpenWrt
Almost all ISPs today offer both IPv4 and IPv6. The same applies to network hardware for home use. Modern devices can easily communicate via IPv4 and IPv6 simultaneously using so-called dual-stack technology. If the server supports both protocols, the end device decides which protocol is used. Of course, this can only work if the software of the entire infrastructure is laid out twice: once for IPv4 and once for IPv6. This dual implementation of the network software also has disadvantages. To begin with, with the complexity of the software increases the attack surface for cyberattacks. Also, the likelihood of misconfiguration raises, which can make the attackers' work easier. Last but not least, all firewall rules must be defined for both protocols, which is prone to errors.
NAT64/DNS64 technology can provide a remedy. Here, communication in the home network takes place exclusively via IPv6. If a device in the home network wants to communicate with an IPv4 only server, the DNS64 proxy in the home router translates the IPv4 address into a pseudo IPv6 address. When the end device contacts the server via this address, the NAT64 of the home router unpacks the IPv6 packet it receives from the end device and forwards the request to the server as a regular IPv4 package. The server's response is packaged back into an IPv6 package on the way back through the home router before being sent to the end device in the home network. The whole communication appears to the end device as IPv6, the conversion process takes place transparently in the router via NAT64/DNS64.
The implementation presented here is based on the OpenWrt 24.10 operating system and should work on any router on which it can be installed. For initial tests 64 MiB RAM is sufficient, for everyday operation 256 MiB RAM is recommended. Since all outgoing IPv4 packets must be unpacked and repackaged, an up-to-date processor in the router is an advantage.
Although all my software works fine with IPv6, there are a few exceptions:
e.g. the VPN client nordvpn
does not work in an IPv6-only network (Wireguard
works). For such cases, the solution presented here provides as a fallback a
conventional IPv4/IPv6 dual stack subnet that can be accessed over the 2.4GHz
Wi-Fi.
NAT64 for a IPv6-only network (Jool)
To give us a brief outline of the related technologies, let's refer to the documention on OpenWrt Wiki: Jool:
NAT64 (Network address translation from IPv6 to IPv4) is a technology for allowing an IPv6-only network to connect and interoperate with the IPv4 Internet.
It's very similar to the NAT44 used by most home networks that forwards packets between IPv4 private address space and IPv4 public address space, except it forwards between IPv6 (public) addresses and IPv4 public addresses.
It works in conjunction with several technologies:
DNS64, where the DNS returns a specially formatted IPv6 address that encodes the target IPv4 address, which is then handled by NAT64 to forward packets.
PREF64, where the router advertises in an ICMPv6 Router Advertisement the NAT64 prefix which devices can use to create a CLAT interface (Android, iOS and macOS uses this).
In OpenWrt, NAT64 can be easily activated using Jool.
The prerequisite for this to work, is that your ISP provides both: IPv4 and IPv6:
Running Jool in a separate network namespace
Note: The above cited article “OpenWrt Wiki: Jool” outlines two options possible to run Jool in namespace. In this section we follow the chapter: Option 2 - Running Jool in a separate network namespace.
Prepare your OpenWrt router
-
The following packages need to be installed first:
opkg update opkg install kmod-veth ip-full kmod-jool-netfilter jool-tools-netfilter
-
Create or copy the following shell script to
/etc/jool/setupjool.sh
#!/bin/sh ip link add jool type veth peer openwrt ip netns add jool ip link set dev openwrt netns jool ip netns exec jool sh <<EOF sysctl -w net.ipv4.conf.all.forwarding=1 sysctl -w net.ipv6.conf.all.forwarding=1 sysctl -w net.ipv6.conf.openwrt.accept_ra=2 sysctl -w net.ipv4.ip_local_port_range="32768 32999" ip link set dev lo up ip link set dev openwrt up ip addr add dev openwrt 192.168.164.2/24 ip addr add dev openwrt fe80::64 ip route add default via 192.168.164.1 modprobe jool jool instance add --netfilter --pool6 64:ff9b::/96 jool global update lowest-ipv6-mtu 1500 jool pool4 add 192.168.164.2 33000-65535 --tcp jool pool4 add 192.168.164.2 33000-65535 --udp jool pool4 add 192.168.164.2 33000-65535 --icmp EOF
-
Make it executable and execute it once.
chmod +x /etc/jool/setupjool.sh /etc/jool/setupjool.sh
-
Add the following line to
/etc/rc.local
through the CLI or Luci UI (System - Startup - Local Startup
), before theexit 0
.... /etc/jool/setupjool.sh exit 0
-
In order to make it persistent across system upgrades, append the path to the script
/etc/sysupgrade.conf
through the CLI or Luci UI (System - Backup / Flash Firmware- Configuration
)cat << EOF >> /etc/sysupgrade.conf /etc/jool/setupjool.sh EOF
Check the success
-
Check the presence of the new IP interface:
ip a show dev jool 12: jool@if11: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 66:29:79:8d:ff:83 brd ff:ff:ff:ff:ff:ff link-netns joold
-
Check that the kernel modules are loaded:
lsmod | grep jool jool 12288 0 jool_common 176128 2 jool_siit,jool jool_siit 12288 0 nf_defrag_ipv4 12288 2 nf_conntrack,jool nf_defrag_ipv6 16384 2 nf_conntrack,jool
Configure Jool
-
Setup
jool
interfaceOur plan:
- use IPv4 subnet 192.168.164.1/24
- allocate one IPv6 /64 with SLAAC
- route NAT64 prefix to fe80::64
- configure
jool
firewall zone and forward fromlan
zone
Enter the following in your router terminal:
# /etc/config/network uci set network.jool=interface uci set network.jool.proto='static' uci set network.jool.device='jool' uci set network.jool.ipaddr='192.168.164.1' uci set network.jool.netmask='255.255.255.0' uci set network.jool.ip6assign='64' uci set network.jool.ip6hint='64'
This will cause changes in
/etc/config/network
config interface 'jool' option proto 'static' option device 'jool' option ipaddr '192.168.164.1' option netmask '255.255.255.0' option ip6assign '64' option ip6hint '64'
-
Configure DHCPv4 and SLAAC/DHCPv6
Enter the following in your router terminal:
# /etc/config/dhcp uci set dhcp.jool=dhcp uci set dhcp.jool.interface='jool' uci set dhcp.jool.start='100' uci set dhcp.jool.limit='150' uci set dhcp.jool.leasetime='12h' uci set dhcp.jool.ignore='1' uci set dhcp.jool.ra='server' uci set dhcp.jool.ra_default='2'
This will add the following to
/etc/config/dhcp
:config dhcp 'jool' option interface 'jool' option start '100' option limit '150' option leasetime '12h' option ignore '1' option ra 'server' option ra_default '2'
-
Add a static IPv6 route
Enter the following in your router terminal:
uci add network route6 uci set network.@route6[-1].interface='jool' uci set network.@route6[-1].target='64:ff9b::/96' uci set network.@route6[-1].gateway='fe80::64'
This will add the following to
/etc/config/network
:config route6 option interface 'jool' option target '64:ff9b::/96' option gateway 'fe80::64'
-
Add
jool
firewall zoneEnter the following in your router terminal:
# /etc/config/firewall uci add firewall zone uci set firewall.@zone[-1].name='jool' uci set firewall.@zone[-1].input='ACCEPT' uci set firewall.@zone[-1].output='ACCEPT' uci set firewall.@zone[-1].forward='REJECT' uci add_list firewall.@zone[-1].network='jool'
This will add the following to
/etc/config/firewall
:config zone option name 'jool' option input 'ACCEPT' option output 'ACCEPT' option forward 'REJECT' list network 'jool'
-
Forward
jool
zone towan
Enter the following in your router terminal:
uci add firewall forwarding # uci set firewall.@forwarding[-1].src='jool' uci set firewall.@forwarding[-1].dest='wan'
-
Add
br-lan64
Bridge# /etc/config/network uci add network device uci set network.@device[-1].type='bridge' uci set network.@device[-1].name='br-lan64'
-
Create
lan64
interface, with the same properties aslan
.# /etc/config/network uci set network.lan64=interface uci set network.lan64.proto='static' uci set network.lan64.device='br-lan64' uci set network.lan64.ip6assign='64' uci set network.lan64.ra_pref64='64:ff9b::/96'
-
Correct assignment length of
lan
:uci set network.lan=interface uci set network.lan.ip6assign='64' uci commit
-
Set up DHCP on
lan64
Android and iOS as well as macOS are working fine in IPv6-only networks. To signal to clients which are able and willing to run IPv6-only, the DHCP option 108 was introduced with RFC8925.
Add this option to the DHCPv4 configuration of the desired zone e.g.,
lan64
file/etc/config/dhcp
30 minutes = 1800 seconds = 0x708 seconds
This results in:
dhcp_option ‘108,0:0:7:8’
After this all your mobile and macOS devices will drop the IPv4 lease and run in IPv6-only mode.
We also add to the router advertisement messages, the NAT64 prefix the network is using. This is relatively a new feature introduced with
v23.05.0
.option ra_pref64 '64:ff9b::/96'
Enter the following in your router terminal:
# /etc/config/dhcp uci set dhcp.lan64=dhcp uci set dhcp.lan64.interface='lan64' uci set dhcp.lan64.start='100' uci set dhcp.lan64.limit='150' uci set dhcp.lan64.leasetime='12h' # This is a IPv6 only network. uci add_list dhcp.lan64.dhcp_option='108,0:0:7:8' uci set dhcp.lan64.ra='server' uci add_list dhcp.lan64.ra_flags='managed-config' uci add_list dhcp.lan64.ra_flags='other-config' # NAT64 prefix. uci set dhcp.lan64.ra_pref64='64:ff9b::/96' uci set dhcp.lan64.dhcpv6='server'
-
Add firewall
lan64
zoneuci add firewall zone uci add_list firewall.@zone[-1].network='lan64' uci set firewall.@zone[-1].name='lan64' uci set firewall.@zone[-1].input='ACCEPT' uci set firewall.@zone[-1].output='ACCEPT' uci set firewall.@zone[-1].forward='ACCEPT' uci set firewall.@zone[-1].family='ipv6'
-
Forward
lan64
zone tojool
Enter the following in your router terminal:
uci add firewall forwarding uci set firewall.@forwarding[-1].src='lan64' uci set firewall.@forwarding[-1].dest='jool'
-
Forward
lan64
zone towan
Enter the following in your router terminal:
uci add firewall forwarding uci set firewall.@forwarding[-1].src='lan64' uci set firewall.@forwarding[-1].dest='wan' uci commit
-
Add some firewall rules for pinging and special protocols.
The following is a copy from the
Allow-IPSec-ESP
rule forlan64
:uci add firewall rule uci set firewall.@rule[-1].name='Allow-IPSec-ESP-lan64' uci set firewall.@rule[-1].dest='lan64' uci set firewall.@rule[-1].family='ipv6' uci set firewall.@rule[-1].proto='esp' uci set firewall.@rule[-1].src='wan' uci set firewall.@rule[-1].target='ACCEPT'
The following is a copy from the
Allow-ISAKMP
rule forlan64
:uci add firewall rule uci set firewall.@rule[-1].name='Allow-ISAKMP-lan64' uci set firewall.@rule[-1].dest='lan64' uci set firewall.@rule[-1].dest_port='500' uci set firewall.@rule[-1].family='ipv6' uci set firewall.@rule[-1].proto='udp' uci set firewall.@rule[-1].src='wan' uci set firewall.@rule[-1].target='ACCEPT'
-
Add firewall forwarding to allow communication between subnets:
uci add firewall forwarding uci set firewall.@forwarding[-1].src='lan64' uci set firewall.@forwarding[-1].dest='lan' uci add firewall forwarding uci set firewall.@forwarding[-1].src='lan' uci set firewall.@forwarding[-1].dest='lan64'
-
Apply changes
uci commit service firewall restart service network restart
Or, if the following test does not work, reboot:
reboot
Try a complete reboot before you start tweaking and debugging.
Check success
See also this chapter.
-
To test the
lan64
interface, connect it to one Wi-Fi antenna, e.g. 2,4 Hz and connect your desktop to it:uci set wireless.default_radio0.network='lan64' uci commit service network restart
-
Confirm working NAT64 from your router
ping 64:ff9b::1.1.1.1
-
Make sure it works also from the connected devices
ping 64:ff9b::1.1.1.1
Configure the DNS64 proxy server
See also:DNS64 - Wikipedia
-
Disable
dnsmasq
:uci set dhcp.@dnsmasq[-1].port='0'
-
Activate Unbound:
# /etc/config/unbound uci set unbound.ub_main.enabled='1' uci set unbound.ub_main.dns64='1' uci del unbound.ub_main.dhcp4_slaac6 uci del unbound.ub_main.query_minimize uci del unbound.ub_main.query_min_strict uci set unbound.ub_main.unbound_control='1' uci set unbound.ub_main.dhcp4_slaac6='1' uci set unbound.ub_main.rebind_localhost='1' # Set trigger to `lan64` uci del unbound.ub_main.iface_trig uci add_list unbound.ub_main.iface_trig='lan64' uci add_list unbound.ub_main.iface_trig='wan' uci set unbound.ub_main.dhcp_link='odhcpd' # Set ISP upstream DNS server as provider uci set unbound.auth_icann.fallback='0' uci set unbound.fwd_isp.fallback='0' uci set unbound.fwd_isp.enabled='1' uci set unbound.fwd_google.fallback='0' uci set unbound.fwd_cloudflare.fallback='0'
You'll see error message about a missing path; we correct that in the next step.
-
Configure
Odhcpd
:#/etc/config/dhcp: uci set dhcp.odhcpd=odhcpd uci set dhcp.odhcpd.leasefile='/var/lib/odhcpd/dhcp.leases' uci set dhcp.odhcpd.leasetrigger='/usr/lib/unbound/odhcpd.sh' uci set dhcp.odhcpd.maindhcp='1'
-
Reboot
uci commit reboot
Check success
-
Connect your PC to Wi-Fi 2.4GHz (which is currently assigned to the
lan64
interface). -
Check with
ip a
that the interface has no IPv4. We are IPv6 only now!5: wlp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether c8:94:02:bd:a6:d3 brd ff:ff:ff:ff:ff:ff inet6 fdde::f6f/128 scope global dynamic noprefixroute valid_lft 43178sec preferred_lft 545sec inet6 2001:XXX:XXXX:XXXX::f6f/128 scope global dynamic noprefixroute valid_lft 1745sec preferred_lft 545sec inet6 2001:XXX:XXXX:XXXX:d80d:76ed:d47:476d/64 scope global temporary dynamic valid_lft 1745sec preferred_lft 545sec inet6 2001:XXX:XXXX:XXXX:56e4:ae0e:28b6:353f/64 scope global dynamic mngtmpaddr noprefixroute valid_lft 1745sec preferred_lft 545sec inet6 fdde::29c7:dd7c:2002:249a/64 scope global temporary dynamic valid_lft 593201sec preferred_lft 74695sec inet6 fdde::993f:600d:6c8a:fc52/64 scope global mngtmpaddr noprefixroute valid_lft forever preferred_lft 604795sec inet6 fe80::79a3:b6cb:84f5:aecf/64 scope link noprefixroute valid_lft forever preferred_lft forever
-
Observe the
unbound
's log:OpenWRT Luci -> Services -> Recursive DNS -> Status -> Log
This is how mine looks like:
Tue Jan 14 21:46:37 2025 daemon.info unbound: [4056:0] info: start of service (unbound 1.21.0). Tue Jan 14 21:46:38 2025 daemon.info unbound: [4056:0] info: generate keytag query _ta-4f66. NULL IN Tue Jan 14 21:48:16 2025 daemon.info unbound: [4056:0] info: service stopped (unbound 1.21.0). Tue Jan 14 21:48:16 2025 daemon.info unbound: [4056:0] info: server stats for thread 0: 96 queries, 27 answers from cache, 69 recursions, 0 prefetch, 0 rejected by ip ratelimiting Tue Jan 14 21:48:16 2025 daemon.info unbound: [4056:0] info: server stats for thread 0: requestlist max 13 avg 2.89855 exceeded 0 jostled 0 Tue Jan 14 21:48:16 2025 daemon.info unbound: [4056:0] info: average recursion processing time 0.182811 sec Tue Jan 14 21:48:16 2025 daemon.info unbound: [4056:0] info: histogram of recursion processing times Tue Jan 14 21:48:16 2025 daemon.info unbound: [4056:0] info: [25%]=0.0551595 median[50%]=0.120149 [75%]=0.276187 Tue Jan 14 21:48:16 2025 daemon.info unbound: [4056:0] info: lower(secs) upper(secs) recursions Tue Jan 14 21:48:16 2025 daemon.info unbound: [4056:0] info: 0.004096 0.008192 3 ... Tue Jan 14 21:48:16 2025 daemon.info unbound: [4056:0] info: 0.524288 1.000000 4 Tue Jan 14 21:48:24 2025 daemon.notice unbound: [5112:0] notice: init module 0: respip Tue Jan 14 21:48:24 2025 daemon.notice unbound: [5112:0] notice: init module 1: dns64 Tue Jan 14 21:48:24 2025 daemon.notice unbound: [5112:0] notice: init module 2: validator Tue Jan 14 21:48:24 2025 daemon.notice unbound: [5112:0] notice: init module 3: iterator Tue Jan 14 21:48:24 2025 daemon.info unbound: [5112:0] info: start of service (unbound 1.21.0). Tue Jan 14 21:48:24 2025 daemon.info unbound: [5112:0] info: generate keytag query _ta-4f66. NULL IN
-
Query an IPv4 only domain from your PC, e.g.:
nslookup web.de
Observe the last 2 IPv6 addresses, they are composed by
unbound
.Server: fddd:0:0:1::1 Address: fddd:0:0:1::1#53 Non-authoritative answer: Name: web.de Address: 82.165.229.83 Name: web.de Address: 82.165.229.138 Name: web.de Address: 64:ff9b::52a5:e58a Name: web.de Address: 64:ff9b::52a5:e553
-
Open an IPv4 only website, e.g. https://web.de or https://ipv4.google.com.
Optional: Connect your LAN to the new IPv6 subnet lan64
Currently, only the 2.4GHz Wi-Fi is connected to the lan64
IPv6 only subnet,
while the LAN is still connected to the dual stack lan
subnet.
This section explains how to invert this assignment:
- IPv6 only subnet
lan64
: Ethernet port LAN + 5GHz Wi-Fi - IPv6 and IPv4 dual stack
lan
: 2,4GHz Wi-Fi
-
Higher weights have priority and get lower IPv6 subnets. We want
0
forlan64
. On my systemlan
gets10
:# /etc/config/network uci set network.lan=interface uci set network.lan.ip6weight='8' uci set network.lan64=interface uci set network.lan64.ip6weight='9' uci commit
-
Assign the Ethernet lan ports to
lan64
:Open a terminal in your OpenWrt router, and edit the file:
vi /etc/config/network
Edit the
device
sections. Before the change:config device option name 'br-lan' option type 'bridge' list ports 'lan1' list ports 'lan2' config device option type 'bridge' option name 'br-lan64'
After the change:
config device option name 'br-lan' option type 'bridge' config device option type 'bridge' option name 'br-lan64' list ports 'lan1' list ports 'lan2'
service network restart
-
Attach the 2.4GHz Wi-Fi to the
lan
interface.uci set wireless.default_radio0.network='lan' uci set wireless.default_radio1.network='lan64'
-
Reboot
uci commit reboot
Conclusion after 3 weeks of operation
In day-to-day operation, it is not noticeable at all that all communication
in the home network runs via IPv6. The only software that does not run so far is
the proprietary NordVPN client. One option is to switch to Wireguard (wgnord
).
Or, connect the computer to the Internet via 2.4GHz Wi-Fi before establishing
the VPN connection as usual. A conventional dual stack IPv4/IPv6 connection runs
on this interface as backup.