tcp connection open and close

wireshark install and usage

下面示例中会用到wireshark抓包工具,安装wireshark并且通过一个 http 访问来查看 TCP 连接打开关闭请求的过程。

 brew install wireshark --with-qt sudo wireshark-qt

wireshark显示过滤规则(display filter)简单示例,更多使用说明可查阅参考链接

host 10.1.2.3ip.addr == 10.1.1.1ip.addr eq 10.1.1.1ip.src != 10.1.2.3 and ip.dst != 10.4.5.6tcp.port == 25tcp.dstport == 25http.request.method == "POST"http.host == "www.google.com"

注意与tshark抓包的过滤规则-f的区别,显示过滤规则是对抓包结果展示时使用的,数据包可能是完整的完全抓取,在tshark里使用参数-Y控制显示过滤规则。

tshark抓包过滤规则示例:

ip src host 10.1.1.1src host 10.7.2.12 and not dst net 10.200.0.0/16tcp dst port 3128

wireshark中打开有数据流量的那个网络设备,并打开新的Terminal输入以下命令,发起一个 http 请求:

 curl -o index.html http://www.baidu.com

请求完成将wireshark监控暂停,并保存结果到文件,方便重新分析使用。

TCP 连接建立

TCP 用三次握手three-way handshake过程建立一个连接,三次握手过程示意图如下:

TCP 连接建立一般是由服务器端打开一个套接字socket,然后监听来自客户端的连接,所以服务器端表示为被动打开passive open,客户端表示为主动打开active open,建立连接过程:

  1. SYN: 客户端通过向服务器端发送一个SYN来建立一个主动打开,作为三次握手的一部分。客户端把这段连接的初始序号设定为随机数A
  2. SYN-ACK: 服务器端应当为一个合法的SYN回送一个SYN/ACKACK确认号在接收的序号上加1,也就是A+1SYN/ACK包本身又有一个随机序号B
  3. ACK: 最后,客户端再发送一个ACK,当服务端受到这个ACK的时候,就完成了三次握手,并进入了连接建立状态,此时包序号被设定为收到的确认号A+1,而响应则为B+1

wireshark 抓包截图如下:

如上图所示,前面 3 条记录是 TCP 建立连接的 3 次握手,图中的显示的初始序号为0,是为了方便查看而显示的序号相对值。

TCP 连接关闭

TCP 连接关闭使用了四次挥手过程four-way handshake,四次挥手过程示意图如下:

  1. 主动关闭方发送一个FIN并进入FIN_WAIT_1状态,并包括一个序号X
  2. 被动关闭方接收到主动关闭方发送的FIN,然后回复ACK确认号X+1和已方的序号Y给主动关闭方,此时被动关闭方进入CLOSE_WAIT状态;主动关闭方收到被动关闭方的ACK后,进入FIN_WAIT_2状态;
  3. 被动关闭方发送一个FIN给主动关闭方,包括ACK确认号X+1和已方的一个序号Y,并进入LAST_ACK状态;
  4. 主动关闭方收到被动关闭方发送的FIN,然后回复ACK确认号Y+1和已方的序号X+1给被动关闭方,此时主动关闭方进入TIME_WAIT状态,经过2*MSL时间后关闭连接;被动关闭方收到主动关闭方的ACK后,关闭连接。

wireshark 抓包截图如下:

如上图所示,最后面 4 条是关闭连接的 4 次挥手,红字那条表示收到重复Ack确认号,这实际上是对的(先发一次Ack确认号,再发一次Fin+Ack关闭通知),最后本机向服务器发了一条确认关闭的Ack确认号。

数据传输

在 TCP 的数据传输过程中,很多重要的机制保证了 TCP 的可靠性和强壮性,它们包括:

  1. 使用序号,对收到的 TCP 报文段进行排序以及检测重复的数据。
  2. 使用校验和来检测报文段的错误。
  3. 使用确认号和重传计时器来检测和纠正丢包或延时。

序号 Seq 和确认号 Ack

  1. 在 TCP 的连接建立状态,两个主机的 TCP 层间要交换初始序号 ISN: initial sequence number
  2. 这些序号用于标识字节流中的数据,并且还是对应用层的数据字节进行记数的整数。
  3. 通常在每个 TCP 报文段中都有一对序号和确认号。
  4. TCP 报文发送者将自己的字节编号做为序号,而将接收者的字节编号做为确认号。
  5. TCP 报文的接收者为了确保可靠性,在接收到一定数量的连续字节流后才发送确认。
  6. SeqAck是以字节数为单位,所以Ack的时候,不能跳着确认,只能确认连续收到的Seq最大的包。
  7. 这是对 TCP 的一种扩展,通常称为选择确认Selective Acknowledgment
  8. 选择确认使得 TCP 接收者可以对乱序到达的数据块进行确认,每一组字节传输过后,ISN号都会递增1

