网络-03 IP 协议
前置阅读:网络-02 链路层 本文只讲 IP 协议本身和地址规划,网关、路由与 NAT 见 网络-05,IP 分片与 MTU 见 网络-04。
1. 网络层的职责
网络层负责在不同网络之间尽力转发数据包,依据的是数据包里的 IP 地址。
注意"尽力"(best-effort)这个词:网络层不负责丢失重传,也不负责顺序,丢了就丢了、乱了就乱了,这些都是上层传输层该干的事。理解这一点是理解 TCP 为什么要做那么多工作的前提。
经常看到下图,有些概念还是要弄清楚的。

2. IP
电脑之间要实现网络通信,就必须要有一个合法的ip地址。IP地址 = 网络地址 + 主机地址,(又称:主机号和网络号组成)ip地址的结构使我们可以在Internet上很方便的寻址。ip地址通常用更直观的,以圆点分隔号的四个十进制数字表示,每个数字从0到255,如某一台主机的ip地址为:128.20.4.1在局域网里,同样也需要ip地址,一般内网的ip地址是以192.168开头的,这样很容易区分公网和内网的ip地址。
ip对于一台计算记得的意义可以举个形象的例子:
“幸福小区”有若干住户,每个住户都有门牌号,范围是0-255,我们若在小区里要找5号,显然很简单就找到了。但是如果我们在大街上找“幸福小区”肯定就蒙了,因为我们压根不知道去哪个小区的地址。所以小区本身也需要一个标识。
网络地址:小区的编号,即主机号部分全为 0 的那个地址,比如 192.168.0.0 主机地址:门牌号,比如 0.0.0.1 IP地址:网络地址+主机地址,192.168.0.1
注意:网络地址不是"第一个住户的地址",它标识的是整个小区本身,不能分配给任何一台主机。 同理主机号全为 1 的地址(如 192.168.0.255)是该网段的广播地址,也不能分配。这就是下文"主机号全0和全1的不让用"、可用主机数要减 2 的原因。一个 /24 网段里真正能给主机用的是 192.168.0.1 ~ 192.168.0.254,共 254 个。
3. IP 地址的分级(历史知识)
主机号为啥要减去2呢?主机号全0和全1的不让用。
A/B/C 类地址是早期互联网的分类方式,现在地址分配和路由都使用下文的 CIDR。面试可能仍会问分类地址,但工程中不要再按“A 类网、B 类网”设计网络。
4. 子网
IP的有了分级,但是会造成IP地址的浪费和不灵活。比如有的会申请一个B类IP地址,但是主机目前并没有这么多。再比如IP不够了,还需要再申请。
子网就是把主机地址的一部分再分割。
申请到IP地址后要不要划分子网及怎么划分是你自己的事情。但是要注意,主机号至少要留2位。
外网并不知道你子网的情况,他们看到的只是网络号。
5. 子网掩码
用来知道子网是多少的。
最为简单的理解就是两台计算机各自的ip地址与子网掩码进行&运算后,得出的结果是相同的,则说明这两台计算机是处于同一个子网络上的,可以进行直接的通讯。
举例:
A的ip为192.168.0.1,子网掩码为255.255.255.0
B的ip为192.168.0.200,子网掩码为255.255.255.0
ip,子网掩码分别换算为2进制,进行&运算,结果一致则在同一网络,可以进行互连。

