阅读 588

TCP通信过程和tcpdump抓包分析

作为网络编程人员,对于TCP的通信过程是肯定要会的,至少要知道,当遇到通信问题时才有可能找到解决方法

TCP报文

关于TCP报文的结构,我这里直接贴一张书上的图片,计算机网络这门课中有详细的介绍

在本篇文章中,我们主要讨论序号和确认号,以及标志位:

  1. ACK 回复通知
  2. PSH 推送报文
  3. SYN 建立连接
  4. FIN 关闭连接

比如报文中标志位SYN设为1时表示这是一个申请建立连接的报文,而ACK就表示收到报文后的回复报文

TCP序列号和确认序列号

在上面中讲到过报文中包含序列号和确认序列号,其中序列号我们简写为seq,确认序列号简写为ack,要特别讲序列号和确认序列号的原因是以后抓包分析网络通信的时候需要关注这两个数值

在一个TCP连接开始时,第一个报文的序列号是主动方随机生成的一个数字,以三次握手为例:

  1. 发起连接方发送SYN报文,此为第一个报文,报文中序列号随机生成,假设序列号=a,确认号没有
  2. 被动方收到SYN报文,得到其中的序列号=a,被动方回复SYN-ACK报文(SYN和ACK两个标志位都为1),报文中序列号随机生成,假设序列号=b,确认序列号=收到的报文中序列号a + 收到的报文中SYN位的值 + 收到的报文中数据部分长度,实际上就等于a + 1 + 0,也就是a+1,SYN报文中数据部分是空的
  3. 主动方收到SYN-ACK报文,回复ACK报文,此报文中序列号=收到的报文中的确认序列号(即a+1),此报文中确认序列号=收到的报文中序列号b + 收到的报文中SYN位的值 + 收到的报文中数据部分长度(和第2步被动方收到报文时生成的确认序列号方法一样),也就是b+1

根据上面的过程可以得到一个结论,确认序列号就是告诉对方发报文回来时报文中该填的序列号

TCP通信过程

结合三次握手和四次挥手来看通信双方的状态,被动方指的是服务端,主动方指的是客户端

  1. 主动方和被动方一开始都是CLOSED,被动方程序还未启动
  2. 被动方程序启动监听端口(调用bind方法),此时被动方状态为LISTEN
  3. 主动方发送SYN(调用connect方法),此时主动方状态为SYN_SENT
  4. 被动方收到SYN(通过accept方法,收到SYN报文时accept方法还是阻塞的),此时被动方状态为SYN_RCVD,被动方回复SYN-ACK,当前连接放入未完成连接队列中
  5. 主动方收到SYN-ACK,此时主动方状态为ESTABLISHED,主动方回复ACK
  6. 被动方收到ACK(accept方法会返回),此时被动方状态为ESTABLISHED
  7. 中间通信过程中双方都是ESTABLISHED状态
  8. 主动关闭方发送FIN(调用close方法),此时主动关闭方状态为FIN_WAIT_1
  9. 被动关闭方收到FIN,回复ACK,此时被动关闭方状态为CLOSE_WAIT
  10. 主动关闭方收到ACK,此时主动关闭方状态转换FIN_WAIT_2
  11. 被动关闭方继续发送FIN(调用close方法),此时被动关闭方状态为LAST_ACK
  12. 主动关闭方收到FIN,回复ACK,此时主动关闭方状态为TIME_WAIT
  13. 被动关闭方收到ACK,此时被动关闭方状态为CLOSED
  14. 主动关闭方等待2倍MSL时间后,状态变为CLOSED

以上3-6为三次握手过程,8-14为四次挥手过程,其中谁发起关闭谁就需要等待2倍MSL时间后才能变为CLOSED,如果有大量的短连接时,这种频繁的发送报文,并且还需要等待一段时间才真的关闭是很耗费资源的

通过netstat -anp命令查看网络连接情况,从中可以统计出当前有多少个ESTABLISHED连接和TIME_WAIT连接数

注意:在上面第3步发生后,主动方没有继续做第5步的操作,那么被动方的未完成连接队列中会被占用,如果主动方不停的这么做,最终被动方的未完成连接队列会被占满,导致被动方无法再接受新的连接。关闭连接时也存在同样的问题,导致产生大量的TIME_WAIT或CLOSE_WAIT,这些都会占用服务端的资源,服务器调优大部分也是调这些参数

tcpdump

了解TCP的通信过程后我们可以抓包分析一下整个通信过程,我们使用tcpdump进行抓包

tcpdump用来抓取TCP报文,分析通信情况,通常遇到偶尔抽风的连接问题时都会先抓个包进行分析

安装

apt install tcpdump
复制代码

命令参数

参数说明
-D列出当前服务器上可抓取的网络接口,就是网卡
-i从指定网络接口抓取
-n显示IP
-nn显示IP和端口
-c设置抓包数量,达到此数值后自动停止抓包
-w指定抓包结果输出到某个文件,同时不再输出到屏幕,如果该文件存在会被覆盖,此文件可以被wireshark打开
-r从文件读取抓包数据并显示到屏幕上,可以配合其他参数,如-nn控制是否显示IP和端口等

