土法炼钢兴趣小组的算法知识备份

【网络工程】IPv6 工程实践:从双栈到纯 IPv6 迁移

文章导航

分类入口
network
标签入口
#IPv6#NDP#SLAAC#dual-stack#Happy-Eyeballs#NAT64#DNS64

目录

2019 年,一个云服务商的工程师发现他们的 IPv6-only 服务器无法访问某个第三方 API。排查后发现:该 API 只有 A 记录没有 AAAA 记录,服务器没有配置 NAT64 网关,DNS64 也没有开启。修复很简单——配一个 NAT64 出口——但根因是他们在做”IPv6-only”迁移时,假设”所有服务都支持 IPv6 了”。

这个假设在 2026 年仍然不完全成立。全球约 45% 的流量走 IPv6,但长尾服务(特别是企业内部系统和小型 SaaS)的 IPv6 支持率远低于此。IPv6 迁移不是”开关切换”,而是一个需要工程策略的渐进过程。

这篇文章不会从头介绍 IPv6 的历史——你已经知道 IPv4 地址不够用了。我们直接进入工程实战:IPv6 改变了哪些网络行为?双栈和迁移的具体方案是什么?踩坑点在哪里?

一、IPv6 首部的工程改进

上一篇分析了 IPv4 首部的各种问题——可变长度、首部校验和、分片复杂性。IPv6 首部的设计有针对性地解决了这些问题。

IPv6 固定首部 40 字节,结构如下:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| Traffic Class |           Flow Label                  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Payload Length        |  Next Header  |   Hop Limit   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
+                                                               +
|                                                               |
+                         Source Address                         +
|                                                               |
+                                                               +
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
+                                                               +
|                                                               |
+                      Destination Address                      +
|                                                               |
+                                                               +
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

与 IPv4 首部的关键差异

改进 IPv4 IPv6 工程影响
首部长度 可变(20-60 字节) 固定 40 字节 路由器硬件转发更高效,不需要解析 IHL
校验和 路由器不需要每跳重新计算校验和,减少 CPU 开销
分片 路由器和端点都可以 只有源端可以 路由器不需要维护分片状态,减少攻击面
选项 首部内嵌,影响转发 扩展头链,路由器通常不需要处理 硬件 Fast Path 可以忽略大部分扩展头
广播 支持 不支持 用组播替代,避免广播风暴
ARP 独立协议 NDP(基于 ICMPv6) 地址解析集成到 ICMPv6,功能更强大

Flow Label(20 位)是 IPv6 新增的字段,用于标识属于同一”流”的包。路由器可以用 Flow Label 做 ECMP 哈希,而不需要深入到 L4 解析端口号——这对加密流量(如 IPsec ESP)特别有价值,因为 L4 信息被加密了。

# 查看 IPv6 接口信息
ip -6 addr show

# 抓包观察 IPv6 首部
tcpdump -i eth0 -nn -v ip6 -c 5
# 输出会显示 flowlabel, hlim(Hop Limit), next-header 等字段

# 查看 IPv6 路由表
ip -6 route show

扩展头链

IPv6 用扩展头(Extension Header)替代了 IPv4 的选项字段。扩展头通过 Next Header 字段链接成链表:

扩展头 Next Header 值 用途 谁需要处理
Hop-by-Hop Options 0 逐跳选项(如 Router Alert) 每个路由器
Routing 43 源路由(已限制使用) 路径上的指定路由器
Fragment 44 分片信息 目标端点
Authentication Header 51 IPsec 认证 目标端点
ESP 50 IPsec 加密 目标端点
Destination Options 60 目标选项 目标端点
TCP 6 上层协议是 TCP 目标端点
UDP 17 上层协议是 UDP 目标端点
ICMPv6 58 ICMPv6 目标端点

关键设计:除了 Hop-by-Hop Options,路由器不需要检查其他扩展头。 这意味着绝大多数 IPv6 包可以走硬件 Fast Path 转发。但有一个工程现实——很多中间设备(防火墙、IDS)仍然会解析扩展头来做安全检查,扩展头链过长或使用罕见扩展头的包可能被丢弃。

# 检测路径上是否有设备丢弃扩展头
# 发送一个带 Destination Options 扩展头的包
# 如果被丢弃,说明路径上有设备过滤扩展头
ping6 -c 3 目标IPv6地址
# 对比正常 ping 是否通

二、IPv6 地址架构

IPv6 使用 128 位地址,通常表示为 8 组 16 位十六进制数,用冒号分隔。但 IPv6 的地址体系远比”地址变长了”复杂。

地址类型

类型 前缀 范围 用途
全局单播(Global Unicast) 2000::/3 公网 类似 IPv4 的公网地址
链路本地(Link-Local) fe80::/10 单条链路 每个接口自动生成,不可路由
唯一本地(Unique Local, ULA) fc00::/7(实践中 fd00::/8) 内部网络 类似 IPv4 的 RFC 1918 私有地址
组播(Multicast) ff00::/8 取决于 scope 替代 IPv4 的广播
环回(Loopback) ::1/128 本机 类似 IPv4 的 127.0.0.1
未指定(Unspecified) ::/128 类似 IPv4 的 0.0.0.0

