网络运输层之(1)TCP连接管理
Author: Once Day Date: 2024年10月22日
一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…
漫漫长路,有人对你微笑过嘛…
全系列文章可参考专栏: 通信网络技术_Once-Day的博客-CSDN博客。
参考文章:
- 《TCP/IP详解卷一》
- TCP协议详解 (史上最全)-CSDN博客
- TCP协议详解 - 知乎 (zhihu.com)
- 计算机网络:这是一份非常全面&详细的TCP/IP协议学习指南-腾讯云开发者社区-腾讯云 (tencent.com)
- 计算机网络原理梳理丨清晰认识 TCP/IP 协议-腾讯云开发者社区-腾讯云 (tencent.com)
文章目录
- 网络运输层之(1)TCP连接管理
- 1. TCP连接管理
- 1.1 连接的建立与终止
- 1.2 半关闭
- 1.3 同时打开和关闭
- 1.4 初始化序列号
- 1.5 连接超时
- 2. TCP选项
- 2.1 最大段大小选项
- 2.2 选择确认选项
- 2.3 窗口缩放选项
- 2.4 时间戳选项和防回绕序列号
- 2.5 用户超时选项
- 2.6 认证选项
- 3. TCP特性
- 3.1 TCP路径最大传输单元发现(MSS)
- 3.2 TCP状态转换
- 3.3 重置报文段
- 3.4 与TCP连接相关的攻击
1. TCP连接管理
1.1 连接的建立与终止
TCP提供面向连接的可靠数据传输服务。它在传输层协议中占据核心地位。
(1) 建立连接(三次握手)
- 客户端发送
SYN
同步报文,随机生成一个初始序列号seq=x
,标志位SYN=1
,发送到服务器。 - 服务器接收到
SYN
报文后,回复SYN+ACK
报文,确认号ack=x+1
,同时也生成一个随机序列号seq=y
,标志位SYN=1
,ACK=1
。 - 客户端收到服务器的
SYN+ACK
后,回复ACK
确认报文,确认号ack=y+1
,序列号seq=x+1
,标志位ACK=1
。 - 双方都收到对方的ACK确认后,TCP连接就建立成功了。连接建立后,会形成一个唯一的会话四元组,即(源IP,源端口,目的IP,目的端口),通过四元组能够唯一标识一个TCP连接。
三次握手能够可靠地建立一个双向的TCP连接,客户端和服务器都能够确认自己和对方的发送与接收能力正常。避免了连接时的歧义和资源浪费。
(2) 数据传输
- TCP连接建立后,双方按照三次握手确定的初始序列号,开始传输数据。
- 发送方给每个传输的字节分配一个序列号
seq
。接收方收到数据后,发送ACK
确认报文,确认号ack=seq+len
。 - 发送方收到接收方的
ACK
后,就确认数据传输成功,然后滑动发送窗口,用新的序列号继续发送后续数据。 - TCP利用序列号、确认应答、重传、流量控制、拥塞控制等机制,能够实现可靠的数据传输。
TCP的字节流服务、全双工通信以及滑动窗口协议,使得应用层看到的似乎是一个无差错、不会丢失、不会重复、保序的数据流。实际的网络物理链路却可能出现丢包、重复、错序等各种问题。TCP正是提供了复杂的机制来解决这些问题,从而呈现简单可靠的服务。
(3) 连接终止(四次挥手)
- 主动关闭方发送
FIN
结束报文,seq=m
,标志位FIN=1
,表示数据发送完毕,请求关闭连接。 - 被动关闭方收到
FIN
后,回复ACK
确认,ack=m+1
。此时主动方到被动方的单向连接就释放了。TCP连接进入半关闭状态。被动方仍可以继续发送数据。 - 被动方数据发送完毕后,也发送
FIN
结束报文,seq=n
,标志位FIN=1
。 - 主动方收到
FIN
后,回复ACK
确认,ack=n+1
。 - 主动方等待
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_2
和CLOSE_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同时打开:
- 同时打开发生于连接双方几乎同时发送
SYN
报文的情况。 - 客户端和服务器都主动打开连接,各自发送
SYN
报文,并进入SYN_SENT
状态。 - 双方收到对方的
SYN
后,就将其视为SYN+ACK
,同时转换到ESTABLISHED
状态。 - 双方再各自回复一个
ACK
,确认收到对方的SYN
。 - 至此,TCP连接建立完成。整个过程实际上只有二次握手,但是有四个报文。
与三次握手的区别:
- 角色对等:同时打开的双方都是主动发起方,而不分客户端和服务器。
- 状态变化:同时打开少了一个
SYN_RCVD
状态,双方直接从SYN_SENT
到ESTABLISHED
。 - 握手次数:同时打开只有两次握手,比三次握手少一次。
TCP同时关闭:
- 同时关闭发生于连接双方几乎同时发送
FIN
报文的情况。 - 此时双方的状态都从
ESTABLISHED
直接转换到CLOSING
。 - 双方都收到对方的
FIN
后,就将其视为FIN+ACK
,同时转换到TIME_WAIT
状态。 - 等待
2MSL
时间后,如果没有收到对方的数据,就关闭连接,转换到CLOSED
状态。 - 整个过程只有二次挥手,没有出现半关闭状态。
与四次挥手的区别:
- 状态变化:同时关闭没有半关闭状态如
CLOSE_WAIT
和LAST_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) 连接建立超时:
- 客户端发送
SYN
后,等待服务器的SYN+ACK
超时,称为SYN
超时。 - 客户端通常会重发
SYN
,如果重试次数达到上限还没有收到响应,就放弃连接。 - 这种情况下,客户端会收到
ETIMEDOUT
错误,状态保持在CLOSED
。
(2) 数据传输超时:
- 发送方发送数据后,等待接收方的ACK超时,称为
RTO(Retransmission Timeout)
超时。 - 发送方会重发数据,如果重试次数达到上限还没有收到
ACK
,就认为连接失效。 - 这种情况下,发送方会收到
ETIMEDOUT
错误,连接状态变为CLOSED
。 - 接收方会收到一个
RST
报文,将连接状态也变为CLOSED
。
(3) 连接关闭超时:
- 主动关闭方发送
FIN
后,等待被动关闭方的ACK
超时,称为FIN_WAIT_1
超时。 - 主动关闭方会重发
FIN
,如果重试次数达到上限还没有收到ACK
,就直接关闭连接。 - 这种情况下,主动关闭方会收到
ETIMEDOUT
错误,状态从FIN_WAIT_1
直接变为CLOSED
。 - 被动关闭方会收到一个
RST
报文,将连接状态从CLOSE_WAIT
变为CLOSED
。
(4) 长时间非活动超时:
- 如果一个已建立的连接长时间没有数据传输,称为非活动超时。
- 这种超时通常由操作系统的TCP保活机制触发,默认时间为2小时。
- 保活机制会发送探测报文,如果多次探测都没有响应,就认为连接失效。
- 这种情况下,双方都会收到ETIMEDOUT错误,连接状态变为CLOSED。
(5) TCP重置报文:
- 当一方发送的数据包对于另一方无法接受时,另一方会发送一个RST报文。
- RST报文表示强制重置连接,双方的状态都将直接变为CLOSED。
- 常见的触发RST的情况有:请求建立一个已存在的连接、请求一个不存在的端口、请求一个已关闭的连接等。
- 应用程序收到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 30 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的典型交互流程:
- 客户端发送
SYN
报文,在TCP选项中携带自己的MSS值(如1460)。
Client -> Server: SYN, MSS=1460
- 服务器收到
SYN
后,选择一个不大于客户端MSS的值作为自己的MSS
(如1400),并在SYN+ACK
报文中回复该MSS
值。
Server -> Client: SYN+ACK, MSS=1400
-
客户端收到服务器的
MSS
值后,调整自己的发送数据段大小,确保不超过1400字节。 -
服务器收到客户端的
ACK
后,也调整自己的发送数据段大小,确保不超过1460字节。 -
后续的数据传输中,双方就按照协商好的
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选项。
- SACK-Permitted选项
(Kind=4, Length=2)
用于在SYN报文中协商是否启用SACK。
0 10 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Kind=4 | Length=2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- SACK选项
(Kind=5, Length=N)
用于在ACK报文中告知发送方已接收到的不连续数据块。
0 1 2 30 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工作原理:
- 发送方在SYN报文中携带
SACK-Permitted
选项,表示自己支持SACK。 - 接收方如果也支持
SACK
,就在SYN+ACK
报文中回复SACK-Permitted
选项。 - 在数据传输过程中,如果接收方收到了不连续的数据段,就在
ACK
报文中使用SACK
选项告知发送方已接收到的数据块。 - 发送方根据
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 20 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) 窗口缩放工作原理:
- 发送方在
SYN
报文中携带窗口缩放选项,指定一个缩放因子(如8)。 - 接收方如果也支持窗口缩放,就在
SYN+ACK
报文中回复窗口缩放选项,也指定一个缩放因子(如7)。 - 此后,双方发送的所有报文中,窗口大小字段的值都要左移相应的缩放因子位。
- 接收方收到报文后,再将窗口大小右移相同的位数,还原真实的窗口大小。
举例来说,如果窗口大小字段为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 30 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 30 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工作原理:
- 应用程序在创建TCP连接时,可以通过套接字选项(如TCP_USER_TIMEOUT)设置用户超时时间。
- TCP协议栈将用户超时时间编码到UTO选项中,在SYN报文和后续数据报文中发送给对端。
- 对端TCP协议栈从UTO选项中解码出用户超时时间,作为该连接的空闲超时时间。
- 当连接空闲时间超过用户超时时间时,TCP将发送一个RST报文关闭连接,并通知应用程序。
- 应用程序可以根据需要捕获连接关闭事件,进行相应的处理。
以下是一个简化的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 30 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工作原理:
- 通信双方offline协商一组主密钥和密钥标识符,并设置密钥轮换策略。
- 在TCP连接建立过程中,双方在
SYN
、SYN-ACK
、ACK
报文中携带TCP-AO选项,对数据进行完整性保护。 - 对于每个要发送的TCP报文,发送方根据相应的主密钥和密钥标识符,计算MAC值,并填充到TCP-AO选项中。
- 接收方收到TCP报文后,根据TCP-AO选项中的密钥标识符,查找相应的主密钥,重新计算MAC值,并与报文中的MAC值进行比对。
- 如果MAC值校验通过,说明数据完整性和发送方身份得到了验证,接收方继续处理该TCP报文;否则,认为该报文不可信,直接丢弃。
- 根据密钥轮换策略,通信双方周期性地更换主密钥,保证长期通信的安全性。
以下是一个简化的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选项进行完整性保护。
注意事项:
- TCP-AO的安全性依赖于离线协商的主密钥,因此主密钥的生成、分发、存储等环节需要有安全保障。
- TCP-AO选项会增加一定的计算开销和数据包长度,对性能有一定影响。
- TCP-AO与其他TCP选项(如时间戳、SACK等)可以同时使用,以获得更好的性能和安全性。
3. TCP特性
3.1 TCP路径最大传输单元发现(MSS)
TCP路径MTU发现(Path MTU Discovery, PMTUD)是一种自适应调整TCP报文段大小的机制,目的是在不产生IP分片的情况下,最大化单个TCP报文段的长度,从而提高TCP传输效率。
(1) PMTUD工作流程:
- 发送方将TCP报文段大小设置为较大的值,如界面MTU或者最大允许的MSS。
- 发送方在IP首部中设置DF(Don’t Fragment)标志,表示不允许中间设备对该数据包进行分片。
- 数据包在网络中传输,如果遇到MTU较小的链路,且数据包长度大于链路MTU,则中间设备丢弃该数据包,并向发送方返回一个ICMP差错报文(“Fragmentation Needed and DF Set”)。
- 发送方收到ICMP差错报文后,从中提取下一跳MTU值,并将TCP报文段大小缩小为该值。
- 发送方继续发送修改后的TCP报文段,重复步骤2-4,直到数据包到达目的地。
- 如果一段时间内没有收到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、路由器等设备的配置和管理。