过滤数据

过滤这一步很重要,比如只抓取指定IP和端口的报文

// 只抓取TCP报文
tcpdump -nn tcp

// 只抓取和指定主机或IP相关的报文,即可以是发送方也可以是接收方
tcpdump -nn tcp host 192.168.3.3

// 指定端口号
tcpdump port 61613

// 指定源主机或IP,即发送方
tcpdump src 192.168.3.3

// 指定目标主机或IP,即接收方
tcpdump dst 192.168.3.3

// 两个条件之间and
tcpdump src 192.168.3.3 and port 80

// 两个条件之间or
tcpdump src 192.168.3.3 or port 80

// 使用括号指定更复杂的过滤条件,在命令行需要在括号外面添加双引号,防止被认为是shell表达式
tcpdump -nn "port 80 and (src 192.168.3.3 or src 192.168.3.4)"
复制代码

分析数据包

有以上的基础后,我们可以来抓取一次通信过程的所有报文并进行分析

本次实验在同一台虚拟机进行,使用tcpdump抓取时需要指定-i lo网卡

先启动抓包程序,我们抓取tcp port 8888端口的报文:

root@today2:~# tcpdump -nn -i lo tcp port 8888
复制代码

启动后输出以下内容,其中说明了监听的网卡为lo

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
复制代码

现在来启动服务端,我们使用nc命令

nc -lk 8888
复制代码

再来启动一个客户端,我们使用telnet命令

telnet 127.0.0.1 8888
复制代码

当启动了客户端之后,抓包的窗口中就已经显示了抓到的报文,如下:

17:48:20.202499 IP 127.0.0.1.59466 > 127.0.0.1.8888: Flags [S], seq 3257142365, win 65495, options [mss 65495,sackOK,TS val 1604396091 ecr 0,nop,wscale 7], length 0
17:48:20.202511 IP 127.0.0.1.8888 > 127.0.0.1.59466: Flags [S.], seq 2103221418, ack 3257142366, win 65483, options [mss 65495,sackOK,TS val 1604396091 ecr 1604396091,nop,wscale 7], length 0
17:48:20.202520 IP 127.0.0.1.59466 > 127.0.0.1.8888: Flags [.], ack 1, win 512, options [nop,nop,TS val 1604396091 ecr 1604396091], length 0
复制代码

以第一行的数据来介绍一下每一段的意思

  1. 17:48:20.202499 该数据报文被抓取的系统本地时间戳
  2. IP 网络层协议类型,这里是 IPv4,如果是 IPv6 协议,该字段值是 IP6
  3. 127.0.0.1.59466 > 127.0.0.1.8888 源IP:端口和目标IP:端口
  4. Flags [S] 这是报文的标志位,下文会用表格列出Flag与报文标志位的对应关系
  5. seq 3257142365 报文的序列号,可以看到这是一个随机数
  6. win 65495 发送方的窗口大小,窗口大小参考《计算机网络》这门课
  7. options [...] 这些是选项
  8. length 0 这是报文中数据部分的长度

上面这段第一行是客户端发来的SYN报文,第二行是服务端回复的SYN-ACK报文,第三行是客户端回复的ACK报文,大家可以自己分析一下其中的seq和ack

现在来发送一个单词,随便是nc或telnet发送都行,假设发送单词为hello,抓包得到以下两个报文

18:04:10.723026 IP 127.0.0.1.8888 > 127.0.0.1.59466: Flags [P.], seq 1:7, ack 1, win 512, options [nop,nop,TS val 1605346770 ecr 1604396091], length 6
18:04:10.723040 IP 127.0.0.1.59466 > 127.0.0.1.8888: Flags [.], ack 7, win 512, options [nop,nop,TS val 1605346770 ecr 1605346770], length 0
复制代码

最后停止nc,这会断开双方的连接,查看抓包结果:

18:05:03.474050 IP 127.0.0.1.8888 > 127.0.0.1.59466: Flags [F.], seq 7, ack 8, win 512, options [nop,nop,TS val 1605399523 ecr 1605390431], length 0
18:05:03.474123 IP 127.0.0.1.59466 > 127.0.0.1.8888: Flags [F.], seq 8, ack 8, win 512, options [nop,nop,TS val 1605399523 ecr 1605399523], length 0
18:05:03.474132 IP 127.0.0.1.8888 > 127.0.0.1.59466: Flags [.], ack 9, win 512, options [nop,nop,TS val 1605399523 ecr 1605399523], length 0
复制代码

注意:大家都说四次挥手,但是这里只有三个报文!说四次挥手是没有错的,这里只有三个报文的原因是上面第二行中将FIN和ACK合并到一个报文中发给主动关闭方了,这样可以减少一次报文传输,在网络上传输报文是有可能丢失的,能减少一次自然是最好的

Flag与报文标志位的对应关系

Flag标志位
SSYN
.ACK
FFIN
PPSH
RRST

最后补充一条提醒:-w参数可将抓包数据保存到文件中,再用自己电脑上的wireshark打开,这样分析会更方便快捷

原创不易,一键三连安排一下吧

文章分类
后端
文章标签