TCP 连接管理

UDP 是一个无连接协议,相比之下 TCP 需要处理连接建立,正常终止,以及重新启动等更多连接上的细节问题。图例:

TCP 连接与终止◎ TCP 的连接与终止示意图·来源于网络

三次握手

  • 客户端发送一个 SYN 报文段,并指明想要连接的 IP 和端口号以及序列号,记为 ISN(c),一般还会发送一个或多个其他选项。
  • 服务器响应一个 SYN 报文段,包含其序列号,记为 ISN(s),为确认客户端的 SYN 服务器将 ISN(c) 加 1 作为 ACK 数值返回。每发送一个 SYN 序号加 1 ,丢失重传。
  • 为确认服务端的 SYN ,客户端将 ISN(s) 加 1 作为 ACK 的数值返回。、

四次挥手

  • 关闭发起方发送一个 FIN 段发送当前的序列号(K),以及一个 ACK 段用于确认对方最近一次发来的数据(L)。
  • 被动关闭者将 K 的数值加 1 作为 ACK 值响应。表明已确认 FIN 。此时上层的应用程序会收到对端已提出关闭请求的反馈。
  • 被动关闭方发送 FIN ,报文序号为 L。
  • 为了完成连接的关闭,最后发送的报文还包含一个 ACK 用于确认上一个 FIN。如果出现 FIN 丢失的情况,那么发送方重新传输直到收到一个 ACK 确认为止。

半关闭

这种特性很少被应用程序使用。

一端可以发起自身连接关闭请求(FIN),表明不会再向对端发送数据,但是可以接收来自对端的数据。当接收半关闭的一方发送完数据之后,发送 FIN 来关闭本方的连接,同时发出一个文件尾提示。最后这个 FIN 被确认后连接全部关闭。

同时打开与关闭

A 和 B 两个主机同时向对端(比如 A 主机通过 7777 端口向 B 8888 端口发送 AYN ,B 通过 8888 端口向 A 7777 端口发送 SYN)发送 SYN 。两个 SYN 分别被确认之后连接建立,所以这种情况下一个连接的建立经过四次握手。同时关闭连接和上文的四次挥手一致。

这种情况发生的也很少,防火墙打孔技术除外。

初始化序列号

  • SYN 建立之前都会选择一个序列号。32位的计数器,每4微妙加 1 。
  • 需要设法确保序列号不重叠否则可能会出现数据重复的情况,比如一个报文延时导致同一端口上的连接再次建立时才到达,此时需要通过 CRC 或者校验和确保在传输过程中没有出现任何错误。
  • 序列号以及端口等报文数据可以被伪造从而导致传输安全问题,linux 和 windows 都是通过使序列号变得不可猜测来保障相对的安全。

TCP 选项

种类 长度 名称 描述
0 1 EOL 选项列表结束
1 1 NOP 无操作,用于填充
2 4 MSS 最大段大小
3 3 WSOPT 窗口缩放因子,窗口左偏移量
4 2 SACK-Permitted 发送者支持SACK选项
5 可变 SACK SACK 阻塞,收到乱序数据
8 10 TSOPT 时间戳选项
28 4 UTO 用户超时,一段时间后的终止
29 可变 TCP-AO 认证选项,使用多种算法
253 可变 Experimental 保留供实验使用
254 可变 Experimental 保留供实验使用
  • MSS: 允许从对方接收到的最大数据段大小(不包括 TCP/IP头部),SYN 段指定。默认536字节,正好能组成 576 字节的最小IP数据报。ipv6中最大段大小的数值相应减少20个字节。发送 MSS 的一端表明不再接收超过此长度的数据报。
  • SACK:SYN 中发送允许选择确认选项后,SACK 就可以包含在任何数据报中,用以告知对端已经接收到的有效数据序号范围(分为好多块,最大为3块),对端重发数据。长度为(8n+2),增加的2个字节记录SACK的长度。
  • WSOPT:窗口缩放因子 s ,最大 14 。控制窗口字段左移 s 位($2^s$)。TCP 使用一个32位的值来维护这个窗口大小。最大窗口大小 $6535*2^s = 1GB$。
  • TSOPT:根据 ACK 估算往返时间以便进一步精确设置超时重传。同时为接收者提供了避免接收旧报文与判断旧报文段正确性的方法。被称为防回绕序列号
  • UTO:USER_TIMEOUT,一个比较新的选项。表明愿意等待 ACK 的最大时间,另一端不是一定要尊从。
    • 达到3次重传阈值时应该通知应用程序。
    • 超时大于100秒时应该关闭连接。
    • 长的等待时间设置会导致资源耗尽。
    • 短的超时时间设置会导致连接过早的断开。如拒绝服务攻击。
  • TCP-AO:TCP 认证选项,是一个比较新的选项。连接之前通信双方共享一套密钥,用来计算一个散列值以验证数据在传输过程中是否被篡改过。

路径的最大传输单元发现

传输路径中存在最大传输单元大小比收发双方的最大段小的设备时,TCP 会执行路径最大传输单元发送过程,以调整数据包大小。

TCP 状态机◎ TCP 状态机·图片涞源于网络

TIME_WAIT状态

  • 也称为 2MSL 等待状态。两倍于最大段生存期,有时被称作加倍等待。受 IP 协议 TTL 限制。
  • 严格的实施中处于 2MSL 状态的端口不可被再次使用。
  • 旨在等待重传的 FIN 以及最终确认的 ACK 。防止新的连接将上一个连接的延迟报文当作自身的合法报文。

