TCP状态转移图说明及使用tcpdump进行观测
一、TCP状态转移图说明
图1.TCP状态转移图
这张图展示了 TCP(Transmission Control Protocol,传输控制协议)的状态转移图,描述了 TCP 连接在不同阶段之间的状态变化和相互转换。
(一)、建立连接(三次握手)
图2.TCP三次握手示意图
1、服务器准备好接受外来连接,通常通过socket、bind和listen完成。(服务器:CLOSED->LISTEN)
2、客户端通过connect连接服务器,客户端TCP将发送一个SYN包,告诉服务器客户端将在待建立连接发送数据的初始序列号。(客户端:CLOSED->SYN_SENT)
3、服务器端必须ACK客户端SYN,同时发送一个SYN,告诉客户端,服务器将在待建立连接发送数据的初始序列号。(服务器:SYN_RCVD)
4、客户端ACK服务器SYN。(客户端:SYN_SENT->ESTABLISHED)
5、服务器接收到客户端ACK。(服务器:SYN_RECV->ESTABLISHED)
(二)、建立连接(同时打开simultaneous open)
参考《TCP/IP详解》卷一第18章18.8节。这种情况发生在两端几乎同时发送SYN并且这两个SYN在网络中交错的情形。这种情况可能发生,但是非常罕见。
例如,主机A的应用程序使用本地端口7777,并主动连接主机B的端口8888。主机B的应用程序使用本地端口8888,并主动连接主机A的端口7777。
下图给出同时打开过程:
图3.TCP建立连接同时打开示意图
(三)、断开连接(四次挥手)
图4.TCP四次挥手打开示意图
1、客户端首先调用close,该端发送一个FIN包,表示数据发送完毕,该应用程序再无更多数据发送给对端。(客户端:ESTABLISHED->FIN_WAIT_1)
2、接收到FIN的对端执行被动关闭,FIN包意味着接收端应用进程在相应的连接上再无额外数据可接收。在收到FIN报文的时候,TCP协议栈会为FIN包插入一个文件结束符EOF到接收缓冲区中,服务端应用程序可以通过read调用来感知这个FIN包,这个EOF会被放在已排队等候的其它已接收的数据之后,所以服务端会先read完接收缓冲区已接收的数据,然后read到EOF,read()就会返回0。然后服务端会ACK这个收到的FIN包。(服务端:ESTABLISHED->CLOSE_WAIT,客户端:FIN_WAIT_1->FIN_WAIT_2)
3、服务端可能会立即关闭socket,也可能继续发送一些数据给客户端后再执行关闭scoket(由上层应用程序决定),所以,客户端的 ACK和 FIN一般都会分开发送,这里就会导致次数增加1。
4、服务端关闭socket时,将发送一个FIN包给客户端。(服务端:CLOSE_WAIT->LAST_ACK)
5、接收FIN的客户端ACK这个FIN。(被动端:LAST_ACK->CLOSED,主动端:FIN_WAIT_2->TIME_WAIT(2MSL之后,TIME_WAIT->CLOSED))
(四)、断开连接(同时关闭simultaneous close)
参考《TCP/IP详解》卷一第18章18.9节。我们在前面讨论了一方(通常但不总是客户方)发送第一个FIN执行主动关闭。双方都执行主动关闭也是可能的,TCP协议也允许这样的同时关闭(simultaneous close)。
下图给出同时关闭过程:
图5.TCP连接同时关闭示意图
二、使用tcpdump观测
(一)、观测TCP连接的建立
TCP三次握手的过程通常涉及以下三个步骤:
1、SYN包发送:客户端向服务器发送一个SYN(同步序列编号)包,请求建立连接。SYN包中的序列号是一个随机值,用于标识该TCP连接的初始序列号。
2、SYN-ACK包响应:服务器收到SYN包后,会发送一个SYN-ACK(同步序列编号确认)包作为响应。这个包同时包含了服务器的SYN序列号(也是一个随机值)和对客户端SYN包的确认(即客户端SYN包的序列号加1)。
3、ACK包确认:客户端收到SYN-ACK包后,会发送一个ACK(确认)包给服务器。这个包中的确认号设置为服务器SYN序列号加1,表示客户端已经成功接收到了服务器的SYN包。
以下是一个tcpdump输出示例,展示了TCP三次握手的过程:
tcpdump -i any tcp port 8888 -S
在tcpdump
命令中,-S
选项用于指定在打印TCP数据包时,不将序列号(sequence numbers)转换为相对序列号。默认情况下,当tcpdump
显示TCP数据包时,它会尝试将序列号转换为相对于第一个捕获到的数据包的序列号(即相对序列号),以便更容易地观察数据包之间的顺序和数据流。然而,在某些情况下,用户可能希望看到数据包的原始序列号,而不是相对序列号,这时就可以使用-S
选项。
(二)、观测TCP连接的关闭
在TCP连接的关闭过程中,你会看到以下类型的TCP数据包:
1、FIN包:表示一方(通常是客户端)想要关闭连接。FIN包的TCP标志位中FIN标志被设置。
2、ACK包:对FIN包的确认。接收方发送ACK包,其确认号设置为FIN包的序列号加1。
3、反向FIN包:如果双方都关闭了它们的数据发送通道,则接收FIN包的一方也会发送一个FIN包。
4、最终ACK包:对反向FIN包的确认,标志着TCP连接的完全关闭。
以下是一个tcpdump输出示例,展示了TCP四次挥手的过程:
- 第一行是客户端发送的FIN包,这是第一次挥手。
- 第二行是服务器对FIN包的确认,并发送自己的FIN包,这是第二次和第三次挥手,常常会将这两次挥手合并,只发送一个
Flags [ F .]
包,即ACK
和FIN。
- 第三行是客户端对服务器FIN包的确认。
(三)、观测server端不启动服务,client端尝试连接
Server(127.0.0.1:8888)未启动服务,如图抓包所示,Client(127.0.0.1:49544)发送SYN(localhost.49544 > localhost.8888: Flags [S]
)启动TCP连接,Server返回localhost.8888 > localhost.49544: Flags [R.]
,R=RESET表示异常关闭连接,连接重置。
(四)、模拟出现SYN_SENT状态
1、设置防火墙iptables丢弃发送给Server(127.0.0.1:8888)的数据包,这样Server不会收到Client发送的SYN。
iptables -I INPUT -s 127.0.0.1 -p tcp --dport 8888 -j DROP
参数说明:
-
iptables
:这是用于设置、维护和检查 IPv4 数据包过滤规则的工具。 -
-I INPUT
:-I
选项用于向指定的链(在这个例子中是INPUT
链)中插入一条规则。INPUT
链用于处理进入本机的数据包。默认情况下,iptables 的规则是按照添加的顺序来处理的,但使用-I
选项时,你可以指定将规则插入到链的开头(如果不指定位置的话)。 -
-s 127.0.0.1
:-s
选项用于指定源 IP 地址。在这个例子中,它指定了源 IP 地址为127.0.0.1
,即本机地址(回环地址)。这意味着这条规则只适用于从本机发出的数据包。 -
-p tcp
:-p
选项用于指定协议类型。这里指定为tcp
,表示这条规则只适用于 TCP 协议的数据包。 -
--dport 8888
:这个选项用于指定目标端口(destination port)。这里指定为8888
,意味着这条规则只适用于目标端口为 8888 的数据包。 -
-j DROP
:-j
选项用于指定匹配规则后采取的动作(jump to target)。DROP
是一个目标动作,意味着如果数据包与这条规则匹配,那么该数据包将被丢弃,且不会给出任何响应
2、使用tcpdump抓取的输出如下:
我们一共抓取了7个TCP报文段,它们都是同步报文段,并且具有相同的序列号,这说明后面6个同步报文段都是超时重连报文段,观察这些TCP报文段的时间间隔,它们分别是1s、2s、4s、8s、16s、32s。因此,TCP模块一共执行了6次重连操作,这是由/proc/sys/net/ipv4/tcp_syn_retries 内核参数定义的。每次重连的超时时间都增加一倍。在6次重连均失败的情况下,TCP模块放弃重连并通知应用程序。
(base) [root@HBD1MM opensource]# cat /proc/sys/net/ipv4/tcp_syn_retries 6
3、netstatat命令也能看到,此时客户端的连接处于SYN_SENT状态。
(五)、模拟出现SYN_RCVD状态
1、设置防火墙丢弃Server(127.0.0.1:8888)发送出去的数据包,这样Client不会收到Server发送的SYN&ACK包。
iptables -I INPUT -s 127.0.0.1 -p tcp --sport 8888 -j DROP
2、使用tcpdump抓取的输出如下:
Client(localhost:49728)发送SYN给Server(localhost:8888),Server发送SYN ACK给Client,但是被防火墙丢弃Server发送Client端的SYN&ACK,Client也就不会发送SYN ACK给Server。
3、Client TCP连接状态转移为SYN_SENT,但是不会转移到ESTABLISHED,Server TCP连接状态由LISTEN转移到SYN_RCVD,但是不会转移到ESTABLISHED。使用netstat命令可观察到:
(六)、模拟出现FIN_WAIT1状态
1、Client在断开连接之前,设置防火墙丢弃Client发送给Server(127.0.0.1:2017)的数据包,这样Server不会收到Client发送的FIN而返回FIN ACK。
2、使用tcpdump抓取的输出如下:
3、Client收不到Server发送的FIN ACK, TCP状态不能由FIN_WAIT1转移到FIN_WAIT2;Server TCP状态为ESTABLISHED,Server接收不到Client的FIN而没有发送FIN ACK, TCP状态不能由ESTABLISHED转移到CLOSE_WAIT。Client TCP连接状态保持在FIN_WAIT1,达到最大重试次数后,超时转移到CLOSED。使用netstatat命令可观察到: