当收到的数据报的协议字段指明这是一个TCP报文段时,ipintr(通过协议协议转换表中的pr_input函数)会调用tcp_input
进行处理,tcp_inut在软件中断一级执行。
函数非常长,我们将分两张讨论,下图列出了tcp_input中的处理框架。本章将结束对RST报文段处理的讲解,下一章开始
介绍ACK报文段的处理。
头几个步骤是非常典型的:对输入报文段做有效性验证(检验和、长度等),以及寻找连接的PCB。尽管后面还有大量的
代码,但通过“首部预测”,算法却有可能完全跳过后续的逻辑。首部预测算法是基于这样的假定,一般情况下,报文段既
不会丢失,次序也不会错误,因此,对于给定连接,TCP总能猜到下一个接收报文段的内容。如果算法起作用,函数直接
返回,这是tcp_input中最快的一条执行路径。
1.预处理
该部分介绍对收到的TCP报文段进行于预处理。处理的大概流程如下:
1.从第一个mbuf中获取IP和TCP首部。
2.验证TCP的检验和。
3.验证TCP偏移字段。
4.把IP和TCP首部及选项放入第一个mbuf。
5.快速处理时间戳选项。
6.保存输入标志,把字段转换成主机字节序。
7.寻找Internet PCB。
8.如果没有找到PCB,则丢弃报文,并发送RST作为响应。
9.如果TCP控制块存在,但连接状态为closed,说明插口已创建,且得到了本地地址和本地端口号,但还未调用connect或
listen。报文段被丢弃,且不发送任何响应。
10.不改变通告窗口大小。
11.如果选定了插口调试选项,则保存连接状态及IP和TCP首部。
12.如果监听插口收到了报文段,则创建新的插口。
13.计算窗口缩放因子。
14.复位空闲时间和保活定时器。
15.如果不处于监听状态,处理TCP选项。
2.首部预测
首部预测算法通过处理两种常见现象,简化单向数据传输的实现。
1.如果TCP发送数据,连接上等待接收的下一个报文段是对已发送数据的ACK。
2.如果TCP接收数据,连接上等待的下一个报文段时顺序到达的数据报文段。
3.TCP输入:缓慢的执行路径
下面介绍首部预测失败时的处理代码,tcp_input中较慢的一条执行路径。
1.丢弃IP和TCP首部,包括TCP选项。
2.计算接收窗口。
因为函数后面的代码必须确定通告窗口中能放入多少数据,所以现在必须计算通告窗口的大小。落在通告窗口之外的接收
数据被丢弃;落在窗口左侧的数据是已接收并确认过的数据,落在窗口右侧的数据时暂时不允许对端发送的数据。
4.完成被动打开或主动打开
如果连接状态等于LISTEN或者SYN_SENT,则执行本节的处理。连接处于这两个状态时,等待接收的报文段为SYN,任何
其他报文将被丢弃。
4.1.完成被动打开
连接状态等于LISTEN时,执行以下处理:
1.丢弃RST、ACK或非SYN。
2.如果是广播报文段或多播报文段,则丢弃它。
3.为客户端的IP地址和端口号分配mbuf。
4.设定PCB中的本地地址。
5.填充PCB中的对端地址。
6.分配并初始化IP和TCP首部模板。
7.处理所有的TCP选项。
8.初始化ISS。
9.初始化控制块中的序号变量。
10.确认SYN并更新状态。
4.2.完成主动打开
1.验证收到的ACK
2.处理并丢弃RST报文段。
3.判断收到的SYN标志是否置位。
4.处理ACK。
5.关闭连接建立定时器。
6.初始化接收序号。
7.连接建立。
8.查看窗口大小选项。
9.向应用进程提交队列中的数据。
10.更新RTT估计器值。
11.处理同时打开。
12.丢弃落在接收窗口外的数据。
13.强制更新窗口变量。
5.PAWS:防止序号回绕
接下来处理可能出现的序号回绕。
1.基本PAWS测试
PAWS算法基于这样的假定:
对于高速连接,32bit时间戳值回绕的速度远小于32bit序号回绕的速度。即使是最高的失踪计数器更新频率(每毫秒加1),
时间戳的符号位也要24天才会回绕一次。而在千兆网络中,序号可能17秒就回绕一次。因此,如果报文段时间戳小于从同
一个连接接收的最近一次的时间戳,说明是个重复报文段,应该被丢弃(还需进行后续的时间戳过期测试)。尽管因为
序号已过时,tcp_input也可将其丢弃,但PAWS算法能够有效地处理序号回绕速率很高的高速网。
注意,PAWS算法是对称的:它不仅丢弃重复的数据报文段,也丢弃重复的ACK。PAWS处理所有收到的报文段。
2.检查过期的时间戳。
3.丢弃重复报文段
6.裁剪报文段使数据在窗口内
本节讨论如何调整收到的报文段,确保它只携带能够放入接收窗口内的数据:
丢弃接收报文段起始处的重复数据。
从报文段尾部起,丢弃超出接收窗口的数据。
从而剩下可放入接收窗口的新数据,用于判断报文段起始处是否存在重复数据。
1.查看报文段前部是否存在重复数据。
2.丢弃重复SYN.
3.判断报文段数据是否完全重复。
4.判断重复FIN。
5.生成重复ACK。
6.处理同时打开或半连接。
7.收到部分重复报文段时,更新统计值。
8.删除重复数据,更新紧急指针。
9.计算落在通告窗口右侧的字节数。
10.如果连接处于TIME_WAIT状态,查看有无新的连接请求。
11.判断是否为窗口探测报文段。
12.丢弃完全落在窗口以外的其他报文段。
13.处理携带部分有效数据的报文段。
7.自连接和同时打开
应用进程创建一个插口,并通过下列系统调用建立自连接:socket,bind半丁到一个本地端口,之后connect试图与同一
本地地址和同一端口号建立连接,如果connect成功,则插口已建立了与自己的连接:向这个插口写入的所有数据,都可以
在同一插口上读出。这有点类似于全双工的管道,但只有一个,而非两个标识符。尽管很少有应用进程会这样做,但实际
上它是一种特殊的同时打开,两者的状态变迁图相同。
8.记录时间戳
下面给出了tcp_input的处理收到的时间戳选项。
如果收到的报文段中带有时间戳,时间戳值保存在变量中。
9.RST处理
下面给出处理RST标志的switch语句,取决于当前的连接状态。
1.SYN_RCVD状态下,插口差错代码设定为ECONNREFUSED,关闭插口。
2.如果在ESTABLISHED、FIN_WAIT_1、FIN_WAIT_2或CLOSE_WAIT状态收到RST,则返回差错代码ECOORESET。
3.如果状态为CLOSING、LAST_ACK或TIME_WAIT,由于应用进程已关闭插口,无需返回差错代码。
4.如果SYN标志依旧置位,说明出现了差错,连接被丢弃,返回代码ECONNRESET。
5.如果ACK标志未置位,报文被丢弃。