Files
cs-note/hhs/NETWORK/03-网络层/03-IPv6地址与扩展头部.md
T
2026-05-27 23:01:37 +08:00

311 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [计算机网络, IPv6, SLAAC, 地址空间, ICMPv6, NDP, 扩展头部]
create time: 2026-05-18 00:20
---
# IPv6 地址与扩展头部
## 概述
IPv6 是为解决 IPv4 地址枯竭而设计的下一代互联网协议。但它的改进远不止于地址空间——简化首部、内置安全支持、无分片(by design)、自动配置等特性使其更适合现代网络架构。
## IPv6 地址表示法
### 十六进制冒号分隔格式
```
2001:0db8:0000:0000:0000:ff00:0042:8329
```
### 缩写规则
| 规则 | 说明 |
|------|------|
| 前导零省略 | `0db8` → `db8`;`0042` → `42` |
| 连续全零段压缩为 `::` | 只能出现**一次**(否则歧义) |
示例:
```
完整: 2001:0db8:0000:0000:0000:ff00:0042:8329
缩写: 2001:db8::ff00:42:8329 ← 最简形式
loopback: 0000:0000:0000:0000:0000:0000:0000:0001
::1 ← 仅一个字符
IPv4映射: 0000:0000:0000:0000:0000:ffff:c0a8:0101
::ffff:192.168.1.1 ← 兼容旧系统
```
### 常见 IPv6 地址类型
| 前缀 | 类型 | 示例 | 范围 |
|------|------|------|------|
| `2000::/3` | 全球单播 (GUA) | `2001:db8::1` | 公网路由 |
| `::1/128` | 环回 (Loopback) | `::1` | 本机 |
| `fc00::/7` | ULA (唯一本地地址) | `fd00::1` | 私有地址,等价 RFC1918 |
| `fe80::/10` | 链路本地 (Link-Local) | `fe80::1` | 仅同链路可用,自动配置 |
| `ff00::/8` | 组播 (Multicast) | `ff02::1` | 多播组(作用域由第二段决定) |
| — | 未指定地址 | `::` | 仅用于 DAD、默认路由等,不可作为目标地址 |
| `2000::/3` 中分配 | 任播 (Anycast) | 与单播地址格式相同 | 一组节点共享同一地址,报文送达最近的一个 |
> [!question] IPv6 地址空间到底有多大?
> 128 位 = 2¹²⁸ ≈ 3.4 × 10³⁸ 个地址。对比:IPv4 仅 2³² ≈ 43 亿,全球沙粒总数约 7.5 × 10¹⁸。IPv6 的地址数足以给地球上每一粒沙子分配约 4.5 × 10¹⁹ 个地址。实际上,全球单播地址仅使用 `2000::/3`(约 1/8 空间),但已绰绰有余。
> [!tip] 链路本地地址 (Link-Local) 是强制的
> 每个启用 IPv6 的接口**必须**自动配置一个 `fe80::` 开头的链路本地地址——即使没有路由器、没有全球前缀。因为 NDP(邻居发现)、RA 接收、DAD 等基础协议都依赖它运作。
### 组播地址作用域
IPv6 组播地址格式为 `ff0s::/8`,其中 `s` 是作用域标志:
| 第二段 (scope) | 前缀 | 作用域 | 常见地址 |
|:-:|:-:|:-:|:-:|
| 1 | `ff01::/16` | 本机 (Interface-Local) | `ff01::1` 所有节点 |
| 2 | `ff02::/16` | 链路 (Link-Local) | `ff02::1` 所有节点, `ff02::2` 所有路由器 |
| 5 | `ff05::/16` | 站点 (Site-Local) | `ff05::1:3` 所有 DHCP 服务器 |
| 8 | `ff08::/16` | 组织 (Organization) | 企业级范围 |
| e | `ff0e::/16` | 全球 (Global) | 跨互联网组播 |
> [!question] IPv6 去掉了广播,用什么替代?
> IPv4 有广播(`255.255.255.255`),IPv6 **完全取消广播**,用组播 + 任播替代。`ff02::1`(所有节点组播)在功能上等价于链路广播,但更精准——只有加入该组的节点才处理。
## IPv6 地址组成
### EUI-64(自动生成)
从 MAC 地址生成接口标识符(正在被隐私扩展取代):
```
MAC: aa:bb:cc:dd:ee:ff
插入 ff:fe → aa:bb:cc:ff:fe:dd:ee:ff
翻转第七位(U/L bit) → a8:bb:cc:ff:fe:dd:ee:ff
↑ aa=10101010 → 第7位取反 → 10101000=a8
结果: fe80::a8bb:ccff:fedd:eeff (link-local)
```
> [!warning] EUI-64 的隐私隐患与 RFC 4941
> EUI-64 将 MAC 地址嵌入 IPv6 地址,意味着你的设备在全球可被追踪(MAC 不变 → IPv6 接口 ID 不变)。
> **RFC 4941 隐私扩展**:主机周期性生成**随机临时地址**用于对外通信(如上网浏览),EUI-64 地址仅用于入站连接。现代 OS(Linux/Windows/macOS)默认启用此机制。
### SLAAC( Stateless Address Autoconfiguration )
主机无需 DHCP 服务器,通过 Router Advertisement(RA)消息自行配置:
```mermaid
sequenceDiagram
participant Host as 主机
participant R as 路由器
R->>Host: Router Advertisement (每 200s~1800s)
Note over Host: RA 中包含:
Note over Host: - 前缀 (如 2001:db8:abcd::/64)
Note over Host: - prefix length
Note over Host: - 默认网关
Note over Host: - DNS (RFC 6106 / RDNSS)
Note over Host: - M/O 标志位
Host->>Host: 自构 IPv6 地址
Host->>Host: DAD: 以 :: 为源地址发送 NS
Host->>R: NDP Neighbor Solicitation (目标=候选地址)
R-->>Host: (无冲突 → 无 NA 回复)
Host->>Host: ✅ 分配 2001:db8:abcd::<interface-id>
```
> [!info] RA 中的 M/O 标志位决定地址获取方式
> | 标志 | 含义 | 行为 |
> |------|------|------|
> | **M** (Managed) | 管理地址配置 | 主机**必须**使用 DHCPv6 获取地址 |
> | **O** (Other) | 其他配置 | SLAAC 获取地址 + DHCPv6 获取 DNS 等信息 |
> | M=0, O=0 | 纯 SLAAC | 前缀 + 接口标识符,无 DHCPv6 |
> [!tip] DAD(重复地址检测)为什么用 `::` 作为源地址?
> 因为发送 DAD 时,该地址还未正式归属于主机——正在验证中。所以用未指定地址 `::` 作为源,避免"自己宣布了一个还没确认的地址"的矛盾。
### DHCPv6 与 DUID
DHCPv6 相比 SLAAC 提供更多控制(DNS、NTP、域名等),适用于企业环境。客户端标识使用 DUID(DHCP Unique Identifier):
| DUID 类型 | 组成 | 特点 |
|:-:|:-:|:-:|
| **DUID-LLT** | MAC + 时间戳 | 最常用,基于链路层地址+生成时间 |
| **DUID-LL** | 仅 MAC | 无时间戳,适合无持久存储设备 |
| **DUID-UUID** | UUID | 持久不变,虚拟机场景 |
> [!info] SLAAC vs DHCPv6 速查
> - **SLAAC**: 零配置、去中心化,适合家庭/小型网络
> - **DHCPv6 Stateful**: 完整地址分配+配置管理,适合企业
> - **DHCPv6 Stateless**: SLAAC 分配地址 + DHCPv6 仅下发 DNS 等信息(最常见组合)
## IPv6 首部 vs IPv4 首部
| 字段 | IPv4 | IPv6 |
|------|------|------|
| 版本 | 4 bit | 4 bit |
| 优先级 | DSCP+ECN (8 bit) | Traffic Class + Flow Label (20 bit) |
| 长度 | Total Length (含首部) | Payload Length (**不含**首部) |
| Next Header | Protocol (替代) | Next Header (等同) |
| Hop Limit | TTL | Hop Limit (改名但不改功能) |
| 源/目的 IP | ✅ | ✅ |
| 首部 Checksum | ✅ | ❌ **已删除!** |
| 分片 | ID/DF/MF/Offset | 移到扩展头(由源端分片) |
IPv6 固定首部仅 40 字节,结构如下:
```mermaid
block-beta
columns 4
block:header:4
columns 4
A["Version 4bit"]:1
B["Traffic Class 8bit"]:1
C["Flow Label 20bit"]:2
end
block:body:4
columns 4
D["Payload Length 16bit"]:1
E["Next Header 8bit"]:1
F["Hop Limit 8bit"]:1
G["reserved"]:1
end
block:src:4
columns 1
H["Source Address 128bit"]
end
block:dst:4
columns 1
I["Destination Address 128bit"]
end
style header fill:#e8f4fd,stroke:#2196f3
style body fill:#f3e5f5,stroke:#9c27b0
style src fill:#e8f5e9,stroke:#4caf50
style dst fill:#fff3e0,stroke:#ff9800
```
> [!question] IPv6 固定首部 40 字节 vs IPv4 最小 20 字节,更大反而更好?
> IPv6 首部虽然更大,但**字段更少且固定**(无 Options),路由器处理时无需条件分支判断——这就是"简化首部"的设计哲学。用空间换时间,线速转发更容易实现。
### IPv6 为什么去掉 Checksum?
- TCP/UDP/ICMPv6 自带端到端校验
- 链路层 Ethernet FCS 已做底层校验
- 每跳计算 Checksum 增加路由器负担 → 提升转发性能
### Flow Label(流标签)
新引入的 20-bit 字段,用于标识属于同一"流"的数据包序列。中间网络设备可据此做 QoS 或负载均衡——无需深度解析载荷。
典型场景:同一 TCP 连接的所有包携带相同的 Flow Label,路由器据此快速分类转发路径,避免逐包查表。结合 SRv6 使用效果更佳。
## IPv6 扩展头部(Extension Headers)
IPv6 将可选功能移出固定首部,放在扩展头链中,由 Next Header 字段串联:
```mermaid
graph LR
FH["Fixed Header"]
HBH["Hop-by-Hop Options"]
DO["Destination Options"]
RT["Routing"]
FR["Fragment"]
AH["AH"]
ESP["ESP"]
UL["Upper Layer TCP/UDP/ICMPv6"]
FH -->|"Next Header=0"| HBH
FH -->|"Next Header=60"| DO
HBH -->|"Next Header=43"| RT
RT -->|"Next Header=44"| FR
FR -->|"Next Header=51"| AH
AH -->|"Next Header=50"| ESP
ESP -->|"Next Header=6/17/58"| UL
DO -->|"Next Header=6/17/58"| UL
```
> [!info] 扩展头的顺序是有规定的
> RFC 8200 建议的推荐顺序:Hop-by-Hop → Destination(路由头之前) → Routing → Fragment → AH → ESP → Destination(上层头之前)。但中间路由器**只需处理** Hop-by-Hop Options,其余扩展头在到达最终目的地时才解析——这对转发性能非常友好。
| 扩展头 | 类型值 | 用途 |
|--------|-------|------|
| Hop-by-Hop Options | 0 | 逐跳选项(极少使用) |
| Routing | 43 | Type 2 (Mobile IPv6);~~Type 0 已废弃~~(见下方警告) |
| Fragment | 44 | 分片信息(ID/offset/MF),仅出现在原始包中 |
| Authentication (AH) | 51 | IPsec 认证(较少独立使用) |
| Encapsulating Security Payload (ESP) | 50 | IPsec 加密封装 |
| Destination Options | 60 | 终点选项(MIPv6、Jumbo Payload) |
| Mobility Header | 135 | Mobile IPv6 |
> [!danger] Routing Header Type 0 (RH0) 已被废弃
> RFC 5095 (2007) **禁止使用 RH0**。原因是攻击者可在 RH0 中列出多个中间节点,让数据包在两个路由器之间反复弹跳(Amplification Attack),造成 DDoS。现代路由器收到 RH0 会直接丢弃。SRv6(Segment Routing over IPv6)使用新的 SRH 扩展头(Type 4)来实现类似功能,且设计上避免了此安全问题。
> [!warning] Path MTU Discovery in IPv6
> IPv6 规定**路由器不允许对 IP 包分片**。如果包超过链路 MTU,路由器直接丢弃并返回 ICMPv6 "Packet Too Big"。这就是 PMTUD 在 IPv6 中成为强制要求的原因。
## ICMPv6:IPv6 的"万能胶水"
ICMPv6(Next Header = 58)在 IPv6 中的角色远超 IPv4 的 ICMP——它同时承载了 IPv4 中 ARP、IGMP 的功能:
| 功能 | IPv4 中的协议 | IPv6 中统一由 ICMPv6 承担 |
|------|:--:|:-:|
| 错误/信息报告 | ICMP | ICMPv6 |
| 地址解析(MAC↔IP) | **ARP** | **NDP** (Neighbor Discovery Protocol) |
| 组播组管理 | **IGMP** | **MLD** (Multicast Listener Discovery) |
| 路由器发现 | 无标准协议 | **RA/RS** (Router Advertisement/Solicitation) |
| 重复地址检测 | 无 | **DAD** (Duplicate Address Detection) |
| Path MTU 发现 | ICMP "Fragmentation Needed" | ICMPv6 "Packet Too Big" |
> [!question] 为什么 IPv6 去掉了 ARP?
> ARP 是一个独立的 L2/L3 之间的"怪胎"协议(既不是 IP 也不是 TCP/UDP),有广播泛洪、无认证等先天缺陷。IPv6 用 NDP(基于 ICMPv6 组播)替代它——地址解析只发到目标节点所在的组播组,而非广播全网,更高效也更安全。
## IPv4 到 IPv6 的过渡机制
| 技术 | 原理 | 适用场景 |
|------|------|---------|
| Dual Stack | 同时运行 IPv4 和 IPv6 | 最常见,逐步迁移 |
| Tunneling (6in4/6to4) | IPv6 包封装在 IPv4 中传输 | 穿越纯 IPv4 骨干网 |
| NAT64/DNS64 | 客户端仅有 IPv6,NAT 翻译到 IPv4 | 运营商级部署 |
| SIIT | 状态less 翻译,地址嵌入 (IPv4-mapped) | ISP、企业边界 |
## Go 中的 IPv6 支持
Go 的 `net` 包天然支持 IPv6,`"tcp6"` 和 `"tcp4"` 区分协议族:
```go
import (
"context"
"net"
"syscall"
)
// 解析与判断
ip := net.ParseIP("2001:db8::1") // net.IP 同时支持 v4/v6
_, cidr, _ := net.ParseCIDR("2001:db8::/32")
fmt.Println(cidr.Contains(ip)) // true
// Dual Stack 监听(同时接受 v4/v6 连接)
// 注意:Linux 默认 IPv6 socket 也接受 v4(IPV6_V6ONLY=0)
ln, _ := net.Listen("tcp", ":8080")
// 仅 IPv6 监听(不接受 v4 映射地址)
lc := net.ListenConfig{
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6,
syscall.IPV6_V6ONLY, 1) // 强制 IPv6 only
})
},
}
ln6, _ := lc.Listen(context.Background(), "tcp6", ":8080")
```
> [!tip] 实战建议
> 生产环境建议使用 `"tcp"` (Dual Stack) 监听,让 OS 同时处理 v4/v6。仅在需要明确区分协议族时才使用 `tcp4`/`tcp6`。
## 关联笔记
- [[hhs/NETWORK/IPv4首部与分段重组]] — IPv4 和 IPv6 首部的差异对比
- [[hhs/NETWORK/DNS原理与优化]] — AAAA 记录解析 IPv6 地址
- [[hhs/NETWORK/CIDR与子网划分]] — CIDR 在 IPv6 中的应用方式相同
- [[hhs/NETWORK/02-网络层/02-ARP与ICMP]] — ARP 协议(被 NDP 替代)