TheUnknownBlog

Back

在继续往下读之前,先花一点时间问问自己:什么是互联网协议(Internet Protocol,IP)? 除了「它就是设备拿到 IP 地址的方式」之外,你能说清它的用途,以及它到底由哪些部分组成吗?

和很多开发者一样,我对网络栈有一种「够用」的理解:我知道 TCP 提供可靠连接,UDP 用于快速收发消息,而 IP 地址是数据包要去的地方。但当我真正停下来追问一句 「互联网协议到底是什么?」 时,我发现自己卡住了。

对于 IP,我脑子里唯一能浮现的画面就是一个 IP 地址。这就带来了一个根本性的困惑:互联网协议难道只是一套关于地址的标准吗?

答案当然是否定的。在深入研究了它的体系结构之后,我终于建立起了一个能「咔哒」一下想通的心智模型。下面就是我学到的东西。

协议不等于地址

我们先来看看大名鼎鼎的 OSI 模型:

+---------------------+
| Application Layer   |  (HTTP, FTP, DNS)
+---------------------+
| Transport Layer     |  (TCP, UDP)
+---------------------+
| Network Layer       |  (IP)
+---------------------+
| Data Link Layer     |  (Ethernet, Wi-Fi)
+---------------------+
| Physical Layer      |  (Cables, Radio Waves)
+---------------------+
plaintext
为什么上面这张图只有 5 层而不是 7 层?

如果你疑惑为什么上图只画了 5 层,而不是通常说的 7 层,可以看看我的频道动态。一句话总结:人们常用一种简化的「互联网协议栈」视角,把 OSI 的会话层(Session)和表示层(Presentation)并入应用层,并把物理层(Physical)和数据链路层(Data Link)合在一起当作「链路」来看待(尽管在严格的 OSI 模型里它们是彼此独立的)。

从这个模型可以很容易地看出 IP 的职责:它工作在网络层(Network Layer),负责把数据包从一台主机跨越不同的网络路由到另一台主机。

我最大的思维障碍,是没能把地址这个名字(IP 地址)和逻辑(IP 协议)区分开。一旦你开始把 IP 理解成一套规则与流程,而不仅仅是一个标签,剩下的一切就都顺理成章了。

互联网是一堆线缆,而 IP 就是在这些线缆之间导航的逻辑。 至于 IP 地址,它是协议的一部分(就像邮政系统里的街道地址),但远不是故事的全部。我曾做过一个关于计算机网络如何为你的数据包选路的小分享,其实 Routing(路由)的逻辑也属于互联网协议的一部分。不仅如此,IP 还定义了数据包是如何组织的(IP 头部)、分片是怎么工作的(注意:过大的数据包可能需要被拆开;在以太网上 MTU 通常是 1500 字节),以及 IP 地址是如何分配的(CIDR、DHCP、SLAAC 等等)。

在我看来,邮局这个类比是网络领域里最好的心智模型之一。当你寄一封信时,邮政系统的运作方式和 IP 的工作方式如出一辙:

  • 信封:IP 协议规定了如何把数据打包成数据包,并在头部写上源地址和目的地址。
  • 寻址:全国统一的地址系统确保每个地点都有唯一的标识,这正像 IP 地址。
  • 选路:邮政系统让信件途经一个个邮局;同样地,IP 让数据包穿过一个个跨网络的路由器。

接下来,我们就带着上面这三个要素,更细致地聊聊路由。

IP 头部

互联网协议只专注于一件事:把一个数据包从计算机 A 送到计算机 B。它完全不关心这个数据包里装的是什么——它不在乎那是一段 4K 视频的片段、一场竞技游戏里的一步操作,还是一个简单的文本文件。

我们先来看看 IPv4 的 IP 头部(图片来源:UC Berkeley CS168):

IPv4 Header

头部里有这么多字段,但仔细一看,你会发现其中很多都相当有用。比如:

  • Version(版本): 表明这是 IPv4 还是 IPv6。
  • Source & Destination IP(源 / 目的 IP): 「发件人」和「收件人」地址。我们需要它们来知道把包送往何处,以及把响应送回何处。
  • TTL(Time To Live,生存时间): 防止数据包永远地在网络里兜圈子。
  • Protocol(协议): 表明负载(payload)是 TCP、UDP、ICMP 还是别的什么。这是为了在目的端对第四层协议进行解复用(demultiplexing),否则负载在收端看来就是一堆乱码。
  • Header Checksum(头部校验和): 保证头部数据的完整性。注意: 这个校验和只覆盖头部,不覆盖负载。这正是源于端到端原则(end-to-end principle):负载的完整性由两端的主机负责,而不是中间的路由器。
  • Fragmentation Fields(分片字段): 允许把大的数据包拆成更小的分片来传输,并在目的端重新组装。

