Linux下TCP协议的抓包分析-《TCP/IP详解卷一》

作者 : 开心源码 本文共3904个字,预计阅读时间需要10分钟 发布时间: 2022-05-13 共206人阅读

W.Richard Stevens写了很多关于Unix的书籍,不幸于1999离世。他的离去,是计算机界巨大的损失。
著作

  1. UNIX Network Programming, Volume 2, Second Edition: Interprocess Communications, Prentice Hall, 1999.
  2. UNIX Network Programming, Volume 1, Second Edition: Networking APIs: Sockets and XTI, Prentice Hall, 1998.
  3. TCP/IP Illustrated, Volume 3: TCP for Transactions, HTTP, NNTP,and the UNIX Domain Protocols, Addison-Wesley, 1996.
  4. TCP/IP Illustrated, Volume 2: The Implementation, Addison-Wesley, 1995.
  5. TCP/IP Illustrated, Volume 1: The Protocols, Addison-Wesley, 1994.
  6. Advanced Programming in the UNIX Environment, Addison-Wesley, 1992.
  7. UNIX Network Programming, Prentice Hall, 1990.

01 测试方法

PC A服务器:LinuxMint IP:192.168.2.102
PC B用户端:Manjaro IP:192.168.2.108
A主机运行一个Python Socket Server, B主机使用telnet连接。在A主机上使用Wireshark查看TCP报文。
A安装运行Wireshark

apt updateapt install wiresharksudo wireshark

server.py

import socketimport sys# create socketserver = socket.socket()# set port reusseserver.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)# bind portserver.bind(('0.0.0.0', 8000))# listingserver.listen(1)# get client datawhile 1:    print("waiting for new client...")    client, addr = server.accept()    print("new client:", addr)    buf = client.recv(1024)    print(buf)    client.close()

在A运行server

python3 server.py

而后在B用户端上telnet连接上 A

telnet 192.168.2.108 8000

而后输入hello,回车给A发送数据。

02 根据Wireshark看懂TCP包首部

TCP报文01.png

TCP的数据包裹在IP报文中,TCP首部占20字节,一个字节八位。TCP包首部图片的第四行,4位首部+保留6位+6各标志位总共为16位,也就是两个字节。

tcp通讯截图1.png
上图是总过程。
我们先点击第一行,也就是TCP连接握手的第一步。从上到下点击红色实体区域,来查看详细信息。
TCP报文端口02.png
上图我们可以看到,DF 5E就是对应的端口号,每一个数字/字符代表4位,两个合起来就是一个字节,DF就是一个字节。,也就正对应着TCP包首部那张图上的说的16位源端口号,DF 5E 转化为10进制为57182。注:tcp用户端会默认随机绑定一个端口。

  1. 再往后看两个字节,是8000,也就是目标端口。
  2. 目标端口后面的是32位序号2e f2 cc d0, 它是随机产生的一个序号,为了方便看,可能会显示为0;
  3. 再后面的是32位确认序号,第一次握手,数据都为0
  4. 后面的a0 是4位首部长度加上(保留6位的前4位),a0对应的2进制为10100000,我们取前面4位,得到4位首部长度。这里我们得到10,首部长40字节(20个已知首部+选项20个字节)。4个bit对应最大是15,得到最多有60个字节, 32bit * 5 = 20字节,32bit*15=60字节。
    2020-02-27_23-53.png
  5. 而后我们看保留位,a0 02之间的6位,都为0
  6. 而后我们看02这个数据,02中的后6位包含了6个标志位,02对应2进制为0000 0010,倒数第二个1,也就是TCP包首部中的SYN标志位。也就印证了TCP握手第一步会发送一个SYN为1的报文。
  7. 而后是fa f0窗口大小,窗口大小表明了发送这个报文的主机的接收缓冲区的大小。
  8. 4e f1校验和
  9. 00 00 16位紧急指针
  10. 选项 我们可以看到后面的20个字节都是属于选项,这20个字节加上前面20个,40个字节,也就是首部的总长度大小。
  11. 我们可以在选项的数据中得到TCP最大报文长度为1460,由于以太网链路层的传输最大长度通常是1500,再减去TCP头部和IP头部得到。假如不设置这个mss,那么就不知道一个报文最大多少才不会让IP协议分片,IP分片会导致传输速率减低。所以有了这个MSS告诉TCP层,你不要给我报文传太多。假如应用层传输的数据大于MSS,那么会TCP会分开这个数据块,分为一个个MSS大小的发出去,也就是常说的分组。UDP则不会,UDP的数据建议也不要太大,需要小于MSS。

