gyptazy

DevOps

Developer

IT Consultant

gyptazy

DevOps

Developer

IT Consultant

Blog Post

HowTo: Create a (public) DNS64 & NAT64 Gateway

HowTo: Create a (public) DNS64 & NAT64 Gateway

As part of my BoxyBSD project, which is designed to operate on IPv6 only network connectivity, I recently implemented and provided DNS64 and NAT64 gateway support to bridge the gap between IPv6 and the legacy IPv4 world. This solution ensures that users can easily access important resources, like GitHub, which – even in 2025 – remains accessible only over the IPv4 protocol.

The DNS64 component translates IPv4 DNS responses into IPv6 compatible records, while NAT64 handles the actual network translation, enabling smooth communication between IPv6 only devices and IPv4 services. Together, these technologies allow BoxyBSD VPS users to interact with the IPv4 internet without needing dual-stack configurations or other workarounds, such like having a dedicated NAT interface IP assigned. This approach makes life simpler for users and aligns with the project’s vision of delivering a streamlined, future-ready environment. By providing this functionality, I aim to reduce friction in the transition to IPv6 while still supporting access to legacy IPv4 resources that are critical for development, collaboration, and everyday use. On social media I got asked to write a HowTo and to describe how something like this can be created – and it’s pretty easy and can quickly be done in some minutes.

Details

A DNS64 and NAT64 gateway functions as a bridge, enabling IPv6 only clients to seamlessly communicate with IPv4 servers by performing both DNS translation and network address translation. This process involves two tightly connected components: DNS64, which handles the DNS layer, and NAT64, which operates at the network layer.

The DNS64 service modifies the way DNS queries are resolved. When an IPv6-only client sends a DNS query for a domain, the DNS64 server checks if the domain has an AAAA record (the DNS record type for IPv6). If such a record exists, DNS64 behaves like any other DNS server and returns the result. However, if only an A record (the IPv4 address) is available, DNS64 synthesizes an AAAA record by embedding the IPv4 address into a predefined IPv6 prefix. This synthesized IPv6 address is then sent back to the client. From the client’s perspective, it appears as though the destination is an IPv6 service.

On the network layer, NAT64 takes over. When the client sends packets to the synthesized IPv6 address, the NAT64 gateway intercepts them. It extracts the original IPv4 address from the embedded IPv6 address, translates the IPv6 packet headers into IPv4 headers, and forwards the packet to the target IPv4 server. The response from the server, which is in IPv4, is then translated back into IPv6 by the NAT64 gateway and sent to the client. This bidirectional translation allows the IPv6-only client to communicate with an IPv4 server without being aware of the underlying conversion.

There are multiple ways to implement DNS64 and NAT64. OpenBSD, for instance, offers a native solution using its Packet Filter (PF). PF can be configured to perform NAT64 translations without requiring additional software, leveraging OpenBSD’s robust network stack. For DNS64, unbound, a versatile DNS resolver, can be configured to handle the DNS synthesis process, making it an excellent choice for environments already relying on unbound for DNS resolution.

For users who prefer other approaches or are working with platforms where native solutions aren’t feasible, dedicated tools like TAYGA (a userspace NAT64 daemon) can be used. TAYGA implements NAT64 functionality and is relatively straightforward to set up, making it a popular choice for smaller or less complex environments. In tandem with TAYGA, unbound or other DNS64-capable resolvers can handle the DNS component.

These implementations provide flexibility for different environments, ranging from lightweight userspace tools like TAYGA to robust, kernel-integrated solutions in systems like OpenBSD. Regardless of the method chosen, DNS64 and NAT64 gateways remain critical in facilitating the transition to IPv6, ensuring IPv6 only clients can still interact with the vast portions of the internet that continue to rely on IPv4.

NAT64 relies on a specific reserved address range, 64:ff9b::/96, which is defined by the IETF to facilitate communication between IPv6 only clients and IPv4 servers. This range acts as a mapping space, where the IPv4 address of the target server is embedded into the lower 32 bits of an IPv6 address. The result is a synthesized IPv6 address that represents the IPv4 server within the IPv6 world.

When an IPv6-only client needs to communicate with an IPv4 server, the DNS64 service synthesizes this special IPv6 address using the 64:ff9b::/96 prefix. For example, if the IPv4 address of a server is 192.0.2.1, the corresponding synthesized IPv6 address would be 64:ff9b::192.0.2.1 (converted into hexadecimal format for the embedded IPv4 portion). This ensures that the IPv6 only client can “see” and interact with the IPv4 server as if it were part of the IPv6 network.

On the NAT64 gateway, this reserved address range is critical for routing and translation. When a packet is sent to an address within 64:ff9b::/96, the NAT64 gateway recognizes it as a request to communicate with an IPv4 resource. It extracts the embedded IPv4 address, translates the packet’s IPv6 headers into IPv4 headers, and forwards it to the appropriate IPv4 destination. Responses from the IPv4 server are translated back into IPv6 and sent to the client, completing the communication cycle.

Using the 64:ff9b::/96 range provides a standardized way to handle this translation without requiring additional custom addressing schemes. It ensures compatibility across implementations and simplifies the configuration of NAT64 systems. This reserved prefix is essential for bridging the gap between IPv6 and IPv4, making it a cornerstone of NAT64 functionality in environments like BoxyBSD or any IPv6-only network. However, for running a public NAT64 gateway we need to switch to a publicly routed IPv6 prefix since we do not want to host a redundant NAT64 gateway on each location rather than a redundant and centralized solution. I will cover this in the next section.