IPv6 的头部则更简洁、更高效(图片来源:UC Berkeley CS168):

IPv6 Header

你可以把它看作 IPv4 头部的一次进化,它把端到端原则推得更彻底:

  • 移除了头部校验和(完全依赖上层协议来做差错检验)。
  • 简化了分片机制(只有源端负责分片,中间的路由器不再分片)。

它还通过 next header(下一个头部)字段保证了可扩展性,让扩展头部得以存在,同时又不让基础头部变得臃肿。

寻址

在谈路由之前,我们必须先问一句:设备一开始究竟是怎么拿到这些地址的?

我们早已告别了 1980 年代那套僵硬的「A/B/C 类」体系,转向了无类别域间路由(CIDR,Classless Inter-Domain Routing)。如果你想了解一下老派的 A/B/C 类体系,可以看看这条频道动态。CIDR 允许我们在任意比特边界上切分 IP 地址空间,从而创建出任意大小的子网,恰到好处地满足需求。

CIDR 记法本身并不复杂;如果你还不熟悉,这里有一个快速回顾:

一个 IP 地址是一个 32 位的数字(IPv4)或 128 位的数字(IPv6)。为简单起见,我们以 IPv4 为例。如果我们想表示一段地址(比如 10.0.0.0 到 10.0.0.255)该怎么办?当然可以写成 10.0.0.*,但这种写法在像 10.0.0.0 到 10.0.0.127 这样的例子上就失效了。于是我们改用 CIDR 记法:用 10.0.0.0/24 表示第一个例子,用 10.0.0.0/25 表示第二个。这里的 /24/25 表示地址中有多少位被固定为网络部分。如果你还是有点晕,我们把它写成二进制看看:

10.0.0.0/24 means:
00001010.00000000.00000000.00000000
|----- 24 bits fixed -----| Hosts |

The first 24 bits define the network, fixed.
The last 8 bits are free for host addresses.
This gives us 2^8 = 256 addresses (10.0.0.0 to 10.0.0.255).
We write `/24` because 24 bits are fixed for the network.

10.0.0.0/25 means:
00001010.00000000.00000000.0|0000000
|------ 25 bits fixed ------| Hosts |

The first 25 bits define the network, fixed.
The last 7 bits are free for host addresses.
This gives us 2^7 = 128 addresses (10.0.0.0 to 10.0.0.127).
We write `/25` because 25 bits are fixed for the network.
plaintext

CIDR 的妙处就在于它的灵活性。你可以在任意比特边界上切分地址空间,创建出恰好所需大小的子网。一个 /30 给你 4 个地址,而一个 /22 给你 23222=10242^{32-22}=1024 个地址。正是这种高效,让互联网得以突破僵硬的 A/B/C 类体系而不断扩展。类似地,对于 IPv6,总位长是 128 位,一个非常常见的局域网子网大小是 /64

现在来谈个实际问题:到底是谁把地址发给主机的? 这就轮到 DHCP 和 SLAAC 登场了,而它们解决的是略有不同的问题

DHCP(IPv4)

在 IPv4 中,默认的模型是 DHCP(动态主机配置协议,Dynamic Host Configuration Protocol):由一台服务器在有限的时间内把配置「租借」给客户端。

客户端拿到的通常不止一个 IP:

  • IPv4 地址
  • 子网掩码(Subnet mask)
  • 默认网关(Default gateway)
  • DNS 服务器
  • 租约时长(Lease time,以及其他选项)

你常常会看到人们用 DORA 来描述这个握手过程:

  1. Discover(发现):客户端广播「有没有 DHCP 服务器呀?」
  2. Offer(提供):服务器提供一个地址 + 各种选项
  3. Request(请求):客户端请求接受那个提供
  4. Ack(确认):服务器确认(租约自此生效)

有两个重要的运维细节很容易被忽略:

  • 租约会过期,也会续租。 客户端会在租约进行到一半左右(通常约 50%)时尝试续租,如果续租失败,它们可以通过向任意 DHCP 服务器广播来「重新绑定(rebind)」。
  • DHCP 可以借助中继跨子网工作。 广播无法穿过路由器,所以网络中常常部署一个 DHCP 中继(relay)(通常就在路由器上),它把 DHCP 请求转发给一台集中式的服务器。

这种「有状态(stateful)」的模型对管理员来说很简单:有一个统一的地方可以审计、预留静态租约以及管理各种选项。

SLAAC(IPv6)

IPv6 引入了 SLAAC(无状态地址自动配置,Stateless Address Autoconfiguration),因为这个世界需要为数以十亿计的设备编号,而不可能靠一台中心服务器去追踪每一个分配。

