目录
问题现象
故障定位
TCP报头
options详解
Opions Kind有哪些
options中的Timestamp详解
TSval & TSecr
Timestamp Value的单位是什么
TCP连接的建立与释放
普通三次握手
带时间戳的三次握手
抓包展示带时间戳的tcp会话
WireShark中的时间 VS tcpdump 直接查看的时间
四次挥手
Linux下TCP常用的内核参数详解
net.ipv4.tcp_tw_recycle
net.ipv4.tcp_tw_recycle = 1 是不是表示,收到FIN后立马断开连接
net.ipv4.tcp_tw_reuse
TCP连接中TIME_WAIT是什么
故障原因
直接原因
根本原因
查看因为时间戳错乱丢弃的报文数量
处理方法
关闭tcp_tw_recycle参数并立即生效
如何查看系统当前系统已建立的tcp连接
问题现象
- ping测试低延迟,0丢包
- 通过curl访问web偶尔卡顿
- telnet连接服务端偶尔超时
- ftp访问服务端偶尔超时
故障定位
通过故障现象可以得出以下结论:
二层与三层网络正常
tcp连接超时,问题一定出在tcp建立连接的过程导致
进一步分析,很容易发现是curl、telnet、ftp客户端发出tcp的syn请求,服务端由于某种原因收到了syn报文却将之忽略,导致连接异常。
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
options详解
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Kind | Option Length | Option Data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Kind | Option Length | Option Data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
选项类型(Option Kind):一个8位的字段,用于标识选项类型。不同的选项类型有不同的功能和长度。
-
选项长度(Option Length):一个8位的字段,指示选项的长度,包括选项类型字段和选项数据字段的总长度。
-
选项数据(Option Data):根据选项类型和选项长度确定的数据字段。它可以是固定长度的,也可以是可变长度的,具体取决于选项类型。
TCP报头中可以包含一个或多个选项,每个选项由上述三个字段组成。选项按照从左到右的顺序排列,并以字节对齐的方式进行填充。
Opions Kind有哪些
TCP报头中的Option Kind(选项类型)字段用于标识不同的TCP选项。以下是一些常见的TCP选项类型:
-
0 - End of Option List(终止选项):指示选项列表的结束。
-
1 - No-Operation(无操作):用于对齐后续选项字段的字节边界,没有实际功能。
-
2 - Maximum Segment Size(最大报文长度):用于告知对方端可以接收的最大TCP报文段的长度。
-
3 - Window Scale(窗口扩大因子):用于扩大TCP窗口的大小,以支持更大的窗口值。
-
4 - Selective Acknowledgment Permitted(选择确认提醒):在建立连接时通知对方可以使用SACK选项。
-
5 - Selective Acknowledgment(选择确认):用于报告已成功接收的数据段范围,从而可以告诉发送方需要重新传输的丢失数据段。
-
8 - Timestamp(时间戳):用于对TCP报文进行时间戳标记,以支持一些高级特性和算法,如RTT(往返时间)估计和序号预测。
这只是一些常见的TCP选项类型,实际上还有其他类型的选项,例如SACK-Permitted(选择确认提醒)、SACK-Extended(扩展的选择确认)等。每个选项类型都有特定的功能和格式,用于满足特定的通信需求和优化TCP性能。
需要注意的是,TCP选项的使用必须得到双方的协商和支持,不同的实现可能支持不同的选项类型。
options中的Timestamp详解
Timestamp(时间戳)是TCP报头中的一种选项,用于对TCP报文进行时间戳标记。它提供了一种机制,允许发送方和接收方在TCP连接中交换时间戳信息。Timestamp选项通常用于实现一些高级特性和算法,如往返时间(RTT)估计、序号预测和拥塞控制等。
Timestamp选项由两个32位的字段组成:
-
发送方时间戳(Timestamp Value):这个字段包含了发送方的时间戳值。发送方在每个TCP报文中都会将自己的时间戳值放置在这个字段中。
-
接收方时间戳回显(Timestamp Echo Reply):这个字段由接收方使用,用于回显发送方发送的时间戳值。接收方将收到的时间戳值放置在这个字段中,然后将其发送回发送方。
通过在TCP报文中使用时间戳选项,可以实现以下功能:
-
往返时间(RTT)估计:发送方可以在报文中包含自己的时间戳,接收方在回复报文中回显该时间戳。通过计算发送方发送报文和接收方回复报文之间的时间差,可以估计往返时间。
-
序号预测:时间戳可以帮助发送方预测接收方下一个期望的序号。发送方可以通过观察接收方的时间戳值和回显值,推断出接收方已经接收到的数据量和速度,从而更准确地选择下一个发送的数据段的序号。
-
拥塞控制:时间戳信息可以用于拥塞控制算法,如基于时间戳的拥塞避免算法。通过观察时间戳值和回显值的变化趋势,发送方可以调整自己的发送速率,以适应网络的拥塞情况。
需要注意的是,时间戳选项是可选的,并且不是所有的TCP连接都会使用时间戳选项。它的使用需要双方的协商和支持,且仅在需要时才会被启用。
TSval & TSecr
TSval :这是指报文中的Timestamp Value字段的值
TSecr :这是指报文中的Timestamp Echo Reply字段的值
通过比较这些时间戳值,可以实现往返时间(RTT)估计、序号预测和拥塞控制等功能。
Timestamp Value的单位是什么
在TCP报头的Timestamp选项中,Timestamp Value字段是一个32位的无符号整数,用于记录发送方在特定时间点的时间戳。这个时间戳值表示从某个参考时间开始到当前时间的经过时间,单位为毫秒。
TCP连接的建立与释放
普通三次握手
带时间戳的三次握手
带时间戳的三次握手即每一个tcp报文都打上时间戳,开启tcp报头中option的timestamp选项即可,在linux中通过设置net.ipv4.tcp_timestamps=1即可开启(默认是开启的),需要双方都开启才会在每个每个tcp报文中打时间戳,开启的一方在发送SYN报文默认会打时间戳,如果收到的报文没有打时间戳,后续发送方也不会继续打时间戳;关闭的一方发送没有时间戳的报文,接收的一方不管是否开启时间戳功能都不会在报文中打时间戳。
抓包展示带时间戳的tcp会话
09:18:59.052407 IP phy.47528 > aa.http: Flags [S], seq 3915756483, win 29200, options [mss 1460,sackOK,TS val 2553000 ecr 0,nop,wscale 7], length 0
09:18:59.052413 IP phy.47528 > aa.http: Flags [S], seq 3915756483, win 29200, options [mss 1460,sackOK,TS val 2553000 ecr 0,nop,wscale 7], length 0
09:18:59.052610 IP aa.http > phy.47528: Flags [S.], seq 1867939121, ack 3915756484, win 28960, options [mss 1460,sackOK,TS val 4294737727 ecr 2553000,nop,wscale 7], length 0
09:18:59.052610 IP aa.http > phy.47528: Flags [S.], seq 1867939121, ack 3915756484, win 28960, options [mss 1460,sackOK,TS val 4294737727 ecr 2553000,nop,wscale 7], length 0
09:18:59.052629 IP phy.47528 > aa.http: Flags [.], ack 1, win 229, options [nop,nop,TS val 2553000 ecr 4294737727], length 0
09:18:59.052630 IP phy.47528 > aa.http: Flags [.], ack 1, win 229, options [nop,nop,TS val 2553000 ecr 4294737727], length 0
09:18:59.052677 IP phy.47528 > aa.http: Flags [P.], seq 1:67, ack 1, win 229, options [nop,nop,TS val 2553000 ecr 4294737727], length 66: HTTP: GET / HTTP/1.1
09:18:59.052679 IP phy.47528 > aa.http: Flags [P.], seq 1:67, ack 1, win 229, options [nop,nop,TS val 2553000 ecr 4294737727], length 66: HTTP: GET / HTTP/1.1
09:18:59.052757 IP aa.http > phy.47528: Flags [.], ack 67, win 227, options [nop,nop,TS val 4294737727 ecr 2553000], length 0
09:18:59.052757 IP aa.http > phy.47528: Flags [.], ack 67, win 227, options [nop,nop,TS val 4294737727 ecr 2553000], length 0
09:18:59.053161 IP aa.http > phy.47528: Flags [.], seq 1:4345, ack 67, win 227, options [nop,nop,TS val 4294737727 ecr 2553000], length 4344: HTTP: HTTP/1.1 403 Forbidden
09:18:59.053161 IP aa.http > phy.47528: Flags [.], seq 1:4345, ack 67, win 227, options [nop,nop,TS val 4294737727 ecr 2553000], length 4344: HTTP: HTTP/1.1 403 Forbidden
09:18:59.053193 IP phy.47528 > aa.http: Flags [.], ack 4345, win 296, options [nop,nop,TS val 2553000 ecr 4294737727], length 0
09:18:59.053195 IP phy.47528 > aa.http: Flags [.], ack 4345, win 296, options [nop,nop,TS val 2553000 ecr 4294737727], length 0
09:18:59.053198 IP aa.http > phy.47528: Flags [P.], seq 4345:5150, ack 67, win 227, options [nop,nop,TS val 4294737727 ecr 2553000], length 805: HTTP
09:18:59.053198 IP aa.http > phy.47528: Flags [P.], seq 4345:5150, ack 67, win 227, options [nop,nop,TS val 4294737727 ecr 2553000], length 805: HTTP
09:18:59.053201 IP phy.47528 > aa.http: Flags [.], ack 5150, win 319, options [nop,nop,TS val 2553000 ecr 4294737727], length 0
09:18:59.053202 IP phy.47528 > aa.http: Flags [.], ack 5150, win 319, options [nop,nop,TS val 2553000 ecr 4294737727], length 0
09:18:59.053327 IP phy.47528 > aa.http: Flags [F.], seq 67, ack 5150, win 319, options [nop,nop,TS val 2553001 ecr 4294737727], length 0
09:18:59.053329 IP phy.47528 > aa.http: Flags [F.], seq 67, ack 5150, win 319, options [nop,nop,TS val 2553001 ecr 4294737727], length 0
09:18:59.053407 IP aa.http > phy.47528: Flags [F.], seq 5150, ack 68, win 227, options [nop,nop,TS val 4294737727 ecr 2553001], length 0
09:18:59.053407 IP aa.http > phy.47528: Flags [F.], seq 5150, ack 68, win 227, options [nop,nop,TS val 4294737727 ecr 2553001], length 0
09:18:59.053422 IP phy.47528 > aa.http: Flags [.], ack 5151, win 319, options [nop,nop,TS val 2553001 ecr 4294737727], length 0
09:18:59.053425 IP phy.47528 > aa.http: Flags [.], ack 5151, win 319, options [nop,nop,TS val 2553001 ecr 4294737727], length 0
tcp_timestamps默认是开启,如果再把tcp_tw_recycle设置为1,则一定时间内同一源ip主机的socket connect请求中的timestamp必须是递增的。 也就是说服务器打开了 tcp_tw_reccycle了,就会检查时间戳,如果对方发来的包的时间戳是乱跳的或者说时间戳是滞后的,这样服务器肯定不会回复, 所以服务器就把带了“倒退”的时间戳的包当作是错误的报文,直接丢掉不回包,就出现了开始说的syn不响应。
给上述抓包timestamp着色,相同颜色部分的时间是相同的
通过上述着色的时间可以发现,两个机器整个tcp会话共用了1毫秒。
上图最左侧的时间为操作系统给每条报文标记的系统时间,报文本身是无感的,可以通过最左端的时间标记验证options中的时间戳,可以发现是一样的,只是时间精度不一样,一个精确到了微秒,一个精确到毫秒。
WireShark中的时间 VS tcpdump 直接查看的时间
如果用WireShark抓包,在options之外还能看到一些时间信息和时间戳,如下图所示(相同的报文用WireShark打开):
通过对比可以发现:
- WireShark将系统时间展示在了frame下面,并把时间精度提高到了纳秒(用0作了补全)。如截图部分展示的Arrival Time: May 11, 2023 09:21:44.939787000 中国标准时间
- WireShark最左端的时间重新做了计算,用收到的第一个frame做参考,依次是收到每个frame的时间间隔。
- WireShark在报文中添加了很多自己的解释说明用方括号 [ ] 括起来,用于体现,那些字段并非报文中的数据。
- [Time since first frame in this TCP stream: ] 表示的是这个tcp数据流距离第一个数据包(frame)的发送时间已经过去的时间。
- [Time since previous frame in this TCP stream: ] 表示的是这个tcp数据流距离上一个数据包(frame)的发送时间已经过去的时间。
四次挥手
Linux下TCP常用的内核参数详解
net.ipv4.tcp_tw_recycle
net.ipv4.tcp_tw_recycle = 0
:表示是否开启TCP连接中TIME_WAIT状态的快速回收机制,其默认值为0,即关闭该机制。当该值为1时,内核会开启快速回收机制,会影响到一些应用程序的正常运行,因此一般不建议开启该选项。
net.ipv4.tcp_tw_recycle = 1 是不是表示,收到FIN后立马断开连接
不完全正确。net.ipv4.tcp_tw_recycle
参数的值为1表示开启了TCP连接中TIME_WAIT状态的快速回收机制,该机制可能会在一定程度上缩短TIME_WAIT状态的持续时间,但并不是简单地在收到FIN报文后立即断开连接。
在快速回收机制开启的情况下,如果内核收到的ACK报文序号比当前已经关闭的TCP连接的最大序号还要早,那么内核就可以安全地假设这是一个新的连接,然后立即将该连接关闭,而不需要进入TIME_WAIT状态。这样可以释放系统资源并减少TIME_WAIT状态的持续时间,从而提高系统性能和连接吞吐量。
net.ipv4.tcp_tw_reuse
net.ipv4.tcp_tw_reuse = 0
:表示是否允许在TIME_WAIT状态下,对新的TCP连接请求重用该端口。其默认值为0,即不允许重用。当该值为1时,表示允许端口重用,可以在一定程度上减少TIME_WAIT状态下的资源占用,但也会带来一些潜在的安全问题,因此一般不建议开启该选项。
TCP连接中TIME_WAIT是什么
在TCP协议中,当一端主动关闭连接时,它会发送一个FIN报文给对端,表示它不再发送数据了,但是它仍然可以接收对端发送的数据。接收到FIN报文的对端会回复一个ACK报文表示已经收到了FIN报文,然后对端也发送一个FIN报文给第一端,表示对端也不再发送数据了。第一端接收到对端的FIN报文后,回复一个ACK报文表示已经收到了对端的FIN报文,然后进入TIME_WAIT状态。
TIME_WAIT状态的目的是为了确保在网络中已经传输完毕的数据都已经被接收方正确处理。在这个状态下,端口不能立即被重用,因为这个端口可能还有残留数据包或ACK包在网络中传输,如果此时重用该端口会导致数据包混淆,引起通信异常。TIME_WAIT状态默认持续2个MSL(最长报文段寿命),也就是一般约为2分钟左右。
因此,TCP连接中的TIME_WAIT状态是在一端关闭连接后,等待一段时间以确保连接的正确关闭,并且在这个状态下端口不能被立即重用。
故障原因
直接原因
在开启tcp_tw_recycle和tcp_timestamp后,服务器会检测同一源IP主机的每个SYN报文的时间戳,将时间戳滞后的SYN报文丢弃,导致三次握手失败。
根本原因
客户端内核在发送tcp报文的时候由于未知的原因将后发送的tcp报文打了更早的时间戳,没有严格的按照时间流逝的顺序给tcp报文打时间戳。
查看因为时间戳错乱丢弃的报文数量
netstat -s | grep timestamp
处理方法
关闭tcp_tw_recycle参数并立即生效
- 打开终端并以 root 用户身份登录系统。
- 使用以下命令之一编辑 /etc/sysctl.conf 文件,并修改需要修改的参数的值:
vi /etc/sysctl.conf
如果文件中已经存在需要修改的参数,则直接修改其对应的值即可。如果不存在,则可以添加以下行来设置参数的值:
net.ipv4.tcp_tw_recycle = 0
3.使用以下命令使修改后的参数立即生效:
sysctl -p
这个命令会重新加载 /etc/sysctl.conf 文件,并将其中的参数设置立即应用到系统中,而不需要重启系统。
临时生效可以通过 sysctl -w net.ipv4.tcp_tw_recycle=0
如何查看系统当前系统已建立的tcp连接
可以使用以下命令查看当前系统已建立的TCP连接:
netstat -an | grep -i "tcp" | grep -i "established"
如果希望查看每个连接的详细信息,可以加上 -p
参数来显示进程信息:
netstat -anp | grep -i "tcp" | grep -i "established"
该命令会显示每个连接所对应的进程 ID 和进程名。另外,还可以通过加上 -t
参数来只显示 TCP 连接:
netstat -antp | grep -i "established"
该命令会列出所有处于 ESTABLISHED 状态的 TCP 连接,以及它们所对应的本地地址、远程地址、状态、进程 ID 和进程名等信息。
需要注意的是,netstat
命令可以在大多数 Linux 和 Unix 系统上使用,但在一些新的系统中可能已被废弃,推荐使用 ss
命令或其他类似的工具。