网络运输层之(1)TCP连接管理

网络运输层之(1)TCP连接管理


Author: Once Day Date: 2024年10月22日

一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…

漫漫长路,有人对你微笑过嘛…

全系列文章可参考专栏: 通信网络技术_Once-Day的博客-CSDN博客

参考文章:


1. TCP连接管理
1.1 连接的建立与终止

TCP提供面向连接的可靠数据传输服务。它在传输层协议中占据核心地位。

(1) 建立连接(三次握手)

  1. 客户端发送SYN同步报文,随机生成一个初始序列号seq=x,标志位SYN=1,发送到服务器。
  2. 服务器接收到SYN报文后,回复SYN+ACK报文,确认号ack=x+1,同时也生成一个随机序列号seq=y,标志位SYN=1ACK=1
  3. 客户端收到服务器的SYN+ACK后,回复ACK确认报文,确认号ack=y+1,序列号seq=x+1,标志位ACK=1
  4. 双方都收到对方的ACK确认后,TCP连接就建立成功了。连接建立后,会形成一个唯一的会话四元组,即(源IP,源端口,目的IP,目的端口),通过四元组能够唯一标识一个TCP连接。

三次握手能够可靠地建立一个双向的TCP连接,客户端和服务器都能够确认自己和对方的发送与接收能力正常。避免了连接时的歧义和资源浪费。

(2) 数据传输

  1. TCP连接建立后,双方按照三次握手确定的初始序列号,开始传输数据。
  2. 发送方给每个传输的字节分配一个序列号seq。接收方收到数据后,发送ACK确认报文,确认号ack=seq+len
  3. 发送方收到接收方的ACK后,就确认数据传输成功,然后滑动发送窗口,用新的序列号继续发送后续数据。
  4. TCP利用序列号、确认应答、重传、流量控制、拥塞控制等机制,能够实现可靠的数据传输。

TCP的字节流服务、全双工通信以及滑动窗口协议,使得应用层看到的似乎是一个无差错、不会丢失、不会重复、保序的数据流。实际的网络物理链路却可能出现丢包、重复、错序等各种问题。TCP正是提供了复杂的机制来解决这些问题,从而呈现简单可靠的服务。

(3) 连接终止(四次挥手)

  1. 主动关闭方发送FIN结束报文,seq=m,标志位FIN=1,表示数据发送完毕,请求关闭连接。
  2. 被动关闭方收到FIN后,回复ACK确认,ack=m+1。此时主动方到被动方的单向连接就释放了。TCP连接进入半关闭状态。被动方仍可以继续发送数据。
  3. 被动方数据发送完毕后,也发送FIN结束报文,seq=n,标志位FIN=1
  4. 主动方收到FIN后,回复ACK确认,ack=n+1
  5. 主动方等待2MSL(最大报文段生存时间)后,没有再收到被动方的数据,就完全关闭TCP连接。被动方收到ACK后,就直接关闭连接。

四次挥手能够可靠地终止一个TCP连接。相比三次握手,多出的一次挥手是为了处理半关闭状态。这样每个方向上都能独立关闭连接,不会丢失数据。2MSL等待状态则是为了让迟到的数据包都消失,新连接不会混淆。

1.2 半关闭

TCP半关闭(TCP half-close)是指在一个TCP连接中,通信的一方关闭了数据的发送,但仍保持接收数据的能力。这种状态下的TCP连接称为半关闭连接。半关闭为TCP连接提供了更灵活的控制方式,特别适用于客户端和服务器需要相互独立地关闭发送通道的场景。

(1) 半关闭的原理

  • 全双工的TCP连接可以看作两个单工的信道:客户端到服务器、服务器到客户端
  • 当通信的一方发送FIN报文关闭其单工信道时,另一方的单工信道仍能继续发送数据。此时TCP连接进入半关闭状态。
  • 直到另一方也发送FIN关闭其单工信道,整个TCP连接才完全关闭。

(2) 半关闭的状态

  • 客户端关闭发送通道后,状态变为FIN_WAIT_1,收到服务器ACK后变为FIN_WAIT_2
  • 此时服务器状态变为CLOSE_WAIT,表示客户端已经关闭了其发送通道,但服务器仍可以继续发送数据。
  • 服务器完成其所有数据发送后,也发送FIN,状态变为LAST_ACK
  • 客户端收到服务器的FIN后,回复ACK,然后经过TIME_WAIT,最终完全关闭。

可见,半关闭引入了FIN_WAIT_2CLOSE_WAIT两个特殊的状态,使得连接双方能够有序地独立关闭各自的发送通道。避免了发送FIN的一方过早关闭连接,导致另一方的数据丢失。

(3) 编程接口中的半关闭

在伯克利套接字编程接口中,可以使用shutdown函数实现半关闭。int shutdown(int sockfd, int how)可以控制关闭TCP连接的某个单向数据流。其中how参数有三种取值:SHUT_RD关闭读通道、SHUT_WR关闭写通道、SHUT_RDWR同时关闭读写通道。

例如,在服务器端调用shutdown(sockfd, SHUT_WR)后,sockfd就只能接收数据,不能发送数据。服务器再调用read(sockfd)读取客户端发来的数据,直到读取到EOF。与close函数直接关闭整个连接不同,shutdown能关闭部分数据流,提供了半关闭的能力。

下面是一个简单的服务器端半关闭示例:

// 服务器关闭写通道,只保留读通道
shutdown(sockfd, SHUT_WR); 
// 继续从sockfd读取客户端数据,直到EOF
while((n = read(sockfd, buf, BUFSIZE)) > 0) {
    
    
    // 处理读取到的数据
}
// 关闭整个连接
close(sockfd); 
1.3 同时打开和关闭

