Self-Hosted NAT64: Native IPv6 Access to IPv4

Preparations

You will need:

  • A dual-stack VPS (with both IPv4 and IPv6)
  • Root access
  • Knowledge of systemd/openrc
  • A Linux system installed (examples for Alpine, Debian/Ubuntu, and RHEL/CentOS are below)
  1. Find your IPv6 subnet
    ip -6 route
    You will see something like this: 2001:db8:1234:5678::/64 dev eth0 proto kernel metric 256 pref medium

This shows your /64 subnet and network interface. Take note of it, you'll need it later!

  1. Choose a /96 prefix for IPv4 mapping
    Choose a 'lucky' /96 prefix from your subnet to be used for mapping IPv4 addresses. If your subnet is 2001:db8:1234:5678::/64, you could use 2001:db8:1234:5678:ff9b::/96. This format uses 96 bits as the IPv6 prefix and 32 bits to embed the IPv4 address.
  2. Add a local route
ip -6 route add local 2001:db8:1234:5678:ff9b::/96 dev lo
  1. Set up NDP proxy
    The purpose of an NDP proxy is to allow your server to respond to NDP and RA requests for the NAT64 prefix.

Alpine:

# Install npd6
apk add npd6

# Configure npd6
cat > /etc/npd6.conf <<EOF
prefix=2001:db8:1234:5678:ff9b::  # Your chosen prefix
interface=eth0
ralogging=off
listtype=none
listlogging=off
collectTargets=100
linkOption=false
ignoreLocal=true
routerNA=true
maxHops=255
pollErrorLimit=20
EOF

# Start the service
rc-update add npd6
service npd6 start

Debian/Ubuntu:

# Install ndppd
apt install ndppd

# Configure ndppd
cat > /etc/ndppd.conf <<EOF
proxy eth0 {
  rule 2001:db8:1234:5678:ff9b::/96 {
    static
  }
}
EOF

# Start the service
systemctl enable ndppd
systemctl start ndppd

RHEL/CentOS:

# Install ndppd
dnf install ndppd

# Configuration is the same as Debian
# Then start the service
systemctl enable ndppd
systemctl start ndppd

Remember to replace 2001:db8:1234:5678:ff9b:: and eth0 with your own actual values!

  1. Test the local route settings
ping -6 2001:db8:1234:5678:ff9b::1.1.1.1

If you run into issues, check that your firewall is on and ICMPv6 is allowed.

Note: Some cloud providers have traffic restrictions. For example, Oracle Cloud filters traffic for IPv6 addresses not bound to a VNIC. Oracle Cloud sucks.

  1. Download the NAT64 server:
curl -fsSL -o nat64 https://github.com/ysshz-ns/nat64/releases/download/v1.0.0/nat64
chmod +x nat64

# If on Alpine, run apk add libc6-compat

If accessing from inside the (G-F-W) to the outside, you might need to enable tcp-brutal acceleration. My github has more detailed instructions.

Then configure iptables to redirect traffic:

ip6tables -t mangle -A PREROUTING -d 2001:db8:1234:5678:ff9b::/96 -p tcp -j TPROXY --on-port=8888 --on-ip=::1

Copy

Run the server:

./nat64
  1. Set up DNS64
    You can choose two DNS64 deployment modes

Server-side DNS64: You can run DNS64 and NAT64 on the dual-stack server. Not recommended, as it's slower.

Client-side DNS64: Run DNS64 on your IPv6-only client machine, and only run NAT64 on the dual-stack server. This method is generally better because it provides:

  • Faster DNS resolution (local DNS queries)
  • Full control over traffic that goes through NAT64
  • The ability to selectively send only certain domains through NAT64

You need to install and configure Unbound on the v6 VPS, pointing to your NAT64 server's prefix.

# Debian/Ubuntu
apt install unbound

# Alpine
apk add unbound

# RHEL/CentOS
dnf install unbound

Configure DNS64:

cat > /etc/unbound/unbound.conf.d/dns64.conf <<EOF
server:
  module-config: "dns64 iterator"
  dns64-prefix: 2001:db8:1234:5678:ff9b::/96
  do-not-query-localhost: yes

forward-zone:
  name: "."
  forward-addr: 1.1.1.1@53
  forward-addr: 8.8.8.8@53
EOF

Replace 2001:db8:1234:5678:ff9b::/96 with your actual NAT64 prefix, and adjust forward-addr to your DNS servers.

Restart Unbound and configure DNS:

systemctl restart unbound
echo "nameserver ::1" > /etc/resolv.conf
  1. Run NAT64 as a service
    For systemd systems:
cat > /etc/systemd/system/nat64.service <<EOF
[Unit]
Description=NAT64 Server
After=network.target

[Service]
ExecStart=/path/to/nat64 -p 8888
Restart=always

[Install]
WantedBy=multi-user.target
EOF

systemctl enable nat64
systemctl start nat64

For Alpine using OpenRC:

cat > /etc/init.d/nat64 <<EOF
#!/sbin/openrc-run

command=/path/to/nat64
command_args="-p 8888"
pidfile=/run/nat64.pid

depend() {
  need net
  after network
}

start() {
  ebegin "Starting NAT64"
  start-stop-daemon --start --make-pidfile --pidfile ${pidfile} --background --exec ${command} -- ${command_args}
  eend $?
}
EOF

chmod +x /etc/init.d/nat64
rc-update add nat64 default
service nat64 start
  1. Create a startup script so your settings persist after a reboot
    Alpine:
cat > /etc/local.d/nat64-setup.start <<EOF
#!/bin/sh

ip -6 route add local 2001:db8:1234:5678:ff9b::/96 dev lo
ip6tables -t mangle -A PREROUTING -d 2001:db8:1234:5678:ff9b::/96 -p tcp -j TPROXY --on-port=8888 --on-ip=::1
EOF

chmod +x /etc/local.d/nat64-setup.start

Debian/Ubuntu:

cat > /etc/network/if-up.d/nat64-setup <<EOF
#!/bin/sh

ip -6 route add local 2001:db8:1234:5678:ff9b::/96 dev lo
ip6tables -t mangle -A PREROUTING -d 2001:db8:1234:5678:ff9b::/96 -p tcp -j TPROXY --on-port=8888 --on-ip=::1
EOF

chmod +x /etc/network/if-up.d/nat64-setup

Test

Visit http://test.ustc.edu.cn, if you can access it, your NAT64 setup is successful

Comments