[2023][Protocol]TCP Sequence Number

本文详细解析了TCP协议中的序列号机制,包括序列号的处理、确认的逻辑、数据序列号的合法性检查,以及KeepQuiet策略以防止重启后数据包重复。还介绍了3WHS和MSL在连接同步和报文生存时间中的作用。
摘要由CSDN通过智能技术生成

Note:本文为阅读RFC9293时的记录

一个TCP的基本设计理念是:通过TCP发送的每个Byte都有一个序列号。因为每个Byte都有一个序列号,所以每个Byte都可以被清楚地辨认。TCP 对Byte的确认机制是累积性的(Cumulative),所以可以推断出,对序列号为 X 的确认标志着X之前的所有Byte均已收到。确认机制使得TCP可以直接检测重传时的重复报文。

序列号空间大小是4Byte,所以在对序列号处理的运算中,必须(Modulo)上 2 32 2^{32} 232

这个操作可以保证序列号永远在 [ 0 , 2 32 − 1 ] [0,2^{32}-1] [0,2321] 之间循环。

在实现TCP时,需要实现一些典型的 序列号比较 运算:

  • 检查 某个TCP 报文下的所有Byte均已确认并执行了对应操作(比如将这些Byte从重传队列中移除)
  • 检查 正在收到的数据片段(Segment)是否是预期的。(即:是否与接收窗口重合)
  • 检查 收到的 确认(Acknowledgement)是否是已经收到但是仍未被确认片段的确认。

1. 合法性检查

1.1 Acknowledgement 合法性

TCP 对于收到的数据会发送确认。

对于TCP 收到的确认,具体是通过以下机制来检查ACK是否合法:

定义

variabledescription
SND.UNAoldest unacknowledged sequence number
SND.NXT下一个即将要发送的序列号
SEG.ACK接收者发来的对下一个序列号的预期;也表示对收到序列号的确认
SEG.SEQ数据片段(Segment)的首个Byte的序列号
SEG.LEN数据片段包括SYN和FIN在内的Byte数,八进制

数据片段的最后一个Byte的序列号应该是 SEG.SEQ + SEG.LEN - 1

那么,对于收到的 SEG.ACK,其中期望的序列号范围应该是:

SND.UNA < SEG.ACK <= SND.NXT

假如说收到了 N + 1,则表示 N 及 N 之后的所有Byte均已正确收到,并期望从 N+1 开始。如果收到了 SND.NXT 的确认,则表示其之前的所有Byte均已经正确接受。并期望从 SND.NXT 开始。

这里的内容十分浓缩,把序列号和确认序列号概况起来了,不愧是官方文档哦🔬

如果说序列号在这个范围内,则就是合法的确认。

1.2 数据序列号合法性

由于语文功底有限,一些内容直接写原文,但是仍然会给出响应的解释

在接收到数据时,需要进行以下比较进行合法性验证

定义

variabledescription
RCV.NXTNext sequence number expected on an incoming segment 即:接收窗口的上边界。
RCV.NXT+RCV.WND-1Last sequence number expected on an incoming segment 即:接收窗口的下边界
SEG.SEQ数据片段的首个Byte的序列号 上边定义过了
SEG.SEQ+SEG.LEN-1数据片段的最后一个序列号,上边也定义过了

对于当前收到的数据片段,如果满足以下条件,则为合法的:

RCV.NXT =< SEG.SEQ < RCV.NXT + RCV.WND

并且

RCV.NXT =< SEG.SEQ + SEG.LEN-1 < RCV.NXT + RCV.WND

在原文档中,这两个条件是 or ,但是下面的描述的关系为 and 。这里暂时理解为 and 。所以翻译为了 “并且”

其实就是检查接收 数据的序列号范围是否是 接收窗口的真子集(在接收窗口内)。

实际上,真实情况复杂一点,因为有 长度为 0 的数据片段,和 长度为 0 的接受窗口。

有四种情况来检测 数据片段的 可接收性 (Acceptability)或者说,合法性:

Segment LengthReceive WindowTest
00SEG.SEQ = RCV.NXT
0>0RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND 因为长度为0,所以只检查片段的上边界即可
>00Not Acceptable (or invalidation)
>0>0按照上述两个条件

