网络协议分析——实验九使用Wireshark分析TCP协议
文章在线:
https://cmd.dayi.ink/mXYpwNZfQXSEJWKHCoYAFg?both
https://type.dayiyi.top
https://blog.dayi.ink
实验目的
本实验旨在使用 Wireshark 工具分析 TCP 协议,包括抓取四种不同类型的 TCP 包并进行详细分析。
实验步骤
步骤1:准备工作
- 打开 WireShark。
- 尝试捕获
步骤2:抓取TCP数据包
- 打开浏览器,访问一个 web 站点,然后关闭,重复操作以捕捉 TCP 数据包。
- 筛选出其中一组 TCP 连接的管理过程。
- 虽然可以随便抓,但是从HTTP/1.1开始访问一个网页可能会只建立一个TCP连接,可能会比较难抓,而且可能会同时建立更多的TCP包。
- 我不太建议用浏览器,我不知道能不能正常抓到,或者虽然抓到了,但是有很多请求,于是难以筛选。打开浏览器之前尽量先加上筛选条件。
注意事项
- HTTP/1.1 开始访问一个网页可能只会建立一个 TCP 连接,可能会比较难抓。
- 在打开浏览器之前,加上筛选条件。
抓包分析
(1)TCP连接建立 (2)TCP连接断开 分析1 在windows下
使用脚本模拟请求:
#pip install requests import requests req = requests.get("http://wireshark.icee.top") req.encoding = "utf-8" print(req.text)
使用
nslookup
命令查找要访问网址的 IP 地址以用于筛选:nslookup wireshark.icee.top
nslookup [你要访问的网址] nslookup wireshark.icee.top
这个查出来的IP就是等会要筛选的ip,方便来进行分析包
筛选条件示例:
ip.addr==43.128.1.238
这个包就很全,包含了建立建立连接和下载数据以及四(三)次挥手断开连接。
- 过程如下:
- SYN:客户端(源IP地址为192.168.31.26,源端口为57619)向服务器(目的IP地址为43.128.1.238,目标端口为80,即HTTP服务)发送了一个TCP同步(SYN)报文,这是TCP三次握手的第一步,用于建立连接。
- SYN-ACK:服务器响应客户端的SYN请求,发送了一个SYN-ACK(同步应答)报文。这是三次握手的第二步,服务器确认接收到了客户端的同步请求。
- ACK:客户端再次发送ACK(确认)报文给服务器,完成三次握手,建立了TCP连接。
- HTTP GET 请求:客户端在建立的TCP连接上发送了一个HTTP GET请求,请求从服务器获取特定的资源(通常是网页)。
- 服务器ACK:服务器确认收到了HTTP GET请求。
- HTTP响应:服务器处理了GET请求,并返回了一个HTTP 200 OK响应,表明请求成功,并附带所请求的资源内容。
- 客户端FIN:通信完成后,客户端发送了一个FIN(结束)报文,用于关闭连接。
- 服务器FIN:服务器同样发送了一个FIN报文,响应客户端的关闭请求。
- 最终ACK:最后,客户端发送了一个ACK报文来确认接收到了服务器的FIN报文,完成了整个TCP连接的终止过程。
挥手的时候:
- FIN ACK
- FIN ACK(服务器到客户端)
- ACK
服务器的FIN和ACK合并在了一起,只发送了一个包,于是就只有3个。
8 0.146942 192.168.31.26 43.128.1.238 TCP 54 58035 → 80 [FIN, ACK] Seq=150 Ack=1093 Win=261888 Len=0
9 0.249596 43.128.1.238 192.168.31.26 TCP 54 80 → 58035 [FIN, ACK] Seq=1093 Ack=151 Win=64128 Len=0
10 0.249734 192.168.31.26 43.128.1.238 TCP 54 58035 → 80 [ACK] Seq=151 Ack=1094 Win=261888 Len=0
(1)TCP连接建立 (2)TCP连接断开 在linux下 分析2
在linux下好像跟windows的包有点不同,但都是服务器发过来的,应该跟操作系统没有关系。
这里我的抓包命令是:
sudo tcpdump tcp and host 43.128.1.238 -w capture_file.pcap
抓取的过程就仅抓与wireshark.icee.top
服务器的交互
包1
另外一个包:
会发现,好像在结束TCP连接的时候会有所不同。
但是大差不大。
资料说:linux发送完最后的TCP会进入一个FIN_WAIT
的阶段,但是windows会直接关上。
看的出来windows蛮着急的,linux就慢悠悠的。
- windows恨不得3个包结束战斗,linux可能会等到全部资源一起结束了。
这段数据是网络数据包的捕获记录,显示了两个设备之间的通信过程。这里是一次典型的TCP(传输控制协议)连接过程,以及随后的HTTP(超文本传输协议)请求和响应。我将逐个解释这些包的作用:
包 1 - SYN
- 动作: 客户端(192.168.106.128)向服务器(43.128.1.238)发送一个TCP同步(SYN)数据包,请求建立连接。
- 详细: 客户端选择源端口48626,并且TCP序列号为0,表示这是一个新的连接请求。
包 2 - SYN, ACK
- 动作: 服务器响应客户端的连接请求,发送一个SYN-ACK(同步应答)数据包。
- 详细: 服务器确认(ACK)客户端的SYN请求,并提出自己的SYN请求。
包 3 - ACK
- 动作: 客户端发送ACK(确认)数据包到服务器。
- 详细: 这个ACK确认了服务器的SYN-ACK,三次握手过程完成,TCP连接建立。
包 4 - HTTP GET 请求
- 动作: 客户端向服务器发送HTTP GET请求。
- 详细: 请求获取服务器上的某个资源(如网页),通常URL会在HTTP头部中指定。
包 5 - ACK
- 动作: 服务器发送ACK确认收到GET请求。
- 详细: 这是对客户端HTTP请求的标准TCP确认。
包 6 - HTTP 200 OK 响应
- 动作: 服务器回应HTTP请求,发送HTTP 200 OK响应,包含请求的数据(如网页内容)。
- 详细: 数据包含所请求资源的内容
包 7 - ACK
- 动作: 客户端确认收到HTTP响应。
- 详细: 标准的TCP确认,表明客户端已成功接收数据包。
包 8 - FIN, ACK
- 动作: 客户端发起关闭TCP连接的请求,发送FIN(结束)和ACK数据包。
- 详细: 客户端表示已经完成数据的发送和接收,请求终止连接。
包 9 - ACK
- 动作: 服务器确认收到了客户端的FIN请求。
- 详细: 服务器通过发送ACK来响应客户端的终止请求。
包 10 - FIN, PSH, ACK
- 动作: 服务器发送FIN和ACK,开始关闭连接的过程。
- 详细: 服务器也表明了自己完成了数据的发送,希望关闭连接。
包 11 - ACK
- 动作: 客户端确认收到服务器的FIN请求。
- 详细: 这是对服务器关闭请求的最后确认,之后连接关闭。
数据传输
HTTP GET 请求
- 数据包4: 客户端发送了一个 HTTP GET 请求到服务器。
ACK (Seq=1, Ack=45)
- 数据包5: 服务器确认收到了 GET 请求。
HTTP 响应
- 数据包6: 服务器响应 HTTP 请求,状态码 200 OK,意味着请求已经成功处理。
填表:
连接建立报文段 | 源端口 | 目的端口 | 序号 | 确认号 | 头部长度 | 6个标志位中,值为1的 | 窗口大小 | MSS选项 |
---|---|---|---|---|---|---|---|---|
第1次 | 48626 | 80 | 0(2229626197) | 不适用(0) | 40 | SYN | 64240 | 1460 |
第2次 | 80 | 48626 | 0(241322383) | 1(2229626198) | 24 | SYN ACK | 64240 | 1460 |
第3次 | 48626 | 80 | 1(2229626198) | 1(241322384) | 20 | ACK | 64240 | 不适用 |
…… |
连接断开报文段 | 源端口 | 目的端口 | 序号 | 确认号 | 头部长度 | 6个标志位中,值为1的 | 窗口大小 |
---|---|---|---|---|---|---|---|
第1次 | 48626 | 80 | 150(2229626347) | 1093(241323476) | 20 | FIN,ACK | 63336 |
第2次 | 80 | 48626 | 1093(2229626348) | 151(241323476) | 20 | ACK | 64239 |
第3次 | 80 | 48626 | 1093(241323476) | 151(2229626348) | 20 | FIN,PSH,ACK | 64239 |
第4次 | 48626 | 80 | 151(2229626348) | 1094(241323477) | 20 | ACK | 63336 |
…… |
(3)TCP重传尝试
- 这个包你可以试试下着东西,然后拿着手机往外面跑。
- 窗口调整的包我几乎没有抓到,但是也有!
- 在网络质量较差的环境下抓包或使用 Linux 系统。
使用 iptables 过滤 RST 包,以避免被内核重置。
iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP
不加这行直接发包会被RST(linux内核发现来了个莫名其妙的包,于是发送了RST,TCP就没了)
- 我这里写了个脚本,连接建立的包模拟了一下,但是可惜的断开连接的过程总是会有点问题,虽然发了包,但是服务器的断开四次挥手有点奇怪。
脚本只到建立TCP,然后挂起,不回复ACK,让服务器认为是包迟迟没有到达,于是不断发重传包。
from scapy.all import IP, TCP, send, sr1, Raw,socket,RandShort
from scapy.layers.http import HTTP, HTTPRequest
# 目标网址和端口
target = "wireshark.icee.top"
target_ip = socket.gethostbyname(target)
target_port = 80
# 创建IP包
ip = IP(dst=target_ip)
# 创建TCP SYN
tcp_syn = TCP(sport=RandShort(), dport=target_port, flags='S')
# 发送SYN并接收SYN-ACK
synack = sr1(ip/tcp_syn)
print(synack.show())
# 从SYN-ACK包中获取序列号,并构造ACK包
tcp_ack = TCP(sport=synack[TCP].dport, dport=target_port, flags='A', seq=synack[TCP].ack, ack=synack[TCP].seq + 1)
# 发送ACK包
send(ip/tcp_ack)
# 构造HTTP GET请求
get_str = 'GET /python-3.9.10-amd64.exe HTTP/1.1\r\nHost: ' + target + '\r\n\r\n'
get_str = 'GET / HTTP/1.1\r\nHost: ' + target + '\r\n\r\n'
http_get = ip / tcp_ack / Raw(load=get_str)
# 发送HTTP GET请求并等待响应
response = sr1(http_get, timeout=1)
# ACK回复已经收到的包
if response and HTTP in response:
# ACK回复已经收到的包
ack = TCP(sport=synack[TCP].dport, dport=target_port, flags='A', seq=response[TCP].ack, ack=response[TCP].seq + len(response[TCP].payload))
send(ip/ack)
else:
print("No HTTP response received")
# 构造TCP FIN包
tcp_fin = TCP(sport=tcp_ack.dport, dport=target_port, flags='FA', seq=tcp_ack.ack, ack=tcp_ack.seq + 1)
# 发送TCP FIN包
send(ip/tcp_fin)
# 接收服务器的ACK响应
ack_response = sr1(ip/tcp_fin, timeout=1)
if ack_response and TCP in ack_response and ack_response[TCP].flags == 'A':
# 打印服务器的响应
print(ack_response.show())
else:
print("No ACK response received")
# 打印服务器的响应
print(ack_response)
# 不发送最终的ACK确认,触发重传
# 服务器应该会重传HTTP响应
# sudo tcpdump tcp and host 43.128.1.238 -w capture_file.pcap
# 休眠一会,等服务器的重传包(气死nginx)
# nginx超时时间在60s左右
import time
n = 40
for i in range(n):
print(f"sleeping {n-i}s..")
time.sleep(1)
# 不发送最终的ACK确认,触发重传
# 休眠一会儿,等服务器的重传包
# time.sleep(30) # 等待30秒来观察服务器的重传行为
对于捕获再开一个终端:
sudo tcpdump tcp and host 43.128.1.238 -w capture_file.pcap
抓到的包如下:
抓了好几天)
也有抓了之后尝试发送FIN,但是好像没太成功
筛选后比较满意的包:
填表:
因为是脚本生成的包(甚至后期直接把TCP填上了80发出去)所以确认号和相对号一样(因为一开始发出去的是0~)
TCP重传 | 时间 | 源端口 | 目的端口 | 序号 | 确认号 | 头部长度 | 窗口大小 |
---|---|---|---|---|---|---|---|
第1次 | 0.174306 | 80 | 8352 | 1(3696288462) | 45(45) | 20 | 64196 |
第2次 | 0.176456 | 80 | 8352 | 1(3696288462) | 45(45) | 20 | 64196 |
第3次 | 0.490942 | 80 | 8352 | 1(3696288462) | 45(45) | 20 | 64196 |
第4次 | 1.069615 | 80 | 8352 | 1(3696288462) | 45(45) | 20 | 64196 |
第5次 | 2.219013 | 80 | 8352 | 1(3696288462) | 45(45) | 20 | 64196 |
第6次 | 4.554897 | 80 | 8352 | 1(3696288462) | 45(45) | 20 | 64196 |
第7次 | 9.162757 | 80 | 8352 | 1(3696288462) | 45(45) | 20 | 64196 |
第8次 | 18.37882 | 80 | 8352 | 1(3696288462) | 45(45) | 20 | 64196 |
第9次 | 37.06662 | 80 | 8352 | 1(3696288462) | 45(45) | 20 | 64196 |
虽然一直丢失,但是重传的时间几乎是按照指数增加的。1 2 4 8 16 32
TCP重传是一种错误控制机制,用于处理丢失或损坏的数据包。文本中的多个数据包(如Frame 6、7、8等)标记为“TCP Retransmission”,表明因为之前的包未能成功传输而重新发送的。实际上就是咱们故意不ACK然后他当然要重传,以为丢失了。
(4)滑动窗口
筛选条件:
tcp.analysis.ack_rtt
这样就有了。
但是整体的那个慢启动,拥塞避免,快重传、恢复,超时MSS设置为-1
如果WIRESHARK抓到了TCP建立过程,是可以把这个指数一起算出来的。
两方的MSS可以不相同。三次握手确定,可以避免在IP层被切片。
TCP滑动窗口机制:这是一种流量控制协议,用于确保发送方不会溢出接收方的缓冲区。文本提到需要对TCP滑动窗口机制进行分析,这通常涉及到监控发送和接收窗口的大小,以及如何根据网络条件动态调整这些窗口大小。
这种RST包就会直接windows设置为0
2.分析
TCP拥塞控制和流量控制
慢启动(Slow Start)、拥塞避免(Congestion Avoidance)、快速重传(Fast Retransmit)和快速恢复(Fast Recovery)。
TCP拥塞控制的阶段
慢启动(Slow Start)
- 在TCP连接刚建立时,拥塞窗口(cwnd)从一个较小的值(如1个MSS,最大报文段长度)开始。
- 随着每个成功传输的轮回,窗口大小加倍,指数增长,直到达到一个阈值(ssthresh)。
拥塞避免(Congestion Avoidance)
- 当拥塞窗口大小达到或超过ssthresh时,TCP进入拥塞避免阶段。
- 在这一阶段,窗口大小增长变得更加保守,通常是线性增长。
快速重传(Fast Retransmit)
- 当发送方收到三个重复的ACK时,意味着后面的包可能丢失,它将立即重传丢失的包,而不是等待超时。
快速恢复(Fast Recovery)
- 在快速重传后,TCP进入快速恢复阶段。
- ssthresh设置为当前拥塞窗口的一半,拥塞窗口设置为ssthresh加上重复ACK的数量,然后继续传输,如果收到新的ACK,则结束快速恢复,否则进入慢启动。
TCP流量控制的关键要素
TCP流量控制是一种确保发送方不会淹没接收方的机制,它利用滑动窗口协议来实现。
滑动窗口(Sliding Window)
- 滑动窗口是TCP流量控制的核心,它控制着在等待确认之前可以发送的数据量。
- 窗口大小(window size)是接收方根据自己的缓冲区大小动态通告给发送方的,表示接收方能够接收的数据量。
接收窗口(rwnd)
- 接收窗口是接收方基于自己的处理能力和缓冲区大小告诉发送方的最大数据量。
- 发送方必须确保未确认的数据总量不超过这个窗口大小。
调节窗口大小
- 如果接收方处理能力较低或者缓冲区快满,它可以通过减小窗口大小通知发送方减缓发送速率。
- 如果接收方的缓冲区空闲了,它可以增加窗口大小来接收更多数据。
分析数据
窗口大小的作用
- 数据中窗口大小一直是64196。这表示接收方已经告诉发送方,在不收到进一步确认的情况下,它能够接受最多64196字节的数据。
流量控制与拥塞控制的区别
- 尽管流量控制和拥塞控制都会影响发送方的发送速率。流量控制是为了避免发送方淹没接收方,而拥塞控制则是为了防止网络拥塞。
无窗口调节的迹象
- 由于窗口大小没有变化,没有直接迹象表明接收方进行了窗口调节。这可能意味着接收方的处理能力和缓冲区空间在此期间保持相对稳定。
- (这里也因为咱们没有去调节这个窗口,因为还要构造数据包)
TCP拥塞控制机制的作用
- TCP尝试通过重传丢失的包来恢复数据传输。
- 持续的重传可能表明TCP尝试适应网络条件。
- 数据包6-13: 显示了多次TCP重传,表明可能出现了网络拥塞或数据包丢失。TCP的拥塞控制机制会在检测到丢包时减少发送数据的速率。由于重复的TCP重传,表明一直未收到对先前数据包的确认。
- 这里的“窗口大小”(Window Size)是流量控制的关键参数,表明接收端愿意接收的数据量。在这个过程中,窗口大小保持不变,表明接收端的接收能力没有变化。
- TCP使用的是“加性增,乘性减”(AIMD)策略。在网络情况良好时逐渐增加窗口大小(增加传输速率),而在检测到丢包(如重传)时减小窗口大小(减少传输速率)。
TCP
- 三次握手过程包括 SYN、SYN-ACK、ACK 包的交换。
- 数据传输阶段涉及 HTTP GET 请求和 HTTP 响应。
- 重传机制展示了 TCP 如何确保数据的可靠传输。
- 流量控制和拥塞控制通过窗口大小和动态调整发送速率来应对网络状况。
TCP 三次握手过程
SYN (Seq=0)
- 数据包1: 客户端(10.1.1.117)向服务器(43.128.1.238)发送了一个 SYN 包以初始化连接。序列号为 0。
SYN-ACK (Seq=0, Ack=1)
- 数据包2: 服务器响应一个 SYN-ACK 包。这表示服务器接受了连接请求并且准备好进行通信。
ACK (Seq=1, Ack=1)
- 数据包3: 客户端回复一个 ACK 包,完成三次握手,建立连接。
窗口大小(Win)
TCP窗口大小是流量控制的一部分,指定了在等待确认之前可以发送的数据量(以字节为单位)。发送方根据接收方给出的窗口大小来决定发送的数据量。防止发送方过快地发送数据而使接收方处理不过来。
最大段大小(MSS)
最大段大小是TCP连接中可以发送的单个数据包的最大数据量。这个值通常在TCP三次握手期间的第一个SYN包中协商,是为了避免IP分片,因为IP分片会增加网络延迟和降低性能。
窗口缩放(WS)
窗口缩放是一个TCP选项,用于在流量控制中支持更大的窗口大小。因为原始的TCP窗口大小字段只有16位,最大值只能是65535字节。窗口缩放选项允许窗口大小值左移(乘以2的指数),使得最大窗口大小可以增加到1GB以上。
SEQ ACK
每个TCP段(数据包)都被赋予一个序列号,这样接收方就可以对接收到的数据进行排序,并检测重复或丢失的数据包。确认号则用于告知发送方哪些数据已经被成功接收。它通常是期望收到的下一个数据包的序列号。
当当
当当:
实验总结
- 本实验通过 Wireshark 工具成功捕获并分析了 TCP 协议的运作机制。
- 分析了 TCP 的三次握手、数据传输、重传机制以及流量控制和拥塞控制。
附录:
一个失败的脚本
from scapy.all import IP, TCP, send, sr1, Raw, socket, RandShort
from scapy.layers.http import HTTP, HTTPRequest
import time
# 目标网址和端口
target = "wireshark.icee.top"
target_ip = socket.gethostbyname(target)
target_port = 80
# 创建IP包
ip = IP(dst=target_ip)
# 创建TCP SYN
tcp_syn = TCP(sport=RandShort(), dport=target_port, flags='S')
# 发送SYN并接收SYN-ACK
print("[+] 发送SYN请求:")
print((ip/tcp_syn).show())
synack = sr1(ip/tcp_syn)
print("[+] 接收到SYN/ACK:")
print(synack.show())
# 从SYN-ACK包中获取序列号,并构造ACK包
print(f"[+] 构造ACK包:ack:[syn ack seq]+1 seq:[syn ack+1]:{synack[TCP].seq + 1}")
tcp_ack = TCP(sport=synack[TCP].dport, dport=target_port, flags='A', seq=synack[TCP].ack, ack=synack[TCP].seq + 1)
# 发送ACK包完成三次握手
print("[+] 发送ACK包:")
print((ip/tcp_ack).show())
send(ip/tcp_ack)
# 构造HTTP GET请求
print("[+] 构造HTTP请求:")
get_str = 'GET / HTTP/1.1\r\nHost: ' + target + '\r\n\r\n'
http_get = ip / tcp_ack / Raw(load=get_str)
print(f"[+] HTTP请求:{http_get.show()}")
# 发送HTTP GET请求并等待响应
print("[+] 发送HTTP请求并等待响应:")
response = sr1(http_get, timeout=5)
print("[+] 接收到HTTP响应:")
print(response.show())
print(f"ACK_dst_port:{synack[TCP].dport} Dport:{target_port}")
print("-----------------------")
# ACK回复已经收到的包
ack = TCP(sport=synack[TCP].dport, dport=target_port, flags='A', seq=response[TCP].ack, ack=response[TCP].seq + len(response[TCP].payload))
print("[+] 发送确认收到HTTP响应的ACK包:")
send(ip/ack)
# # 构造TCP FIN包
# tcp_fin = TCP(sport=synack[TCP].dport, dport=target_port, flags='FA', seq=response[TCP].ack, ack=response[TCP].seq + len(response[TCP].payload))
# print("[+] 发送TCP FIN包以开始断开连接:")
# fin_ack_response = sr1(ip/tcp_fin)
# print("[+] 接收到FIN-ACK响应:")
# print(fin_ack_response.show())
四次挥手没构建成功
先这样bia,数据够啦。
构建出来SEQ真的是0 哈哈哈哈哈
重传
尝试在网络质量差的环境下进行抓包,或者用linux
写了个脚本,结果被RST了
感觉是内核RST的。
脚本的包应该是正常发送,但是内核不知道脚本在做什么,然后接收到了个SYN ACK报文,于是不知道这是什么东西,直接发送了RST,然后这样脚本之后发的东西,服务器也觉得不知道什么东西,也直接RST了。
网上查了查,用iptables过滤了包就行大概?
apt-get install iptables
apt install python3-scapy
# 抓包命令(开另外一个ssh)
tcpdump tcp and host 43.128.1.238 -w capture_file.pcap
#用iptables把要发的RST包筛了去,这样就算收到奇怪的包也不会发RST。
iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP
OK,这样没有RST包了
然后重新发送,并且只运行一次脚本
- 在TCP连接中,每收到一个数据包,理论上应发送一个ACK包来确认收到。在实际操作中,TCP协议通常会通过延迟确认机制(Delayed ACK)来减少需要发送的ACK包数量。
- TCP不会立即对每个接收到的数据包发送ACK,而是可能会延迟一段时间,直到收到更多数据包,或者达到一定的延迟时间阈值后,才发送一个ACK来确认多个数据包。
TCP 重传机制
- 数据包7到13显示了多次 TCP 重传。网络中存在丢包、延迟或通信故障时。TCP 协议通过重传未被确认的数据包来确保数据的可靠传输。
流量控制和拥塞控制
流量控制 (Window Size)
- TCP 使用窗口大小来控制数据传输的速率,以防止接收方的缓冲区被溢出。在这个例子中,窗口大小在不同的包中有所变化,如 Seq=1, Ack=45 时窗口大小为 64196。
拥塞控制
- TCP 拥塞控制机制通过减少在网络中传输的数据量来应对网络拥塞。重传的包可能表明网络中存在拥塞问题,TCP 会动态调整其发送速率来适应网络状况。
抓包脚本1:
import requests
# encode = "utf-8"
req = requests.get("http://wireshark.icee.top",)
# print(req.encoding)
req.encoding = "utf-8"
print(req.text)
# req.encoding = "gbk"
print(req.text)
抓包脚本2:
已经再上文中啦
需要linux(且ROOT)环境