0x1 前言
过去遇到一些只有 IPv6 的线路,使用者在实际使用体验较差。因为 IPv6 在互联网中的路由较差,相比 Ipv4,仍有明显进步空间。因此时常接到期望通过低价的 IPv6 线路,走 IPv4 IP 出口的需求。
0x2 解决方案
这个需求使用 NAT64 和 DNS64 即可解决,市场上的一些硬件设备也支持相关功能。本文则采用开源的解决方案:使用 Jool 部署 NAT64 网关,使用 Bind9 部署 DNA64 服务。
我们先进一步明确具体情况。客户侧只有 IPv6 地址,也就是只能访问 IPv6 地址,这时候需要 NAT64 网关做地址转换,它会使用 Linux 内核的 NAT64 实现相关功能。因为我们要转换为 IPv4 出口,也就是有状态的 NAT64。
因为是 NAT,就涉及目标地址的转换。在 IPv6 中有一段专门给 NAT64 使用的 prefix :
- 64:ff9b::/96
- https://en.wikipedia.org/wiki/NAT64
地址转换公式如下:
# IPv6 → IPv4 ipv4_address = ipv6_address[96:128] # 提取最后32位 # IPv4 → IPv6 ipv6_address = nat64_prefix + ipv4_address # 拼接前缀
在部署 NAT64 网关的时候有一点要注意:无法在网关本地进行测试,需要在外部进行。可以是同一个 IPv6 prefix,如果 prefix 不同,需要写静态路由。
当使用 IPv6 向 DNS 请求解析时,可能会返回 IPv4、IPv6 或同时返回双栈 IP 的结果。在本地有或只有 IPv6 IP 的情况下,会优先选择 IPv6 的结果进行访问。为了让客户端访问目标的 IPv4 地址,Bind9 支持将 IPv4 组合成 NAT64 的地址,然后返回给客户端。
0x3 NAT64 网关
NAT64 网关服务器要具备双栈 IP,确认 IPv4 和 IPv6 都可以访问公网:
然后调整内核:
# 开启双栈的 forwarding net.ipv4.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1
接着安装 Jool:
apt install -y jool-tools
然后创建 Jool 实例,这里为了方便管理,创建1个系统服务:
# 创建文件 nano /etc/systemd/system/jool-nat64.service # 写入内容 [Unit] Description=Jool NAT64 After=network.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/bin/jool instance add default --netfilter --pool6 64:ff9b::/96 [Install] WantedBy=multi-user.target # reload systemctl daemon-reload # 设为开机启动 systemctl enable jool-nat64.service # 检查状态 systemctl status jool-nat64.service
上面文件中的 pool6 可以依照实际情况替换为其他 prefix,但建议使用 well-known NAT64 prefix,不建议使用公网的 prefix。
完成后即可在客户机上尝试访问 IPv4 IP,比如 ping 1.1.1.1:
root@node1-nat64-d1:~# ping6 64:ff9b::1.1.1.1 PING 64:ff9b::1.1.1.1(64:ff9b::101:101) 56 data bytes 64 bytes from 64:ff9b::101:101: icmp_seq=1 ttl=53 time=3.00 ms 64 bytes from 64:ff9b::101:101: icmp_seq=2 ttl=53 time=2.82 ms 64 bytes from 64:ff9b::101:101: icmp_seq=3 ttl=53 time=2.63 ms 64 bytes from 64:ff9b::101:101: icmp_seq=4 ttl=53 time=2.79 ms 64 bytes from 64:ff9b::101:101: icmp_seq=5 ttl=53 time=3.28 ms ^C --- 64:ff9b::1.1.1.1 ping statistics --- 5 packets transmitted, 5 received, 0% packet loss, time 4007ms rtt min/avg/max/mdev = 2.627/2.903/3.277/0.221 ms root@node1-nat64-d1:~#
如果可以看到上面的内容,则继续下一步,否则请进一步排查问题。
0x4 DNS64
首先安装 Bind9,可以在网关上部署,也可以新建一台服务器部署:
apt install -y bind9
然后调整配置文件:
root@node1-nat64-p1:~# cat /etc/bind/named.conf.options options { directory "/var/cache/bind"; dnssec-validation no; listen-on-v6 { any; }; allow-query { any; }; dns64 64:ff9b::/96 { clients { any; }; mapped { !::/0; 0.0.0.0/0; }; break-dnssec yes; exclude { ::/0; }; }; forwarders { 8.8.8.8; 1.1.1.1; }; };
这里需要注意 exclude 部份,这个配置会忽略上游 DNS 解析器所有 IPv6 的响应。如何我上面提及的,IPv6 相比 IPv4 有更高的优先级。在默认情况下,当上游响应内容中包含 IPv6 时,Bind 不会进行 DNA64 的转换,而是直接返回原生的 IPv6 地址。如果你希望只有响应在目标没有 IPv6 的情况下进行 NAT64 转换,则需要删除 exclude 的部份;如果你希望所有流量都走 IPv4 出口,则保留。
具体结果如下:
# 保留 exclude root@node1-nat64-d1:~# dig +short AAAA ip.sb 64:ff9b::681a:c1f 64:ff9b::ac43:4bac 64:ff9b::681a:d1f # 移除 exclude root@node1-nat64-d1:~# dig +short AAAA ip.sb 2606:4700:20::681a:d1f 2606:4700:20::681a:c1f 2606:4700:20::ac43:4bac
最后,在客户机配置 DNS 服务器为 DNS64 服务器的 IP。然后进行测试:
root@node1-nat64-d1:~# curl -v ip.sb * Trying 64:ff9b::ac43:4bac:80... * Connected to ip.sb (64:ff9b::ac43:4bac) port 80 (#0) > GET / HTTP/1.1 > Host: ip.sb > User-Agent: curl/7.74.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Date: Thu, 21 Aug 2025 11:52:14 GMT < Content-Type: text/plain < Content-Length: 14 < Connection: keep-alive < Server: cloudflare < Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800} < Cache-Control: no-cache < Cf-Cache-Status: DYNAMIC < CF-RAY: 9729ec50ceab08e5-HKG < alt-svc: h3=":443"; ma=86400 < 206.xxx.xxx.xxx * Connection #0 to host ip.sb left intact
0x5 IPSec
在实际应用中,客户端和网关之间不一定是直连,可以考虑 IPv6 IPSec。不但可以使用硬件设备,还可以使用 strongSwan。strongSwan 配置文件如下:
root@node1-nat64-d1:~# cat /etc/ipsec.conf config setup charondebug="ike 2, cfg 2" uniqueids=no conn %default # P1 keyexchange=ikev2 ike=aes256-sha256-modp2048! ikelifetime=1440m keylife=60m rekeymargin=10m keyingtries=1 authby=secret # P2 esp=aes256-sha256-modp2048! lifetime=360m rekeyfuzz=100% rekeymargin=3m type=tunnel dpddelay=30s dpdtimeout=150s dpdaction=restart conn ipv6-tunnel left=2xxx:4a0:a:0:1::1 leftsubnet=64:FF9B::/96,2xxx:4A0:A:0:1::/128 right=%any rightsubnet=2xxx:4A0:7FA6::1:0:0/96 auto=add install_routes=yes proposals=aes256-sha256-modp2048 keyexchange=ikev2 leftfirewall=no
请留意 leftsubnet 的部份,包含 NAT64 prefix 和 1个 IPv6 地址,这个 IPv6 地址是 DNS64 的 IP。
如果使用网络设备,如我这边是 USG 防火墙,那么配置如下:
0x6 结语
这个解决方案需要进一步考虑网络安全、路由指向、冗余性和性能问题。