TCP同时打开(TCP Simultaneous Open)和同时关闭(TCP Simultaneous Close)是TCP连接建立和终止过程中的两种特殊情况。它们分别对应于连接双方同时发起建立请求和关闭请求的场景。

TCP同时打开

  1. 同时打开发生于连接双方几乎同时发送SYN报文的情况。
  2. 客户端和服务器都主动打开连接,各自发送SYN报文,并进入SYN_SENT状态。
  3. 双方收到对方的SYN后,就将其视为SYN+ACK,同时转换到ESTABLISHED状态。
  4. 双方再各自回复一个ACK,确认收到对方的SYN
  5. 至此,TCP连接建立完成。整个过程实际上只有二次握手,但是有四个报文。

与三次握手的区别:

  • 角色对等:同时打开的双方都是主动发起方,而不分客户端和服务器。
  • 状态变化:同时打开少了一个SYN_RCVD状态,双方直接从SYN_SENTESTABLISHED
  • 握手次数:同时打开只有两次握手,比三次握手少一次。

在这里插入图片描述

TCP同时关闭

  1. 同时关闭发生于连接双方几乎同时发送FIN报文的情况。
  2. 此时双方的状态都从ESTABLISHED直接转换到CLOSING
  3. 双方都收到对方的FIN后,就将其视为FIN+ACK,同时转换到TIME_WAIT状态。
  4. 等待2MSL时间后,如果没有收到对方的数据,就关闭连接,转换到CLOSED状态。
  5. 整个过程只有二次挥手,没有出现半关闭状态。

与四次挥手的区别:

  • 状态变化:同时关闭没有半关闭状态如CLOSE_WAITLAST_ACK, 而是新增一个CLOSING状态。
  • 挥手次数:同时关闭只有两次挥手,比四次挥手少两次。
  • 连接终止:同时关闭的双方都要经过TIME_WAIT,而四次挥手只有主动方有TIME_WAIT

在这里插入图片描述

需要注意的是,虽然同时打开和关闭能够简化建立和终止过程,但它们出现的概率较低。大多数情况下,TCP连接还是由明确的客户端和服务器通过三次握手、四次挥手来建立和终止。三次握手和四次挥手更具典型性,同时打开和关闭反而是一种例外情况。

下面是同时打开和同时关闭的状态变化示意:

同时打开:  
CLOSED  ->  SYN_SENT  ->  ESTABLISHED

同时关闭:
ESTABLISHED -> CLOSING -> TIME_WAIT -> CLOSED
1.4 初始化序列号

TCP初始序列号(Initial Sequence Number, ISN)是TCP连接建立过程中非常重要的一个参数。它决定了后续数据传输的起始序列号,对于保证可靠传输至关重要。如果ISN选择不当,就可能引发连接劫持、数据注入等安全问题。因此,现代操作系统在选择ISN时都采用了一些随机化算法,以增强TCP连接的安全性和鲁棒性。

初始序列号的作用

  • 标识TCP连接:ISN是每个TCP连接的起始序列号,与IP地址和端口号一起唯一标识一个TCP连接。
  • 防止历史分节:随机化的ISN能避免新连接误用上一个相同四元组连接的历史分节。
  • 防止伪造分节:ISN的不可预测性增加了攻击者伪造合法分节的难度。

初始序列号的生成算法

  • 时钟算法:最简单的算法是用一个计时器从0开始计数,每4微秒加1。这个算法容易被猜测,安全性较差。
  • 基于时钟的随机增量算法:在时钟算法的基础上,每次加一个随机增量(如64000到128000之间)。安全性有所提高,但仍可能被统计分析。
  • RFC1948算法ISN = M + F(localhost, localport, remotehost, remoteport)。其中M是一个计时器,每4微秒加1;F是一个Hash算法,根据连接四元组生成一个随机值。这个算法在Linux、FreeBSD等系统中广泛使用。
  • 加密算法:使用安全的加密算法如MD5、SHA-1等,根据时钟、四元组、密钥等因素生成ISN。这种算法安全性最高,但计算开销也最大。

常见系统的实现

  • Linux内核使用了一个基于RFC1948的随机化算法,称为secure_tcp_sequence_number。它用一个密钥(随机生成,定期更换)、时钟、四元组等作为输入,用MD5算法生成一个32位的哈希值。再用这个哈希值异或一个时间相关的随机值,得到最终的ISN。

  • Windows的TCP/IP实现使用了一种称为"加密增强的伪随机数生成器"的算法。该算法使用了类似密钥的种子值、时钟计数器、四元组等作为输入,生成随机的ISN。对于每个连接,会刷新种子值,以进一步增强不可预测性。

  • FreeBSD的实现与Linux类似,也是基于RFC1948的一个变种。主要区别在于使用SHA-1算法代替了MD5,生成160位的哈希值。然后取其高32位与时钟计数器异或,得到最终的ISN。

下面是Linux内核中生成ISN的简化示意:

// 安全的TCP初始序列号生成
static u32 secure_tcp_sequence_number(u32 saddr, u32 daddr, u16 sport, u16 dport) {
    
    
    u32 hash[4];
    u32 sec, usec;
    // 获取时钟值
    get_clock(&sec, &usec); 
    // 用MD5计算四元组的哈希值
    md5_transform(hash, sec, saddr, sport, daddr, dport, secret_key); 
    // 高32位与时钟异或
    return seq = hash[0] ^ (saddr + daddr + sec + usec);
}

可见,通过加密算法、时钟、四元组等因素的综合运用,现代操作系统能够生成足够随机和安全的TCP初始序列号。这种随机化虽然会带来一定的计算开销,但相比其在安全性和可靠性方面的提升,这种代价是值得的。

