What is NAT64 / DNS64 and how do they work? (+ why use them / how)

What is NAT64 and DNS64?

Diagram showing how NAT64 and DNS64 work together


NAT64 (short for Network Address Translation IPv6 to IPv4) is a backwards compatibility technique to allow IPv6-only clients to access IPv4-only servers.

Encoding an IPv4 address into an IPv6 address

The first thing needed to be able to access IPv4 addresses from an IPv6-only host, is a way of encoding IPv4 addresses inside of IPv6 addresses.

This is possible because IPv6 addresses are 128-bit, while IPv4 addresses are only 32-bit, thus the entire IPv4 address space can fit into a tiny /96 of IPv6 (most server hosts give you a /64 of IPv6 included with your VPS/Dedi, enough to fit the entire IPv4 address space over 4 million times).

Unlike IPv4 (which uses octal integers - 0 to 255), IPv6 uses hexadecimal - NAT64 works around this by encoding each 8-bit octet (one of the 4 segments in an IPv4 address) into 2x 4-bit hexadecimal characters.

Let's use the following IPv4 address as an example. We need to convert each octet of the address into hexadecimal.

We'll use the Python programming language REPL to convert each octet into hexadecimal:

>>> hex(185)
>>> hex(130)
>>> hex(44)
>>> hex(9)

We ignore the leading 0x. You may notice the last octet (9) resulted in just a single hex character 9 - so we need to prepend 0 to make it into 2 characters, i.e. 09.

If we put them all together, we get b9822c09 - but each "chunk" / "segment" of an IPv6 address only holds 4 hex characters, so we split it across the last two 16-bit chunks in an IPv6 address: ::b982:2c09

Finally, we need to prepend a prefix to the hex-encoded IP address, to make it into a valid IPv6 address. Most networks use the well-known prefix of 64:ff9b::/96 - so we prepend 64:ff9b:: to the hex-encoded address:


Now we have a valid NAT64 address!

NOTE: Some networks may use a custom prefix, especially if they don't have full control over their network's routing, e.g. 2a07:e01:ffff:ff::/96 - but using a custom prefix means the systems on that network would need to use the operator's DNS64 server, as public DNS64 servers use the well-known prefix.

Running a NAT64 server (e.g. Tayga)

For NAT64 to work, you need a NAT64 server to proxy IPv6 NAT64 addresses to real IPv4 addresses. Some modern datacenter routers may have their own NAT64 service built-in, which may be easier to setup / use than a Linux NAT64 server such as Tayga.

The most commonly used NAT64 gateway server is Tayga, a NAT64 service which runs on Linux.

NOTE: We use Tayga at Privex for our NAT64.

A NAT64 server acts as a gateway for the NAT64 prefix (e.g. 64:ff9b::/96) - it acts as a middleman between an IPv6-only client, and an IPv4-only destination server, translating NAT64 traffic from the IPv6 client over to the real IPv4 address, as well as traffic sent back from the IPv4 address to the IPv6 address while the connection is open.

How to install Tayga on Ubuntu/Debian Linux for NAT64

We recommend using either Ubuntu Server, or Debian for running Tayga. The following instructions are intended for Ubuntu Server, they should work on other Debian-based distros, but we can't guarantee that.

First you'll need a server running Linux (Ubuntu Server LTS recommended), which has both an IPv6 address (can be internal/private), and a public IPv4 address.

In the examples, we'll be using the IPv6 address 2a07:e01:ffff::2 - which is the IPv6 address for our NAT64 server. Simply replace that address, with the appropriate IPv6 address for your server.

Tayga can generally be found in the standard package repos, so simply install it from apt:

apt update
apt install -y tayga

If you haven't already enabled IP forwarding for both IPv4 and IPv6, please do so now.

To enable forwarding immediately on your running system:

sysctl -w net.ipv6.conf.all.forwarding=1
sysctl -w net.ipv4.conf.all.forwarding=1

For permanence, add the following two lines to the new file /etc/sysctl.d/99_custom (or /etc/sysctl.conf if sysctl.d does not exist):


Now configure Tayga. Below are example configurations, please adjust them as required - make sure to replace the IPv6 address 2a07:e01:ffff::2 with the actual IPv6 address of your NAT64 server:


tun-device nat64

# Tayga's IPv4 address (doesn't really matter)

# Tayga's IPv6 address (to be routed to)
ipv6-addr 2a07:e01:ffff::2

# V6 Prefix to use for the IPv4 internet
prefix 64:ff9b::/96

data-dir /var/spool/tayga


# Defaults for tayga initscript
# sourced by /etc/init.d/tayga
# installed at /etc/default/tayga by the maintainer scripts

# Change this to "yes" to enable tayga

# Configure interface and set the routes up

# Configure NAT44 for the private IPv4 range

# Additional options that are passed to the Daemon.

# IPv4 address to assign to the NAT64 tunnel device

# IPv6 address to assign to the NAT64 tunnel device

Once finished configuring Tayga, you may enable the service and start it:

systemctl enable tayga
systemctl restart tayga

Note that you generally can't connect to NAT64 addresses from the NAT64 server itself, so to test Tayga, you'll need to either:

  1. Add a static route on a separate server (preferably on the same network) for 64:ff9b::/96 to Tayga's IPv6 address, e.g.

    ip -6 route add 64:ff9b::/96 via 2a07:e01:ffff::2

    Then you can test NAT64 from that separate server using mtr/ping - you can use IPv6-wrapped IPv4 syntax for convenience, e.g.

    ping6 64:ff9b::
    mtr -bz6 64:ff9b::
    ping6 64:ff9b::
    mtr -bz6 64:ff9b::
    ping6 64:ff9b::
    mtr -bz6 64:ff9b::
  2. If you operate the network, and have a prosumer/business/datacenter class switch/router which supports static routes, then you should add a static route on your core router / switch, which routes 64:ff9b::/96 to your NAT64 server's IPv6 address (e.g. 2a07:e01:ffff::2).

    For example, on a Cisco or Arista router/switch, you'd run the following command (replace 2a07:e01:ffff::2 with your NAT64 server IPv6):

    ipv6 route 64:ff9b::/96 2a07:e01:ffff::2 name TAYGA-NAT64

Once you've done either #1 or #2 - you can test NAT64 from a separate server using mtr/ping - you can use IPv6-wrapped IPv4 syntax for convenience, e.g.

ping6 64:ff9b::
mtr -bz6 64:ff9b::

ping6 64:ff9b::
mtr -bz6 64:ff9b::

ping6 64:ff9b::
mtr -bz6 64:ff9b::