链路本地地址的工程重要性:每个 IPv6 接口都会自动生成一个 fe80:: 开头的链路本地地址,即使没有任何 IPv6 配置。路由协议(如 OSPFv3、BGP)通常使用链路本地地址作为邻居地址。在排查 IPv6 路由问题时,链路本地地址的连通性是第一个要检查的。

# 查看所有接口的 IPv6 地址(包括链路本地)
ip -6 addr show
# 输出示例:
# 2: eth0: <BROADCAST,MULTICAST,UP>
#     inet6 2001:db8::100/64 scope global dynamic
#     inet6 fe80::a00:27ff:fe4e:66a1/64 scope link
# scope global = 全局可路由
# scope link  = 链路本地,只在本链路有效

# 用链路本地地址 ping(需要指定出口接口)
ping6 -c 3 fe80::1%eth0
# 注意 %eth0 后缀——链路本地地址必须指定接口,
# 因为不同接口可能有相同的 fe80:: 地址

地址表示法

# IPv6 地址缩写规则:
# 1. 每组的前导零可省略:0001 → 1,00ab → ab
# 2. 连续的全零组可用 :: 替代(只能用一次)

# 完整形式
2001:0db8:0000:0000:0000:0000:0000:0001
# 缩写形式
2001:db8::1

# 工程中的常见困惑::: 在地址的不同位置
2001:db8::1        # = 2001:0db8:0:0:0:0:0:1(末尾)
2001:db8::1:0      # = 2001:0db8:0:0:0:0:1:0(不同!)
::ffff:192.168.1.1 # IPv4-mapped IPv6 地址

# 在 URL 中使用 IPv6 地址(用方括号括起来)
# http://[2001:db8::1]:8080/api
# 忘记方括号是常见的配置错误

接口标识符与隐私

IPv6 地址的后 64 位是接口标识符(Interface ID)。早期的 SLAAC 使用 MAC 地址(EUI-64)生成接口 ID,这导致了隐私问题——你的 MAC 地址暴露在每个 IPv6 包中,任何人都可以追踪你的设备。

RFC 4941 定义了隐私扩展(Privacy Extensions),生成随机的临时地址,并定期轮换:

# 检查隐私扩展是否启用
sysctl net.ipv6.conf.eth0.use_tempaddr
# 0 = 禁用
# 1 = 启用(生成临时地址,但优先使用固定地址)
# 2 = 启用(优先使用临时地址) ← 推荐客户端使用

# 查看临时地址
ip -6 addr show dev eth0 | grep temporary
# 输出示例:
# inet6 2001:db8::7a8b:3f2e:1d4c:5e6a/64 scope global temporary dynamic

# 临时地址的有效期
sysctl net.ipv6.conf.eth0.temp_valid_lft    # 默认 604800 秒(7 天)
sysctl net.ipv6.conf.eth0.temp_prefrd_lft   # 默认 86400 秒(1 天)

服务器不要启用隐私扩展。 服务器需要固定的 IPv6 地址用于 DNS 记录和防火墙规则。客户端设备应该启用。

三、NDP:IPv6 的邻居发现协议

IPv6 用 NDP(Neighbor Discovery Protocol,RFC 4861)替代了 IPv4 的 ARP 和部分 ICMP 功能。NDP 基于 ICMPv6,是 IPv6 网络正常运行的核心协议。

NDP 的五种消息类型

消息 ICMPv6 类型 类比 IPv4 功能
Router Solicitation(RS) 133 主机请求路由器通告自己
Router Advertisement(RA) 134 路由器宣告前缀、MTU、默认网关等
Neighbor Solicitation(NS) 135 ARP Request 解析 IPv6 地址到 MAC 地址
Neighbor Advertisement(NA) 136 ARP Reply 响应 NS,提供 MAC 地址
Redirect 137 ICMP Redirect 通知主机更优的下一跳
# 查看 IPv6 邻居缓存(等价于 IPv4 ARP 表)
ip -6 neigh show
# 输出示例:
# fe80::1 dev eth0 lladdr aa:bb:cc:dd:ee:ff router REACHABLE
# 2001:db8::50 dev eth0 lladdr 00:11:22:33:44:55 STALE

# 抓取 NDP 消息
tcpdump -i eth0 -nn 'icmp6 and (ip6[40] == 133 or ip6[40] == 134 or ip6[40] == 135 or ip6[40] == 136)' -c 10
# 133=RS, 134=RA, 135=NS, 136=NA

# 手动发送 Router Solicitation(触发路由器重新通告)
rdisc6 eth0
# 或者
ndisc6 -r eth0

Router Advertisement 的工程含义

