Note:本文为阅读RFC9293时的记录
Note:本文中 老旧的报文
或者其他类似的说法意为:因为网络拥塞或者其他原因导致延迟的报文。并且,该报文已经TCP新发送的新的同类型报文替代。比如:TCP对等体发送的SYN 命名为A报文,此时A报文因为网络拥塞原因发送了延迟,TCP对等体因为长时间没有收到对A报文的确认报文,所以发送了一个新的SYN报文,命名为B报文。此时A就是老旧报文(原文为:old duplicated),已经再无任何用处。
在阅读该篇文章之前,应该阅读TCP Sequence
三报文握手(3WHS) 是建立连接的基本步骤,这个过程通常有一个TCP peer 主动开启,由另一个 TCP peer 响应。也可以两个TCP Peer 同时主动开启,两个对等体同时发送 没有ACK置位的 SYN 报文。
即 SYN 置位的报文
为了 防止 旧的重复 SYN 报文
的到达使得接接收者误认为 对方也要主动开启连接,所以正确使用 “reset” 报文也是非常有必要的。
接下来展示最简单的3WHS过程。
其中 左箭头(–>) 代表报文是从 A 发往 B 的,并且被B正确收到,右箭头相反。
箭头可能为 (…) 代表报文延迟了
为了便于描述,这里省略了TCP报文的其他信息(如端口号等)
A 直接进入了 ESTABLISHED 状态
接下来看一个复杂一点的同步案例:
A,B 是同时开始主动连接的。
TCP实现必须支持 主动,被动,同时主动的打开方式。而且TCP必须跟踪 连接是主动还是被动打开从而达到 SYN-RECEIVED 状态的。
该案例下:AB 同时发送SYN,同时进入 SYN-RECEIVED 同时发送对对方SYN的确认,同时进入 ESTABLISHED 状态。
这里说 的 “同时” 简化了报文传输中时间上的差异
3WHS 是为了防止旧的(Old duplicated) SYN造成混乱。为此,指定了一个特殊的控制报文用于复位
。
如果对等体处于非同步
状态(SYN-SEND 或者 SYN-RECEIVED 状态之一),则将在接收RESET报文后返回到 LISTEN 状态。
如果对等体已经处于同步
状态(Establish,FIN-wait-1,FIN-wait-2,CLOSE-WAIT,CLOSING,LAST-ACK,TIME-WATI 之一),则RESET会终止连接并通知用户
,这种情况在 “Half-open” 中讨论。
案例:
-
第二行 A 发送 ISN为100的SYN报文企图开启连接,但此时老旧的SYN(第二行)比新SYN先到达B。
-
此时B发送对 旧SYN的确认,A收到后,发现我发的ISN是100,你怎么确认90的?
-
A 发送RESET 报文,重置B的状态,并重新发送SYN报文
之后的步骤就是简单的Open步骤。
如果 第6行的报文比RESET提前到达,则B也会发送RESET报文。
B认为,我预期的SEQ接下来应该从91开始,怎么来了个100的?
1.1 Half-Open Connection and Other Anomalies
如果说已经建立连接的两个TCP 对等体不知道对方因为某些原因关闭了,而自己仍然是Established状态,则此时这个连接称为半开连接
。任何一方尝试发送报文,这个连接都会被重置。
half-open 并不常见 因为现代操作系统在关闭之前会通知进程关闭,如果软件设计有条理,则会在关闭时主动释放所有资源,包括TCP连接。
假如说有两个用户进行A,B 正在通信,此时A因为某种异常导致重启了,此时A再次启动时,A很有可能从头
开始或从 保存点
开始重新发送数据。A 很可能会再次尝试打开连接,或者直接从连接上尝试发送数据 (A 认为连接已经打开了,因为是从保存点开始的,而且因为重启时间很短,A认为连接还没有被关闭);在后一种情况下,A会收到TCP实现的 连接未打开
的异常。所以A不得不重新建立连接。
第二行就是典型的半开。
当SYN进行到第三行时,处于同步状态的B,收到了序列号为400的Segment,不在B的接收窗口范围内,所以B回复一个ACK报文用于声明自己接下来希望接收的序列号。
然后A发现这并不是对自己SYN的确认,此时A发现这是一个Half-Open连接,A发送了一个复位。B收到后,进入关闭状态,并重新开始监听,之后的步骤就是基本的3WHS。
上述是A在重启后主动发送SYN,如果A在重启后在主动开启之前,收到了B的同步状态的报文。
则在A收到该报文后,也是发现Half-Open,并立即发送RST。
如果双方都没有意图主动开启连接,但是其中一方收到未知的SYN报文,最终也会有RST报文
1.2 Reset Generation
TCP使用者可以在连接开启的任何时候发送Reset,只要发生了某些异常。
发送reset的一方之后应该进入TIME-WAIT
状态,这通常有助于减少服务器的负载。
总的来说,如果连接的任何一方发现收到的报文显然不属于该连接,则就发送RST。如果不清楚是不是这种情况,则就不能发送RST。
总共有三种情况:
- 如果连接已经处于关闭(CLOSE)状态,那么之后收到的任何报文,都必须回复RST;且状态必须一直保持在CLOSE。
ACK序列号处理方式:
如果收到的报文中,ACK置位,则RST必须使用该报文的ACK序列号+1 (如上图 TCP Peer A)。否则,RST的序列号为0,且ACK序列号为 收到报文
的Sequence Number
字段与 Segment Length
之和。
-
如果当前的状态处于未同步状态(LISTEN,SYN-SENT,SYN-RECEIVED),并且收到的报文
确认了
一些仍未发送的内容,(即:这是一个不可接受的ACK报文),或者收到的报文不匹配 本地期望的信息(如MLS,Compartment等);此时就需要发送reset。ACK序列号与序列号处理方式如上
。 -
如果连接已经处于同步状态 (ESTABLISHED, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT),收到任何
不可接受的Segment
必须回复空(不携带任何数据)的ACK报文,以声明自己期望接收哪些数据。
ACK序列号与序列号处理方式如上。
不可接受的Segment:窗口规定的序列号范围之外,不可接受的ACK序列号
MLS : RFC 9293:TCP IPsec
第 6 章 使用多级别安全(MLS) Red Hat Enterprise Linux 8 | Red Hat Customer Portal
1.3 Reset Processing
在除了 SYN-SENT 之外所有TCP状态中,都需要检查收到RST报文的SEQ片段。如果SEQ在接收窗口声明的范围内,则就是合法的。对于 SYN-SENT 状态,想要置位的话则必须回复 对SYN的确认,同时RST置位即可。
A --> <SEQ=100><SYN> --> B
A <-- <SEQ=1921,ACK=100+1,RST> <-- B
这也是为什么RST需要将SEQ设置为收到的ACK报文的ACK.SEQ+1,因为只要是报文,就必须在对方的可接受窗口规定的序列号范围内
如果RST接收者认为该RST是合法的,则会改变自身的状态。
-
如果在 LISTEN 状态收到了RST,则忽略
-
如果在 SYN-RECEIVED 装填收到了 RST,假设该 SYN-RECEIVED 是从 LISTEN过渡来的,则直接返回到LISTEN状态。
-
其他情况下,收到合法RST则将关闭连接,然后进入CLOSE状态
TCP 实现标准 允许在RST报文中附加一些数据,这些数据用于描述该RST产生的原因。目前仍未定义该数据的规范格式。