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 eth0Router Advertisement 的工程含义
RA(Router Advertisement)消息是 IPv6 网络配置的核心。路由器定期发送 RA,包含以下信息:
- 本链路的 IPv6 前缀(用于 SLAAC 或 DHCPv6)
- 本链路的 MTU
- 默认路由器的链路本地地址
- 配置标志(M 标志:使用 DHCPv6 获取地址;O 标志:使用 DHCPv6 获取其他信息如 DNS)
# 查看本链路收到的 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 3RA 安全问题:任何设备都可以发送 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 地址:
- 主机生成链路本地地址(
fe80::+ 接口 ID) - 执行 DAD(Duplicate Address Detection)确保地址唯一
- 发送 Router Solicitation
- 从 Router Advertisement 中获取前缀(如
2001:db8::/64) - 组合前缀 + 接口 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=yesDAD(重复地址检测)
无论是 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 子网上不工作。
这个约定的工程含义:
- 一个 /48 前缀 = 65536 个 /64 子网。这是 RIPE/ARIN 分配给终端站点的典型大小
- 一个 /64 子网 = 1.8 × 10^19 个地址。你永远不需要担心子网内的地址耗尽
- 子网扫描在 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 203. 防火墙规则的双重维护
IPv4 用 iptables,IPv6 用
ip6tables——两套独立的规则。忘记在
ip6tables
中配置对应的规则是极其常见的安全疏忽。
# 检查 IPv6 防火墙规则
ip6tables -L -n -v
# 常见遗漏:只在 iptables 中限制了访问,ip6tables 是全开的
# 这意味着攻击者可以通过 IPv6 绕过你的防火墙规则!
# 使用 nftables 统一管理(推荐)
# nftables 可以在一个规则集中同时处理 IPv4 和 IPv6
nft list ruleset4. 监控和日志的盲区
很多监控系统和日志分析工具是为 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 超时。
算法原理
- 同时发起 DNS A 和 AAAA 查询
- 优先尝试 IPv6 连接
- 在极短的时间内(通常 250ms),如果 IPv6 连接没有成功,同时发起 IPv4 连接
- 哪个先成功就用哪个,取消另一个
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 的局限性
- 不支持嵌入 IPv4 地址的应用层协议:FTP 的 PORT 命令、SIP 的 SDP 中包含了 IPv4 地址字面量——NAT64 无法翻译应用层的地址
- IPv4 字面量无法访问:如果应用硬编码了
IPv4 地址(如
http://1.2.3.4/api),DNS64 无法介入。需要额外的 CLAT(Customer-side Translator, RFC 6877)来处理 - 性能开销:NAT64 需要维护连接状态表和做协议翻译,比原生 IPv6 有额外开销
- 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 在性能上有一些差异:
- 首部开销更大:IPv6 固定首部 40 字节 vs IPv4 最小 20 字节,意味着 IPv6 的有效载荷比率稍低。在 MTU 1500 的网络中,IPv6 的 TCP MSS 是 1440(vs IPv4 的 1460)
- 无首部校验和:路由器不需要每跳重新计算校验和,在纯软件路由器上有微小的性能提升
- 无路由器分片:路由器不需要维护分片状态和做分片操作,减少了处理延迟
- 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 ACCEPTRogue 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
# 只允许特定端口发送 RAIPv6 源地址选择
一个接口可能有多个 IPv6 地址(链路本地、全局、临时、ULA)。出站包使用哪个源地址由 RFC 6724 的源地址选择算法决定。这个算法比 IPv4 的源地址选择复杂得多,是很多 IPv6 连接问题的根因。
基本规则(简化版):
- 优先使用与目标同类型的地址:目标是全局地址,就用全局源地址;目标是链路本地,就用链路本地源地址
- 优先使用非临时地址(如果目标是已知服务器)
- 优先使用最匹配的前缀
- 如果有 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。这些变化对网络工程实践有实质影响。
四个核心要点:
NDP 是 IPv6 网络的生命线。 它替代了 ARP、提供了 SLAAC 自动配置、实现了路由器发现。ICMPv6(NDP 的基础)绝对不能像 IPv4 ICMP 那样被一刀切地屏蔽——否则 IPv6 网络会完全瘫痪。
双栈部署的最大风险是不一致。 IPv4 和 IPv6 是两套独立的协议栈,防火墙规则、路由策略、监控指标都需要双份维护。忘记配置
ip6tables规则是最常见的安全疏忽。Happy Eyeballs 是用户体验的安全网。 它确保双栈环境中,即使 IPv6 路径有问题,用户也能在 250ms 内回退到 IPv4。但如果你的 IPv6 质量持续不好,250ms × 每次连接的代价会累积——不如暂时不发布 AAAA 记录。
IPv6-only 迁移需要 NAT64/DNS64 作为过渡。 公网仍有大量 IPv4-only 服务,纯 IPv6 网络必须有 NAT64 出口。Apple 从 2016 年就要求 App Store 应用支持 IPv6-only 网络——如果你的服务只有 IPv4,在这些网络中就不可达。
一个务实的建议:在基础设施层面启用双栈,在应用层面确保 IPv6 兼容,在迁移策略上做好 NAT64/DNS64 准备。 不要急于关闭 IPv4——但也不要继续忽视 IPv6。
下一篇:ICMP 与网络诊断:ping 和 traceroute 的工程本质
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Kubernetes 网络深度系列】IPv4/IPv6 双栈:K8s 的地址空间演化
K8s 双栈网络的演化历程、地址分配机制、CNI 支持现状与现实中的陷阱
【网络工程】QUIC 生态与工程部署:从实验到生产
QUIC 已经不是实验性协议——HTTP/3 标准化后,CDN、浏览器和主流服务端框架都在推进 QUIC 支持。本文从工程视角对比主流 QUIC 库的成熟度和性能特征,讲解 CDN/负载均衡器的 QUIC 适配方案、从 TCP 迁移到 QUIC 的渐进路径、QUIC 调试工具链,以及生产环境的部署陷阱和性能调优实践。
【网络工程】eBPF 可编程网络:从包过滤到流量工程
eBPF 正在重新定义网络工程——从传统的 iptables/netfilter 规则堆砌,到可编程、可观测、高性能的网络数据平面。本文系统讲解 eBPF 网络程序类型(XDP/TC/Socket)、Map 数据结构、Cilium 的 eBPF 数据平面实现,以及 eBPF 在负载均衡、可观测性和网络安全中的工程实践。
【网络工程】可编程数据平面与 P4:软件定义转发
传统网络设备的转发逻辑固化在硬件中。P4 语言让交换机的转发管线可编程——你可以定义自己的包头解析、匹配规则和转发动作。本文从 P4 语言核心概念出发,讲解 Parser/Match-Action/Deparser 的编程模型、可编程交换机芯片(Tofino)的架构、P4 在数据中心和运营商网络中的应用案例,以及 P4 与 eBPF 的定位差异。