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 :

地址转换公式如下:

# 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 结语

这个解决方案需要进一步考虑网络安全、路由指向、冗余性和性能问题。