文章目录
- 前言
- RFC 9293 传输控制协议Transmission Control Protocol (TCP)
- 摘要
- 文档状态
- 版权声明
- 1. 目的和范围
- 2. 引言
- 3. Functional Specification 功能规范
- 3.1. Header Format 头部格式
- 3.2. Specific Option Definitions 特定选项解释
- 3.3. TCP Terminology Overview TCP术语概述
- 3.4. Sequence Numbers 序列号
- 3.5. Establishing a Connection 建立连接
- 3.6. Closing a Connection 关闭连接
- 3.7. Segmentation 分段
- 3.8. Data Communication 数据通信
- 3.8.1. Retransmission Timeout 重传超时
- 3.8.2. TCP Congestion Control TCP拥塞控制
- 3.8.3. TCP Connection Failures TCP连接失败
- 3.8.4. TCP Keep-Alives TCP保活
- 3.8.5. The Communication of Urgent Information 紧急消息的通信
- 3.8.6. Managing the Window 管理窗口
- 3.9. Interfaces 接口
- 3.10. Event Processing 事件处理
前言
之前搜TCP协议的资料,发现零零散散的各种文档规范,如RFC793等,其中有的有点过时了。其实,有个集大成者RFC9293,发现网上说的较少,且没找到中文版。因此翻译一版,便于大家学习TCP。翻译如有谬误,请大家不吝指教。
RFC 9293 传输控制协议Transmission Control Protocol (TCP)
摘要
本文档规定了传输控制协议(TCP)。TCP是互联网协议栈中的重要的传输层协议,并且随着数十年的使用和互联网发展,TCP不断改进。在此期间,对RFC793的TCP进行了大量更改,尽管这些更改只是以零碎的方式记录下来。本文档收集了那些更改,并整合了更改和RFC793中的协议规范,废弃了RFC793,以及对RFC793中部分内容有更改的RFC879、2873、6093、6429、6528、6691。它更新了RFC1011、1122,并应该考虑以本文档代替那些文档中处理TCP要求的部分。它也通过在SYN-RECEIVED状态时的重置处理中添加了一个小的声明,更新了RFC5691。RFC793中的TCP头部控制位也根据RFC3168更新了。
文档状态
这是互联网标准跟踪文档。
本文档是互联网工程任务组(IETF)的成果。它代表了IETF社区的共识。它已经收到了公开的审查并获得了互联网工程指导组(IESG)的批准发布。进一步的互联网标准的信息,请参见RFC7841的第2节。
有关本文档当前状态的信息,任何勘误表,以及如何提供反馈,可以在https://www.rfc-editor.org/info/rfc9293中获取。
版权声明
版权所有(c)2022 IETF基金和文档中声明的作者。保留所有权利。
本文档受BCP78和IETF基金有关IETF文档的法律规定的约束(http://trustee.ietf.org/license-info),自本文件出版之日起生效。请仔细阅读这些文档,因为它们描述了您对本文档的权利和限制。从中提取的代码组件必须包含修订后的 BSD 许可证文本,如基金法律条款的第 4.e节,并如修订后的 BSD 许可证中所述,提供仅供参考。
本文档可能包含了来自IETF Documents或者IETF Contributions发布或者在2008年11月10日前公开发表的材料。一些材料的版权所有人,可能没有授予IETF基金,允许IETF标准处理机构以外的人修改这些材料的权力。如果未从材料版权相关人员处获得足够的许可证,除了调整格式便于RFC发布或翻译成英语以外的语言以外,本文档不得在IETF标准处理机构以外进行修改,以及创建衍生产品。
1. 目的和范围
1981年,RFC793发布了,规定了传输控制协议(TCP)并替代了之前发布的TCP规范。
自从那时起,TCP已经被广泛地实施,并作为传输层协议应用于互联网数量庞大的应用中。
数十年来,RFC793加上一系列其他文档一起成为了TCP的核心规范。随着时间推移,提交了许多RFC793的勘误表。在安全、性能和许多其他方面,也发现并修改了一些缺陷。多年来通过许多独立的文档,加强优化的数量不断增长。之前从来没有累计一起全面更新到基础规范中。
本文档的目的是提供所有IETF标准追踪更改和其他为基础TCP功能规范(RFC793)所做的声明,并统一它们到一个新的规范版本中。
一些相关文档引用了用于TCP(例如拥塞控制)的重要算法,但是没有完整收录在本文档中。这是一个特意的选择,因为这个基础规范可以和多个单独开发、组织的附加算法一起使用。本文档聚焦于所有TCP实现为了交互必须支持的通用基础规范。因为一些额外的TCP特性已经变得相当复杂(例如高级丢包恢复和拥塞控制),未来的相关文档可能尝试一样一起汇总。
除了描述要在代码中实现的TCP段格式、生成和处理规则的协议规范,RFC793和其他更新也包含了信息性和描述性的文本,便于读者理解协议设计和运行的各个方面。本文档并没有尝试改变或者更改信息性的文本,仅聚焦于更新标准的协议规范。在适当情况下,本文档保留了包含重要解释和基本原理的参考文档的引用。
本文档旨在用于检查现有 TCP 实现的一致性,以及编写新的实现。
2. 引言
RFC793包含了TCP设计目标的讨论,并提供了操作的示例,例如包括了连接建议、连接终止和修复丢包的包重传。
本文档描述了当代TCP实现中所需的基础功能,并代替RFC793中的协议规范。它不会复制或者尝试更改RFC793的第1、2节中介绍和设计哲学。引用了其他文档,以提供对操作理论、基本原理的解释,和设计决策的详细讨论。本文档只聚焦于协议的标准行为。
“TCP路线图”提供了定义TCP和描述各种重要算法的RFC文档的更广泛的指南。TCP路线图包含了,提升TCP性能和提升本文档基础操作规范以外的其他方面的强烈建议的增强。例如,实现拥塞控制(例如,RFC5681)是TCP的一项要求,但是它本身就是个复杂的主题,本文档中没有详细阐述,因为有许多不影响基础交互功能的选项和可能方案。同样的,今日的大部分TCP实现包含了高性能扩展,但是本文档没有严格要求或者讨论。TCP的多路径的思考也单独规范。
RFC793的更改列表包含在第5节中。
2.1. Requirements Language 要求语言
本文档中的关键词“必须”、“不得”、“要求”、“应”、“不应”、“应该”、“不应该”、“推荐”、“不推荐”、“可以”和“可选”应且仅当它们全部大写时,按照BCP14 [ 3 ] [ 12 ]中的描述进行解释,如下所示。
文档中每个RFC2119的关键字的使用都有单独标记,并在附录B中引用,其中总结了实现要求。
使用“必须”的句子被标记为“ 必须-X ”,其中X是个数字标识,可以容易地定位到附录B的引用。
同样地,使用”应该“的句子被标记为“ 应该-X ”、“ 可以”用“可以-X”标记,“ 推荐 ”用“推荐-X”标记。
为了达到统一标记的目的,“不应”和“不得”的标签与“应该”和“必须”的例子类似。
2.2. Key TCP Concepts 关键的TCP概念
TCP为应用提供了可靠的、有序的、字节流服务。
应用的字节流在网络中通过TCP的段来传输,每个TCP段被作为一个IP数据包发送。
TCP可靠性由丢包检测(通过序列号sequence numbers)和错误检测(通过每个段的校验和)、重传校正构成。
TCP支持数据的单播传输。有的泛播的应用能够不对TCP修改而成功使用TCP,但是因为更低层级的转发行为[46]的变化,有些不稳定的风险。
TCP是面向连接的,尽管它并没有内置包含探活功能。
基于TCP连接的数据流是支持双工的,但是应用也可以选择自由地单向发送数据。
TCP使用端口号来识别应用服务,并在主机间多路复用不同的流。
TCP对比其他传输协议的特性的更详细描述,请参见 [52] 的第3.1节。
3. Functional Specification 功能规范
3.1. Header Format 头部格式
TCP段被作为网络数据包发送。IP的头(header)携带了多个信息字段,包括源主机地址和目标主机地址[1] [13]。TCP的头(header)跟在IP的头(header)后面,提供特定于TCP的信息。这个划分方式允许TCP以外的主机间协议的存在。在互联网协议套件开发的早期,IP的头(header)字段一直是TCP的一部分。
本文档描述了使用TCP头(header)的TCP。
段中的一个TCP头(header),后面跟着任何用户数据,格式如下,使用[66]中的样式:
-
源端口 Source Port:
16 bits
源端口号 -
目标端口 Destination Port:
16 bits
目标端口号 -
序列号 Sequence Number:
32 bits
这个段中的第一个数据的8位字节所处于的序列号(设置SYN标志位的除外)。如果SYN设置了,序列号是初始值(ISN),并且第一个数据的8位字节所处于的序列号是ISN+1。 -
确认号 Acknowledgment Number:
32 bits
如果ACK控制位被设置了,这个字段包含了段的发送方期望接收的下一个序列号。一旦连接建立后,确认号会一直发送。 -
数据偏移量 Data Offset (DOffset):
4 bits
TCP的头(header)中,以32位为单位的头长度。这代表了数据从哪开始。TCP的头(还包括选项options)是一个32位整数倍的长度。 -
保留位 Reserved (Rsrvd):
4 bits
为将来使用预留的一组控制位。在生成的段中必须是0,如果相应的未来的特性没有被发送主机或者接收主机实现,那么在接收的段必须忽略它。
控制位 Control bits:
控制位也被称为标识位。由IANA在"TCP头部标志位"注册表中管理分配工作。当前已分配的控制位是CWR、ECE、URG、ACK、PSH、RST、SYN和FIN。
-
拥塞窗口减少 CWR:
1 bit
Congestion Window Reduced.
拥塞窗口减少标志(参见 [6])。 -
拥塞Echo响应 ECE:
1 bit
显式拥塞通知(ECN)的Echo响应(参见 [6])。 -
紧急 URG:
1 bit
紧急指针(Urgent Pointer)字段值得注意。 -
确认 ACK:
1 bit
确认号(Acknowledgment)字段值得注意。 -
推送 PSH:
1 bit
推数据功能(参见第3.9.1节)。 -
重置 RST:
1 bit
重置连接。 -
同步 SYN:
1 bit
同步序列号。 -
结束 FIN:
1 bit
发送方不再有数据。
-
窗口大小 Window:
16 bits
这个段的发送方愿意接受的8位字节数,字节会从确认号(Acknowledgment)字段中的值开始。
窗口大小必须作为无符号数字,否则超大的窗口大小有可能出现负数,导致TCP无法工作(必须-1)。推荐的实现方法是,连接记录中,发送和接收窗口的大小保留32位字段,并且基于32位(推荐-1)进行所有窗口大小计算。 -
校验和 Checksum:
16 bits
校验和字段是头部和文本中按照16位字划分后的和。校验和的计算需要确保数据累加后16位对齐。如果一个段包含了一个奇数数目的头和文本字节,为了生成校验和,可以在最后的字节右边用0补足一个字节来对齐,最后的字节和0就形成了16位。补足的部分不会作为段的一部分传输。当计算校验和时,校验和字段本身会被0替换。
校验和也涵盖了一个伪头部(图片2),概念上放在TCP头(header)。伪头部在IPv4是96位,在IPv6是320位。在校验和中包含伪头部,防止了TCP连接处理路由错误的段。这个信息在IP头部中携带了,并且通过TCP网络接口传参来转换,或者是作为IP层之上的TCP实现的调用结果。
图片 2: IPv4 伪头部Pseudo-header
IPv4的伪头部组成:
源地址 Source Address:
按网络字节序排列的IPv4的源地址
目标地址 Destination Address:
按网络字节序排列的IPv4的目标地址
零 zero:
每一个位都设置为0
协议号 PTCL:
IP头中的协议号
TCP长度 TCP Length:
TCP头的长度加上数据的字节长度(这不是一个显式传输的数量,而是计算得到),它不包含伪头部的12个字节。
对IPv6,伪头部定义在RFC8200的第8.1节[13],并包含了IPv6的源地址、目标地址、上层数据包长度(32位的值,否则等同于IPv4的伪头部中的TCP长度)、三个以0填充的字节、下一个头部值(Next Header Value,如果IPv6和TCP间有扩展头部,则与IPv6的头部值不同)。
TCP的校验和不是可选项。发送方必须生成它(必须-2),接收方必须校验它(必须-3)。
-
紧急指针 Urgent Pointer:
16 bits
这个字段把紧急指针中的当前值作为了这个段中相对于序列号的正偏移量。这个紧急指针指向了紧急数据后的字节序列号。这个字段仅当段中的URG控制位被设置时生效。 -
选项 Options:
[TCP选项];size(选项Options) == (数据偏移量DOffset-5)*32;仅当数据偏移量DOffset>5时存在。注意这个长度表达式也包含了实际选项尾部的任何填充。
选项可以在TCP头(header)空占着,并且8位整数倍的长度。所有的选项都被校验和包含了。一个选项可以从任何字节边界开始。对于一个选项的格式,有两种情况:
情况1:
仅一个字节,代表选项种类(option-kind)。
情况2:
选项种类(option-kind)1个字节、选项长度(option-length)1个字节、实际的选项数据(option-data)字节。
选项长度(option-length)包括了选项种类、选项长度和选项数据的总长度。
注意选项列表可能比数据偏移量(Data Offset)字段表示得更短。选项列表结束项(End of Option List Option)以外的头部内容必须以0做头部填充(必须-69)。
当前所有以定义的选项列表由IANA [62]管理,每个选项在其他RFC文档中定义,也如那里所示。该集合包含了扩展支持多个并行使用 [45]的实验性选项。
一个给定的TCP实现中能够支持任何当前已定义的选项,但是以下的选项必须被支持(必须-4 – 注意最大段数(Maximum Segment Size)选项支持也是第3.7.1节的必须-14中的一部分)。
这些选项在第3.2节中详细说明。
TCP实现必须支持在任何段中接收TCP选项(必须-5).
TCP实现必须(必须-6)无错误地忽略它未实现的TCP选项,并认为这个选项有长度字段。除了选项列表结束项(End of Option List Option,EOL)和无操作(No-Operation,NOP)的选项,所有TCP选项都必须有长度字段,包括所有的未来的选项(必须-68)。TCP实现必须准备好处理非法的选项长度(例如0);建议的处理方式是重置连接并记录错误原因日志(必须-7)。
注意:有进行中的工作在扩展TCP选项的可用空间,例如[65]。
- 数据 Data:
可变长度
TCP段中携带的用户数据。
3.2. Specific Option Definitions 特定选项解释
强制选项集合中的TCP选项,包括了选项列表结束( End of Option List)项、无操作(No-Operation)选项、最大段大小(Maximum Segment Size)选项。
-
选项列表结束( End of Option List)项格式如下:
种类 Kind:
1 byte; 种类Kind == 0.
这个选项code代表了选项列表的结束。这可能与TCP头(header)中数据偏移量(Data Offset)字段指明的结尾处不一致。它在所有选项的结尾使用,而不是一个选项的结尾,并且,只有当选项的结尾和TCP头(header)的结尾不一致时才被使用。 -
A No-Operation Option is formatted as follows 无操作(No-Operation)选项的格式如下:
种类 Kind:
1 byte; 种类Kind == 1.
这个选项code可以在选项之间使用,例如,在字的边界上对齐之后的选项。无法保证发送方会使用这个选项,因此接收方必须准备好处理选项,即使这些选项并不从字的边界开始(必须-64)。 -
最大段(Maximum Segment Size)选项的格式如下:
种类 Kind:
1 byte; 种类Kind == 2.
如果这个选项存在,那么在发送段的TCP端点,它传达了最大接收段大小。它的值受到IP分片重组上限的限制。这个字段可以在初始连接的请求中发送(例如,在设置了SYN控制位的段),并且不得在其他段中发送(必须-65)。如果这个选项没有被使用,任何段大小都是允许的。有关此选项更多完整的描述参见第3.7.1节。
长度 Length:
1 byte; 长度Length == 4.
这个选项的字节长度。
最大段大小 Maximum Segment Size (MSS):
2 bytes.
发送段的TCP端点的最大接收段大小。
3.2.1. Other Common Options 其他常用选项
其他RFC文档定义了一些其他常用的选项,为了高性能它们被推荐实现,但对基本的TCP交互而言不是必须的。有TCP的选择性确认(Selective Acknowledgment,SACK)选项 [22] [26]、TCP窗口缩放(Window Scale,WS)选项 [47]。
3.2.2. Experimental TCP Options 实验性的TCP选项
实验性的TCP选项值定义在[30], [45] 描述了这些实验性值的当前推荐使用方式。
3.3. TCP Terminology Overview TCP术语概述
这一节包含了理解文档的其余部分详细协议操作所需的关键术语的概述。在第4节中有个术语列表。
3.3.1. Key Connection State Variables 核心连接状态变量
在我们详细讨论TCP实现的操作前,我们需要介绍一些详细术语。TCP连接的保持需要维护多个变量的状态。我们设想这些变量存储在连接记录中,连接记录被称为传输控制块(Transmission Control Block)或者TCB。在TCB的变量间是本地和远程的IP地址、端口号、IP安全等级和连接区间(参见附录A.1)、指向用户发送和接收缓冲的指针、指向重传队列和当前段的指针。另外,一些关于发送和接收序列号的变量也被存储在TCB。
变量 | 描述 |
---|---|
SND.UNA | send unacknowledged 发送未确认 |
SND.NXT | send next 下一个发送 |
SND.WND | send window 发送窗口 |
SND.UP | send urgent pointer 发送紧急指针 |
SND.WL1 | segment sequence number used for last window update 用于最后窗口更新的段序列号 |
SND.WL2 | segment acknowledgment number used for last window update 用于最后窗口更新的段确认号 |
ISS | initial send sequence number 初始序列号 |
Table 2: 发送序列变量 Send Sequence Variables
变量 | 描述 |
---|---|
RCV.NXT | receive next 下一个接收 |
RCV.WND | receive window 接收窗口 |
RCV.UP | receive urgent pointer 接收紧急指针 |
IRS | initial receive sequence number 初始接收序列号 |
Table 3: 接收序列变量 Receive Sequence Variables
下图可以帮助关联一些变量和序列空间。
1 2 3 4
----------|----------|----------|----------
SND.UNA SND.NXT SND.UNA
+SND.WND
1 - 已确认的序列号 old sequence numbers that have been acknowledged
2 - 未确认的序列号 sequence numbers of unacknowledged data
3 - 允许新数据传输的序列号 sequence numbers allowed for new data transmission
4 - 还未允许传输的未来的序列号 future sequence numbers that are not yet allowed
Figure 3: 发送序列空间 Send Sequence Space
发送窗口是图3(Figure 3)中标记为3 的序列空间的部分。
1 2 3
----------|----------|----------
RCV.NXT RCV.NXT
+RCV.WND
1 - 已确认的序列号 old sequence numbers that have been acknowledged
2 - 允许新接收的序列号 sequence numbers allowed for new reception
3 - 还未允许接收的未来的序列号 future sequence numbers that are not yet allowed
Figure 4: 接收序列空间 Receive Sequence Space
接收窗口是图4(Figure 4)中标记为2的序列空间的部分。
讨论中,也有一些经常使用的变量从当前段的字段中获取它们的值。
变量 | 描述 |
---|---|
SEG.SEQ | segment sequence number 段序列号 |
SEG.ACK | segment acknowledgment number 段确认号 |
SEG.LEN | segment length 段长度 |
SEG.WND | segment window 段窗口 |
SEG.UP | segment urgent pointer 段紧急指针 |
Table 4: 当前段变量 Current Segment Variables
3.3.2. State Machine Overview 状态机概述
在连接的生命周期中,通过一系列的状态推动连接。状态有:监听 LISTEN、同步已发送 SYN-SENT、同步已收到 SYN-RECEIVED、已连接 ESTABLISHED、结束等待1 FIN-WAIT-1、结束等待2 FIN-WAIT-2、关闭等待 CLOSE-WAIT、关闭中 CLOSING、最后确认 LAST-ACK、时间等待 TIME-WAIT和虚拟状态已关闭 CLOSED。已关闭是虚拟的,因为它代表了没有TCB的状态,因此,没有连接。简介下状态含义:
- 监听 LISTEN -
代表了正在等待远程TCP对端的连接请求。 - 同步已发送 SYN-SENT -
代表了,已经发送连接请求后,正在等待匹配的连接请求。 - 同步已收到 SYN-RECEIVED -
代表了,已经收到并发送连接请求后,正在等待确认中的连接请求确认。 - 已连接 ESTABLISHED -
代表了一个打开的连接,接收的数据可以交付给用户。连接阶段数据传输的正常状态。 - 结束等待1 FIN-WAIT-1 -
代表了,正在等待远程TCP对端的的连接中断请求,或者对之前发送的连接请求的确认。 - 结束等待2 FIN-WAIT-2 -
代表了正在等待远程TCP对端的连接中断请求。 - 关闭等待 CLOSE-WAIT -
代表了正在等待本地用户的连接中断请求。 - 关闭中 CLOSING -
代表了正在等待远程TCP对端的连接中断请求确认。 - 最后确认 LAST-ACK -
代表了,正在等待之前发送给远程TCP对端的连接中断请求(这个发送到远程TCP对端的中断请求已经包含了一个发送给远程TCP对端的中断请求的确认)的确认。 - 时间等待 TIME-WAIT -
代表了,正在等待一段充足的时间,来确保远程TCP对端已经接收了连接中断请求的确认,避免新的连接被之前连接的延迟的段影响。 - 已关闭 CLOSED -
代表了完全无连接状态。
TCP连接从一个状态迁移到另一个状态以响应事件。事件有,用户的调用、OPEN、SEND、RECEIVE、CLOSE、ABORT和STATUS;刚收到的段,特别是那些包含SYN、ACK、RST、FIN标志位的;以及超时。
OPEN调用指明了连接建立是主动请求,还是被动等待。
被动的OPEN请求意思是,进程想要接受即将到来的连接请求,和主动的OPEN尝试初始化连接正好相反。
图5(Figure 5)的状态图仅说明了状态变化,引起状态变化的事件,以及导致结果的行为,但是
不会说明错误条件和与状态变化无关的行为。在后面的节中,会提供关于TCP实现对事件的处理的更多详情。一些状态名称是缩写的或者带连字符的,有别于文档中它们在其他地方的图。
请注意:
这个图仅仅是一个概要,不得作为总规范。许多细节并未包含。
+---------+ ---------\ active OPEN
| CLOSED | \ -----------
+---------+<---------\ \ create TCB
| ^ \ \ snd SYN
passive OPEN | | CLOSE \ \
------------ | | ---------- \ \
create TCB | | delete TCB \ \
V | \ \
rcv RST (note 1) +---------+ CLOSE | \
-------------------->| LISTEN | ---------- | |
/ +---------+ delete TCB | |
/ rcv SYN | | SEND | |
/ ----------- | | ------- | V
+--------+ snd SYN,ACK / \ snd SYN +--------+
| |<----------------- ------------------>| |
| SYN | rcv SYN | SYN |
| RCVD |<-----------------------------------------------| SENT |
| | snd SYN,ACK | |
| |------------------ -------------------| |
+--------+ rcv ACK of SYN \ / rcv SYN,ACK +--------+
| -------------- | | -----------
| x | | snd ACK
| V V
| CLOSE +---------+
| ------- | ESTAB |
| snd FIN +---------+
| CLOSE | | rcv FIN
V ------- | | -------
+---------+ snd FIN / \ snd ACK +---------+
| FIN |<---------------- ------------------>| CLOSE |
| WAIT-1 |------------------ | WAIT |
+---------+ rcv FIN \ +---------+
| rcv ACK of FIN ------- | CLOSE |
| -------------- snd ACK | ------- |
V x V snd FIN V
+---------+ +---------+ +---------+
|FINWAIT-2| | CLOSING | | LAST-ACK|
+---------+ +---------+ +---------+
| rcv ACK of FIN | rcv ACK of FIN |
| rcv FIN -------------- | Timeout=2MSL -------------- |
| ------- x V ------------ x V
\ snd ACK +---------+delete TCB +---------+
-------------------->|TIME-WAIT|------------------->| CLOSED |
+---------+ +---------+
Figure 5: TCP连接状态图 TCP Connection State Diagram
以下的说明适用于对图5(Figure 5):
注 1(note 1):
接收到RST时从SYN-RECEIVED到LISTEN的迁移,条件是在被动OPEN后已经到达了SYN-RECEIVED状态。
注 2(note 2):
图省略了一种迁移,如果收到了FIN且本地的FIN也已被确认了,从FIN-WAIT-1到TIME-WAIT的迁移。
注 3(note 3):
RST可以从任何状态发送,并相应地迁移到TIEM-WAIT(原因参见 [70])。这些迁移并未显式展示;否则,图将很难阅读。同样的,在任何状态接收到RST会导致迁移到LISTEN或者CLOSED,尽管为了便于阅读,图中也被省略了。
3.4. Sequence Numbers 序列号
设计中的一个基本观念是,通过TCP连接发送的每一个数据字节都有序列号。因为每个字节都有序列,每个都可以被确认。所采用的确认机制是渐增的,所以序列号X的确认预示着除了X以外的所有X之前的字节都已经被接收了。这个机制允许存在重传的情况下做直接的重复检测。一个段中的字节编号方案如下:直接在头(header)后的第一个数据字节是最小的编号,其后的字节连续编号。
有个关键要记住,真实的序列号空间是有限的,尽管很大。空间范围是0到 232 - 1。由于空间是有限的,所有序列号的计算处理必须以模232执行。当循环从232 - 1再回到0时,这种无符合的计算保留了序列号间的关系。计算取模有一些微妙之处,所以在编程比较值时要非常小心。符号"=<"意思时“小于或者等于”(模232)。
TCP实现必须包含的典型序列号比较方式:
( a ) 确定指向已发送但未确认的序列号的确认。
( b ) 确定一个段中的所有序列号已经被确认了(例如,从重传队列中移除这个段)。
( c ) 确定传入段中包含了期望的序列号(即,段和接收窗口"重叠")。
作为对发送数据的响应,TCP端点将收到确认。处理确认需要以下的比较:
-
SND.UNA = 最早的未确认序列号 oldest unacknowledged sequence number
-
SND.NXT = 下一个将要发送的序列号 next sequence number to be sent
-
SEG.ACK = 来自TCP接收方的确认(TCP接收方期望收到的下一个序列号) acknowledgment from the receiving TCP peer (next sequence number expected by the receiving TCP peer)
-
SEG.SEQ = 一个段的第一个序列号 first sequence number of a segment
-
SEG.LEN = 段中数据所占的字节数(包括计算SYN和FIN代表的一个字节) the number of octets occupied by the data in the segment (counting SYN and FIN)
-
SEG.SEQ+SEG.LEN-1 = 一个段的最后的序列号 last sequence number of a segment
一个新的确认(被称为“可接受的确认”)要满足以下不等式:
- SND.UNA < SEG.ACK =< SND.NXT
重传队列中的一个段,如果它的序列号以及数据长度的和小于等于传入段的确认号,则它已被完整确认了。
当收到数据,需要做以下的比较:
-
RCV.NXT = 期望的传入段的下一个序列号,并等于接收窗口的左边界 next sequence number expected on an incoming segment, and is the left or lower edge of the receive window
-
RCV.NXT+RCV.WND-1 = 期望的传入段的最后的序列号,并等于接收窗口的右边界 last sequence number expected on an incoming segment, and is the right or upper edge of the receive window
-
SEG.SEQ = 传入段所占用的第一个序列号 first sequence number occupied by the incoming segment
-
SEG.SEQ+SEG.LEN-1 = 传入段所占用的最后的序列号 last sequence number occupied by the incoming segment
如果满足以下条件,则认为段占用了有效接收空间的一部分:
RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
或者
RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND
此判断条件的第一部分是检验段的开头是否在窗口中,判断条件的第二部分是检验段的结尾是否在窗口中;如果段通过了判断条件的任一部分,则它有数据在窗口中。
实际情况比这要复杂一点。由于0窗口大小和0长度段,对于传入段的可接受性,我们有4种情况:
段长度 | 接收窗口 | 测试 |
---|---|---|
0 | 0 | SEG.SEQ = RCV.NXT |
0 | >0 | RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND |
>0 | 0 | 不接受 not acceptable |
>0 | >0 | RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND 或者 RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND |
Table 5: 段可接受性测试 Segment Acceptability Tests
注意,当接收窗口大小是0,除了ACK段,其他段都不能接受。因此,TCP实现有可能在传输数据和接收ACK时保持0大小的接收窗口。TCP接收者必须处理所有传入段的RST和URG字段,即使接收窗口为0(必须-66)。
我们也利用了编号方案来保护特定的控制信息。这是通过在序列空间中隐式包含一些控制位,以便它们能被重传和无混淆地确认(例如,有且仅有一个控制位起作用)。控制信息并未物理携带在段的数据空间中。因此,我们必须采用规则隐式地分配序列号来控制。SYN和FIN是仅有的需要这种保护的控制信息,它们仅在连接打开和关闭中使用。出于序列号的目的,SYN被认为发生在段的第一个实际数据出现前,而FIN被认为发生在段的最后的实际数据出现之后。段长度(SEG.LEN)包含了数据和序列空间占用控制信息。当存在SYN时,SEG.SEQ是SYN的序列号。
3.4.1. Initial Sequence Number Selection 初始序列号选择
连接由一对套接字定义。连接可以被重用。一个连接的多个新实例将被视为连接的化身。由此产生的问题是-“TCP实现如何区分来自前一个连接化身的重复的段?”如果连接被快速连续地打开和关闭,或者连接因内存丢失而中断,然后重新建立,那么此问题会变得明显。为了支持解决此问题,TIME-WAIT状态限制了连接重用的速率,而下面描述的初始序列号选择进一步防止了传入数据包对应于哪个连接化身的歧义。
为了避免混淆,我们必须防止一个连接化身的段被使用,而同样的序列号可能仍然在网络中存在,它来自更早的连接化身。我们想要确保这个,即使TCP端点丢失了所有它使用中的序列号的确认。当新的连接建立了,一个初始序列号(ISN)生成器会选择一个新的32位ISN。如果有一个非正常路径的攻击者能够预测或者猜测ISN的值 [42],那么会导致安全问题。
TCP初始序列号由单调递增的数字序列(有上限)生成,大致被称为“时钟”。这个时钟是一个32位的计数器,通常大概每4微秒至少递增一次,尽管它既不被认为是实时的,也不是精准的,并且重启时也不需要持久保存。这个时钟组件是用来确保在一个最长报文段寿命(Maximum Segment Lifetime,MSL)内,生成的ISN是唯一的,因为它每接近4.55小时才循环重新初始化,比MSL长得多了。请注意,对于支持高速率数据的现代网络,连接可能开始并在MSL内快速地提高序列号产生序列号重叠,推荐实现时间戳选项(Timestamp Option),在之后的节3.4.3中会提及。
TCP实现必须使用以上类型的“时钟”,进行时钟驱动的初始化序列号选择(必须-8),并应该使用表达式生成初始化序列号:
- ISN = M + F(localip, localport, remoteip, remoteport, secretkey)
M是4微秒的定时器,F()是一个伪随机方法(pseudorandom function,PRF),它基于连接的标识参数(“本地ip localip, 本地端口 localport, 远程ip remoteip, 远程端口 remoteport”)和密钥(“secretkey”)(应该-1)。F()不得从外部计算(必须-9)得到,否则攻击者仍然可以通过其他连接的ISN猜测序列号。PRF可以实现为TCP连接参数拼接一些私密数据后的加密哈希。关于特定哈希算法的选择和密钥数据的管理,请参见 [42]的第3节。
每个连接都有一个发送序列号和一个接收序列号。初始发送序列号(initial send sequence number,ISS)由数据发送的TCP端选择,而初始接收序列号(initial receive sequence number,IRS)是在连接建立过程中习得。
为了连接的建立完成或者初始化完成,2个TCP对端必须同步对方的初始序列号。这是在连接建立报文段的交换中完成的,段中携带了控制位“SYN”(为了同步)和初始序列号。携带SYN控制位的报文段也被简写为“SYNs"。因此,这个解决方案需要一个合适的机制来选择初始序列号,并需要一个稍微复杂的握手来交互ISN。
同步需要每边都发送它自己的初始序列号,并接收来自远程TCP对端对初始序列号的确认。每边必须也接收远程端的初始序列号,并发送一个确认。
1) A --> B SYN 我的序列号是X
2) A <-- B ACK 你的序列号是X
3) A <-- B SYN 我的序列号是Y
4) A --> B ACK 你的序列号是Y
因为步骤2和3能在一个消息中合并发送,这被称为3次握手(three-way handshake,3WHS)。
3次握手是必须的,因为在网络中,序列号没有绑定一个全局时钟,且TCP实现可能有不同的机制来选择初始序列号(ISN)。第一个SYN的接收者无法知道报文段是否是之前的段,除非它记住了连接中最后的序列号(这并不总是可能的),所以它必须问发送方去验证这个SYN。3次握手和ISN选择的时钟驱动设计在[69]中进行讨论。
3.4.2. Knowing When to Keep Quiet 知道何时保持平静
存在一个理论上的问题,数据可能被破坏,因为在主机重启后,如果相同端口和序列空间被重用了,网络中的旧报文段和新报文段会被混淆。下面讨论的“平静时间”的概念解决了这个问题,讨论中包含了它可能相关的情况,尽管在当前大都数的实现中认为不是必要的。这个问题在TCP历史的早期中更为重要。在如今的互联网实际使用中,出错情况不大可能发生,可以安全地忽略它。现在忽略它的原因包含:(a) 初始发送序列号(ISS)和临时端口随机化已经减少了重启后端口、序列号重用的可能性,(b) 随着网速变快,互联网实际的最长报文段寿命(MSL)已经降低了, 并且 © 无论如何,重启的耗时一般都比一个最长报文段寿命(MSL)更长。
为了确保TCP实现中的段序列号不会和仍残留在网络中的旧报文段中的重复,TCP端点必须在分配任何序列号之前,保持一个MSL的平静,分配序列号可能发生在启动时或者从内存的在用序列号丢失处恢复时。对于本规范MSL取值2分钟。这是一个工程选择,如果经验预示它应该修改那么可以修改。注意,如果一个TCP端点在某种意义上被重新初始化了,仍然保留着它使用中的序列号内存,那么它完全不需要等待;它必须且只需确保使用比最近使用的更大的序列号。
3.4.3. The TCP Quiet Time Concept TCP平静时间概念
对于在活跃(即未关闭)连接上丢失了最后序列号的确认的任何原因,主机都应该在其所在的网络系统中延迟发送任何TCP段,延迟至少商议的MSL时间。在下面的段落中,给出了这个规范的解释。TCP实现可能违反“平静时间”的限制,但是,只有导致一些旧数据被作为新数据接收时,或者新数据被网络系统的接收者作为旧重复数据拒绝时才有风险。
TCP端点每次生成报文段,会消耗序列号空间,并进入了源主机的网络的输出队列。TCP的重复检测和排序算法依赖于段数据和序列空间的唯一绑定,在段数据绑定已经发送和被接收者确认的序列号、以及所有段的重复副本在网络中“耗尽”之前,序列号不会遍历完232值进入循环。如果没有这样的假设,两个不同的TCP段可能被分配一样或者重叠的序列号,导致接收者混淆哪个数据是新的、哪个是旧的。记住,有多少含数据、SYN或者FIN标志位的段,每个段就绑定多少连续的序列号。
正常情况下,TCP实现会保持对下一个要发送的序列号和最早的等待中的确认的追踪,以避免在序列号的初次使用被确认前错误地重用序列号。单凭借这不足以保证旧的重复数据在网络中耗尽,所以序列空间被做得很大,以减少游荡中的重复数据抵达时造成麻烦的可能性。在2Mbit/秒时,花费4.5小时用尽232的序列空间。因为网络中的最长报文段寿命(Maximum Segment Lifetime)不大可能超过几十秒,这被认为是对可预见网络的充足保护,即使数据速率扩大到10Mbit/秒。在100Mbit/秒时,循环时间时5.4分钟,可能有点短但是仍然在合理范围内。如今更高得多的数据速率是可能的,影响在这一节的最后一段中描述。
可是,如果源TCP端没有给定连接上最后使用的序列号的任何内存,那么TCP的基本的重复检测和排序算法可能失败。例如,如果TCP实现都以序列号0开始所有连接,那么在主机重启时,TCP对端可能重组一个早期的连接(可能在半连接解析后),并以一样的序列号或者于网络中包重叠的序列号发送数据包,这些序列号来自之前的同一连接化身。在特定连接上,如果不知道它使用的序列号,那么TCP规范推荐源主机在发送该连接的报文段前先延迟MSL秒,来让之前连接化身中的端有时间从网络中耗尽。
即使主机能够记住当日的时间并用它选择初始序列号,也不能完全避免这个问题(即,即使每个新的连接化身都使用当日的时间来选择初始序列号)。
例如,假设有个连接以序列号S开始。假设这个连接并不频繁使用,最终,初始序列号生成函数(ISN(t))得到一个相等的值,称为S1,它是这个TCP端在特定连接上发送的最后的报文段。现在假设,此刻,主机重启了并建立了一个新的连接化身。初始序列号的选择是S1 = ISN(t) – 之前的连接化身的最后使用的序列号!如果恢复发生得足够快,在S1附近的网络中承载着任何之前的重复序列号可能到达,并被新连接化身的接收者作为新的包。
这个问题是指,恢复的主机可能不知道重启时下线了多久,也不知道系统中是否有来自之前连接化身的重复序列号。
处理这个问题的一个方式是,重启后恢复时故意延迟MSL秒再发送报文段–这是“平静时间”的规范。倾向于避免等待并愿意冒给定目的下新老包可能混淆的风险的主机,可以选择不等待“平静时间”。实现者可以提供给TCP用户,重启后是否选择逐个连接等待的能力,或者可以非正式地为所有连接实现“平静时间”。显然,即使用户选择了等待,主机重启至少MSL秒后也没有必要等待。
总之:每个发送的段都在序列空间中占用了一个或者更多的序列号,被段占用的号码在过了MSL秒之前,都是“忙碌的”或者“在使用”。重启后,一块序列空间被任何可能仍然在途的段的字节数据、SYN或者FIN标志占用了。如果一个新的连接启动太快,并使用了那些之前连接化身中可能仍然在途的段的空间足迹中的序列号,那么有可能序列号重叠导致接收者混淆。
高性能场景会有更短的循环时间,相比上述考虑描述的基础TCP设计中的兆比特每秒。在1Gbps时,循环时间时34秒,10Gbps时只有3秒,100Gbps时大约1/3秒。在这些高性能场景,TCP的Timestamp选项和回绕序列号保护(Protection Against Wrapped Sequences,PAWS)[47]提供了检测、丢弃旧的重复段所需要的能力。
3.5. Establishing a Connection 建立连接
“3次握手”是是用于建立连接的步骤。这个步骤通常由一个TCP端发起,被另一个TCP端响应。如果2个TCP对端同时打开并发起3次握手,这个步骤也生效,在发送SYN段后,每个TCP对端会收到一个无确认号的SYN段。当然,之前的重复SYN段的到来也可能让接收者认为有同时发起的连接在进行中。正确的使用“reset”段能够消除这些场景的歧义。
以下是一些连接初始化的示例。尽管这些示例没有展示携带数据报文段的连接同步,这也是完全合理的,只要接收中的TCP端在确认数据有效前不把数据上送给用户(例如,考虑到3次握手减少了错误连接的可能性,数据会留在接收者的缓冲区内,直到连接达到了已连接ESTABLISHED状态)。这是内存和消息之间的权衡,以提供信息给检查。
最简单的3次握手(3WHS)在图6(Figure 6)中展示。此图应按以下方式来解释。每一行被编号了以供参考。右箭头(–>)代表了发送从对端A到对端B的TCP段或者从A出发的段到达了B。左箭头 (<–) 代表了相反的情况。省略号 (…) 代表了段仍然在网络中(延迟了)。注释在括号内。TCP连接状态代表了段发送或到达后的状态(段的内容展示在每行的中间)。段内容以缩略形式展示,带有序列号、控制位和确认号字段。为了清楚起见,其他字段例如窗口、地址、长度和文本被省略了。
TCP Peer A TCP Peer B
1. CLOSED LISTEN
2. SYN-SENT --> <SEQ=100><CTL=SYN> --> SYN-RECEIVED
3. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
4. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED
5. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED
Figure 6: 基本的连接同步的3次握手 Basic Three-Way Handshake for Connection Synchronization
在图6(Figure 6)的第2行,TCP端A首先发送了一个SYN段,代表它将以序列号100开始使用序列号。在第3行,TCP端B发送了一个SYN段,并确认了从TCP端A收到的SYN。注意,确认号字段代表了TCP端B现在期望下次收到序列号101,并确认了占用序列号100的SYN段。
在第4行,TCP端A响应了一个包含对TCP端B的SYN的ACK确认的空报文段;在第5行,TCP端A发送了一些数据。注意,第5行的段的序列号和第4行的是一样的,因为ACK不占用序列号空间(如果它占用了,我们将结束确认中ACK!)。
同时启动只是稍微复杂点,如图7(Figure 7)所示。每个TCP端的连接状态都循环从CLOSED到SYN-SENT,再到SYN-RECEIVED,再到ESTABLISHED。
TCP Peer A TCP Peer B
1. CLOSED CLOSED
2. SYN-SENT --> <SEQ=100><CTL=SYN> ...
3. SYN-RECEIVED <-- <SEQ=300><CTL=SYN> <-- SYN-SENT
4. ... <SEQ=100><CTL=SYN> --> SYN-RECEIVED
5. SYN-RECEIVED --> <SEQ=100><ACK=301><CTL=SYN,ACK> ...
6. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
7. ... <SEQ=100><ACK=301><CTL=SYN,ACK> --> ESTABLISHED
Figure 7: 同时连接的同步 Simultaneous Connection Synchronization
TCP实现必须支持同时打开的尝试(必须-10)。
注意,TCP实现必须保持跟踪连接是否已经达到SYN-RECEIVED状态,它是被动打开或者主动打开导致的(必须-11)。
3次握手的主要原因是为了防止旧的重复连接启动造成混淆。为了处理这个问题,指定了一个特殊的控制消息reset。如果TCP接收端处于非同步完成状态(即,SYN-SENT, SYN-RECEIVED),它在收到可接受的reset后会返回到LISTEN状态。如果TCP端处于同步完成状态(ESTABLISHED, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT),它会中止连接并通知其用户。我们将在下面的“半开“连接中讨论后一种情况。
TCP Peer A TCP Peer B
1. CLOSED LISTEN
2. SYN-SENT --> <SEQ=100><CTL=SYN> ...
3. (duplicate) ... <SEQ=90><CTL=SYN> --> SYN-RECEIVED
4. SYN-SENT <-- <SEQ=300><ACK=91><CTL=SYN,ACK> <-- SYN-RECEIVED
5. SYN-SENT --> <SEQ=91><CTL=RST> --> LISTEN
6. ... <SEQ=100><CTL=SYN> --> SYN-RECEIVED
7. ESTABLISHED <-- <SEQ=400><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
8. ESTABLISHED --> <SEQ=101><ACK=401><CTL=ACK> --> ESTABLISHED
Figure 8: 从旧的重复SYN恢复 Recovery from Old Duplicate SYN
作为一个从旧的重复段恢复的简单示例,参考图8(Figure 8)。在第3行,一个旧的重复SYN到达了TCP端B。TCP端B不知道这是一个旧的重复段,所以它正常地响应了(第4行)。TCP端A检测到ACK字段不正确,于是返回了一个RST(重置 reset),其选择了ACK值作为序列号(SEQ)字段来让这个段可信。TCP端B,在收到RST时,回退到了LISTEN状态。当第6行的原始SYN最终到达时,同步正常进行。如果第6行的SYN在RST之前已经到达,那么可能发生更复杂的交互,两边都发送RST。
3.5.1. Half-Open Connections and Other Anomalies 半开连接和其他异常
如果TCP对端的其中一端在对方不知情下关闭或者中止了连接,或者连接的两端由于故障或者重启导致了内存丢失都已经不同步了,则已建立的连接会被称为“半开”。如果在任何一侧尝试发送数据,这种连接会自动重置。但是,半开连接预计是不寻常的。
如果站点A的连接不存在了,然后站点B有用户尝试发送数据会导致站点B的TCP端收到重置(reset)控制消息。这样的消息对站点B的TCP端指示出现了错误,预计会中止连接。
假设,两个用户进程A和B正在互相通信,而故障或者重启发生导致A的TCP实现内存丢失了。根据支持A的TCP实现的操作系统,有可能存在错误恢复机制。当TCP端再次启动时,A可能从头再次开始或者从一个恢复点开始。因此,A可能尝试再次打开(OPEN)连接,或者尝试在它认为已打开的连接上发送(SEND)。后者的情况,它会从本地(A)的TCP实现收到错误消息“连接未打开”。尝试建立连接的情况时,A的TCP实现会发送一个包含SYN的报文段。这种场景导致图9(Figure 9)中的示例。在TCP端A重启后,用户尝试重新打开连接。TCP端B,与此同时,认为连接是已打开的。
TCP Peer A TCP Peer B
1. (REBOOT) (send 300,receive 100)
2. CLOSED ESTABLISHED
3. SYN-SENT --> <SEQ=400><CTL=SYN> --> (??)
4. (!!) <-- <SEQ=300><ACK=100><CTL=ACK> <-- ESTABLISHED
5. SYN-SENT --> <SEQ=100><CTL=RST> --> (Abort!!)
6. SYN-SENT CLOSED
7. SYN-SENT --> <SEQ=400><CTL=SYN> -->
Figure 9: 半开连接发现 Half-Open Connection Discovery
当第3行的SYN到达时,TCP端B正处于已同步的状态,传入的段不在窗口范围中,于是响应了一个代表它接下来期望接收到的序列号(ACK 100)的确认。TCP端A看到了报文段没有确认它发送的任何内容,是未同步的状态,发送一个重置(RST),因为它检测到了半开连接。第5行TCP端B中止了连接。TCP端A会继续尝试建立连接。问题现在简化为图6(Figure 6)中基本的3次握手了。
当TCP端A重启且TCP端B试着在它认为已同步的连接上发送数据时,会出现一个有趣的其他情况。这在图10(Figure 10)中说明。这种情况下,从TCP端B发送到TCP端A(第2行)的数据是不可接受的,因为没有这个连接存在,所以TCP端A发送了RST(重置)。这个RST是可接受的,所以TCP端B处理并中止了连接。
TCP Peer A TCP Peer B
1. (REBOOT) (send 300,receive 100)
2. (??) <-- <SEQ=300><ACK=100><DATA=10><CTL=ACK> <-- ESTABLISHED
3. --> <SEQ=100><CTL=RST> --> (ABORT!!)
Figure 10: 活动端导致半开连接发现 Active Side Causes Half-Open Connection Discovery
在图11(Figure 11)中,描述了两个TCP端A和B,它们存在正在等待SYN的被动连接。一个旧的重复段到达了TCP端B(第2行)促使B采取行动。一个SYN-ACK被返回了(第3行),导致TCP端A生成了一个RST(第3行的ACK不可接受)。TCP端B接受了这个重置段并返回到了它的被动LISTEN状态。
TCP Peer A TCP Peer B
1. LISTEN LISTEN
2. ... <SEQ=Z><CTL=SYN> --> SYN-RECEIVED
3. (??) <-- <SEQ=X><ACK=Z+1><CTL=SYN,ACK> <-- SYN-RECEIVED
4. --> <SEQ=Z+1><CTL=RST> --> (return to LISTEN!)
5. LISTEN LISTEN
Figure 11: Old Duplicate SYN Initiates a Reset on Two Passive Sockets
各种各样的其他情况也是可能的,所有的都由以下的RST生成处理规则来说明。
3.5.2. Reset Generation 重置生成
TCP用户或者应用可以在任何时候在连接上发送重置(reset),尽管当各种错误情况发生时,重置事件也会由协议本身生成,如下所述。连接中发送重置的那一端应该进入TIME-WAIT状态,这一般有助于减少繁忙服务器的负载,原因在 [70]。
一般来说,每当一个明显不是用于当前连接的报文段到达,重置(RST)就会发送。如果不清楚是这种情况,重置不得发送。
有三组状态:
-
如果连接不存在(CLOSED),那么响应任何传入的除了重置以外报文段,都会发送重置。通过这种方式不匹配已有连接的SYN段会被拒绝。
如果传入的报文段设置了ACK控制位,重置会采用这个段的ACK(确认号)字段作为序列号;否则,重置的序列号为0,且ACK(确认号)字段设置为序列号与传入段长度之和。连接仍处于CLOSED状态。 -
如果连接处于任何非已同步状态(LISTEN, SYN-SENT, SYN-RECEIVED),并且传入的段确认的是尚未发送的(这个段携带了一个不可接受的ACK确认号),或者传入的段有不能完全符合连接请求中的的相应安全等级或分区(附录 A.1),那么重置会被发送。
如果传入的报文段有ACK确认号字段,重置会采用这个段的ACK(确认号)字段作为序列号;否则,重置的序列号为0,且ACK(确认号)字段设置为序列号与传入段长度之和。连接状态不变。 -
如果连接处于已同步状态(ESTABLISHED, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT),任何不可接受的段(超出窗口的序列号或者不可接受的确认号)必须被响应一个空的确认段(没有任何用户数据),段包含了当前发送序列号和代表期望接下来接收到的序列号的确认号,并且连接状态不变。
如果传入段有不能完全符合连接请求中的的相应安全等级或分区,重置就会发送,连接变成CLOSED状态。重置把传入段的ACK字段作为它的序列号。
3.5.3. Reset Processing 重置处理
在除了SYN-SENT以外的所有的状态中,所有的重置(RST)报文段都是通过校验SEQ字段来验证的。如果序列号在窗口中则重置段时有效的。在SYN-SENT状态(收到响应初始的SYN的RST)时,如果ACK字段确认了SYN,则RST是可接受的。
RST的接收方首先做校验,然后改变状态。如果接收方在LISTEN状态,无视RST。如果接收方在SYN-RECEIVED状态,并之前在LISTEN状态,那么接收方回退到LISTEN状态;否则,接收方中止连接并迁移到CLOSED状态。如果接收方在任何其他状态,中止连接并通知用户,迁移到CLOSED状态。
TCP实现应该允许接收到的RST段包含数据(应该-2)。有人建议RST段可以包含诊断数据来解释RST的导致原因。目前尚未建立这种数据的标准。
3.6. Closing a Connection 关闭连接
CLOSE是一个操作,意味着“我没有更多数据要发送了”。当然,关闭全双工连接的概念可能有不明确的解释,因为它在如何处理连接的接收方上可能不明显。我们选择以一个简单的方式处理关闭。发起关闭的用户,在TCP接收方告诉远程对端也已经关闭之前,可以继续接收。因此,一个程序可以发送几个数据后再接着关闭,然后继续接收,直到收到接收失败的信号,因为远程对端已经关闭。即使没有接收方是未完成的,TCP实现也会发远程对端已经关闭的信号给用户,这样用户可以优雅地中止连接。TCP实现,会可靠地交付所有连接已关闭之前发送的缓冲数据,这样期望没有数据返回的用户只需要等待监听连接关闭成功,就知道所有的数据在目标TCP端已经被接收了。为了发送的数据,用户必须保持读取他们发起关闭的连接,直到TCP实现指示没有更多数据了。
基本上有3种场景:
- 用户通过告诉TCP实现关闭连接来发起关闭(图12的TCP端A)。
- 远程TCP端通过发送FIN控制信号来发起关闭(图12的TCP端B)。
- 两边用户同时发起关闭(图13)。
-
场景 1:
本地用户发起关闭
在这个场景中,FIN段会被构造并放置到发出的段队列中。TCP实现不再接受来自用户的SEND,并进入了FIN-WAIT-1状态。此状态下接收仍然是允许的。所有FIN之前的和包含FIN的报文段会被重新传输直到被确认。当另一边的TCP端确认了FIN并发送了它自己的FIN,第一个TCP端就能发送ACK确认这个FIN。注意接收FIN的TCP端会发送ACK确认,但是在用户也关闭连接前不会发送它自己的FIN。 -
场景 2:
TCP端从网络上接收到FIN
如果从网络上接收到未经请求的FIN,TCP接收方会发送ACK确认它,并告诉用户连接正在关闭。用户会响应CLOSE,即在发送任何残留的数据后TCP端发送FIN给对端。TCP端然后等待直到它自己的FIN被确认了,于是它删除此连接。如果ACK段没有来临,则在用户超时后中止连接并通知用户。 -
场景 3:
两边用户同时发起关闭
连接两边的用户同时发起关闭会导致FIN段被交换(图 13)。当所有FIN之前的报文段被处理并确认后,每个TCP对端会发送ACK确认它收到的FIN。在收到这些ACK后,两边都会删除连接。
TCP Peer A TCP Peer B
1. ESTABLISHED ESTABLISHED
2. (Close)
FIN-WAIT-1 --> <SEQ=100><ACK=300><CTL=FIN,ACK> --> CLOSE-WAIT
3. FIN-WAIT-2 <-- <SEQ=300><ACK=101><CTL=ACK> <-- CLOSE-WAIT
4. (Close)
TIME-WAIT <-- <SEQ=300><ACK=101><CTL=FIN,ACK> <-- LAST-ACK
5. TIME-WAIT --> <SEQ=101><ACK=301><CTL=ACK> --> CLOSED
6. (2 MSL)
CLOSED
Figure 12: 正常的关闭顺序 Normal Close Sequence
TCP Peer A TCP Peer B
1. ESTABLISHED ESTABLISHED
2. (Close) (Close)
FIN-WAIT-1 --> <SEQ=100><ACK=300><CTL=FIN,ACK> ... FIN-WAIT-1
<-- <SEQ=300><ACK=100><CTL=FIN,ACK> <--
... <SEQ=100><ACK=300><CTL=FIN,ACK> -->
3. CLOSING --> <SEQ=101><ACK=301><CTL=ACK> ... CLOSING
<-- <SEQ=301><ACK=101><CTL=ACK> <--
... <SEQ=101><ACK=301><CTL=ACK> -->
4. TIME-WAIT TIME-WAIT
(2 MSL) (2 MSL)
CLOSED CLOSED
Figure 13: 同时关闭的顺序 Simultaneous Close Sequence
TCP连接可以有2种方式终止:(1) 使用FIN握手(图 12)的正常TCP关闭顺序, (2) 发送一个或多个RST段并立即丢弃连接状态的“中止”。如果本地TCP连接由于接收到远程端的FIN或者RST被远程端关闭了,那么本地应用必须被通知是否是正常关闭还是中止(必须-12).
3.6.1. Half-Closed Connections 半关闭连接
正常的TCP关闭顺序在两个方向上都可靠地交付缓冲数据。因为TCP的两个方向是各自独立关闭的,对一个连接来说,有可能是“半关闭”,例如,只在一个方向上关闭了,主机允许在关闭的连接上向开放的方向继续发送数据。
主机可以实现“半双工”的TCP关闭顺序,这样已经调用关闭(CLOSE)的应用就不能继续从连接上读取数据(可以-1)。如果当TCP连接中接收到的数据仍然未处理完时,这样的主机发起了关闭的调用,或者,如果在关闭被调用后新数据被接收到了,它的TCP实现应该发送一个RST来表示数据丢失了(应该-3)。讨论见[23],第2.17节。
当连接被主动关闭了,它必须停留在TIME-WAIT状态2*MSL(最长报文段寿命)的时间(必须-13)。但是,如果满足以下条件时,它可以接收来自远程TCP端的新的SYN,从TIME-WAIT状态直接重新打开连接(可以-2):
(1) 为新连接分配它的初始序列号,初始序列号要比它之前的连接化身中使用的最大序列号更大,以及
(2) 如果SYN被证明是旧的重复段,则退回到TIME-WAIT状态。当TCP的Timestamp选项是有效的, [40] 中描述了一个改进算法,可以支持更高的连接建立速率。这个减少TIME-WAIT的算法是应该被实现的最佳当前实践,因为Timestamp选项是常用的,并且使用它们减少TIME-WAIT对繁忙的互联网服务器是有益的(应该-4)。
3.7. Segmentation 分段
术语“分段”指的是,TCP从发送中的应用收到字节流并打包它们到TCP段时,执行的活动。单个TCP段通常不会和应用对发送(或者套接字写入)的单次调用一一对应。应用可以在上层协议中以消息的粒度执行写,但是TCP不保证,发送、收到的TCP段边界和用户应用数据的读写缓冲边界的相关性。在一些特定协议中,例如使用直接数据放置(Direct Data Placement,DDP)和标记PDU对齐帧(Marker PDU Aligned Framing,MPA)的远程直接内存访问(Remote Direct Memory Access,RDMA)[34],当TCP段和应用数据单元的关系能被控制时,有可能做性能的优化,MPA包含了检测、验证TCP段与应用消息数据结构的关系的特定机制,但是这是像RDMA这种应用特有的。一般来说,有多个目的会影响TCP实现创建的TCP段的大小。
驱动发送更大的段的目的包括:
- 减少网络中在途包的数量。
- 通过更少的打断和层间交互,来提高处理效率和潜在的性能。
- 限制TCP头部的开销。
注意发送更大段的性能收益会随着大小增长而减低,可能有优势被反转的边界。例如,在一些实现架构中那个,1025字节的段可能导致比1024字节更糟的性能,纯粹因为复制操作的数据对齐。
驱动发送更小的段的目的包括:
-
避免发送的TCP段过大导致IP网络路径上的IP数据报大小超过最小的MTU,因为这会引起包丢失或者包分片。更糟糕的是,一些防火墙或者中间件可能丢弃有关分片的分片包或ICMP消息。
-
防止应用数据流的延迟,特别当TCP在等待应用生成更多数据,或者当应用在等待来自对端的时间或输入以生成更多数据。
-
能够让TCP段和更下层的数据单元“命运共享”(例如,IP层以下,对于比IP的MTU更小的链路单元或帧)。
为了满足这些互相竞争的目的,TCP包含了多个机制,包括最大分段大小(Maximum Segment Size)选项,路径MTU发现(Path MTU Discovery),Nagle算法,和对IPv6 Jumbograms的支持,如下面的章节所述。
3.7.1. Maximum Segment Size Option 最大分段大小选项
TCP端必须实现发送和接收MSS选项(必须-14)。
当TCP的接收MSS有别于IPv4默认的536或者IPv6默认的1220时,TCP实现应该在每一个SYN段中发送一个MSS选项(应该-5),也可以总是发送它(可以-3)。
如果MSS选项没有在连接建立时接收到,TCP实现必须假设一个默认的发送MSS,IPv4是536(576 - 40),IPv6是1220(1280 - 60)(必须-15)。
TCP端实际发送的段的最大大小,“有效发送MSS”,必须在以下的2个中取较小值,发送MSS(反映了远程主机的可用重组缓冲区大小,即EMTU_R [19])和IP层允许的最大传输大小(EMTU_S [19]):
Eff.snd.MSS = min(SendMSS+20, MMS_S) - TCPhdrsize - IPoptionsize
解释:
- SendMSS是从远程主机接收到的MSS值,或者如果没有接收到MSS选项时,是默认的IPv4的536或者IPv6的1220。
- MSS_S是TCP可以发送的传输层消息的最大大小。
- TCPhdrsize是TCP固定头部和任何选项的大小。在没有选项的(极少数)场景时是20,但是如果TCP选项被发送了,可能更大。注意一些选项可能不是所有段都包含的,但是对每一个发送的段,发送方应该在Eff.snd.MSS(译者:Eff是指Effective Window Size,有效窗口大小)中相应地调整数据长度。
- IPoptionsize是与TCP连接关联的任何IPv4选项或者IPv6扩展报头的大小。注意,一些选项或者扩展报头可能不是所有包都包含的,但是对每一个发送的段,发送方应该在Eff.snd.MSS(译者:Eff是指Effective Window Size,有效窗口大小)中相应地调整数据长度。
MSS选项中发送的MSS值应该等同于有效MTU减去固定的IP和TCP头。当计算MSS选项的值时忽略IP和TCP的选项,如果包里有任何IP或者TCP选项被发送,那么发送方必须相应地减少TCP数据的大小。RFC6691 [43] 对此进行了更详细的讨论。
MSS选项中发送的MSS的值必须小于或等于:
MMS_R - 20
MMS_R是能被接收到(并在IP层重组)的传输层消息的最大大小。TCP从IP层获取MMS_R和MMS_S;参见RFC1122的第3.4节的通用调用GET_MAXSIZES。这些是根据它们的IP MTU的等效值定义的,EMTU_R and EMTU_S [19]。
当TCP在IP或者TCP头非固定大小情况下被使用时,发送方必须根据IP和TCP选项使用的字节数,在任何包中都减少TCP数据的数量。这已经是历史上的一个混淆点了,如RFC6691的第3.1节的解释。
3.7.2. Path MTU Discovery 路径MTU发现
TCP实现可能意识到了直接连接链路的MTU,但是很少了解整个网络路径的MTU。对IPv4,RFC1122推荐对非直接连接目的地使用小于等于576的IP层默认的有效MTU,而IPv6时将是1280。使用这些固定值会限制TCP连接性能和效率。相反,为了TCP改进分段决策,强烈推荐实现路径MTU发现(Path MTU Discovery ,PMTUD)和分组层路径MTU发现(Packetization Layer Path MTU Discovery ,PLPMTUD)。PMTUD和PLPMTUD都能帮助TCP选择段大小,以避免路径分片(对IPv4)和源分片(IPv4和IPv6)。
IPv4 [2]或者IPv6 [14]的PMTUD是在TCP、IP和ICMP间结合实现的。它依赖避免源分片和设置IPv4的DF (don’t fragment)标志位,后者会抑制路径上的分片。每当段太大不能穿过链路时,它依赖来自路径上路由器的ICMP报错。对关于PMTUD的TCP实现的一些调整,在RFC2923中介绍,以处理实践中经历的问题 [27]。PLPMTUD[31]是PMTUD的标准跟踪改进,放宽了跨路径的ICMP支持的要求,提升了ICMP不能保持传输场景下的性能,但是仍然尝试避免源分片。在所有四个RFC中的这个机制推荐包含在TCP实现中。
TCP的MSS选项声明了可接收包的大小的上限(见[43])。因此,在MSS选项中设置该值可以影响PMTUD或PLPMTUD的找到更大路径MTU的能力。RFC1191讨论了许多旧的TCP实现的可能的影响,这些旧的TCP实现对非本地目的地设置了MSS为536(对应了IPv4的默认MTU的576字节),而不是如推荐那样从连接接口的MTU处获得。
3.7.3. Interfaces with Variable MTU Values 可变MTU值的接口
当和可变压缩一起使用时,有效MTU有时会变,例如,鲁棒性头部压缩(RObust Header Compression,ROHC) [37]。它鼓动TCP实现公告最大可能MSS,以支持压缩载荷的最有效使用。不幸的是,一些压缩方案偶尔需要传输完整的头(因此更小的载荷)来重新同步在它们的端压缩器/解压缩器的状态。如果最大MTU被用来计算MSS选项中的公告的值,TCP重传可能干扰压缩器的重新同步。
因此,当接口的有效MTU因包而异产生变化时,TCP实现应该使用最小的接口有效MTU来计算公告在MSS选项中的值(应该-6)。
3.7.4. Nagle Algorithm Nagel算法
“Nagle算法”的描述参见RFC896 [17],它在RFC1122[19]中推荐使用,以缓解早期太多小数据包生成的问题。它被大多数现在的TCP代码底层实现了,有时有微小的变化(参见目录A.3)。
如果有未确认的数据(即,SND.NXT > SND.UNA),那么TCP发送端会缓冲所有用户的数据(不管PSH位),直到未确认的数据被确认了,或者,直到TCP端能够发送满大小的段(Eff.snd.MSS个字节)。
TCP实现应该实现Nagle算法来合并短的段(应该-7)。但是,应用必须有方式来在单独连接上禁用Nagle算法(必须-17)。在所有情况下,发送数据也受制于慢启动算法[8]的限制。
由于Nagle算法和延迟的确认间可能有疑难的交互,因此一些实现使用了Nagle算法的微笑变体,例如在附录A.3中描述的。
3.7.5. IPv6 Jumbograms IPv6超大分组
为了支持基于IPv6超大分组(Jumbograms)的TCP,其实现需要能够发送比MSS选项能够传输的64KB限制更大的TCP段。RFC2675 [24] 定义了,65535字节大小的MSS值应被视为无限大,且路径MTU发现[14]被用于确定实际的MSS。
不支持连接到大于65575 [24]的MTU的链路的IPv6节点,其不需要实现或者理解超大载荷(Jumbo Payload)选项,目前的IPv6节点的要求也并不包含对超大分组(Jumbograms )的支持[55]。
3.8. Data Communication 数据通信
一旦连接建立完成,数据通过段的交互来通信。因为段可能由于错误(校验和checksum测试失败)或者网络拥塞丢失,TCp使用了重传来确保每个段的传递。由于网络或者TCP重传,可能有重复的段到达。正如(第3.4节)序列号一节所述,TCP实现对段中的序列号和确认号执行了特定的测试以验证它们的可接受性。
数据的发送方保持对下一个序列号的跟踪,以在变量SND.NXT中使用。数据的接收方保持对变量RCV.NXT中的下一个在期望序列号的跟踪。数据的发送方保持对变量SND.UNA中的最早未确认序列号的的跟踪。如果数据流暂时空闲且所有发送的数据都已被确认,那么这三个变量将相等。
当发送方创建了一个段并传输它,发送方会增大SND.NXT。当接收方接受了一个段,它会增大RCV.NXT并发送一个确认。当数据发送方收到了确认,它会增大SND.UNA。这些变量差异的程度时连接延迟的衡量标准。变量增大的值是段中的数据和SYN或FIN标志的长度。注意,一旦处于ESTABLISHED状态,所有的段都必须携带当前的确认信息。
CLOSE用户调用意味着推送功能(见第3.9.1节),传入段中的FIN控制位也是。
3.8.1. Retransmission Timeout 重传超时
由于构成互联网系统的网络的可变性和TCP连接的广泛使用,必须动态确定重传超时(RTO)。
RTO必须通过[10]中的算法来计算,包括Karn的RTT采样算法(必须-18)。
RFC793包含了一个计算RTO的早期示例步骤,它基于IEN 177[71]中所提的工作。然后被RFC1122中描述的算法所替代,随后在RFC2988中更新,在RFC6298中再次更新。
RFC1122允许,如果一个重传包等同于原始的包(这意味着不仅数据边界没有变化,而且头部也没有变化),那么可以使用同样的IPv4标识(Identification )字段(参见RFC1122的第3.2.1.5节)(可以-4)。无论怎样同样的IP标识(Identification )字段可以被重用,因为它只有当数据报分片时 [44]才是有意义的。TCP实现不应该依赖于这个字段,也不应以任何方式和这个IPv4头部字段交互。它不是一个合理的方式来表示重复的已发送段或者识别重复的已收到的段。
3.8.2. TCP Congestion Control TCP拥塞控制
RFC2914 [5] 解释了拥塞控制对互联网的重要性。
RFC1122要求实现Van Jacobson的拥塞控制算法,包括慢启动、拥塞避免以及对同一个段的连续RTO值的指数退避。RFC2581提供了对慢启动和拥塞避免的IETF标准跟踪的描述,以及快重传和快恢复。RFC5681时这些算法的当前描述,也是为TCP拥塞控制提供了准则的当前的标准跟踪规范。RFC6298描述了RTO值的指数退避,包括保持退避值,直到之后的新数据的段在无重传下被发送且确认了。
TCP端必须实现基本的拥塞控制算法,慢启动、拥塞避免和RTO的指数退避,以避免造成拥塞崩溃条件(必须-19)。RFC5681和RFC6298描述了在IETF标准跟踪上的被广泛使用的基本算法。存在多个其他合适的算法,并被广泛使用了。许多TCP实现支持一组替代算法,并可以为了使用在端上配置。如果算法符合RFC2914、 RFC5033 [7]、 和RFC8961 [15] (可以-18)这些IETF标准跟踪中的TCP规范的要求,一个端可以实现这样的替代算法。
在RFC3168中描述了显式拥塞通知(ECN),它是IETF标准跟踪的增强,有许多益处[51]。
TCP端应该实现RFC3168中描述的ECN(应该-8)。
3.8.3. TCP Connection Failures TCP连接失败
TCP端对同一个段的过度重传预示了远程主机或者网络路径存在故障。故障可能持续很短或者很长。必须使用以下步骤来处理数据段的过度重传(必须-20):
- ( a ) 有两个阈值R1和R2来衡量同一个段已重传量。R1和R2可以是时间单位来衡量或者以重传次数衡量(如果需要,带上当前的RTO和相应的退避作为转换因子)。
- ( b ) 当同一个段的传输次数达到或者超过阈值R1,推送负面建议(参见[19]的第3.3.1.4节)给IP层,来触发网关失效诊断。
- ( c ) 当同一个段的传输次数超过R1达到阈值R2,关闭连接。
- ( d ) 应用必须(必须-21)能够对特定连接设置R2的值。例如,人机交互应用可能设置R2为”无限大“,由用户控制何时断开连接。
- ( e ) 当达到R1未到R2时,TCP实现应该(应该-9)通知应用有传输问题(除非这个通知消息被应用禁用了;参见"异步报告"部分(第3.9.1.8节))。例如,这会允许远程登陆应用程序通知用户。
在当前的RTO下,R1的值应该至少对应3次重传(应该-10)。R2的值应该至少对应100秒(应该-11)。
如果过度的SYN段重传,或者收到RST段或ICMP端口不可达,尝试打开TCP连接可能会失败。SYN重传必须以数据重传中描述的一般方式进行处理,包括应用层的通知。
但是,对于SYN和数据段,R1和R2的值可以是不同的。尤其,对于SYN段的R2值必须被设置得足够大,来保证至少3分钟的段重传(必须-23)。当然,应用能够更早地关闭连接(即放弃打开尝试)。
3.8.4. TCP Keep-Alives TCP保活
如果较长时间没有接收到传入的段,也没有新的或者未确认的数据发送,TCP连接被称为“空闲”。
实现方可以在TCP实现中包含“保活(keep-alives)”(可以-5),尽管这种做法没有普遍接受。但是,一些TCP实现以及包含了保活机制。为了确认空闲连接仍然有活性,这些实现会发送一个探测段以引起TCP对端的响应。这个段一般包含SEG.SEQ = SND.NXT-1,可以包含或者可以不包含垃圾数据。如果包含了保活,应用必须能够为每个TCP连接开启或者关闭保活(MUST-24),且必须默认关闭。
保活包必须仅当没有发送的数据是未完成的,且在一个间隔内没有收到连接的数据或者确认包时才发送(必须-26)。这个间隔必须可配置(必须-27),且必须默认不低于2小时(必须-28)。
极其重要的是,记住,不包含数据的ACK段,TCP不做可靠传输。因此,如果保活机制被实施了,不得把对任何特定探测的响应失败视为死连接(必须-29)。
TCP实现应该发送无数据的保活段(应该-12);但是,为了兼容错误的TCP实现,它也可以配置成发送包含一个垃圾字节的保活段(可以-6)。
3.8.5. The Communication of Urgent Information 紧急消息的通信
由于实现的差异和中间盒交互,新应用不应该使用TCP的紧急机制(应该-13)。可是,TCP实现必须仍然包含对紧急机制的支持(必须-30)。关于一些TCP实现如何解释紧急指针的信息可以参见RFC6093 [39]。
TCP紧急机制的目的是,允许发送方用户催促接收方用户接受一些紧急数据,并准许接受方的TCP端指示接收方用户何时所有的当前已知的紧急数据已经被用户收到。
这个机制允许在数据流中有个指针,指定了紧急消息的结束。每当这个指针在TCP接收端的接收序列号(RCV.NXT)之前,那么TCP实现必须高速用户进入“紧急模式”;当接收序列号赶上了紧急指针,TCP实现必须高速用户进入“正常模式”。如果紧急指针在用户处于“紧急模式”时更新了,这个更新会对用户不可见。
这个方式使用了所有传输段中都携带的紧急(urgent)字段。URG控制位预示这个紧急字段是生效的,且必须被添加到段序列号才能产生紧急指针。未设置此标志位代表了没有未完成的紧急数据。
为了发送紧急指示,用户必须也发送至少一个数据字节。如果发送方用户还指示了一个推送(push)标志,那么会增强目标进程对紧急消息交付的及时性。注意,因为在紧急指针中的变化对应了发送方应用写入的数据,紧急指针不能在序列空间中“后退”,但是TCP接收者应该对无效的紧急指针数据保持鲁棒性。
TCP实现必须支持任何长度的紧急数据的序列(必须-31)[19]。
紧急指针必须指向紧急数据后的字节序列号(必须-62)。
每当TCP实现收到紧急指针,并有之前的未决的紧急数据时,或者每当紧急指针在数据流中前进时,TCP实现必须(必须-32)异步地通知应用层。TCP实现必须(必须-33)提供为应用一个方式来获悉连接中剩余读取的紧急数据,或者至少确定是否有更多的紧急数据待读取 [19]。
3.8.6. Managing the Window 管理窗口
在每个段中发送的窗口代表了,窗口的发送方(数据接收方)当前准备接受的序列号范围。假设这与当前可用于此连接的数据缓冲区空间有关。
发送方TCP端把传输数据打包为适配当前窗口大小的段,并可能重新打包重传队列中的段。这种重新打包不是必须的,但可能有帮助。
在单向数据流的连接中,窗口信息会被携带在序列号相同的确认段中,所以如果它们无序到达了,没有方式来重新排序。这不是严重的问题,但是它会允许窗口信息偶尔临时地基于数据接收方的旧报文。避免这个问题的改进方式是按照携带了最大确认号的段(即,段的序列号等于或者大于之前收到的最大值)的窗口信息来处理。
指示一个很大的窗口会鼓励传输。如果超过能接受的更多的数据到达了,它会被抛弃。这回导致过度重传,不必要地增加了网络和TCP端的负载。指示一个小窗口可能会限制数据传输到引入每个新传输段间的往返时延的程度。
已提供的机制允许TCP端通告一个很大的窗口,随后通告一个未接受那么多数据的小得多的窗口。这个被称为“缩小窗口”,强烈不推荐。鲁棒性原则 [19] 规定TCP对等体将不会缩小它们自己的窗口,但是会为其他TCP对等体的这种行为做准备。
TCP接收方不应缩小窗口,即把右窗口边界向左移动(应该-14)。但是,发送方TCP端必须足够健壮来应对窗口缩小,这可能导致“可用窗口”(参加第3.8.6.2.1节)成为负数(必须-34)。
如果这发生了,发送方不应该发生新的数据(应该-15),但是应该正常重传SND.UNA和SND.UNA+SND.WND间(应该-16)旧的未确认的数据。发送方也可以重传SND.UNA+SND.WND外(可以-7)的旧数据,但是,如果超过右窗口边界的数据未被确认时,不应该连接超时(应该-17)。如果窗口缩小到0,TCP实现必须以标准方式探测(以下描述)(必须-35)。
3.8.6.1. Zero-Window Probing 零窗口探测
发送方TCP端必须有规律地传输至少一个字节的新数据(如果可用),或者即使发送窗口为0也重传到接收方TCP端,这是为了“探测”窗口。为了保证,当无论哪个TCP端有个0窗口时,窗口的重新打开将可靠地报告给另一端,重传是至关重要的。在其他文献中,这被称为零窗口探测(ZWP)。
必须支持(提供)0窗口探测(必须-36)。
TCP实现可以无限期地保持它提供的接收窗口为关闭(可以-8)。只要接收的TCP端继续发送响应探测段的确认,发送的TCP端旧必须允许连接保持打开(必须-37)。这使得TCP能够在例如第4.2.2.17节[19]描述的“打印机缺纸”等场景时仍然正常工作。这种行为受制于实现的资源管理问题,如[41]所述。
当接收方TCP端有0窗口并且有报文段到达了,它必须仍然发送一个确认,来告知他的下一个期望的序列号和当前窗口大小(0)。
当0窗口已经在重传超时期间(应该-29)(第3.8.1节)内存在,传输主机应该发送第一个零窗口探测,并应该指数性地提高连续探测的间隔时间(应该-30).
3.8.6.2. Silly Window Syndrome Avoidance 避免糊涂窗口综合症
“糊涂窗口综合征”(SWS)是小的递增窗口移动的一个沉重的形式,会导致极度低下的TCP性能。对发送方和接收方的避免SWS的算法在下面都有描述。RFC1122包含了更多SWS问题的进一步讨论。注意Nagle算法和发送方SWS避免算法在提升性能上扮演着互补的角色。Nagle算法在待发送数据增量很小时会劝阻发送微小的段,而SWS避免算法劝阻由右窗口边缘以小增量前进引起的小段。
3.8.6.2.1. Sender’s Algorithm – When to Send Data 发送方的算法–何时发送数据
TCP实现必须在发送方包含SWS避免算法(必须-38)。
第3.7.4节的Nagle算法另外描述了如何合并短小的段。
发送方的SWS避免算法比接收方的更困难,因为发送方不(直接)知道接收方的总缓冲空间大小(RCV.BUFF)。一个行之有效的方法是,发送方计算Max(SND.WND),它是这个连接上至今最大的发送窗口,并用这个值作为RCV.BUFF的预估值。不幸的是,这只能是预估;接收方可以在任何时候减小RCV.BUFF的大小。为了避免因此产生的死锁,有必要设置一个超时来强制数据传输,它会覆盖SWS避免算法。在实践中,这个超时应该很少发生。
“可用窗口”是:
U = SND.UNA + SND.WND - SND.NXT
即,提供的窗口大小减去已发送但未确认的数据量。如果D是在发送方TCP端排队的尚未发送的数据的数量,那么推荐以下的规则组:
Send data:
发送数据:
-
(1) 如果一个最大大小的段能被发送,即,如果:
min(D,U) >= Eff.snd.MSS; -
(2) 或者如果数据被推送并且所有排队的数据现在能被发送,即:
[SND.NXT = SND.UNA and] PUSHed and D <= U
(the bracketed condition is imposed by the Nagle algorithm);
中括号内的条件是Nagle算法强加的; -
(3) 或者如果至少最大窗口乘以小数Fs能被发送,即,如果
[SND.NXT = SND.UNA and] min(D,U) >= Fs * Max(SND.WND); -
(4) 或者如果覆盖超时发生了。
这里的Fs是推荐值为1/2的小数。覆盖超时时间应该在0.1~1秒的范围内。将此计时器与用于探测零窗口的计时器结合起来可能很方便(第3.8.6.1节)。
3.8.6.2.2. Receiver’s Algorithm – When to Send a Window Update 接收方的算法–何时发送窗口更新
TCP实现必须在接收方包含SWS避免算法(必须-39)。
接收方的SWS避免算法决定了何时右窗口边界可以被推进;这通常被称为“更新窗口”。此算法结合了延迟ACK算法(第3.8.6.3节),来决定何时包含当前窗口的ACK段会真正发送给接收方。
接收方SWS的解决方案是避免以很小的增量推进右窗口边界RCV.NXT+RCV.WND,即使从网络上收到了小段中的数据。
假设总的接收缓冲空间是RCV.BUFF。在任何给定的时刻,这个总空间中的RCV.USER可能忙于已接收并确认但是尚未被用户进程消费的数据。当连接静止时,RCV.WND = RCV.BUFF且RCV.USER = 0。
随着数据到达并被确认,保持右窗口边界固定需要接收方提供小于它的全部缓冲空间的空间,即,接收方必须声明一个RCV.WND,它在RCV.NXT增长时可以保持RCV.NXT+RCV.WND不变。因此,总缓冲空间RCV.BUFF一般被分割成3个部分:
|<------- RCV.BUFF ---------------->|
1 2 3
----|---------|------------------|------|----
RCV.NXT ^
(Fixed)
1 - RCV.USER = 已收到但是尚未消费的数据 data received but not yet consumed;
2 - RCV.WND = 通告给发送方的空间 space advertised to sender;
3 - Reduction = 可用但是尚未通告的空间 space available but not yet advertised.
推荐的接收方的SWS避免算法是保持RCV.NXT+RCV.WND固定,直到reduction满足:
RCV.BUFF - RCV.USER - RCV.WND >= min( Fr * RCV.BUFF, Eff.snd.MSS )
其中Fr是一个推荐值为1/2的小数,Eff.snd.MSS是连接的有效发送MSS(effective send MSS)(参见第3.7.1节)。当满足了不等式时,RCV.WND设置为RCV.BUFF-RCV.USER。
注意,此算法的一般效果是避免以Eff.snd.MSS(对于实际的接收缓冲: Eff.snd.MSS < RCV.BUFF/2)的增量推进RCV.WND。还要注意,接收方必须使用它自己的Eff.snd.MSS,假设它和发送方的相同。
3.8.6.3. Delayed Acknowledgments – When to Send an ACK Segment 延迟确认–何时发送ACK段
正在接收TCP数据段流的主机,能够通过对每个接收到的数据段发送少于一个的ACK确认段来提高网络和主机的效率;这被称为“延迟ACK”。
TCP端应该实现延迟ACK(应该-18),但是ACK不应过度延迟;特别是,延迟时间必须低于0.5秒(必须-40)。应该至少每秒为满大小的段或2*RMSS字节的新数据生成一个ACK(其中,RMSS是正在接收要确认的段的TCP端规定的,或者是未规定时的默认值)(应该-19)。ACK过度的延迟会扰乱往返时间和包的“时钟”算法。对于延迟ACK行为的更完整的讨论参见RFC5681[8]的第4.2节,包含了立即确认乱序段、序列空间内间隙上方的段或填充全部或部分间隙的段,以加速损失恢复的建议。
注意,有一些目前的做法会进一步减少ACK的数量,包括了generic receive offload (GRO) [72],ACK compression和ACK decimation[28]。
3.9. Interfaces 接口
自然而然有2个要关注的接口:用户/TCP接口和TCP/更低层级接口。我们有一个相当复杂的用户/TCP接口模型,但是对更低层级协议模块的接口留着并未在此规定,因为它会在更低层级的协议规范中详细规定。对于更低层级时IP的场景,我们要注意TCP实现可能用到的一些参数值。
3.9.1. User/TCP Interface 用户/TCP接口
以下对于TCP实现的用户命令的功能性描述,充其量只能算作是虚构的,因为每个操作系统会有不同的特色。因此,我们必须警告读者,不同的TCP实现可能有不同的用户接口。但是,所有的TCP实现必须提供一组特定的最小服务集,来保证所有的TCP实现都能支持相同的协议层级。本节规定了所有TCP实现都要求的功能接口。
[53] 的第3.1节还确认了TCP提供的原语,它可以用作实现者的额外参考。
以下章节功能上描述了用户/TCP接口的特征。使用的符合类似于高级语言中的大多数的程序代码或者函数调用,但是这种使用并不意外着排除了陷阱类型的服务调用。
以下描述的用户命令规定了TCP实现必须执行的基础功能,以支持进程间通信。各自的实现必须定义它们自己的确切格式,可以在单个调用中提供基础功能的组合或者子集。特别的是,一些实现可能希望对给定连接,在用户发出第一个SEND或者RECEIVE时自动OPEN连接。
在提供进程间通信的能力时,TCP实现不得仅仅接收命令,还必须返回信息给它服务的进程。后者包括:
- (a) 关于一个连接的基本信息(例如,中断,远程关闭,不明确的远程套接字的绑定)。
- (b) 代表了成功或者各种失败类型的对指定用户命令的响应。
3.9.1.1. Open 打开
格式:OPEN (local port, remote socket, active/passive [, timeout] [, Diffserv field] [, security/compartment] [, local IP address] [, options]) -> local connection name
如果active/passive位被设置为被动passive,那么这个调用会为传入的连接做监听LISTEN。一个被动的OPEN可能有一个完全指定的的远程套接字来等待进行特定的连接,或者有一个未确定的远程套接字来等待进行任何调用。通过随后执行SEND,一个完全指定的被动调用可以变成主动。
一个传输控制块(transmission control block,TCB)被创建了,并部分填充了来自OPEN命令参数中的数据。
每个被动的OPEN调用会在LISTEN状态时创建新的连接记录,或者返回一个错误;它不得影响任何之前创建的连接记录(必须-41)。
支持多个同时连接的TCP实现,必须提供OPEN调用,该调用在当一个相同本地端口的连接块处于SYN-SENT或者SYN-RECEIVED状态时,在功能上允许应用监听端口(必须-42).
在一个主动OPEN命令上,TCP端会立即开启同步连接的程序(即建立连接)。
超时时间timeout,如果存在,允许调用者对所有提交到TCP的数据设置一个超时时间。如果数据没有在超时时间内成功传输到目的地,TCP端会中止连接。当前的全局默认值是5分钟。
TCP实现或者操作系统的一些组件,会在打开带有指定Diffserv字段值或者security/compartment的连接时,验证用户的权限。在OPEN调用中,没有指定Diffserv字段值或者security/compartment代表了必须使用默认值。
只有当传入请求的security/compartment信息和OPEN调用中的请求完全匹配时,TCP才会接受传入的请求。
用户指示的Diffserv字段值只影响传出的包,可以在网络的路由中被修改,和接收到的包没有直接的关系。
本次连接名(local connection name)会被TCP实现返回给用户。本地连接名能够被用作该连接的简称,连接被定义为一对<本地套接字, 远程套接字>。
可选项"local IP address"参数必须被支持,以允许指定本地IP地址(必须-43)。这使得当存在多宿主时,应用能够按需选择使用的本地IP地址。
指定了"local IP address"参数的被动OPEN调用,会等待传入此地址的连接请求。如果参数未指定,被动的OPEN会等待传入任何本地IP地址的连接请求,然后绑定该连接的本地IP地址到使用的特定地址上。
对一个主动OPEN调用,指定的 "local IP address"参数会被用于打开连接。如果该参数未被指定,主机会选择一个何时的本地IP地址(参见RFC1122,第3.3.4.2节)。
当主动打开TCP连接时,如果多宿主主机上的应用没有指定本地IP地址,那么TCP实现必须在发送(第一个)同步SYN之前(必须-44),要求IP层选择一个本地IP地址。参见RFC1122的第3.4节的函数GET_SRCADDR() 。
在所有其他时间,此连接上之前的段已经被发送或者接收到了,那么TCP实现必须使用和那些之前段所用相同的本地地址。
TCP实现必须把本地对无效远程IP地址(例如,广播或者多播地址)的OPEN调用作为错误来拒绝。
3.9.1.2. Send 发送
格式:SEND (local connection name, buffer address, byte count, URGENT flag [, PUSH flag] [, timeout])
该调用会把包含在指定的用户缓冲中的数据,在指定的连接上发送。如果连接尚未打开,SEND会视为错误。一些实现可能允许用户先SEND;这种情况下,会做自动的OPEN。例如,这可能时应用数据包含在SYN段中的一种方式。如果调用的进程未被授权使用此连接,会返回错误。
TCP端可以在SEND调用上实现PUSH标志位(可以-15)。如果PUSH标志位未被实现,那么发送的TCP端:(1)不得无限缓冲数据(必须-60),(2)必须在最后缓冲的段(即,当没有更多排队数据待发送)中设置PSH位。以下剩余的描述假设PUSH标志位在SEND调用中是支持的。
如果PUSH标志位被设置了,应用打算把数据立即传输给接收方,且PSH位会在缓冲的最后TCP段中被设置。
PSH位不是记录标记,且和段边界无关。当打包数据时,传输者应该折叠连续的比特,以发送尽可能大的段(应该-27)。
如果PUSH标志位被设置了,数据可以和之后的SEND数据结合一起发送,以提高传输效率。当应用发起了一系列未设置PUSH标志位的SEND调用,TCP实现可以内部聚合数据而不发送它(可以-16).注意,当使用Nagle算法时,TCP实现可以在发送前缓冲数据,不管PUSH标志位(参见第3.7.4节)。
每当需要强制发送数据以避免通信僵局时,应用程序在逻辑上被要求在SEND调用中设置PUSH标志位。但是,TCP实现应该尽可能发送最大大小的段(应该-28),以提升性能(参见第3.8.6.2.1节)。
因为实现的差异和中间盒问题,新的应用不应设置URGENT标志位[39](应该-13)。
如果URGENT标志位被设置了,发送到目标TCP端的段会设置紧急指针(urgent pointer)。如果紧急指针指示紧急指针之前的数据尚未被接受方进程消费,那么TCP接收方会发紧急情况的信号给接收方进程。URGENT标志位的目的是催促接收方处理紧急数据,并指示接收方何时所有的当前已知紧急数据被接收到了。发送方用户的TCP实现发送紧急的信号次数,没必要等于接收方用户被通知紧急数据存在的次数。
如果在OPEN中没有指定远程套接字,但是连接已建立(例如,由于远程的段到达了本地套接字,监听(LISTEN)中的连接变得明确),然后指定的缓冲被发送到暗含的远程套接字上。未指定远程套接字使用OPEN的用户,可以在使用SEND之时,无需曾经清楚地知道远程套接字地址。
但是,如果在远程套接字变得明确前,尝试了SEND,会返回错误。用户能够使用STATUS调用来确定连接的状态。当绑定了未指定的套接字,一些TCP实现可能会通知用户。
如果超时时间(timeout)被指定了,该连接的当前的用户超时时间会被改为新的。
在最简单的实现中,在传输完成前,或者超过超时时间前,SEND不会返回控制权给发送方进程。但是,这个简单的方式既受制于死锁(例如,连接两端可能都在执行RECEIVE前尝试SEND),又性能很差,所以不推荐。更先进的实现会立即返回,允许进程和网络I/O同时运行,并且,此外,还允许多个SEND都在运行中。多个SEND会以先到先得的顺序来提供服务,所以TCP端会把它们排队,不会立即服务。
我们已经暗暗地假设了一个异步的用户接口,在接口中,SEND稍后会从服务的TCP端引出一些信号或者伪中断。另一种方式是立即返回一个响应。例如,SEND可能立即返回本地的确认,即使发送的段尚未被远方的TCP端确认。我们可以乐观地假设最终成功了。如果我们错了,连接无论如何会关闭的,因为有超时。在这种(同步的)实现中,仍然会有一些异步的信号,但是这些信号会处理连接自身,不会处理指定的段或者缓冲。
为了让进程对不同的SEND区分错误或者成功的指示,可能合适的方式是,把缓冲地址和对SEND请求的代码响应一起返回。TCP到用户的信号在下面讨论,它指出了应该被返回给调用进程的信息。
3.9.1.3. Receive 接收
格式:RECEIVE (local connection name, buffer address, byte count) -> byte count, URGENT flag [, PUSH flag]
该命令分配了关联指定连接的接收缓冲。如果该命令之前没有OPEN,或者调用进程未被授权使用此连接,会返回错误。
在最简单的实现中,缓冲被填满或者发生错误之前,控制权不会返回给调用程序,但是该机制高度受制于死锁。更先进的实现会允许多个RECEIVE同时进行。这些会在段到达时被填满。该策略允许以更复杂机制(可能是异步)的代价提高吞吐量,来通知调用程序PUSH已经被看到了或者缓冲填满了。
TCP接收者可以通过接口中PUSH标志位(可以-17)来把接收到的PSH传给应用层,但是它不是必须的(这在RFC1122的第4.2.2.2节中阐明)。以下描述RECEIVE调用的剩余文本假设了支持传递PUSH指示。
如果在PUSH被看见前,足够的数据到达并填满了缓冲,那么不会在对RECEIVE的响应中设置PUSH标志位。缓冲会被填充尽可能多 的数据。如果在缓冲填满前看到了PUSH,缓冲会返回部分填充并指示有PUSH。
如果有紧急数据,数据通过TCP到用户的信号到达时,用户就会收到通知。接收的用户应该因此进入“紧急模式”。如果URGENT flag是打开的,则有额外的紧急数据残留。如果URGENT flag是关闭的,则该RECEIVE调用已经返回了所有紧急数据,且用户现在可以脱离“紧急模式”。注意,紧急指针后面的数据(不紧急的数据)不能在同一个缓冲中和之前的紧急数据一起传送给用户,除非对用户有明确的边界标记。
为了在区分几个未完成的RECEIVE,并处理缓冲未完全填满的情况,返回值会有一个缓冲指针和一个字节数,字节数指示了已接收数据的实际长度。
RECEIVE的替代实现可以优TCP端分配缓冲容量,或者TCP端可以和用户共享环形缓冲。
3.9.1.4. Close 关闭
格式:CLOSE (local connection name)
该命令会导致指定连接关闭。如果连接未打开或者调用进程未被授权使用此连接,会返回错误。关闭连接是打算做一个优雅的操作,大意为,只要流量控制允许,未完成的SEND会被传输(并重传),直到所有的SEND都被服务。因此,在调用了几个SEND后调用CLOSE,并期望所有数据发送到目的地,应该是可接受的。还应该清楚的是,用户应该继续在CLOSING的连接上RECEIVE,因为远程对端可能正在尝试传输它最后的数据。因此,CLOSE意味着“我没有更多数据要发送”,但并不意味“我不再接收任何数据”。可能发生(如果用户等级协议并未很好地深思熟虑)这种情况,关闭侧无法在超时之前清空它所有的数据。在这情况下,CLOSE会转变为ABORT,关闭的TCP对端会放弃。
用户可以随时主动关闭连接,或者响应来自TCP实现的各种提示(例如,远程关闭已执行,超过传输超时实际,目的地无法访问)。
因为关闭连接要求和远程TCP对端通信,连接可能会在短时间内保持关闭状态。在TCP对端响应CLOSE命令之前尝试重新打开连接,会导致返回错误响应。
关闭也意味了推送(push)功能。
3.9.1.5. Status 状态
格式:STATUS (local connection name) -> status data
这是依赖于具体实现的用户命令,可以被排除而无不利影响。返回的信息一般来自TCB关联的连接。
该命令返回一个包含以下信息的数据块:
local socket(本地套接字),
remote socket(远程套接字),
local connection name(本地连接名),
receive window(接收窗口大小),
send window(发送窗口大小),
connection state(连接状态),
number of buffers awaiting acknowledgment(等待确认的缓冲区数量),
number of buffers pending receipt(等待接收的缓冲区数量),
urgent state(紧急状态),
Diffserv field value(差分服务字段值),
security/compartment(安全等级/分区), and
transmission timeout(传输超时时间).
根据连接的状态或者实现自身,该信息中的某些可能不是有效的或者有意义的。如果调用进程未被授权使用该连接,会返回错误。这阻止了未授权的进程获取该连接信息。
3.9.1.6. Abort 中止
格式:ABORT (local connection name)
该命令会导致所有等待中的SEND和RECEIVE中止,TCB会被移除,特定的RST消息会被发送给连接的远程TCP对端。根据实现,用户可能对每个未完成的SEND或RECEIVE接收到中止指示,或者可能只是简单地接收到一个中止确认(ABORT-acknowledgment)。
3.9.1.7. Flush 刷新
一些TCP实现包含了FLUSH调用,它将清空TCP发送队列中用户已发出SEND调用但仍在当前发送窗口右侧的任何数据。也就是说,它在不丢失序列号同步的情况下,尽可能多地刷新排队的发送数据。FLUSH调用可以被实现(可以-14)。
3.9.1.8. Asynchronous Reports 异步报告
必须有个机制来报告TCP软件错误情况给应用(必须-47)。一般来说,我们假定采用应用提供ERROR_REPORT的常规形式,它可能是从传输层异步地向上通知:
ERROR_REPORT(local connection name, reason, subreason)
reason和subreason参数的精确编码此处并未指定。但是,异步报告给应用的情况必须包含:
但是,不想要接收这种ERROR_REPORT调用的应用程序,应该能够有效地禁用这些调用(应该-20)。
3.9.1.9. Set Differentiated Services Field (IPv4 TOS or IPv6 Traffic Class) 设置差分服务字段(IPv4的TOS或者IPv6的Traffic Class)
应用层必须能够对连接上发送的段指定差分服务(Differentiated Services)字段(必须-48)。差分服务字段包含了6位的差分服务编码点(Differentiated Services Codepoint,简称DSCP)值。它不是必须的,但是应用应该能够在连接生命周期内改变差分服务字段(应该-21)。当在连接上发送段时,TCP实现应该将当前的区分服务字段值不做更改地传递给IP层(应该-22)。
差分服务字段在连接的每个方向上会被单独指定,以便接收方应用指定用于ACK段的差分服务字段。
TCP实现可以把最近接收到最多的差分服务字段上送给应用(可以-9)。
3.9.2. TCP/Lower-Level Interface TCP/低层级的接口
TCP端调用低层级的协议模块来真正发送和接收网络的信息。TCP下面的2个当前标准的网络层(IP)协议版本是IPv4[1]和IPv6 [13]。
如果低层级协议是IPv4,它提供服务类型(在差分服务字段中使用)和存活时间的参数。TCP为这些参数使用以下的设置:
- Diffserv field:
Diffserv字段的IP头的值由用户给出。这包括了差分服务编码点(DSCP)的比特。 - Time to Live (TTL):
用于发送TCP端的TTL值必须是可配置的(必须-49)。
– 注意RFC793指定了1分钟(60秒)作为TTL的常量,因为假设的最长报文段寿命是2分钟。这是为了明确要求,如果段不能在1分钟内通过网络系统传输,那么段会被销毁。RFC1122更新了RFC793,要求TTL可配置。
– 注意Diffserv字段允许在一个连接中发生改变(RFC1122的第4.2.4.2节)。但是,应用接口可能并未支持此能力,应用并不了解单独的TCP段,所以它最多只能在粗粒度上被使用。该限制在RFC7657 (第5.1节、第5.3节和 第6节) [50]中做了进一步讨论。一般来说,应用不应该在连接的过程中改变Diffserv字段值(应该-23)。
任何低层级协议必须提供源地址、目的地址和协议字段,和确定“TCP长度”的某种方式,以提供IP的功能等价服务,并用于TCP的校验和。
当接收到的选项是从IP层传递到TCP的,TCP实现必须忽略它不能理解的选项(必须-50)。
TCP实现可以支持时间戳(Timestamp)选项(可以-10)和记录路径(Record Route)选项(可以-11)。
3.9.2.1. Source Routing 源路由
如果更低层级是IP(或者其他提供此特性的协议),并使用了源路由(source routing),接口必须允许传输路由信息。这特别重要,以便TCP校验和中使用的源地址和目的地址是原始的来源和最终的目的地。同样重要的是,保留返回的路由来回答连接的请求。
当主动打开TCP连接时,应用必须能够指定源路由(必须-51),并且这必须优先数据报中收到的源路由(必须-52)。
当TCP连接被动打开,且携带完整IP源路由选项(包含了返回路由)的包到达了,TCP实现必须保存该返回路由,并用于此连接上的所有段(必须-53)。如果在后面的段中有不同的源路由到达了,后者定义的应该覆盖之前的(应该-24)。
3.9.2.2. ICMP Messages ICMP消息
TCP实现必须对从IP层传输来的ICMP错误采取行动,引导它到产生该错误的连接(必须-54)。必须的多路复用信息可以在ICMP消息包含的IP头中找到。
这不仅适用于IPv4 ICMP,也适用于ICMPv6。
[35]包含了特定ICMP和ICMPv6消息的的讨论,它们被分类为可能产生不同响应的“软件”或者“硬件”错误。ICMP消息类别的处理如下所述:
-
Source Quench 源抑制
TCP实现必须静默丢弃任何接收到的ICMP源抑制消息(必须-55)。讨论参见 [11]。 -
Soft Errors 软件错误
对IPv4的ICMP,这些包含了:目的地不可达–code是0、1、5;超时–code是0、1;和参数问题。
For ICMPv6, these include: Destination Unreachable – codes 0, 3; Time Exceeded – codes 0, 1; and Parameter Problem – codes 0, 1, 2.
对ICMPv6,这些包含了:目的地不可达–code是0、3;超时–code是0、1;和参数问题–code是0、1、2。
因为这些不可达消息代表了软件错误情况,TCP实现不得中止连接(必须-56),应该使信息对应用可用(应该-25)。 -
Hard Errors 硬件错误
对ICMP,这些包含目的地不可达–code是2-4。
这些是硬件错误情况,所以TCP实现应该中止连接(应该-26)。[35] 注意,当处于任何同步状态的连接收到ICMP硬件错误时,有些实现并没有中止连接。
注意[35] ,第4节描述了广泛传播的实现行为,该行为在连接建立期间把软件错误作为了硬件错误。
3.9.2.3. Source Address Validation 源地址验证
RFC1122要求对传入的SYN包校验地址:
传入的携带无效源地址的SYN,必须被TCP或者IP层忽略(必须-63)(参见第3.2.1.3节)。
TCP实现必须静默舍弃传入的定位到广播或多播地址的SYN段(必须-57)。
这可以防止错误生成连接状态和回复,实现者应该注意,如RFC1122中特别指出的那样,此指导适用于所有传入段,而不仅仅是SYN。
3.10. Event Processing 事件处理
本节中描述的处理时可能的实现的一个例子。其他实现可用有轻微的不同的处理顺序,但是它们应该只是细节上和本节不同,而不是实质上。
TCP端的活动可以被描述为对事件的响应。发生的事件可以被划分为3类:用户调用、段到达、超时。本节描述了TCP端响应每个事件的处理。在许多情况下,所需的处理依赖于连接的状态。
发生的事件:
-
User Calls 用户调用
OPEN 打开
SEND 发送
RECEIVE 接收
CLOSE 关闭
ABORT 中止
STATUS 状态 -
Arriving Segments 段到达
SEGMENT ARRIVES 段到达 -
Timeouts 超时
USER TIMEOUT 用户超时
RETRANSMISSION TIMEOUT 重传超时
TIME-WAIT TIMEOUT 等待超时
TCP/用户接口的模式是,用户命令通过事件或伪中断接收即时返回和可能的延迟响应。在以下描述中,术语“信号”意味着引起了延迟响应。
本文中的错误响应由字符串标识。例如,指向不存在连接的用户命令会收到“error: connection not open”。
请注意,以下的所有对序列号、确认号、窗口等的算数计算,都是模232(序列号空间的大小)。还要注意,“=<” 意思是小于或者等于(模232)。
思考处理传入的段的一个自然的方式,是想象它们被首先被测试是否是正确的序列号(即,它们的内容位于序列号空间的预计的“接收窗口”范围内),然后它们一般会以序列号顺序排队并处理。
当一个段和其他已接收到的段重叠了,我们重建该段来包含新数据,并调整头部字段以保持一致。
注意,如果没有提及状态变更,TCP连接会保持在相同的状态。
3.10.1. OPEN Call OPEN调用
CLOSED状态(即,TCB不存在)
- 创建一个新的传输控制块(TCB)来保存连接状态信息。填写local socket identifier, remote socket, Diffserv field, security/compartment, 和user timeout 信息。注意在被动OPEN中,远程套接字的部分信息可能未指定,而是由传入的SYN段中的参数补充填写。验证请求的security和Diffserv值是否允许,如果不允许,返回"error: Diffserv value not allowed"或者"error: security/compartment not allowed"。如果是被动,进入LISTEN状态并返回。如果是主动且远程套接字未指定,返回"error: remote socket unspecified";如果主动且指定了远程套接字,会发送一个SYN段。选中一个初始发送序列号(ISS),SYN段会以 <SEQ=ISS><CTL=SYN>的格式发送。设置SND.UNA为ISS,SND.NXT 为ISS+1,进入SYN-SENT状态并返回。
- 如果调用者无权访问指定的本地套接字,返回"error: connection illegal for this process"。如果没有空间创建新连接了,返回"error: insufficient resources"。
LISTEN状态
- 如果OPEN调用是主动且指定了远程套接字,那么会把连接从被动改为主动,并选择一个ISS。发送一个SYN段,设置SND.UNA为ISS,SND.NXT 为ISS+1。进入SYN-SENT状态。有关SEND的数据可能会和SYN段一起发送,或者排队等待进入ESTABLISHED状态后的传输。命令中请求的紧急(urgent )位必须与作为此命令结果发送的数据段一起发送。如果没有空间排队请求,返回"error: insufficient resources"。如果远程套接字未指定,那么返回"error: remote socket unspecified"。
SYN-SENT状态
SYN-RECEIVED状态
ESTABLISHED状态
FIN-WAIT-1状态
FIN-WAIT-2状态
CLOSE-WAIT状态
CLOSING状态
LAST-ACK状态
TIME-WAIT状态
- 返回"error: connection already exists"。
3.10.2. SEND Call SEND调用
CLOSED状态(即,TCB不存在)
- 如果用户无权访问这个连接,那么返回"error: connection illegal for this process"。
- 否则,返回"error: connection does not exist"。
LISTEN 状态
- 如果指定了远程套接字,那么会把连接从被动改为主动,并选择一个ISS。发送一个SYN段,设置SND.UNA为ISS,SND.NXT 为ISS+1。进入SYN-SENT状态。有关SEND的数据可能会和SYN段一起发送,或者排队等待进入ESTABLISHED状态后的传输。命令中请求的紧急(urgent )位必须与作为此命令结果发送的数据段一起发送。如果没有空间排队请求,返回"error: insufficient resources"。如果远程套接字未指定,那么返回"error: remote socket unspecified"。
SYN-SENT状态
SYN-RECEIVED状态
- 排队等待进入ESTABLISHED状态后的传输。如果没有空间排队请求,返回"error: insufficient resources"。
ESTABLISHED状态
CLOSE-WAIT状态
- 对缓冲区分段,以捎带的确认(piggybacked acknowledgment)(确认值=RCV.NXT)来发送它。如果没有足够的空间来保存缓冲,只需返回"error: insufficient resources"。
- 如果设置了紧急标志,那么SND.UP <- SND.NXT,并在传出的段中设置紧急指针。
FIN-WAIT-1状态
FIN-WAIT-2状态
CLOSING状态
LAST-ACK状态
TIME-WAIT状态
- 返回 “error: connection closing”,不服务请求。
3.10.3. RECEIVE Call RECEIVE调用
CLOSED状态(即,TCB不存在)
- 如果用户无权访问这个连接,那么返回"error: connection illegal for this process"。
- 否则,返回"error: connection does not exist"。
LISTEN状态
SYN-SENT状态
SYN-RECEIVED状态
- 排队等待进入ESTABLISHED状态后的处理。如果没有空间排队请求,返回"error: insufficient resources"。
ESTABLISHED状态
FIN-WAIT-1状态
FIN-WAIT-2状态
- 如果传入的段不足以排队满足请求,让请求排队。如果没有排队的空间来保存RECEIVE,返回"error: insufficient resources"。
- 重组排队的传入段到接收缓冲中,并返回给用户。如果有这种场景,则标记“已看到push”(PUSH)。
- 如果RCV.UP在当前已传给用户的数据之前,通知用户存在紧急数据。
- 当TCP端负责传递数据给用户时,该事实必须通过确认通信给发送方。下面在处理传入段的讨论中描述了这种确认的形成。
CLOSE-WAIT状态
- 由于远程侧已经发送了FIN,RECEIVE必须由手头已有但未传递给用户的数据来满足。如果没有文本在等待传输,RECEIVE会得到"error: connection closing"的响应。否则,任何剩余的数据都会被用来满足RECEIVE。
CLOSING状态
LAST-ACK状态
TIME-WAIT状态
- 返回"error: connection closing"。
3.10.4. CLOSE Call CLOSE调用
CLOSED状态(即,TCB不存在)
- 如果用户无权访问这个连接,那么返回"error: connection illegal for this process"。
- 否则,返回"error: connection does not exist"。
LISTEN状态
- 任何未完成的RECEIVE都会返回"error: closing"的响应。删除TCB,进入CLOSED状态,并返回。
SYN-SENT状态
- 删除TCB,返回"error: closing"给任何排队的SEND或者RECEIVE。
SYN-RECEIVED状态
- 如果没有SEND已被发出,并没有等待发送的数据,那么组成一个FIN段并发送,进入FIN-WAIT-1状态;否则,排队等待进入ESTABLISHED状态后的处理。
ESTABLISHED状态
- 排队直到所有之前的SEND都被分段,然后生成一个FIN段并发送。在任何情况下,都会进入FIN-
WAIT-1状态。
FIN-WAIT-1状态
FIN-WAIT-2状态
- 严格来说,这是一个错误,应该收到"error: connection closing"的响应。"ok"的响应也是可接受的,只要不发出第二个FIN(不过第一个FIN可能会重传)。
CLOSE-WAIT状态
- 排队直到所有之前的SEND都被分段;然后发送一个FIN段,进入LAST-ACK状态。
CLOSING状态
LAST-ACK状态
TIME-WAIT状态
- 响应"error: connection closing"。
3.10.5. ABORT Call ABORT调用
CLOSED状态(即,TCB不存在)
- 如果用户无权访问这个连接,那么返回"error: connection illegal for this process"。
- 否则,返回"error: connection does not exist"。
LISTEN状态
- 任何未完成的RECEIVE都应该返回"error: connection reset"的响应。删除TCB,进入CLOSED状态,并返回。
SYN-SENT状态
- 所有排队的SEND和RECEIVE都应该收到“connection reset”通知。删除TCB,进入CLOSED状态,并返回。
SYN-RECEIVED状态
ESTABLISHED状态
FIN-WAIT-1状态
FIN-WAIT-2状态
CLOSE-WAIT状态
- 发送一个重置(reset)段:
<SEQ=SND.NXT><CTL=RST> - 所有排队的SEND和RECEIVE都应该收到“connection reset”通知。所有排队等待传输(除了上面生成的RST)或者重传的段都应该被刷新(flush)。删除TCB,进入CLOSED状态,并返回。
CLOSING状态
LAST-ACK状态
TIME-WAIT状态
- 响应"ok",删除TCB,进入CLOSED状态,并返回。
3.10.6. STATUS Call STATUS调用
CLOSED状态(即,TCB不存在)
- 如果用户无权访问这个连接,那么返回"error: connection illegal for this process"。
- 否则,返回"error: connection does not exist"。
LISTEN状态
- 返回"state = LISTEN"和TCB指针。
SYN-SENT状态
- 返回"state = SYN-SENT"和TCB指针。
SYN-RECEIVED状态
- 返回"state = SYN-RECEIVED"和TCB指针。
ESTABLISHED状态
- 返回"state = ESTABLISHED"和TCB指针。
FIN-WAIT-1状态
- 返回"state = FIN-WAIT-1"和TCB指针。
FIN-WAIT-2状态
- 返回"state = FIN-WAIT-2"和TCB指针。
CLOSE-WAIT状态
- 返回"state = CLOSE-WAIT"和TCB指针。
CLOSING状态
- 返回"state = CLOSING"和TCB指针。
LAST-ACK状态
- 返回"state = LAST-ACK"和TCB指针。
TIME-WAIT状态
- 返回"state = TIME-WAIT"和TCB指针。
3.10.7. SEGMENT ARRIVES 段到达
3.10.7.1. CLOSED STATE CLOSED状态
如果状态是CLOSED(即,TCB不存在),那么
- 传入段中的所有数据都会被丢弃。包含RST的传入段会被丢弃。不包含RST的传入段会导致响应RST。选择确认号和序列号字段,使得重置序列可以被发送违规段的TCP端接受。
- 如果ACK位未设置,会使用序列号0,
<SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK> - 如果设置了ACK位,
<SEQ=SEG.ACK><CTL=RST> - 返回。
3.10.7.2. LISTEN STATE LISTEN状态
如果状态是LISTEN,那么
第一步,检查RST:
- 传入的RST段无效,因为它不可能是作为对此连接化身发送的任何内容的响应而发送的。应忽略传入的RST。返回。
第二步,检查ACK:
- 如果连接在LISTEN状态下,任何到达的确认都是错的。应该对任何到达的携带ACK的段生成一个可接受的reset段。这个RST的格式应该如下:
<SEQ=SEG.ACK><CTL=RST> - 返回。
第三步SYN:
-
如果设置了SYN位,检查安全性。如果传入段的security/compartment没有完全符合TCB中的security/compartment,那么发送一个reset并返回。
<SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK> -
设置RCV.NXT为SEG.SEQ+1,IRS设置为SEG。SEQ,和任何其他控制或者文本应该排队等待之后处理。应该选择ISS,并发送一个该形式的SYN段:
<SEQ=ISS><ACK=RCV.NXT><CTL=SYN,ACK> -
设置SND.NXT为ISS+1,SND.UNA为ISS。连接状态应该被改为SYN-RECEIVED。注意,任何其他传入的控制或者数据(和SYN结合)会在SYN-RECEIVED状态下被处理,但是SYN和ACK不应该重复处理。如果没有完全指定listen(即,远程套接字未被完全指定),那么应该现在填写未指定的字段。
第四步,其他数据或者控制:
- 这是不应该达到的。舍弃该段并返回。任何其他控制或携带数据的段(不包含SYN)都必须有一个ACK,因此会被第二步中的ACK处理丢弃,除非它在第一步中首先被RST检查丢弃。
3.10.7.3. SYN-SENT STATE SYN-SENT状态
如果状态是SYN-SENT,那么
第一步,检查ACK位:
- 如果设置了ACK位,
-
- 如果SEG.ACK =< ISS 或者 SEG.ACK > SND.NXT,发送一个reset(除非设置了RST位,否则丢弃该段并返回)
<SEQ=SEG.ACK><CTL=RST>
- 如果SEG.ACK =< ISS 或者 SEG.ACK > SND.NXT,发送一个reset(除非设置了RST位,否则丢弃该段并返回)
-
- 丢弃该段。返回。
-
- 如果SND.UNA < SEG.ACK =< SND.NXT,那么ACK是可接受的。一些已部署的TCP代码使用的校验是SEG.ACK == SND.NXT(使用了"==“, 而不是”=<"),但是,当堆栈有能力在SYN上发送数据时,这是不合适的,因为TCP对端可能不会接受并确认SYN上的数据。
第二步,校验RST位:
- 如果设置了RST位,
-
- RFC5691[9]中描述了一个可能的盲重置攻击。在该文中描述的缓解方法有其中解释的特定的适用范围,不能代替加密保护(例如,IPsec或者TCP-AO)。支持RFC5961中的缓解方式的TCP实现,应该在执行下一段中的行为前,首先校验序列号是否与RCV.NXT完全匹配。
-
- 如果ACK是可接受的,那么发送"error: connection reset"信号给用户,丢弃该段,进入CLOSED状态,删除TCB并返回。否则(无ACK),丢弃该段并返回。
第三步,校验安全:
-
如果段中的security/compartment(安全等级/分区)没有完全匹配TCB中的security/compartment,发送一个重置:
-
- 如果有一个ACK,
<SEQ=SEG.ACK><CTL=RST>
- 如果有一个ACK,
-
- 否则,
<SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK>
- 否则,
-
如果已发送一个重置,丢弃该段并返回。
第四步,校验SYN位:
- 只有当ACK是没问题的,或者,无ACK且段不包含RST时,才应该会到达这一步。
- 如果SYN位是开启的,且security/compartment(安全等级/分区)是可接受的,那么RCV.NXT设置为SEG.SEQ+1,IRS设置为SEG.SEQ。SND.UNA应该被提前到等于SEG.ACK(如果有ACK)处,重传队列中因此确认的的任何段应该被删除。
- 如果SND.UNA > ISS(我们的SYN已经被确认了),改变连接状态为ESTABLISHED,形成一个ACK段
<SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK> - 并发送它。可以包括排队等待传输的数据或控制。当接收的段包含在以后的处理步骤中必会生成一个确认的数据时,一些TCP实现禁止发送该段,从而避免发送SYN的额外确认。如果段中有其他控制或文本,则在第3.10.7.4节下的第六步继续处理,在其中检查URG位;否则,返回。
- 否则,进入SYN-RECEIVED,形成一个SYN,ACK的段
<SEQ=ISS><ACK=RCV.NXT><CTL=SYN,ACK> - 并发送它。设置变量:
SND.WND <- SEG.WND
SND.WL1 <- SEG.SEQ
SND.WL2 <- SEG.ACK
如果段中有其他的控制或段中的文本,将它们排队,以在达到ESTABLISHED状态后进行处理,返回。 - 注意,在SYN段(这是上面提及的“段中的文本”)上发送和接收应用数据是合理的。历史上一直存在着对这一话题的重大误传和误解。一些防火墙和安全设备认为这是可疑的。但是,这个能力已在T/TCP [21]中使用,也用于TCP快速打开(TFO)[48],所以对于实现和网络设备来说,允许它很重要。
第五步,如果SYN或RST位都没有设置,则丢弃该段并返回。
3.10.7.4. Other States 其他状态
否则
第一步,校验序列号:
-
SYN-RECEIVED STATE
-
ESTABLISHED STATE
-
FIN-WAIT-1 STATE
-
FIN-WAIT-2 STATE
-
CLOSE-WAIT STATE
-
CLOSING STATE
-
LAST-ACK STATE
-
TIME-WAIT STATE
-
- 分段按顺序处理。到达时的初始测试用于丢弃旧的重复项,但以SEG.SEQ的顺序进行进一步的处理。如果段的内容跨越新旧之间的边界,则仅处理新的部分。
-
- 通常,必须实现对接收段的处理,以尽可能聚合ACK段(MUST-58)。例如,如果TCP端点正在处理一系列排队的段,则它必须在发送任何ACK段之前处理所有这些段(MUST-59)。
-
- 传入段的可接受性测试有四种情况:
- 传入段的可接受性测试有四种情况:
-
- 在实现此处所述的序列号验证时,请参见附录A.2
-
- 如果RCV.WND是0,不接受任何段,但应特别考虑接受有效的ACK、URG和RST。
-
- 如果传入段不可接受,则应发送应答确认(除非设置了RST位,否则删除段并返回):
<SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK>
- 如果传入段不可接受,则应发送应答确认(除非设置了RST位,否则删除段并返回):
-
- 发送确认后,丢弃不可接受的段并返回。
-
- 注意,对于TIME-WAIT状态,[40]中描述了一种改进的算法,用于处理利用时间戳而不是依赖于这里描述的序列号检查的传入SYN段。当实现改进的算法时,上述逻辑不适用于在TIME-WAIT状态下的连接上接收的具有时间戳选项的传入SYN段。
-
- 在下文中,假设段是从RCV.NXT开始的理想段,不超过窗口大小。可以通过修剪位于窗口之外的任何部分(包括SYN和FIN)来定制实际段以适应该假设,并且如果段随后从RCV.NXT开始,则仅进一步处理。具有较高起始序列号的段应保留以供以后处理(SHLD-31)。
第二步,校验RST位:
RFC5961[9]的第3节描述了潜在的盲重置攻击和可选的缓解方法。这不提供加密保护(例如,在IPsec或TCP-AO中),但可以适用于RFC5961中描述的情况。对于实现RFC5961中描述的保护的堆栈,下面的三个检查适用;否则,下面进一步指示对这些状态的处理。
- 如果设置了RST位,并且序列号在当前接收窗口之外,则静默丢弃该段。
- 如果设置了RST位,并且序列号与下一个期望的序列号(RCV.NXT)完全匹配,则TCP端点必须根据连接状态,以下面规定的方式重置连接。
- 如果设置了RST位,并且序列号不完全匹配下一个期望的序列值,但仍然在当前接收窗口内,则TCP端点必须发送确认(challenge ACK):
<SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK>
在发送challenge ACK后,TCP端点必须丢弃不可接受的段,并停止进一步处理传入数据包。请注意,RFC5961和勘误表ID4772[99]包含实现中ACK调节的其他注意事项。
- SYN-RECEIVED STATE
如果设置了RST位,
如果该连接是通过被动OPEN(即来自LISTEN状态)初始化的,则将该连接返回到LISTEN状态并返回。无需通知用户。如果此连接是通过主动OPEN(即来自SYN-SENT状态)初始化的,则拒绝连接;向用户发出“connection refused”的信号。在任何一种情况下,都应该刷新重传队列。在主动OPEN情况下,进入CLOSED状态并删除TCB,然后返回。 - ESTABLISHED STATE
- FIN-WAIT-1 STATE
- FIN-WAIT-2 STATE
- CLOSE-WAIT STATE
如果设置了RST位,则任何未完成的RECEIVE和SEND都应接收“重置”的响应。应刷新所有段队列。用户还应收到未被要求的常规“connection reset”信号。进入CLOSED(关闭)状态,删除TCB,返回。 - CLOSING STATE
- LAST-ACK STATE
- TIME-WAIT STATE
如果设置了RST位,则进入关闭状态,删除TCB,返回。
第三步,校验安全:
- SYN-RECEIVED STATE
如果段中的security/compartment没有完全匹配TCB中的security/compartment,则发送一个重置并返回。 - ESTABLISHED STATE
- FIN-WAIT-1 STATE
- FIN-WAIT-2 STATE
- CLOSE-WAIT STATE
- CLOSING STATE
- LAST-ACK STATE
- TIME-WAIT STATE
如果段中的security/compartment没有完全匹配TCB中的security/compartment,则发送一个重置;任何未完成的RECEIVE和SEND都应收到“reset’”响应。应刷新所有段队列。用户还应收到未被要求的常规“connection reset”信号。进入CLOSED状态,删除TCB,返回。
请注意,此检查放在序列检查之后,以防止来自这些具有不同安全性的端口号之间的旧连接的段导致当前连接中止。
第四步,校验SYN位:
-
SYN-RECEIVED STATE
如果连接是通过被动OPEN初始化的,则将该连接返回到LISTEN状态并返回。否则,请按照以下同步状态的说明进行处理。 -
ESTABLISHED STATE
-
FIN-WAIT-1 STATE
-
FIN-WAIT-2 STATE
-
CLOSE-WAIT STATE
-
CLOSING STATE
-
LAST-ACK STATE
-
TIME-WAIT STATE
-
- 发送确认后,TCP实现必须丢弃不可接受的段并停止进一步的处理。请注意,RFC5961和勘误表ID4772[99]包含用于实现的额外ACK调节的说明。
-
- 对于不遵循RFC5961的实现,本段中遵循RFC793中描述的原始行为。如果SYN在窗口中,则它是一个错误:发送重置,任何未完成的RECEIVE和SEND应接收“reset”响应,应刷新所有段队列,用户还应收到未被要求的常规“connection reset”信号,进入CLOSED状态,删除TCB,返回。
-
- 如果SYN不在窗口中,则不会到达该步骤,并且会在第一步中发送ACK(序列号检查)。
第五步,校验ACK字段
如果ACK位关闭,则丢弃段并返回。
如果设置了ACK位,
RFC5961[9]的第5节描述了潜在的盲数据注入攻击,以及实现可以选择包括的缓解(MAY-12)。实现RFC5961的TCP堆栈必须添加输入检查,以确保只有在ACK值在(((SND.UNA - MAX.SND.WND) =< SEG.ACK =< SND.NXT)的范围内时,ACK值才可接受。必须丢弃ACK值不满足上述条件的所有传入段,并发回ACK。新的状态变量MAX.SND.WND被定义为本地发送方从其对等方接收到的最大窗口(受窗口缩放影响),或者可以硬编码为最大允许窗口值。当ACK值可接受时,适用于以下各个状态处理:
-
SYN-RECEIVED STATE
-
- 如果SND.UNA < SEG.ACK =< SND.NXT,则进入ESTABLISHED状态,并继续处理,设置以下的变量:
SND.WND <- SEG.WND
SND.WL1 <- SEG.SEQ
SND.WL2 <- SEG.ACK
- 如果SND.UNA < SEG.ACK =< SND.NXT,则进入ESTABLISHED状态,并继续处理,设置以下的变量:
-
- 如果该段的确认不可接受,形成一个重置段
<SEQ=SEG.ACK><CTL=RST>
- 如果该段的确认不可接受,形成一个重置段
-
- 并发送它。
-
ESTABLISHED STATE
-
- 如果SND.UNA < SEG.ACK =< SND.NXT,那么设置SND.UNA <- SEG.ACK。因此已完全确认的重传队列上的任何段都被删除。用户应收到已发送和完全确认的缓冲区的肯定确认(即,SEND缓冲区应返回“ok”响应)。如果ACK是重复的(SEG.ACK=<SND.UNA),则可以忽略它。如果ACK确认尚未发送的内容(SEG.ACK>SND.NXT),则发送ACK,丢弃该段,返回。
-
- 如果SND.UNA =< SEG.ACK =< SND.NXT,发送窗口应该更新。如果 (SND.WL1 < SEG.SEQ or (SND.WL1 = SEG.SEQ and SND.WL2 =< SEG.ACK)),设置SND.WND <- SEG.WND,设置SND.WL1 <- SEG.SEQ,并设置SND.WL2 <- SEG.ACK,
-
- 请注意SND.WND是来自SND.UNA的偏移量。那个SND.WL1记录用于更新SND.WND的最后一个段的序列号,SND.WL2记录用于更新SND.WND的最后一个段的确认号。此处的校验防止使用旧段更新窗口。
-
FIN-WAIT-1 STATE
除了ESTABLISHED状态的处理外,如果FIN段现在被确认,则进入FIN-WAIT-2并在该状态下继续处理。 -
FIN-WAIT-2 STATE
除了ESTABLISHED状态的处理外,如果重传队列为空,则可以确认用户的CLOSE(“ok”),但不要删除TCB。 -
CLOSE-WAIT STATE
执行与ESTABLISHED状态相同的处理。 -
CLOSING STATE
除了ESTABLISHED状态的处理外,如果ACK确认我们的FIN,则进入TIME-WAIT状态;否则,忽略该段。 -
LAST-ACK STATE
在这种状态下,唯一可以到达的是对我们的FIN的确认。如果我们的FIN现在已被确认,则删除TCB,进入CLOSED状态,并返回。 -
TIME-WAIT STATE
唯一可以达到这种状态的是远程FIN的重传。确认它,并重新启动2 MSL超时。
第六步,校验URG位:
- ESTABLISHED STATE
- FIN-WAIT-1 STATE
- FIN-WAIT-2 STATE
如果设置了URG位,那么设置RCV.UP <- max(RCV.UP,SEG.UP),并且如果紧急指针(RCV.UP)在消耗的数据之前,则向用户发送信号,告知远程侧具有紧急数据。如果用户已经收到此连续紧急数据序列的信号(或仍处于“紧急模式”),则不要再次向用户发出信号。 - CLOSE-WAIT STATE
- CLOSING STATE
- LAST-ACK STATE
- TIME-WAIT STATE
这不应该发生,因为已经从远程端接收到FIN。忽略URG。
第七步,处理段的文本:
-
ESTABLISHED STATE
-
FIN-WAIT-1 STATE
-
FIN-WAIT-2 STATE
-
- 一旦处于ESTABLISHED状态,就可以将段数据传递到用户RECEIVE缓冲区。来自段的数据可以移动到缓冲区中,直到缓冲区已满或段为空。如果段清空并带有PUSH标志,则当返回缓冲区时,通知用户已收到PUSH。
-
- 当TCP端点负责将数据交付给用户时,它还必须确认数据的接收。
-
- 一旦TCP端点对数据负责,它就会提高RCV.NXT超过接受的数据,并调整RCV.WND适用于当前可用缓冲区。RCV.NXT与RCV.WND的和不应降低。
-
- 当一个有效段到达时,该段位于窗口中,但不在窗口左侧边缘,TCP实现可以发送确认RCV.NXT的ACK段(MAY-13)。
-
- 请注意第3.8节中的窗口管理建议。
-
- 发送一个该格式的确认:
<SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK>
- 发送一个该格式的确认:
-
- 如果可能,该确认应捎带(piggybacked )在正在传输的段上,而不会引起过度的延迟。
-
CLOSE-WAIT STATE
-
CLOSING STATE
-
LAST-ACK STATE
-
TIME-WAIT STATE
这不应该发生,因为已经从远程端接收到FIN。忽略段文本。
第八步,校验FIN位:
如果由于SEG.SEQ无法验证导致状态为CLOSED、LISTEN或SYN-SENT,则不要处理FIN;丢弃段并返回。
如果设置了FIN位,则向用户发出“connection closing”信号,并以相同消息返回任何即将发送的RECEIVE,基于FIN提高RCV.NXT,并发送FIN的确认。请注意,FIN表示对任何段文本的PUSH都尚未传递给用户。
- SYN-RECEIVED STATE
- ESTABLISHED STATE
进入CLOSE-WAIT状态 - FIN-WAIT-1 STATE
如果我们的FIN已被确认(可能在该段中),则进入TIME-WAIT,启动时间等待计时器,关闭其他计时器;否则,进入CLOSING状态。 - FIN-WAIT-2 STATE
进入TIME-WAIT状态。启动时间等待计时器,关闭其他计时器。 - CLOSE-WAIT STATE
Remain in the CLOSE-WAIT state.
保持在CLOSE-WAIT状态。 - CLOSING STATE
保持在CLOSING状态。 - LAST-ACK STATE
保持在LAST-ACK状态。 - TIME-WAIT STATE
保持在TIME-WAIT状态。重启2 MSL时间等待超时。
并返回。
3.10.8. Timeouts 超时
USER TIMEOUT
用户超时
- 对于任何状态,如果用户超时到期,刷新所有队列,通常向用户发送“error: connection aborted due to user timeout”的信号,对于任何未完成的调用,删除TCB,进入CLOSED状态,返回。
RETRANSMISSION TIMEOUT
重传超时
- 对于任何状态,如果重传队列中的某个段的重传超时到期,请再次发送重传队列前面的段,重新初始化重传计时器,然后返回。
TIME-WAIT TIMEOUT
时间等待超时
- 如果连接的时间等待超时时间到期,删除TCB,进入CLOSED状态,然后返回。