1.5 连接超时

TCP连接超时是指在建立连接、传输数据、关闭连接等各个阶段,由于网络问题、对端崩溃等原因,导致一方迟迟等不到另一方的响应,最终触发超时机制的情况。针对不同的超时场景,TCP协议定义了不同的错误报文和状态,以通知应用程序采取相应的措施。

(1) 连接建立超时

  1. 客户端发送SYN后,等待服务器的SYN+ACK超时,称为SYN超时。
  2. 客户端通常会重发SYN,如果重试次数达到上限还没有收到响应,就放弃连接。
  3. 这种情况下,客户端会收到ETIMEDOUT错误,状态保持在CLOSED

(2) 数据传输超时

  1. 发送方发送数据后,等待接收方的ACK超时,称为RTO(Retransmission Timeout)超时。
  2. 发送方会重发数据,如果重试次数达到上限还没有收到ACK,就认为连接失效。
  3. 这种情况下,发送方会收到ETIMEDOUT错误,连接状态变为CLOSED
  4. 接收方会收到一个RST报文,将连接状态也变为CLOSED

(3) 连接关闭超时

  1. 主动关闭方发送FIN后,等待被动关闭方的ACK超时,称为FIN_WAIT_1超时。
  2. 主动关闭方会重发FIN,如果重试次数达到上限还没有收到ACK,就直接关闭连接。
  3. 这种情况下,主动关闭方会收到ETIMEDOUT错误,状态从FIN_WAIT_1直接变为CLOSED
  4. 被动关闭方会收到一个RST报文,将连接状态从CLOSE_WAIT变为CLOSED

(4) 长时间非活动超时

  1. 如果一个已建立的连接长时间没有数据传输,称为非活动超时。
  2. 这种超时通常由操作系统的TCP保活机制触发,默认时间为2小时。
  3. 保活机制会发送探测报文,如果多次探测都没有响应,就认为连接失效。
  4. 这种情况下,双方都会收到ETIMEDOUT错误,连接状态变为CLOSED。

(5) TCP重置报文

  1. 当一方发送的数据包对于另一方无法接受时,另一方会发送一个RST报文。
  2. RST报文表示强制重置连接,双方的状态都将直接变为CLOSED。
  3. 常见的触发RST的情况有:请求建立一个已存在的连接、请求一个不存在的端口、请求一个已关闭的连接等。
  4. 应用程序收到RST报文后,通常会得到ECONNRESET错误。

下面是一个TCP超时重传的简化示意:

发送方                      接收方
  |                          |
  | --> 数据seq=1000 ------>  |
  |                          |
  | <-------- 超时 -------->  |
  |                          |
  | --> 数据seq=1000 ------>  |
  |                          |
  | <-------- 超时 -------->  |
  |                          |
  | --> RST --------------> X|
  |                          |
2. TCP选项

下面是TCP常见选项的表格:

种类 长度 名称 RFC文档 描述
0 - End of Option List RFC 793 标志选项列表的结束,用于填充TCP首部的选项字段
1 - No-Operation RFC 793 用于选项之间的分隔和填充
2 4 Maximum Segment Size RFC 793, 879, 6691 指定TCP最大报文段长度(MSS),用于TCP连接建立时协商MSS
3 3 Window Scale RFC 7323 用于高带宽延迟网络下扩展TCP窗口大小,最大可扩展到30位
4 2 SACK Permitted RFC 2018 发起端支持并同意使用SACK选择确认机制
5 N SACK RFC 2018, 2883, 6675 用于选择确认机制,指明收到的不连续数据块
8 10 Timestamp RFC 7323 包含发送和回显时间戳,用于计算RTT和防止序列号回绕
14 3 TCP Alternate Checksum Algorithm RFC 1146 指定备用校验和算法,如8位Fletcher算法
15 N TCP Alternate Checksum Data RFC 1146 与种类(14)配合,携带备用校验和所需数据
19 18 MD5 Signature Option RFC 2385 携带MD5签名,用于增强TCP连接的安全性
28 4 User Timeout Option RFC 5482 指定用户超时时间,即应用程序等待数据的最长时间
29 N TCP Authentication Option RFC 5925 提供对TCP段的认证和完整性保护,防止DoS和MITM攻击
30 N Multipath TCP RFC 8684 实现多路径TCP,允许单个TCP连接利用多个网络路径
34 N TCP Fast Open RFC 7413 在SYN报文中携带数据,减少连接建立的RTT
254 N Experiment RFC 4727 用于TCP扩展选项的实验和测试
2.1 最大段大小选项

TCP的最大段大小(Maximum Segment Size, MSS)选项是TCP性能优化的重要手段之一。它允许通信双方在连接建立阶段协商数据段的最大长度,避免发送过大的数据段导致分片和重组,从而提高TCP传输效率。

MSS选项格式

  • MSS选项的Kind字段值为2,表示这是一个MSS选项。
  • MSS选项的Length字段值为4,表示该选项的总长度为4字节。
  • MSS选项的Value字段占2字节,表示发送方能够接收的最大TCP数据段长度。
  • MSS值不包括TCP首部和选项的长度,只针对TCP数据部分。
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Kind=2    |    Length=4   |           MSS Value           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

MSS选项作用

  • 发送方利用MSS告知接收方自己能够接收的最大数据段长度,避免接收到过大的数据段。
  • 接收方根据发送方的MSS设置和自身条件,选择一个合适的MSS值,控制发送数据段的大小。
  • 合理的MSS值能够减少分片和重组,提高网络利用率和吞吐量。
  • MSS值的选择需要综合考虑MTU、TCP首部大小、路径MTU等因素。

