> For the complete documentation index, see [llms.txt](https://book.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://book.bsdcn.org/4.3-bsd-unix-cao-zuo-xi-tong-she-ji-yu-shi-xian/di-si-bu-fen-jin-cheng-jian-tong-xin/12.3.-wang-ji-xie-yi-ip.md).

# 12.3 网际协议（IP）

考察过简单的传输协议之后，我们继续讨论网络层协议 \[Postel, 1981a; Postel et al., 1981]。网际协议（IP）负责主机到主机的编址与路由、分组转发、分组的分片与重组。与传输协议不同，它不一定代表本地主机上的某个套接字运作：它可能转发分组、接收没有本地套接字的分组，或针对这些情况产生错误分组。

IP 的功能由其分组头的内容体现，见图 12.6。头部标识源和目的主机及目的地协议，含头部长度和分组长度。标识与分片字段用于分组或分片必须拆小以便下一跳传输时，以及分片到达目的地后重组时。分片标志有 **不可分片**（Don't Fragment）和 **还有分片**（More Fragments）；后者加分片偏移足以在目的地组装出原始分组。

```sh
图 12.6 网际协议头（IHL 为以 4 个八位组为单位计的 Internet 头部长度；
选项以 IHL 界定）
 0               3 4               7 8             15 16            31
| version | IHL  | type of service |        total length            |
|                ID                |  fragment flags and offset     |
| time to live    | protocol       |        header checksum         |
|                        source address                            |
|                      destination address                          |
|                          options …                                |
```

头部长度字段的值大于最小值时，IP 分组中存在 IP 选项。 **空操作** 选项和 **选项表结束** 选项各长一个八位组；其余选项都自编码，类型和长度在附加数据之前。主机和网关由此能跳过自己不实现的选项。既有选项的例子有 **时间戳** 和 **记录路由** 选项——每个转发分组的网关都更新它们，以及 **源路由** 选项——提供到目的地的完整或部分路由。

**输出**

IP 输出例程的调用约定已经见过：

```c
    error = ip_output(m, opt, ro, flags);
        struct mbuf *m, *opt;
        struct route *ro;
        int flags;
```

如前所述，参数 `m` 是装待发分组的 mbuf 链（含一个 IP 骨架头）；`opt` 是可选的、装要插在头部之后的 IP 选项的 mbuf。给出路由 `ro` 时，它是对一个路由表项（`rtentry` 结构）的引用，可能含有上次调用到目的地的路由，新路由也将留在其中备将来使用。`flags` 可以允许使用广播，或指示应绕过路由表。

`ip_output()` 所做工作的轮廓如下：

* 插入任何 IP 选项
* 填好其余头部字段（IP 版本、零偏移、头部长度和新的分组标识）
* 确定路由（即外出接口和下一跳目的地）
* 检查目的地是否广播地址；是则检查是否允许广播
* 分组大小不超过外出接口的最大分组大小时，计算校验和并调用接口输出例程
* 分组大小超过外出接口的最大分组大小时，把分组拆成分片逐个发送

细看路由这一步。首先，没有路由表项作为参数传入时，临时使用一个内部路由表项。调用方传入的路由表项要检查它确是到同一目的地的路由、且仍然有效；任一测试不过，旧路由即释放。这些检查之后若无路由，调用 `rtalloc()` 分配。返回的路由含指向外出接口信息的指针；这些信息包括最大分组大小、含广播能力的标志和输出例程。路由带 RTF\_GATEWAY 标志时，下一跳网关的地址由路由给出；否则分组的目的地就是下一跳目的地。因 MSG\_DONTROUTE 选项（见 10.1 节）或 SO\_DONTROUTE 选项而要绕过路由时，寻找与目的地共享的直接相连网络；没有直接相连网络则返回错误。外出接口和下一跳目的地找到后，发送分组的信息就齐了。

如第 11 章所述，接口输出例程通常校验目的地址、把分组放上输出队列，只在接口宕掉、输出队列满或目的地址无法理解时返回错误。

**输入**

第 11 章描述过网络接口接收分组并放进相应协议的输入队列。网络接口处理程序随后在网络状态字中置相应位、安排 **软件中断**，调度协议运行。网络接口收到 Internet 协议的消息时，IP 输入例程经此软件中断被调用，因此调用时不带参数。输入例程 `ipintr()` 一次一个地从输入队列摘下分组处理到底。分组的处理以四种方式之一完成：作为输入交给更高层的协议；遇到错误并报告回源；因错误被丢弃；或沿通往目的地的路径被转发。提纲挈领地说，IP 分组输入的处理步骤如下：

1. 验证分组至少有 IP 头那么长，用 `m_pullup()` 使头部连续。
2. 对分组头做校验和，有错则丢弃分组。
3. 验证分组至少有头部声称的长度，不足则丢弃；裁掉分组末尾的任何填充。
4. 处理头部中的任何 IP 选项。
5. 检查分组是否发给本主机。是则继续处理；否则，若在做 IP 分组转发，尝试转发；再否则丢弃分组。
6. 分组已被分片时，保留到全部分片收齐并重组，或到它老得留不得。
7. 把分组交给下一个更高层协议的输入例程。

分组从输入队列摘下时，附带收到它的接口的指示。这一信息传给下一个协议、转发函数或错误报告函数。检测到错误并要报告给分组的始发者时，错误报文的源地址按分组的目的地和到来接口设置。

是否接收一个分组的决定不像看上去那么简单。主机有多个地址时，分组的目的地与其中一个匹配即接收。任一所接网络支持广播且目的地是广播地址时，分组也被接收。（出于 12.1 节给出的原因，一个给定的网络可能有多达五个可能的广播地址。）

IP 输入例程用一个简单高效的方案定位入局分组接收协议的输入例程。IP 分组中的协议字段 8 位长，因此可能有 256 个协议。实际定义或实现的协议不足 256 个，Internet 协议开关的表项更是远少于 256。于是 IP 输入用一个 256 元素的映射数组，把协议号映射到接收协议的协议开关表项。数组的每个表项最初设为协议开关中 “裸” IP 表项的下标；然后对系统中每个有独立实现的协议，把相应映射表项设为该协议在 IP 协议开关中的下标。收到分组时，IP 径直用协议字段索引映射数组，用该位置的值作接收协议在协议开关表中的下标。

**转发**

Internet 协议的传统实现要么供主机、要么供网关使用，不供两者通用：一个系统要么是 IP 分组的端点（作源或目的地），要么是网关（在不同网络的主机之间转发分组，上层协议只用于维护功能）。传统主机系统不含分组转发功能；收到不发给自己的分组就丢弃。4.2BSD 是第一个试图在正常运行中同时提供主机和网关服务的常见 IP 实现。这一做法有利有弊：接多个网络的 4.2BSD 主机可以身兼网关和主机，减少对专用网关机器的需求——早年的网关既不便宜也不算强大；另一方面，普通主机带网关功能，使配置错误更易在所接网络上惹出麻烦。最严重的问题是分组目的地被发送方或接收方误解时的广播分组转发。4.3BSD 的折中是：主机上有多个网络接口才启用分组转发的网关功能，且这些功能随时可以停用；只有一个网络接口的主机绝不尝试转发分组或响应误投分组返回错误报文——除非显式配置成网关（那样它被视为接口损坏的网关）。结果，能把同步或重复的广播（“广播风暴”）带上本地网络的配置错误大为减少。

主机收到发往另一主机的 IP 分组后的转发过程如下：

1. 检查转发已启用；未启用则丢弃分组。
2. 检查目的地址允许转发。发往网络 0 或网络 127（官方环回网络）的分组、发往不在 A、B、C 类网络上的地址的分组，都不得转发。
3. 至多保存收到报文的 64 个八位组，以备需要产生错误报文作响应。
4. 确定转发分组所用的路由。
5. 外出路由与收到分组的接口相同、且始发主机就在该网络上时，向始发主机发 ICMP 重定向报文（ICMP 见 12.8 节）。
6. 调用 `ip_output()` 把分组发往目的地或下一跳网关。
7. 检测到错误时，向源主机发 ICMP 错误报文。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://book.bsdcn.org/4.3-bsd-unix-cao-zuo-xi-tong-she-ji-yu-shi-xian/di-si-bu-fen-jin-cheng-jian-tong-xin/12.3.-wang-ji-xie-yi-ip.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
