Engineering blog from Studio Drydock.

DNS64/NAT64 Raspberry Pi WiFi

First published 14 April 2017

A Raspberry Pi is set up as an IPv6-only wireless hotspot supporting DNS64/NAT64 for accessing the IPv4 Internet.  This is ideal for testing iOS apps for Apple’s IPv6 compliance.


Why not use the MacOS NAT64 option to create a wireless hotspot?  As Apple’s documentation suggests (and is confirmed via experimentation), this DNS64 server always synthesises NAT64 addresses, and the hotspot is unable to pass-through non-NAT64 IPv6 addresses.  This means that devices attached to the hotspot exist in an IPv6-only network, but are unable to connect to the IPv6 Internet.

In order to have complete confidence that our application works in a NAT64 environment, we need to create our own WiFi access point that:


The hardware is a Raspberry Pi 3 Model B.  This model was selected as it has both wired and wireless networking.  Any Linux box with two network interfaces (at least one of them wireless) will work.  The OS is Raspian Lite (a Debian-like Linux).  The interfaces are:

The Pi is connected via ethernet to the Internet router, which provides a dual-stack IPv4/IPv6 connection.  Most ISPs do not support this (I use Internode, who have this set up enabled by default and for no charge), and so a separate IPv6 tunnel would be required.

We then use:

For debugging:

Getting everything working took about 10 hours of work over a week, as I came in with virtually no networking or IPv6 experience, and lacked this guide 🙂

IP Forwarding

Raspian is not configured as a router out of the box.  Edit **/etc/sysctl.conf **and enable:


Apply these settings immediately (or reboot):

sudo sysctl --system

IPv6 subnet

We will set up the wireless interface on wlan0 with a private IPv6 subnet of fec0::/64, with the interface itself having address fec0::1.

allow-hotplug wlan0
iface wlan0 inet6 static
address fec0::1
netmask 64

Apply these settings immediately (or reboot):

sudo ifdown wlan0
sudo ifup wlan0


First step is to get the Pi advertising its own SSID.  This just involves installing and configuring hostapd.  I used this guide as a starting point:

Enable hostapd by editing /etc/default/hostapd:


And configure in /etc/hostapd/hostapd.conf.  Most of the default values are fine, take note of:

ssid=pi64 # The name of the wireless network to advertise
wpa_passphrase=password # Invent a password for wireless here

Start hostapd with:

sudo service hostapd start

Once this is running we will see the desired SSID being advertised, however it will not yet be possible to connect successfully.


The next goal is to run an IPv6 network on the Pi’s wireless.  dnsmasq is the DHCP-equivalent for IPv6.  I used this guide:

/etc/dnsmasq.conf is configured to advertise the IPv6 subnet already set up on the interface wlan0, and to instruct clients to use the Google DNS64 service.

The slaac option instructs clients to assign themselves an IPv6 address within the fec0::/64 subnet based on their MAC address:

# DNS server (Google DNS64)
# Local DNS name

Start dnsmasq with:

sudo service dnsmasq start

Once this is running a computer attaching to pi64 will receive two IPv6 addresses (a link-local address beginning with fe80: and a global address beginning with 2001:), and will be able to ping IPv6 addresses and even access IPv6-capable servers such as

Even though we have our DNS64 server dishing out NAT64 addresses (thanks to Google’s public DNS64 server), we need the Pi to proxy traffic to these addresses to the IPv4 Internet, otherwise they will be unreachable.


tayga is a user-mode network device that listens to IPv6 addresses with the NAT64 prefix, forwards them to an IPv4 private address, and routes the response back again.

First, set up /etc/tayga.conf:

tun-device nat64
ipv6-addr # a made-up global IPv6 address within your subnet
prefix 64:ff9b::/96

Explanation of the above:

tayga does not actually manage the setup of the nat64 network device itself; it assumes you have created and brought up the link, assigned addresses, set up routes and NAT masquerading for the IPv4 subnet.