MSS选项交互流程:以下是客户端和服务器在TCP连接建立过程中协商MSS的典型交互流程:

  1. 客户端发送SYN报文,在TCP选项中携带自己的MSS值(如1460)。
Client -> Server: SYN, MSS=1460
  1. 服务器收到SYN后,选择一个不大于客户端MSS的值作为自己的MSS(如1400),并在SYN+ACK报文中回复该MSS值。
Server -> Client: SYN+ACK, MSS=1400
  1. 客户端收到服务器的MSS值后,调整自己的发送数据段大小,确保不超过1400字节。

  2. 服务器收到客户端的ACK后,也调整自己的发送数据段大小,确保不超过1460字节。

  3. 后续的数据传输中,双方就按照协商好的MSS值来发送数据段,避免了分片和重组。

下面是一个实际的MSS选项交互示例(在TCP首部中):

// 客户端发送的SYN报文
0x02 0x04 0x05 0xb4

// 服务器回复的SYN+ACK报文 
0x02 0x04 0x04 0x00

可以看出,客户端的MSS值为1460(0x05b4),服务器选择的MSS值为1024(0x0400),双方最终协商的MSS为1024。这个MSS值在后续的数据传输中会控制TCP数据段的最大长度,避免不必要的分片。

2.2 选择确认选项

TCP的选择确认(Selective Acknowledgment, SACK)是一种重要的丢包恢复机制。它允许接收方告知发送方具体收到了哪些数据段,使发送方能够针对性地重传丢失的数据段,提高了丢包恢复的效率。

(1) SACK选项格式:SACK选项由两个部分组成,SACK-Permitted选项和SACK选项。

  1. SACK-Permitted选项(Kind=4, Length=2)用于在SYN报文中协商是否启用SACK。
 0                   1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Kind=4    |    Length=2   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  1. SACK选项(Kind=5, Length=N)用于在ACK报文中告知发送方已接收到的不连续数据块。
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Kind=5    |    Length=N   |          Left Edge 1          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Right Edge 1          |          Left Edge 2          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Right Edge 2          |              ...              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

其中,Left Edge和Right Edge分别表示已接收数据块的左右边界(序列号),可以包含多个不连续的数据块。

(2) SACK工作原理

  1. 发送方在SYN报文中携带SACK-Permitted选项,表示自己支持SACK。
  2. 接收方如果也支持SACK,就在SYN+ACK报文中回复SACK-Permitted选项。
  3. 在数据传输过程中,如果接收方收到了不连续的数据段,就在ACK报文中使用SACK选项告知发送方已接收到的数据块。
  4. 发送方根据SACK信息,重传丢失的数据段,直到接收方确认所有数据都已收到。

(3) SACK应用示例

发送方                                    接收方
  |                                       |
  | --- 数据段1 (seq=1000) ----->          |
  | --- 数据段2 (seq=2000) ----->          |
  | --- 数据段3 (seq=3000) --X             |
  | --- 数据段4 (seq=4000) ----->          |
  |                                       |
  | <--- ACK 3000, SACK=[(4000,5000)] --- |
  |                                       |
  | --- 数据段3 (seq=3000) ----->          |
  |                                       |
  | <--- ACK 5000 ----------------        |
  |                                       |

在这个例子中,数据段3丢失,接收方在ACK中使用SACK告知发送方已收到数据段4。发送方重传数据段3后,接收方确认所有数据都已收到。可以看出,SACK机制避免了发送方盲目重传所有未确认数据的问题,提高了丢包恢复效率。

2.3 窗口缩放选项

TCP的窗口缩放(Window Scale)选项是为了解决TCP窗口大小限制而引入的一种机制。它允许通信双方协商一个缩放因子,将窗口大小扩展到原来的65535字节以上,从而提高了高带宽、高延迟网络下的TCP传输性能。

(1) 引入背景

  • TCP首部中的窗口大小字段长度为16位,最大值为65535字节。
  • 在高带宽、高延迟网络(如卫星链路)下,65535字节的窗口大小远不够填满带宽时延积(BDP)。
  • 窗口过小会导致发送方频繁地等待接收方的确认,降低了传输效率。
  • 为了突破65535字节的限制,需要引入一种窗口缩放机制。

(2) 窗口缩放选项格式:窗口缩放选项(Kind=3, Length=3)用于在SYN报文中协商缩放因子。

 0                   1                   2
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Kind=3    |    Length=3   |  Shift Count  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

其中,Shift Count表示缩放因子,取值范围为0~14。缩放因子用于将窗口大小左移,扩大窗口。

(3) 窗口缩放工作原理

  1. 发送方在SYN报文中携带窗口缩放选项,指定一个缩放因子(如8)。
  2. 接收方如果也支持窗口缩放,就在SYN+ACK报文中回复窗口缩放选项,也指定一个缩放因子(如7)。
  3. 此后,双方发送的所有报文中,窗口大小字段的值都要左移相应的缩放因子位。
  4. 接收方收到报文后,再将窗口大小右移相同的位数,还原真实的窗口大小。

举例来说,如果窗口大小字段为1000,发送方的缩放因子为8,接收方的缩放因子为7,则真实的窗口大小为:

  • 发送方发送的窗口大小: 1000 << 8 = 256000字节
  • 接收方接收到的窗口大小: 1000 << 7 = 128000字节

可以看出,窗口缩放机制将原本16位的窗口大小扩展到了30位(16+14),大大突破了65535字节的限制。

(4) 窗口缩放应用示例

// 客户端发送的SYN报文
0x03 0x03 0x08

// 服务器回复的SYN+ACK报文
0x03 0x03 0x07

