目录
IP层
IP报文格式
IP的理解
运营商
分片与组装
IP层
传输层的TCP或者UDP协议能直接将数据发送到网络中吗?显然不能,封装完的TCP报文还是需要向下交付,经过协议栈,从链路层发送到物理层也就是网路中。
那么tcp做了什么工作呢? 保证可靠性与效率。 传输层往下的协议就不会考虑可靠性或者像TCP一样保证可靠性的策略,像网络层,他的重点在于路由的转发,以及定位对应的主机,而链路层的核心在于真正的传输,他是真正将数据发送到网络中的协议。
在我们讲协议栈的时候就说了,网络协议栈之所以分层,其中一个原因就是每一层都重点解决一类问题,高内聚。
IP层(IP地址)的核心工作是什么呢?
IP地址的作用就是用来定位主机的。同时IP层还要具有将数据报从a主机跨网络送到b主机的能力,这个能力并不是说具体的传送工作,而是说它可以指挥下层如何转发数据报以及如何选择路径。
但是IP层虽然能够将数据报跨网络转发,但是有能力就一定能做到呢?网络转发过程中一定可能会出现异常状况,而如果在途中丢包了,IP层也不需要关心,因为他只负责选择路径和封装,具体的关于可靠性等传输的策略都是由传输层来完成的,而网络层主要负责定位和路由的功能。
那么IP和网络层就能保证数据报的跨网络的可靠传输了。而跨网络通信的本质就是网络通信的本质。
那么IP是怎么做到跨网络转发的呢?
数据在跨网络过程中,是要经过路由器的,路由器就是一个网络传输中的节点,或者我们也可以将其直接看成一个简易的主机,只不过不考虑应用层和传输层,工作在网络层,路由器通过将报文中的目标IP地址提取出来,然后选择一条最优路径,将其转发给网络中的下一个节点,最终交付到目标主机。
在路径选择中,目的IP非常重要,决定了报文该如何转发。
在报文的转发过程中,目的IP是不变的,而源IP可能发生变化(比如在运营商子网中),同时,我们还有一个地址是时刻变化的,就是我们前面讲过的 mac 地址,报文中的源mac和目的mac地址在转发途中是不断在更新的,永远保存的是上一个节点和下一个节点的mac。 所有的路由器在处理我们的报文的时候,都是根据目的IP进行决策的。
IP = 目标网络 + 目标主机
这一点我们可能不好理解,但是后续我们会使用一些案例来说明这一点。我们的一个IP地址肯定不是当成一个整体来用的,就跟我们的身份证一样,前几位是用来表明我们的所属地的,跟我们同一属地的人的身份证号码对应前几位都是相同的,后面的数字就是用来标识我们的唯一性的,这样设计的好处就是,当你拿到一个身份证号的时候,你只需要从前几位就能判断出这个身份证号是属于哪个地区的,那么有什么优势呢?我们后续会讲到。 IP 也是一样的,可能前面几位是表示目标主机所在网络的,后面的几位才表示的是该主机在该网络中的编号,也就是主机号,这样当然也能标识唯一性,定位唯一主机。当然网络层也只负责送到目标主机,至于到达主机之后交付给对应的进程的工作,是传输层和端口号的工作。
目前我们可以认识一下三个概念:
主机:配有IP地址,可以进行路由控制的设备
路由器:配有IP地址,主要负责路由控制
节点:路由器和主机的统称
IP报文格式
报文格式如图
首先在初步学习IP协议的时候,我们不关心16位标识,3位标志和13位片偏移,这是后续结合MAC协议来学习的。
首先,我们根据报头字段来了解IP报文如何解包和分用?
首先,跟TCP协议一样,他的报头是变长的,所以他也需要有一个首部长度字段来表示报头的长度,也就是四位首部长度,这个首部长度字段和TCP中的首部长度是一模一样的,单位是4字节,那么IP报头的取值范围也是 [20,60] 字节。
那么我们如何保证一个IP报文是被接收方网络层完整收到的呢?
也很简单,首先报头中还有两个字段, 16 位总长度和16位首部检验和,首先,根据首部检验和以及4位首部长度,能够将IP报头提取出来并检验 IP 报头的完整性和正确性。检验正确之后,使用16总长度来和我们收到的IP报文的长度做对比,如果相等,那么就意味着有效载荷的长度和发出的IP报文的有效载荷是一样的,至于其中的内容是否出现差错,这是TCP报头中的检验和来检查的。IP层只能保证收到的报文的有效载荷长度和发出时是一样的,只能讲这种出现明显错误的报文检测出来,具体的可靠性检验还是需要TCP来完成。
这也说明了IP层不负责数据的可靠传输,这不是IP层该操心的。
然后就是如何交付给上层的问题,传输层是由不同的协议的,那么网络层收到IP报文之后,解包出来该把有效载荷交给传输层的哪个协议呢?
很明显,IP报头中有一个字段是 8 位协议,他表示的就是上层协议的协议号。
在发送方传输层将数据交给IP层的时候,与此同时会将协议号交给IP层,那么IP层会讲上层的协议号填充到报头的8位协议字段中,以便接收方的网络层进行分用。、
8位生存时间:
首先,我们要了解一个概念,数据在网络传输时,注定要经过网络中的节点,可能是路由器,也可能是送达目标主机,那么数据包从源主机发出后,每经过一个路由器,就称为一跳。
同时,网络的拓朴结构是十分复杂的,我们无法保证数据在网络的转发过程中不会出现问题,因为数据一旦从源主机发出去之后,就已经脱离了源主机的控制,生死有命富贵在天了。 数据再转发的过程中,有可能遇到故障的路由器,或者遇到一些原因导致目标主机不可达,最终可能会导致我们的报文在一条环路中进行转发,我们可以理解为在死循环。 如果这种情况不进行处理,就会导致网络中存在大量的环路转发的报文,占用网络资源,对路由器造成不必要的负担。 或者因为目标主机不可达了,那么这时候路由器该怎么做? 所以,我们必须赋予路由器一个权力:有权力丢弃转发到他这里的报文。
所以在IP报头中有一个 8 位生存时间,这个生存时间并不是指的我们日常所有的时间,几分几秒之类的,而是指的报文的跳数(TTL)。TTL就代表了从源主机发送出去,在网络中所经历的最大跳数。 我们知道,路由器收到报文之后,首先在链路层会进行解包交给网络层,在网络层也要解包提取出目标 IP 地址,然后决定转发的路径,再重新封装IP报头并交给链路层。在这个时候,路由器也会将该报文的TTL字段进行减一操作。 每经过一个网络节点,报文的TTL都会进行减一操作,当TTL减到0时,就会被路由器直接丢弃。所以有了生存时间,就不用担心一个报文在环路转发一直消耗资源的情况了,因为经过一定的跳数就会被丢弃。
8位的跳数其实在现在的网络环境中已经完全够用了,我们不要小看了路由器和底层的传输距离,以及网络的拓扑结构的复杂程度。 目前来说,我们的报文经过几个或者十几个路由器就已经完全能够送到目标主机了。
数据包脱离源主机之后,路上的节点怎么知道这个报文被谁发出,要交给谁? 这就要看报头中的32位源IP地址和32位目标IP地址。 尤其是目的IP地址,路由器会根据这个目的IP进行路由的选择。
所以,我们在调用 bind函数进行绑定IP和端口号 或者说 connect 进行连接请求的时候,其实把IP交给了网络层,把端口交给了传输层,最终网络层和传输层会使用目的IP和端口封装进自己的报头中。
4位版本:版本其实指的是IP的版本。 一般填的是 4 ,表示使用ipv4的IP地址。
那么能不能在这个字段填上 6 表示ipv6呢?显然是不可以的,我们也能看到报头中的源IP和目的IP都是32位的,也放不下ipv6的地址,16个字节,128位,就算加上选项字段也放不进。
ipv4和ipv6是不兼容的。所以这个字段一般都是固定的,我们也不需要关心这个字段。
我们也知道,ipv4的32位IP地址在当前的网络环境是远远不够的,入网的设备远远超过IP的总数,毕竟设计的时候谁也无法想到未来网络设备会如此之多,这是因为时代的局限性,没有办法的事情。
既然ipv4地址远远不够,那么就需要有对应的解决方案,目前来说国际上主流的解决方案还是在ipv4的基础上进行填坑与挖坑,虽然能暂时解决燃眉之急,但是从长远来看不是根本的解决方案。
那么为什么不全部替换成ipv6的网络呢?其中一个重要的原因还是政治因素,由于我国的ipv6是世界领先的,如果直接将全球网络替换为ipv6,那么主导权就在我国了,那么有些眼红的霸权国家肯定容不下。 其次,基础设施也是一大因素,因为ipv4和ipv6不兼容,如果要全面更换到ipv6,那么意味着目前的所有的只提供ipv4服务的基础设备都无法使用了,需要更换到ipv6的设备,这个工作量是十分巨大的,虽然我国在内网中很多场景下都已经开始使用ipv6了,但是想要全球都进行设备的更新,还是太难做到了。还有一个原因就是,网络层是植入在操作系统内核的,如果要使用ipv6,那么意味着我们的操作系统也需要做更新,不只是一个人的操作系统需要更新,而是全网都需要更新到支持ipv6的系统,这也是很麻烦的,影响太大。 所以目前来说,我们在内网中虽然也会用到ipv6,但是在公网环境,还是使用ipv4。那么在内网发送到公网时,就需要我们的路由器进行ip地址的替换。 这在后续我们提到NAT技术时会了解。
8位服务类型
通常指的是我们的数据包传输时选择最优路径的标准。 比如我们需要的是最快到达,最高可靠性,最小丢包还是其他的要求或者服务,其实就是在为路由选择的最优路径下定义。
那么至此我们就已经将IP报头解析完了,剩下的4个字节的内容在IP报文的分片与组装会用到。
IP的理解
我们说IP要拆分成两个部分来看,网络号和主机号。
网络号和主机号分别是什么呢? 为什么要这样划分IP呢?怎么划分?
先说为什么要划分,本质是为了提高查找效率。
我们举一个例子:在我们的大学中,有许多学院,同时在学校中我们使用学号来标识每一个学生,学号在学校里是唯一的。假设学校为了管理和学生之间的交流,将一个学院的学生都拉进了一个群聊中,同时为每个学院的设计了一个管理者,这个管理者也是学生,他也有自己的学号。 同时,为了方便学院之间的交流,还存在一个学校的群,但是这个群聊只有各个学院的管理者加入。那么各个学院的管理员我们就可以称之为与其他学院交流的出口。 在学校中,我们的学号是被设计过的,比如前几位表示所属学院。
那么在某一天,一个普通的学生如果有消息需要转发给一个特定的学号的学生。首先,在他看来,他只知道所属学院的学院号,如果目标学号的学院号和他的学院号相同,那么他就能知道对方是跟他在一个群聊中的,那么他就可以在群聊中的成员中找到对应的学号的学生,将消息发送在群聊中,并@目标学生,这时候所有学生都会收到这条消息,但是绝大部分学生会直接忽略这个,因为不是目标对象,只有指定学号的学生会对这条消息进行处理。如果在群聊中,有些学生并没有讲昵称修改为学号,那么这时候他就需要在群聊中发送一条消息,比如”谁是学号xxxxx,请回复我“,这时候,群聊中所有的学生都会受到并处理这条消息,但是绝大多数都不会进行恢复,只有对应学号的学生会进行恢复,并@我们的询问方 。 这时候就能找到目标学生了,我们可以对其设置标签,将学号与这个微信号绑定,并且将消息发送给他,@目标对象。
但是如果我们发现,该学号的前几位学院号并不是我所处的学院,那么由于我们不清楚或者说不敢自己猜测其他的学院的学号的定义方式,这时候,我们就无法知道这个消息该发送给谁。但是我们普通学生可能不知道,管理员肯定是要知道的,这时候我们会将消息以及消息要发送给哪个学号的学生发送在群聊中,并且@管理员。 而管理员收到我们发出的消息之后,由于他的属性设定,他是知道学号是怎么划分的,这时候它能够将目标学号的学院号提取出来,根据学院号,将消息发送给学校群聊中的对应学院的管理员(入口),这时候对应的管理员拿到了消息,就变成了学院群内的转发了,那么就和前面的情况一样了。
那么在这里,学号前面部分的学院号起了什么作用呢?
在跨学院转发时,管理员能够根据学号前面的学院号,得知消息属于哪个学院,将该消息转发给对应的学院的入口,就相当于将其他的学院都排除在外了。而消息的转发过程我们其实就是一个查找的过程,查找的本质就是排除,那么在我们学院的出口,一次就将其他的学院的学生排除了,排除的效率高了,也就代表查找的效率高了。
在上面的案例中,我们可以理解为学院的群聊就是局域网,而普通学生就是一个个的主机,管理者就是一个出口或者入口路由器,所有的出口路由器处在一个我们也可以理解为大的局域网中。而学号就是我们的IP地址,学号前面部分的学院号就是网络号,能够以定位在那个局域网中,而在局域网内,则是主要依赖学号中的除学院号的字段,来表示局域网内的主机,这就是主机号。
所以,在互联网中,每一台主机都要隶属于一个子网,为了方便定位这个主机,提高查找的效率。
同时,这也意味着,全球在进行网络规划的时候,也离不开一个话题,如何进行子网划分。
也就是怎么进行IP的划分?
互联网是一个被设计过的世界,那么是谁设计的呢?对于我国来说,设计者就是运营商,因为他们是基础设施的建设者,有绝对的话语权。但是更高级别的,运营商的IP又是怎么来的呢? 这就需要全球的各个地区共同协商出一个标准,将IP按照某些约定分配给各个区域。
我们假设IP是按照国家进行划分的(实际上是按照区域划分),IP地址一共是32位,假设前 8 个位用来表示国家,那么各国按照标准,在自己的内网中,只能使用前八位为自己编号的IP地址,剩下的24位由各个国家自行划分。 同时每个国家都由一个国际路由器,所有的国家的国际路由器都处在一个子网当中,如果需要数据的跨网络传输,就可以经过国际路由器进行转发给对应的国家的国际路由器,然后由对方的国际路由器进行转发。
那么在国家内部,假设在我国,分配到的所有的ip地址的前八位都是相同的,那么这些IP地址怎么划分给各个省份呢?由运营商进行标准的指定,比如再使用 6 个比特位用来表示各个省份,每个省份只能够使用自己对应省份号的IP,这时候前14位就相当于网络号。 同时IP分配给各个省份之后,再在省内进行IP的分配,比如再使用6个比特位来标识每个市的编号,那么又将IP分配到了市,而每个市还可以再使用几个比特位来表示区县,将IP地址再划分。当到区县这一级之后,我们就发现IP地址完全不够分配给每一个人了,这时候运营商就会划分区域,组建内网,一个内网分配一个对外转发的公网IP。
当IP进行划分之后,数据的转发就很明显了,在各级网络中,网络号的位数是不同的,在各级转发时,根据网络号决定将其转发给上级网络还是在本局域网内转发。
这就是一个潦草版的子网划分。 实际中的划分情况远比这种方式复杂, 需要考虑各种因素,不过我们不需要了解这么深入。
不管怎么说,网络中的每个主机都必须处于同一网段内
不同网段的主机的网络号不同,但是主机号可以相同。
而同一网段内的网络号相同,主机号必须不同。
两个不同的网段如果是直接相连的,他们之间必须要有路由器,这也意味着路由器至少要同时处于两个子网中,也就意味着路由器必须至少要有两个IP地址,就必须至少要有两张网卡。一般情况下,路由器的IP(在自己构建的子网那一端),主机号就是 1 。
同时,在一个子网中,不断会有主机接入到网络,以及主机下线退出网络,这时候就需要对子网的IP地址做管理,这个管理工作也是路由器做的。以前是通过网络管理员手动为每一台入网的主机分配IP地址,但是十分不方便,后续出现了 DHCP 技术取代了手动的网管。
那么IP真实是怎么划分的呢?
首先,最开始的时候,IP说是被分为 ABCDE 五类地址的。
首先,这几类地址就是根据从左往右的第一个为 0 的比特位来进行标识的。
第一个比特位为 0 的IP是 A 类地址,他的主机号有24位,除去全0之外,最多能给2^24-1台主机分配IP地址,也就是一千六百多万
第二个比特位为 0 的IP是 B类地址,他的主机号有16位,除去全 0 之外 ,还有2^16-1,也就是65535 个
第三个比特位为 0 的是 C 类地址,他的主机号有 8 位,那么主机数最多就是 2^8-1 ,也就是 255 个
第四个比特位为 0 的是D类地址。D类地址一般用于多点广播,也就是一个标识一个群组,可以同时向该群组的多个主机发送消息,其实就是广播地址。
第五个比特位为 0 的地址是 E类地址,留待后用。
注意,上面的地址的划分是对网络的整体进行划分,他在划分的时候并没有考虑国家地区,也没有规定哪些地址属于哪些地区。
划分好之后,国家,组织,企业,学校等就可以去申请这几类网络了。
但是这样一来,其实使得本就不富裕的IP数量更加捉襟见肘了。
试想一下,A类地址一般用来干什么? 一千六百多万个主机的网络是不是太浪费了。 又比如说,一个学校申请一个B类地址(先不考虑B类地址不够的问题),那么假设学校里只有 几千或者两三万人,可是一个B类网络可以标识六万多IP ,剩下的也是浪费了。
IP地址本身就不够用了,通过这样固定的划分方式还会导致很多的浪费现象。
所以这种划分方案很快就被弃用了,新的解决方案=称为 CIDR ,也就是地址掩码的方案。
CIDR就是在IP地址的基础上,增加了一个子网掩码的概念,用子网掩码从一个IP中提取出网络号和主机号。
子网掩码也是一个32位的整数,由一串 1 和一串 0 构成。
IP地址按位与上子网掩码就是我们的网络号,而剩下的就是主机号。
有了子网掩码的方案之后,我们就可以自己定义网络号为多少位,更加灵活的进行 IP 的划分。从此以后,就只有子网掩码,而跟ABCDE类网络的概念没有关系了。
虽然有了子网掩码的概念,但是IP整体在公网中还是不能冲突的,要保证唯一性。
那么所谓的子网 或者说 子网掩码在哪呢?
子网是由路由器管理的,子网的网络号,子网掩码,主机号都会有路由器进行配置或者分配。
所以所谓的子网掩码和网络号,就是在路由器中事先配置好的数据。
当然,主机号全 0 的还是不使用,用来表示网络号,全 1 也不用,用来当作广播地址,用来向局域网或者子网中所有主机广播数据时使用。 而 1 号主机还是路由器。
还有一类地址不在公网中使用,就是 127.* ,也就是前八位为 127 的IP都不能用在公网中,而是用于本地环回,代表本主机自己,通常是 127.0.0.1
但是CIDR方案也只是优化了子网划分,减少了浪费率,并没有增加IP的数量上限,IP仍然是不够用的状况。
有三种解决方案:
1 动态分配IP地址:只给接入网络的设备分配IP地址。 因此同一个设备接入用一个网络时,每次接入可能分配的IP地址不一样。 这个我们的路由器中基本都有,DHCP。
2 NAT 技术:就是一种地址转换的服务,内网IP和公网IP之间在路由器中相互转换。后续我们会将私网和公网的区别。
3 IPV6,这是最根本的解决方案。
当然,由于NAT技术只需要让我们的路由器支持NAT服务就可以了,并不需要更改我们的操作系统的基础设施,所以在目前来看还是一个能缓解IP不足的方案,但是从长远来看,根本的解决方案还是使用IPV6。NAT技术也间接的阻碍了ipv6地推广,不过他也会IPV6提供有了一种思路,既然NAT能将内网IP转换为公网IP,自然也可以设计一个服务,将IPV6转换为IPV4,在内网中使用IPV6,一旦要将数据转发到公网,将地址更换为公网地IPV4地址。 所以我们在内网中可能是用的IP地址是IPV6地,但是照样可以访问公网。
内网IP:
如果一个组织内部组建局域网,IP地址只能用于该局域网内的通信,而不能直接连接到因特网(公网),理论上,任意地址都可以用来构建内网,但是 RFC 1918 规定了用于组建局域网的私有IP地址:
10.* :前 8 位是网络号,共一共16777216个地址
172.16./* ~ 173.31./* ,前12位为网络号,共1048576个地址
192.168./* : 前16位是网络号,共65536 个地址
规定只能用这三类地址来组建局域网,在这三类地址范围内的IP我们成为私有IP。 除这些意外的都叫全局或者公网IP。
所以我们前面一直说的IP具有唯一性,其实指的是公网IP具有唯一性。
如果是内网IP,就只在他所处的局域网唯一。除了局域网,没有唯一性。
我们经常使用的IP都是私有IP,比如学校中使用的 192.168. ,或者我们云服务器使用的IP就是 10.*或者172... 。
同时,从上图中,我们也可以看到,并不是说网络号就只能是 8 位或者 16 位等,组织内部还可以对其进行划分,将更多的比特位作为网络号,在内部进行使用。
运营商
运营上是网络基础设施的搭建者。
对于我们普通人来说,我们要访问服务器是必须经过运营商的。因为在物理设施上就已经决定了。
平常我们所谓的入网,其实就是附近的运营商路由器中拉一根网线,然后街道我们自己的家用的路由器上,在构建一个局域网。
因为我们的消息需要经由局域网,在运营商网络中不断转发,最终才能到达服务器,那么就意味着,我们的数据报文是在运营商的控制之下的。 如果你欠费了或者王菲非法IP,那么运营商就可以在路由器转发的时候设置一定的策略直接将你的报文丢弃,导致你连不上对应的服务器。
同时运营商也会对你的上网内容进行审核,有时候会拦截你的范围,这就叫做“长城”,也叫“墙”,其实就是在运营商的转发逻辑中对你的报文拦截。
目前我们所知道的路由器的功能:
数据包的转发,DHCP,组建局域网(其实就是DHCP),NAT
想必我们也见过如何接网,其实就是在附近的运营商路由器中接一根网线到我们的家用路由器,然后配置家用路由器,设置网络名称和密码等,就组建好局域网了。我们的家用路由器一般功率都比较低,覆盖范围小,不过家庭用也够了,而对应的运营商路由器肯定是要比我们的家用路由器要强大得多的,同时,他们也有组建内网的功能。
那么目前来说,我们的家用路由器要组建局域网,也要连接运营商路由器,其实就是在运营商路由器组建的子网中。
但是运营商的路由器一般可不只是一级,而是有很多很多级的运营商路由器组建的子网,在运营商内网中不断进行转发才能发送到公网。
一般路由器都会配有两个或以上的IP,对内也就是处于自己所组建的子网的端口的IP,叫做LAN口IP,而对外的IP,也就是他所在的别人组建的子网的端口的IP,叫做WAN口IP。
LAN口IP对内,WAN口IP对外。
路由器LAN口连接的主机,都属于当前这个路由器中所组建的子网。
每一个家用路由器其实又作为运营商路由器中的一个节点,这样的运营商路由器可能会有很多级,最外层的路由器接入公网,配有公网IP,这个路由器我们称之为出口或者入口路由器。
其实,从上面的知识点我们也能知道,家用路由器作为运营商网络的节点,本就可以将数据包转发给运营商路由器,或者说,我们就可以看作是一个运营商路由器,所以其实家用路由器配置稍微好一点,也是能够再接别的家用路由器的。
那么数据包是怎么从我们的家庭局域网发送到公网的服务器中的呢?
很简单,在经过每一个路由器节点时,都在路由器中将该报文的源IP替换为 路由器的WAN口IP,然后转发给上一级路由器,以此往复知道交给出口路由器,出口路由器再将该报文的源IP替换为自己的公网IP,这样,我们的报文就能在公网中进行转发了。
这样一来,就能经过层层运营商路由器组建的内网,将报文从我们的家庭局域网最终转发到公网,最终转发给服务器。
私网IP是不能出现在公网中的,但是不将一些IP划分为不具有唯一性的用于内网入网的私有IP的话,我们的IP地址又远远不够,可是数据包如何从私网经过公网转发到目标服务器,又是一个问题,于是就有了NAT技术,我们上面的过程就是NAT技术的向外转发的过程,NAT就是net address transform ,也就是地址的替换。
那么再将这个内网的逻辑接入到我们上面所画的公网的划分中,我们的整个网络的大体框架就有了,我们可以将这个运营商的子网的拓扑的根节点连接到划分之后的公网IP中。
由于IP的划分,大部分的时间,我们的路由器其实拿到我们的目的IP之后,并不是全部解析,而是提取出目标IP的网络号。而路由器之间的转发其实就是一个一个的子网转发,发送的端口和接收的端口都处于同一个子网中。 最终送到目标主机所在的子网之后,就变成了局域网内的转发了。 当然其实路由器之间也是局域网内的转发。
而数据包走到路由器之后,其实会存在以下几种情况:
1 路由器不清楚目标主机的子网,这时候不进行转发。 这种情况一般不会存在,因为路由器的逻辑就是用来转发,不可能拒绝转发报文
2 路由器不清楚目标主机的子网, 路由器在查不到的转发路径的情况下,将报文转发到一个默认路由器中。
3 路由器知道目标子网,但是不是直接相连,不知道具体的所有的路线,但是他能查到下一个应该转发给哪个路由器,这时候他会转发给下一个路由器。 其实数据包的转发大部分都是在重复这一步,不断将数据包交给下一跳,最终送到对方子网。
4 路由器就在目标子网中,这时候就是同局域网也就是内网转发的通信逻辑了。
数据包的转发我们就可以理解为一跳一跳的问路的过程,所谓的一跳,就是数据链路层的一个区间,具体一点就是在以太网中从源mac地址到目的mac地址的帧传输区间。 数据包最终在网络中传输都是以mac帧的形式传输的,网络层交给链路层,链路层封装完才交给物理层进行传输。
那么在路由器进行目的网络的路径查找的时候,就需要用到路由表,所谓的路由表就是在路由器中维护的一张表结构的数据,包含目的网络,目的网络对应的下一跳路由器,端口,标志位,子网掩码等。
在Linux中我们可以使用 route 命令查看当前主机的路由表,没错,我们的普通的主机也是由路由表的,不说别的,他至少要保存他所在的子网的路由器的地址,我们的主机可以看作一个简单路由器,只不过他不是专门用来进行数据转发的。
link-local就是本地连接,而上面的 10.2.8.0显而易见就是我们的云服务器所在的子网了。
当然上面显示路由信息其实没有按照一定的顺序,把default 放在最后一行更适合我们理解。
其实路由器收到报文之后,就是拿着目的IP,然后将其与Genmask进行按位与,判断得到的网络号是否和第一列的 Destination 相同,如果相同,就说明匹配上了,转发到 Iface 指定的端口。 如果不相符,那么就直接继续往下遍历每一个条目。 直到最后,匹配到default的时候,由于Genmask为0.0.0.0, 不管什么IP与他按位与出来都是0.0.0.0,都会走着一条转发路径。
当然路由器也有一些更新策略,只不过我们不关心这个了,在IP这一个章节我们主要就是理解网络的大体框架。
路由表中的默认网关其实可能就是表中已经出现过未匹配上的路由器,无所谓。
数据包在网络中转发的过程中目标网络号是否会发生变化?
会的,网络是时刻变化的,什么情况都有可能发生。就像上面说的,有可能数据包还在转发途中,目标网络直接或者说组网的路由器直接出故障了,也是有可能的。
分片与组装
涉及到前面讲解报头时没有讲解的三个字段:16为标识,3个标志位,13位片偏移。
IP协议并不是实际进行传输的协议,真正将其发送到网络中还是数据链路层的MAC帧协议,在物理层网线上传输的是MAC帧,而IP协议最主要的作用是定位目标主机以及规划转发路径,同时路由器还有屏蔽底层网络差异的功能,但是最终封装成IP报文之后,都是要向下交付给MAC帧协议作封装的,也就是IP报文还会作为MAC帧的有效载荷部分进行封装。
而MAC帧协议有一个限制,有效载荷的长度不能超过1500字节,这叫做MTU(最大传送单元),一般MAC帧协议都是1500,可以修改,但是没有偶必要。
也就是说,IP层交给数据链路层的IP报文,不能超过 1500 字节。 如果链路层发现IP层给他的报文长度超过了 1500 ,这时候会直接丢弃,不进行传送。 链路层不负责可靠传输,他只负责在自己的能力范围内进行数据的传送。 但是,IP层也无法决定整个IP报文的大小,因为IP层的数据也是由传输层给的。 传输层有两个协议UDP和TCP,如果是UDP的话,那么交付给IP的报文的大小完全就是由用户层决定,因为UDP是面向报文的,用户给他一个报文,他进行封装之后就直接交给网络层了。 而对于TCP协议而言,如果数据很多,窗口很大,能够发送更多的数据,那么为什么不直接一次性发出去呢?减少网络IO的次数不是效率更高吗? 这时候我们可以回想一下我在TCP协议时画的图,为什么要一次 1000 字节的发,而不是直接将所有数据一次性发完?其实就是因为链路层有限制,不允许发送太大的数据。
所以,正常情况下,TCP是不会将超过1460(TCP和IP的标准包报头都是20字节,1500-40=1460)字节的数据一次性交给IP层的。
但是,架不住特殊情况,如果TCP就是一次性将超过1460字节的数据给IP层了呢?或者说上层使用的是UDP,用户一次性发的数据超过 1460 字节呢? 这时候难道IP层也直接把数据丢掉吗? 不能,IP只能当个两头受气的角色。
IP层也要有对应的解决方案。这个方案就叫做 分片与组装。
在讲分片与组装之前,我们要先建立一个共识: 分片是谁做的?组装又是谁做的?
由发送方的IP层进行分片,由接收方的IP层进行组装。
为什么需要分片呢? 不就是因为TCP或者UDP给他的报文太大吗? 所以分片的工作肯定跟TCP和UDP无关,也不会跟数据链路层有关,数据链路层只负责数据传输。而接收方的传输层也不会关心组装,他只关心收到的数据是否完整,是否跟发送的数据一样,那么也就是说组装的工作也必须在接收方的IP层做完我,将组装好的完整报文再交付给传输层。
分片与组装都是IP层自己的行为,他要与TCP和MAC协议的工作进行解耦。
分片的时候,我们不能随意分片,必须要考虑对方的IP层的组装问题,对端IP层可能会面临几个问题:
1 如何确定一个报文是否被分片了?
2 怎么识别同一个报文的分片?(因为可能会有多个报文同时被分片,然后接收到)
3 哪一个分片是报文的第一个分片?哪一个是最后一个? 如何确定是否收全或者丢失了?
4 如何将所有的分片按顺序组装成原始报文?
5 怎么保证组装之后的报文是完整的,正确的?
带着这5个问题,我们就能知道每一个字段有什么功能?
首先,数据链路层并不会作任何有关分片的工作,他只负责将IP层给的数据送到下一跳节点以及最后局域网的通信,那么对方IP层怎么知道链路层交给他的报文是一个完整IP报文的多个分片呢?也就是如何识别同一个报文的所有的分片? 这就需要IP层给每一个报文(分片之前的) 设置一个标识/id。而16位标识就是用来标识IP报文在通信双方之间的唯一性的。
如果一个报文太大,需要切片,那么他的每一个切片的16位标识都是相同的。
这里我们要注意,即使是切片,也是要有IP报头的,因为交给链路层的一定是一个有IP报头有数据(可选)的数据,我们不要认为切片就不需要报头了。
有了16位标识,就能知道哪些分片报文是同一个报文的分片了。也就解决了第2个问题。
3位标识,其中第一个比特位被保留,留待后用。第二个比特位表示的是 禁止分片。 如果该比特位为1,那么就是禁止被分片,就算超过了1500,也不会分片,交付给链路层就被丢弃了,这个比特位就是为了防止出现大报文,不过一般不进行设置。 第三个比特位表示的是更多分片。如果为1,表示的含义是有更多分片,如果是0,就表示没有更多分片。
也就是说,如果对方IP层收到的一个IP报文中,这个更多分片标记位 是 1, 就表示我们还没有把所有分片收完,后续还有分片。 而如果为 1,那么就表示后面没有更多分片了。 该标记位为1有两种情况,一种是本来就是完整报文,没有被分片,那么自然就是没有更多分片。 第二种情况就是这是对方发过来的多个分片中的最后一个,既然是最后一个分片,那么也是没有更多分片了。
最后就是 13 位片偏移了。进行分片之后,我们还需要能够将分片按照一定顺序,组装成原始报文,这时候就需要知道每一个分片的数据在原始报文(一定是先封装成一个IP报文,然后发现IP报文过大才会进行分片)的位置,这里的片偏移的单位是8字节,这就是片偏移的作用。
比如第一个分片的片偏移为 0 ,如果第一个分片的大小是1500(算上原始报头),那么第二个分片的片偏移就是 1500/8 ,而如果第二个分片的整个IP报文的大小也是 1500 ,那么意味着他传送的原始报文的数据大小为 1460,那么他的片偏移就是 (1500+1460)/8 ,以此类推。 为什么是1460而不是1480呢?因为片偏移的单位是 8 字节,那么就要求每一个分片所携带的原始报文的数据大小必须是 8 的整数倍。
那么我们再来解答上面的几个问题: 首先是 1 ,如何判断一个报文是否被分片了,其实就是对方的IP层拿到报文之后,都会先检测一下报头中的更多分片标记位,如果为 1 或者片偏移不为0(因为网络中报文送达的顺序可能和发出的顺序不一样),就代表被分片了,这时候就不会直接交给上层,而是先将其维护在一个临时的缓冲区中,并且设置一个定时器,等到后续收掉所有的分片组装成一个完整的原始的IP报文,再按照正常流程对这个完整IP报文进行解包与分用。
第3个问题,第一个分片的更多分片标记位为1,同时片偏移为0. 最后一个分片的片偏移不为0,同时更多分片为0。 在这里我们也能区分最后一个分片和 普通的完整IP报文的区别,虽然更多分片标记为都为0,但是完整的报文,片偏移也为 0 。而如何确定是否收全则要跟第 4 个问题一起讨论。
第4个问题,就是将分片组装成一个完整报文,这里面最重要的就是分片的原始位置,其实也很简单了,根据每一个分片的片偏移,将分片的数据放到缓冲区的指定位置。 如果偏移量为0,就代表是第一个分片,也就是携带了原始报头的分片,那么这个分片有点特殊,不会将报头去掉,而是会将报头也放到缓冲区。 而其他的分片的报头都跟原始报头无关了,都是要先去掉报头,只将有效载荷放到缓冲区中进行组装。
而最后一个为标题,如何确定报文的完整性? IP层这里,只能判断出分片组装完整之后,是否有缺失,以及通过首部检验和判断报头是否出问题,而无法判断报文数据部分内容是否发生变化,比如比特位翻转 。不过我们也不需要担心,内容是否出问题这个判断是在TCP或者UDP中的检验和来判断的。
分片的过程我们可以用图来描述:
分片与组装的原理其实不难。
那么分片好吗?
可以肯定的是,分片肯定是不好的。分片和组装都只在网络层完成,但是对于链路层和传输层来说,他们根本不关心是否分片,只关心数据是否完整,而不关心这一个数据在底层是怎么发送过来的。
可是分片就意味着一个完整报文要分多次发送出去,而只要其中任意一个报文丢失,在接收方网络层进行组装时,是会设置计时器的,如果超时了,那么就说明有分片在网络中丢失了导致无法组装成完整的报文,那么就会将目前已经接收到的分片全部丢弃。 然后什么都不做了。
而接收方的TCP由于根本就没有收到网络层交付的数据,根本就不会进行应答,那么发送方的TCP协议就会触发超时重传,超时重传可不是只对某一个分片进行重传,在TCP协议中是没有分片的概念的,TCP这时候就会将这份数据重新交给网络层。 而网络层还是需要进行分片。。。
分片会增加丢包概率,因为只要一个分片丢了,那么就相当于整个报文都丢了,这时候需要重传(如果是TCP的需要重传,而如果是UDP的话丢了就丢了)。
当然这个 MTU 对TCP协议肯定也会有影响,因为我们都知道了分片会增加重传的概率,那么TCP既然要可靠高效,肯定就需要避免分片,下一篇数据链路层协议的文章会提一嘴。