profile picture

A (mostly) IPv6-only home network

23 April 2021 - dns networking technology

For a while now I wanted to make my home network IPv6-only. That is a challenge, because my ISP does not enable IPv6. Therefore, I used a Hurricane Electric tunnel to give my home network IPv6. Here I will describe my setup.

Requirements

To ensure that my whole network is able to use IPv6 I deployed a Raspberry Pi 4 as a router. It is plugged into the router that I received from the ISP.

This router needs access to both IPv4 and IPv6, and is the only machine that requires this1.

Also, of course, this requires a DNS zone from the provider. In my case, I received a /48 from Hurricane Electric. Here, we will call it 2001:db8:fa12::/48.

Setup

Packages

To install the required packages, run:

apt install ufw dnsmasq dnsutils tayga

HE Tunnel

In order to set up the HE Tunnel, the ISP router needs to be configured to consider the Raspberry Pi as a DMZ, so that it will forward all unknown packets to it by default.

After that, the configuration starts with creating a network device in /etc/systemd/network/50-he-ipv6.network. This will create the actual tunnel.

[Match]
 
[NetDev]
Name=he-ipv6
Kind=sit
MTUBytes=1480
 
[Tunnel]
Local=192.168.0.101 # RPi IPv4
Remote=216.66.84.46 # HE Tunnel server
TTL=255
Independent=true

Then, we need to set up IPv6 connectivity on this device, in /etc/systemd/network/50-he-ipv6.network.

[Match]
Name=he-ipv6
 
[Network]
Address=2001:db8:1234:fedc::2/64 # Client address from HE site
Gateway=2001:db8:1234:fedc::1 # Server address from HE site.

# HE DNS resolver
DNS=2001:470:20::2

Finally, it is time to set up the eth0 interface with IPv6 as well, in /etc/system/network/10-eth0.network:

[Match]
Name=eth0

[Network]
Address=192.168.0.101/24 # My IPv4 address, to communicate with ISP router
Gateway=192.168.0.1  # Router IPv4 address

# Global address from HE /48
Address=2001:db8:fa12:100::1/56
Gateway=2001:db8:1234:fedc::1 # Same as in 50-he-ipv6.network

# HE DNS resolver
DNS=2001:470:20::2

Now, bring the links up:

ip -6 link set he-ipv6 up
ip -6 link set eth0 down
ip -6 link set eth0 up

Now there should be IPv6 connectivity. Try it out by pinging an IPv6 service: ping -6 one.one.one.one.

CoreDNS

CoreDNS is lightweight and versatile DNS server. It is used here to provide DNS64 resolving. This is used in combination with Tayga (see next section) to ensure that the IPv4 world can still be reached.

So download CoreDNS and ensure that it is installed in /usr/local/bin/coredns. Then add the unit file /etc/systemd/system/coredns.service:

[Unit]
Description=CoreDNS DNS server
Documentation=https://coredns.io
After=network.target

[Service]
LimitNOFILE=1048576
LimitNPROC=512
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true

DynamicUser=yes
StateDirectory=coredns

ExecStart=/usr/local/bin/coredns -conf=/etc/coredns/Corefile
ExecReload=/bin/kill -SIGUSR1 $MAINPID
Restart=on-failure

[Install]
WantedBy=multi-user.target

And add the configuration file at /etc/coredns/Corefile:

.:53535 {
    acl {
        allow net 192.168.62.0/24 fe80::/10 2001:db8:fa12::/48 ::1/128 2001:470:1f0a:910::2/64 127.0.0.0/8
        block
    }
    forward . 1.1.1.1
    dns64 fec0:64::/96
    log {
        class error denial
    }
    cancel 1s
}

Explanation of this configuration:

First, and very important, an access control list is configured. The acl block defines which clients may use this DNS server. This is important because without it, the DNS server is an open resolver: anyone can use it, and also attack others with it using a DNS amplification attack. This configuration is parsed in order. So here it shows that only RFC1918 (local IPv4) addresses and my delegated IPv6 addresses can use this server, all the rest is blocked.

The forward line forwards all queries (.) to the DNS server at 1.1.1.1. On the next line, dns64 fec0:64::/96 does DNS64 translation, and returns IPv6 addresses for IPv4-only services from the fec6:64::/96 range. See the Tayga section next for an explanation.

Then, we instruct CoreDNS to log error messages and access denied errors. This is very useful for debugging.

Finally, CoreDNS is instructed to fail all DNS requests that take more than one second to resolve.

Tayga

Tayga is a NAT64 service. That means that it provides connectivity to IPv4 addresses through IPv6. It does so by routing IPv6 addresses in a specific /96 range to the IPv4 internet.

A /96 range is used, because the full IPv4 address range fits in that. IPv6 has 128 bits of address space, and IPv4 has 32 bits of address space. Therefore, all of IPv4 fits in a 128 - 32 = 96 bit range, hence the /96 address range for NAT64.

Combined with CoreDNS which does the translation of IPv4 addresses into IPv6 addresses, this provides a solution to connect to IPv4 addresses from an IPv6-only network. Of course, the gateway, where Tayga is installed, must have access to both networks in order for this to work.

To set this up, configure tayga in /etc/tayga.conf:

# Route packets through the na64 deice
tun-device nat64

# Translate packets in the fec0:64::/96 address range.
# Use the first address in that range for myself.
prefix fec0:64::/96
ipv6-addr fec0:64:1::1

# This address pool is used as source addresses for IPv4
# traffic. Make sure it does not collide with an actual
# network.
# Again, use the first address for myself.
dynamic-pool 192.168.254.0/24
ipv4-addr 192.168.254.1

# Keep some translation state in this directory
data-dir /var/spool/tayga

Also ensure that a tunnel is created in /etc/systemd/network/90-nat66.netdev:

[NetDev]
Name=nat64
Kind=tap

And start tayga with systemctl enable --now tayga.service.

dnsmasq

With these changes, the router now has IPv6 connectivity and is ready to serve it to the rest of the network as well. So now it is time to make sure that IPv6 is enabled on the rest of the network as well.

For that, I use dnsmasq, a great and versatile tool for setting up DHCP and DNS in a home network.

So in order for it to give out IPv6 addresses, I configured it as follows:

interface=eth0
port=53535

# Don't check /etc/hosts, /etc/resolv.conf, etc
no-hosts
no-resolv
no-poll

# Never forward addresses in non-routed address spage
bogus-priv

# Forward queries to the local CoreDNS.
server=::1#53

strict-order

# My local-only domain is .int. Serve that also from here.
local=/int/
domain=int
address=/router.int/2001:db8:fa12:::1

# Automatically add local domain to simple host names
expand-hosts

# IPv6 DHCP
dhcp-range=::1681:ff,::1681:ffff,constructor:eth0,ra-names,12h
dhcp-option=option6:dns-server,[2001:db8:fa12:::1]
dhcp-option=option6:information-refresh-time,1h
dhcp-option=option6:domain-search,int

# Set the DHCP server to authoritative mode to 
# speed up lease acquisition.
dhcp-authoritative

enable-ra
quiet-ra

Current issues

In the end, I could not use IPv6 only the Windows machine I got from work, because the Windows Subsystem for Linux v2 does not route IPv6 connectivity to the VMs.

Credits

A lot of the credits for this setup go to Dirk Hillbrecht's amazing IPv6-first network guide.

1

In the end, that didn't fully work out, due to the Windows issue described at the end of this post.