在这个例子中,客户端和服务器协商的缩放因子分别为8和7。假设后续传输过程中的窗口大小字段为1000,则实际的窗口大小分别为256000字节和128000字节,远大于65535字节的限制。

2.4 时间戳选项和防回绕序列号

TCP的时间戳(Timestamp)选项和防回绕序列号(Protection Against Wrapped Sequence Numbers, PAWS)是为了提高TCP性能和安全性而引入的两种机制。时间戳选项用于精确计算往返时间(RTT)和处理乱序数据段,PAWS机制则用于防止序列号回绕导致的旧数据段被误认为新数据段。

(1) 时间戳选项格式:时间戳选项(Kind=8, Length=10)由两个32位的时间戳字段组成:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Kind=8    |    Length=10  |          TS Value             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                TS Value (continued)                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          TS Echo Reply        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

其中,TS Value表示数据段发送时的时间戳值,TS Echo Reply表示上一次收到对方数据段的时间戳值。

(2) 时间戳选项工作原理

  • 发送方在每个数据段中都加入当前的时间戳值。
  • 接收方在确认数据段时,将发送方的时间戳值原样复制到TS Echo Reply字段。
  • 发送方收到确认后,用当前时间减去TS Echo Reply,得到一个精确的RTT样本。
  • 发送方和接收方还可以根据时间戳值处理乱序数据段,提高传输效率。

(3) 序列号回绕问题

  • TCP使用32位序列号,理论上最大传输4GB数据后序列号就会回绕。
  • 如果回绕后的旧数据段延迟到达,接收方可能会误以为是新的数据段,导致数据错乱。

(4) PAWS解决方案

  • 发送方和接收方在时间戳选项中维护一个时间戳时钟(Timestamp Clock)。
  • 发送方的时间戳时钟必须是单调递增的,每次发送数据段时都要增加。
  • 接收方检查收到数据段的时间戳值,如果小于上一次接收到的时间戳值,就认为是旧的数据段,直接丢弃。
  • 由于时间戳值的范围远大于序列号(32位vs16位),因此在序列号回绕期间时间戳值不会回绕,可以有效地区分新旧数据段。

以下是一个简化的时间戳选项和PAWS应用示例:

发送方                              接收方
  |                                  |
  | -- 数据段 (seq=1000, ts=100) -->  |
  |                                  |
  | <---- ACK 1000, ts_echo=100 ---- |
  |                                  |
  | -- 数据段 (seq=1000, ts=50) --->  |
  |                                  |
  |                             (丢弃旧数据段)
  |                                  |

在这个例子中,发送方在时间戳选项中发送了自己的时间戳值100,接收方在确认数据段时将其复制到ts_echo字段。后来,发送方重发了一个旧的数据段(可能是因为网络延迟),其时间戳值为50。接收方发现该数据段的时间戳值小于上一次接收到的时间戳值100,因此判断为旧的数据段,直接丢弃。这样就避免了旧数据段被误认为新数据段的问题。

2.5 用户超时选项

TCP的用户超时(User Timeout, UTO)选项是为了让应用程序能够更好地控制TCP连接的超时时间而引入的一种机制。它允许应用程序为每个TCP连接指定一个用户超时时间,当连接空闲时间超过该时间时,TCP将主动关闭连接,避免连接长时间占用系统资源。

(1) 引入背景

  • 传统TCP的超时时间由操作系统内核根据网络条件自适应地调整,应用程序无法直接控制。
  • 在某些场景下(如移动网络、电力有限的设备),应用程序希望能够更短的超时时间,以便及时释放连接资源。
  • 传统TCP的连接释放机制(如keep-alive)灵活性不够,不能满足应用程序的多样化需求。

(2) UTO选项格式:UTO选项(Kind=28, Length=4)用于在TCP首部中携带应用程序指定的用户超时时间。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Kind=28   |    Length=4   |G|        User Timeout         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                User Timeout (continued)                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

其中,G(Granularity)标志位表示用户超时时间的粒度,0表示秒,1表示分钟。User Timeout字段是一个31位的无符号整数,表示用户超时时间。

(3) UTO工作原理

  1. 应用程序在创建TCP连接时,可以通过套接字选项(如TCP_USER_TIMEOUT)设置用户超时时间。
  2. TCP协议栈将用户超时时间编码到UTO选项中,在SYN报文和后续数据报文中发送给对端。
  3. 对端TCP协议栈从UTO选项中解码出用户超时时间,作为该连接的空闲超时时间。
  4. 当连接空闲时间超过用户超时时间时,TCP将发送一个RST报文关闭连接,并通知应用程序。
  5. 应用程序可以根据需要捕获连接关闭事件,进行相应的处理。

以下是一个简化的UTO应用示例(在TCP首部中):

// 客户端发送的SYN报文
0x1c 0x04 0x00 0x00 0x27 0x10

// 服务器回复的SYN+ACK报文
0x1c 0x04 0x00 0x00 0x27 0x10

在这个例子中,客户端和服务器都在SYN报文中携带了UTO选项,G标志位为0,用户超时时间为10000秒(0x2710)。这表示如果连接空闲时间超过10000秒,TCP将自动关闭连接,释放连接资源。

注意事项

  • UTO选项只是一种建议,对端TCP协议栈可以根据自身策略决定是否采纳。
  • 为了避免UTO选项被滥用,操作系统内核通常会对用户超时时间的范围进行限制(如1秒到30天)。
  • UTO选项的设置需要谨慎,过短的超时时间可能导致连接频繁关闭,过长的超时时间又可能浪费系统资源。
2.6 认证选项

TCP的认证选项(TCP Authentication Option, TCP-AO)是为了增强TCP连接的安全性而引入的一种机制。它允许通信双方在TCP连接建立过程中对数据进行完整性验证和身份认证,防止了TCP会话劫持、重放攻击等安全威胁。

