一、IPv4 数据报格式
0 4 8 16 31
┌─────┬─────┬──────────┬─────────────────────┐
│版本 │头长 │ 服务类型 │ 总长度(字节) │
├─────┴─────┴──────────┼──────┬──────────────┤
│ 标识符 (16) │标志(3)│ 片偏移 (13) │ ← 分片用
├───────────┬──────────┼──────┴──────────────┤
│ TTL (8) │ 协议 (8) │ 头部校验和 (16) │
├───────────┴──────────┴─────────────────────┤
│ 源 IP 地址 (32) │
├────────────────────────────────────────────┤
│ 目的 IP 地址 (32) │
├────────────────────────────────────────────┤
│ 选项(可变长,很少使用) │
├────────────────────────────────────────────┤
│ 数据 │
└────────────────────────────────────────────┘
关键字段
| 字段 | 说明 |
|---|---|
| 版本 | 4 或 6 |
| 头部长度 | 以 4 字节为单位;无选项时为 5(20 字节) |
| 服务类型(ToS/DSCP) | QoS 标记;低 2 位现用于 ECN(第 16 讲) |
| 总长度 | 头部+数据,最大 65535 字节 |
| 标识/标志/片偏移 | 分片重组用 |
| ⭐ TTL | 每经过一台路由器减 1,减到 0 就丢弃并回 ICMP 超时 |
| 协议 | 上层协议:6 = TCP,17 = UDP,1 = ICMP |
| 头部校验和 | ⚠️ 只覆盖头部,不覆盖数据;且每跳都要重算(因为 TTL 变了) |
⭐ TTL 的两个作用:① 防止路由环路导致分组永远转圈;② traceroute 的实现基础(第 3 讲)。
头部开销
无选项时 20 字节。对 1500 字节的 MTU:
IP 头 20 + TCP 头 20 = 40 字节,占 2.7%
二、IP 分片
2.1 为什么需要
不同链路的 MTU(最大传输单元)不同:
以太网 1500 字节
PPPoE 1492 字节
IEEE 802.11 2304 字节
一些隧道 1400 字节甚至更小
一个大数据报要经过 MTU 更小的链路时,必须被切开。
2.2 机制
⭐ 分片由【路由器】完成,重组由【目的主机】完成
**为什么重组只在终点?**因为分片可能走不同路径,中间路由器不一定能收齐所有片;而且让路由器维护重组状态会破坏其无状态性。
三个字段协同工作:
| 字段 | 作用 |
|---|---|
| 标识符 | 同一个原始数据报的所有片段共享同一个标识符 |
| 标志位 MF | More Fragments:1 表示后面还有,0 表示这是最后一片 |
| 片偏移 | ⭐ 本片数据在原始数据中的位置,以 8 字节为单位 |
2.3 计算示例
一个 4000 字节的数据报(20 字节头 + 3980 字节数据)要通过 MTU = 1500 的链路:
每片最多携带数据 = 1500 − 20 = 1480 字节
⚠️ 片偏移以 8 字节为单位,所以每片数据必须是 8 的倍数
1480 ÷ 8 = 185 ✓ 正好
片 1: 数据 1480 字节,偏移 = 0, MF=1 总长 1500
片 2: 数据 1480 字节,偏移 = 185, MF=1 总长 1500
片 3: 数据 1020 字节,偏移 = 370, MF=0 总长 1040
(1480+1480+1020 = 3980 ✓)
偏移的计算:片 n 的偏移 = 前面所有片的数据字节数 ÷ 8。
2.4 分片的问题
⚠️ 分片在工程上被普遍认为是一个坏主意:
- 一片丢失 = 整个数据报作废(TCP 要重传整个报文段)
- 重组消耗接收方资源,是 DoS 攻击面(teardrop 攻击)
- 中间设备(防火墙、NAT)难以处理非首片(没有 TCP 端口号)
⭐ IPv6 直接取消了路由器分片(第 19 讲)。现代做法是 Path MTU Discovery:发送方主动探测路径上的最小 MTU,自己保证不超过它。
三、IP 编址与子网
3.1 地址属于接口,不属于主机 ⭐
IP 地址标识的是一个「接口」,而不是一台主机或路由器。
一台路由器有多个接口,每个接口都有自己的 IP 地址。一台装了 WiFi 和有线网卡的笔记本,两个接口有两个地址。
3.2 点分十进制
11000000 10101000 00000001 00000001
192 . 168 . 1 . 1
3.3 子网的精确定义 ⭐
子网是一组接口的集合,这些接口彼此之间可以【不经过路由器】直接通信。
判定方法:把所有路由器接口断开,剩下的每一个孤立的网络岛,就是一个子网。
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│主机A │ │主机B │ │主机D │ │主机E │
└──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘
│ │ │ │
═══╧═════════╧═══[R1]═════════╧═════════╧═══
子网 1: 223.1.1.0/24 子网 2: 223.1.2.0/24
3.4 CIDR 表示法
a.b.c.d / x
x = 前缀长度(网络部分的位数)
32 − x = 主机部分的位数
例:200.23.16.0/23
11001000 00010111 00010000 00000000
└──────── 23 位网络前缀 ────┘└─9 位主机─┘
地址范围:200.23.16.0 ~ 200.23.17.255
可用主机数:2⁹ − 2 = 510
⚠️ 为什么要 −2:全 0 的是网络地址,全 1 的是广播地址,都不能分配给主机。
3.5 子网掩码
/24 → 255.255.255.0 → 11111111.11111111.11111111.00000000
/26 → 255.255.255.192 → 11111111.11111111.11111111.11000000
/20 → 255.255.240.0
📌 速查表(背下来能省很多时间):
| 前缀 | 掩码末字节 | 每子网地址数 | 可用主机数 |
|---|---|---|---|
| /24 | 0 | 256 | 254 |
| /25 | 128 | 128 | 126 |
| /26 | 192 | 64 | 62 |
| /27 | 224 | 32 | 30 |
| /28 | 240 | 16 | 14 |
| /29 | 248 | 8 | 6 |
| /30 | 252 | 4 | 2(点对点链路专用) |
3.6 从分类编址到 CIDR
历史(有类别编址):
A 类: /8 —— 1600 万个地址 (只有 126 个这样的块)
B 类: /16 —— 65534 个地址
C 类: /24 —— 254 个地址
⚠️ 问题:一个需要 500 台主机的组织,C 类不够(254),B 类浪费 99.2%(65534)。这种粗粒度导致了 1990 年代初的地址快速枯竭。
CIDR(1993, RFC 1519) 的解法:前缀长度可以是任意值,按需分配。
3.7 地址聚合(Route Aggregation)⭐
CIDR 的第二个收益:减小全球路由表。
ISP 拥有: 200.23.16.0/20
分配给客户:
组织 0: 200.23.16.0/23
组织 1: 200.23.18.0/23
组织 2: 200.23.20.0/23
...
组织 7: 200.23.30.0/23
⭐ ISP 向互联网只通告【一条】路由:200.23.16.0/20
而不是 8 条
**这是层次化编址的核心价值。**没有它,全球路由表会膨胀到无法承受。
例外情形:更具体路由
若组织 1 换了 ISP,但想保留原地址:
新 ISP 必须单独通告 200.23.18.0/23
⭐ 最长前缀匹配保证了流量会去新 ISP
代价:全球路由表多了一条无法被聚合的条目
📌 这就是全球 BGP 路由表持续膨胀的主要原因之一(第 21 讲)。
3.8 特殊地址
| 地址块 | 用途 |
|---|---|
10.0.0.0/8 |
⭐ 私有地址(RFC 1918) |
172.16.0.0/12 |
私有地址 |
192.168.0.0/16 |
私有地址 |
127.0.0.0/8 |
环回(localhost) |
169.254.0.0/16 |
链路本地(DHCP 失败时自动配置,APIPA) |
224.0.0.0/4 |
多播 |
100.64.0.0/10 |
运营商级 NAT(CGNAT) |
255.255.255.255 |
受限广播 |
四、DHCP:即插即用地址分配
主机怎么得到 IP 地址?两种方式:管理员手工配置,或 DHCP(动态主机配置协议)。
4.1 DHCP 提供的四样东西
不只是 IP 地址:
① 客户端的 IP 地址
② 子网掩码
③ ⭐ 默认网关(第一跳路由器)的地址
④ ⭐ 本地 DNS 服务器的地址
⚠️ 没有 ③ 和 ④,你有 IP 也上不了网。这就是"网络配置"的全部内容。
4.2 四步交互(DORA)
DHCP 运行在 UDP 上(客户端 68,服务器 67)。
客户端(还没有 IP!) DHCP 服务器
│
│ ① DHCP DISCOVER
│ 源 IP: 0.0.0.0 ⭐ 我还没有地址
│ 目的: 255.255.255.255 ⭐ 广播,我不知道服务器在哪
├──────────────────────────────────────────▶
│
│ ② DHCP OFFER (可能有多个服务器响应)
│◀──────────────────────────────────────────┤
│ 「我可以给你 192.168.1.100,租期 12 小时」
│
│ ③ DHCP REQUEST ⭐ 仍然是广播
├──────────────────────────────────────────▶
│ 「我接受这个 offer」(广播是为了让其他服务器知道自己没被选中)
│
│ ④ DHCP ACK
│◀──────────────────────────────────────────┤
│ 「确认,地址是你的了」
⭐ 为什么用广播?因为客户端此时既没有自己的 IP,也不知道服务器的 IP。这是一个「先有鸡还是先有蛋」问题,只能用广播打破。
⭐ **为什么用 UDP 而不是 TCP?**TCP 需要三次握手,而握手需要有 IP 地址——同样的循环依赖(第 10 讲的 UDP 使用场景)。
4.3 租期与中继
租期(lease):地址是借的,不是给的。租期过半时客户端会尝试续租。这允许地址在设备离开后被回收。
DHCP 中继代理:广播不能跨越路由器。若 DHCP 服务器不在本子网,路由器可配置为中继代理,把广播转成单播转发给服务器。
五、NAT:网络地址转换
5.1 动机
IPv4 只有 2³² ≈ 43 亿个地址,而全球有上百亿台联网设备。
NAT 的思路:一个家庭/组织内部用私有地址,对外只用一个公网地址。
5.2 机制
内网 NAT 路由器 外网
138.76.29.7 (公网)
10.0.0.1:3345 ──────────────┤
│ NAT 转换表
10.0.0.2:3345 ──────────────┤ ┌──────────────┬──────────────┐
│ │ 公网侧 │ 内网侧 │
10.0.0.3:5001 ──────────────┤ ├──────────────┼──────────────┤
│ │138.76.29.7:5001│10.0.0.1:3345│
│ │138.76.29.7:5002│10.0.0.2:3345│
│ │138.76.29.7:5003│10.0.0.3:5001│
│ └──────────────┴──────────────┘
出站:
① 收到 (10.0.0.1:3345 → 服务器:80)
② 分配一个未用的公网端口,如 5001
③ 记入转换表
④ ⭐ 改写源 IP 和源端口为 (138.76.29.7:5001)
⑤ ⭐ 重算 IP 头校验和【和】TCP/UDP 校验和(因为伪首部,第 10 讲)
⑥ 转发
入站:
① 收到 (服务器:80 → 138.76.29.7:5001)
② 查表 → 5001 对应 10.0.0.1:3345
③ 改写目的 IP 和端口
④ 转发到内网
⭐ **本质:NAT 用「端口号」作为额外的地址位。**16 位端口理论上可支持 65535 个并发连接共享一个 IP。
5.3 收益
- 节省地址:ISP 只需给一个家庭分配一个 IP
- 内网地址可自由改变,不影响外部
- 更换 ISP 不需要重新编址内网
- ⚠️ 副作用式的"安全":外部无法主动发起到内网主机的连接
5.4 NAT 的争议 ⭐
反对者的论点(都很有力):
1️⃣ 它违反了端到端原则(第 4 讲)
网络中间的设备改写了传输层信息。这是分层的严重破坏。
2️⃣ 路由器不应该处理第 4 层
按分层,路由器只该到第 3 层。NAT 让它读写端口号,这是一个层次侵犯。
3️⃣ 它破坏了 IP 的可寻址性
内网主机无法被主动连接。这直接杀死了 P2P 应用、IP 电话、在家自建服务器的能力。
4️⃣ 它把「地址短缺」的压力转成了「一切都要打补丁」
有人认为:NAT 让 IPv4 苟延残喘,反而拖延了 IPv6 的部署。
支持者的回应:
**NAT 是让互联网活到今天的东西。**没有它,IPv4 地址在 2000 年代初就会彻底耗尽,而 IPv6 当时远未准备好。理论上的纯洁性不能当地址用。
📌 这是网络课上最好的一场辩论,因为双方都对。它展示了一件事:工程决策经常在"正确"和"可行"之间,而可行的往往赢。
5.5 NAT 穿越
问题:外网的 A 想主动连接 NAT 后的 B,怎么办?
| 方案 | 原理 |
|---|---|
| 端口转发(静态映射) | 手动在 NAT 上配置「外部 8080 → 内部 192.168.1.10:80」 |
| UPnP / NAT-PMP | 内网应用自动请求 NAT 建立映射 |
| STUN | 让 B 询问外部服务器「我在公网上看起来是什么地址:端口?」 |
| ⭐ 打洞(Hole Punching) | 双方都先向外发包建立映射,再借助中间服务器交换地址,然后直连 |
| TURN | 打洞失败时的兜底:通过中继服务器转发全部流量(昂贵) |
| ICE | 上述方案的统一框架,WebRTC 使用 |
📌 打洞的巧妙之处:NAT 只阻止「未经请求的入站」。如果双方同时向对方发包,各自的 NAT 都会认为这是自己发起的连接的回包,从而放行。它利用了 NAT 规则的对称性。
5.6 CGNAT
ISP 地址不够时,会做运营商级 NAT——用户拿到的是 100.64.0.0/10 里的地址,再经过 ISP 的 NAT 出去。
⚠️ 后果:双层 NAT,打洞成功率大幅下降,很多 P2P 应用退化到必须用 TURN 中继。这是移动网络上 P2P 体验差的主要原因。
六、例题(Worked Example)
题目:某公司获得地址块 203.0.113.0/24。需要划分为四个子网:
- 工程部:100 台主机
- 销售部:50 台主机
- 管理部:25 台主机
- 一条连接两台路由器的点对点链路
请给出各子网的 CIDR 前缀、地址范围与可用主机范围。
解答:
⭐ 方法:VLSM(可变长子网掩码),从需求最大的开始分配。
第 1 步:确定每个子网需要的前缀长度
工程部 100 台 → 需 100+2 = 102 个地址 → 2⁷=128 ≥ 102 → /25
销售部 50 台 → 需 52 个地址 → 2⁶=64 ≥ 52 → /26
管理部 25 台 → 需 27 个地址 → 2⁵=32 ≥ 27 → /27
点对点链路 → 需 2 个地址 → 2²=4 → /30
第 2 步:从大到小依次分配
① 工程部 /25:
203.0.113.0/25
地址范围: 203.0.113.0 – 203.0.113.127
网络地址: 203.0.113.0
广播地址: 203.0.113.127
⭐ 可用主机: 203.0.113.1 – 203.0.113.126 (126 个 ≥ 100 ✓)
② 销售部 /26(从 128 开始):
203.0.113.128/26
地址范围: 203.0.113.128 – 203.0.113.191
⭐ 可用主机: 203.0.113.129 – 203.0.113.190 (62 个 ≥ 50 ✓)
③ 管理部 /27(从 192 开始):
203.0.113.192/27
地址范围: 203.0.113.192 – 203.0.113.223
⭐ 可用主机: 203.0.113.193 – 203.0.113.222 (30 个 ≥ 25 ✓)
④ 点对点链路 /30(从 224 开始):
203.0.113.224/30
地址范围: 203.0.113.224 – 203.0.113.227
⭐ 可用主机: 203.0.113.225 – 203.0.113.226 (2 个 ✓)
剩余未分配: 203.0.113.228 – 203.0.113.255(28 个地址,留作扩展)
验证掩码:
/25 → 255.255.255.128
/26 → 255.255.255.192
/27 → 255.255.255.224
/30 → 255.255.255.252
📌 两个易错点:
- 必须从大到小分配,否则会产生无法使用的地址碎片
- 每个子网都要减去网络地址和广播地址——「100 台主机」需要的是 /25 而不是 /26(62 < 100),也不是 /24(浪费)
七、随堂自测
- IP 头部校验和覆盖数据吗?为什么每跳都要重算?
- 分片由谁做?重组由谁做?为什么不对称?
- 一个 5000 字节的数据报经过 MTU=1500 的链路,会分成几片?写出每片的偏移和 MF。
- 用一句话精确定义「子网」。给出判定方法。
192.168.10.0/26有多少可用主机地址?范围是什么?- 为什么 CIDR 取代了分类编址?地址聚合带来了什么?
- DHCP 为什么必须用广播和 UDP?
- NAT 违反了哪条设计原则?给出支持和反对 NAT 的各一条最有力的论证。
- 打洞(hole punching)为什么能成功?
八、本讲要点回顾
- TTL 防环路,且是 traceroute 的基础;IP 头校验和只覆盖头部、每跳重算。
- 分片由路由器做,重组只由目的主机做;片偏移以 8 字节为单位。IPv6 取消了路由器分片。
- ⭐ IP 地址属于接口,不属于主机。
- ⭐ 子网 = 一组不经过路由器就能互相通信的接口。
- CIDR 让前缀长度任意,带来按需分配与⭐ 地址聚合(减小全球路由表)。
- 主机数 = 2^(32−x) − 2(去掉网络地址和广播地址)。
- DHCP 四步 DORA,给出 IP + 掩码 + 网关 + DNS;用广播和 UDP 是因为循环依赖。
- NAT 用端口号当地址位,救了 IPv4,也破坏了端到端原则和可寻址性。
- NAT 穿越:STUN / 打洞 / TURN / ICE;CGNAT 让情况更糟。
九、自测答案
1. 不覆盖数据,只覆盖头部。(数据的完整性交给传输层的校验和与链路层的 CRC——这是分层的分工。)每跳必须重算,是因为 TTL 每跳减 1,头部内容发生了变化,原校验和失效。(这也是 IPv6 干脆取消头部校验和的原因之一:每跳重算是纯粹的开销。)
2. 分片由路由器做,重组只由目的主机做。不对称的原因:① 各片段可能沿不同路径传输,中间路由器无法保证收齐所有片;② 让路由器为重组维护状态会破坏其无状态转发的特性、消耗内存并构成 DoS 攻击面;③ 端到端原则——重组只有终点能正确完成。
3. 数据部分 = 5000 − 20 = 4980 字节。每片最多携带 1480 字节数据(1500 − 20,且 1480 是 8 的倍数)。
片 1: 数据 1480, 偏移 = 0, MF = 1
片 2: 数据 1480, 偏移 = 185, MF = 1
片 3: 数据 1480, 偏移 = 370, MF = 1
片 4: 数据 540, 偏移 = 555, MF = 0
共 4 片(1480×3 + 540 = 4980 ✓)
4. **子网是一组接口的集合,这些接口彼此之间不经过路由器就能直接通信。**判定方法:把每台路由器的所有接口从图上"剪断",剩下的每一个相互连通的孤岛就是一个子网。
5. /26 → 主机位 32−26 = 6 位 → 2⁶ = 64 个地址 → 62 个可用主机地址。范围:网络地址 192.168.10.0,广播地址 192.168.10.63,可用主机 192.168.10.1 – 192.168.10.62。
6. 分类编址的粒度太粗(/8、/16、/24 三档),导致严重浪费:需要 500 台主机的组织,C 类(254)不够而 B 类(65534)浪费 99%。这加速了 1990 年代初的地址枯竭。CIDR 允许任意前缀长度,可按实际需求分配。地址聚合让一个 ISP 能用一条前缀通告其下所有客户的地址块,从而把全球路由表规模从「每个组织一条」压缩了几个数量级——没有聚合,BGP 路由表规模会失控。
7. 广播:客户端在获得地址之前既没有自己的 IP,也不知道 DHCP 服务器的 IP,无法进行任何单播通信,只能向 255.255.255.255 广播。UDP:TCP 需要三次握手,而握手本身要求已有可用的 IP 地址——这是同一个循环依赖。UDP 无连接,可以直接发送。
8. 违反了端到端原则(第 4 讲)——网络中间的设备改写了传输层的端口信息,且路由器越层处理了第 4 层内容。反对最有力的一条:它破坏了 IP 的可寻址性,使内网主机无法被主动连接,从而系统性地扼杀了 P2P、IP 电话、个人自建服务等一整类应用,把互联网从「对等网络」推向了「客户机-服务器」的不对称结构。支持最有力的一条:没有 NAT,IPv4 地址会在 2000 年代初彻底耗尽,而当时 IPv6 远未具备大规模部署条件——NAT 用架构上的妥协,为互联网换来了二十年的时间。
9. 因为 NAT 的规则是「阻止未经请求的入站分组」,而不是「阻止所有入站分组」。当内网主机 B 主动向外发出一个分组时,NAT 会为它建立一条映射并在一段时间内放行该映射对应的入站分组。打洞的做法是:让 A 和 B 同时向对方(的公网地址:端口,通过中间信令服务器交换得到)发包——各自的 NAT 都会把对方的回包视为「我方主动发起的会话的响应」而放行,双向映射同时建立,直连成功。⚠️ 这依赖 NAT 的映射行为可预测;对称型 NAT(Symmetric NAT) 为每个目的地分配不同的外部端口,因而打洞失败,只能退回 TURN 中继。