1. 从一个真实困惑说起:为什么需要地址翻译器
很多做网络运维或者后端开发的朋友,第一次接触 IPv4 和 IPv6 共存的环境时,都会遇到一个很实际的问题:公司内网还有一大堆只认 IPv4 的老设备,但运营商给的新线路已经是 IPv6 了,两边怎么互通?更具体一点,一台只有 IPv4 地址的监控摄像头,怎么让一个纯 IPv6 的客户端访问到它?
这个问题在《TCP/IP 详解》第 2 版第 2 章"互联网地址架构"里被单独拎出来讲,放在 2.5 节"有特殊用途的地址"下面的 2.5.1 小节,标题就叫"IPv4/IPv6 寻址翻译器"。能进这个位置,说明它不是普通的过渡技术,而是一类被标准明确赋予了特殊用途的地址机制。我最初看这一节的时候也觉得抽象,直到自己在实验环境里搭了一套双栈加翻译的拓扑,才真正理解它解决的是什么问题、代价在哪里。
这篇文章我就围绕这个主题,把 IPv4/IPv6 寻址翻译器这件事讲透。包括它到底翻译的是什么、为什么不能简单地把地址做映射、几种主流翻译机制的区别、实际配置时容易踩的坑,以及我在实验里验证过的行为。适合已经了解 IPv4 和 IPv6 基本地址结构、但对两者互通机制还比较模糊的读者。如果你正在做双栈改造、或者被"为什么我的 IPv6 客户端访问不了 IPv4 服务"这类问题卡住,这篇应该能帮你理清思路。
2. 翻译器到底在翻译什么:地址、协议还是两者都翻
2.1 先分清"翻译"这个词的两种含义
很多人一听到"IPv4/IPv6 翻译器",第一反应是"把 IPv4 地址转成 IPv6 地址"。这个理解只对了一半,而且是最容易误导人的那一半。实际上翻译器要处理的是两个层面的问题,而且这两个层面经常被混为一谈。
第一个层面是地址层面的映射。IPv4 是 32 位,IPv6 是 128 位,两者长度不同,语义也不同。IPv4 地址里没有"作用域"的概念(除了私有地址段),而 IPv6 地址天然带有链路本地、唯一本地、全局单播等作用域划分。所以你不能简单地在 IPv4 地址前面补 96 个 0 就当成 IPv6 用,那样会丢掉作用域信息,也会和真正的 IPv6 地址空间冲突。
第二个层面是协议层面的转换。IPv4 和 IPv6 的报文头结构完全不同,IPv4 有首部校验和、可选字段、分片字段的布局也不一样;IPv6 去掉了首部校验和,把分片信息放到了扩展头里,还引入了流标签。翻译器如果只是改地址而不动报文头,两边的协议栈根本解析不了对方的包。
注意:地址映射和协议转换是两件必须同时做的事,但它们是独立设计的。只做地址映射不做协议转换,包发过去对方也认不出来;只做协议转换不做地址映射,转换后的包不知道往哪送。
2.2 无状态翻译和有状态翻译的分野
理解了上面两个层面,就能理解为什么翻译器会分成两大流派:无状态和有状态。
无状态翻译的核心思路是:给定一个 IPv4 地址,通过一个固定的算法就能算出对应的 IPv6 地址,反过来也一样。这个算法不需要记录任何连接状态,所以叫无状态。它的典型代表就是后来标准化的地址嵌入方案,把 IPv4 地址嵌进 IPv6 地址的特定位置,形成一个可逆的映射。这种方案的好处是简单、可扩展,翻译器不需要维护会话表;坏处是它要求 IPv4 地址和 IPv6 地址之间存在一种"可计算"的对应关系,而且这种映射会占用 IPv6 地址空间。
有状态翻译则相反,它维护一张会话表,记录每个连接的两端地址和端口。当第一个包经过时,翻译器分配一个临时的 IPv6 地址(通常从某个地址池里取),建立映射关系,后续的包都按这张表来转。这种方案的好处是不需要占用固定的 IPv6 地址空间,而且可以做端口级的复用;坏处是它引入了状态,翻译器成了单点,一旦重启或者表项超时,正在进行的连接就会断。
我在实验里对比过这两种方案的行为差异,最直观的感受是:无状态翻译在抓包时看到的地址是可预测的,你能一眼看出这个 IPv6 地址对应哪个 IPv4 地址;有状态翻译看到的地址是翻译器临时分配的,不查表根本不知道对面是谁。这个差异在排错时影响很大。
2.3 为什么这一节被归到"有特殊用途的地址"
回到书里的章节安排。2.5 节讲的是"有特殊用途的地址",2.5.1 讲翻译器。这说明翻译器用到的地址不是普通的单播地址,而是被标准赋予了特殊语义的地址段。
具体来说,无状态翻译会定义一个特定的 IPv6 前缀,凡是落在这个前缀里的地址,都表示"这是一个被翻译过来的 IPv4 地址"。这个前缀不是随便选的,它需要满足几个条件:不能和现有的全局单播地址冲突,要能容纳完整的 IPv4 地址空间,还要在路由层面可以被识别和转发。有状态翻译则通常会用到 IPv6 里专门保留的一段地址,用来表示"这是一个由翻译器动态分配的临时地址"。
这些地址段的特殊之处在于:它们只在翻译场景下有意义的,普通主机不应该把它们当作可路由的全局地址来用。如果你在路由器上看到一条指向这类前缀的路由,基本可以判断这个网络里存在翻译机制。这也是为什么书里要把它们放在"特殊用途"下面讲,而不是放在普通的地址分配章节里。
3. 几种主流翻译机制的实际行为对比
3.1 地址嵌入型方案的工作细节
地址嵌入型方案是最容易理解的一种。它的核心思想是:既然 IPv4 地址只有 32 位,那我就把它塞进 IPv6 地址的某一段里,剩下的位用来标识"这是一个翻译地址"。
常见的做法是把 IPv4 地址放在 IPv6 地址的最低 32 位,前面加上一个特定的前缀。比如某个标准定义的前缀是 64 位,那么一个 IPv4 地址192.0.2.1翻译成 IPv6 后,就是"前缀 + 若干 0 + 192.0.2.1"。这样翻译器收到一个目标地址落在这个前缀里的 IPv6 包时,直接取出最低 32 位,就得到了目标 IPv4 地址,然后构造一个 IPv4 包发出去。
反过来,IPv4 主机要访问 IPv6 主机时,翻译器需要把 IPv4 包转成 IPv6 包。这时候目标 IPv6 地址怎么来?通常的做法是让 IPv4 主机使用一种特殊的地址格式,或者通过 DNS 解析得到翻译后的地址。这里就涉及到 DNS 层面的配合,也就是常说的 DNS64 机制,不过那是另一个话题了。
我在实验里配置这种方案时,最需要注意的是前缀的选择。如果前缀和现有的 IPv6 路由冲突,或者前缀长度设置不当导致地址空间重叠,翻译就会出错。书里强调这类地址"有特殊用途",很大程度上就是在提醒读者:不要随便拿一个前缀就用,要按标准来。
3.2 有状态翻译的会话建立过程
有状态翻译的行为更像我们熟悉的 NAT。当一个 IPv6 主机想访问一个 IPv4 主机时,它发出的包目标地址是一个特殊的翻译地址(通常由 DNS 返回)。翻译器收到这个包后,做几件事:
第一,从自己的 IPv6 地址池里分配一个临时地址,这个地址代表"这个 IPv4 目标在 IPv6 侧的映射"。第二,在会话表里记录一条映射:临时 IPv6 地址 + 端口 对应 真实 IPv4 地址 + 端口。第三,把包转换成 IPv4 格式发出去。后续这个连接的返回包到达时,翻译器查表找到对应的临时地址,再转回 IPv6 发给发起方。
这个过程和 IPv4 NAT 非常像,区别只是跨了协议版本。我在抓包时观察到的一个细节是:有状态翻译分配的临时地址通常是从一个较大的地址池里取的,所以同一个 IPv4 目标在不同时间可能对应不同的临时 IPv6 地址。这意味着如果你在日志里看到两个不同的 IPv6 地址,它们可能其实指向同一个 IPv4 服务,不查会话表是看不出来的。
提示:有状态翻译的会话表有超时机制。如果一条连接长时间没有流量,表项会被回收,临时地址也会被释放。这时候如果原来的连接突然又有包过来,翻译器找不到表项,包就会被丢弃。这是很多"连接莫名其妙断了"问题的根源。
3.3 两种方案在排错时的表现差异
从运维角度看,这两种方案最大的区别在于可观测性。
无状态翻译的优点是地址可预测。你在两端抓包,看到的地址是有固定对应关系的,不需要额外工具就能推断出对面是谁。排错时只要确认前缀配置正确、路由可达,基本就能定位问题。缺点是它要求地址空间规划得非常规整,一旦前缀选错或者路由配错,问题会直接表现为"包发不出去",比较容易发现。
有状态翻译的优点是灵活,不需要占用固定的地址空间,而且可以做端口复用。缺点是排错困难。因为地址是动态分配的,你在日志里看到的地址和实际目标之间隔了一层会话表。如果会话表满了、超时了、或者翻译器重启了,问题表现往往是"时通时不通",非常难复现。我在实验里就遇到过翻译器内存不足导致会话表提前回收的情况,表现是长连接每隔几分钟断一次,查了很久才定位到是资源限制的问题。
下面这张表是我根据实验观察整理的对比,供参考:
| 对比维度 | 无状态翻译 | 有状态翻译 |
|---|---|---|
| 地址映射方式 | 固定算法,可逆 | 动态分配,需查表 |
| 是否维护会话状态 | 否 | 是 |
| 地址空间占用 | 需要专用前缀 | 需要地址池 |
| 排错难度 | 较低,地址可预测 | 较高,依赖会话表 |
| 典型故障表现 | 包直接丢弃 | 时通时不通 |
| 对翻译器性能要求 | 较低 | 较高,需维护表项 |
4. 配置翻译器时最容易踩的几个坑
4.1 前缀长度和地址空间重叠
这是我在实验里踩的第一个坑。当时我随手选了一个 96 位的前缀,觉得剩下的 32 位刚好放 IPv4 地址,很合理。结果配置完之后发现,有些 IPv6 主机的地址居然也落在这个前缀范围内,导致翻译器把正常的 IPv6 流量也当成需要翻译的流量处理了。
问题的根源在于:IPv6 的全局单播地址目前分配的前缀通常是 48 位或者 56 位,如果你选的翻译前缀和这些已分配的前缀有重叠,就会冲突。书里之所以强调这类地址"有特殊用途",就是在提醒读者:翻译前缀的选择必须避开现有的地址分配,通常标准会指定一个专门保留的前缀段,不要自己发明。
正确的做法是查一下当前的标准文档,确认哪个前缀段是专门留给翻译用的,然后用那个。如果实在记不住,至少要在配置前用路由表检查一遍,确认没有冲突。
4.2 DNS 返回的地址类型不匹配
第二个坑和 DNS 有关。在有状态翻译的场景里,IPv6 主机访问 IPv4 服务时,需要 DNS 返回一个翻译地址,而不是真实的 IPv4 地址。如果 DNS 配置不当,返回了 A 记录(IPv4 地址),IPv6 主机拿到这个地址后根本不知道怎么发包,因为它的协议栈只认 IPv6。
这个问题在双栈环境里特别容易混淆。双栈主机同时有 IPv4 和 IPv6 地址,它可能优先用 IPv6 发包,但如果 DNS 返回的是 A 记录,它就得回退到 IPv4。回退逻辑各家实现不一样,有的快有的慢,表现就是"有时候能通有时候不能通"。
解决办法是确保 DNS 在翻译场景下返回正确的记录类型。如果是无状态翻译,通常需要 DNS 返回一个嵌入了 IPv4 地址的 IPv6 地址(AAAA 记录);如果是有状态翻译,则需要 DNS 返回翻译器分配的临时地址。这中间涉及到 DNS 和翻译器的配合,配置时要一起考虑。
4.3 分片和 MTU 问题
第三个坑比较隐蔽,和 MTU 有关。IPv6 要求链路 MTU 至少 1280 字节,而 IPv4 的常见 MTU 是 1500 字节。当一个大包从 IPv6 侧发往 IPv4 侧时,如果翻译器不做分片处理,包可能会因为超过 IPv4 侧的 MTU 而被丢弃。
更麻烦的是,IPv6 的路由器不做分片,分片由发送方负责;而 IPv4 的路由器可以分片。这个差异导致翻译器在处理大包时,必须自己承担分片或者发送 ICMP 差错报文的责任。如果翻译器实现不完善,表现就是"小包能通,大包不通",比如 ping 能通但传文件失败。
我在实验里验证过这个现象:用默认 MTU 时,一个 2000 字节的包从 IPv6 侧发出去,翻译器直接丢弃,没有任何提示。把 MTU 调小到 1280 之后,同样的包就能通过了。所以配置翻译器时,一定要确认 MTU 设置,必要时在两端都做调整。
注意:MTU 问题在无状态翻译和有状态翻译里都存在,但有状态翻译因为要重组和重新分片,对性能影响更大。如果翻译器性能不足,大流量场景下会成为瓶颈。
5. 从实验环境看翻译器的实际部署位置
5.1 翻译器放在网络边界还是主机侧
翻译器的部署位置直接影响它的行为和适用范围。放在网络边界(比如出口路由器上)时,它通常服务于整个内网,内网主机不需要做任何特殊配置,翻译对它们透明。这种部署方式的优点是改造成本低,缺点是翻译器成为单点,性能和可靠性要求高。
放在主机侧(比如在服务器上装一个翻译模块)时,它只服务于本机,影响范围小,但每台需要翻译的主机都要单独配置。这种方式的优点是灵活,可以针对不同主机做不同策略;缺点是管理成本高,主机多了之后配置容易不一致。
我在实验里两种都试过。边界部署适合"内网整体需要和外部互通"的场景,主机侧部署适合"只有少数几台机器需要特殊处理"的场景。选择哪种,取决于你的网络规模和改造目标。
5.2 翻译器和防火墙的配合
翻译器通常和防火墙一起部署,因为翻译过程中地址会变化,防火墙规则需要相应调整。如果防火墙规则是基于原始地址写的,翻译后的包可能匹配不上,导致被误拦。
一个常见的做法是在翻译器上同时配置防火墙策略,让翻译和过滤在同一个设备上完成。这样可以避免地址变化导致的规则失效。但这也意味着翻译器的负载更重,需要更强的硬件。
我在实验里遇到过防火墙把翻译后的包当成异常流量拦截的情况。原因是翻译后的源地址落在一个防火墙不认识的网段里,默认策略是拒绝。解决办法是在防火墙上显式放行翻译器使用的地址段,或者把翻译器放在防火墙的信任区域里。
5.3 监控和日志要关注哪些指标
翻译器上线之后,监控是必不可少的。根据我的经验,至少要关注这几个指标:会话表的使用率(有状态翻译)、翻译失败的包数量、地址池的剩余量、以及翻译器的 CPU 和内存占用。
会话表使用率是最关键的。如果表项接近上限,新连接就会被拒绝,表现是"部分用户能通,部分用户不能通"。地址池剩余量在有状态翻译里也很重要,池子空了就没法分配临时地址。翻译失败的包数量能帮你发现配置问题,比如前缀不匹配或者路由不可达。
日志方面,建议记录翻译失败的包的源和目的地址,方便定位是哪类流量出了问题。但要注意日志量,翻译器在高负载下产生的日志可能非常多,需要做采样或者限流。
6. 几个容易被忽略的边界情况
6.1 回环地址和私有地址的翻译
回环地址(127.0.0.1 和 ::1)和私有地址段在翻译场景下需要特别处理。IPv4 的私有地址(比如 10.x.x.x)在 IPv6 侧没有直接对应,如果翻译器不加区分地做映射,可能会把私有地址暴露到全局路由里,造成安全问题。
正确的做法是在翻译器上配置策略,明确哪些地址段允许翻译、哪些不允许。私有地址通常只在内部使用,不应该被翻译成全局可达的 IPv6 地址。回环地址更是如此,翻译它没有任何意义,反而可能引起混乱。
我在实验里就犯过这个错误:没有限制私有地址的翻译,结果内网的 10.x.x.x 地址被映射成了全局 IPv6 地址,虽然实验环境里没造成实际影响,但如果是生产环境,这就是一个信息泄露的隐患。
6.2 多播和广播地址的处理
IPv4 有广播地址,IPv6 没有广播只有多播。这个差异导致翻译器在处理广播流量时很尴尬:IPv4 的广播包翻译成 IPv6 后,应该发给哪个多播组?标准里对这个问题有专门的规定,但实现起来各家可能不一样。
实际影响是:依赖广播发现的服务(比如某些老式的设备发现协议)在翻译场景下可能失效。如果你的网络里有这类服务,要么避免让它们跨翻译器通信,要么确认翻译器支持相应的多播映射。
6.3 翻译器自身的地址怎么配
翻译器自己也需要地址,而且通常需要同时有 IPv4 和 IPv6 地址。它的 IPv4 地址用于和 IPv4 侧通信,IPv6 地址用于和 IPv6 侧通信。这两个地址的配置方式会影响翻译行为。
一个容易忽略的点是:翻译器的 IPv6 地址不应该落在它自己负责翻译的前缀里。否则会出现递归翻译的问题,翻译器把自己的地址也当成需要翻译的目标,导致死循环或者异常。这个坑我在实验里踩过一次,表现是翻译器 CPU 跑满,抓包看到大量重复的翻译请求。
7. 我在实际环境里总结的几条经验
折腾了这么久,有几条经验我觉得值得单独拿出来说。
第一条,先确认需求再选方案。如果你的场景只是"少数 IPv6 客户端访问少数 IPv4 服务",有状态翻译可能更合适,因为它不需要规划地址空间。如果是"整个网络需要双向互通",无状态翻译的可预测性会省很多事。不要一上来就选最复杂的方案。
第二条,地址规划要提前做。翻译前缀、地址池、路由策略这些东西,一旦上线再改成本很高。我在实验里因为前缀选错,推倒重来了一次,浪费了不少时间。建议在配置前画一张地址规划图,把 IPv4 侧、IPv6 侧、翻译器用的地址段都标清楚。
第三条,MTU 和分片问题一定要测。不要只测 ping,要测大包、测长连接、测高并发。很多问题在小流量下不暴露,一上量就出来了。我习惯用大文件传输和长时间 ping 来验证稳定性。
第四条,监控和日志从第一天就要有。翻译器是网络里的关键节点,出问题影响面大。会话表、地址池、失败计数这些指标,早监控早发现。日志不用记太细,但失败原因要能追溯。
第五条,文档要写清楚。翻译器的配置往往涉及多个设备(DNS、防火墙、路由器),过几个月再来看,没有文档根本记不住当时为什么这么配。把前缀选择理由、地址池范围、策略规则都记下来,对以后的维护帮助很大。
最后说一个我自己的体会:IPv4/IPv6 翻译器这个技术,本质上是在两个不兼容的体系之间搭桥。桥能走人,但走起来不如两边都是同一种路那么顺畅。所以如果条件允许,能上双栈就上双栈,翻译器更多是过渡期的权宜之计。理解它的原理和限制,比盲目依赖它更重要。