(1) 引入背景

  • 传统TCP缺乏对数据完整性和身份真实性的验证,容易受到会话劫持、中间人攻击等威胁。
  • 虽然TCP MD5选项(RFC 2385)提供了一定的完整性保护,但其安全强度不足,且存在密钥管理等问题。
  • 为了提供更强的安全保障,IETF开发了TCP-AO(RFC 5925),作为TCP MD5选项的替代者。

(2) TCP-AO格式:TCP-AO选项(Kind=29)的格式如下:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Kind=29   |    Length     |   KeyID       | Next KeyID    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     MAC (Message Authentication Code)    
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

其中,Length字段表示TCP-AO选项的总长度,KeyID表示当前使用的主密钥(Master Key)标识符,Next KeyID表示下一个要使用的主密钥标识符(用于密钥轮换),MAC字段则是根据数据和密钥计算出的消息认证码。

(3) TCP-AO工作原理

  1. 通信双方offline协商一组主密钥和密钥标识符,并设置密钥轮换策略。
  2. 在TCP连接建立过程中,双方在SYNSYN-ACKACK报文中携带TCP-AO选项,对数据进行完整性保护。
  3. 对于每个要发送的TCP报文,发送方根据相应的主密钥和密钥标识符,计算MAC值,并填充到TCP-AO选项中。
  4. 接收方收到TCP报文后,根据TCP-AO选项中的密钥标识符,查找相应的主密钥,重新计算MAC值,并与报文中的MAC值进行比对。
  5. 如果MAC值校验通过,说明数据完整性和发送方身份得到了验证,接收方继续处理该TCP报文;否则,认为该报文不可信,直接丢弃。
  6. 根据密钥轮换策略,通信双方周期性地更换主密钥,保证长期通信的安全性。

以下是一个简化的TCP-AO应用示例(在TCP首部中):

// 客户端发送的SYN报文
0x1d 0x10 0x01 0x02 0x12 0x34 0x56 0x78 0x9a 0xbc 0xde 0xf0 0x11 0x22 0x33 0x44

// 服务器回复的SYN-ACK报文
0x1d 0x10 0x01 0x02 0x56 0x78 0x9a 0xbc 0xde 0xf0 0x11 0x22 0x33 0x44 0x55 0x66

在这个例子中,客户端在SYN报文中携带了TCP-AO选项,KeyID为1,Next KeyID为2,MAC值为一个16字节的哈希值。服务器在SYN-ACK报文中也携带了TCP-AO选项,表示接受客户端的认证请求,并附上自己计算的MAC值。后续的数据传输则会继续使用TCP-AO选项进行完整性保护。

注意事项

  1. TCP-AO的安全性依赖于离线协商的主密钥,因此主密钥的生成、分发、存储等环节需要有安全保障。
  2. TCP-AO选项会增加一定的计算开销和数据包长度,对性能有一定影响。
  3. TCP-AO与其他TCP选项(如时间戳、SACK等)可以同时使用,以获得更好的性能和安全性。
3. TCP特性
3.1 TCP路径最大传输单元发现(MSS)

TCP路径MTU发现(Path MTU Discovery, PMTUD)是一种自适应调整TCP报文段大小的机制,目的是在不产生IP分片的情况下,最大化单个TCP报文段的长度,从而提高TCP传输效率。

(1) PMTUD工作流程

  1. 发送方将TCP报文段大小设置为较大的值,如界面MTU或者最大允许的MSS。
  2. 发送方在IP首部中设置DF(Don’t Fragment)标志,表示不允许中间设备对该数据包进行分片。
  3. 数据包在网络中传输,如果遇到MTU较小的链路,且数据包长度大于链路MTU,则中间设备丢弃该数据包,并向发送方返回一个ICMP差错报文(“Fragmentation Needed and DF Set”)。
  4. 发送方收到ICMP差错报文后,从中提取下一跳MTU值,并将TCP报文段大小缩小为该值。
  5. 发送方继续发送修改后的TCP报文段,重复步骤2-4,直到数据包到达目的地。
  6. 如果一段时间内没有收到ICMP差错报文,发送方会尝试增大TCP报文段大小,以探测路径MTU是否有所增加(因为路由是动态变化的)。

(2) PMTUD状态分析:PMTUD可以看作是一个有限状态机,主要有以下几种状态:

  • Initial状态:发送方使用较大的TCP报文段大小,如界面MTU或最大允许的MSS。
  • Searching状态:发送方收到ICMP差错报文后,缩小TCP报文段大小,并继续发送数据,探测路径MTU。
  • Found状态:发送方找到了当前路径的MTU值,使用该值作为TCP报文段大小。
  • Robustness状态:发送方定期尝试增大TCP报文段大小,以适应路径MTU的可能变化。

在实际传输过程中,PMTUD会在这几种状态之间进行切换,以适应网络状况的变化。

(3) ICMP差错报文:PMTUD依赖以下两种ICMP差错报文:

  • "Fragmentation Needed and DF Set" (Type 3, Code 4):当数据包长度超过下一跳MTU且设置了DF标志时,中间设备会返回这个差错报文,并在报文中携带下一跳MTU值。

  • "Packet Too Big" (Type 2, Code 0):当IPv6数据包长度超过下一跳MTU时,中间设备会返回这个差错报文,也会携带下一跳MTU值。

发送方需要正确解析这两种差错报文,并及时调整TCP报文段大小,以保证PMTUD的正常工作。

