World IPv6 Launch Logo
World IPv6 Launch Logo

It’s fairly straightforward to build your own router, and there are a number of tutorials for setting up IPv4 forwarding and NAT rules on Linux1. However, IPv6 is a bit more complicated. There are many BSD and Linux based operating systems like pfSense and OpenWRT, which have web management tools to make setting up IPv6 straight forward. However, if you like to run your own custom Linux distribution on your home router and control everything from the command line, this tutorial will take you through configuring dhcpcd, dnsmasq, unbound, iptables and ip6tables for full IPv6 support on your local network.

Interface Setup

The router I’m currently using has several independent network adapters. I went ahead an set the kernel options biosdevname=0 net.ifnames=02 so I could rename the adapters as if1 through if6 through the following udev rules. In my configuration, if1 is my WAN port and if2/if3 are bridged together for the LAN (one going to a local switch and the other to a Wi-Fi 6 access point). These are just my personal choices, and you may choose a different naming convention or use the unique names set by the Linux kernel.

# /etc/udev/rules.d/70-netif-names.rules
SUBSYSTEM=="net",ACTION=="add",DRIVERS=="?*",ATTR{address}=="xx:xx:xx:xx:xx:10",ATTR{dev_id}=="0x0",ATTR{type}=="1",KERNEL=="eth*",NAME="if1"
SUBSYSTEM=="net",ACTION=="add",DRIVERS=="?*",ATTR{address}=="xx:xx:xx:xx:xx:11",ATTR{dev_id}=="0x0",ATTR{type}=="1",KERNEL=="eth*",NAME="if2"
SUBSYSTEM=="net",ACTION=="add",DRIVERS=="?*",ATTR{address}=="xx:xx:xx:xx:xx:12",ATTR{dev_id}=="0x0",ATTR{type}=="1",KERNEL=="eth*",NAME="if3"
SUBSYSTEM=="net",ACTION=="add",DRIVERS=="?*",ATTR{address}=="xx:xx:xx:xx:xx:13",ATTR{dev_id}=="0x0",ATTR{type}=="1",KERNEL=="eth*",NAME="if4"
SUBSYSTEM=="net",ACTION=="add",DRIVERS=="?*",ATTR{address}=="xx:xx:xx:xx:xx:14",ATTR{dev_id}=="0x0",ATTR{type}=="1",KERNEL=="eth*",NAME="if5"
SUBSYSTEM=="net",ACTION=="add",DRIVERS=="?*",ATTR{address}=="xx:xx:xx:xx:xx:15",ATTR{dev_id}=="0x0",ATTR{type}=="1",KERNEL=="eth*",NAME="if6"

Void Linux recommends using /etc/rc.local to set up networking, if you’re not using a tool like NetworkManager. Network setup varies by distribution, sometimes residing in /etc/network or /etc/netplan. Set up your interfaces according to your distribution’s documentation. In the following example, I create a bridge called brlan, attach if2 and if3 to it, and give the bridge a private IPv4 address and /24 subnet.

# /etc/rc.local

ip link set dev if2 up
ip link set dev if3 up

brctl addbr brlan
brctl addif brlan if2
brctl addif brlan if3

ip addr add 10.10.10.1/24 dev brlan
ip link set dev brlan up

DHCP / WAN

One of the best guides I’ve found on IPv6 router support in Linux is by Tor Hveem3. Unfortunately he uses the wide-dhcpv6-client, which isn’t available in Void Linux, nor does it seem to be maintained. However, the dhcpcd client, found in many distributions, has fairly well documented support for IPv64. You may need to adjust ia_na and ia_pd, as the correct values for getting a delegated prefix seems to vary depending on your ISP5.

The following is my /etc/dhcpcd.conf. It’s configured to not allow its DNS servers to be set by my ISP, because I’ll be running my own DNS server which we’ll see later.

controlgroup wheel

duid
persistent
vendorclassid

nooption domain_name_servers, domain_name, domain_search
option classless_static_routes
option interface_mtu

option host_name
option rapid_commit
require dhcp_server_identifier
slaac private

noipv6rs                 # disable routing solicitation
denyinterfaces brlan     # Don't touch the LAN at all
interface if1
  ipv6rs                 # enable routing solicitation for WAN adapter
  ia_na 1                # request an IPv6 address
  ia_pd 1 brlan/0        # request a PD and assign it to the LAN

Local Network

dhcpcd will give us IP addresses from our ISP, but we then need to assign addresses to our network. Some tutorials may instruct people to use dhcpd for issuing IPv4 addresses and radvd for IPv6 router advertisements. Others may use dhcpd for both IPv4 and IPv6. The trouble with many of these tutorials is that they tend to have hard coded, static IPv6 addresses. I found the best solution was to use dnsmasq which provides a DHCP server, IPv6 router advertisements and even local DNS.

The following is my /etc/dnsmasq.conf. Change if1 to be your WAN interface, brlan to be your LAN interface or bridge, and setup local to whatever you want your internal DNS name to be. dnsmasq will intercept DNS requests to this special domain, allowing you to reach devices by their advertised hostname. The bottom of this file also contains examples for statically assigning IPs and hostnames to certain machines based on their hardware MAC addresses6. Finally, we use no-resolv and server to forward DNS requests to an unbound server running on port 5252.

# Wan Interface
except-interface=if1

bogus-priv

