编写一个TCP/IP栈3-TCP基础与握手

来自程序员技术小站
Xlong留言 | 贡献2025年12月24日 (三) 10:40的版本 (创建页面,内容为“如今我们的用户空间TCP/IP堆栈在以太网和IPv4上的实现已降至最低,现在是时候研究令人恐惧的传输控制协议(TCP)了。 <sup>1</sup>''transport''在第四组OSI网络1层、传输端操作时,TCP负责修复数据包递送中的错误连接和故障。事实上,TCP 是互联网的主力,如今几乎在所有计算机网络中都提供了可靠的通信服务。 TCP <sup>2</sup>并非全新的协议——第一个规范于1974…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳转到导航 跳转到搜索

如今我们的用户空间TCP/IP堆栈在以太网和IPv4上的实现已降至最低,现在是时候研究令人恐惧的传输控制协议(TCP)了。

1transport在第四组OSI网络1层、传输端操作时,TCP负责修复数据包递送中的错误连接和故障。事实上,TCP 是互联网的主力,如今几乎在所有计算机网络中都提供了可靠的通信服务。

TCP 2并非全新的协议——第一个规范于1974年问世。从那以后,很多事情都发生了变化,TCP 3也获得了许多扩展和修正3。

本文将深入探讨TCP背后的基本理论,并尝试为其设计提供动力。此外,我们将查看TCP头部并讨论建立连接(TCP握手)。作为最后一步,我们将展示TCP在网络栈中的首个功能。

可靠性机制

可靠地发送数据的问题看似肤浅,但其实际实现却涉及问题。主要涉及数据报式网络中的错误修复问题:

  • 发送方应等待接收方确认多长时间?
  • 如果接收方无法按发送速度处理数据怎么办?
  • 如果中间的网络(例如路由器)无法像发送数据时那样快速处理数据,该怎么办?

在所有情况下,数据包交换网络的潜在危险都适用——接收方的确认可能会被损坏,甚至在传输过程中丢失,从而使发送方陷入棘手的境地。

为解决这些问题,可以使用多种机制。sliding window最常见的可能是滑动窗口技术,即双方对传输的数据进行记录。窗口数据被视为顺序(如数组的切片),该窗口在数据被双方处理(并确认)时向前滑动:

使用这种滑动窗口的便利之处在于,flow control它还能缓解流量控制问题。当接收器无法以发送速度快速处理数据时,需要进行流量控制。在这种情况下,滑动窗口的大小将被协商以降低,从而从发送方获得节流输出。

Congestion control另一方面,充血控制有助于发送方和接收方之间的网络堆栈不受拥堵影响。有两种通用方法:在显式版本中,协议有一个字段,用于向发送方明确告知拥塞状态。在隐式版本中,发送方会尝试猜测网络何时出现拥堵,并限制其输出。总体而言,拥塞控制是一个复杂且反复出现的网络问题,相关研究至今仍在进行。

TCP基础

TCP 中的底层机制比其他协议(如 UDP 和 IP)要多得多。即TCP是一种面向连接的协议,这意味着作为第一步,在两边之间建立统一通信通道。双方正在积极维护这一联系:建立联系(握手),向对方告知数据状况及可能的问题。

streamingTCP的另一个重要特性是它是一种流式协议。与UDP不同,TCP 无法保证应用程序在发送和接收时能够稳定地“记录”数据。TCP 实现必须缓冲数据,当数据包丢失、重新排序或损坏时,TCP 必须等待并组织缓冲区中的数据。只有当数据被认定为完整时,TCP 才能将数据交给应用程序的套接字。

当TCP以数据流的形式运行时,来自该流的“块”必须转换为IP可以承载的数据包。这称为包化,其中TCP头块包含流中当前索引的序列号。这还具有方便的属性,即流可以分成许多可变大小的段,repacketize而TCP则知道如何重新集成它们。

与IP类似,TCP也会检查消息是否具有完整性。这是通过与IP相同的校验和算法实现的,但增加了更多细节。主要情况下,校验和是端到端的,这意味着头像和数据都包含在校验中。此外,还包含一个由IP头编造的伪头。

如果TCP实现接收损坏的片段,就会丢弃它们,并且不会通知发送方。此错误由发送方设置的定时器进行纠正,如果接收方从未承认该片段,则可用于重新传输片段。

TCP full-duplex也是一个全双工系统,意味着流量可以双向同时流动。这意味着通信端必须将数据顺序保持在内存的两个方向。更深入地说,TCP 通过在发送的细分领域中包含对相反流量的识别来保护其流量占用空间。

本质上,数据流的排序是TCP的主要原理。然而,保持同步的问题并不简单。

TCP 头像格式

接下来,我们将定义消息头并描述其字段。TCP 头似乎很简单,但包含了大量关于通信状态的信息。

TCP 头部为 20 个 5

Source PortDestination Port源端口和目的地端口字段用于建立来自主机和主机的多个连接。即,伯克利套接字是应用程序绑定到TCP网络栈的常见接口。通过端口,网络栈知道该将流量引导到哪里。由于字段大小为16位,因此端口值范围为0到65535。

由于流中的每个字节都已编号,Sequence Number序列号表示TCP段的窗口索引。握手时,包含初始序列号(ISN)。

Acknowledgment Number已知号包含发送方期望接收的下一个字节的窗口索引。握手后,必须始终驻足处理ACK场地。

头长(HL)字段以32位字表示头长。

接着,会显示几面旗帜。前4位(rsvd)未使用。

  1. 降压窗口(C)用于告知发送方降低发送速率。
  2. ECN Echo(E)通知发送者已收到交通拥堵通知。
  3. 紧急指针(U)表示该细分部分包含优先数据。
  4. ACK(A)字段用于传达TCP握手的状态。其余连接时仍会保持。
  5. PSHPSH(P)用于表示接收方应尽快将数据推送到应用程序。
  6. RST(R) 重置 TCP 连接。
  7. SYN(S)用于在初始握手中同步序列号。
  8. FIN(F) 表示发送方已发送数据。

Window Size窗口大小字段用于宣传窗口尺寸。换句话说,这是接收方愿意接受的字节数。由于它是一个16位字段,最大窗口大小为65,535字节。

TCP Checksum 字段用于验证 TCP 段的完整性。该算法与互联网协议相同,但输入段也包含TCP数据,以及IP数据报的伪头。

Urgent Pointer紧急指针在设置U旗时使用。指针表示流中紧急数据的位置。

头球后,可以提供多种选项。这些选项的一个示例是最大分段大小(MSS),其中发送方向另一端的片段最大尺寸提供信息。

在可能的选项之后,实际数据随之而来。然而,这些数据并非必需。例如,仅使用TCP头完成握手。

TCP握手

TCP连接通常经历以下阶段:连接设置(握手)、数据传输以及连接的关闭。以下图表描述了TCP的惯常握手习惯:

  1. 主机A的插座处于封闭状态,这意味着它不接受连接。相反,连接到特定端口的主机B的插座正在监听新的连接。
  2. 主机A计划与主机B发起连接。因此,A 会创建一个 TCP 段,其 SYN 标志集以及 Sequence 字段也填充了一个值(100)。
  3. 主机B使用具有SYN和ACK字段集的TCP段进行响应,并通过向其添加1(ACK=101)来确认A的序列号。同样,B 生成一个序列号(300)。
  4. 三方握手由连接请求的发起方(A)发送的ACK完成。确认字段反映了主机接下来期望从另一侧接收的序列号。
  5. 数据开始流动,主要是因为双方都承认了彼此的细分数据。

这是建立TCP连接的常见场景。然而,出现了几个问题:

  1. 初始序列号是如何选择的?
  2. 如果双方同时要求彼此连接会怎样?
  3. 如果片段被延迟一段时间或无限期地推迟了怎么办?

初始序列号(ISN)由双方在初次联系时由双方独立选择。由于这是识别连接的关键部分,因此必须选择它,以便其最有可能独一无二且不易被轻易猜出。事实上,TCP Sequence Number Attack6TCP序列号攻击6是指攻击者能够复制TCP连接并有效地将数据馈送到目标,冒充可信主机的情况。

原始规范表明,ISN 是由一个计数器选择的,该计数器每4微秒递增一次。然而,攻击者可以猜测这一点。事实上,现代网络栈通过更复杂的方法生成ISN。

两个端点相互接收连接请求(SYN)Simultaneous Open的情况称为同步开放。通过TCP握手中的额外消息交换来解决:双方发送ACK(但不知道对方也做到了),双方SYN-ACK请求均未确定。此后,数据传输开始。

最后,TCP 实现必须有一个计时器,以便知道何时放弃建立连接。试图重新建立连接,通常具有指数级的回退,但一旦达到最高重试或时间阈值,该连接就被视为不存在。

TCP 选项

TCP 头端段的最后一个字段保留用于可能的 TCP 选项。原始规格提供了三种选择,但后续的规格又增加了更多。接下来,我们将介绍最常见的选项。

最大分段大小(MSS)选项可告知TCP实现愿意接收的最大TCP段大小。IPv4 中的典型值为 1460 字节。

选择性识别(SACK)选项可优化传输过程中丢失许多数据包,并在接收机的数据窗口中填充“漏洞”的情况。为了修复导致的吞吐量下降,TCP 实现可以向发送方告知未使用 SACK 接收到的特定数据包。因此,发送者比累积的已知方案更直接地接收到有关数据状态的信息。

Window Scale窗口缩放选项可增加有限的16位窗口大小。即,如果双方在握手部分包含此选项,则窗口大小会随此量表倍增加。更大的窗户尺寸对批量数据传输主要很重要。

Timestamps时间戳选项允许发送者将时间戳设置为TCP段,然后用于计算每个ACK段的RTT。此信息可用于计算TCP重传超时。

测试TCP握手

现在我们已经模拟了TCP握手程序,并且它有效地监听了每个端口,接下来我们来测试一下:

由于nmap进行SYN扫描(仅等待SYN-ACK决定目标端口是否开放),因此很容易误以为我们只需返回一个SYN-ACK TCP段即可在端口上监听应用程序。

结论

只需选择一个序列号、设置SYN-ACK标志并计算生成的TCP段的校验和,即可实现最小可行的TCP握手程序,这相对轻松。

下次,我们将探讨TCP最重要的责任:可靠的数据传输。管理流的窗口对于使用TCP传输数据至关重要,其逻辑可能会变得有些复杂。

此外,sockets通过套接字为应用程序提供绑定到TCP实现的方法。因此,我们将研究伯克利Socket API,看看是否可以针对应用程序进行模拟,从而实现我们自定义的TCP实现。

该项目的源代码托管在 GitHub 上。