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,建立连接过程:
- SYN: 客户端通过向服务器端发送一个
SYN来建立一个主动打开,作为三次握手的一部分。客户端把这段连接的初始序号设定为随机数A。 - SYN-ACK: 服务器端应当为一个合法的
SYN回送一个SYN/ACK,ACK确认号在接收的序号上加1,也就是A+1,SYN/ACK包本身又有一个随机序号B。 - ACK: 最后,客户端再发送一个
ACK,当服务端受到这个ACK的时候,就完成了三次握手,并进入了连接建立状态,此时包序号被设定为收到的确认号A+1,而响应则为B+1。
wireshark 抓包截图如下:
如上图所示,前面 3 条记录是 TCP 建立连接的 3 次握手,图中的显示的初始序号为0,是为了方便查看而显示的序号相对值。
TCP 连接关闭
TCP 连接关闭使用了四次挥手过程four-way handshake,四次挥手过程示意图如下:
- 主动关闭方发送一个
FIN并进入FIN_WAIT_1状态,并包括一个序号X; - 被动关闭方接收到主动关闭方发送的
FIN,然后回复ACK确认号X+1和已方的序号Y给主动关闭方,此时被动关闭方进入CLOSE_WAIT状态;主动关闭方收到被动关闭方的ACK后,进入FIN_WAIT_2状态; - 被动关闭方发送一个
FIN给主动关闭方,包括ACK确认号X+1和已方的一个序号Y,并进入LAST_ACK状态; - 主动关闭方收到被动关闭方发送的
FIN,然后回复ACK确认号Y+1和已方的序号X+1给被动关闭方,此时主动关闭方进入TIME_WAIT状态,经过2*MSL时间后关闭连接;被动关闭方收到主动关闭方的ACK后,关闭连接。
wireshark 抓包截图如下:
如上图所示,最后面 4 条是关闭连接的 4 次挥手,红字那条表示收到重复Ack确认号,这实际上是对的(先发一次Ack确认号,再发一次Fin+Ack关闭通知),最后本机向服务器发了一条确认关闭的Ack确认号。
数据传输
在 TCP 的数据传输过程中,很多重要的机制保证了 TCP 的可靠性和强壮性,它们包括:
- 使用序号,对收到的 TCP 报文段进行排序以及检测重复的数据。
- 使用校验和来检测报文段的错误。
- 使用确认号和重传计时器来检测和纠正丢包或延时。
序号 Seq 和确认号 Ack
- 在 TCP 的连接建立状态,两个主机的 TCP 层间要交换初始序号 ISN:
initial sequence number。 - 这些序号用于标识字节流中的数据,并且还是对应用层的数据字节进行记数的整数。
- 通常在每个 TCP 报文段中都有一对序号和确认号。
- TCP 报文发送者将自己的字节编号做为序号,而将接收者的字节编号做为确认号。
- TCP 报文的接收者为了确保可靠性,在接收到一定数量的连续字节流后才发送确认。
Seq和Ack是以字节数为单位,所以Ack的时候,不能跳着确认,只能确认连续收到的Seq最大的包。- 这是对 TCP 的一种扩展,通常称为选择确认
Selective Acknowledgment。 - 选择确认使得 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 Length为1480bytes,减去 IP 头长度20bytes,再减去 TCP 头长度20bytes,实际数据的长度为1440bytes,服务器正确收到报文后应该返回一个数据长度为 0 的ACK报文,其中Acknowledgment Number应该为1441,并且客户端下一个 TCP 报文的序号也是1441,计算公式如下:
1 + 1440 = 1441
TCP 数据传输过程举例一
- 上图所示的最上面三条报文说明了 TCP 连接建立的 3 次握手过程。
- 上图所示的第 32 条记录,服务器发送第 1 个包含序号为 1(相对值)和 1440 字节数据的 TCP 报文段给客户端。
- 上图所示的第 33 条记录,服务器发送第 2 个包含序号为 1441(1+1440)和 1440 字节数据的 TCP 报文段给客户端。
- 上图所示的第 34 条记录,服务器发送第 3 个包含序号为 2881(1441+1440)和 1440 字节数据的 TCP 报文段给客户端。
- 上图所示的第 35 条记录,服务器发送第 4 个包含序号为 4321(2881+1440)和 930 字节数据的 TCP 报文段给客户端。
- 上图所示的第 36 条记录,客户端返回第 1 个包含序号为 87,确认号为 2881(1441+1440)和 0 字节数据的 TCP 报文段给服务器,通知服务器已经正确收到服务器发过来的前 2 个报文段。说明:数据包都是连续的情况下,接收方没有必要每一次都回应,比如,他收到第 1 到 2 条 TCP 报文段,只需回应第 2 条就行了。
- 上图所示的第 37 条记录,客户端返回第 2 个包含序号为 87,确认号为 4321(2881+1440)和 0 字节数据的 TCP 报文段给服务器,通知服务器已经正确收到服务器发过来的前 3 个报文段。
- 上图所示的第 38 条记录,客户端返回第 3 个包含序号为 87,确认号为 5251(4321+930)和 0 字节数据的 TCP 报文段给服务器,通知服务器已经正确收到服务器发过来的全部 4 个报文段。
- 第 39 条记录中客户端发起 TCP 连接关闭
FIN请求,第 40 条服务器返回一个FIN请求,在FIN消息前缺少一条服务器给客户端的ACK报文,最后客户端回复服务器一个ACK报文,TCP 连接关闭。 - 假如在上述例子中服务器发出的第 2 条 TCP 报文段被丢失了,所以尽管客户端收到了后面的第 3 条和第 4 条,然而他只能回应第 1 条
ACK报文,确认号为1441。 - 服务器在发送了第 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 连接建立和关闭时的seq和ack值的变化,与连接建立之后数据传输过程中的seq和ack计算方式不同,服务端没有发送数据,所以在客户端收到最后一个FIN前,服务端的数据包的seq始终是1。
TCP 状态机
下表为TCP状态码列表:
- LISTEN: 服务启动后首先处于侦听
LISTENING状态。 - SYN_SENT: 在发送连接请求后等待匹配的连接请求。通过 connect()函数向服务器发出一个同步
SYNC信号后进入此状态。 - SYN_RECEIVED: 已经收到并发送同步
SYNC信号之后等待确认ACK请求。 - ESTABLISHED : 连接已经建立,表示 2 台机器可以相互通信,此时连接两端是平等的。
- FIN_WAIT_1 : 主动关闭端调用
close()函数发出 FIN 请求包,表示本方的数据发送全部结束,等待 TCP 连接另一端的确认包或FIN请求包。 - FIN_WAIT_2 : 主动关闭端在
FIN_WAIT_1状态下收到确认包,进入等待远程 TCP 的连接终止请求的半关闭状态,这时可以接收数据,但不再发送数据。 - CLOSE_WAIT : 被动关闭端接到
FIN后,就发出ACK以回应FIN请求,并进入等待本地用户的连接终止请求的半关闭状态,这时可以发送数据,但不再接收数据。 - CLOSING : 双方同时发出
FIN,同时进入等待对方对连接终止FIN的确认ACK时进入的状态,极少见。 - LAST_ACK : 被动关闭端全部数据发送完成之后,向主动关闭端发送
FIN,进入等待确认包的状态。 - TIME_WAIT : 主动关闭端接收到
FIN后,就发送ACK包,等待足够时间(2 倍MSL时间)以确保被动关闭端收到了终止请求的确认包。 - CLOSED : 连接关闭,代表双方无任何连接状态。
状态机示意图一
状态机示意图二
TIME_WAIT and MSL
MSL就是最大分节生命期maximum segment lifetime,这是一个 IP 数据包能在互联网上生存的最长时间,超过这个时间 IP 数据包将在网络中消失。MSL在 RFC 1122 上建议是2 分钟,Windows 默认是120秒,但微软建议设置为30秒,Linux 默认为60秒,而源自 berkeley 的 TCP 实现传统上使用30 秒。TIME_WAIT状态维持时间是两个MSL时间长度,也就是在1-4 分钟,Windows操作系统就是 4 分钟。- 进入
TIME_WAIT状态的一般情况下是客户端,如果并发 20 个线程,每个线程打开并关闭socket超过一定次数,就会产生太多TIME_WAIT状态的连接,并产生连接异常。 - 服务器端更多应该是
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
常见网络协议的头格式
TCP 三次握手和四次挥手的示意图
文中配图主要来源于以下参考链接。