Linux:网络层IP协议
如果通信双方在同一局域网内,可直接通过数据链路层进行相互通信(通过MAC地址寻址)。
但是不同局域网之间无法直接相互通信,原因在于MAC地址是“扁平”且“无意义”的:MAC地址(如 00-1A-2B-3C-4D-5E
)只是一个设备的唯一硬件标识符。它不包含任何关于这个设备位于哪个网络的信息。就像一个人的身份证号只标识了“你是谁”,但没有告诉你“你在哪个国家、哪个城市”。这时就需要一个统一的寻址和路由系统,将各个局域网连接起来,形成一个更庞大的“互联网”——这就是网络层的作用
本文我们先介绍IP地址及如何通过IP地址进行路由,随后会介绍IP协议的报头
子网掩码
IP 地址有32位,分为两个部分, 网络号和主机号
网络号:标识了这个IP地址属于哪个特定的网络。就像邮寄地址中的“城市和街道”。
主机号:标识了在这个特定网络中的哪一台设备。就像邮寄地址中的“门牌号”。
每一个IP都标识一台唯一的网络设备,但是它并不是与该设备绑定的,换一个网段,该设备的IP就会发生变化(当然这个工作一般不是由用户来做的)。
现在的问题在于,如何知道IP地址哪部分是网络号,哪部分是主机号呢?(也就是如何合理划分IP地址,使其能够被充分利用,网络号过长,则能容纳的设备就变少;主机号过长,就可能因为实际设备远远小于可容纳设备的上限而浪费IP地址)
为此,早期的人们把IP地址分为了五类:
随着互联网的飞速发展,这种划分方案的局限性很快显现出来,大多数组织都申请B类网络地址, 导致 B类地址很快就分配完了, 而 A 类却浪费了大量地址。归根结底,还是这种粗粒度的划分不够精细,不能适应实际需求
为了解决IP地址被浪费的问题,又在原方案的基础上引入了子网掩码概念:
设立子网掩码的目的:更灵活地划分IP地址,确定IP地址中哪一部分是网络号
子网掩码也是32位,实际的网络号=子网掩码&IP地址,一个网段下的所有设备的子网掩码相同
这样一来,一个网段到底可以容纳多少台设备(即主机号有多少位)也可以通过设置子网掩码被更精细地设置,而不是像原来一样被粗糙的分为五类
这里举例演示一下:
按照原来划分五类的标准,A所在的网段192.168.1最多可容纳256台设备,但实际可能只有十几台设备,这显然没有充分利用IP地址;
而引入子网掩码以后,A和B的前24位IP地址相同,但实际网络号却不相同,A的网络号是IP地址后24位,而B的网络号则是27位;
这里最后还要说明一点:127.xxx.xxx.xxx的 IP 地址用于本机环回测试(即自己和自己进行通信),通常是 127.0.0.1
NAT
要注意的是,子网掩码只是提供了一种更合理更充分利用IP地址的方式,它并没有创造新的IP地址;IP地址一共只有32位,那么这意味着最多只有2^32个IP地址,大概43亿。显然,即使充分利用了所有的IP地址,对于高速发展的互联网而言还是远远不够。为了解决IP地址不足的问题,又诞生了NAT技术。当然在介绍NAT之前,我们还需引入一个概念:私网IP和公网IP
私有IP和公有IP
私有IP:在私有网络内部(如家庭、公司、学校局域网)自由使用的IP地址,就像公司内部的分机号(比如分机号101、102)。这些地址在互联网上没有意义,也不能被路由。
范围:特意预留出了三段地址作为私有IP:
10.0.0.0 – 10.255.255.255 (超大型网络)
172.16.0.0 – 172.31.255.255 (中型网络)
192.168.0.0 – 192.168.255.255 (小型网络,家庭网络最常见)
特点:可以无限重复使用。你家可以用 192.168.1.1
,我家也可以用 192.168.1.1
,因为它们只在各自的内网中有效,互不冲突。
公有IP:
全球唯一的IP地址,就像电话号码的国家代码+区号+个人号码一样,不能重复。它是互联网上的“通用货币”,任何设备想要直接与互联网上的其他服务器(如谷歌、百度)通信,都必须有一个公有IP。
特点:由IANA、APNIC等国际或地区性互联网注册机构统一管理和分配。数量有限,需要付费租用。
而 NAT (网络地址转换)的核心功能是在将一个私有IP和端口号与为一个公有IP和端口号建立映射关系,这样一来,一个有限的公有IP就可以对应多个私有IP,在互联网上无数个私有IP的通信实际上就变成了有限的公有IP之间的通信,这样就基本解决了IP地址不够的问题。
NAT IP转换过程
当你的电脑想要访问百度:
第一步,内网请求:你的电脑(私有IP 192.168.1.101
)想访问百度的服务器(公有IP 39.156.66.10
)。它发送一个数据包:源地址: 192.168.1.101:54321
目标地址: 39.156.66.10:80
第二步,NAT转换:数据包先到达你的路由器。路由器会执行NAT操作:它用自己的公有IP替换掉数据包里的私有IP,并记录下这个转换关系。修改后的数据包:源地址: [你的公有IP]:62000
目标地址: 39.156.66.10:80
(百度)这个 62000
是路由器随机分配的一个端口号,用来唯一标识这是内网哪台设备发起的请求。
第三步,互联网路由:这个数据包在互联网上正常传输,最终到达百度。百度看来,这个请求就是来自 [你的公有IP]:62000
,它会将回复发送到这个地址。
第四步,NAT转换:回复数据包回到了你的路由器:源地址: 39.156.66.10:80
目标地址: [你的公有IP]:62000
第五步,转发回内网:路由器查看之前记录的转换表:将目标地址改回内部的私有IP。修改后的数据包:源地址: 39.156.66.10:80
目标地址: 192.168.1.101:54321
,路由器将这个数据包准确发送给你的电脑。
整个过程对你的电脑和百度服务器都是透明的:你的电脑以为自己在直接和百度通信,百度也以为它是在和你的路由器通信。
NAPT
如果局域网内, 有多个主机都访问同一个公网服务器, 那么对于服务器返回的数据包,其目的IP都是相同的(路由器替换后的公有IP),那么路由器如何判定将这个数据包转发给哪个的主机?
这时候 NAPT 来解决这个问题了:使用 IP+port 来建立关联关系,即私有IP+端口号对应一个公有IP+端口号,同一局域网内,设备的私有IP被替换成的公有IP相同,但是被替换后的端口号不同,通过端口号就可以确定是哪个设备的哪个进程
路由
一跳:
一跳指的是数据包在从源设备到目的设备的传输过程中,从一个网络节点转发到下一个直接相连的网络节点的一次移动。因为直接相连,所以它们之间通信使用的是MAC地址,而不是IP地址。要注意,一跳发生在数据链路层
而路由就是决定下一跳应该到跳哪一个IP地址下的过程,它发生在网络层。
路由的过程:一个数据包发给路由器,路由器查询路由表进行匹配:如果与目标主机直连则直接交付给对方;如果不行则尝试匹配路由表上的其他路由器,匹配则发给该路由器;如果都不匹配则发给默认路由器
到这里你可能会有一个疑惑:路由决定的不是下一跳的IP地址吗,而一跳用的是MAC地址啊,那它是怎么获取这个MAC地址的呢,这就需要ARP协议了
ARP协议
ARP协议的目的是通过IP地址获取相应的MAC地址
其过程分为两步:请求和应答
ARP请求(广播):
主机A首先查看自己的 ARP缓存表,看是否有该IP对应的MAC地址记录。
如果没有,主机A就会构造一个 ARP请求包。这个包的主要内容是:
发送方IP、发送方MAC、目标IP、目标MAC(全0,表示未知)
主机A将这个ARP请求包 以广播的形式发送到整个局域网。这意味着局域网内的所有设备都会收到这个包。
ARP应答(单播)
局域网内所有设备都会收到这个广播包,并拆开查看。
其他设备发现目标IP不是自己的,于是丢弃这个包。
主机B 发现目标IP正是自己的IP地址。主机B于是将主机A的IP和MAC地址对应关系记录到自己的ARP缓存表中。
随后向主机A发送一个ARP应答包(以单播的形式,直接发给电脑A的MAC地址)。这个包的内容是:发送方IP(主机B的IP)发送方MAC、(主机B的MAC)、目标IP、目标MAC
主机A收到主机B的ARP应答后,就知道了主机B的MAC地址。它将这个对应关系存入自己的 ARP缓存表 中,并设置一个有效期(通常几分钟到二十分钟)。现在,主机A就可以给主机B发送数据了。
IP协议
IP协议是用于将多个分组交换网络(数据报交换方式)连接起来的最典型通信协议。
该协议是无连接的服务,负责在源地址和目的地址之间传送数据报,然后为了适应不同网络对分组大小的要求,需要对上层传来的报文进行分割,最后调用本地网络协议将数据报传送给下一个网关或目的计算机。
1.主要功能
把数据报在互连的网络上传送,将数据报在一个个IP模块间传送直到传送到目的模块。网络中每个主机和网关上都有IP模块。数据报在一个个模块间通过路由处理网络地址传送到目的地址。
(1)寻址
在同一以太局域网内部,结点间的寻址可以通过二层MAC地址进行,但在不同网络之间,是不能通过MAC地址的,因为用于MAC地址寻址的广播帧只能在同一个以太网段内部进行
在不同网络中只能通过三层地址(该协议中为IP地址)进行寻址.
(2)数据报的封装
从传输层到达的数据段都需要经过IP协议进行重新封装的。因为IP协议是无连接的服务,并且采用数据报交换方式,所以封装后形成的是IP数据报。
IP封装的目的就是标识此IP数据报发送节点和接收节点的IP地址及控制信息
(3)分片与重组
不同网络上的链路可以传输的最大报文大小是不同的,这就是我们通常所说的MTU(最大传输单元)。为了使我们要传输的数据报能在不同网络中传输,当一些尺寸较大的数据报要在某个MTU值比较小的网络链路上传输时就可能需要对原来的数据报进行拆分,形成一个个小的分段,然后再把这些分段依次传输出去。
在接收节点自然就要把这些被拆分的分段重新组合起来,还原成原来的大的数据报的问题
下面我们来解释IP协议报头:
报头格式
4 位版本号(version):
指定 IP 协议的版本, 对于 IPv4 来说, 就是 4.
4 位头部长度(header length):
IP 头部的长度是4字节的多少倍, 4bit 表示最大的数字是 15, 因此 IP 头部最大长度是 60 字节.
8 位服务类型(Type Of Service),分别包括:3 位优先权字段(已经弃用), 4 位 TOS 字段, 和 1 位保留字段(必须置为 0).
4 位 TOS 分别表示: 最小延时, 最大吞吐量, 最高可靠性, 最小成本. 这四者相互冲突, 只能选择一个. 对于 ssh/telnet 这样的应用程序, 最小延 时比较重要; 对于 ftp 这样的程序, 最大吞吐量比较重要.
16 位总长度(total length):
IP 数据报整体占多少个字节
16 位标识(id):
唯一的标识主机发送的报文. 如果 IP 报文在数据链路层被分片 了, 那么每一个片里面的这个 id 都是相同的.
3 位标志字段:
这个字段是分段标志,指出该IP数据报后面是否还有分段。目前只有前两位有意义:最低1位记为MF(More Fragment),如果MF=1,即表示后面还有分段,如果MF=0表示这已是某个数据报的最后一个分段;中间1位记为DF(Don’t Fragment),当DF=1时表示不允许分段,DF=0时表示允许分段;最高1位没有使用。
13 位分片偏移(framegament offset):
是分片相对于原始 IP 报文开始处的偏移. 其实就是在表示当前分片在原报文中处在哪个位置. 实际偏移的字节数是这个值乘8得到的. 因此, 除了最后一个报文之外, 其他报文的长度必须是 8 的整数倍(否则报文就不连续了).
8 位生存时间(Time To Live, TTL):
数据报到达目的地的最大报文跳数. 一般是 64. 每次经过一个路由, TTL -= 1, 一直减到 0 还没到达, 那么就丢弃了. 这个字段主要是用来防止出现报文在网络中循环路由
8 位协议:
表示上层协议的类型
16 位头部校验和: 使用 CRC 进行校验, 来鉴别头部是否损坏.
32 位源地址和 32 位目标地址: 表示发送端和接收端.
选项字段(不定长, 最多 40 字节): 略
分片与重组
想象一下,你要用一辆大卡车(大数据包)运送一批货物,但途中必须经过一条狭窄的隧道(MTU小的网络)。卡车无法直接通过,唯一的办法就是把货物卸下,分装到几辆小卡车(小数据包)上,依次通过隧道,到了宽阔的道路后再重新装回大卡车。
在网络中,这个“隧道的宽度限制”就是 MTU(Maximum Transmission Unit,最大传输单元)。
定义:MTU是指数据链路层所能承载的上层数据(即IP数据包)的最大长度(单位:字节)。它是由底层的网络硬件和技术决定的。
常见的MTU值:
以太网(Ethernet):MTU通常为 1500 字节(这是最常见的)。
PPPoE(宽带拨号连接):MTU通常为 1492 字节。
FDDI:MTU为4352字节。
令牌环网:MTU可以是4464字节等。
当一个大的IP数据包从一个MTU较大的网络发出,要经过一个MTU较小的网络时,这个大数据包就无法被一次性传输。就要将这个数据包分成几个小段——分片,并在目的地进行还原——重组。
谁负责分片?谁负责重组?
分片:由路由器执行。当路由器收到一个IP数据包,其长度大于要将它转发出去的那个网络的MTU时,路由器就必须对这个数据包进行分片。
重组:由最终的目的主机执行。所有分片到达目的地后,由目的主机的IP层负责将它们重新组装成原始的数据包,再交给上层协议(如TCP/UDP)。中间的路由器绝不负责重组,它们只负责转发(可能包括分片)。
IP协议通过首部中的三个字段来协调分片和重组的过程:
16位分片标识
所有属于同一个原始IP包的分片,都拥有相同的标识符。这样,接收端才能知道哪些分片应该被组合在一起。
3位标志
位 0 (保留位):必须为0。
位 1 (DF - Don't Fragment,不分片):
如果设置为 1
,则告诉途中的路由器:“禁止分片这个包”。
如果路由器发现这个包太大又设置了DF位,它会直接丢弃该包,并向源主机发送一个ICMP“需要分片但DF位已设置”的错误消息。
位 2 (MF - More Fragments,更多分片):
如果设置为 1
,表示这个分片不是最后一个,后面还有分片。
如果设置为 0
,表示这是原始包的最后一个分片,或者表示这个包本身就没被分片。
13位片偏移
作用:指示当前分片所携带的数据,在原始未分片IP包的数据部分中的起始位置(以8字节为单位)。这确保了接收端能够以正确的顺序重组分片,即使分片到达的顺序是乱序的。
如何进行重组?
目的主机收到分片后:
识别:根据标识符和源IP地址,判断哪些分片属于同一个原始数据包。
排序:根据片偏移字段的值,对所有分片进行排序。
组装:检查第一个分片的偏移是否为0,最后一个分片的MF位是否为0。然后按顺序将每个分片的数据部分拼接起来。
交付:当所有分片都到达并组装完毕后,得到一个完整的原始IP数据包,计算其总长度,然后将其交付给上层协议(由IP首部的“协议”字段指定,如TCP或UDP)。
分片带来的问题
分片虽然解决了传输问题,但它是有代价的:
设想一下,一个大小为7420字节的数据包被分成了5个片段(每个片段刚好1480个字节),假设每个片段的丢包率为1%,那么一个片段都不丢失的几率为(99%)^5=95%,而如果丢失任何一个片段,整个IP数据包都无法重组,只能丢弃,对于TCP而言这要重传整个数据包,而不是某一个丢失的片段,对比一下,分片传7420个字节的数据包,重传的期望为5%*7420B=371B,而如果一开始就在应用层控制数据包的大小,将其分为5个数据包,其重传的期望为5*1%*1500B=75B,因此还是应该尽量避免分片