实践经验

  • 一些防火墙或者NAT设备可能会过滤ICMP差错报文,导致PMTUD无法工作。解决办法是在这些设备上开启ICMP差错报文的转发,或者使用TCP选项中的MSS值来限制报文段大小。

  • 如果网络中存在MTU值很小的链路(如PPPoE、VPN等),可能会导致TCP性能下降。解决办法是在TCP连接建立时,使用较小的MSS值,避免过大的报文段。

  • 在某些网络环境下,中间设备可能不会发送ICMP差错报文,导致PMTUD失效。解决办法是使用TCP选项中的MSS值来限制报文段大小,或者使用基于探测的PMTUD方法(如PLPMTUD)。

  • 为了避免PMTUD过程中的延迟和丢包,一些实现会缓存已发现的路径MTU值,并在后续连接中直接使用,以加快收敛速度。

TCP路径MTU发现是一种自适应调整报文段大小的机制,通过与ICMP差错报文的交互,发现当前路径的MTU值,并动态调整TCP报文段大小,在不产生IP分片的情况下最大化传输效率。

3.2 TCP状态转换

TCP状态转换图是描述TCP连接建立、数据传输和连接释放过程中,TCP连接所经历的各种状态以及状态之间转换关系的一种图形表示。它直观地展示了TCP协议的工作流程和状态管理机制。

在这里插入图片描述

在TCP状态转移图中,有几个典型的过程值得特别关注,它们体现了TCP协议的核心功能和特性。同时,RFC协议规定和实际的套接字实现(如伯克利套接字)之间也存在一些差异。

(1) LISTEN状态的处理:RFC 793中,LISTEN状态是一个显式的状态,当应用程序调用listen()函数时,TCP进入LISTEN状态,等待客户端连接。在伯克利套接字实现中,LISTEN状态是一个隐式的状态,当应用程序调用socket()函数创建套接字时,就自动进入了LISTEN状态,无需显式调用listen()函数。

(2) SYN_SENT状态的处理:RFC 793中,当TCP发送SYN报文后,会进入SYN_SENT状态,等待对方的SYN+ACK报文。如果收到的是对方的SYN报文(而非SYN+ACK),TCP会回复SYN+ACK报文,并进入SYN_RCVD状态。

在伯克利套接字实现中,如果TCP在SYN_SENT状态收到对方的SYN报文,会直接进入ESTABLISHED状态,而不经过SYN_RCVD状态。这种行为称为"同时打开(simultaneous open)"。

同时打开在实际网络中较为罕见,但在某些场景下(如P2P网络)可能会发生。应用程序需要正确处理这种情况,以避免连接异常。

(3) FIN_WAIT_2状态的处理:RFC 793中,当TCP在FIN_WAIT_1状态收到对方的ACK报文后,会进入FIN_WAIT_2状态,等待对方的FIN报文。如果一直未收到FIN报文,TCP会持续停留在FIN_WAIT_2状态。

在伯克利套接字实现中,如果TCP在FIN_WAIT_2状态停留时间过长,会触发保活计时器,主动向对方发送探测报文,以确认连接状态。如果探测失败,TCP会直接进入CLOSED状态,而不再等待对方的FIN报文。

这种差异可以防止TCP连接长时间占用系统资源,提高了系统的稳定性和可用性。但也可能导致连接被意外关闭,应用程序需要合理设置保活计时器的阈值。

(4) TIME_WAIT状态的处理:RFC 793中,当TCP在TIME_WAIT状态等待2MSL超时后,就会直接进入CLOSED状态,连接彻底关闭。

在伯克利套接字实现中,为了优化服务器的性能和资源利用,引入了TIME_WAIT状态的快速回收机制。当服务器在短时间内需要大量创建和关闭连接时,可以通过设置SO_REUSEADDR选项,让新的连接重用处于TIME_WAIT状态的端口,而无需等待2MSL超时。

这种优化可以显著提高服务器的并发处理能力,但也可能引入一些安全隐患(如端口劫持),需要谨慎使用。

(5) CLOSE_WAIT状态的处理:RFC 793中,当TCP收到对方的FIN报文后,会进入CLOSE_WAIT状态,等待应用程序主动关闭连接。

在伯克利套接字实现中,如果应用程序一直不关闭连接,TCP会持续停留在CLOSE_WAIT状态,占用系统资源。为了防止这种情况,一些实现会引入半关闭连接的概念,允许应用程序单向关闭连接,而无需等待对方的FIN报文。

半关闭连接可以提高资源利用效率,但也增加了应用程序的复杂性,需要仔细设计和实现。

3.3 重置报文段

TCP重置报文段(RST)是一种特殊的TCP报文,用于异常终止一个TCP连接。当出现某些错误情况时,TCP会发送RST报文,立即关闭连接,而不经过正常的四次挥手过程。

(1) 针对不存在端口的连接请求:当一个TCP客户端向服务器发送连接请求(SYN报文)时,如果服务器上没有相应的端口在监听,服务器就会返回一个RST报文,告知客户端连接请求被拒绝。这种情况通常发生在以下场景:

  • 服务器端口未打开或者服务进程未启动。
  • 客户端连接请求的目的端口号错误。
  • 服务器端口被防火墙或者安全策略阻止。

通过RST报文,服务器可以快速向客户端通知连接请求失败,避免客户端长时间等待。

(2) 终止一个已建立的连接:在某些情况下,需要立即终止一个已经建立的TCP连接,而不能等待正常的四次挥手过程完成。这时,可以通过发送RST报文来强制关闭连接。常见的场景包括:

  • 应用程序异常退出或者崩溃,来不及正常关闭连接。
  • 检测到连接上有非法或者恶意的数据传输。
  • 因为网络故障或者系统错误,连接状态出现不一致。

当一方发送RST报文后,连接将立即进入CLOSED状态,不再发送或者接收任何数据。

