编写一个TCP/IP栈3-TCP基础与握手:修订间差异

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


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


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


本文将深入探讨TCP背后的基本理论,并尝试为其设计提供动力。此外,我们将查看TCP头部并讨论建立连接(TCP握手)。作为最后一步,我们将展示TCP在网络栈中的首个功能。
本文将深入探讨TCP背后的基本理论,并尝试为其设计提供动力。此外,我们将查看TCP头部并讨论建立连接(TCP握手)。作为最后一步,我们将展示TCP在网络栈中的首个功能。
第16行: 第16行:
在所有情况下,数据包交换网络的潜在危险都适用——接收方的确认可能会被损坏,甚至在传输过程中丢失,从而使发送方陷入棘手的境地。
在所有情况下,数据包交换网络的潜在危险都适用——接收方的确认可能会被损坏,甚至在传输过程中丢失,从而使发送方陷入棘手的境地。


为解决这些问题,可以使用多种机制。''sliding window''最常见的可能是滑动窗口技术,即双方对传输的数据进行记录。窗口数据被视为顺序(如数组的切片),该窗口在数据被双方处理(并确认)时向前滑动:
为解决这些问题,可以使用多种机制。最常见的就是滑动窗口技术,即双方对传输的数据进行记录。窗口数据被视为顺序(如数组的切片),该窗口在数据被双方处理(并确认)时向前滑动:
[[文件:屏幕截图 2025-12-25 222529.png|居中|缩略图|630x630像素]]


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


''Congestion control''另一方面,充血控制有助于发送方和接收方之间的网络堆栈不受拥堵影响。有两种通用方法:在显式版本中,协议有一个字段,用于向发送方明确告知拥塞状态。在隐式版本中,发送方会尝试猜测网络何时出现拥堵,并限制其输出。总体而言,拥塞控制是一个复杂且反复出现的网络问题,<sup>相关研究至今仍在进行。</sup>
使用这种滑动窗口的便利之处在于,它能缓解流量控制。当接收端无法以发送速度快速处理数据时,需要进行流量控制。在这种情况下,滑动窗口的大小将被协商以降低,从而限制发送方的字节流输出。
 
拥塞控制有助于发送方和接收方之间的网络堆栈不受拥堵影响。有两种通用方法:在显式版本中,协议有一个字段,用于向发送方明确告知拥塞状态。在隐式版本中,发送方会尝试猜测网络何时出现拥堵,并限制其输出。总体而言,拥塞控制是一个复杂且反复出现的网络问题,相关研究至今仍在进行。


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


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


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


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


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


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


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


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


TCP 头部为 20 个 <sup>5</sup> 号
TCP 报头大小为 20 字节:
[[文件:屏幕截图 2025-12-25 223342.png|居中|缩略图|558x558像素]]


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