Thankfully the Debian package manages all of this, however we need to set up /etc/default/tayga:

IPV6_TUN_ADDR=# Your Pi's global IPv6 address

I found the tayga documentation, and especially the comments within this defaults file, to be extremely confusing.  My explanation:

A persistent problem I ran into while trying to get this working is that with the routes incorrectly configured, pings would be sent successfully, but the response would not be routed all the way back.

Start tayga with:

sudo service tayga start

At this point you can ping a NAT64 address:

ping6 64:ff9b::

And once that’s working, any device connecting to the Pi’s wireless will have full access to the Internet, via IPv6 and NAT64.


Log files to watch:

dmesg (this is a program, not a log file)

Trace all ICMP (i.e., “ping”) activity on all network interfaces:

tcpdump -i any icmp -n

Display network interface configuration:

Show IPv4/IPv6 network addresses on all interfaces
ip addr
ip -6 addr
Show IPv4/IPv6 routing rules.  Note they are applied top-to-bottom.
ip route
ip -6 route
# Show IPv4/IPv6 filtering (I just left everything as ACCEPT
# without any filtering rules)
iptables -L -v
ip6tables -L -v
# Show IPv4 NAT filtering
iptables -L -v -t nat

Final Configuration

Here is my final configuration (with global IPv6 address and MAC addresses obfuscated for privacy):

ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
 inet scope host lo
 valid_lft forever preferred_lft forever
 inet6 ::1/128 scope host
 valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
 link/ether xx:xx:xx:xx:xx:a2 brd ff:ff:ff:ff:ff:ff
 inet brd scope global eth0
 valid_lft forever preferred_lft forever
 inet6 2001:xxxx:xxxx:xxxx:yyyy:yyyy:yyyy:157d/64 scope global noprefixroute dynamic
 valid_lft 6293sec preferred_lft 2692sec
 inet6 fe80::xxxx/64 scope link
 valid_lft forever preferred_lft forever
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
 link/ether xx:xx:xx:xx:xx:f7 brd ff:ff:ff:ff:ff:ff
 inet6 fec0::1/64 scope site
 valid_lft forever preferred_lft forever
 inet6 fe80::xxxx/64 scope link
 valid_lft forever preferred_lft forever
8: nat64: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 500
 inet scope global nat64
 valid_lft forever preferred_lft forever
 inet6 2001:xxxx:xxxx:xxxx:yyyy:yyyy:yyyy:157d/128 scope global
 valid_lft forever preferred_lft forever

ip route

default via dev eth0 metric 202 dev eth0 proto kernel scope link src metric 202 dev nat64 scope link

ip -6 route

64:ff9b::/96 dev nat64 metric 1024
2001:xxxx:xxxx:xxxx:yyyy:yyyy:yyyy:157d dev nat64 proto kernel metric 256
2001:xxxx:xxxx:xxxx::/64 dev eth0 proto kernel metric 202 mtu 1492
fe80::/64 dev eth0 proto kernel metric 256
fe80::/64 dev wlan0 proto kernel metric 256
fec0::/64 dev wlan0 proto kernel metric 256
default via fe80::zzzz dev eth0 metric 202 mtu 1492

iptables -L -t nat

target     prot opt source               destination         
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
target     prot opt source               destination         
MASQUERADE  all  --     anywhere

The only item of interest is the MASQUERADE rule which provides NAT translation from the tayga private subnet ( to the Pi’s actual IPv4 subnet.


ping6 64:ff9b::

tcpdump -i any icmp
02:51:34.123206 IP > ICMP echo request, id 3290, seq 4, length 64
02:51:34.135922 IP > ICMP echo reply, id 3290, seq 4, length 64
02:51:34.136173 IP > ICMP echo reply, id 3290, seq 4, length 64

Note the reply being routed first to the IPv4 subnet that the Pi lives on, and then to the tayga subnet, thanks to the MASQUERADE NAT rule.