第一次握手,用户端B发送了一个SYN为1的报文给服务器A:

  • 6个标志位只有SYN设置为了1
  • 32位确认序号为0,只有ACK标志位为1才有效
  • 32位序号为一个随机值2e f2 cc d0

其中, 32位序号表示了自己发送了多少,初始的时候会有个不为0值,发送一个会字节+1(发送一个SYN也会+1);
32位确认序号为下一次期望收到的数据序号,同时可以计算得到已经传过来的数据长度。

03 第二次握手

TCP报文02.png

在02节中,我们不仅知道了怎样看对应的数据,我们同时也知道了第一次握手的所有传输的字节数据。

  1. 查看6位标志位处,发现变为了12, 也就是ACK位和SYN位都设置为了1
  2. 查看32位序号,发现为79 5b 83 99,是第一次随机生成的。
  3. 查看32位确认序号,发现为2e f2 cc d1,在第一次握手的32位序号为2e f2 cc d0,可以看到,发送回去的32位确认序号加了1,同时也代表了A主机收到了A的SYN数据包。

第二次握手,服务器A发送一个SYN和ACK标志都为1的报文给用户端A

  • 6个标志位,SYN和ACK都设置为了1
  • A服务器产生一个32位序列,以及将从B收到的32位序号+1返回给B用户端

04 第三次握手

TCP报文03.png

  1. 查看32位序号为2e f2 cc d1,这个序列代表了自己将要发送的数据的第一个字节的序号
  2. 32位确认序号为79 5b 83 9a,这个值就是B用户端的发送过来的序号+1
  3. 只有ACK位为1

第三次握手,用户端B给服务器发送了一个ACK标识的报文,用以标识用户端收到了消息。
以下称32位确认序号位Ack,称32位序号位Seq。为了不搞混,了解Seq为自己发送了多少数据,Ack为自己收到了多少别人的数据。

05 B用户端数据传输A服务器

TCP报文04.png

  1. 查看Seq为2e f2 cc d1,和上一次ACK标识报文的Seq一致。说明ACK报文是不消耗Seq
  2. 查看Ack为79 5b 83 9a,也是和上一次一致。
  3. 传输的数据为后面的7位,最后两个是换行符号。

06 A服务器发送数据B用户端确认

  1. 只有ACK位为1
  2. Seq=79 5b 83 9a,相对于初始Seq,多了1个。一个SYN占一个。
  3. Ack=2e f2 cc d8,相对于初始Seq,多了9个,一个SYN+8字节数据。

07 A服务器告诉B用户端, A服务器不会再向B发送任何数据。A服务器主动关闭发送。

  1. Seq=79 5b 83 9a
  2. ACK和FIN都为1,四次握手的中间两步合并在一起发送。
  3. Ack为2e f2 cc d8

服务器代码的close就会让操作系统去发送FIN

发送这个FIN代表,关闭从A->B方向的数据传输。

08 B收到A的服务器FIN,B也说,我不会再向A服务器发送数据。B用户端主动关闭发送和接收。

  1. Seq=2e f2 cc d8
  2. Ack=‘79 5b 83 9a’, 可以看到FIN也会消耗一个序号。
  3. FIN和ACK都为1

09 服务器收到B的ACK报文后,执行关闭接收。

  1. Seq=79 5b 83 9b
  2. Ack=2e f2 cc d9
  3. ACK=1

整个挥手如下

TCP报文05.pngTCP报文06.png

假如在TCP数据挥手握手过程中,任何一方主动关闭了连接,另一方没有手动被动关闭,另一方有可能造成CLOSE_WAIT状态,注意FIN需要自己手动调用close发送。CLOSE_WAIT和LAST_ACK中对应的代码逻辑通常是判断接收到数据能否是EOF,假如是调用close。就会进入LAST_ACK状态。

假如出现,在你的程序里TIME_WAIT连接状态过多的,TCP在设计时候,由于为了防止意外,一个端口在关闭连接后需要等待2MSL时间才能再次使用。
假如你的程序大量的段时间的连接断开socket连接,你就会有可能出现大量的TIME_WAIT。严重的可能会把所有可用端口占满。处理的办法是改变你的代码逻辑和使用SO_REUSEADDR选项复用端口。

假如在连接握手中,任何一方没有正确的握手,都有可能造成资源的白费,例如SYN洪水攻击。

窗口大小问题

假如接收端recv解决不过来,一开始会造成自动窗口变大,直到接收缓冲区饱满。可以写一个用户端,一直发送数据,服务端不调用recv就能看到这个问题。

如有不正确的地方,欢迎大家指正!

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Linux下TCP协议的抓包分析-《TCP/IP详解卷一》

发表回复