当接受窗口大小为0时,除了ACK,其他所有片段都是不接受的。因此TCP的实现有可能在发送数据 和 接收数据时保持0接收窗口。

但是,TCP仍然必须处理报文的RSTURG字段,即使接收窗口为0。

为了使得控制信息和实际的数据信息分离

原文其实没有说,这是我自己的理解

TCP 使用了一些 Control Flag 来进行传输控制。比如SYN 和 FIN,这两个Flag只在连接打开和关闭时使用。SYN报文的发送在第一个数据片段发送之前,SYN所携带的序列号和第一个数据片段的序列号一致。FIN 在最后一个数据片段之后发送。

2. 数据序列号初始化

TCP 连接由一对 Socket 定义,连接可以被复用。新的连接实例可以认为是旧实例的化身(Incarnation)。这里就有一个问题,TCP 如何识别 当前接收的报文是不是 以前连接上的报文,而不是新实例上的。

连接A上的报文因为某些原因,报文没有发完(报文还在路上),但是连接关闭了,新连接B复用了之前的连接,假如此时那些没有发送完的报文过来了,TCP 如何识别是不是新实例上的报文。

This problem becomes apparent if the connection is being opened and closed in quick succession or if the connection breaks with loss of memory and is then reestablished

TIME-WAIT 状态限制了连接的复用率,序列号的初始化选择限制了报文发送的随意性

为了避免上述的报文混淆,我们必须防止 来自旧的连接上的报文 被复用。网络中可能仍然存在以前使用的序列号。尽管 TCP 节点并不会记录之前一直使用的序列号,但我们仍然要确保ISN的随机性。

所以,在创建新链接时,会使用生成器重新生成一个新的32位初始序列号(ISN)。

如果第三方攻击者可以预测ISN值,那么就会有安全问题

2.1 时钟

TCP 的ISN 来自于 单调递增的数据序列,该序列号是一个32位计数器,或者大致称为 时钟 (clock),这个时钟既不是事实的,也不需要特别精确,它做的,只是每 4 微妙(microsecond)将序列递增一次,

也可能5微秒哦

时钟只是定义了序列增长的行为,而序列的初始值为:

Initial Sequence Number = M + F (local ip , local port, remote ip , remote port, secret key)

其中,M 是4微秒计时器, F (•) 是伪随机函数,F 必须不能从外部计算,不然攻击这可以从其他连接使用的ISN中猜出序列号。

有关选择特定散列算法和管理密钥数据的讨论 计算细节参考:RFC 6691

2.2 序列号同步 (3WHS

每个连接都会存在一个 发送序列号 和一个 接收序列号初始发送序列号(Initial send sequence ,ISS)由发送数据的一方选择,而初始接收序列号(Initial Receive Sequence Number, IRS) 由接收方通过连接建立时获得。

为了建立TCP连接,两个TCP对等体必须同步对方的 ISN 。具体由连接初始化阶段的 SYN 报文;报文的 SYN control flag 置位。(SYN报文只是简称,表示SYN置位的 Segment)。

同步要求每个TCP对等体发送自己的 ISN,并需要对端进行确认。同时需要接受对方的 ISN,并发送确认。

 	1) A --> B  SYN my sequence number is X
    2) A <-- B  ACK your sequence number is X
    
    3) A <-- B  SYN my sequence number is Y
    4) A --> B  ACK your sequence number is Y

因为第二步的报文和第三步的报文可以合并在一个报文中,因此

1) A --> B  SYN my sequence number is X

2) A <-- B  ACK your sequence number is X .
 			SYN my sequence number is Y
 			
3) A --> B  ACK your sequence number is Y

这个经常被称为三报文握手 (Three-way handshake and 3WHS for short)

这个东西可以说人尽皆知了

3WHS 是必须要的,因为序列号与网络中的全局时钟无关(所以对等体的时间一致性就用不上了),此外,TCP 的 ISN 初始化可能有不同的实现。接受者也无从得知 第一个 SYN 报文是不是从旧连接上来的(连接复用)。

2.3 Keep Quiet