6. CIDR 表示法
上面用的 255.255.255.0 这种点分十进制写法比较啰嗦。现在通行的是 CIDR(Classless Inter-Domain Routing,无类别域间路由)记法,直接在 IP 后面用斜杠加上网络号的位数:
192.168.0.1/24 等价于 IP=192.168.0.1,掩码=255.255.255.0
10.0.0.0/8 等价于 掩码=255.0.0.0
172.16.0.0/12 等价于 掩码=255.240.0.0
/24 的意思就是"前 24 位是网络号,剩下 8 位是主机号"。换算很简单——掩码里有几个 1,斜杠后面就是几:
| CIDR | 子网掩码 | 可用主机数 | 常见用途 |
|---|---|---|---|
| /8 | 255.0.0.0 | 16,777,214 | A 类私有网段 10.0.0.0/8 |
| /16 | 255.255.0.0 | 65,534 | B 类,K8s 常见 Pod 网段 |
| /24 | 255.255.255.0 | 254 | 最常见的局域网 |
| /30 | 255.255.255.252 | 2 | 点对点链路 |
| /32 | 255.255.255.255 | 1 | 单个主机(如路由/防火墙规则) |
一般子网的可用主机数 = 2^主机位数 - 2,减 2 就是上文说的网络地址和广播地址。两个常见例外是:点对点链路可使用 /31(RFC 3021),而 /32 表示一条精确的主机路由,不适用“减 2”公式。
CIDR 的意义不只是写法更短。它带来两个关键能力:
- 摆脱了 A/B/C 类的僵化划分(这就是名字里 Classless 的含义)。老的分类法只能按 8 位为单位切,要么给你 254 个地址(C 类)要么给你 65534 个(B 类),中间没有档位,浪费极其严重。CIDR 可以按任意位数切。
- 路由聚合(Route Aggregation)。相邻的多个网段可以合并成一条路由表项对外宣告,比如
192.168.0.0/24和192.168.1.0/24可以聚合成192.168.0.0/23。这大幅压缩了骨干路由器的路由表规模,是 IPv4 能撑到今天的重要原因之一。
日常工作里 CIDR 到处都是——K8s 的 podCIDR、serviceCIDR,云厂商 VPC 的网段规划,安全组和防火墙规则,全都用这个记法。
7. IP 数据报格式
由首部和数据两部分组成.首部的前一部分是固定长度,共 20 字节,是所有IP数据报必须具有的.在首部的固定部分的后面是一些可选字段,其长度是可变的。ipv6已经将首部长度改为固定的,去掉可选部分。
版本:占4位,指IP协议的版本.通信双方使用的IP协议版本必须一致.目前公网普遍同时存在 IPv4 与 IPv6,服务端通常以双栈方式对外提供服务。
首部长度:占 4 位。
总长度:总长度指首都及数据之和的长度,单位为字节.因为总长度字段为 16位,所以数据报的最大长度为 216-1=65 535字节.在IP层下面的每一种数据链路层都有自己的帧格式,其中包括帧格式中的数据字段的最大长度,即最大传送单元 MTU (Maximum Transfer Unit).当一个数据报封装成链路层的帧时,此数据报的总长度 (即首部加上数据部分)一定不能超过下面的数据链路层的MTU值,否则要分片.
标识 (Identification):占 16位.IP软件在存储器中维持一个计数器,每产生一个数据报,计数器就加 1,并将此值赋给标识字段.但这个"标识"并不是序号,因为 IP是无连接的服务,数据报不存在按序接收的问题.当数据报由于长度超过网络的 MTU 而必须分片时,这个标识字段的值就被复制到所有分片后的数据报的标识字段中.相同的标识字段的值使分片后的各数据报片最后能正确地重装成为原来的数据报.
标志 (Flag):占3 位,但目前只有2位有意义. 标志字段中的最低位记为 MF(More Fragment).MF=1即表示后面"还有分片"的数据报.MF=0表示这已是若干数据报片中的最后一个.标志字段中间的一位记为DF(Don’t Fragment),意思是"不能分片",只有当 DF=0时才允许分片.
片偏移:占 13位.较长的分组在分片后,某片在原分组中的相对位置.也就是说,相对用户数据字段的起点,该片从何处开始.片偏移以 8个字节为偏移单位,这就是说,每个分片的长度一定是 8字节(64位)的整数倍.
生存时间:占 8位,生存时间字段常用的英文缩写是TTL(Time To Live),其表明数据报在网络中的寿命.
协议:占 8 位.协议字段指出此数据报携带的数据是使用何种协议,以便使目的主机的IP层知道应将数据部分上交给哪个处理过程。比如交给tcp还是udp。TCP的协议号为6,UDP的协议号为17。ICMP的协议号为1,IGMP的协议号为2.
首部检验和:占 16 位。只检验首部,不检验数据部分。这样做是因为数据报每经过一个路由器,首部里的 TTL、标志、片偏移等字段都可能改变,检验和必须重新计算,把数据也算进来开销太大;数据部分的完整性交由传输层的检验和负责。IPv6 已经彻底去掉了这个字段,把差错检测的职责完全下放给链路层和上抬给传输层。
源地址:占 32 位。发送方的 IP 地址。
目的地址:占 32 位。接收方的 IP 地址。注意这两个地址在整条转发路径上是始终不变的(NAT 场景例外),而链路层帧首部里的源/目的 MAC 地址每经过一跳都会被改写——这是理解"IP 负责端到端寻址、MAC 负责逐跳转发"的关键。
可选字段:长度可变,从 1 个字节到 40 个字节不等,用来支持排错、测量以及安全等措施(如记录路由、时间戳)。实际中很少使用,且因为长度可变增加了路由器处理首部的复杂度,IPv6 索性把首部长度固定为 40 字节,把可选内容改用"扩展首部"承载。
填充:为了保证首部长度是 4 字节的整数倍,末尾用全 0 填充。
8. IPv6:服务端必须掌握的部分
IPv6 不只是“把地址从 32 位加长到 128 位”。它重新设计了首部、邻居发现和分片方式。对 Go 服务端开发者来说,最实际的影响是:监听地址、URL、DNS、访问控制和排障工具都要能处理 IPv6。
8.1 IPv6 基础首部格式
IPv4 首部最短 20 字节、长度可变;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 位) |
+---------------------------------------------------------------+
| Destination Address(128 位) |
+---------------------------------------------------------------+
| 字段 | 长度 | 作用 |
|---|---|---|
| Version | 4 位 | 固定为 6 |
| Traffic Class | 8 位 | 类似 IPv4 的 DSCP/ECN,用于服务质量和拥塞通知 |
| Flow Label | 20 位 | 标识同一条流,便于网络设备一致处理 |
| Payload Length | 16 位 | 基础首部之后的负载长度,包含扩展首部 |
| Next Header | 8 位 | 指向下一个扩展首部,或 TCP(6)、UDP(17)、ICMPv6(58) |
| Hop Limit | 8 位 | 等价于 IPv4 的 TTL,每经过一跳减 1 |
| Source/Destination | 各 128 位 | 源地址和目的地址 |
和 IPv4 相比,IPv6 基础首部删掉了首部长度、首部校验和、分片字段和可选字段。需要的附加能力由链式的扩展首部承载。路由器处理固定格式的基础首部更简单,也不必每跳重算首部校验和。
8.2 地址写法与常见类型
IPv6 用 8 组十六进制数表示,每组 16 位:
2001:0db8:0000:0000:0000:ff00:0042:8329
书写时可以去掉每组前导 0,并用一次 :: 压缩最长的连续 0:
2001:db8::ff00:42:8329
常见地址必须认识:
| 地址 | 含义 |
|---|---|
::1/128 |
回环地址,相当于 IPv4 的 127.0.0.1 |
::/128 |
未指定地址 |
::/0 |
默认路由 |
fe80::/10 |
链路本地地址,只在当前二层链路有效 |
fc00::/7 |
唯一本地地址(ULA),用途近似 IPv4 私网 |
ff00::/8 |
组播地址;IPv6 没有广播 |
2000::/3 |
当前全球单播地址的主要分配范围 |
IPv6 地址放进 URL 时必须加方括号,否则地址里的冒号会和端口分隔符冲突:
http://[2001:db8::10]:8080/health
链路本地地址还可能带 zone,例如 fe80::1%eth0,因为同一个主机的多块网卡上都可能出现相同的链路本地地址。
8.3 NDP 取代 ARP
IPv6 不使用 ARP,而使用 NDP(Neighbor Discovery Protocol)。NDP 基于 ICMPv6,除了完成“IPv6 地址 → MAC 地址”的邻居解析,还负责:
- 路由器发现和前缀发现;
- 邻居可达性检测;
- 重复地址检测(DAD);
- 无状态地址自动配置(SLAAC)。
查看邻居表:
ip -6 neigh
因此 ICMPv6 不能像普通 ICMP 那样被一刀切全部禁止。错误地拦截 ICMPv6 会让邻居发现、地址配置和路径 MTU 发现一起失效,表现为“地址看起来正常,但连接莫名超时”。
8.4 IPv6 不允许路由器分片
IPv4 路由器在 DF=0 时可以沿途分片;IPv6 中,中间路由器绝不分片。包太大时,路由器丢包并返回 ICMPv6 Packet Too Big,由源主机降低包大小。
如果确实需要分片,只能由发送端加入 Fragment 扩展首部。这让 PMTUD 对 IPv6 更重要:防火墙拦截 ICMPv6 Packet Too Big,很容易形成 MTU 黑洞——小请求正常,大响应或 TLS 握手卡死。详见 网络-04 MTU 与 MSS。
8.5 Go 服务的双栈注意事项
Go 中若只想监听某个地址族,明确使用 tcp4 或 tcp6:
ln4, err := net.Listen("tcp4", "0.0.0.0:8080")
ln6, err := net.Listen("tcp6", "[::]:8080")
net.Listen("tcp", ":8080") 在许多系统上会创建同时接受 IPv4 和 IPv6 的双栈 socket,但这受操作系统及 IPV6_V6ONLY 设置影响。生产环境不要靠猜,启动后用 ss -lnt 确认;需要严格行为时就分别监听 tcp4、tcp6。
处理地址时,新代码优先使用 net/netip:
addr, err := netip.ParseAddr("2001:db8::10")
if err != nil {
return err
}
fmt.Println(addr.Is6(), addr.String())
netip.Addr 可比较、可作为 map key,而且不会混淆“16 字节 IPv4 表示”和真正的 IPv6。做白名单和 CIDR 判断时使用 netip.Prefix.Contains:
prefix := netip.MustParsePrefix("2001:db8::/32")
allowed := prefix.Contains(addr)
域名可能同时返回 A 和 AAAA。客户端通常使用 Happy Eyeballs 并行或错峰尝试 IPv6/IPv4,避免其中一个地址族故障时长时间等待。因此排障时要分别验证:
curl -4 https://example.com
curl -6 https://example.com
dig A example.com
dig AAAA example.com
只测域名、不区分地址族,可能把“只有 IPv6 路径坏了”误判成随机网络抖动。
9. ICMP 与 IGMP
9.1 ICMP 协议
ICMP协议是网络层在ip上层的协议,主要用于传递差错报告和网络诊断信息。 ping 命令就是它的典型应用——它发送 ICMP Echo Request 并等待 Echo Reply,用来探测目标是否可达以及往返时延。(注意 ping 是连通性探测工具,不是抓包工具;抓包分析用的是 tcpdump、Wireshark 这类嗅探器。) traceroute 也是基于 ICMP:它通过逐步递增 TTL,让路径上每一跳路由器在 TTL 归零时回送 ICMP 超时报文,从而把整条路径探出来。 ping命令结果中有ttl,其含义是每经过一个路由器ttl就减少1,当减少到0的时候就不往下传了,这样可以避免路由循环占用流量。ttl的开始数据根据操作系统不同而不同。
9.2 IGMP 协议
IGMP 用于 IPv4 主机向本地路由器报告自己加入或离开了哪个组播组。它只管理“成员关系”,不负责传输组播业务数据,也不负责路由器之间的组播路由。
IPv6 不使用 IGMP,对应能力由 **MLD(Multicast Listener Discovery)**提供;MLD 本身是 ICMPv6 的一部分。
10. 本章面试题
子网掩码的作用是什么?怎么判断两台机器在不在同一网段?
子网掩码用来区分 IP 地址中哪部分是网络号、哪部分是主机号。
判断方法:两台机器的 IP 分别与子网掩码做按位与(AND)运算,结果相同则在同一网段,可以直接通信;不同则必须经过网关(见 网络-05)。
例:192.168.0.1/24 与 192.168.0.200/24 做与运算都得 192.168.0.0,同网段。
CIDR 是什么?/24 代表什么?为什么它很重要?
CIDR(无类别域间路由)用 IP/前缀长度 表示网络,斜杠后的数字是网络号的位数。/24 即前 24 位是网络号,等价于掩码 255.255.255.0。
一般 IPv4 子网的可用主机数 = 2^主机位数 - 2(减去网络地址和广播地址)。/24 有 254 个可用地址;/31 点对点链路和 /32 主机路由是例外。
两个关键价值:
- 摆脱 A/B/C 类的僵化划分(名字里 Classless 的含义)。老分类法只能按 8 位切,要么 254 个要么 65534 个,中间没档位,浪费严重;
- 路由聚合——相邻网段可合并成一条路由宣告(
192.168.0.0/24+192.168.1.0/24→192.168.0.0/23),大幅压缩骨干路由表,是 IPv4 撑到今天的重要原因。
日常处处可见:K8s 的 podCIDR/serviceCIDR、VPC 网段规划、安全组规则。
IPv6 首部与 IPv4 相比有哪些关键变化?
- 地址从 32 位扩展为 128 位;
- 基础首部固定 40 字节,可选功能移到扩展首部;
- 删除首部校验和,路由器不用每跳重算;
TTL改名为Hop Limit,作用相同;Protocol改为Next Header,既可指向 TCP/UDP,也可串联扩展首部;- 中间路由器不能分片,包过大时回 ICMPv6
Packet Too Big。
服务端还要记住:IPv6 使用 NDP 取代 ARP,没有广播,地址放在 URL 中必须写成 [2001:db8::1]:8080。
Go 服务怎样避免 IPv4/IPv6 双栈问题?
需要确定行为时,分别使用 net.Listen("tcp4", "0.0.0.0:8080") 和 net.Listen("tcp6", "[::]:8080");不要假设 net.Listen("tcp", ":8080") 在所有系统上的双栈行为相同。
排障时用 curl -4/-6 和 dig A/AAAA 分别验证两个地址族。地址解析、比较和 CIDR 白名单优先使用 net/netip,避免字符串切割和 net.IP 表示差异带来的 bug。
为什么主机号全 0 和全 1 不能用?
- 主机号全 0 → 是网络地址,标识整个网段本身(如
192.168.0.0); - 主机号全 1 → 是广播地址,发给该网段所有主机(如
192.168.0.255)。
两者都不能分配给具体主机,所以可用主机数要减 2。一个 /24 网段真正可用的是 .1 到 .254。
IP 首部的检验和为什么只校验首部,不校验数据?
因为数据报每经过一个路由器,首部里的 TTL、标志、片偏移等字段都可能改变,检验和必须重新计算。如果把数据部分也算进来,每一跳都要对整个包做一遍计算,开销太大。
数据部分的完整性交由传输层的检验和负责(TCP/UDP 首部都有自己的检验和)。
补充:IPv6 已彻底去掉这个字段,把差错检测完全下放给链路层、上抬给传输层。
ICMP 是什么?ping 和 traceroute 的原理?
ICMP 是网络层协议,用于传递差错报告和网络诊断信息,本身不传输用户数据。
- ping:发送 ICMP Echo Request,等待 Echo Reply,用来探测目标可达性和往返时延(RTT)。注意 ping 是连通性探测工具,不是抓包工具。
- traceroute:逐步递增 TTL(1、2、3…),让路径上每一跳路由器在 TTL 归零时回送 ICMP 超时报文,从而把整条路径探出来。
注意:ICMP 常被防火墙禁止,所以 ping 不通不代表端口不通,排查时应该用 nc -zv 或 telnet 测端口(见 网络-22)。
xingliuhua