在 SLAAC 环境下,路由器并不逐个发放地址。取而代之的是,它周期性地发送 路由器通告(Router Advertisements,RA)(它属于 NDP/ICMPv6 的一部分),内容大致是:

  • 「这条链路的**前缀(prefix)**是这个(通常是 /64)。」
  • 「默认网关是这个。」
  • 「这些是相关的时间参数(valid lifetime、preferred lifetime)。」

随后,每台主机通过组合以下两部分,自己生成地址:

  • 通告里的前缀
  • 自己生成的接口 ID(Interface ID)(低 64 位)

出于隐私方面的考虑,这个接口 ID 并不总是从 MAC 地址推导而来。现代操作系统普遍使用临时的、随机化的地址;它会随时间轮换,以降低被追踪的风险。在主机开始使用某个地址之前,它会运行 重复地址检测(Duplicate Address Detection,DAD),确保链路上没有其他人已经在用这个地址。

所以 SLAAC 的「无状态」是指:没有任何服务器维护一份记录主机地址的租约数据库。网络只负责通告前缀,主机自己挑选地址。(在实践中,把 SLAAC 和 DHCPv6 混用也很常见:用 SLAAC 来获得地址,用 DHCPv6 来获取 DNS / 搜索域,具体取决于网络策略。)

DHCPv6 前缀委派(PD)

下面这块拼图,正是让家庭 IPv6 与家庭 IPv4 体验大不相同的关键所在:在 IPv4 时代,家庭网络常常依赖 NAT——你的路由器只拿到一个公网 IP,然后把其他所有设备都藏在它后面。而在 IPv6 中,NAT 是不被鼓励的;虽然你确实可以在 OpenWRT 之类的路由器上配置 NAT66,但这并不是常规做法(我是说,IPv6 地址多得是,何必折腾呢?)。

IPv6 的目标是恢复端到端寻址,所以你家的路由器需要的不只是一个地址,而是一整个地址块,大到足以切分成多个 /64 的局域网(访客 Wi-Fi、IoT VLAN 等等)。这正是 PD 的用武之地。

可以把它理解成一个两级的过程:

  1. ISP -> 路由器(DHCPv6-PD): 你的路由器通过一个 **IA_PD(Identity Association for Prefix Delegation,前缀委派的身份关联)**来请求一个前缀。ISP 会「委派」给你的路由器一个诸如 /56/60 的块。
  2. 路由器 -> 局域网主机(通过 RA 进行 SLAAC): 你的路由器拿到那个被委派的块后,为每个局域网段各选出一个 /64,并通过 **路由器通告(RA)**逐一通告这些 /64。主机随后用 SLAAC 自行分配地址。

路由

那么,一个数据包究竟是怎样横跨大洋、找到去路的呢?路由器依据一种叫做**最长前缀匹配(Longest Prefix Match,LPM)**的逻辑来做决策。路由器并不会去记住地球上的每一个 IP 地址,相反,它记住的是「前缀」(回想我们上面讨论的 CIDR)。如果一个数据包同时匹配路由表里的多个条目,路由器总会挑选最具体的那一个(比如,若 /24 和 /22 都匹配,它会选择 /24 的路由)。

这些表又是从哪儿来的呢?这就是路由协议的领域了。根据作用范围的不同,它们被分成两个截然不同的家族:IGP 和 BGP。

IGP

内部网关协议(Interior Gateway Protocols,IGP)工作在单个 AS(自治系统,Autonomous System)内部。

什么是自治系统?

请记住:我们把整个互联网看成一个「网络的网络」。它不是一个中心化的系统,而更像一个联邦式的系统。在每一个小网络内部,我们可以有自己的路由策略。为了管理这种复杂性,人们引入了自治系统(AS)这一概念。

自治系统(AS)是由单一组织控制、并对外呈现统一路由策略的一组 IP 网络与路由器的集合。每个 AS 都会被分配一个唯一的 AS 编号(ASN)以作标识。

主要有两大类 IGP 协议:

  • 距离矢量协议(Distance-Vector Protocols)
  • 链路状态协议(Link-State Protocols)

你很可能完全不知道上面这两种协议在讲什么,这很正常。但在深入展开之前,先让我给你一个快速的总结:

  • 距离矢量协议是通过周期性地获取邻居的路由表,来得到对整个网络的全局视图。
  • 链路状态协议是通过本地计算、并向全网洪泛(flooding)链路状态通告,来得到对整个网络的全局视图。

也许上面的解释很糟糕 😢,也许你还是没抓住要点。别担心,希望你能从下面的细节里把它弄明白。

距离矢量协议

我们先看一张图,来理解距离矢量协议是怎么工作的:

Distance Vector

