目录
[13.1 引言](#13.1 引言)
[13.2 TCP连接的建立与终止](#13.2 TCP连接的建立与终止)
[13.2.1 TCP半关闭](#13.2.1 TCP半关闭)
[13.2.2 同时打开与关闭](#13.2.2 同时打开与关闭)
[13.2.3 初始序列号](#13.2.3 初始序列号)
[13.2.4 例子](#13.2.4 例子)
[13.2.5 连接建立超时](#13.2.5 连接建立超时)
[13.2.6 连接与转换器](#13.2.6 连接与转换器)
[13.3 TCP 选项](#13.3 TCP 选项)
[13.3.1 最大段大小选项](#13.3.1 最大段大小选项)
[13.3.2 选择确认选项](#13.3.2 选择确认选项)
[13.3.3 窗口缩放选项](#13.3.3 窗口缩放选项)
[13.3.4 时间戳选项与防回绕序列号](#13.3.4 时间戳选项与防回绕序列号)
[13.3.5 用户超时选项](#13.3.5 用户超时选项)
[13.3.6 认证选项](#13.3.6 认证选项)
[13.4 TCP的路径最大传输单元发现](#13.4 TCP的路径最大传输单元发现)
[13.4.1 例子](#13.4.1 例子)
[13.5 TCP状态转换](#13.5 TCP状态转换)
[13.5.1 TCP状态转换图](#13.5.1 TCP状态转换图)
[13.5.2 TIME_WAIT 状态](#13.5.2 TIME_WAIT 状态)
[13.5.3 静默时间](#13.5.3 静默时间)
[13.5.4 FIN_WAIT_2 状态](#13.5.4 FIN_WAIT_2 状态)
[13.5.5 同时打开与关闭的转换](#13.5.5 同时打开与关闭的转换)
[13.6 重置报文段](#13.6 重置报文段)
[13.6.1 请求端口不存在](#13.6.1 请求端口不存在)
[13.6.2 终止一条连接](#13.6.2 终止一条连接)
[13.6.3 半开连接](#13.6.3 半开连接)
[13.6.4 时间等待错误](#13.6.4 时间等待错误)
[13.7 TCP服务器选项](#13.7 TCP服务器选项)
[13.7.1 TCP 端口号](#13.7.1 TCP 端口号)
[13.7.2 限制本地IP地址](#13.7.2 限制本地IP地址)
[13.7.3 限制外部节点](#13.7.3 限制外部节点)
[13.7.4 进入连接队列](#13.7.4 进入连接队列)
[13.8 与TCP连接管理相关的攻击](#13.8 与TCP连接管理相关的攻击)
[13.9 总结](#13.9 总结)
13.1 引言
建立连接时,通信双方通过TCP选项交换参数。
某些选项只被允许在连接建立时发送。
TCP头部中选项最多为40字节。
13.2 TCP连接的建立与终止
ISN:初始序列号。发起方设置的随机的序列号。
上图连接关闭时,四次挥手中二三次报文序列号Seq都是L,这是因为没有发送数据。
TCP的SYN报文可承载应用数据。由于伯克利的socket API不支持,所以使用少。
每个TCP连接基本开销是7个报文段(三次握手,四次挥手)。
如果只传输少量数据,可用UDP协议,减少开销。但UDP存在拥塞管理,流量控制等问题。
13.2.1 TCP半关闭
如下图:关闭连接时,四次挥手只完成其中两次。此时即TCP半关闭状态。
场景:已完成数据发送,并发送FIN给对方,但仍希望接收来自对方数据,直到收到对方的FIN。
半关闭API:
shutdown(sock_fd, SHUT_WR);
关闭套接字写入端,但允许接收对方数据。
shutdown和close对比:
shutdown:
通过不同参数选择只关闭套接字读或写某一方向,或读写同时关闭。
不会立即关闭套接字,而是进入半关闭状态
close:
完全关闭套接字,读写均关闭,并释放资源。
13.2.2 同时打开与关闭
同时打开:
两个对等端同时发送连接请求(SYN),并相互确认(ACK)对方的连接请求。
作用:
同时发送连接请求,可减少连接延迟。
同时发送连接请求,可使NAT设备进行连接追踪conntrak正确处理连接。避免TCP连接失败。
报文交互:
4次握手( SYN+ACK,SYN+ACK)
同时关闭:
两个对等端同时发送连接关闭请求(FIN),并相互确认(ACK)对方的关闭请求。
13.2.3 初始序列号
初始序列号:即ISN。
每个TCP连接都有不同初始序列号,不与其他连接重复。
ISN作用:
用于数据包排序和重组。
防止旧连接延迟报文段被视为新连接的有效数据。
防止伪造合法TCP报文段。
Linux生成初始序列号ISN方法:
当前时钟+随机偏移量。
随机偏移量:连接标识(即4元组)散列得到。
13.2.4 例子
TCP有的选项不能在连接建立时使用,而有的选项可以。
13.2.5 连接建立超时
如服务器关闭,连接建立超时。
net.ipv4.tcp_syn_retries:
若未收到第二次握手SYN + ACK报文,第一次握手SYN报文最大重传次数。
通常为5。
net.ipv4.tcp_synack_retries:
若未收到第三次握手ACK报文,第二次握手SYN+ACK报文最大重传次数。
通常为5。
SYN和SYN+ACK两个报文重传时间都遵循指数回退原则。
13.2.6 连接与转换器
TCP计算校验和时会生成伪头部,伪头部包含源目IP等。
NAT转换后,会更改报文源IP,所以需要重新计算TCP校验和。
在NAT转换中也会读取TCP状态机,方便跟踪连接,包括当前状态、序列号,ACK号。
13.3 TCP 选项
下面按章节讲解常见选项。
13.3.1 最大段大小选项
最大段大小:即MSS。
即TCP载荷最大值,不包含TCP头。
TCP连接建立时,双方都在SYN报文的MSS选项中指明各自MSS。
最后选择双方较小的MSS,作为该连接MSS。未指明时默认536。
MSS典型值:
IPv4 MSS通常为1460字节(1500 MTU - 20 IP头 - 20 TCP头)
IPv6 MSS通常为1440字节。IPv6头部比IPv4多20个字节。
MSS协商成功后,双方在连接过程中不接收大于MSS报文段。
13.3.2 选择确认选项
传统的TCP只能确认收到的连续字节,而不能确认中间丢失的数据。
选择确认:即SACK。
SACK选项允许接收方报告成功接收的连续数据段以及中间丢失部分,使发送方只重传中间丢失部分,提高网络效率。
SACK选项两部分组成:
SACK Permitted:表示是否支持SACK 选项。
SACK Blocks:指示已成功接收的数据段范围。
TCP头部选项空间有限,一个报文段最多3个SACK Blocks。
13.3.3 窗口缩放选项
缩放:SCALE
窗口缩放选项(Window Scale Option):
TCP头的窗口缩放选项中包含一个缩放因子,指示对方的窗口大小应按比例缩放。
TCP头部本来有窗口字段值,再乘以缩放因子,实现窗口缩放。
窗口缩放选项只能出现SYN报文段。
可根据网络丢包/延迟情况动态调整窗口大小,提高传输性能。
适用于:
高速网络:传统TCP窗口太小,无法充分利用网络带宽。
长距离网络
高丢包率网络
13.3.4 时间戳选项与防回绕序列号
时间戳选项(TSOPT):
在TCP头中加入时间戳信息,用于测量传输延迟,计算往返时间RTT。
时间戳选项包含两个值:
TSval:Timestamp Value
发送端发送TCP报文时记录的时间戳。
TSecr:Timestamp Echo Reply
接收端表示接收到报文时的时间戳。
通过TSval ,TSecr值即可计算RTT。
RTT是衡量网络性能和稳定性的重要指标。
RTO(Retransmission Timeout):
用于确定数据包的重传的时间间隔。
RTO值通常设为众多RTT采样值中较大一个,以便RTT波动大或网络拥塞时,仍能够正常传输,避免过早重传。
时间戳选项其他作用:
序列号回绕:指当TCP序列号达到最大值后(2^32-1),重新从0开始计数。
即使在序列号回绕时,时间戳选项提供了另一个递增的、唯一的标识符,避免序列号重叠问题,提高传输稳定性。
13.3.5 用户超时选项
用户超时(UTO)选项:
表示未收到数据ACK之前等待最长时间。
该选项允许应用程序自定义的超时时间,以便快速检测连接异常,并定制化超时处理策略。
设置TCP UTO选项:
int utimeout_ms = 5000;
setsockopt(sockfd, IPPROTO_TCP, TCP_USER_TIMEOUT, &utimeout_ms, sizeof(utimeout_ms)) ;
检测TCP UTO是否超时:
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
if (error == ETIMEDOUT) {
//UTO超时处理,如重传等。
}
NAT设备可将UTO时间设置为NAT连接活动计时器。
13.3.6 认证选项
目的:替换TCP-MD5机制。
TCP 认证选项(TCP Authentication Option, TCP-AO)
作用:握手时身份验证,密钥协商,加密通信。
13.4 TCP的路径最大传输单元发现
路径最大传输单元:PMTU,路径中所有设备MTU最小值。
13.4.1 例子
PPPoE的MTU为1492字节(以太网MTU 1500,减6字节PPPoE头,再减2字节PPP头)
配置接口MTU:
ifconfig ppp0 mtu 288
该接口向外发出报文最大为288字节,但是接收不受288限制。
内核参数net.ipv4.route.min_pmtu:
手动设置本地IPv4的PMTU。
本地转发IP数据包时,如果数据包大于min_pmtu,可分片。
13.5 TCP状态转换
13.5.1 TCP状态转换图
13.5.2 TIME_WAIT 状态
不携带数据的ACK不会消耗序列号
ACK丢失,不会重传ACK,而是对方等待ACK超时后,重传之前数据。
TIME_WAIT状态:等待2MSL时候后才变为CLOSED状态。
MSL:最大段生存期(Maximum Segment Lifetime)
一个TCP段在网络上能存在的最长时间。
TIME_WAIT状态等待一段时间才完全关闭连接。
作用:
如果服务器要重传FIN时,以便再次回复ACK。
处理延迟/重传数据:可等待接收因网络延迟或重传数据,之后再彻底关闭连接。
防止收到旧连接报文:
TIME_WAIT状态时,不允许建立新连接,所以不存在新连接接收到旧连接数据。
(除非使用了SO_REUSEADDR socket选项)
防SYN泛红攻击:TIME_WAIT状态时,不允许新连接,自然不处理SYN报文。
拓展:使用SO_REUSEADDR选项立即建立新连接后,如果收到TIME_WAIT旧连接的延迟数据,如何处理?
- 区分数据:
内核通常通过检查序列号SN等标识来区分TIME_WAIT连接和新连接数据。
- 处理:
如果数据与TIME_WAIT连接有关,内核丢弃数据。
13.5.3 静默时间
13.5.4 FIN_WAIT_2 状态
FIN_WAIT_2:
表示本端已发送关闭连接请求(FIN),并收到对方ACK。等待对方发送FIN报文。
对应内核参数net.ipv4.tcp.fin.timeout:
表示FIN_WAIT_2等待状态的最长时间,默认60秒。
如果在此时间内未收到对端的FIN报文,内核将释放/复位此连接。
13.5.5 同时打开与关闭的转换
13.6 重置报文段
即TCP头中带RST标志位的报文。
作用:强制终止TCP连接。
使用场景:
终止异常连接,如系统崩溃。
收到了不期望的SYN连接请求时,服务器拒绝连接请求。
收到非法数据流,终止连接。
下面章节详细介绍使用场景。
13.6.1 请求端口不存在
收到一个报文,本地没有监听该报文目的端口时:
UDP:回复ICMP目的地不可达(端口不可达)。
TCP:回复RST重置报文段。
13.6.2 终止一条连接
终止一条TCP连接的两种方法:
FIN:即正常四次挥手,有序释放,所有排队数据都已发送后再发送FIN,通常不会出现丢失数据情况。
RST:发送RST报文,断开连接。任何排队的数据都将被抛弃。
调用 close函数关闭套接字时,内核会尝试将套接字的发送缓冲区中的数据发送出去。
如果还有数据包没收到对端ACK,close函数会阻塞等待ACK确认或超时。
如果不启用SO_LINGER,会立即关闭套接字,而不管发送缓冲区中是否有未发送数据。
13.6.3 半开连接
半开连接:
含义:只单方面关闭一个方向连接。
造成原因:
客户端发送了FIN,并收到服务器ACK。但是服务器一直没有发送FIN。
此时只断开了单方向连接。客户端不能发送数据,但可从服务器接收数据。
典型场景:
通信一方的主机崩溃,断电,拔网线。
如果不通过半开连接传输数据,就无法检测出另一端已崩溃。
解决方法:
周期发送TCP keepalive消息来检测连接状态。
若超时没收响应,使用RST报文终止连接。
13.6.4 时间等待错误
时间等待错误(TIME-WAIT Assassination TWA)
网络滞留段有使得TIME_WAIT状态被意外结束。
举例:
TCP A收到旧连接延迟数据,并回复ACK。
TCP B收到莫名其妙的ACK后,会发出RST报文。
TCP A收到RST包后,TCP A提前从TIME-WAIT状态变为CLOSED状态。
解决方法:TIME_WAIT状态时忽略所有RST报文。
13.7 TCP服务器选项
13.7.1 TCP 端口号
查看本地TCP连接详细信息,包括使用的端口号:
netstat -antp
-a:显示所有连接,包括正连接和已建立。
-n:使用数字形式显示地址和端口。
-t: 仅显示TCP连接信息。
-p: 显示TCP连接的进程名和PID。
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN -
tcp 0 0 172.19.254.6:22 172.19.1.46:49244 ESTABLISHED -
tcp 0 0 172.19.254.6:445 172.16.15.62:65306 SYN_RECV -
13.7.2 限制本地IP地址
如果本地有多个IP,可指定使用哪个IP地址用于TCP连接。
server_addr.sin_addr.s_addr = inet_addr("192.168.1.100");
bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
反之:
server_addr.sin_addr.s_addr =INADDR_ANY;
此时服务器将监听所有网络接口上的数据。
13.7.3 限制外部节点
作用:
等待一个指定的客户端IP+端口发起主动连接。
伯克利套接字API没有该实现,即没有对应socket选项,只能等待连接到来后,再检查客户端的IP地址与端口号。
13.7.4 进入连接队列
半连接队列:
即SYN队列。
收到客户端的SYN报文时,服务器将连接请求放入半连接队列。此时服务器为SYN_RCVD 状态。
全连接队列:
当服务器调用accept函数接受连接,服务器将连接请求从半连接队列中移出,并放入全连接队列。此时服务器为ESTABLISHED状态。
内核参数net.ipv4.tcp_max_syn_backlog:
当服务器的SYN_RCVD状态的连接数超过该阈值,拒绝新连接请求。
未完成连接(backlog):
已完成TCP三次握手,但还没有通过accpet函数接收连接。
backlog队列最大值为:net.core.somaxconn,默认128。
服务器队列溢出时,如果收到客户端连接请求,如何处理?
如果回复RST报文给客户端,客户端会认为服务器存在并繁忙,后续会继续请求连接。
所以最好不回复任何消息,让客户端以为服务器不存在。
13.8 与TCP连接管理相关的攻击
- SYN泛洪攻击:
含义:
随机伪造源IP,发起大量SYN连接请求,使服务器维护大量半连接,耗尽系统资源尽。
半连接队列满后,服务器无法接受新连接请求,从而实现DoS攻击。
防攻击方法:
开启SYN cookies机制。
对应文件/proc/sys/net/ipv4/tcp_syncookies
SYN cookies工作原理:
服务器收到SYN包时,根据报文参数(如源目IP地址、源目端口号等)计算哈希值。
服务器将哈希值作为SYN+ACK包的序列号,将SYN+ACK发送给客户端。
客户端收到SYN+ACK包后,解析出序列号,并+1后作为ACK中确认号,将ACK回复给服务器。
服务器收到客户端ACK包后,将确认号-1,和之前的哈希值比较,若一致是合法请求。
开启SYN cookies后,不会在收到SYN报文后立刻创建半连接队列。
只有三次握手完毕才分配内存。
- MTU非常小的ICMP PTB packet too big
PTB:Packet Too Big
含义:
伪造ICMP PTB消息,其中包含非常小的下一跳MTU。
被攻击方TCP被迫只能发送非常小数据包,降低性能。
解决方法:
当ICMP PTB消息的下一跳MTU小于576字节时,禁用路径最大传输单元发现(PMTUD)功能。
- 欺骗攻击
含义:伪造4元组和校验都正确的RST报文,发给一个TCP连接。
解决方法:使用TCP-AO选项认证每一个报文。