464XLAT 是走向 IPv6 单栈的过程中,用来让纯 IPv6 网络能够通过地址翻译访问 IPv4 网络的技术。他的工作流程是:IPv4 应用直接发送 IPv4 分组,到达 CLAT(Customer-side Translator,用户侧翻译器)进行无状态 NAT46 转换成 IPv6 分组,通过 IPv6 网络传输到达 PLAT(Provider-side Translator,运营商侧翻译器)后再进行 NAT64 转换成 IPv4 分组到达目标 IPv4 主机。
有了这个技术,IPv4 协议栈就可以在接入层上完全砍掉了,只需要有 CLAT 将 IPv4 分组送到 IPv4 网络的边界即可。CLAT 之所以叫“用户侧”,是因为他通常运行在光猫,甚至终端设备(如手机、电脑等)上。本文记录的是终端设备运行 CLAT。
2 月春节期间,我在家里的网络上设置了 NAT64 作为 PLAT,并配置了 DHCPv4 的 IPv6-Only Preferred 标记之后,DHCPv4 的地址分配骤减到 4 个,还在使用 IPv4 的只剩下智能家居、无线电台这些嵌入式设备了。

可以见到,图中 3 个平台的 IPv4 地址均为空或者 RFC 7335 指定用于 v4 转换的 192.0.0.0/29。
其实这种事情不是第一次干,早在 2019 年就尝试过一次:当时我用 tayga 在大学寝室的 OpenWrt 上配置了 NAT64,然后使用 bind9 做了 DNS64。这一套方案通过将没有 AAAA 记录的域名的 A 记录转换成 AAAA 的方式来实现 IPv4 网络的访问,也是当时的标准做法。这样操作的问题显而易见,毕竟总有应用不按照你所想的方式去查找服务器:他们使用指定的 DNS 服务器、使用 HTTPDNS,甚至使用了硬编码的 IPv4 地址。这些都是 DNS64 方案无法解决的,因此 DNS64 只能提升接入层的 IPv6 流量比例,而并不能完全抛弃接入层的 IPv4 协议栈。
所以更好的方案是,让设备去完成这个 IPv4→IPv6 的转换。那么问题来了,设备怎么知道 NAT64 可用、怎么知道使用什么前缀呢?当时 RFC 7050 提供了通过查询 ipv4only.arpa 获得 NAT64 前缀自动配置的方案,但是他依旧受到了 DNS 服务器的限制,不适合反映接入网络的真实情况,支持度也基本局限于蜂窝网络。甚至这个域名加入 IANA 的 Special-Use Domain Names 列表都是 2020 年的事情。
到了 2020 年,RFC 8781 往 Router Advertisement 中新增了一个名为 PREF64(Type 38)的选项,路由器可以在宣告默认路由、前缀等 v6 信息的同时,宣告将 IPv4 地址嵌入什么前缀就可以通过 NAT64 访问 IPv4 网络了。这才是自动发现应该有的最佳方案,毕竟 Router Advertisement 作为通常的终端设备自动发现并配置 IPv6 网络的必要手段,反应的也正是当前网络的信息。

2021 年发布的 Android、2022 年发布的 iOS 16 和 macOS 13 都支持了 PREF64 参数。同期支持的还有 RFC 8925:他往 DHCPv4 中新增了一个名为 IPv6-Only Preferred(Option 108)的选项,支持 CLAT 的设备在尝试请求 IPv4 地址时如果收到了这个选项,那么他就会主动放弃 IPv4 协议栈。(当然也可以选择直接关闭 DHCPv4,只是这样不支持 CLAT 的设备就无法访问 IPv4 了。)
2025 年秋天,Windows 发布了 CLAT 的封测,我光速报名了。这下三大厂都支持了,于是便有了文章开头的内容。当然在测试过程中,我也发现了一些问题,已经反馈回相关负责人了。
2 月 19 日,Windows Insider Preview (Canary) 的代码从 Br(溴)更新到了 Kr(氪),build number 为 29531。这个版本引入了 CLAT,只是默认没有启用。如果你家也部署了 IPv6-Only Preferred 的网络,并想体验 Windows 的 CLAT 的话,可以通过以下两个操作开启:
- 使用
netsh interface clat set global permit=enabled开启 CLAT。 - 在
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters中新增DhcpIpv6OnlyPreferred = (DWORD)1,这样 Windows 就会在 CLAT 可用时主动放弃 IPv4 协议栈。
因为 Windows 的实现还有些问题,所以暂时不太建议在自家网络之外的地方开启。等 Windows 正式版支持 CLAT 后,微软/苹果/谷歌三大商业操作系统就都有 CLAT 且 IPv6-only capable 了,接入层可以只有 IPv6 的好时代就来了!
另外再借个位置记录一些东西,是我在配置 464XLAT 过程中遇到的一些其他的坑。
首先是 Juniper。我使用 Juniper SRX 系列产品作为家里的防火墙已经有 7 年历史了,配置文件也是祖传的。为了能够把自己的 IPv6 在传出到电信之前转换成电信 DHCPv6-PD 下发的前缀,之前正常的工作的 static NAT 是这么写的:
set security nat static rule-set ctv6 from interface pp0.0
set security nat static rule-set ctv6 rule soha-home match destination-address 240e:390:REDA:CTED::/64
set security nat static rule-set ctv6 rule soha-home then static-nat prefix 2a0e:aa06:40e:beef::/64
而 IPv4 地址嵌入 IPv6 前缀也是一种 static NAT,我又加入了这样的代码:
set security nat static rule-set nat64 from zone INT-soha-home
set security nat static rule-set nat64 rule plat-soha match source-address ::/0
set security nat static rule-set nat64 rule plat-soha match destination-address 2a0e:aa06:40e:64::/96
set security nat static rule-set nat64 rule plat-soha then static-nat inet
结果 PLAT 一直不工作,打 trace 以后注意到系统说 The packet destination ip is not same as source ip version, drop it。原来之前因为偷懒,match 条件只写了 destination-address 而没写 source-address ::/0,导致他在处理跨地址簇报文的时候爆炸了。补回去以后工作正常,又顺手把 source NAT 那边的也都补上了。
然后是 Android 的坑。刚设置上 IPv6-Only Preferred 的时候,Android 手机直接掉线,显示 Wi-Fi 连接失败,而我可以确认他是在收到 DHCPv4 响应以后断开的,一开始我百思不得其解,直到发现了我手机上并没有 IPv6 的默认路由,紧接着注意到系统上设置了 accept_ra_min_lft=180。

我配置的 Router Advertisement 的最大发送间隔是 45s,而 Juniper 默认在 Router Advertisement 报文中填入的 lifetime 是最大发送间隔的 3 倍,即 135s,小于 Android 的默认 180s 限制,故不会自动产生默认路由。没有默认路由更别提上网了,所以 Android 就显示了“Wi-Fi 连接失败”。修改 lifetime 后 Android 也正常工作了。
这种行为比较违反 RFC,RFC 4861 可是说“receivers should handle any value”,只要 >0 就应当创建默认路由的。但 Android 这么设计是为了减少收到 RA 时拉起系统处理的电量消耗,变更发生于 2023 年 9 月。这下同样解答了我之前的另外一个疑惑,最近半年多来总是发现手机在用 IPv4 上网,而我一直以为是自己 IPv6 网太烂而回落到了 IPv4……