RA(Router Advertisement)消息是 IPv6 网络配置的核心。路由器定期发送 RA,包含以下信息:

# 查看本链路收到的 RA 信息
rdisc6 eth0
# 输出示例:
# Hop limit        :           64
# Stateful address conf. (M): No
# Stateful other conf. (O)  : Yes    ← 用 DHCPv6 获取 DNS 等信息
# Router preference         : medium
# Router lifetime           : 1800 (00:30:00)
# Reachable time            : 30000 (00:00:30)
# Retransmit time           : 1000 (00:00:01)
# Prefix                    : 2001:db8::/64
#   Valid lifetime          : 2592000 (30 days)
#   Preferred lifetime      : 604800 (7 days)
#   On-link                 : Yes
#   Autonomous address conf.: Yes  ← 可以用 SLAAC 自动配置地址

# 监控 RA 消息(排查地址配置问题时有用)
tcpdump -i eth0 -nn -v 'icmp6 and ip6[40] == 134' -c 3

RA 安全问题:任何设备都可以发送 RA。恶意或错误配置的 RA(Rogue RA)可以劫持整个子网的默认路由。解决方案是 RA Guard(RFC 6105),在交换机上过滤非授权端口的 RA 消息。

# 检查是否有多个路由器在发送 RA(可能有 Rogue RA)
rdisc6 -m eth0
# 如果看到多个不同的 RA 源地址,需要排查

# 在 Linux 上,可以通过 sysctl 控制是否接受 RA
sysctl net.ipv6.conf.eth0.accept_ra
# 0 = 不接受
# 1 = 接受(如果不作为路由器) ← 默认
# 2 = 即使作为路由器也接受

四、SLAAC 与 DHCPv6:地址配置方式

IPv6 提供了两种自动地址配置方式:SLAAC(Stateless Address Autoconfiguration)和 DHCPv6。

SLAAC(无状态自动配置)