HowTo Create a DNS64 & NAT64 Gateway on Linux Debian

For BoxyBSD, the major goal was to provide a redundant and centralized solution. Therefore, I decided to stick to both mentioned solutions – based on OpenBSD and TAYGA. As requested on social medias, the TAYGA solution was requested and is covered in the next steps. This works on all major Linux systems and the required packages of unbound and TAYGA are packaged in almost all common Linux distributions. The same also applies to BSD based solutions like FreeBSD – but the commands will slightly differ from the Linux ones. For Debian based distributions you can simply follow this guide:

apt-get -y install unbound tayga

After installing the required packages we simply adjust the file containing the server directive which is unfortunately placed in /etc/unbound/unbound.conf.d/root-auto-trust-anchor-file.conf. Of course, it’s up to you change the folder / file / drop-in directory setup, but for a quick setup we will keep this and edit the following file: /etc/unbound/unbound.conf.d/root-auto-trust-anchor-file.conf

The most common setups are mostly for internal usage and this is where the 64:ff9b::/96 prefix kicks in but in this setup, we want to provide a public reachable NAT64 gateway which requires us to use a public prefix. We can also define the access controls to limit the source ranges who can or may use this service.

Overview

Creating a public facing service means that we’re required to have a public facing IPv6 address for the server itself, as well as a public IPv6 subnet for address translations. In this example, client will use the public IPv6 2a01:d0:b0aa::b00b in order to use this service. Adress translation will be done within the public DNS64 prefix network 2a01:d0:b0aa:666::/96. To allow everyone to use this service, the access control will be set to allow ::0/0. I will also provide an example how to restrict this to certain IPv6 subnets. In most cases, you probably do not want to run a public service and limit this to your personal resources.

  • Public IPv6: 2a01:d0:b0aa::b00b
  • Public DNS64 Prefix: 2a01:d0:b0aa:666::/96
  • Access-Control: ::0/0

Example: Public

This example shows how to create a public DNS64 service (also required for NAT64 gateway), which can be used from anyone on the internet without any further restrictions.

server:
   module-config: "dns64 validator iterator"
   interface: 2a01:d0:b0aa::b00b
   access-control: ::0/0 allow
   dns64-prefix: 2a01:d0:b0aa:666::/96

Example: Restricted

In this example we will generally block all sources (::0/0) and then explicitly allow the IPv6 source subnets 2a14:a0:1337::/48 and 2a14:a1:1337::/48 for the DNS64 service.

server:
   module-config: "dns64 validator iterator"
   interface: 2a01:d0:b0aa::b00b
   access-control: ::0/0 refuse
   access-control: 2a14:a0:1337::/48 allow
   access-control: 2a14:a1:1337::/48 allow
   dns64-prefix: 2a01:d0:b0aa:666::/96

Note: If you want to run simply an internal DNS64 & NAT64 service you can simply use the reserved dns64-prefix 64:ff9b::/96.

While the DNS64 part is now already configured, we need to switch over to the NAT64 part. As initially written, we will use TAYGA for this service and this configuration is also pretty simple. While using NAT, we do not really need to take care about the IPv4 address range – just make sure that this RFC1918 address range is not already in use in your local setup – if it is, simply switch to any other subnet. We can now simply replace the content of /etc/tayga.conf with the following one:

tun-device nat64
ipv4-addr 192.168.255.1
ipv6-addr 2a01:d0:b0aa::b00b
prefix 2a01:d0:b0aa:666::/96
dynamic-pool 192.168.255.0/24
data-dir /var/spool/tayga
# Use these options if you only want
# to run an internal service which is
# not public reachable.
#ipv6-addr fd67:c166:c737:ea24::1
#prefix 64:ff9b::/96

Note: Make sure to align the prefix and ipv6-addr parameters with your unbound configuration which we configured previously. If you’re running only an internal service, you may switch to the reserved 64:ff9b::/96 subnet. Simply uncomment the last lines and delete the duplicated ones.

Services

Now, let’s ensure that the service will be started when rebooting and start the services now:

systemctl enable unbound
systemctl enable tayga
systemctl start unbound
systemctl start tayga

Test the Service

Finally, we can test this service and DNS64 can easily be tested. While GitHub does not have any AAAA records, we simply query for such a record from our newly created DNS64 host by running the following command:

dig +short -t AAAA github.com @2a01:d0:b0aa::b00b
2a01:d0:b0aa:666::8c52:7903

To use the DNS64 and NAT64 service, we only need to define the IPv6 address of this service as a resolver on clients – that’s it!

Public DNS64 & NAT64 Gateways

I provide public DNS64 and NAT64 gateways primarily for my BoxyBSD project, although it is also available for use by others. These services are hosted on two systems located in Frankfurt, Germany, and Kiev, Ukraine. For more details on setup, usage, and support, please refer to the BoxyBSD FAQ.

Overview

  • 2a13:e3c1:1337::b00b (DE, Frankfurt: de-gyptazy-01)
  • 2a01:d0:b0aa::b00b (UA, Kiev: ua-gyptazy-01)
Taggs: