网络层最核心的协议就是IP协议(Internet Protocol,因特网协议)。IP协议根据数据包的目的IP地址来决定如何投递它。如果数据包不能直接发送给目标主机,那么IP协议就为它寻找一个合适的下一跳(next hop)路由器,并将数据包交付给路由器来转发。多次重复这个一跳一跳的过程,最终将数据包交送给目标主机,或者由于发送失败而被丢弃。
IP协议的三个特点:
- 无状态:是指IP通信双方不同步传输数据的状态信息,因此所有IP数据报的发送、传输和接收都是相互独立、没有上下文关系的。这种服务的缺点就是无法处理乱序和重复的IP数据报(比如第N+1个IP数据报要比第N个IP数据报先到接收端,而同一个IP数据报可能通过不同路径多次到达接收端)。接收端的IP层只要接收到了IP数据报就将数据部分交给上层协议,那么从上层协议来看,这些数据可能是乱序的(注意IP头部的标识段是用来标识IP分片的,能够标识一个IP数据报的分片,而不是用来指示多个IP数据报的接收顺序)。无状态也有优点:那就是简单,高效
- 无连接:IP通信双方都不长久地维持对方的任何信息。上层协议每次发送数据的时候,都必须明确指定IP地址。
- 不可靠:IP协议不能够确保IP数据报能够准确到达,只承诺尽最大努力。当IP数据报因为种种原因被中途丢弃的时候,会发送一个ICMP错误消息给发送端,这样就会通知上层协议发送失败,而不会试图重新传送。因此IP协议的上层协议(比如TCP协议)需要自己实现数据确认、超时重传等机制以达到可靠传输
本文先来看看IP协议的头部结构。
IP协议头
- 4位版本号:指定IP协议的版本,IPv4就是4
- 4位首部长度:IP头部的长度是多少个32位。4位的最大值是15,32位是4个字节,所以IP头部的最大值是4*15=60个字节。从上图可以推断出,选项部分最多有40个字节
- 8位服务类型(TOS):3位优先权字段(现在已经被忽略),4位TOS字段,和1位保留字段(必须置为0),4位TOS分别表示:最小延时,最大吞吐量,最高可靠性,最小成本。这四者相互冲突,只能选择一个。对于ssh/telnet这样的应用程序,最小延时比较重要;对于ftp这样的应用程序,最大吞吐量比较重要
- 16位总长度:IP数据报整体占多少个字节,因此IP数据报的最大长度是2^16-1=65535个字节,但由于MTU的限制,长度超过MTU的数据都将被分片传输,所以实际传输的IP数据报(或分片)的长度都远远没有达到最大值,接下去的三个字段描述如何分片
- 16位标识:唯一的标识主机发送的每一个数据报,其初始值由系统随机生成;每发送一个数据报,其值就+1。如果IP报文在数据链路层被分片了,那么每一个片里面的这个标识是相同的
- 3位标志(MF):第一位保留(现在不用,没想好以后用不用),第二位置为1表示禁止分片,这时候如果报文长度超过MTU,IP模块就会丢弃报文并返回一个ICMP差错报文。第三位表示“更多分片”,即如果最后这一位是1,表示这是最后一个分片,其余之前的分片这一位是0,这一位相当于一个结束标志,表示之后还有没有分片
- 13位分片偏移:是分片相对于原始IP报文开始处的偏移。其实就是表示当前分片在原报文中的哪个位置。实际偏移的字节数是这个数*8得到的。因此,除了最后一个报文外,其他报文的长度必须是8的整数倍(否则报文就不连接了)
- 8位生存时间(TTL):数据报到达目的地的最大报文跳数。一般是64。每次经过一个路由,TTL-1,一直减到0还没到达,那么就丢弃了并向源端发送一个ICMP差错报文。这个字段主要是用来防止出现路由循环
- 8位协议(protocol):用来区分上层协议,其中,ICMP是1,TCP是6,UDP是17,具体的可看/etc/protocols文件
- 16位头部校验和:由发送端填充,接收端对其使用使用CRC(循环冗余校验)算法进行校验,来鉴别头部是否损坏
- 32位源地址和32位目的地址:表示发送端和接收端
-
选项:最长40字节(60最大长度-20固定长度)。
- 记录路由:告诉数据报途径的所有路由器都将自己的IP地址填入IP头部的选项部分
- 时间戳:告诉每个路由器都将数据报被转发的时间(或时间与IP地址对)填入IP头部的选项部分
- 松散源路由选择:指定一个路由器IP地址列表,数据报发送过程必须经过其中所有的路由器
- 严格源路由选择:与松散源类似,不同的是只能经过列表中的被指定的路由器
- 数据:即上一层TCP和UDP的数据报
使用tcpdump观察IPv4头部结构
我们从测试机器上执行telnet登录本机,并用tcpdump抓取这个过程中telnet客户端程序和telnet服务器程序之间交换的数据包。具体命令如下
sudo tcpdump -ntx -i lo //抓取本地回路上的数据包,-x可以以十六进制显示数据包内容
telnet 127.0.0.1 //另开一个终端,登录本地telnet服务器
此时观察tcpdump输出的第一个数据包
该数据报描述的是一个IP数据报,由于我们是使用telnet登录本机的,所以源IP地址和目的IP地址都是127.0.0.1。telnet服务器的端口号是23,而客户端程序所用的是临时端口号59200。“Falgs seq win options”都是TCP的头部信息,这里暂且不讨论,length表示该IP数据报所携带的应用程序数据的长度。
重点来看看下面以十六进制格式打印出来的数据,是IP头部的内容信息
十六进制数 |
十进制表示 |
IP头部信息 |
0x4 |
4 |
IP版本号 |
0x5 |
5 |
头部长度为5个32位(20字节) |
0x10 |
TOS选项中最小延时服务被开启 |
|
0x003c |
60 |
数据报总长度,60字节 |
0x2654 |
数据报标识,随机生成 |
|
0x4 |
设置了禁止分片标志(实际4位,只用前3位,实际0100,取010,第二位为1表示禁止分片) |
|
0x000 |
0 |
分片偏移 |
0x40 |
64 |
TTL被设为64 |
0x06 |
6 |
协议字段为6,表示上层协议是TCP |
0x1656 |
IP头部校验和 |
|
0x7f000001 |
32位源IP地址127.0.0.1 |
|
0x7f000001 |
32位目的IP地址127.0.0.1 |
如上可见,telnet服务选择使用具有最小延时的服务,且默认的传输层协议是TCP。