由于流中的每个字节都已编号,''Sequence Number''序列号表示TCP段的窗口索引。握手时,''包含初始序列号(''ISN)。
''源端口''''目标端口''字段用于建立与主机之间的多个连接。具体来说,伯克利套接字是应用程序绑定到 TCP 网络协议栈的常用接口。网络协议栈通过端口来确定流量的路由。由于这些字段为 16 位,因此端口值的范围为 0 到 65535。


''Acknowledgment Number''已知号包含发送方期望接收的下一个字节的窗口索引。握手后,必须始终驻足处理ACK场地。
由于数据流中的每个字节都已编号,因此''序列号''代表 TCP 段的窗口索引。在握手过程中,该序列号包含''初始序列号'' (ISN)。


''头长''(HL)字段以32位字表示头长。
''确认号''包含发送方预期接收的下一个字节的窗口索引。握手完成后,ACK 字段必须始终填充。


接着,会显示几面旗帜。前4位(''rsvd'')未使用。
''头部长度'' (HL) 字段以 32 位表示头部的长度。


# ''降压窗口''(C)用于告知发送方降低发送速率。
接下来,会显示几个标志位。前 4 位( ''rsvd'' )未使用。
# ''ECN Echo''(E)通知发送者已收到交通拥堵通知。
# ''紧急指针''(U)表示该细分部分包含优先数据。
# ''ACK''(A)字段用于传达TCP握手的状态。其余连接时仍会保持。
# ''PSH''PSH(P)用于表示接收方应尽快将数据推送到应用程序。
# ''RST''(R) 重置 TCP 连接。
# ''SYN''(S)用于在初始握手中同步序列号。
# ''FIN''(F) 表示发送方已发送数据。


''Window Size''窗口大小字段用于宣传窗口尺寸。换句话说,这是接收方愿意接受的字节数。由于它是一个16位字段,最大窗口大小为65,535字节。
# ''拥塞窗口减少'' (C) 用于通知发送方降低发送速率。
# ''ECN 回显'' (E) 表示发送方已收到拥塞通知。
# ''紧急指针'' (U) 表示该段包含优先数据。
# ''ACK'' (A)字段用于传递 TCP 握手的状态。在连接的剩余时间内,它将一直保持开启状态。
# ''PSH'' (P)用于指示接收方应尽快将数据“推送”到应用程序。
# ''RST'' (R)重置 TCP 连接。
# ''SYN'' (S) 用于在初始握手中同步序列号。
# ''FIN'' (F)表示发送方已完成数据发送。


''TCP Checksum'' 字段用于验证 TCP 段的完整性。该算法与互联网协议相同,但输入段也包含TCP数据,以及IP数据报的伪头。
''窗口大小''字段用于指定窗口大小。换句话说,它表示接收方愿意接收的字节数。由于这是一个 16 位字段,因此最大窗口大小为 65,535 字节。


''Urgent Pointer''紧急指针在设置U旗时使用。指针表示流中紧急数据的位置。
''TCP 校验和''字段用于验证 TCP 数据段的完整性。其算法与互联网协议相同,但输入数据段除了包含 TCP 数据外,还包含来自 IP 数据报的伪头部。


头球后,可以提供多种选项。''这些选项的一个示例是最大分段大小''(MSS),其中发送方向另一端的片段最大尺寸提供信息。
当设置了 U 标志时,会使用''紧急指针'' 。该指针指示数据流中紧急数据的位置。


在可能的选项之后,实际数据随之而来。然而,这些数据并非必需。例如,仅使用TCP头完成握手。
在邮件头之后,可以提供多个选项。例如, ''最大段大小'' (MSS) 就是其中之一,发送方可以通过它告知接收方数据段的最大长度。
 
在列出所有可选方案之后,才是实际数据。然而,这些数据并非必需。例如,握手过程仅需 TCP 头部即可完成。


= TCP握手 =
= TCP握手 =
TCP连接通常经历以下阶段:连接设置(握手)、数据传输以及连接的关闭。以下图表描述了TCP的惯常握手习惯:
TCP 连接通常经历以下几个阶段:连接建立(握手)、数据传输和连接关闭。下图展示了 TCP 的典型握手过程:
[[文件:屏幕截图 2025-12-25 224135.png|居中|缩略图|551x551像素]]


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


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


# 初始序列号是如何选择的?
# 初始序列号是如何选择的?
# 如果双方同时要求彼此连接会怎样?
# 如果双方同时向对方提出连接请求呢?
# 如果片段被延迟一段时间或无限期地推迟了怎么办?
# 如果某些环节延迟一段时间甚至无限期延迟怎么办?


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


原始规范表明,ISN 是由一个计数器选择的,该计数器每4微秒递增一次。然而,攻击者可以猜测这一点。事实上,现代网络栈通过更复杂的方法生成ISN。
The original specification suggests that the ISN is chosen by a counter that increments every 4 microseconds. This, however, can be guessed by an attacker. In reality, modern networking stacks generate the ISN by more complicated methods.


两个端点相互接收连接请求(SYN)''Simultaneous Open''的情况称为同步开放。通过TCP握手中的额外消息交换来解决:双方发送ACK(但不知道对方也做到了),双方SYN-ACK请求均未确定。此后,数据传输开始。
原始规范表明,ISN 由一个每 4 微秒递增一次的计数器生成。然而,攻击者可以猜到这一点。实际上,现代网络协议栈采用更复杂的方法生成 ISN。


最后,TCP 实现必须有一个计时器,以便知道何时放弃建立连接。试图重新建立连接,通常具有指数级的回退,但一旦达到最高重试或时间阈值,该连接就被视为不存在。
当两端都收到对方的连接请求(SYN)时,这种情况称为''同时打开'' 。TCP 握手过程中会通过额外的消息交换来解决这个问题:双方都发送一个 ACK(彼此并不知道对方是否也发送了 ACK),然后双方都发送 SYN-ACK 来确认请求。之后,数据传输开始。
 
最后,TCP 实现必须包含一个计时器,用于判断何时放弃建立连接。系统会尝试重新建立连接,通常采用指数退避算法,但一旦达到最大重试次数或时间阈值,连接就会被视为已断开。


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


''最大分段大小''(MSS)选项可告知TCP实现愿意接收的最大TCP段大小。IPv4 中的典型值为 1460 字节。
''最大段大小'' (MSS) 选项指定 TCP 实现能够接收的最大 TCP 段大小。在 IPv4 中,该值的典型值为 1460 字节。


''选择性识别''(SACK)选项可优化传输过程中丢失许多数据包,并在接收机的数据窗口中填充“漏洞”的情况。为了修复导致的吞吐量下降,TCP 实现可以向发送方告知未使用 SACK 接收到的特定数据包。因此,发送者比累积的已知方案更直接地接收到有关数据状态的信息。
''选择性确认'' (SACK) 选项优化了传输过程中大量数据包丢失、接收端数据窗口出现“空洞”的情况。为了弥补由此导致的吞吐量下降,TCP 实现可以通过 SACK 通知发送方哪些数据包未收到。因此,与累积确认机制相比,发送方能够以更直接的方式获取数据状态信息。


''Window Scale''窗口缩放选项可增加有限的16位窗口大小。即,如果双方在握手部分包含此选项,则窗口大小会随此量表倍增加。更大的窗户尺寸对批量数据传输主要很重要。
''窗口缩放''选项可以增大原本受限的 16 位窗口大小。具体来说,如果双方都在握手段中包含此选项,则窗口大小将乘以该缩放比例。更大的窗口大小对于大批量数据传输尤为重要。


''Timestamps''时间戳选项允许发送者将时间戳设置为TCP段,然后用于计算每个ACK段的RTT。此信息可用于计算TCP重传超时。
''时间戳''选项允许发送方在 TCP 数据段中插入时间戳,该时间戳可用于计算每个 ACK​​ 数据段的往返时间 (RTT)。此信息随后可用于计算 TCP 重传超时时间。


= 测试TCP握手 =
= 测试TCP握手 =
现在我们已经模拟了TCP握手程序,并且它有效地监听了每个端口,接下来我们来测试一下:
现在我们已经模拟了TCP握手程序,并且它有效地监听了每个端口,接下来我们来测试一下:
[[文件:屏幕截图 2025-12-25 225216.png|居中|缩略图|627x627像素]]
由于nmap进行SYN扫描(它只等待 SYN-ACK 来判断目标端口是否打开),因此很容易误以为我们只需返回一个SYN-ACK TCP段即可在端口上监听应用程序。
= 关键实现 =
<syntaxhighlight lang="c" line="1">
struct net_ops tcp_ops = {
    .alloc_sock = &tcp_alloc_sock,
    .init = &tcp_v4_init_sock,
    .connect = &tcp_v4_connect,
    .disconnect = &tcp_disconnect,
    .write = &tcp_write,
    .read = &tcp_read,
    .recv_notify = &tcp_recv_notify,
    .close = &tcp_close,
    .abort = &tcp_abort,
};
void tcp_init()
{
}
static void tcp_init_segment(struct tcphdr *th, struct iphdr *ih, struct sk_buff *skb)
{
    th->sport = ntohs(th->sport);
    th->dport = ntohs(th->dport);
    th->seq = ntohl(th->seq);
    th->ack_seq = ntohl(th->ack_seq);
    th->win = ntohs(th->win);
    th->csum = ntohs(th->csum);
    th->urp = ntohs(th->urp);
    skb->seq = th->seq;
    skb->dlen = ip_len(ih) - tcp_hlen(th);
    skb->len = skb->dlen + th->syn + th->fin;
    skb->end_seq = skb->seq + skb->dlen;
    skb->payload = th->data;
}
static void tcp_clear_queues(struct tcp_sock *tsk) {
    skb_queue_free(&tsk->ofo_queue);
}
void tcp_in(struct sk_buff *skb)
{
    struct sock *sk;
    struct iphdr *iph;
    struct tcphdr *th;
    iph = ip_hdr(skb);
    th = (struct tcphdr*) iph->data;
    tcp_init_segment(th, iph, skb);
   
    sk = inet_lookup(skb, th->sport, th->dport);
    if (sk == NULL) {
        print_err("No TCP socket for sport %d dport %d\n",
                  th->sport, th->dport);
        free_skb(skb);
        return;
    }
    socket_wr_acquire(sk->sock);
    tcp_in_dbg(th, sk, skb);
    /* if (tcp_checksum(iph, th) != 0) { */
    /*    goto discard; */
    /* } */
    tcp_input_state(sk, th, skb);
    socket_release(sk->sock);
}
int tcp_udp_checksum(uint32_t saddr, uint32_t daddr, uint8_t proto,
                    uint8_t *data, uint16_t len)
{
    uint32_t sum = 0;
    sum += saddr;
    sum += daddr;
    sum += htons(proto);
    sum += htons(len);
   
    return checksum(data, len, sum);
}
int tcp_v4_checksum(struct sk_buff *skb, uint32_t saddr, uint32_t daddr)
{
    return tcp_udp_checksum(saddr, daddr, IP_TCP, skb->data, skb->len);
}
struct sock *tcp_alloc_sock()
{
    struct tcp_sock *tsk = malloc(sizeof(struct tcp_sock));
    memset(tsk, 0, sizeof(struct tcp_sock));
    tsk->sk.state = TCP_CLOSE;
    tsk->sackok = 1;
   
    tsk->rmss = 1460;
    // Default to 536 as per spec
    tsk->smss = 536;
    skb_queue_init(&tsk->ofo_queue);
   
    return (struct sock *)tsk;
}
int tcp_v4_init_sock(struct sock *sk)
{
    tcp_init_sock(sk);
    return 0;
}
int tcp_init_sock(struct sock *sk)
{
    return 0;
}
void __tcp_set_state(struct sock *sk, uint32_t state)
{
    sk->state = state;
}
static uint16_t generate_port()
{
    /* TODO: Generate a proper port */
    static int port = 40000;
    pthread_rwlock_wrlock(&tcplock);
    int copy =  ++port + (timer_get_tick() % 10000);
    pthread_rwlock_unlock(&tcplock);
    return copy;
}
int generate_iss()
{
    /* TODO: Generate a proper ISS */
    return (int)time(NULL) * rand();
}
int tcp_v4_connect(struct sock *sk, const struct sockaddr *addr, int addrlen, int flags)
{
    uint16_t dport = ((struct sockaddr_in *)addr)->sin_port;
    uint32_t daddr = ((struct sockaddr_in *)addr)->sin_addr.s_addr;
    sk->dport = ntohs(dport);
    sk->sport = generate_port();
    sk->daddr = ntohl(daddr);
    /* TODO: Do not hardcode lvl-ip local interface */
    sk->saddr = parse_ipv4_string("10.0.0.4");
    return tcp_connect(sk);
}
int tcp_disconnect(struct sock *sk, int flags)
{
    return 0;
}
int tcp_write(struct sock *sk, const void *buf, int len)
{
    struct tcp_sock *tsk = tcp_sk(sk);
    int ret = sk->err;
    if (ret != 0) goto out;
    switch (sk->state) {
    case TCP_ESTABLISHED:
    case TCP_CLOSE_WAIT:
        break;
    default:
        ret = -EBADF;
        goto out;
    }
    return tcp_send(tsk, buf, len);   
out:
    return ret;
}
int tcp_read(struct sock *sk, void *buf, int len)
{
    struct tcp_sock *tsk = tcp_sk(sk);
    int ret = -1;
    switch (sk->state) {
    case TCP_CLOSE:
        ret = -EBADF;
        goto out;
    case TCP_LISTEN:
    case TCP_SYN_SENT:
    case TCP_SYN_RECEIVED:
        /* Queue for processing after entering ESTABLISHED state.  If there
          is no room to queue this request, respond with "error:
          insufficient resources". */
    case TCP_ESTABLISHED:
    case TCP_FIN_WAIT_1:
    case TCP_FIN_WAIT_2:
        /* If insufficient incoming segments are queued to satisfy the
          request, queue the request. */
       
        break;
    case TCP_CLOSE_WAIT:
        /* If no text is awaiting delivery, the RECEIVE will get a
          "error:  connection closing" response.  Otherwise, any remaining
          text can be used to satisfy the RECEIVE. */
        if (!skb_queue_empty(&tsk->sk.receive_queue)) break;
        if (tsk->flags & TCP_FIN) {
            tsk->flags &= ~TCP_FIN;
            return 0;
        }
        break;
    case TCP_CLOSING:
    case TCP_LAST_ACK:
    case TCP_TIME_WAIT:
        ret = sk->err;
        goto out;
    default:
        goto out;
    }
    return tcp_receive(tsk, buf, len);   
out:
    return ret;
}
int tcp_recv_notify(struct sock *sk)
{
    if (&(sk->recv_wait)) {
        return wait_wakeup(&sk->recv_wait);
    }
    // No recv wait lock
    return -1;
}
int tcp_close(struct sock *sk)
{
    switch (sk->state) {
    case TCP_CLOSE:
    case TCP_CLOSING:
    case TCP_LAST_ACK:
    case TCP_TIME_WAIT:
    case TCP_FIN_WAIT_1:
    case TCP_FIN_WAIT_2:
        /* Respond with "error:  connection closing". */
        sk->err = -EBADF;
        return -1;
    case TCP_LISTEN:
    case TCP_SYN_SENT:
    case TCP_SYN_RECEIVED:
        return tcp_done(sk);
    case TCP_ESTABLISHED:
        /* Queue this until all preceding SENDs have been segmentized, then
          form a FIN segment and send it.  In any case, enter FIN-WAIT-1
          state. */
        tcp_set_state(sk, TCP_FIN_WAIT_1);
        tcp_queue_fin(sk);
        break;
    case TCP_CLOSE_WAIT:
        /* Queue this request until all preceding SENDs have been
          segmentized; then send a FIN segment, enter LAST_ACK state. */
        tcp_queue_fin(sk);
        break;
    default:
        print_err("Unknown TCP state for close\n");
        return -1;
    }
    return 0;
}
int tcp_abort(struct sock *sk)
{
    struct tcp_sock *tsk = tcp_sk(sk);
    tcp_send_reset(tsk);
    return tcp_done(sk);
}
static int tcp_free(struct sock *sk)
{
    struct tcp_sock *tsk = tcp_sk(sk);
    tcp_clear_timers(sk);
    tcp_clear_queues(tsk);
    wait_wakeup(&sk->sock->sleep);
    return 0;
}
int tcp_done(struct sock *sk)
{
    tcp_set_state(sk, TCP_CLOSING);
    tcp_free(sk);
    return socket_delete(sk->sock);
}
void tcp_clear_timers(struct sock *sk)
{
    struct tcp_sock *tsk = tcp_sk(sk);
    tcp_stop_rto_timer(tsk);
    tcp_stop_delack_timer(tsk);
    timer_cancel(tsk->keepalive);
    tsk->keepalive = NULL;
    timer_cancel(tsk->linger);
    tsk->linger = NULL;
}
void tcp_stop_rto_timer(struct tcp_sock *tsk)
{
    if (tsk) {
        timer_cancel(tsk->retransmit);
        tsk->retransmit = NULL;
        tsk->backoff = 0;
    }
}
void tcp_release_rto_timer(struct tcp_sock *tsk)
{
    if (tsk) {
        timer_release(tsk->retransmit);
        tsk->retransmit = NULL;
    }
}
void tcp_stop_delack_timer(struct tcp_sock *tsk)
{
    timer_cancel(tsk->delack);
    tsk->delack = NULL;
}
void tcp_release_delack_timer(struct tcp_sock *tsk)
{
    timer_release(tsk->delack);
    tsk->delack = NULL;
}
void tcp_handle_fin_state(struct sock *sk)
{
    switch (sk->state) {
    case TCP_CLOSE_WAIT:
        tcp_set_state(sk, TCP_LAST_ACK);
        break;
    case TCP_ESTABLISHED:
        tcp_set_state(sk, TCP_FIN_WAIT_1);
        break;
    }
}
static void *tcp_linger(void *arg)
{
    struct sock *sk = (struct sock *) arg;
    socket_wr_acquire(sk->sock);
    struct tcp_sock *tsk = tcp_sk(sk);
    tcpsock_dbg("TCP time-wait timeout, freeing TCB", sk);
    timer_cancel(tsk->linger);
    tsk->linger = NULL;
    tcp_done(sk);
    socket_release(sk->sock);
    return NULL;
}
static void *tcp_user_timeout(void *arg)
{
    struct sock *sk = (struct sock *) arg;
    socket_wr_acquire(sk->sock);
    struct tcp_sock *tsk = tcp_sk(sk);
    tcpsock_dbg("TCP user timeout, freeing TCB and aborting conn", sk);
    timer_cancel(tsk->linger);
    tsk->linger = NULL;
    tcp_abort(sk);
    socket_release(sk->sock);
   
    return NULL;
}
void tcp_enter_time_wait(struct sock *sk)
{
    struct tcp_sock *tsk = tcp_sk(sk);
    tcp_set_state(sk, TCP_TIME_WAIT);
    tcp_clear_timers(sk);
    /* RFC793 arbitrarily defines MSL to be 2 minutes */
    tsk->linger = timer_add(TCP_2MSL, &tcp_linger, sk);
}
void tcp_rearm_user_timeout(struct sock *sk)
{
    struct tcp_sock *tsk = tcp_sk(sk);
    if (sk->state == TCP_TIME_WAIT) return;
    timer_cancel(tsk->linger);
    /* RFC793 set user timeout */
    tsk->linger = timer_add(TCP_USER_TIMEOUT, &tcp_user_timeout, sk);
}
void tcp_rtt(struct tcp_sock *tsk)
{
    if (tsk->backoff > 0 || !tsk->retransmit) {
        // Karn's Algorithm: Don't measure retransmissions
        return;
    }
    int r = timer_get_tick() - (tsk->retransmit->expires - tsk->rto);
    if (r < 0) return;
    if (!tsk->srtt) {
        /* RFC6298 2.2 first measurement is made */
        tsk->srtt = r;
        tsk->rttvar = r / 2;
    } else {
        /* RFC6298 2.3 a subsequent measurement is made */
        double beta = 0.25;
        double alpha = 0.125;
        tsk->rttvar = (1 - beta) * tsk->rttvar + beta * abs(tsk->srtt - r);
        tsk->srtt = (1 - alpha) * tsk->srtt + alpha * r;
    }
    int k = 4 * tsk->rttvar;
    /* RFC6298 says RTO should be at least 1 second. Linux uses 200ms */
    if (k < 200) k = 200;
    tsk->rto = tsk->srtt + k;
}


由于nmap进行SYN扫描(仅等待SYN-ACK决定目标端口是否开放),因此很容易误以为我们只需返回一个SYN-ACK TCP段即可在端口上监听应用程序。
int tcp_calculate_sacks(struct tcp_sock *tsk)
{
    struct tcp_sack_block *sb = &tsk->sacks[tsk->sacklen];


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


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


此外,''sockets''通过套接字为应用程序提供绑定到TCP实现的方法。因此,我们将研究伯克利Socket API,看看是否可以针对应用程序进行模拟,从而实现我们自定义的TCP实现。
    list_for_each_safe(item, tmp, &tsk->ofo_queue.head) {
        next = list_entry(item, struct sk_buff, list);


该项目的源代码托管在 GitHub 上。
        if (sb->left == 0) {
            sb->left = next->seq;
            tsk->sacklen++;
        }
       
        if (sb->right == 0) sb->right = next->end_seq;
        else if (sb->right == next->seq) sb->right = next->end_seq;
        else {
            if (tsk->sacklen >= tsk->sacks_allowed) break;
           
            sb = &tsk->sacks[tsk->sacklen];
            sb->left = next->seq;
            sb->right = next->end_seq;
            tsk->sacklen++;
        }
    }
   
    return 0;
}
</syntaxhighlight>该项目的源代码托管在 GitHub 上。

2025年12月25日 (四) 14:57的版本

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

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

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

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

可靠性机制

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

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

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

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


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

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

TCP基础

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

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

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

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

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

TCP full-duplex是一个全双工系统,意味着流量可以双向同时流动。通信端必须将两个方向数据顺序保存在内存中。更具体地说,TCP 通过在发送的数据段中包含对反向流量的确认信息来减少流量占用。

从本质上讲,数据流的排序是 TCP 的主要原则。然而,保持数据流同步并非易事。

TCP 报头格式

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

TCP 报头大小为 20 字节:


源端口目标端口字段用于建立与主机之间的多个连接。具体来说,伯克利套接字是应用程序绑定到 TCP 网络协议栈的常用接口。网络协议栈通过端口来确定流量的路由。由于这些字段为 16 位,因此端口值的范围为 0 到 65535。

由于数据流中的每个字节都已编号,因此序列号代表 TCP 段的窗口索引。在握手过程中,该序列号包含初始序列号 (ISN)。

确认号包含发送方预期接收的下一个字节的窗口索引。握手完成后,ACK 字段必须始终填充。

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

接下来,会显示几个标志位。前 4 位( rsvd )未使用。

  1. 拥塞窗口减少 (C) 用于通知发送方降低发送速率。
  2. ECN 回显 (E) 表示发送方已收到拥塞通知。
  3. 紧急指针 (U) 表示该段包含优先数据。
  4. ACK (A)字段用于传递 TCP 握手的状态。在连接的剩余时间内,它将一直保持开启状态。
  5. PSH (P)用于指示接收方应尽快将数据“推送”到应用程序。
  6. RST (R)重置 TCP 连接。
  7. SYN (S) 用于在初始握手中同步序列号。
  8. FIN (F)表示发送方已完成数据发送。

窗口大小字段用于指定窗口大小。换句话说,它表示接收方愿意接收的字节数。由于这是一个 16 位字段,因此最大窗口大小为 65,535 字节。

TCP 校验和字段用于验证 TCP 数据段的完整性。其算法与互联网协议相同,但输入数据段除了包含 TCP 数据外,还包含来自 IP 数据报的伪头部。

当设置了 U 标志时,会使用紧急指针 。该指针指示数据流中紧急数据的位置。

在邮件头之后,可以提供多个选项。例如, 最大段大小 (MSS) 就是其中之一,发送方可以通过它告知接收方数据段的最大长度。

在列出所有可选方案之后,才是实际数据。然而,这些数据并非必需。例如,握手过程仅需 TCP 头部即可完成。

TCP握手

TCP 连接通常经历以下几个阶段:连接建立(握手)、数据传输和连接关闭。下图展示了 TCP 的典型握手过程:

  1. 主机 A 的套接字处于关闭状态,这意味着它不接受连接。相反,主机 B 绑定到特定端口的套接字正在监听新的连接。
  2. 主机 A 打算与主机 B 建立连接。因此,A 构造了一个 TCP 段,该段的 SYN 标志已设置,并且序列字段的值也已填充为 (100)。
  3. 主机 B 以一个设置了 SYN 和 ACK 字段的 TCP 段进行响应,并通过将 A 的序列号加 1 来确认(ACK=101)。同样,B 也生成一个序列号(300)。
  4. 三次握手以连接请求发起方 (A) 发送的确认 (ACK) 结束。确认字段反映了主机接下来期望从另一方收到的序列号。
  5. 数据开始流动,主要是因为双方都确认了对方的数据序号。

这是建立 TCP 连接的常见场景。然而,由此会产生几个问题:

  1. 初始序列号是如何选择的?
  2. 如果双方同时向对方提出连接请求呢?
  3. 如果某些环节延迟一段时间甚至无限期延迟怎么办?

初始序列号 (ISN) 由通信双方在首次连接时独立选择。由于它是识别连接的关键部分,因此必须选择尽可能唯一且不易猜测的序列号。事实上, TCP 序列号攻击 指的是攻击者可以复制 TCP 连接并有效地向目标发送数据,从而冒充可信主机的情况。

The original specification suggests that the ISN is chosen by a counter that increments every 4 microseconds. This, however, can be guessed by an attacker. In reality, modern networking stacks generate the ISN by more complicated methods.

原始规范表明,ISN 由一个每 4 微秒递增一次的计数器生成。然而,攻击者可以猜到这一点。实际上,现代网络协议栈采用更复杂的方法生成 ISN。

当两端都收到对方的连接请求(SYN)时,这种情况称为同时打开 。TCP 握手过程中会通过额外的消息交换来解决这个问题:双方都发送一个 ACK(彼此并不知道对方是否也发送了 ACK),然后双方都发送 SYN-ACK 来确认请求。之后,数据传输开始。

最后,TCP 实现必须包含一个计时器,用于判断何时放弃建立连接。系统会尝试重新建立连接,通常采用指数退避算法,但一旦达到最大重试次数或时间阈值,连接就会被视为已断开。

TCP 选项

TCP 报头段的最后一个字段保留用于存储 TCP 选项。最初的规范提供了三个选项,但后来的规范又增加了更多选项。接下来,我们将介绍最常用的选项。

最大段大小 (MSS) 选项指定 TCP 实现能够接收的最大 TCP 段大小。在 IPv4 中,该值的典型值为 1460 字节。

选择性确认 (SACK) 选项优化了传输过程中大量数据包丢失、接收端数据窗口出现“空洞”的情况。为了弥补由此导致的吞吐量下降,TCP 实现可以通过 SACK 通知发送方哪些数据包未收到。因此,与累积确认机制相比,发送方能够以更直接的方式获取数据状态信息。

窗口缩放选项可以增大原本受限的 16 位窗口大小。具体来说,如果双方都在握手段中包含此选项,则窗口大小将乘以该缩放比例。更大的窗口大小对于大批量数据传输尤为重要。

时间戳选项允许发送方在 TCP 数据段中插入时间戳,该时间戳可用于计算每个 ACK​​ 数据段的往返时间 (RTT)。此信息随后可用于计算 TCP 重传超时时间。

测试TCP握手

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


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

关键实现

struct net_ops tcp_ops = {
    .alloc_sock = &tcp_alloc_sock,
    .init = &tcp_v4_init_sock,
    .connect = &tcp_v4_connect,
    .disconnect = &tcp_disconnect,
    .write = &tcp_write,
    .read = &tcp_read,
    .recv_notify = &tcp_recv_notify,
    .close = &tcp_close,
    .abort = &tcp_abort,
};

void tcp_init()
{
}
static void tcp_init_segment(struct tcphdr *th, struct iphdr *ih, struct sk_buff *skb)
{
    th->sport = ntohs(th->sport);
    th->dport = ntohs(th->dport);
    th->seq = ntohl(th->seq);
    th->ack_seq = ntohl(th->ack_seq);
    th->win = ntohs(th->win);
    th->csum = ntohs(th->csum);
    th->urp = ntohs(th->urp);

    skb->seq = th->seq;
    skb->dlen = ip_len(ih) - tcp_hlen(th);
    skb->len = skb->dlen + th->syn + th->fin;
    skb->end_seq = skb->seq + skb->dlen;
    skb->payload = th->data;
}

static void tcp_clear_queues(struct tcp_sock *tsk) {
    skb_queue_free(&tsk->ofo_queue);
}

void tcp_in(struct sk_buff *skb)
{
    struct sock *sk;
    struct iphdr *iph;
    struct tcphdr *th;

    iph = ip_hdr(skb);
    th = (struct tcphdr*) iph->data;

    tcp_init_segment(th, iph, skb);
    
    sk = inet_lookup(skb, th->sport, th->dport);

    if (sk == NULL) {
        print_err("No TCP socket for sport %d dport %d\n",
                  th->sport, th->dport);
        free_skb(skb);
        return;
    }
    socket_wr_acquire(sk->sock);

    tcp_in_dbg(th, sk, skb);
    /* if (tcp_checksum(iph, th) != 0) { */
    /*     goto discard; */
    /* } */
    tcp_input_state(sk, th, skb);

    socket_release(sk->sock);
}

int tcp_udp_checksum(uint32_t saddr, uint32_t daddr, uint8_t proto,
                     uint8_t *data, uint16_t len)
{
    uint32_t sum = 0;

    sum += saddr;
    sum += daddr;
    sum += htons(proto);
    sum += htons(len);
    
    return checksum(data, len, sum);
}

int tcp_v4_checksum(struct sk_buff *skb, uint32_t saddr, uint32_t daddr)
{
    return tcp_udp_checksum(saddr, daddr, IP_TCP, skb->data, skb->len);
}

struct sock *tcp_alloc_sock()
{
    struct tcp_sock *tsk = malloc(sizeof(struct tcp_sock));

    memset(tsk, 0, sizeof(struct tcp_sock));
    tsk->sk.state = TCP_CLOSE;
    tsk->sackok = 1;
    
    tsk->rmss = 1460;
    // Default to 536 as per spec
    tsk->smss = 536;

    skb_queue_init(&tsk->ofo_queue);
    
    return (struct sock *)tsk;
}

int tcp_v4_init_sock(struct sock *sk)
{
    tcp_init_sock(sk);
    return 0;
}

int tcp_init_sock(struct sock *sk)
{
    return 0;
}

void __tcp_set_state(struct sock *sk, uint32_t state)
{
    sk->state = state;
}

static uint16_t generate_port()
{
    /* TODO: Generate a proper port */
    static int port = 40000;

    pthread_rwlock_wrlock(&tcplock);
    int copy =  ++port + (timer_get_tick() % 10000);
    pthread_rwlock_unlock(&tcplock);

    return copy;
}

int generate_iss()
{
    /* TODO: Generate a proper ISS */
    return (int)time(NULL) * rand();
}

int tcp_v4_connect(struct sock *sk, const struct sockaddr *addr, int addrlen, int flags)
{
    uint16_t dport = ((struct sockaddr_in *)addr)->sin_port;
    uint32_t daddr = ((struct sockaddr_in *)addr)->sin_addr.s_addr;

    sk->dport = ntohs(dport);
    sk->sport = generate_port();
    sk->daddr = ntohl(daddr);
    /* TODO: Do not hardcode lvl-ip local interface */
    sk->saddr = parse_ipv4_string("10.0.0.4"); 

    return tcp_connect(sk);
}

int tcp_disconnect(struct sock *sk, int flags)
{
    return 0;
}

int tcp_write(struct sock *sk, const void *buf, int len)
{
    struct tcp_sock *tsk = tcp_sk(sk);
    int ret = sk->err;

    if (ret != 0) goto out;

    switch (sk->state) {
    case TCP_ESTABLISHED:
    case TCP_CLOSE_WAIT:
        break;
    default:
        ret = -EBADF;
        goto out;
    }

    return tcp_send(tsk, buf, len);    

out: 
    return ret;
}

int tcp_read(struct sock *sk, void *buf, int len)
{
    struct tcp_sock *tsk = tcp_sk(sk);
    int ret = -1;

    switch (sk->state) {
    case TCP_CLOSE:
        ret = -EBADF;
        goto out;
    case TCP_LISTEN:
    case TCP_SYN_SENT:
    case TCP_SYN_RECEIVED:
        /* Queue for processing after entering ESTABLISHED state.  If there
           is no room to queue this request, respond with "error:
           insufficient resources". */
    case TCP_ESTABLISHED:
    case TCP_FIN_WAIT_1:
    case TCP_FIN_WAIT_2:
        /* If insufficient incoming segments are queued to satisfy the
           request, queue the request. */
        
        break;
    case TCP_CLOSE_WAIT:
        /* If no text is awaiting delivery, the RECEIVE will get a
           "error:  connection closing" response.  Otherwise, any remaining
           text can be used to satisfy the RECEIVE. */
        if (!skb_queue_empty(&tsk->sk.receive_queue)) break;
        if (tsk->flags & TCP_FIN) {
            tsk->flags &= ~TCP_FIN;
            return 0;
        }

        break;
    case TCP_CLOSING:
    case TCP_LAST_ACK:
    case TCP_TIME_WAIT:
        ret = sk->err;
        goto out;
    default:
        goto out;
    }

    return tcp_receive(tsk, buf, len);    

out: 
    return ret;
}

int tcp_recv_notify(struct sock *sk)
{
    if (&(sk->recv_wait)) {
        return wait_wakeup(&sk->recv_wait);
    }

    // No recv wait lock
    return -1;
}

int tcp_close(struct sock *sk)
{
    switch (sk->state) {
    case TCP_CLOSE:
    case TCP_CLOSING:
    case TCP_LAST_ACK:
    case TCP_TIME_WAIT:
    case TCP_FIN_WAIT_1:
    case TCP_FIN_WAIT_2:
        /* Respond with "error:  connection closing". */
        sk->err = -EBADF;
        return -1;
    case TCP_LISTEN:
    case TCP_SYN_SENT:
    case TCP_SYN_RECEIVED:
        return tcp_done(sk);
    case TCP_ESTABLISHED:
        /* Queue this until all preceding SENDs have been segmentized, then
           form a FIN segment and send it.  In any case, enter FIN-WAIT-1
           state. */
        tcp_set_state(sk, TCP_FIN_WAIT_1);
        tcp_queue_fin(sk);
        break;
    case TCP_CLOSE_WAIT:
        /* Queue this request until all preceding SENDs have been
           segmentized; then send a FIN segment, enter LAST_ACK state. */
        tcp_queue_fin(sk);
        break;
    default:
        print_err("Unknown TCP state for close\n");
        return -1;
    }

    return 0;
}

int tcp_abort(struct sock *sk)
{
    struct tcp_sock *tsk = tcp_sk(sk);
    tcp_send_reset(tsk);
    return tcp_done(sk);
}

static int tcp_free(struct sock *sk)
{
    struct tcp_sock *tsk = tcp_sk(sk);

    tcp_clear_timers(sk);
    tcp_clear_queues(tsk);

    wait_wakeup(&sk->sock->sleep);

    return 0;
}

int tcp_done(struct sock *sk)
{
    tcp_set_state(sk, TCP_CLOSING);
    tcp_free(sk);
    return socket_delete(sk->sock);
}

void tcp_clear_timers(struct sock *sk)
{
    struct tcp_sock *tsk = tcp_sk(sk);
    tcp_stop_rto_timer(tsk);
    tcp_stop_delack_timer(tsk);

    timer_cancel(tsk->keepalive);
    tsk->keepalive = NULL;
    timer_cancel(tsk->linger);
    tsk->linger = NULL;
}

void tcp_stop_rto_timer(struct tcp_sock *tsk)
{
    if (tsk) {
        timer_cancel(tsk->retransmit);
        tsk->retransmit = NULL;
        tsk->backoff = 0;
    }
}

void tcp_release_rto_timer(struct tcp_sock *tsk)
{
    if (tsk) {
        timer_release(tsk->retransmit);
        tsk->retransmit = NULL;
    }
}

void tcp_stop_delack_timer(struct tcp_sock *tsk)
{
    timer_cancel(tsk->delack);
    tsk->delack = NULL;
}

void tcp_release_delack_timer(struct tcp_sock *tsk)
{
    timer_release(tsk->delack);
    tsk->delack = NULL;
}

void tcp_handle_fin_state(struct sock *sk)
{
    switch (sk->state) {
    case TCP_CLOSE_WAIT:
        tcp_set_state(sk, TCP_LAST_ACK);
        break;
    case TCP_ESTABLISHED:
        tcp_set_state(sk, TCP_FIN_WAIT_1);
        break;
    }
}

static void *tcp_linger(void *arg)
{
    struct sock *sk = (struct sock *) arg;
    socket_wr_acquire(sk->sock);
    struct tcp_sock *tsk = tcp_sk(sk);
    tcpsock_dbg("TCP time-wait timeout, freeing TCB", sk);

    timer_cancel(tsk->linger);
    tsk->linger = NULL;

    tcp_done(sk);
    socket_release(sk->sock);

    return NULL;
}

static void *tcp_user_timeout(void *arg)
{
    struct sock *sk = (struct sock *) arg;
    socket_wr_acquire(sk->sock);
    struct tcp_sock *tsk = tcp_sk(sk);
    tcpsock_dbg("TCP user timeout, freeing TCB and aborting conn", sk);

    timer_cancel(tsk->linger);
    tsk->linger = NULL;

    tcp_abort(sk);
    socket_release(sk->sock);
    
    return NULL;
}

void tcp_enter_time_wait(struct sock *sk)
{
    struct tcp_sock *tsk = tcp_sk(sk);

    tcp_set_state(sk, TCP_TIME_WAIT);

    tcp_clear_timers(sk);
    /* RFC793 arbitrarily defines MSL to be 2 minutes */
    tsk->linger = timer_add(TCP_2MSL, &tcp_linger, sk);
}

void tcp_rearm_user_timeout(struct sock *sk)
{
    struct tcp_sock *tsk = tcp_sk(sk);

    if (sk->state == TCP_TIME_WAIT) return;

    timer_cancel(tsk->linger);
    /* RFC793 set user timeout */
    tsk->linger = timer_add(TCP_USER_TIMEOUT, &tcp_user_timeout, sk);
}

void tcp_rtt(struct tcp_sock *tsk)
{
    if (tsk->backoff > 0 || !tsk->retransmit) {
        // Karn's Algorithm: Don't measure retransmissions
        return;
    }

    int r = timer_get_tick() - (tsk->retransmit->expires - tsk->rto);
    if (r < 0) return;

    if (!tsk->srtt) {
        /* RFC6298 2.2 first measurement is made */
        tsk->srtt = r;
        tsk->rttvar = r / 2;
    } else {
        /* RFC6298 2.3 a subsequent measurement is made */
        double beta = 0.25;
        double alpha = 0.125;
        tsk->rttvar = (1 - beta) * tsk->rttvar + beta * abs(tsk->srtt - r);
        tsk->srtt = (1 - alpha) * tsk->srtt + alpha * r;
    }

    int k = 4 * tsk->rttvar;

    /* RFC6298 says RTO should be at least 1 second. Linux uses 200ms */
    if (k < 200) k = 200;

    tsk->rto = tsk->srtt + k;
}

int tcp_calculate_sacks(struct tcp_sock *tsk)
{
    struct tcp_sack_block *sb = &tsk->sacks[tsk->sacklen];

    sb->left = 0;
    sb->right = 0;

    struct sk_buff *next;
    struct list_head *item, *tmp;

    list_for_each_safe(item, tmp, &tsk->ofo_queue.head) {
        next = list_entry(item, struct sk_buff, list);

        if (sb->left == 0) {
            sb->left = next->seq;
            tsk->sacklen++;
        }
        
        if (sb->right == 0) sb->right = next->end_seq;
        else if (sb->right == next->seq) sb->right = next->end_seq;
        else {
            if (tsk->sacklen >= tsk->sacks_allowed) break;
            
            sb = &tsk->sacks[tsk->sacklen];
            sb->left = next->seq;
            sb->right = next->end_seq;
            tsk->sacklen++;
        }
    }
    
    return 0;
}

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