网络是怎样连接的(十六)—— 生成包含接收方 IP 地址的 IP 头部

404 阅读8分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情

前言

在上一篇文章中,我们从 IP 模块的视角来了解了网络包发送的整体流程,本篇文章我们学习的是 IP 头部时如何生成的。

IP 头部格式

IP 模块接受 TCP 模块的委托负责包的收发工作,它会生成 IP 头部并附加在 TCP 头部前面。

首先我们先来了解一下 IP 的头部格式,IP 头部包含的内容如下图所示,其中最重要的内容就是 IP 地址,它表示这个包应该发送哪里去。这个地址是由 TCP 模块告知的,而 TCP 又是在执行连接操作时从应用程序那里获得这个地址的,因此这个地址的最初来源就是应用程序。IP 不会自行判断包的目的地,而是将包发往应用程序指定的接收方,即便应用程序指定了错误的 IP 地址,IP 模块也只能照做。当然,这样做可能会出错,但这个责任应该由应用程序来承担。

字段名称长度(比特)含义
版本号4IP 协议版本号,目前使用的是版本 4
头部长度(IHL)4IP 头部的长度。可选字段可导致头部长度变化,因此这里需要执行头部的长度
服务类型(Tos)8表示包传输优先级。最初的协议规格里对这个参数的规定很模糊,最近 DiffServ 规格重新定义了这个字段的用法
总长度16表示 IP 消息的总长度
ID 号16用于识别包的编号,一般为包的序列号。如果一个包被 IP 分片,则所有分片都拥有想用的 ID
标志(Flag)3该字段有 3 个比特,其中 2 个比特有效,分别代表是否允许分片,以及当前包是否为分片包
分片偏移量13表示当前包的内容为整个 IP 消息的第几个字节开始的内容
生存时间(TTL)8表示包的生存时间,这是为了避免网络出现回环时一个包永远在网络中大专。每经过一个路由器,这个值就会减 1,减到 0 时这个包就会被丢弃
协议号8协议号表示协议的类型(以下皆为 16 进制)TCP:06;UDP:11;ICMP:01
头部校验和16用于检查错误,现在已不使用
发送方 IP 地址32网络包发送方的 IP 地址
接收方 IP 地址32网络包接收方的 IP 地址
可选字段可变长度除了上面的头部字段之外,还可以添加可选字段用于记录其他控制信息

发送方 IP 地址

IP 头部中还需要填些发送方的 IP 地址,大家可能认为是发送方计算机的 IP 地址,实际上“计算机的 IP 地址”这种说法并不准确。一般的客户端计算机上只有一块网卡,因此也就只有一个 IP 地址,这种情况下我们可以认为这个 IP 地址就是计算机的 IP 地址,但如果计算机上游多个网卡,情况就没有那么简单了。

IP 地址实际上并不是分配给计算机的,而是分配给网卡的,因此当计算机存在多块网卡时,每一块网卡都会有自己的 IP 地址

很多服务器上都会安装多块网卡,这时一台计算机就有多个 IP 地址,在填写发送方 IP 地址时就需要判断到底应该填写哪个地址。这个判断相当于在多块网卡中判断应该使用那一块网卡来发送这个包,也就相当于判断应该把包发往哪个路由器,因此只要确定了目标路由器,也就确定了应该使用哪块网卡,也就确定了发送方的 IP 地址。

IP 头部的“接收方 IP 地址”填写通信对象的 IP 地址。

发送方 IP 地址需要判断发送所使用的网卡,并填写该网卡的 IP 地址。

那么,我们应该如何判断应该把包交给哪块网卡呢?其实是和路由器使用 IP 表判断下一个路由器位置的操作是一样的。因为协议栈的 IP 模块与路由器中负责包收发的部分都是根据 IP 协议规则来进行收发操作的,所以它们也都用相同的方法来判断把包发送给谁。

路由表

Linux 系统可以通过 route print 命令来显示路由表,Mac 系统可以通过 netstat -nr 命令来查看。

➜  ~ netstat -nr
Routing tables

