13.1 IPv6 数据包、地址与子网
IPv6 不只是“把地址从 32 位加长到 128 位”。它重新安排了基本首部、分片和邻居发现,也让一台主机同时拥有多个不同作用域、不同生命周期的地址成为常态。
本课目标
- 解释 IPv6 基本首部与扩展首部的分工;
- 正确理解发送端分片、Path MTU Discovery 和 ICMPv6;
- 规范书写与解析 IPv6 地址;
- 区分 global、ULA、link-local、multicast 与 anycast;
- 设计常见
/64子网,同时知道它不是所有接口的绝对规则。
为什么还需要 IPv6
IPv4 的 32 位空间只有约 43 亿个数值,其中还有保留和特殊用途范围。CIDR、私有地址与 NAT 延缓了耗尽,却没有增加端到端可用的公网地址。
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 |
| 128 bits |
+---------------------------------------------------------------+
| Destination Address |
| 128 bits |
+---------------------------------------------------------------+| 字段 | 位数 | 作用 |
|---|---|---|
| Version | 4 | 值为 6 |
| Traffic Class | 8 | DSCP 与 ECN 使用的流量分类位 |
| Flow Label | 20 | 标识属于同一流的包,具体使用受实现与网络策略影响 |
| Payload Length | 16 | 基本首部之后的长度;jumbogram 是受限的扩展场景 |
| Next Header | 8 | 指向扩展首部或 TCP、UDP、ICMPv6 等上层协议 |
| Hop Limit | 8 | 每次转发减一,转发时减到零则丢弃,作用类似 IPv4 TTL |
| Source/Destination | 各 128 | 源与目的 IPv6 地址 |
下面的解析器只处理基本首部,不试图遍历扩展首部:
from dataclasses import dataclass
import ipaddress
import struct
@dataclass(frozen=True)
class IPv6BaseHeader:
traffic_class: int
flow_label: int
payload_length: int
next_header: int
hop_limit: int
source: ipaddress.IPv6Address
destination: ipaddress.IPv6Address
def parse_ipv6_base_header(packet: bytes) -> IPv6BaseHeader:
if len(packet) < 40:
raise ValueError("truncated IPv6 base header")
first_word, length, next_header, hop_limit, source, destination = (
struct.unpack("!IHBB16s16s", packet[:40])
)
version = first_word >> 28
if version != 6:
raise ValueError(f"expected IPv6, got version={version}")
return IPv6BaseHeader(
traffic_class=(first_word >> 20) & 0xFF,
flow_label=first_word & 0xFFFFF,
payload_length=length,
next_header=next_header,
hop_limit=hop_limit,
source=ipaddress.IPv6Address(source),
destination=ipaddress.IPv6Address(destination),
)payload_length 不含 40 字节基本首部。这里成功返回只说明基本首部可读,不证明 payload 完整。生产解析器还须检查捕获长度、扩展首部链和资源上限。Payload Length = 0 也不能直接解释成空包:符合 jumbogram 条件时,长度由 Hop-by-Hop 中的 Jumbo Payload 选项表达。Flow Label 本身则不承诺带宽或优先级。
扩展首部不是“路由器一概不看”
IPv6 把可选信息放进扩展首部,由 Next Header 串成链。常见类型包括 Hop-by-Hop Options、Routing、Fragment、Destination Options 和 AH/ESP。
多数扩展首部只由目的节点处理;Hop-by-Hop Options 具有沿途处理语义,但 RFC 8200 允许节点仅在显式配置时检查它。现实设备也可能因安全策略、硬件能力或异常首部链而丢弃包。因此不能从“协议支持扩展首部”推出“任意组合都能穿过互联网”。
没有 IPv6 首部校验和意味着什么
IPv6 基本首部没有像 IPv4 那样的 header checksum,路由器改变 Hop Limit 时无需重算它。链路层通常有差错检测,TCP 与 ICMPv6 也有校验和;IPv6 中 UDP 校验和通常必须非零,只有少数明确限定的隧道场景允许例外。
这不意味着任何一层都能“保证数据绝不损坏”。校验和提供的是一定强度的差错检测,不是密码学完整性。
发送端分片与 PMTUD
IPv6 路由器不会替源主机分片。包超过下一跳 MTU 时,路由器丢弃它,并返回 ICMPv6 Packet Too Big,其中带可用 MTU。源主机据此降低包大小;若确实需要 IPv6 分片,Fragment header 由源节点添加。
source -- packet too large --> router
source <-- ICMPv6 Packet Too Big, MTU=... -- router
source -- smaller packets or source fragments --> destination每条 IPv6 链路必须向 IPv6 提供至少 1280 字节的 MTU;若底层无法一次承载,需要在 IPv6 之下完成适配或分片重组,不是交给 IPv6 路由器分片。以太网常见 MTU 是 1500,但端到端路径可能更小。屏蔽 Packet Too Big 会破坏 PMTUD,造成“小请求正常、大响应卡住”的黑洞。
传输层还可以使用 Packetization Layer PMTUD,在不完全依赖 ICMP 的情况下通过受控探测调整包大小。
地址文本表示
一个 IPv6 地址由 8 组 16 位十六进制数组成:
2001:0db8:0000:0000:0000:ff00:0042:8329压缩规则:
- 每组可以去掉前导零;
- 一段连续的全零组可以用
::代替; ::在一个地址中最多出现一次;- RFC 5952 的规范文本形式选择最长的零串;若并列,压缩最左边一段,并使用小写十六进制;规范形式不把单独一个零组压成
::。
2001:0db8:0000:0000:0000:ff00:0042:8329
2001:db8::ff00:42:8329两行表示同一个 128 位值。文本相等判断容易被不同写法骗过,应先解析成二进制地址对象:
import ipaddress
left = ipaddress.IPv6Address("2001:0db8:0:0:0:ff00:42:8329")
right = ipaddress.IPv6Address("2001:db8::ff00:42:8329")
assert left == right
assert left.compressed == "2001:db8::ff00:42:8329"
assert left.exploded == "2001:0db8:0000:0000:0000:ff00:0042:8329"URL 中的 IPv6 literal 必须用方括号包住,避免地址中的冒号与端口分隔符冲突:
http://[2001:db8::10]:8080/link-local 地址常需 zone identifier 指定接口,例如 Linux 上的 fe80::1%eth0。zone 属于使用地址时的作用域信息,不是全局可传递的 DNS 名称组成部分;接口名只在本机有意义。带 zone 的地址如何输入 URL/UI 取决于工具支持,不能把 Linux 的 %eth0 写法当成通用、可跨机器使用的 URL。
重要地址范围
| 范围 | 名称 | 边界 |
|---|---|---|
::/128 | unspecified | 只能作为尚未确定地址等场景的源,不能分配给接口作为普通单播地址 |
::1/128 | loopback | 只在本机有效 |
2000::/3 | 当前常见 global unicast 分配范围 | 可被全球路由取决于路由与策略,不是“天然可达” |
2001:db8::/32 | documentation | 只用于文档示例,不应出现在公网路由 |
fc00::/7 | unique local | 不应在全球互联网路由;实际本地生成通常使用 fd00::/8 |
fe80::/10 | link-local | 只在单条链路范围使用,路由器不转发 |
ff00::/8 | multicast | 作用域由地址中的 scope 字段决定 |
ULA 不是 IPv4 RFC 1918 地址的完全同义词。RFC 4193 的 locally assigned ULA 通常以 fd 开头,使用随机生成的 40 位 Global ID 来降低组织合并时的碰撞概率;它仍可能在组织内部跨子网路由。
单播、多播与任播
IPv6 没有 IPv4 式广播。邻居发现等功能使用具有明确作用域的 multicast:
ff02::1:link-local scope 的 all-nodes;ff02::2:link-local scope 的 all-routers;- solicited-node multicast:NDP 根据目标单播地址低 24 位构造。
多播减少了某些广播带来的无关协议栈处理,但并不表示所有多播都不会打扰其他设备。组成员管理、交换机 MLD snooping 配置和链路行为都会影响实际转发。
IPv6 anycast 使用普通 unicast 地址格式:把同一地址配置到多个节点,并通过路由把流量送到拓扑度量选中的实例。“最近”指路由策略下的最佳路径,不保证地理距离最短,也不保证连续请求落到同一实例。
/64 是重要惯例,不是宇宙常数
大多数普通 LAN 使用 /64,SLAAC 的主流工作方式也以 64 位 interface identifier 为基础。运营者通常从上级分配的 /48、/56 或其他前缀中,为各二层网段分配 /64。
但不能说“任何 IPv6 主机部分永远是 64 位”:
- loopback 是
/128; - 路由项和聚合前缀可使用各种长度;
- point-to-point 链路可按部署规范使用
/127; - 特殊地址与协议可能有自己的结构。
子网设计的目标不是节省单个地址,而是保留清晰的层次、聚合能力和一致运维模型。
import ipaddress
from itertools import islice
site = ipaddress.IPv6Network("2001:db8:1200::/48")
first_four_lans = list(islice(site.subnets(new_prefix=64), 4))
assert [str(network) for network in first_four_lans] == [
"2001:db8:1200::/64",
"2001:db8:1200:1::/64",
"2001:db8:1200:2::/64",
"2001:db8:1200:3::/64",
]一个 /48 包含 2^(64-48) = 65,536 个 /64。示例用 islice 只消费前四项,避免为了展示四个子网先创建 65,536 个对象。地址很富余,不代表列表也必须铺满。
本课小结
IPv6 的基本首部更规整,分片责任移到源端,PMTUD 依赖 ICMPv6。128 位地址带来了作用域和多地址并存的新常态;/64 是 LAN 与 SLAAC 的核心约定,但必须放在具体接口和协议语境中理解。
下一课将沿着一台主机接入链路的过程,理解 NDP、RA、SLAAC、DHCPv6 和地址生命周期。
规范入口
RFC 8200:IPv6 Specification;
RFC 4291:IPv6 Addressing Architecture;
RFC 5952:IPv6 Text Representation;
RFC 4193:Unique Local IPv6 Unicast Addresses;
RFC 8201:Path MTU Discovery for IPv6;
RFC 6164:Using 127-Bit IPv6 Prefixes on Inter-Router Links。
RFC 9844: Entering IPv6 Zone Identifiers in User Interfaces.