一般而言,通常客户端执行关闭操作进入 TIME_WAIT 状态,被动关闭一端不直接进入 TIME_WAIT 状态。通常客户端使用操作系统分配的临时端口号,不存在 2MSL 等待问题。但是服务端关闭后立即以同一个端口启动会因为 2MSL 状态而无法启动。

静默等待时间

处于 2MSL状态的主机崩溃重启,并在同一端口启动连接将导致 2MSL 失去意义。此时建议的做法是 在连接开启前等待一个 MSL 的时间。但是这个设定一般来说没有实际意义,因为主机通常在 2MSL 等待时间后才会完成重启。上层应用程序如果采用了校验或者加密手段也很容易验证数据的正确性。

FIN_WAIT_2 状态

发送一个 FIN 并且得到确认,除去半关闭的情况下 该端会进入 FIN_WAIT_2 状态。等待接收对端的 FIN 和文件末尾通知并且回馈 ACK。此时正在关闭的 TCP 才会转移至 TIME_WAIT 状态。也就是说连接的一端能够永远保持这种状态,另一端也会依然处于 CLOSE_WAIT 状态,直到应用程序关闭它。一般的在非进入半关闭状态时关闭发起端设置一个计时器,在一个计时周期内连接空闲则转移至 CLOSDE 状态。

重置报文段

当发现一个到达的报文段对于相关连接而言是不正确的时,TCP 就会发送一个 RST 重置报文段。

  • 当端口未被侦听时发送 RST 重置报文段。一般客户端将会给出Connection refused的提示消息。
  • 通过发送一个 FIN 关闭连接,之前多有排队数据都已发送后才会被发送出去,称为有序释放
  • 任何时刻都可以通过发送一个重置报文来替代 FIN 终止一个连接,称为终止释放。强制关闭服务进程(ctrl+c)时就是这种情况。
    • 未发送数据都将被丢弃,并且一个重置报文段会被立即发送出去。
    • 重置报文段接收方会说明对端采用了终止的方式而不是正常关闭。类似连接被另一端重置的错误。
  • 半开连接:连接的一端非正常关闭,使另外一端认为连接仍然处于ESTABLISHED状态。非正常关闭一端重新连接时又建立一个新的连接,导致未检测到关闭一端仍然保留有半开连接(通常时服务器),可以使用keepalive选项发现另外一端已经消失。一些应用程序也实现了相应的心跳存活检查策略。客户端发生半开连接并且发送至服务端会引起服务端连接重置响应。
  • 时间等待错误:2MSL状态下收到重置报文等特殊数据,将会破坏 2MSL 状态。一般通过规定 TIME_WAIT 状态不对重置报文段做出响应来避免此问题。

TCP 服务器选项

查看连接:

1
netstat -nat
  • -a:报告所有处于侦听和未侦听状态的节点。
  • -n:以点分十进制(或十六进制)数打印 IP 地址。
  • -t:只选择 TCP 节点。

以上命令大致输出如下:

1
2
3
4
5
6
  协议   本地地址                外部地址                状态           卸载状态
  TCP    127.0.0.1:1313         0.0.0.0:0              LISTENING       InHost
  TCP    127.0.0.1:1313         127.0.0.1:52835        ESTABLISHED     InHost
  TCP    127.0.0.1:1313         127.0.0.1:52836        ESTABLISHED     InHost
  TCP    127.0.0.1:1313         127.0.0.1:52848        ESTABLISHED     InHost
  TCP    127.0.0.1:51105        0.0.0.0:0              LISTENING       InHost
  • 处于 LISTENING 状态的节点会独自运行,为服务器接收未来可能出现的请求。新的连接到达并被接收时,TCP 模块创建处于 ESTABLISHED 状态的新节点,端口号不变。
  • 处于 ESTABLISHED 的节点不能接收 SYN 报文,LISTENING 节点不能接收数据报文。

进入连接队列

在被用于应用程序的新连接可能会处于以下两种状态;

  • 尚未完成但是已经接收到 SYN 的处于 SRN_RCVD 状态。
  • 已经完成了三次握手处于 ESTABLISHED 状态。

在 linux 系统中适用以下规则:

  • SYN 报文段到达时检查 net.ipv4.tcp_max_syn_backlog (默认1000),如果处于 SRN_RCVD 状态的连接超过这个阈值,进入的连接将会被拒绝。
  • 每一个侦听节点都拥有一个固定长度的连接队列,其中的连接已经完成三次握手,但是尚未被应用程序接受。称为未完成连接(backlog)。backlog在 0 和 net.core.somaxconn (默认值 128)之间。应用程序通过可以限制这个队列的长度。backlog 不会对以建立连接的最大数目以及服务器能够处理的最大客户端数量造成影响。
  • 如果侦听节点的队列中仍有空间,此时如果超出应用程序处理能力连接将会完成三次握手并放入侦听节点队列。
  • 如果队列满了,将会延迟对 SYN 做出响应。linux 中坚持在能力允许范围内不忽略进入的连接,net.ipv4.tcp_abort_on_ 被设定,新进入的连接会被重置报文段重新置位。

与连接管理有关的攻击。

  • SYN 泛洪。
  • 序列号攻击。
  • 欺骗攻击。
  • 最大传输单元发现攻击。通过 ICMP 协议攻击。

TCP 在资源耗尽与欺骗攻击面前是很脆弱的,已经又一些方法来抵御相关问题。还可以与其他协议一起提供安全支持,比如 IPsec、TLS/SSL。

updatedupdated2020-05-182020-05-18
加载评论