SLAAC 是 IPv6 的特色功能——主机无需 DHCP 服务器就能自动获取全局可路由的 IPv6 地址:

  1. 主机生成链路本地地址(fe80:: + 接口 ID)
  2. 执行 DAD(Duplicate Address Detection)确保地址唯一
  3. 发送 Router Solicitation
  4. 从 Router Advertisement 中获取前缀(如 2001:db8::/64
  5. 组合前缀 + 接口 ID 生成全局地址
sequenceDiagram
    participant H as 主机
    participant R as 路由器

    Note over H: 接口启动,生成<br>fe80:: 链路本地地址

    H->>H: DAD: NS for fe80::xxx<br>(组播,检查地址是否已被使用)

    Note over H: 无响应,地址唯一

    H->>R: Router Solicitation(RS)<br>src=fe80::xxx, dst=ff02::2

    R->>H: Router Advertisement(RA)<br>Prefix: 2001:db8::/64<br>M=0, O=1

    Note over H: 组合 2001:db8:: + 接口ID<br>生成全局地址

    H->>H: DAD: NS for 2001:db8::xxx<br>(确认全局地址唯一)

    Note over H: 配置完成<br>fe80::xxx(链路本地)<br>2001:db8::xxx(全局)

SLAAC vs DHCPv6 对比

特性 SLAAC DHCPv6
地址分配 自动生成(前缀 + 接口 ID) 服务器分配
需要服务器 只需路由器发 RA 需要 DHCPv6 服务器
地址记录/审计 困难(无中央记录) 容易(服务器有记录)
DNS 服务器 通过 RDNSS 选项(RA)或 DHCPv6 通过 DHCPv6 选项
子网掩码 始终 /64 可以分配任意前缀长度
适用场景 客户端设备、简单网络 企业网络、需要地址管理

工程建议:大多数场景使用”SLAAC + DHCPv6 for DNS”(RA 的 M=0, O=1)。服务器使用 DHCPv6 分配固定地址(或手动配置)。纯 SLAAC 在需要地址审计的企业环境中不够用,因为没有中央记录谁用了哪个地址。

# 查看当前 IPv6 地址的来源
ip -6 addr show dev eth0
# 注意 dynamic 标签(SLAAC/DHCPv6 分配的)vs 没有 dynamic(手动配置的)

# 手动配置 IPv6 地址(服务器推荐)
ip -6 addr add 2001:db8::100/64 dev eth0

# 配置 DHCPv6 客户端(以 systemd-networkd 为例)
# /etc/systemd/network/eth0.network:
# [Network]
# DHCP=ipv6
# IPv6AcceptRA=yes

DAD(重复地址检测)

无论是 SLAAC 还是手动配置,IPv6 都会执行 DAD(Duplicate Address Detection)来确保地址在链路上是唯一的。DAD 通过发送 Neighbor Solicitation 到被检测地址的 Solicited-Node 组播地址来实现。

# 查看 DAD 状态
ip -6 addr show dev eth0
# 如果地址后面有 tentative 标签,说明 DAD 正在进行
# 如果有 dadfailed,说明地址冲突

# DAD 相关内核参数
sysctl net.ipv6.conf.eth0.dad_transmits
# 默认 1,发送 1 个 NS 探测包
# 0 = 禁用 DAD(不推荐,但在某些快速启动场景下有用)

# DAD 在高密度环境中可能导致地址配置延迟
# 如果你的容器/虚拟机需要快速启动,可以考虑禁用 DAD
# 但要确保地址分配机制(如 IPAM)能保证唯一性
sysctl -w net.ipv6.conf.eth0.accept_dad=0
sysctl -w net.ipv6.conf.eth0.dad_transmits=0

/64 子网长度的工程影响

IPv6 的一个重要约定:接口标识符(Interface ID)固定占 64 位。 这意味着所有子网分配的最小粒度应该是 /64。使用小于 /64 的子网(如 /127 用于点对点链路)在技术上可行(RFC 6164),但某些功能(如 SLAAC)在非 /64 子网上不工作。

这个约定的工程含义:

  1. 一个 /48 前缀 = 65536 个 /64 子网。这是 RIPE/ARIN 分配给终端站点的典型大小
  2. 一个 /64 子网 = 1.8 × 10^19 个地址。你永远不需要担心子网内的地址耗尽
  3. 子网扫描在 IPv6 中几乎不可能。扫描一个 /64 子网的所有地址需要 500 万亿年(以每秒 100 万地址的速度)——这大幅减少了暴力扫描的攻击面
# IPv6 子网规划示例
# 假设你从 ISP 获得了 2001:db8:abcd::/48
python3 -c "
import ipaddress
prefix = ipaddress.ip_network('2001:db8:abcd::/48')
subnets = list(prefix.subnets(new_prefix=64))
print(f'总共 {len(subnets)} 个 /64 子网')
print(f'前 5 个子网:')
for s in subnets[:5]:
    print(f'  {s}')
print(f'最后一个子网: {subnets[-1]}')
"
# 输出:总共 65536 个 /64 子网

五、双栈部署的工程挑战

双栈(Dual Stack)是最常见的 IPv6 迁移策略——同时运行 IPv4 和 IPv6,让应用逐步迁移。听起来简单,实际部署中有很多坑。

常见问题

1. 应用绑定地址的问题

# 很多应用默认只监听 IPv4
ss -tlnp | grep 8080
# 如果只看到 0.0.0.0:8080 而没有 :::8080,说明只监听了 IPv4

# Linux 上 IPV6_V6ONLY socket 选项决定了 :: 是否同时接受 IPv4
sysctl net.ipv6.bindv6only
# 0 = :: 同时接受 IPv4 和 IPv6(默认) ← 大多数情况下你需要这个
# 1 = :: 只接受 IPv6

# 检查应用是否正确监听双栈
ss -tlnp | grep -E ":::.*8080|0.0.0.0:.*8080"

2. DNS 解析顺序

当一个域名同时有 A(IPv4)和 AAAA(IPv6)记录时,客户端先尝试哪个?如果 IPv6 路径有问题,用户会经历显著的连接延迟——因为系统会先尝试 IPv6,超时后才回退到 IPv4。

# 查看 DNS 解析的地址顺序
getent ahosts www.example.com
# 输出会显示 IPv6 和 IPv4 地址的优先级顺序

# 查看 /etc/gai.conf 中的地址选择策略
cat /etc/gai.conf
# precedence ::ffff:0:0/96  100   ← 如果加上这行,优先使用 IPv4
# precedence ::1/128         50
# precedence ::/0            40
# precedence 2002::/16       30
# precedence ::/96           20

3. 防火墙规则的双重维护

IPv4 用 iptables,IPv6 用 ip6tables——两套独立的规则。忘记在 ip6tables 中配置对应的规则是极其常见的安全疏忽。

# 检查 IPv6 防火墙规则
ip6tables -L -n -v

# 常见遗漏:只在 iptables 中限制了访问,ip6tables 是全开的
# 这意味着攻击者可以通过 IPv6 绕过你的防火墙规则!

# 使用 nftables 统一管理(推荐)
# nftables 可以在一个规则集中同时处理 IPv4 和 IPv6
nft list ruleset

4. 监控和日志的盲区

很多监控系统和日志分析工具是为 IPv4 设计的。IPv6 地址格式不同(128 位、十六进制、有缩写),日志解析正则表达式可能匹配不到 IPv6 地址。

# IPv6 地址的正则表达式比 IPv4 复杂得多
# IPv4: \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
# IPv6: 需要处理 :: 缩写、大小写、IPv4-mapped 等变体

# 检查 Nginx 日志中是否记录了 IPv6 访问
grep -E '\[?[0-9a-fA-F:]{3,39}\]?' /var/log/nginx/access.log | head -5

双栈迁移检查清单

在启用 IPv6 之前,按这个清单逐项检查:

# ===== 基础设施层 =====

# 1. 确认网络设备支持 IPv6
# 交换机、路由器、防火墙、负载均衡器是否都支持 IPv6?
# 有些老设备的 IPv6 性能远低于 IPv4

# 2. 确认 ISP/云服务商提供了 IPv6 前缀
# AWS VPC 默认支持双栈
# 阿里云/腾讯云需要申请 IPv6 前缀

# 3. DNS 配置
dig AAAA your-domain.com    # 确认 AAAA 记录存在
dig NS your-domain.com      # 确认 DNS 服务器支持 IPv6 查询

# ===== 应用层 =====

# 4. 检查应用是否监听了 IPv6
ss -tlnp | grep ":::"       # 如果有 ::: 开头的条目,说明监听了 IPv6

# 5. 检查配置文件中的地址格式
# Nginx: listen [::]:80;    ← 需要方括号
# Redis: bind ::1 127.0.0.1
# MySQL: bind-address = ::  ← 同时接受 IPv4 和 IPv6

# ===== 安全层 =====

# 6. 对比 IPv4 和 IPv6 防火墙规则
iptables -L -n | wc -l      # IPv4 规则数
ip6tables -L -n | wc -l     # IPv6 规则数
# 如果 IPv6 规则明显少于 IPv4,说明有遗漏

# ===== 监控层 =====

# 7. 确认监控系统支持 IPv6
# Prometheus targets 是否包含 IPv6 地址?
# 日志收集系统能否解析 IPv6 地址?
# 告警规则中是否区分了 IPv4 和 IPv6 的指标?

分阶段迁移建议

阶段 动作 风险 回退方案
1. 内部测试 在测试环境启用双栈 关闭 IPv6
2. 内部服务 内部服务启用双栈 删除 AAAA 记录
3. 非关键服务 对外非关键服务发布 AAAA 删除 AAAA 记录
4. 主要服务 主域名发布 AAAA,启用 Happy Eyeballs 删除 AAAA 记录
5. IPv6 优先 调整 DNS 权重,优先 IPv6 恢复 IPv4 优先
6. IPv6-only 关闭 IPv4(如果可能) 重新启用 IPv4

六、Happy Eyeballs:双栈连接优化

Happy Eyeballs(RFC 8305)算法解决了双栈环境中的一个关键问题:当 IPv6 路径不通或很慢时,不要让用户等待 IPv6 超时。

算法原理

  1. 同时发起 DNS A 和 AAAA 查询
  2. 优先尝试 IPv6 连接
  3. 在极短的时间内(通常 250ms),如果 IPv6 连接没有成功,同时发起 IPv4 连接
  4. 哪个先成功就用哪个,取消另一个
flowchart TD
    A["DNS 查询 A + AAAA"] --> B{"同时收到 A 和 AAAA?"}
    B -- "是" --> C["优先尝试 IPv6 连接"]
    B -- "只有 A" --> D["直接用 IPv4"]
    B -- "只有 AAAA" --> E["直接用 IPv6"]
    C --> F{"IPv6 在 250ms 内<br>连接成功?"}
    F -- "是" --> G["使用 IPv6"]
    F -- "否" --> H["同时发起 IPv4 连接"]
    H --> I{"哪个先成功?"}
    I -- "IPv6 先成功" --> G
    I -- "IPv4 先成功" --> J["使用 IPv4"]
    I -- "都失败" --> K["报告连接失败"]

工程影响

Happy Eyeballs 对于终端用户体验至关重要,但对于服务端基础设施也有影响:

# 查看 curl 是否使用 Happy Eyeballs(curl 7.60+ 默认启用)
curl -v --connect-timeout 5 http://www.example.com 2>&1 | grep "Trying"
# 输出示例:
# * Trying [2606:2800:220:1:248:1893:25c8:1946]:80...
# * Trying 93.184.216.34:80...
# 可以看到 curl 同时尝试了 IPv6 和 IPv4

# 强制使用特定协议版本(调试用)
curl -4 http://www.example.com    # 只用 IPv4
curl -6 http://www.example.com    # 只用 IPv6

服务端注意事项:如果你的服务器同时有 IPv4 和 IPv6 地址,但 IPv6 路径有问题(丢包高、延迟大),客户端的 Happy Eyeballs 会自动切换到 IPv4——但这个切换有 250ms 的代价。如果你的 IPv6 接入质量不好,不如暂时不发布 AAAA 记录,而不是让客户端每次都经历 Happy Eyeballs 的切换延迟。

七、NAT64 与 DNS64:IPv6-only 网络的出路

当你的网络是 IPv6-only,但需要访问 IPv4-only 的服务时,需要 NAT64 和 DNS64 配合工作。

工作原理

IPv6-only 客户端 ──→ DNS64 ──→ NAT64 网关 ──→ IPv4-only 服务器

1. 客户端查询 ipv4only.example.com 的 AAAA 记录
2. 真正的 DNS 返回:无 AAAA 记录,只有 A 记录 93.184.216.34
3. DNS64 服务器合成一个 AAAA 记录:64:ff9b::93.184.216.34 → 64:ff9b::5db8:d822
4. 客户端向 64:ff9b::5db8:d822 发送 IPv6 包
5. NAT64 网关收到这个包,提取嵌入的 IPv4 地址(93.184.216.34)
6. NAT64 用 IPv4 将请求转发给 93.184.216.34
7. IPv4 响应经过 NAT64 翻译为 IPv6 返回给客户端

部署实践

# 使用 Jool(Linux 上成熟的 NAT64 实现)

# 检查系统是否已有 NAT64 前缀
ip -6 route | grep "64:ff9b"

# 测试 NAT64 是否工作
# 先解析一个只有 A 记录的域名
dig AAAA ipv4only.arpa @your-dns64-server
# 如果返回了 64:ff9b:: 开头的地址,说明 DNS64 工作正常

# 然后测试连通性
ping6 -c 3 64:ff9b::8.8.8.8
# 如果通,说明 NAT64 工作正常

# 常见的 Well-Known 前缀:64:ff9b::/96(RFC 6052)
# 也可以使用私有前缀,但需要在 DNS64 和 NAT64 上保持一致

NAT64 的局限性

  1. 不支持嵌入 IPv4 地址的应用层协议:FTP 的 PORT 命令、SIP 的 SDP 中包含了 IPv4 地址字面量——NAT64 无法翻译应用层的地址
  2. IPv4 字面量无法访问:如果应用硬编码了 IPv4 地址(如 http://1.2.3.4/api),DNS64 无法介入。需要额外的 CLAT(Customer-side Translator, RFC 6877)来处理
  3. 性能开销:NAT64 需要维护连接状态表和做协议翻译,比原生 IPv6 有额外开销
  4. IPv4 端口复用限制:NAT64 和 NAT44 一样受端口数量限制——一个 IPv4 出口地址最多约 64K 个并发连接
# 检查是否有应用使用了 IPv4 字面量(在迁移评估时有用)
grep -rn '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' /etc/ --include='*.conf' 2>/dev/null | head -10
# 检查配置文件中是否有 IPv4 地址硬编码

其他迁移技术

除了 NAT64/DNS64 外,还有几种迁移技术值得了解:

技术 原理 适用场景 局限性
6to4 将 IPv6 包封装在 IPv4 中,用 2002::/16 前缀 已废弃(RFC 7526) 依赖中继路由器,不可靠
Teredo 通过 UDP/IPv4 隧道传输 IPv6 已基本废弃 性能差,NAT 穿越问题
6in4(手动隧道) IPv6-in-IPv4 封装,需要配置隧道端点 少量站点互联 手动配置,不可扩展
6rd(快速部署) ISP 自动 6in4,基于 IPv4 地址派生 IPv6 前缀 ISP 过渡方案 地址空间利用率低
MAP-E/MAP-T 在 IPv4 地址共享的同时提供 IPv6 大型 ISP 实现复杂
464XLAT CLAT(客户端 NAT46)+ PLAT(NAT64) 移动网络 IPv6-only 需要客户端支持

工程建议:对于新部署,直接使用双栈或 NAT64/DNS64。6to4 和 Teredo 已经被正式废弃,不要再使用。464XLAT 是移动运营商的主流方案(Android 自带 CLAT 支持)。

IPv6 性能特点

IPv6 和 IPv4 在性能上有一些差异:

  1. 首部开销更大:IPv6 固定首部 40 字节 vs IPv4 最小 20 字节,意味着 IPv6 的有效载荷比率稍低。在 MTU 1500 的网络中,IPv6 的 TCP MSS 是 1440(vs IPv4 的 1460)
  2. 无首部校验和:路由器不需要每跳重新计算校验和,在纯软件路由器上有微小的性能提升
  3. 无路由器分片:路由器不需要维护分片状态和做分片操作,减少了处理延迟
  4. Flow Label 优化 ECMP:路由器可以用 Flow Label 而不需要解析 L4 端口来做负载均衡
# 对比 IPv4 和 IPv6 的路径延迟
ping -c 10 -4 www.google.com 2>/dev/null | tail -1
ping -c 10 -6 www.google.com 2>/dev/null | tail -1
# 理论上延迟应该相近,如果差异大说明 IPv6 路由路径可能更长

# 用 mtr 对比两种协议的路径差异
mtr -4 --report -c 50 www.google.com > /tmp/mtr4.txt 2>/dev/null &
mtr -6 --report -c 50 www.google.com > /tmp/mtr6.txt 2>/dev/null &
wait
diff /tmp/mtr4.txt /tmp/mtr6.txt
# 路径的跳数和中间路由器可能完全不同

八、IPv6 安全工程

IPv6 引入了一些新的安全考虑:

ICMPv6 不能像 IPv4 ICMP 一样一刀切地屏蔽

IPv6 的很多基础功能(NDP、PMTUD、SLAAC)依赖 ICMPv6。如果像某些管理员对待 IPv4 ICMP 那样屏蔽所有 ICMPv6,IPv6 网络会完全不可用。

必须放行的 ICMPv6 类型

类型 名称 原因
1 Destination Unreachable PMTUD 需要(类似 IPv4 的 Type 3)
2 Packet Too Big PMTUD 需要(IPv6 不能在路由器分片!)
3 Time Exceeded traceroute 需要
128/129 Echo Request/Reply ping 需要
133 Router Solicitation NDP 需要
134 Router Advertisement NDP 需要
135 Neighbor Solicitation NDP 需要(等价于 ARP)
136 Neighbor Advertisement NDP 需要
# ip6tables 的 ICMPv6 最小放行规则
ip6tables -A INPUT -p icmpv6 --icmpv6-type destination-unreachable -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type packet-too-big -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type time-exceeded -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type router-solicitation -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-solicitation -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-advertisement -j ACCEPT

Rogue RA 防护

任何设备都可以发送 Router Advertisement。一台配置错误的设备(如开发者笔记本上的虚拟机网桥意外发送 RA)可以劫持整个子网的默认路由和 DNS 配置。

# 检测是否有多个设备在发送 RA
rdisc6 -m eth0
# 如果看到多个来源,除了合法路由器外的都是可疑的

# 在受管交换机上启用 RA Guard(Cisco 示例):
# ipv6 nd raguard policy ROUTER
#   device-role router
# interface GigabitEthernet0/1
#   ipv6 nd raguard attach-policy ROUTER
# 只允许特定端口发送 RA

IPv6 源地址选择

一个接口可能有多个 IPv6 地址(链路本地、全局、临时、ULA)。出站包使用哪个源地址由 RFC 6724 的源地址选择算法决定。这个算法比 IPv4 的源地址选择复杂得多,是很多 IPv6 连接问题的根因。

基本规则(简化版):

  1. 优先使用与目标同类型的地址:目标是全局地址,就用全局源地址;目标是链路本地,就用链路本地源地址
  2. 优先使用非临时地址(如果目标是已知服务器)
  3. 优先使用最匹配的前缀
  4. 如果有 ULA 和全局地址,访问 ULA 目标用 ULA 源地址,访问全局目标用全局源地址
# 查看特定目标的源地址选择结果
ip -6 route get 2001:4860:4860::8888
# 输出示例:
# 2001:4860:4860::8888 ... src 2001:db8::100
# src 后面就是内核选择的源地址

# 常见问题:ULA 地址被选为全局目标的源地址
# 当系统同时有 ULA(fd00::)和全局(2001:db8::)地址时
# 如果地址选择出错,可能导致包可以发出但回程路由不通
# (因为远端无法路由 ULA 地址的回程流量)

# 检查所有 IPv6 地址的 scope 和优先级
ip -6 addr show | grep -E "inet6|scope"

排查思路:如果 IPv6 连接”单向不通”(能发包但收不到回复),检查出站包的源地址——如果源地址是 ULA 或链路本地,远端无法回复到这个地址。

九、IPv6 排查速查表

# ===== 地址与接口 =====
ip -6 addr show                          # 查看所有 IPv6 地址
ip -6 addr show dev eth0 scope global    # 只看全局地址
ip -6 neigh show                         # 查看邻居缓存(等价于 ARP 表)

# ===== 路由 =====
ip -6 route show                         # 查看 IPv6 路由表
ip -6 route get 2001:db8::1              # 查询特定目标的路由决策

# ===== 连通性 =====
ping6 -c 3 2001:db8::1                   # ping IPv6 地址
ping6 -c 3 fe80::1%eth0                  # ping 链路本地地址(需指定接口)
traceroute6 2001:db8::1                  # IPv6 traceroute
mtr -6 --report 2001:db8::1             # IPv6 mtr

# ===== DNS =====
dig AAAA www.example.com                 # 查询 IPv6 地址记录
dig AAAA www.example.com @2001:4860:4860::8888  # 用 Google IPv6 DNS 查询

# ===== NDP =====
rdisc6 eth0                              # 查看路由器通告
ndisc6 目标IPv6地址 eth0                  # 解析 IPv6 地址到 MAC

# ===== 抓包 =====
tcpdump -i eth0 -nn ip6 -c 20           # 抓 IPv6 包
tcpdump -i eth0 -nn icmp6 -c 10         # 抓 ICMPv6
tcpdump -i eth0 -nn 'ip6 and tcp port 443' -c 10  # 抓 IPv6 HTTPS

# ===== 内核参数 =====
sysctl -a 2>/dev/null | grep ipv6 | grep -E "forwarding|accept_ra|use_tempaddr|disable_ipv6"

云环境和容器中的 IPv6

在 Kubernetes 和公有云环境中,IPv6 支持正在快速成熟,但仍有一些工程注意事项:

# Kubernetes 双栈集群检查
kubectl get nodes -o wide
# 查看 Node 是否有 IPv6 地址

kubectl get svc -A -o wide | head -10
# 查看 Service 是否分配了 IPv6 ClusterIP

kubectl get pods -o wide | head -10
# 查看 Pod 是否有 IPv6 地址

# AWS VPC IPv6 检查
# AWS VPC 支持双栈,但需要为 VPC 分配 IPv6 CIDR
# 每个子网可以分配 /64 的 IPv6 前缀
# 安全组规则需要同时配置 IPv4 和 IPv6

# 容器网络的 IPv6 支持
# Docker: docker network create --ipv6 --subnet="2001:db8:1::/64" my-net
# Calico: 支持双栈,需要配置 IPv6 Pool
# Cilium: 原生支持双栈,推荐使用

常见问题:容器运行时(如 Docker)的 IPv6 默认配置可能不正确。需要在 Docker daemon.json 中明确启用 IPv6 并配置子网。如果不配置,容器可能只有 IPv4 连接。

关键内核参数汇总

参数 默认值 推荐值 说明
disable_ipv6 0 0(启用) 1=禁用 IPv6
forwarding 0 看场景 路由器/网关=1,其他=0
accept_ra 1 看场景 路由器需要=2,客户端=1
use_tempaddr 0 2(客户端) 服务器用 0
accept_source_route 0 0 安全:禁用源路由
dad_transmits 1 1 容器快速启动可设 0
max_addresses 16 16 每接口最大 IPv6 地址数

十、结论

IPv6 不只是”地址变长了”。它重新设计了首部结构、取消了广播、引入了 NDP 替代 ARP、改变了分片策略、增加了 Flow Label。这些变化对网络工程实践有实质影响。

四个核心要点:

  1. NDP 是 IPv6 网络的生命线。 它替代了 ARP、提供了 SLAAC 自动配置、实现了路由器发现。ICMPv6(NDP 的基础)绝对不能像 IPv4 ICMP 那样被一刀切地屏蔽——否则 IPv6 网络会完全瘫痪。

  2. 双栈部署的最大风险是不一致。 IPv4 和 IPv6 是两套独立的协议栈,防火墙规则、路由策略、监控指标都需要双份维护。忘记配置 ip6tables 规则是最常见的安全疏忽。

  3. Happy Eyeballs 是用户体验的安全网。 它确保双栈环境中,即使 IPv6 路径有问题,用户也能在 250ms 内回退到 IPv4。但如果你的 IPv6 质量持续不好,250ms × 每次连接的代价会累积——不如暂时不发布 AAAA 记录。

  4. IPv6-only 迁移需要 NAT64/DNS64 作为过渡。 公网仍有大量 IPv4-only 服务,纯 IPv6 网络必须有 NAT64 出口。Apple 从 2016 年就要求 App Store 应用支持 IPv6-only 网络——如果你的服务只有 IPv4,在这些网络中就不可达。

一个务实的建议:在基础设施层面启用双栈,在应用层面确保 IPv6 兼容,在迁移策略上做好 NAT64/DNS64 准备。 不要急于关闭 IPv4——但也不要继续忽视 IPv6。


上一篇:IP 协议深度解剖:首部、分片与路由

下一篇:ICMP 与网络诊断:ping 和 traceroute 的工程本质

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2025-08-04 · network

【网络工程】QUIC 生态与工程部署:从实验到生产

QUIC 已经不是实验性协议——HTTP/3 标准化后,CDN、浏览器和主流服务端框架都在推进 QUIC 支持。本文从工程视角对比主流 QUIC 库的成熟度和性能特征,讲解 CDN/负载均衡器的 QUIC 适配方案、从 TCP 迁移到 QUIC 的渐进路径、QUIC 调试工具链,以及生产环境的部署陷阱和性能调优实践。

2025-08-05 · network

【网络工程】eBPF 可编程网络:从包过滤到流量工程

eBPF 正在重新定义网络工程——从传统的 iptables/netfilter 规则堆砌,到可编程、可观测、高性能的网络数据平面。本文系统讲解 eBPF 网络程序类型(XDP/TC/Socket)、Map 数据结构、Cilium 的 eBPF 数据平面实现,以及 eBPF 在负载均衡、可观测性和网络安全中的工程实践。

2025-08-06 · network

【网络工程】可编程数据平面与 P4:软件定义转发

传统网络设备的转发逻辑固化在硬件中。P4 语言让交换机的转发管线可编程——你可以定义自己的包头解析、匹配规则和转发动作。本文从 P4 语言核心概念出发,讲解 Parser/Match-Action/Deparser 的编程模型、可编程交换机芯片(Tofino)的架构、P4 在数据中心和运营商网络中的应用案例,以及 P4 与 eBPF 的定位差异。


By .