(3) 处理半开连接:半开连接是指一端认为连接已经关闭,而另一端认为连接还在维持的情况。这通常发生在以下场景:

  • 一方异常关闭连接(如断电、崩溃等),而没有发送FIN报文。
  • 一方发送的FIN报文在网络中丢失,未能到达对端。
  • 一方关闭连接后,另一方仍然向其发送数据。

当出现半开连接时,仍然维持连接的一方会收到对端发来的RST报文,从而意识到连接已经关闭,进入CLOSED状态。这样可以避免半开连接长时间占用系统资源。

(4) 处理TIME_WAIT状态错误:在TCP四次挥手过程中,主动关闭连接的一方会进入TIME_WAIT状态,等待2MSL超时,以确保对端收到了最后的ACK报文。如果在TIME_WAIT状态时,又收到了对端发来的数据报文,就会出现TIME_WAIT状态错误。这时,主动关闭方会发送RST报文,强制关闭连接,防止新旧数据包混淆。

(5) 处理序列号错误:TCP通过序列号来保证数据的可靠传输和有序接收。如果一方收到的数据报文的序列号不在期望的窗口范围内,就会发送RST报文,强制关闭连接,防止错误数据的传输。这种情况通常发生在以下场景:

  • 网络中存在重复的数据报文,导致序列号错乱。
  • 一方的接收缓存溢出,无法接收新的数据。
  • 一方因为系统错误,发送了序列号错误的数据报文。

通过RST报文,可以及时终止异常的连接,避免错误数据的传输和处理。

3.4 与TCP连接相关的攻击

TCP作为互联网上最广泛使用的传输层协议,虽然提供了可靠、有序的数据传输服务,但也存在一些安全漏洞和攻击风险。

(1) SYN泛洪攻击(SYN Flooding)

  • 攻击原理:攻击者发送大量的SYN连接请求报文,但不完成三次握手过程,导致服务器上积累大量的半开连接(SYN_RECV状态),耗尽服务器的资源和内存,使其无法响应正常的连接请求。

  • 防范方法:

    • 启用SYN Cookie机制,对每个SYN请求编码一个Cookie,只有收到合法的ACK报文才分配连接资源。
    • 限制SYN_RECV状态的连接数量,超过阈值就丢弃新的SYN请求。
    • 缩短SYN_RECV状态的超时时间,尽快释放半开连接占用的资源。
    • 使用防火墙或者入侵检测系统(IDS)检测和过滤异常的SYN请求。

(2) TCP复位攻击(TCP Reset Attack)

  • 攻击原理:攻击者伪造RST报文,发送给连接的一方,导致连接异常关闭。攻击者通过猜测或者嗅探获得连接的序列号和端口号,从而构造出合法的RST报文。

  • 防范方法:

    • 启用TCP时间戳选项,在RST报文中携带时间戳信息,防止伪造的RST报文被接受。
    • 使用加密协议(如SSL/TLS)对TCP连接进行保护,防止连接信息被嗅探和伪造。
    • 对RST报文进行合法性验证,如检查序列号是否在合理范围内,防止伪造的RST报文被接受。
    • 使用防火墙或者IDS检测和过滤异常的RST报文。

(3) TCP会话劫持(TCP Session Hijacking)

  • 攻击原理:攻击者通过嗅探或者猜测获得连接的序列号和端口号,伪造数据包插入到连接中,从而劫持会话,冒充合法用户进行非法操作。

  • 防范方法:

    • 使用加密协议(如SSL/TLS)对TCP连接进行保护,防止连接信息被嗅探和伪造。
    • 启用TCP MD5选项,对TCP报文进行完整性验证,防止伪造的数据包被接受。
    • 使用强壮的身份认证机制(如双因素认证)验证用户身份,防止会话被劫持后的非法操作。
    • 限制连接的生命周期,定期更新会话密钥,减少会话劫持的风险。

(4) TCP欺骗攻击(TCP Spoofing)

  • 攻击原理:攻击者伪造TCP报文的源IP地址,欺骗服务器与其建立连接,从而绕过IP地址验证,进行非法操作。

  • 防范方法:

    • 在服务器上启用严格的反向路径过滤(RPF)机制,验证数据包的源IP地址是否合法。
    • 使用加密协议(如SSL/TLS)对TCP连接进行保护,防止连接信息被伪造。
    • 在应用层实现强壮的身份认证和授权机制,验证用户身份和权限。
    • 使用防火墙或者IDS检测和过滤伪造的TCP报文。

(5) TCP小值MSS攻击(TCP MSS Attack)

  • 攻击原理:攻击者故意在TCP连接建立过程中,发送一个很小的MSS(Maximum Segment Size)值,导致后续数据传输中出现大量的小包,增加服务器的处理开销和网络拥塞。

  • 防范方法:

    • 在服务器上设置合理的最小MSS值,拒绝过小的MSS请求。
    • 限制单个连接的数据包数量和频率,防止小包攻击耗尽服务器资源。
    • 使用流量整形和限速机制,控制单个连接的带宽和数据包速率。
    • 使用防火墙或者IDS检测和过滤异常的MSS值。

与TCP相关的攻击种类繁多,涉及连接建立、数据传输、连接释放等各个阶段。攻击者利用TCP协议的特性和缺陷,试图耗尽服务器资源、窃取敏感信息、劫持会话控制等。为了防范这些攻击,我们需要采取多层次、全方位的安全措施,包括加密通信、身份认证、完整性验证、访问控制、异常检测等。同时,也要加强网络基础设施的安全性,如防火墙、IDS、路由器等设备的配置和管理。

猜你喜欢

转载自blog.csdn.net/Once_day/article/details/143193862