通过使用序号和确认号,TCP 层可以把收到的报文段中的字节按正确的顺序交付给应用层。序号是 32 位的无符号数,在它增大到2^32-1时,便会回绕到 0。对于 ISN 的选择是 TCP 中关键的一个操作,它可以确保强壮性和安全性。

数据传输时的序号和确认号及 TCP 报文中数据长度的关系如下,注意与 TCP 连接建立和关闭不一样:

Sequence Number In + Bytes of Data Received = Acknowledgment Number Out

当前发送的报文序号和下一个报文序号之间的关系:

Sequence Number Out + Bytes of Data Sent = Acknowledgment Number IN = Next Sequence Number Out

举例客户端发出的 TCP 报文的序号Sequence Number为 1,报文总长度Total Length1480bytes,减去 IP 头长度20bytes,再减去 TCP 头长度20bytes,实际数据的长度为1440bytes,服务器正确收到报文后应该返回一个数据长度为 0 的ACK报文,其中Acknowledgment Number应该为1441,并且客户端下一个 TCP 报文的序号也是1441,计算公式如下:

1 + 1440 = 1441

TCP 数据传输过程举例一

  1. 上图所示的最上面三条报文说明了 TCP 连接建立的 3 次握手过程。
  2. 上图所示的第 32 条记录,服务器发送第 1 个包含序号为 1(相对值)和 1440 字节数据的 TCP 报文段给客户端。
  3. 上图所示的第 33 条记录,服务器发送第 2 个包含序号为 1441(1+1440)和 1440 字节数据的 TCP 报文段给客户端。
  4. 上图所示的第 34 条记录,服务器发送第 3 个包含序号为 2881(1441+1440)和 1440 字节数据的 TCP 报文段给客户端。
  5. 上图所示的第 35 条记录,服务器发送第 4 个包含序号为 4321(2881+1440)和 930 字节数据的 TCP 报文段给客户端。
  6. 上图所示的第 36 条记录,客户端返回第 1 个包含序号为 87,确认号为 2881(1441+1440)和 0 字节数据的 TCP 报文段给服务器,通知服务器已经正确收到服务器发过来的前 2 个报文段。说明:数据包都是连续的情况下,接收方没有必要每一次都回应,比如,他收到第 1 到 2 条 TCP 报文段,只需回应第 2 条就行了。
  7. 上图所示的第 37 条记录,客户端返回第 2 个包含序号为 87,确认号为 4321(2881+1440)和 0 字节数据的 TCP 报文段给服务器,通知服务器已经正确收到服务器发过来的前 3 个报文段。
  8. 上图所示的第 38 条记录,客户端返回第 3 个包含序号为 87,确认号为 5251(4321+930)和 0 字节数据的 TCP 报文段给服务器,通知服务器已经正确收到服务器发过来的全部 4 个报文段。
  9. 第 39 条记录中客户端发起 TCP 连接关闭FIN请求,第 40 条服务器返回一个FIN请求,在FIN消息前缺少一条服务器给客户端的ACK报文,最后客户端回复服务器一个ACK报文,TCP 连接关闭。
  10. 假如在上述例子中服务器发出的第 2 条 TCP 报文段被丢失了,所以尽管客户端收到了后面的第 3 条和第 4 条,然而他只能回应第 1 条ACK报文,确认号为1441
  11. 服务器在发送了第 2 条以后,没能收到回应,会在重传计时器超时后,重发第 2 条。当第 2 条被成功接收,接收方可以直接确认第 4 条,因为第 3 条和第 4 条已收到。

决定报文是否有必要重传的主要机制是重传计时器Retransmission Timer,它的主要功能是维护重传超时RTO值。当报文使用 TCP 传输时,重传计时器启动,收到ACK时计时器停止。

报文发送至接收到ACK的时间称为往返时间Round Trip Time - RTT

对若干次时间取平均值,该值用于确定最终RTO值。在最终RTO值确定之前,确定每一次报文传输是否有丢包发生。当报文发送之后,但接收方尚未发送ACK报文,发送方假设源报文丢失并将其重传。重传之后,RTO值加倍;如果在 2 倍RTO值到达之前还是没有收到ACK报文,就再次重传。如果仍然没有收到ACK,那么RTO值再次加倍。如此持续下去,每次重传RTO都翻倍,直到收到ACK报文或发送方达到配置的最大重传次数。

最大重传次数取决于发送操作系统的配置值,默认情况下,Windows 主机默认重传5次,大多数 Linux 系统默认最大15次,两种操作系统都可配置。

TCP 数据传输过程举例二 (2019-03-28 更新)

UNIX Network Programming, Volume 1, Second Edition: Networking APIs: Sockets and XTI, Prentice Hall, 1998, ISBN 0-13-490012-X 这里下载 sock 程序源码,另外我也在 github 上放了一份源码和编译生成的可执行程序,或者直接从 sourceforge.net 下载二进制 sock 程序。

上图中下面的命令是用sock启动服务端,然后启动下图中的tshark命令进行数据包捕获,最后执行上图上面的命令,捕获的数据包如下:

