跳到内容

第 4 卷 / 第 13 章 / 第 1 课

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 字节的基本首部 ​

text
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                              |
+---------------------------------------------------------------+
字段位数作用
Version4值为 6
Traffic Class8DSCP 与 ECN 使用的流量分类位
Flow Label20标识属于同一流的包,具体使用受实现与网络策略影响
Payload Length16基本首部之后的长度;jumbogram 是受限的扩展场景
Next Header8指向扩展首部或 TCP、UDP、ICMPv6 等上层协议
Hop Limit8每次转发减一,转发时减到零则丢弃,作用类似 IPv4 TTL
Source/Destination各 128源与目的 IPv6 地址

下面的解析器只处理基本首部,不试图遍历扩展首部:

python
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 由源节点添加。

text
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 位十六进制数组成:

text
2001:0db8:0000:0000:0000:ff00:0042:8329

压缩规则:

  1. 每组可以去掉前导零;
  2. 一段连续的全零组可以用 :: 代替;
  3. :: 在一个地址中最多出现一次;
  4. RFC 5952 的规范文本形式选择最长的零串;若并列,压缩最左边一段,并使用小写十六进制;规范形式不把单独一个零组压成 ::。
text
2001:0db8:0000:0000:0000:ff00:0042:8329
2001:db8::ff00:42:8329

两行表示同一个 128 位值。文本相等判断容易被不同写法骗过,应先解析成二进制地址对象:

python
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 必须用方括号包住,避免地址中的冒号与端口分隔符冲突:

text
http://[2001:db8::10]:8080/

link-local 地址常需 zone identifier 指定接口,例如 Linux 上的 fe80::1%eth0。zone 属于使用地址时的作用域信息,不是全局可传递的 DNS 名称组成部分;接口名只在本机有意义。带 zone 的地址如何输入 URL/UI 取决于工具支持,不能把 Linux 的 %eth0 写法当成通用、可跨机器使用的 URL。

重要地址范围 ​

范围名称边界
::/128unspecified只能作为尚未确定地址等场景的源,不能分配给接口作为普通单播地址
::1/128loopback只在本机有效
2000::/3当前常见 global unicast 分配范围可被全球路由取决于路由与策略,不是“天然可达”
2001:db8::/32documentation只用于文档示例,不应出现在公网路由
fc00::/7unique local不应在全球互联网路由;实际本地生成通常使用 fd00::/8
fe80::/10link-local只在单条链路范围使用,路由器不转发
ff00::/8multicast作用域由地址中的 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;
  • 特殊地址与协议可能有自己的结构。

子网设计的目标不是节省单个地址,而是保留清晰的层次、聚合能力和一致运维模型。

python
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.

Built with VitePress | Software Systems Atlas