Internet:
Destination        Gateway            Flags        Netif Expire
0/1                utun2              USc          utun2
default            192.168.0.1        UGSc           en0
default            link#20            UCSI         utun2
1.180.235.194      link#20            UHWIi        utun2
1.193.218.76       link#20            UHW3I        utun2     82
8.8.4.4            link#20            UHWIi        utun2
8.8.8.8            link#20            UHW3I        utun2    188
8.133.123.139      link#20            UHWIi        utun2
10.5.112.222       10.5.112.222       UH           utun2
10.8.7.220         link#20            UHWIi        utun2
10.8.7.224         link#20            UHWIi        utun2
10.8.8.18          utun2              UHS          utun2
10.8.8.18/32       utun2              USc          utun2
10.227.76.230      192.168.0.1        UGHS           en0
17.57.145.132      link#20            UHWIi        utun2
23.200.24.64       link#20            UHW3I        utun2    117
23.200.24.88       link#20            UHW3I        utun2    101
192.168.0.1        d0:76:e7:67:2d:ba  UHLWIir        en0   1198
192.168.0.104/32   link#9             UCS            en0      !
192.168.0.106      e8:9f:6d:a9:14:1c  UHLWI          en0   1097
224.0.0/4          link#9             UmCS           en0      !
224.0.0/4          link#20            UmCSI        utun2
224.0.0.251        1:0:5e:0:0:fb      UHmLWI         en0
239.255.255.250    1:0:5e:7f:ff:fa    UHmLWI         en0
239.255.255.250    link#20            UHmW3I       utun2    209
255.255.255.255/32 link#9             UCS            en0      !
255.255.255.255/32 link#20            UCSI         utun2

Internet6:
Destination                             Gateway                         Flags         Netif Expire
default                                 fe80::%utun0                    UGcI          utun0
default                                 fe80::%utun1                    UGcI          utun1

通过 netstat -nr 命令我们可以看到路由表,有 IPV4 路由表和 IPV6 路由表。这里我们主要关注 IPV4 路由表,我们先来看看每个字段的含义。

  • Network Destination:表示网络包的最终目的地;
  • Netmask:目标地址掩码;
  • Gateway:转发路由器的 IP 地址。如果本列与右边的 Interface 列内容想用,则表示不通过路由器转发,可直接向目标 IP 地址发送包;
  • Interface:发送包的网络接口。当匹配到本条路由时,就会使用这一列中的 IP 地址对应的网络接口向左侧王冠中的路由器发送包;
  • Metric:通过这条路由传送包的成本,这个数越小说明距离越近;

接着我们来看下如何选择网卡。

首先,我们对套接字中记录的目的地 IP 地址与路由表左侧的 Network Destination 栏进行比较,找到对应的一行。例如,TCP 模块告知的目标 IP 地址为 192.168.0.21,那么就对应上面路由表中的 192.168.0.* 的那列,然后将包发送给对应的路由器。

default            192.168.0.1        UGSc           en0

这列表示默认网关,如果其他所有条目都无法匹配,就会自动匹配这一行。

这样一来,我们就可以判断出应该使用哪块网卡来发送包了,然后就可以在 IP 头部的发送方 IP 地址中填上这块网卡对应的 IP 地址。

接下来还需要填写协议号,它表示包的内容是来自哪个模块的。例如,如果是 TCP 模块委托的内容,则设置为 06(十六进制),如果是 UDP 模块委托的内容,则设置为 17(十六进制),这些值都是按照规则来设置的。在现在我们使用的浏览器中,HTTP 请求消息都是通过 TCP 来传输的,因此这里就会填写表示 TCP 的 06(十六进制)。

对于 IP 头部的其他字段也需要填入相应的值,但是对大局没有什么影响,这里我们就先不学习了。

总结

IP 地址实际上并不是分配给计算机的,而是分配给网卡的,因此当计算机存在多块网卡时,每一块网卡都会有自己的 IP 地址

IP 头部的“接收方 IP 地址”填写通信对象的 IP 地址。

发送方 IP 地址需要判断发送所使用的网卡,并填写该网卡的 IP 地址。

参考文档

  • 《网络是怎样连接的》—— 户根勤

往期文章