跳转至

网络层IP协议

约 6938 个字 8 行代码 10 张图片 预计阅读时间 23 分钟

本篇介绍

网络基础知识部分提到过,局域网内部传输内容需要使用到MAC地址,而在多个局域网之间传输数据就需要使用到IP地址,那么为什么IP地址可以做到从一个局域网找到另外一个局域网?当前局域网的计算机又是怎么知道需要发送的数据不属于其所在的局域网中?这些问题都是接下来讨论IP协议需要重点解决的问题

IP协议报

基本结构

在了解IP协议如何实现不同局域网传输之前,先了解一下IP协议报中存在的字段,对于IP协议报来说,其结构示意图如下:

从上图中可以看到,IP协议的大小与TCP协议一致,并且结构也非常类似。下面基于上面的结构对每一个字段进行解释

4位版本号

现在的IP协议分为IPv4和IPv6,所以设置了这个版本号用于区分具体的协议报版本。尽管如此,这个值在绝大部分情况下都是IPv4,因为IPv6和IPv4在结构上是不兼容的,这也就意味着如果使用IPv6就需要使用一套新的结构,同时为了从该结构中读取到内容就需要新的解决方案,整个过程迭代太复杂

在设置这个值时,按照下面的方式设置:

  1. 值为4(二进制:0100):表示IPv4协议
  2. 值为6(二进制:0110):表示IPv6协议

4位首部长度

这个值与TCP协议中的首部长度概念是一致的:都是描述协议报(包括选项)的总长度,同TCP协议报一样,如果直接使用这4位表示,那么连最基本的部分都无法完整表示,所以此处的单位依旧是4字节,具体计算方式与TCP协议一样,此处不再具体解释

8位服务类型(TOS)

服务类型(Type of Service)字段是IP头部中的一个8位字段,用于指示如何处理数据包,特别是关于优先级和传输特性方面的需求

传统IP协议中,TOS字段的8个比特位被划分为:

  • 前3位:优先级(Precedence),定义数据包的重要性
  • 接下来4位:服务类型标志,包括:
  • D位(Delay): 表示延迟敏感度,1表示低延迟
  • T位(Throughput): 表示吞吐量要求,1表示高吞吐量
  • R位(Reliability): 表示可靠性要求,1表示高可靠性
  • M位(Monetary Cost): 表示成本偏好,1表示低成本
  • 最后1位:保留位,通常设置为0

现代网络中,TOS字段已经被重新定义为差分服务(DiffServ)字段:

  • 前6位:差分服务代码点(DSCP),用于定义服务类别
  • 后2位:显式拥塞通知(ECN),用于网络拥塞控制

这个字段使网络设备(如路由器)能够:

  1. 为实时流量(如VoIP)提供低延迟服务
  2. 为大型数据传输优化吞吐量
  3. 为关键业务应用提供优先处理
  4. 实现Quality of Service(QoS)策略

虽然在早期互联网中TOS字段应用有限,但在现代拥堵的网络环境下,差分服务模型已成为网络流量管理和服务质量保障的重要机制

16位总长度

这个字段用于描述数据部分的内容大小,具体内容会在后面的组装和分片部分结合16位标识、3位标志和13位片偏移一起介绍

16位标识符、3位标志和13位片偏移

在后面的组装和分片部分与16位总长度一起具体介绍

8位生存时间(TTL)

在了解这个字段的具体作用之前,先了解一般情况下在网络传输中会遇到的一种问题:数据包无限循环

所谓数据包无限循环是指数据包从一个路由器传输到下一个路由器时被下一个路由器继续传输到先前的路由器,如此循环

如果有大量数据包在网络中出现无限循环的情况就会导致网络拥塞。为了尽可能防止这个问题,为每一个数据包设置了一个数据有效值,每当数据包经过一个路由器,这个值就减1,如果这个值减到了0,那么路由器就会丢弃该数据并返回ICMP超时消息,这个值就是生存时间(Time to Live)

在不同操作系统下,这个值有可能不同:

  1. Windows系统下:默认初始为128
  2. Linux/Unix系统下:默认初始为64
  3. 路由器/网络设备:通常是255

8位协议

IP协议处在网络层,其上层是传输层,在传输层部分存在很多的协议,不同的协议传输的数据类型也不同,例如前面提到的UDP和TCP,这两个协议一个传输的是数据包,一个传输的是字节数据,所以在接收时,网络层想要区分不同的协议就需要用到该字段的值

常见的8位协议值有:

协议号 协议名称 描述
1 ICMP 互联网控制消息协议,用于网络诊断
2 IGMP 互联网组管理协议,用于多播
6 TCP 传输控制协议,面向连接的传输层协议
17 UDP 用户数据报协议,无连接的传输层协议
41 IPv6 IPv6封装,用于IPv6过渡
50 ESP IPsec加密安全载荷
89 OSPF 开放最短路径优先,路由协议

16位首部校验和

使用CRC进行校验, 来鉴别头部是否损坏

32位源IP地址和32位目的IP地址

在前面提到,不同的局域网之间传递数据时,对应的计算机会有两套地址,其中一套就是源IP地址和目的IP地址,为了知道数据包最后发送到哪一个局域网中的哪一个计算机,就需要用到目的IP地址,对应的接收方如果想知道数据是哪一个计算机传输的或者想传输数据给发送方,就需要用到源IP地址

TCP协议和IP协议的关系

现在提出一个问题:既然IP协议的作用是负责将这些数据包从源主机发送到目标主机,为什么由TCP实现各种传输控制方式而不是由IP协议来实现?

IP协议和TCP协议的职责分工是基于网络设计的核心原则:分层设计,这种分离有多个重要原因:

  1. 职责分层的设计哲学:

    • IP协议(网络层)职责:只负责数据包的路由和传递,提供"尽力而为"的无连接服务
    • TCP协议(传输层)职责:负责端到端的可靠传输,包括流量控制、拥塞控制和错误恢复
  2. 减少网络路径中间节点的负担:如果在IP层实现可靠性机制,每个路由器都需要维护连接状态、处理确认和重传,这会极大增加网络核心设备的复杂性和处理负担,路由器需要专注于高效转发,而不是复杂的状态管理

  3. 端到端原则:基于网络设计中的关键原则「功能应尽可能在终端实现,而非网络中间设备」,TCP在通信两端实现可靠性,无需改变网络内部基础设施,这使互联网能够在各种底层网络技术上运行
  4. 灵活性和多样性支持:IP作为基础层保持简单,允许上层协议根据需要选择不同的服务质量,不是所有应用都需要TCP的可靠性(如UDP用于实时多媒体),将可靠性机制从IP中分离,允许应用根据需求选择传输协议
  5. 异构网络适应性:IP协议可以运行在各种不同物理特性的网络上,每种网络可能有不同的错误特性和传输特性,在传输层处理这些差异,使IP层保持简单和通用

这种分层设计是互联网成功的关键因素之一,使网络既灵活又可扩展,能够适应各种不同的应用需求和底层技术变化

综上所述,TCP协议本质就是提供传输的策略,而IP协议提供的就是传输的能力。这就是为什么称为TCP/IP协议的原因

网段划分

基本概念

先从宏观角度来理解网段划分,以下图为例:

假设现在主机A需要将数据传输到主机D,那么可用的路径为:主机A → 路由器A → 路由器E → 路由器G → 路由器H → 路由器J → 主机D

因为主机A到路由器A属于局域网通信,所以一旦主机A发送数据时知道目标IP地址不是当前局域网内的数据,就会将数据发送到路由器A,由该路由器进行转发给路由器E,此时路由器A和路由器E就处于同一个局域网内,同样,路由器E转发给路由器G、路由器G到路由器H、路由器H到路由器J,路由器J到主机D亦是如此

Note

在整个数据传输的过程中,每一台主机都可以看作是一个节点

从上面的过程中可以看出,不同局域网之间的主机通信实际上就是多个局域网相互通信,那么IP地址在处理局域网通信时就可以考虑一种方式:将IP地址分为局域网地址(网络号)+主机地址(主机号),这种方式就是网段划分。基于这种方式,在数据传输过程中,在到达目标局域网之前,只需要比较目标局域网地址是否匹配即可,这样一来,只要当前局域网地址和目标局域网地址不同,就不再需要拿着目标IP地址和当前局域网内的所有计算机进行比较,从一定程度上大大提高了传输数据的性能,一旦到达目标局域网,接下来就只需要拿着主机地址进行比较即可(先不考虑局域网内部使用MAC地址)

划分原理

了解了网段划分后现在的问题就是:多少位是局域网地址,多少位又是主机地址呢?

在先前,有这样一种划分网络号和主机号的方式:

将IP地址分为5类IP,分别对应着A类地址、B类地址、C类地址、D类地址和E类地址,如下图所示:

基于上图,每一类地址可以表示的范围如下:

  1. A类:0.0.0.0127.255.255.255
  2. B类:128.0.0.0191.255.255.255
  3. C类:192.0.0.0223.255.255.255
  4. D类:224.0.0.0239.255.255.255
  5. E类:240.0.0.0247.255.255.255

但是,随着网络的发展,这种分类会有一个问题,例如申请一个B类地址,对应的范围以二进制的形式表示就是从10000000.00000000.00000000.0000000010111111.11111111.11111111.11111111,而其中的后三部分中,每一部分的一位可以表示一台主机,总共就有65534台主机,但是现实是这些位置大概率不会完全用完才去申请其他类地址,这就导致B类有严重的浪费,如果每一个种类都有这种情况,最后就导致IPv4地址数量不足

计算主机数量

计算一个IP地址范围内可以表示的主机数量,主要取决于主机号的位数。以下是计算方法:

\(主机数量 = 2^{\text{主机号位数}} - 2\)

减去2的原因:一个地址用于网络标识(主机号全0),一个地址用于广播地址(主机号全1)

具体步骤:

  1. 确定主机号位数:主机号位数 = IP地址总位数(32位) - 网络号位数
  2. 代入公式计算

例如:

A类地址:

  • 网络号位数:8位
  • 主机号位数:32 - 8 = 24位
  • 主机数量\(2^{24} - 2 = 16777214\)

B类地址:

  • 网络号位数:16位
  • 主机号位数:32 - 16 = 16位
  • 主机数量\(2^{16} - 2 = 65534\)

C类地址:

  • 网络号位数:24位
  • 主机号位数:32 - 24 = 8位
  • 主机数量\(2^8 - 2 = 254\)

针对上面这种问题,提出了一种解决方案:CIDR(Classless Interdomain Routing)。在这个方案中,提出了子网掩码(Subnet mask)的概念,子网掩码的作用就是区分出主机号和网路号,其也是32位正整数,一般以一串0结尾,具体区分方式为:将IP地址与子网掩码做按位与&运算,得到的结果就是网络号,因为可以规定子网掩码1的个数来决定网路号,所以这样一来就不需要确定是哪一类地址来区分网络号的位数,例如下面的两个例子:

在这种表示方法下,IP地址也引入了新的表示方式:IP地址/网络号位数,不过也只是在书面上,例如192.168.144.1/24,其中的24代表的就是前24位数构成的就是网络号

思维拓展

如果仔细观察可以发现,这种通过网络号淘汰一批IP地址的机制非常类似于深度优先搜索中的剪枝操作

但是,尽管有上面通过子网掩码划分的方式缓解了IP地址不足的问题,但是从IP地址最基本的数量来看,最多也就是\(2^{32} = 4294967296\)个主机,根据TCP/IP协议规定,每一个设备都需要有IP地址,所以很显然这个数量也是不足的,何况还有一些特殊IP地址

基于上面的原因,还需要提出新的解决方案来缓解IPv4数量不足的问题,目前有三种:

  1. 动态分配IP地址:只给接入网络的设备分配IP地址。因此同一个MAC地址的设备,每次接入互联网中,得到的IP地址不一定是相同的
  2. NAT技术:NAT(Network Address Translation),允许多个设备共享较少数量公网IP地址的技术,后面会重点介绍,本篇不做介绍
  3. IPv6技术:不实用原因前面已经提及,此处不再说明

私有IP和公网IP

如果一个组织内部组建局域网,IP地址只用于局域网内的通信而不直接连到Internet上,理论上使用任意的IP地址都可以,但是RFC 1918规定了用于组建局域网的私有IP地址:

  1. 10.*,前8位是网络号,共16777216个地址
  2. 172.16.*172.31.*,前12位是网络号,共1048576个地址
  3. 192.168.*,前16位是网络号,共65536个地址

在上面范围内的IP地址全部是私有IP地址,其他都是公网IP地址。需要注意的是,私有IP地址只能用来组建局域网,不能出现在公网中,这也就意味着不同的局域网可以使用相同的私有IP地址从而达到了IP地址的复用性,而有构建局域网能力的设备就是路由器,每一台路由器都有DHCP(Dynamic Host Configuration Protocol)功能,有了这个功能,路由器就可以动态为当前局域网内的主机分配IP地址

基于上面的概念,再看最开始提到的两台主机通信的过程:

在上图中,因为路由器具有组建局域网的能力,所以一台路由器和其对应的主机就属于一个局域网,在同一个局域网下的所有主机的网络号就是相同的,而一个路由器除了属于一个局域网外,还可能属于另外一个局域网,所以一台路由器除了需要LAN口(局域网接口)IP地址外,还需要WAN口(广域网接口)IP地址。当IP地址为198.168.1.201/24的设备需要向IP地址122.77.241.3/24的设备发送数据时,首先判断出122.77.241.3/24不属于当前局域网,将数据交给IP地址为198.168.1.1/24的路由器(路由器的主机号一般为1),由路由器将数据进行转发,但是这里需要一点:因为198.168.1.201/24不能出现在公网中,而IP协议报中的字段需要有源IP地址,所以路由器在对数据进行转发时需要将198.168.1.201/24转换为WAN口IP地址,即10.1.1.2/24,接着继续判断发现122.77.241.3/24依旧不属于当前局域网(即路由器10.1.1.1/24组建的局域网),需要将数据交给路由器10.1.1.1/24由其进行转发,同样转发前需要对私有IP地址转换为122.77.241.4/24,最后传输到公网上,找到了公网IP地址为122.77.241.3/24的设备。最后122.77.241.3/24设备收到的IP协议报中的源IP地址为122.77.241.4/24而不是一开始的198.168.1.201/24

如果从上面的过程看待全球通信,那么基本的过程可以理解为如下:

每个国家申请一个公网IP地址,具体每个国家如何分配这个公网IP地址由各个国家决定。为了不同的国家可以相互通信,需要一个非常大的路由器进行连接,基本示意图如下:

接着,当国家之间需要通信,就需要由该路由器进行转发,而一个国家内部也需要公网,所以基于这个国家的公网再组建公网,以中国为例:

上图中每一个局域网内的第一个路由器需要有WAN口IP地址和LAN口IP地址,而其中的WAN口IP地址就是图中省份公网IP地址,例如省份1的LAN口为10.1.1.1,WAN口为5.1.0.0

最后,当中国中的某一个数据要传输到美国,首先经过省内的局域网通信将数据交给国家间的路由器,再有国家间的路由器转发给美国,进入到美国某一个州的路由器后再继续局域网转发即可完成基本的通信

Note

需要注意,公网IP地址实际上并不只是按照国家划分,更多还是根据机构、学校等划分,国内也不只是按照省份、州等进行划分

运营商

运营商(网络服务提供商,Internet Service Provider,ISP)是互联网架构中的关键角色,负责将终端用户连接到互联网骨干网络

运营商一般有下面的功能:

  1. IP地址分配

    • 从地区互联网注册管理机构(RIR)获取IP地址块
    • 为用户分配公网IP地址,通常是动态分配
    • 管理私有IP到公网IP的转换(NAT)
  2. 网络基础设施

    • 建设和维护骨干网络
    • 部署大量路由器、交换机形成层次化网络
    • 提供"最后一公里"连接到用户
  3. 路由服务

    • 维护复杂的路由表
    • 实现用户网络与互联网之间的数据转发
    • 执行BGP等路由协议与其他ISP互联

运营商网络通常呈现层次化结构:

  1. 接入层:直接与终端用户连接
  2. 汇聚层:将多个接入网络的流量集中
  3. 核心层:高速骨干网络,连接不同地区网络
  4. 互联层:连接其他运营商网络的边界路由器

运营商的分类一般有:

  1. 一级(Tier 1)运营商:拥有全球性骨干网络,不需向其他ISP付费即可访问全网。例如:AT&T、Level 3、NTT等
  2. 二级(Tier 2)运营商:区域性网络提供商,但是需要向一级运营商购买传输服务,例如:许多国家级运营商
  3. 三级(Tier 3)运营商:本地接入提供商,从更大的ISP购买带宽,直接服务终端用户

在中国,主要运营商包括中国电信、中国移动和中国联通,它们在全国范围内提供网络接入服务,并维护连接国际互联网的出口。

路由与路由器

前面提到,路由器会找到对应的路径,即确定数据包从源地址到目的地址的最佳路径的过程,而这个行为就被称为路由

在路由的过程中,路由器怎么知道自己所处的局域网谁是根路由?实际上就是靠路由表,一个路由表一般包括目的网络、下一跳地址、出口接口、路由度量值等

基于上面的概念,所以两台主机传输数据可以理解为下图:

路由表的类型分为两类:

  1. 静态路由:由管理员手动配置,不会随着网络的变化而自动调整,比较适合简单、稳定的网络拓扑结构
  2. 动态路由:通过路由协议自动学习和更新,会适应网络拓扑结构变化

在Linux中可以使用route指令看到当前设备的路由表:

需要注意的是,一个路由器需要有默认网关,因为有时路由器并不能知道下一跳的位置,而默认网关一般就是构建当前局域网的路由器

在上面图中,每一个字段表示的含义如下:

  1. Destination:表示目标网络或目标主机的地址,其中default 表示默认路由,适用于所有未匹配到具体目标的流量
  2. Gateway:表示下一跳的网关地址。如果是0.0.0.0,表示目标网络在本地网络中,无需通过网关转发。_gateway 是网关的别名,通常指向默认网关
  3. Genmask:表示子网掩码,用于确定目标网络的范围。例如,255.255.255.255 表示单个主机,255.255.240.0 表示一个子网
  4. Flags:表示路由的标志,常见值包括:

    • U:路由是活动的(Up)
    • G:路由需要通过网关
    • H:目标是一个主机
    • UH:目标是一个主机且路由是活动的
  5. Metric:表示路由的优先级,值越小优先级越高,用于在多条路由中选择最佳路径

  6. Ref:表示路由的引用计数,通常用于调试目的。在现代Linux系统中,这个字段通常为0
  7. Use:表示路由被使用的次数。用于统计路由的使用频率
  8. Iface:表示出接口,即数据包通过哪个网络接口发送。例如,eth0 表示通过以太网接口发送

IP分片与组装

基本介绍

所谓的IP分片就是在网络层将有效载荷进行切分,而切分的依据就是数据链路层的MTU(Maximum Transmission Unit),在Linux中,这个值一般是1500字节。发送方的网络层需要根据MTU进行分片,那么对应的接收方就需要对分片的数据进行组装

但是如果出现分片,那么就会增加丢包的概率,假如,一个包丢包的概率为0.01,那么对应的接收方正常收到数据的概率就是0.99,如果需要分片为10个数据,此时丢包的概率就变成了\(1-0.99^{10}\),所以在实际需要尽可能减少分片的情况

在前面介绍TCP协议时提到了一个滑动窗口的机制,这个机制可以控制一次传输的内容,但是当时并没有介绍为什么滑动窗口内部的内容不是一次性打包发送,而是要分批发送

实际上,想要减少网络层分片就需要传输层传输的数据大小不能太大,例如,以MTU为1500字节为例,那么减去不考虑带选项的IP协议报大小20字节,剩下的数据最大只能为1480字节

IP协议报与实现分片和组装

发送方自己进行了分片,那么接收方就需要对分片的多个数据进行组装,但是接收方如何知道一个数据进行过分片?这里就需要用到IP协议报中的三个字段:16位标识符、3位标志和13位片偏移,下面解释这三个字段的含义:

  1. 16位标识符:用于标记一组进行过分片的数据,即一组分片数据标识符相同。注意如果一个数据并没有分片,该值也不为0
  2. 3位标志:第一位保留待用,第二位置为1表示禁止分片,第三位是否有更多分片,如果有更多分片,该位置为1,否则为0
  3. 13位片偏移:表示当前数据在整个数据的偏移量,例如第一个数据在整个数据的偏移量为0,第二个数据在整个数据的偏移量为前一个数据的偏移量+前一个数据的大小,以此类推

但是,这里存在一个问题,13位片偏移量表示的是当前分片数据在整个数据中的偏移量,这个值最大为\(2^{13}\),而在IP协议报中,最大可以表示的数据大小为\(2^{16}\),此时超过\(2^{13}\)的部分就无法被正确表示。所以,为了解决这个问题,实际填入到13位片偏移中的值为\(2^{16} \div 2^{3}\),即\(2^{16 - 3} = 2^{13}\),此时就可以正确表示。对应地,在组装时需要将偏移量乘以8

基于上面的字段,接收方想知道一个数据是否进行过分片就可以进行下面的步骤:

  1. 收到第一个分片数据:3位标志的第三位置为1,13位片偏移为0
  2. 收到中间某一个分片数据:3位标志的第三位置为1,13位片偏移不为0
  3. 收到最后一个分片数据:3位标志的第三位置为0,13位片偏移不为0

对应的判断一个数据没有被分片只需要判断是否存在3位标志的第三位与13位片偏移均为0即可

用伪代码表示即:

C++
1
2
3
4
5
6
7
8
if(3位标志的第三位 == 0 && 13位片偏移值 == 0)
{
    // 未分片
}
else if(3位标志的第三位 != 0 || 13位片偏移值 != 0)
{
    // 分片
}

基于上面的判断方式,接收方又如何知道自己收到了分片后的全部数据?可以考虑一种思路:对所有分片数据按照片偏移进行升序排序,如果缺少3位标志为1或者为0,那么缺少的就是第一个分片数据或者最后一个分片数据,如果升序排序,片偏移量不连续,说明缺少中间分片数据

以一个具体例子为例,假如现在传输层给网络层的数据大小为3000字节,这个值显然大于了MTU=1500,所以需要进行分片,分片过程如下:

至于为什么数据链路层需要规定这个MTU值在下一节数据链路层会具体讲解