Linux学习|Gentoo/Arch/FreeBSD我用 Linux

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

2020-02-29  本文已影响0人  霡霂976447044

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:

其中, 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

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就能看到这个问题。

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

上一篇 下一篇

猜你喜欢

热点阅读