美文网首页Linux学习|Gentoo/Arch/FreeBSD我用 Linux
Linux下TCP协议的抓包分析-《TCP/IP详解卷一》

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

作者: 霡霂976447044 | 来源:发表于2020-02-29 20:17 被阅读0次

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 update
apt install wireshark
sudo wireshark

server.py

import socket
import sys

# create socket
server = socket.socket()

# set port reusse
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

# bind port
server.bind(('0.0.0.0', 8000))

# listing
server.listen(1)

# get client data

while 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.png TCP报文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就能看到这个问题。

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

相关文章

网友评论

    本文标题:Linux下TCP协议的抓包分析-《TCP/IP详解卷一》

    本文链接:https://www.haomeiwen.com/subject/krafhhtx.html