从上图中可以查看 TCP 连接建立和关闭时的seqack值的变化,与连接建立之后数据传输过程中的seqack计算方式不同,服务端没有发送数据,所以在客户端收到最后一个FIN前,服务端的数据包的seq始终是1

TCP 状态机

下表为TCP状态码列表:

  1. LISTEN: 服务启动后首先处于侦听LISTENING状态。
  2. SYN_SENT: 在发送连接请求后等待匹配的连接请求。通过 connect()函数向服务器发出一个同步SYNC信号后进入此状态。
  3. SYN_RECEIVED: 已经收到并发送同步SYNC信号之后等待确认ACK请求。
  4. ESTABLISHED : 连接已经建立,表示 2 台机器可以相互通信,此时连接两端是平等的。
  5. FIN_WAIT_1 : 主动关闭端调用close()函数发出 FIN 请求包,表示本方的数据发送全部结束,等待 TCP 连接另一端的确认包或FIN请求包。
  6. FIN_WAIT_2 : 主动关闭端在FIN_WAIT_1状态下收到确认包,进入等待远程 TCP 的连接终止请求的半关闭状态,这时可以接收数据,但不再发送数据。
  7. CLOSE_WAIT : 被动关闭端接到FIN后,就发出ACK以回应FIN请求,并进入等待本地用户的连接终止请求的半关闭状态,这时可以发送数据,但不再接收数据。
  8. CLOSING : 双方同时发出FIN,同时进入等待对方对连接终止FIN的确认ACK时进入的状态,极少见。
  9. LAST_ACK : 被动关闭端全部数据发送完成之后,向主动关闭端发送FIN,进入等待确认包的状态。
  10. TIME_WAIT : 主动关闭端接收到FIN后,就发送ACK包,等待足够时间(2 倍MSL时间)以确保被动关闭端收到了终止请求的确认包。
  11. CLOSED : 连接关闭,代表双方无任何连接状态。

状态机示意图一

状态机示意图二

TIME_WAIT and MSL

  1. MSL就是最大分节生命期maximum segment lifetime,这是一个 IP 数据包能在互联网上生存的最长时间,超过这个时间 IP 数据包将在网络中消失。
  2. MSL在 RFC 1122 上建议是2 分钟,Windows 默认是120秒,但微软建议设置为30秒,Linux 默认为60秒,而源自 berkeley 的 TCP 实现传统上使用30 秒
  3. TIME_WAIT状态维持时间是两个MSL时间长度,也就是在1-4 分钟Windows操作系统就是 4 分钟。
  4. 进入TIME_WAIT状态的一般情况下是客户端,如果并发 20 个线程,每个线程打开并关闭socket超过一定次数,就会产生太多TIME_WAIT状态的连接,并产生连接异常。
  5. 服务器端更多应该是CLOSE_WAIT状态。

统计 TCP 连接状态

 netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'TIME_WAIT 25ESTABLISHED 100LAST_ACK 12

服务器上的监听服务列表

# The word tulpen means tulips(郁金香) in german netstat -tulpen

上述命令输出如下:

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State User Inode PID/Program name
tcp 0 0 0.0.0.0:3000 0.0.0.0: LISTEN 0 11481 1497/node
tcp 0 0 0.0.0.0:1723 0.0.0.0:
LISTEN 0 9773 1010/pptpd
tcp 0 0 0.0.0.0:8000 0.0.0.0: LISTEN 0 10994 1376/node
tcp 0 0 203.88.168.146:8388 0.0.0.0:
LISTEN 0 9811 1018/ss-server
tcp 0 0 127.0.0.1:5000 0.0.0.0: LISTEN 0 4732265 30298/python
tcp 0 0 0.0.0.0:80 0.0.0.0:
LISTEN 0 9225 862/nginx
tcp 0 0 0.0.0.0:22 0.0.0.0: LISTEN 0 8997 777/sshd
tcp6 0 0 :::80 :::
LISTEN 0 9226 862/nginx
udp 0 0 127.0.0.1:123 0.0.0.0: 0 10618 1330/ntpd
udp 0 0 203.88.168.146:8388 0.0.0.0:
0 9816 1018/ss-server
udp6 0 0 :::123 :::* 0 10612 1330/ntpd

TCP/IP headers format

常见网络协议的头格式

  1. tcp-header-format.png
  2. ip-header-format.png
  3. udp-header-format.png
  4. icmp-header-format.png

TCP 三次握手和四次挥手的示意图

  1. tcp-simultaneous-open.png
  2. tcp-simultaneous-close.png
  3. tcp-open-close.jpg

文中配图主要来源于以下参考链接。

References

  1. Unix Network Programming, Volume 1 (3rd Edition)
  2. Transmission Control Protocol
  3. 传输控制协议
  4. TCP/IP Reference
  5. wireshark-filter - Wireshark filter syntax and reference
  6. UNIX Network Programming, Volume 1, Second Edition: Networking APIs: Sockets and XTI, Prentice Hall, 1998, ISBN 0-13-490012-X