# Internet Protocol (IP)

## 报文传送 Datagram Delivery

报文就是可以在网络中传输的一种信息包(packet)，因为每个报文中都包含了完整的信息，所以接收到报文的路由从报文本身就可以知道该如何将它转发到目的机去。

源机只管把报文发送出去，IP 会尽力把报文传送到目的机；至于报文最终有没有到达目的机，在网络传输过程是否发生报文丢失、篡改、错传、重复、乱序、延时等问题，IP 一概不管，也不会做任何修复行为，因此这个服务模型也被称为**尽力模型 best-effort model**，有时也叫**不可靠服务 unreliable service**。

**IP 为什么要设计成"不可靠的"呢？**

因为 IP 所连接的网络可能是不可靠的，另外，IP 要设计成可以适应各种网络的。

如果 IP 要提供可靠的服务，那么有两种情况：

1. 如果 IP 下层的网络提供的是可靠的服务，那 IP 就不需要做什么额外的工作了；
2. 如果 IP 下层的网络提供的是不可靠服务，那 IP 就得自行增加一些功能来保证服务的可靠性；但几乎网络中的所有路由都实现了 IP，也就是说，每一个路由都需要实现额外的功能，这就跟 IP 的设计初衷不符了；

## 报文格式 Packet Format

![IPv4\_headers](https://cdn.jsdelivr.net/gh/suukii/Articles/assets/network/IPv4_headers.png)

* Version：IP 版本。
* HLen：头部长度，是以 32-bit word 为单位来表示的，如果头部没有包含 Options 字段，这个值就是 5，也就是 20 bytes(32 \* 5 / 8)。
* TOS(type of service)：区分服务，一般是为了让程序知道报文是否需要特殊处理，比如报文是否需要放到一个特殊队列中以保证无延时传送。
* Length：报文的长度，包括了头部和数据，跟 HLen 不同，Length 的计算单位是 byte，这个字段占据 16 bit，也就是说，IP 报文的最大长度是 65,535 bytes (2^16 - 1)；不过，IP 下层的网络不一定支持传送这么大的报文，因此 IP 还需要提供信息包的分割和重组功能。
* Ident、Flags、Offset：这几个字段都是与分组相关的信息。
* TTL(Time to Live)：包的生存时间，目的是分辨出在网络中游离时间过长的包，并将它们丢弃，以免浪费资源。这个值每经过一个路由就会减去 1，等减至 0 的时候包就会被丢弃，目前的默认设置值是 64。
* Protocol：demultiplexing key，用来决定由哪种上层协议来接收经过 IP 处理的报文。
* Checksum：是通过将头部分成 16-bit words 后计算得到的值，目的机接收到报文后会重新计算一遍，如果在传输过程中头部被污染，目的机计算的值和源机计算的值不一样，这个报文就会被丢弃；注意到 Checksum 并不会检查报文的具体数据是否被污染。
* SourceAddr：用来让目的机决定是否接受这个包，以及目的机作出响应时也需要使用这个字段。
* DestinationAddr：用来找到目的机，网络中的路由会根据这个字段来决定如何转发报文。
* Options、Pad：这两个可选字段并不常用。

## 分割与重组 Fragmentation And Reassembly

由于每个物理网络的**最大传输单元 MTU(Maximum Transimission Unit)**&#x662F;不一样的，所以 IP 有两种设计选项：

1. 把 IP 报文的大小定义为一个小于所有物理网络 MTU 的值；
2. 提供分割与重组功能，当 IP 报文大于某个网络的 MTU 时，把 IP 报文分割成若干个分段，到达目的机后再将报文分段重组；

选项 1 并不太合理，首先，当有新的网络技术出现时，IP 报文的大小可能也要进行调整。再者，如果由于网络中有一个特别小的 MTU，导致 IP 报文的大小上限也被定义得很小，这样的话，数据会被分成很多个报文分段发送出去；而报文分段都是独立的包，每个报文分段都会包含所有的 IP 头部信息，这种情况下要将一份数据传送出去，就要多次传输以及解析头部信息，造成了资源的浪费。所以 IP 选择了第 2 种设计。

与报文分组相关的信息记录在头部字段 `Ident` 中，对于在某个时间段从同一个源机发来的所有报文而言，这个值是唯一的。所以目的机可以根据这个标识知道某个报文的哪些分段收到了，哪些没有收到。如果所有分段都被接收了，目的机就会进行**重组**工作，将报文分段重组为完整的报文；如果有些分段丢失了，目的机就会放弃重组并将已接收的报文分段也都丢弃，IP 并不会尝试修复丢包问题。

**那么什么时候进行报文的分割与重组呢？**

一般来说，源机会把报文大小定为它所直接连接的网络的 MTU，接着如果在路由的过程中碰到的网络有更小的 MTU，那路由就会将报文进行分割后再继续传输；不过，如果 IP 的上层协议传过来的数据大于本地网络的 MTU，源机也会进行报文分割操作。

跟分割操作不同的是，报文重组只会发生在目的机。无论一个报文在路由的过程中被分割了多少次，这些报文片段都只会在到达目的机之后再被重组。但如果有一个报文分组丢失了，目的机还是会尝试重组其他报文片段，并最终放弃重组，这种无谓地消耗资源的行为可能会导致 denial-of-service 攻击，所以应该尽量避免报文分割。目前推荐使用的方法是 path MTU discovery，也就是找出从源机到目的机这一路上将会遇到的所有网络的最小 MTU，把 IP 报文大小定为这个 MTU 值，从而避免了报文分割。

![](https://cdn.jsdelivr.net/gh/suukii/Articles/assets/network/ip_fragmentation.png)