理论上,存在这样一个问题:如果重复使用相同的端口号和序列空间,那么数据就有可能有损坏的风险。比如:TCP正常数据传输过程中,发送端突然重启了,然后发送端又以 相同的端口号和序列空间来发送新的报文,那么此时接收端的数据就会损坏。

接下来讨论的Quiet Time机制就解决了这个问题。

在现代网络中,这种情况并不常见,因此也不是必须实现的。这个问题最早出现在TCP刚被发布的那个年代。在当代网络环境下,这种情况完全可以忽略

这个问题基本可以被忽略的原因有三点:

  • ISS (Initial Send Sequence)和 短暂的(Ephemeral) 端口随机 机制已经打打减少了端口复用和序列号空间复用的概率。
  • 有效 报文存活时间(maximum segment lifetime, MSL) 随着网络链路速率的提升已经大大降低。
  • TCP 对等体重启所消耗的时间现在远大于 MSL。所以重启后,之前的报文已经非常有可能已经过期

为了确保 TCP 不会创建已有的序列号造成重复,TCP 端点在重新开始分配序列号之前,必须保持一个MSL静默时间(Keep Quiet)。

本规范(RFC 9293)将MSL定义为2分钟,这是工程上的选择,如果根据你的经验,发现它可以被更改,那么也是允许的。

如果你发现在TCP重新初始化之后仍在使用内存中保留的序列号,则需要重启TCP端点。


如果主机因为某种原因,它不知道每个活动连接上最后的序列号,则它必须要延迟一个MSL的时间发送接下来的首个报文。TCP 实现可以违反 “Keep Quiet” 限制,但前提是 旧的报文可以被当作新的报文使用而不会造成数据损坏,或是 接收方可以拒绝这些旧数据报文。

即:如果违反这个限制,则就会有数据损坏的风险

TCP 源端点在每次一个报文形成并进入传输队列时,都会消耗序列号资源。TCP 中的重复检测排序算法都依赖于报文数据与序列空间的唯一绑定(Byte-Sequence Number)。

在绑定到序列空间的数据被传递并被确认之前 以及 所有重复报文的副本被网络完全消耗之前,序列号不会循环使用全部的 2 32 2^{32} 232 个值。

正常情况下,TCP 实现需要跟踪下一个要发送报文的序列号,以及最老的仍未被确认报文的序列号,以避免在首次使用序列号之前错误地重复使用以有的序列号。但是仅靠这一点是不足以保证旧的序列号已经被网络完全消耗了(即:使用相同序列号的报文仍在在网络上存活)。因此序列号空间被设计地很大。

当今在 2MB/s 的速度下,需要4.5个小时才用完 2 32 2^{32} 232 大的序列号空间

即:理论上,在 2MB/s 下,序列号 N 到下一个相同序列号的产生之间有 4.5 个小时

而现在网络速率非常高的今天,网络报文的寿命一般不超过几十秒,所以在下一个相同序列号报文出现之前,上一个相同序列号的报文已经被消耗了。

如果所有TCP实现都从序列号0开始建立连接,那么在主机重启时(又从0开始),就会产生重复的数据包。所以,在不知道特定连接上使用的序列号的情况下,TCP规范建议源延迟MSL秒后再在连接上发送数据片段。以便让之前的报文从系统中消耗。

在重启恢复后故意延迟一个 MSL 时间–这就是 "静默时间 "规范。如果主机希望避免等待,并愿意冒在特定目的地混淆新旧数据包的风险,则可以选择不等待 “静默时间”。实施者可以为 TCP 用户提供逐个连接选择是否在重启后等待的功能,也可以非正式地为所有连接实施 “静默时间” ,如果主机重启消耗的时间不小于 MSL ,则这个机制是不必要的。

总得来说,Keep Quiet 的应用场景就是为了防止TCP端点在重启后使用的序列号与之前的 仍在存活的报文的序列号重复。

但是在更高性能的场景下,比如 1Gbps 循环周期仅为 34s , 10 Gbps 为 3 秒,而 100Gps 仅为 0.3333秒,这些场景下,TCP时间戳选项和 Protection Against Wrapped Sequences(PAWS) 提供了重复数据检测的能力。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天晴丶SnowCrystal

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值