在距离矢量协议中,每台路由器维护一张表(即「矢量」),其中列出了到每个目的地的已知最优距离,以及到达该目的地的下一跳(next hop)。每隔一段时间,每台路由器就把自己的表分享给它的直接邻居。形式化地,我们可以这样描述更新过程:

  • 当你听说有一条通往某个目的地的路径时,在以下情况下更新你的表:
    • 你还没有任何到达该目的地的路径,或者
    • 这条新路径比你当前已知的路径更短。
  • 把更新后的表告诉你的邻居。

这个过程正是我们上面说的「周期性地获取邻居的路由表」。路由器通过「邻居给出的结果」+「到达该邻居的开销」来得到到每个目的地有多远

但你或许已经注意到,这个过程存在一些问题。最主要的问题是:如果一条链路断了,可能要过很久所有路由器才会意识到这条路径已经失效,并找到新的最优路径。

针对上述问题,一个简单的优化是加入毒化报文(poison packets)机制。当一台路由器检测到某条链路断开时,它会立即告知邻居:到受影响目的地的距离是无穷大(或某个非常大的数)。但加入这种报文也会引入一些我们此前没遇到过的问题(这里我不打算展开)。如果你想看看加入毒化报文之后的最终算法,它在这里:

  • 当你听到关于某个目的地的一条通告时,在以下情况下更新表并重置计时器:
    • 该目的地不在表中,或者
    • 通告里的开销 + 链路开销优于已知的最优开销,或者
    • 该通告来自当前的下一跳(包括毒化通告)。
  • 当表发生变化时,向邻居通告更新;并周期性地通告。
    • 不要把更新通告回给下一跳(水平分割,split horizon),或者把毒化信息通告回去(毒性逆转,poison reverse)。
    • 任何 ≥ 某个阈值(例如 RIP 中的 16)的开销都被视为无穷大。
  • 如果某条表项过期了,把它标记为毒化并通告出去。

链路状态协议

距离矢量传播的是结果(「这是我到各处的最优距离」)。链路状态传播的是事实(「这是我直接相连的对象」)。看下面这张图:

Link State

在链路状态协议(OSPF / IS-IS)中,每台路由器做三件大事:

  • 发现邻居
    路由器之间交换 hello 报文,以找到相邻的路由器并建立邻居关系。

  • 洪泛链路状态通告
    每台路由器通告它本地链路的状态与开销(例如「我有一条到 R2 的链路,开销为 10」)。
    这些 LSA 会被洪泛(不断向外转发),直到收敛。

  • 运行 Dijkstra 算法
    一旦所有路由器都拥有相同的拓扑数据库,每台路由器就独立地运行 Dijkstra 算法,计算出一棵以自身为根的最短路径树。
    计算结果就成为该路由器的转发表项(「要到达前缀 X,下一跳是 Y」)。

相比距离矢量,这种方式有几个优势:

  • 收敛更快: 由于路由器拥有完整的拓扑视图,链路状态协议能更快地对变化做出反应。
  • 路由逻辑一致: 所有路由器计算出的是同一棵最短路径树,因而更不容易出现路由环路。

不过它也有取舍,一个主要问题是它消耗更多的 CPU / 内存: 维护一个数据库并运行 Dijkstra,比基础的距离矢量要重得多。

BGP

IGP 关心的是单个组织内部「按某种度量得出的最优路径」。BGP 关心的则是策略(policy),而且是跨组织的策略。请记住,网络是联邦式的:没有任何单一实体控制整个互联网。每个 AS 对于接受、偏好或通告哪些路由,都有自己的策略。要把数据包跨越 AS 边界传输,就必须遵守这些策略。

BGP(边界网关协议,Border Gateway Protocol)是互联网的域间路由协议。它常被描述为一种**路径矢量(path-vector)**协议:

为什么不用链路状态?

回想一下,在链路状态协议中,每台路由器都向全网洪泛链路状态通告,以构建出完整的拓扑图。从隐私的角度看,这对 BGP 来说是无法接受的。每个 AS 都想让自己的内部拓扑与客户信息保持私密。因此,BGP 只共享可达性信息(哪些前缀可以到达)以及一组路径属性,而不暴露 AS 的内部结构。

从宏观上看,全球互联网由许许多多的 AS 组成(ISP、云服务商、企业、高校等)。每个 AS 在内部可以运行自己的 IGP(OSPF / IS-IS 等)。而在边界处,各个 AS 用 BGP 来交换各自能够到达哪些前缀。

关于 peering 与 transitiBGP 与 eBGP热土豆路由(hot-potato routing)的更多细节,请参阅我那个关于计算机网络如何为你的数据包选路的小分享。这里我就略去这些细节了。

什么是互联网协议(IP)?
https://20051110.xyz/blog/internet-protocol/zh
Author TheUnknownThing
Published at January 27, 2026
Comment seems to stuck. Try to refresh?✨
浙ICP备2025146421号-1 浙公网安备33010502012185号