enable-ra

dhcp-range=tag:brlan,::1,constructor:brlan, ra-names, slaac, 12h
local=/penguin.internal/
expand-hosts
domain=penguin.internal
dhcp-range=lan,10.10.10.50,10.10.10.100,12h
dhcp-authoritative

dhcp-host=aa:bb:cc:dd:ee:01,10.10.10.10,fileserver
dhcp-host=aa:bb:cc:dd:ee:05,10.10.10.20,gaming
dhcp-host=aa:bb:cc:dd:ee:10,10.10.10.30,laptop

no-resolv
server=127.0.0.1#5252

I use unbound for DNS. By default, my ISP’s DNS servers do terrible things like redirecting all requests to their portal page and insisting you install their mobile app before using the Internet. They likely redirect non-existent domains to their search page as well. So I ignore them and run my own. Since dnsmasq also runs a local DNS server, we’ll run unbound on port 5252 and let dnsmasq forward requests there. The following is my /etc/unbound.conf, but you can use PowerDNS Recursor, BIND , a DNS-over-HTTPS proxy, or any DNS server you want.

server:
  use-syslog: yes
  port:5252

If you decide to use your ISPs DNS servers, you’ll need to remove domain_name_servers from dhcpcd.conf, as well as no-resolv and server=127.0.0.1#5252 from the dnsmasq.conf.

Firewall Rules

We’ll need some firewall rules for IPv4 NAT and security, as well as for allowing/blocking IPv6 traffic. Here is an example /etc/iptables/iptables.rules for IPv4 traffic. It blocks all incoming traffic, allows established outbound connections, and forwards appropriately to the internal network with NAT masquerading for outbound connections.

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i brlan -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -s 10.10.10.0/24 -i brlan -j ACCEPT
-A FORWARD -d 10.10.10.0/24 -i if1 -j ACCEPT
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o if1 -j MASQUERADE
COMMIT

The rules for IPv6 are fairly straightforward as well. Remember, unlike IPv4, all your machines are getting public IPv6 addresses. So firewalls at this level are essential at keeping your private network secure. For IPv6 router advertisements to work correctly, you must allow udp/5467, or else your WAN interface will not be able to get an IPv6 address assignment.

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -j ACCEPT -m state --state RELATED,ESTABLISHED
-A INPUT -m conntrack --ctstate NEW -m udp -p udp --dport 546 -d fe80::/64 -j ACCEPT
-A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -j ACCEPT -i brlan
COMMIT

If you’re trying to diagnose connectivity issues, you can also add the following lines to allow ICMP (ping) to IPv6 addresses within your internal network.

-A INPUT -j ACCEPT -p icmpv6
-A FORWARD -p ipv6-icmp -j ACCEPT
-A OUTPUT -p ipv6-icmp -j ACCEPT

Finally, you’ll need to make sure the kernel has forwarding and router advertisements enabled for the appropriate interfaces. The following is an example /etc/sysctl.conf:

net.ipv6.conf.all.forwarding=1
net.ipv6.conf.if1.accept_ra=2
net.ipv4.ip_forward=1

Fitting it Together

This tutorial assumes you have experience with Linux services, and know how to start or reload all the services we’ve configured, as well as being able to check the logs to ensure everything starts up. If everything is setup correctly, you should be able to run ip -br -c a and see a public IPv4 address on your WAN interface, a private address on your LAN interface, and public IPv6 addresses on both interfaces.

Screenshot of terminal displaying public IPv4 and IPv6 addresses assigned to adapters

Your other devices should now be getting globally scoped IPv6 addresses as well.

Windows 10 ipconfig with IPv6 Addresses
Windows 10 ipconfig with IPv6 Addresses

Closing Thoughts

I’ve been running custom Linux routers for nearly twenty years, however they’ve always been IPv4 only. I’ve only worked at one company that implemented IPv6 on their corporate network, and this is only the second residential ISP I’ve had with IPv6 support. The previous was AT&T, which locked network authentication to their proprietary routers (there were workarounds, but I moved out of their service area before I could seriously look into it).

It’s not a trivial task to implement dual-stack IPv4/IPv6. I’ve taken the time to configure IPv6 on all my self-hosted infrastructure. IPv6 adoption is growing, mostly in cellphone networks, but also in residential service providers. A lot of existing router software, both commercial and opensource, does make configuring IPv6 easier (if not trivial). However, setting up a router from the command line in Linux, without the fancy web interfaces, is a great way to learn about how networking and routing works at a fairly low level. It can also help us understand the complexities around IPv6, and why global uptake of the protocol has taken as long as it has.

  1. Masquerading Made Simple HOWTO. 21 July 2004. Tapsell. TLDP. 

  2. Disable consistent network device naming in RHEL7. 11 June 2014. Baird. Red Hat Customer Portal. Retrieved 8 October 2021. 

  3. Using dnsmasq on a Linux router for DHCPv6. 20 December 2014. Hveem. 

  4. dhcpcd.conf(5). Retrieved 8 October 2021. Arch Manual pages. 

  5. Setting up IPv6 using a DHCP client. 21 October 2016. K3a. 

  6. DHCP static IP addresses with dnsmasq. 3 September 2008. Bokma. 

  7. What are the essential iptables rules for IPv6 to work properly?. 1 July 2018. Pierre. UNIX Stack Exchange.