版本: ? ? V 1.0 主要功能: ? ? 用于分析TCP交互过程中的用时信息,找出网络卡慢原因 前置操作: ? ? 安装 python3.x 环境 ? ? 通过tcpdump或wireshark等抓包软件抓包并另存为PCAP文件格式 ? ? 在程序末尾设置: ? ? ? ? 日志文件存放目录位置 ? ? ? ? PCAP文件或存放多个PCAP文件的目录位置 ? ? ? ? 拆分PCAP文件时存放的目录位置 TCP用时分析包含: ? ? 客户端发送请求的传输用时(有多个数据包时计算传输完成用时) ? ? 服务端对客户端请求进行处理的计算用时(服务端处理速度) ? ? 服务端处理完后返回响应数据的传输用时(有多个数据包时计算传输完成用时) ? ? 客户端发起多个请求之间的间隔用时(如:等待用户输入、等待其他进程先完成) ? ? 客户端原因长时间等待保持连接用时(Keep-Alive) ? ? 服务端原因长时间等待保持连接用时(Keep-Alive) ? ? 客户端接收服务器响应数据包时缓冲区满造成等待用时(Windows=0) ? ? 服务端接收客户端请求数据包时缓冲区满造成等待用时(Windows=0) ? ? 会话超时被强制终止前的用时(未完成) UDP用时分析包含: ? ? 未完成 额外功能: ? ? 画流量图(精确到秒)需要先安装 matplotlib 模块 ? ? 从PCAP文件(或存放多个PCAP文件的目录中)中提取指定会话的数据
import socket, struct, os, time
from binascii import b2a_hex, a2b_hex
import logging
import re
ReadMe = '''
版本:
V 1.0
主要功能:
用于分析TCP交互过程中的用时信息,找出网络卡慢原因
前置操作:
安装 python3.x 环境
通过tcpdump或wireshark等抓包软件抓包并另存为PCAP文件格式
在程序末尾设置:
日志文件存放目录位置
PCAP文件或存放多个PCAP文件的目录位置
拆分PCAP文件时存放的目录位置
TCP用时分析包含:
客户端发送请求的传输用时(有多个数据包时计算传输完成用时)
服务端对客户端请求进行处理的计算用时(服务端处理速度)
服务端处理完后返回响应数据的传输用时(有多个数据包时计算传输完成用时)
客户端发起多个请求之间的间隔用时(如:等待用户输入、等待其他进程先完成)
客户端原因长时间等待保持连接用时(Keep-Alive)
服务端原因长时间等待保持连接用时(Keep-Alive)
客户端接收服务器响应数据包时缓冲区满造成等待用时(Windows=0)
服务端接收客户端请求数据包时缓冲区满造成等待用时(Windows=0)
会话超时被强制终止前的用时(未完成)
UDP用时分析包含:
未完成
额外功能:
画流量图(精确到秒)需要先安装 matplotlib 模块
从PCAP文件(或存放多个PCAP文件的目录中)中提取指定会话的数据另存为PCAP文件
'''
####################
## 解析数据包函数 ##
####################
## TCP校验
def 计算校验和(DATA):
LEN = len(DATA)
if LEN % 2 == 0:
FMT = '!' + str(LEN//2) + 'H'
else:
DATA += b'\x00'
LEN = len(DATA)
FMT = '!' + str(LEN//2) + 'H'
X = struct.unpack(FMT, DATA)
SUM = 0
for i in X:
SUM += i
while(SUM > 65535): ## 值大于 65535 说明二进制位数超过16bit
H16 = SUM >> 16 ## 取高16位
L16 = SUM & 0xffff ## 取低16位
SUM = H16 + L16
校验和 = SUM ^ 0xffff
#Log.debug(f"计算 TCP_Checksum {hex(校验和)}")
return(校验和)
## 解析包头(16字节)
def Packet_Header(BytesData):
PacketHeader = struct.unpack('IIII', BytesData)
时间戳 = PacketHeader[0]
微秒 = PacketHeader[1]
抓取数据包长度 = PacketHeader[2] # 所抓获的数据包保存在pcap文件中的实际长度,以字节为单位。
实际数据包长度 = PacketHeader[3] # 所抓获的数据包的真实长度,如果文件中保存不是完整的数据包,那么这个值可能要比前面的数据包长度的值大。
#return(时间戳, 微秒, 抓取数据包长度, 实际数据包长度)
时间戳_float = 时间戳 + 微秒/1000000
return(时间戳_float, 抓取数据包长度, 实际数据包长度)
## 解析帧头(14字节)
def EthernetII_Header(BytesData):
DstMAC = BytesData[0:6] # 目的MAC地址
SrcMAC = BytesData[6:12] # 源MAC地址
FrameType = BytesData[12:14] # 帧类型
return(DstMAC, SrcMAC, FrameType)
## 解析IP头(20字节)
def IPv4_Header(BytesData):
IP_B, IP_TOS, IP_Total_Length, IP_Identification, IP_H, IP_TTL, IP_Protocol, IP_Header_checksum, IP_Source, IP_Destination = struct.unpack('!BBHHHBBHII', BytesData) # B
IP_Version = IP_B >> 4 # 取1字节的前4位
IP_Header_Length = IP_B & 0xF # 取1字节的后4位
IP_Flags = IP_H >> 13
IP_Fragment_offset = IP_H & 0b0001111111111111
return(IP_Version, IP_Header_Length, IP_TOS, IP_Total_Length, IP_Identification, IP_Flags, IP_Fragment_offset, IP_TTL, IP_Protocol, IP_Header_checksum, IP_Source, IP_Destination)
## 解析TCP头(20字节)
def TCP_Header(BytesData):
TCP_Source_Port, TCP_Destination_Port, TCP_Sequence_Number, TCP_Acknowledgment_Number, TCP_H, TCP_Window, TCP_Checksum, TCP_Urgent_Pointers = struct.unpack('!HHLLHHHH', BytesData) # H(2B)
TCP_Data_Offset = TCP_H >> 12
先去掉后6位 = TCP_H >> 6
TCP_Reserved = 先去掉后6位 & 0b0000111111
TCP_Flags = TCP_H & 0b0000000000111111
return(TCP_Source_Port, TCP_Destination_Port, TCP_Sequence_Number, TCP_Acknowledgment_Number, TCP_Data_Offset, TCP_Reserved, TCP_Flags, TCP_Window, TCP_Checksum, TCP_Urgent_Pointers)
## 解析TCP头中Options(0字节或4字节的倍数)
def TCP_Options(BytesData):
#print("TCP_Options", BytesData)
## 格式 Kind/Type(1 Byte) + Length(1 Byte) + Value(X Bytes)
## EOL 和 NOP Option 只有 Kind/Type(1 Byte)
## 准备需要return的变量
MSS = 0 # 本端可以接受的最大实际数据长度(单位字节,不含TCP Header),默认值536,最大65535
WSOPT = 0 # 窗口扩大系数(转成倍数是 2**WSOPT)
#SACK_Premitted = 0 # 告知对方自己支持SACK(允许只重传丢失部分数据),自定义值,1表示启用,0表示不启用
L_SACK_INFO = [] # 重传的数据信息
N = 0 # 记录已经读到的位置
while N < len(BytesData):
# 读取首字节判断 Kind/Type(1 Byte)
Kind = BytesData[N:N+1]
#print(" Kind/Type", Kind)
if Kind == b'\x02':
## 最大Segment长度(MSS)
#print(" Kind: Maximum Segment Size (最大Segment长度)")
Length = 4 ## 固定为4
#print(" Length:", Length)
Value = struct.unpack('!H', BytesData[N+2:N+Length])[0]
#print(" Value: ", Value)
N += Length ## 更新N为实际已经读取了的字数
MSS = Value ## 赋值准备return的变量
elif Kind == b'\x01':
## 补位填充
#print(" Kind: NOP Option (补位填充)")
N +=1
elif Kind == b'\x00':
## 选项列表结束
#print(" Kind: EOL (选项列表结束)")
N +=1
elif Kind == b'\x03':
## 窗口扩大系数
#print(" Kind: Window Scaling Factor (窗口扩大系数)")
Length = 3 ## 固定为3
#print(" Length:", Length)
Value = struct.unpack('B', BytesData[N+2:N+Length])[0]
#print(" Value: ", Value)
N += Length
WSOPT = Value
elif Kind == b'\x04':
## 支持SACK
#print(" Kind: SACK-Premitted(支持SACK)")
Length = 2 ## 固定为2
#print(" Length:", Length)
N += Length
#SACK_Premitted = 1 ## 自定义,1表示启用,0表示不启用
elif Kind == b'\x05':
## 乱序/丢包数据
#print(" Kind: SACK Block(乱序/丢包信息)")
## 长度不固定,存在后续1个字节中
Length = struct.unpack('B', BytesData[N+1:N+2])[0]
#print(" Length:", Length)
Value = BytesData[N+2:N+Length]
#print(" Value: ", b2a_hex(Value))
#print("len(Value)", len(Value))
for i in range(0, len(Value)-2, 8):
T = struct.unpack('!II', Value[i:i+8])
#print("T", T)
L_SACK_INFO.append(T)
N += Length
elif Kind == b'\x08':
## Timestamps(随时间单调递增的值)
#print(" Kind: Timestamps(随时间单调递增的值)")
Length = 10 ## 固定为10
#print(" Length:", Length)
Value = BytesData[N+2:N+Length]
#print(" Value: ", b2a_hex(Value))
N += Length
elif Kind == b'\x13': # 19
## MD5认证
#print(" Kind: TCP-MD5(MD5认证)")
Length = 18 ## 固定为18
#print(" Length:", Length)
Value = BytesData[N+2:N+Length]
#print(" Value: ", b2a_hex(Value))
N += Length
elif Kind == b'\x1c': # 28
## 超过一定闲置时间后拆除连接
#print(" Kind: User Timeout(超过一定闲置时间后拆除连接)")
Length = 4 ## 固定为4
#print(" Length:", Length)
Value = BytesData[N+2:N+Length]
#print(" Value: ", b2a_hex(Value))
N += Length
elif Kind == b'\x1d': # 29
## 认证(可选用各种算法)
#print(" Kind: TCP-AO(认证(可选用各种算法))")
## 长度不固定,存在后续1个字节中
Length = struct.unpack('B', BytesData[N+1:N+2])[0]
#print(" Length:", Length)
Value = BytesData[N+2:N+Length]
#print(" Value: ", b2a_hex(Value))
N += Length
elif Kind == b'\xfe': # 254
## 科研实验保留
#print(" Kind: Experimental(科研实验保留)")
## 长度不固定,存在后续1个字节中
Length = struct.unpack('B', BytesData[N+1:N+2])[0]
#print(" Length:", Length)
Value = BytesData[N+2:N+Length]
#print(" Value: ", b2a_hex(Value))
N += Length
else:
print("【W】未知 TCP Option,终止")
break
return(MSS, WSOPT, L_SACK_INFO)
## 粗略解析TCP实际数据(以UTF8编码解析,忽略解析不出的部分)
def TCP_DATA(BytesData):
print(BytesData.decode('UTF8', errors='ignore'))
##################################################
## 解析PCAP文件中每个数据包,结果存入全局总字典 ##
##################################################
## 总字典 {(源地址,源端口,目的地址,目的端口):D_TCP, (目的地址,目的端口,源地址,源端口):D_TCP}
## 参数 D_TCP 子字典,存单个TCP会话交互中的包信息
## 参数 PacketHeader_Bytes PCAP文件存储每个数据包的包头(16字节)
## D_TCP['L_DATA'] TCP会话交互中的包简要信息列表 (编号,时间戳,SrcIP,DstIP,SrcPort,DstPort,Seq,Ack,FLAGE,TCP_DATA_LEN,TCP_req_or_ack,TCP_Window,hex(TCP_Checksum),PS)
## 前置条件:不能含VLAN信息,这些由前置拆分PCAP文件的函数解决
##################################################
def D_SAVE(D_TCP, PacketHeader_Bytes, PacketData, 编号):
TCP校验 = 0 # 是否检查TCP校验和,0关闭 1开启
PS = '' # 初始化每个包的备注信息
时间戳, 抓取数据包长度, 实际数据包长度 = Packet_Header(PacketHeader_Bytes)
DstMAC, SrcMAC, FrameType = EthernetII_Header(PacketData[0:14]) # 以太帧头
###########【IPv4】###########
if FrameType == b'\x08\x00':
## 解析IP头(20字节[14:34])
(IP_Version, IP_Header_Length, IP_TOS, IP_Total_Length, IP_Identification, IP_Flags, IP_Fragment_offset, IP_TTL, IP_Protocol, IP_Header_checksum, IP_Source, IP_Destination) = IPv4_Header(PacketData[14:34])
IP部首字节长度 = IP_Header_Length*4
###########【TCP】###########
if IP_Protocol == 6: # TCP b'\x06'
TCP_Source_Port, TCP_Destination_Port, TCP_Sequence_Number, TCP_Acknowledgment_Number, TCP_Data_Offset, TCP_Reserved, TCP_Flags, TCP_Window, TCP_Checksum, TCP_Urgent_Pointers = TCP_Header(PacketData[34:54])
if TCP_Reserved != 0:
Log.debug("【注意,保留6bit被使用,支持(CWR/ECN)功能】TCP_Reserved(6bit)", TCP_Reserved)
## 根据TCP部首长度计算是否有TCP可选字段
TCP部首实际长度 = TCP_Data_Offset*4
TCP部首固定长度 = 20
TCP选项长度 = TCP部首实际长度 - TCP部首固定长度
## 初始化TCP选项信息
MSS = 0 # 本端可以接受的最大实际数据长度(单位字节,不含TCP Header),默认值536,最大65535
WSOPT = 0 # 窗口扩大系数
L_SACK_INFO = [] # 放丢包信息
if TCP选项长度 > 0:
MSS, WSOPT, L_SACK_INFO = TCP_Options(PacketData[54:54+TCP选项长度])
if MSS == 0:
MSS = 536 # 使用默认值
TCP_DATA_LEN = IP_Total_Length - IP部首字节长度 - TCP部首固定长度 - TCP选项长度
## 计算数据长度
## 最小60字节,不足会填充
#剩余全部数据 = PacketData[54+TCP选项长度:]
剩余有效数据 = PacketData[54+TCP选项长度:54+TCP选项长度+TCP_DATA_LEN]
#print("剩余全部数据字节数(TCP选项后全部)", len(剩余全部数据)) ## 当不足最小长度会填充,这个会比下面的大
#print("剩余有效数据字节数(计算得到)", len(剩余有效数据))
## 是否进行TCP校验,校验错误的包可能可以正常使用
if TCP校验 == 1:
## 校验TCP是否正确
#print("提取 TCP_Checksum", hex(TCP_Checksum))
TCP_Pseudo_Total_Length = TCP部首实际长度 + TCP_DATA_LEN
#print("TCP_Pseudo_Total_Length", TCP_Pseudo_Total_Length)
TCP_Pseudo_Header = PacketData[26:34] + b'\x00\x06' + struct.pack('!H', TCP_Pseudo_Total_Length) # 构造TCP伪部首
if TCP选项长度 == 0:
DATA = TCP_Pseudo_Header + PacketData[34:54] + 剩余有效数据
else:
TCP选项_Bytes = PacketData[54:54+TCP选项长度]
DATA = TCP_Pseudo_Header + PacketData[34:54] + TCP选项_Bytes + 剩余有效数据
校验结果 = 计算校验和(DATA) #校验通过返回为0
if 校验结果 != 0:
PS = PS + f"TCP校验错误({hex(校验结果)})"
## 分析数据包中的TCP内容
TCP_req_or_ack = '' # 标识一下是请求还是响应(一般都是客户端发起请求,服务端响应)
FLAGE = ''
SrcIP_Bytes = PacketData[26:30] # 源IP地址
DstIP_Bytes = PacketData[30:34] # 目的IP地址
SrcIP = socket.inet_ntoa(SrcIP_Bytes)
DstIP = socket.inet_ntoa(DstIP_Bytes)
SrcPort = TCP_Source_Port
DstPort = TCP_Destination_Port
Seq = TCP_Sequence_Number
Ack = TCP_Acknowledgment_Number
if TCP_Flags == 24: # 24 011000 URG ACK PSH RST SYN FIN
FLAGE = 'ACK PSH'
elif TCP_Flags == 16: # 16 010000 URG ACK PSH RST SYN FIN
FLAGE = 'ACK'
elif TCP_Flags == 17: # 17 010001 URG ACK PSH RST SYN FIN
FLAGE = 'ACK FIN'
elif TCP_Flags == 18: # 18 010010 URG ACK PSH RST SYN FIN
FLAGE = 'ACK SYN'
elif TCP_Flags == 2: # 2 000010 URG ACK PSH RST SYN FIN
FLAGE = 'SYN'
elif TCP_Flags == 20: # 20 010100 URG ACK PSH RST SYN FIN
FLAGE = 'ACK RST'
elif TCP_Flags == 4: # 4 000100 URG ACK PSH RST SYN FIN
FLAGE = 'RST'
elif TCP_Flags == 25: # 25 011001 URG ACK PSH RST SYN FIN
FLAGE = 'A.P.F'
elif TCP_Flags == 28: # 28 011100 URG ACK PSH RST SYN FIN
FLAGE = 'A.P.R'
else:
FLAGE = 'NA'
Log.error("【ERROR】TCP_Flags unknown %d URG ACK PSH RST SYN FIN 编号 %d" % (TCP_Flags, 编号))
## 先判断出客户端和服务端的IP及端口
## 如果前面还没有判断出客户端和服务端,且这个包不是'SYN'和'ACK SYN',就根据端口大小估计客户端和服务端,大的为客户端
if D_TCP['CLIENT'] == ():
if FLAGE not in ('SYN', 'ACK SYN'):
if SrcPort > DstPort:
D_TCP['CLIENT'] = (SrcIP, SrcPort)
D_TCP['SERVER'] = (DstIP, DstPort)
D_TCP['C_Tx_DATA_ALL'] = Seq
D_TCP['S_Tx_DATA_ALL'] = Ack
else:
D_TCP['CLIENT'] = (DstIP, DstPort)
D_TCP['SERVER'] = (SrcIP, SrcPort)
D_TCP['S_Tx_DATA_ALL'] = Seq
D_TCP['C_Tx_DATA_ALL'] = Ack
## 根据FLAGE分类存数据包
if FLAGE in ('ACK', 'ACK PSH'):
## 先区分是哪端发的ACK
if SrcPort == D_TCP['CLIENT'][1]:
## 客户端发ACK
## Seq = C发的这个包的数据编号(累计值,不含当次数据,不含重发)
## Ack = C确认已经接到S数据的编号(丢包/乱序信息放在TCP选项SACK中发送)
## 判断是否有数据
if TCP_DATA_LEN == 0:
TCP_ACK_KeepAlive_Mark = 0 # 标记是否是Keep-Alive包,0表示不是。1表示是,初始化为0
if TCP_Window == 0: # 记录C发的ACK且窗口为0的包信息,说明C接收缓存满,同一段接收满的包的(Seq,Ack)相同,就只记录一次Ack
PS = PS + '接收缓冲区已满(C)'
if Ack not in D_TCP['D_C_WIN_0']:
D_TCP['D_C_WIN_0'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)] # 记录C接收缓存满的包信息
D_TCP['C_ACK_WIN_0'].append((Seq,Ack)) # 记录C接收缓存满的包信息(判断对方探测包使用)
else:
D_TCP['D_C_WIN_0'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)) # 记录C接收缓存满的包信息
if L_SACK_INFO != []:
if L_SACK_INFO[0][0] +1 == L_SACK_INFO[0][1]:
PS = PS + f'【TCP Keep-Alive ACK】(C) 响应保持连接包,L_SACK_INFO={L_SACK_INFO}'
TCP_ACK_KeepAlive_Mark = 1 # 标记是Keep-Alive包
## 保存C响应保持连接的包信息
if Ack in D_TCP['C_TCP_Keep_Alive_ACK']:
D_TCP['C_TCP_Keep_Alive_ACK'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
else:
D_TCP['C_TCP_Keep_Alive_ACK'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
else:
PS = PS + f'C丢包乱序通知={L_SACK_INFO}'
## 保存纯确认消息包到 D_C_ACK (C确认收完S数据的纯回应包会在这里)
if TCP_ACK_KeepAlive_Mark == 0: # 忽略响应保持连接包
if Ack in D_TCP['D_C_ACK']:
D_TCP['D_C_ACK'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window))
else:
D_TCP['D_C_ACK'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window)]
## 数据长度为1,Seq(自发送累计) 主动变小1的,应该就是C发的keep-alive包,不累计大小
elif TCP_DATA_LEN == 1 and 剩余有效数据 == b'\x00':
PS = PS + '【TCP Keep-Alive】 (C) 请求保持连接包(长度1,内容空)'
## 保存C发起保持连接的包信息
if Ack in D_TCP['C_TCP_Keep_Alive']:
D_TCP['C_TCP_Keep_Alive'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
else:
D_TCP['C_TCP_Keep_Alive'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
elif TCP_DATA_LEN == 1 and (Ack,Seq) in D_TCP['S_ACK_WIN_0']: # TCP数据长度是1且能和S缓存满的消息能对应上,说明是C发的探测S窗口的包
PS = PS + '(C=>S Window=?)'
#elif TCP_DATA_LEN == 1: # 查看长度为1情况
# print(编号, 剩余有效数据, len(剩余有效数据))
else:
## 已经避开了纯消息包和保持连接的包
## 记录TCP有效数据的包信息(C)发起的(一般都是客户端发起请求,包信息保存到 C_TCP_REQ 字典 Key=Ack Value=[(包信息1),(包信息2)] 一个请求可能会分成多个包发送)
TCP_req_or_ack = 'C_req' # 备注标识为:客户端发起请求
if Ack in D_TCP['D_C_ACK_DATA']:
D_TCP['D_C_ACK_DATA'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
else:
D_TCP['D_C_ACK_DATA'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
## 重复的情况
if (Seq,Ack) not in D_TCP['P_C_Tx_Seq_Ack']:
D_TCP['P_C_Tx_Seq_Ack'].add((Seq,Ack)) # 加入C发送过的数据集合,查重复用
D_TCP['C_Tx_DATA_ALL'] += TCP_DATA_LEN # 累计C发送数据(也可以用于识别C发的keep-alive)
else:
PS = PS + 'C重发数据(Seq,Ack)完全重复'
else:
## 服务端发ACK
## Seq = S自己已经发数据的累计(不含本次的数据量,不累计重发数据)
## Ack = S已经接到了C发的多少数据(S告诉C端,S是接到C多少数据后的回应,在HTTP里这个可以用于区别这个回应是针对哪个请求的)
if TCP_DATA_LEN == 0:
## 保存到 D_S_ACK
if Ack in D_TCP['D_S_ACK']:
D_TCP['D_S_ACK'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window))
else:
D_TCP['D_S_ACK'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window)]
if TCP_Window == 0: # 记录S发的ACK且窗口为0的包信息(Seq,Ack)说明S接收缓存满
PS = '接收缓冲区已满(S)'
if Ack not in D_TCP['D_S_WIN_0']:
D_TCP['D_S_WIN_0'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
D_TCP['S_ACK_WIN_0'].append((Seq,Ack)) # 记录S接收缓存满的包信息
else:
D_TCP['D_S_WIN_0'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
if L_SACK_INFO != []:
if L_SACK_INFO[0][0] +1 == L_SACK_INFO[0][1]:
PS = PS + f'【TCP Keep-Alive ACK】(S) 响应保持连接包,L_SACK_INFO={L_SACK_INFO}'
## 保存S响应保持连接的包信息
if Ack in D_TCP['S_TCP_Keep_Alive_ACK']:
D_TCP['S_TCP_Keep_Alive_ACK'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
else:
D_TCP['S_TCP_Keep_Alive_ACK'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
else:
PS = PS + f'S丢包乱序通知={L_SACK_INFO}'
## 数据长度是1且能和C缓存满的消息能对应上(Seq Ack 交换位置),说明是S发的探测C窗口的包
elif TCP_DATA_LEN == 1 and (Ack,Seq) in D_TCP['C_ACK_WIN_0']:
PS = PS + '探测窗口(S=>C Window=?)'
## (S) 请求保持连接包(长度1,内容空)
elif TCP_DATA_LEN == 1 and 剩余有效数据 == b'\x00':
PS = PS + '【TCP Keep-Alive】 (S) 请求保持连接包(长度1,内容空)'
## 保存S发起保持连接的包信息
if Ack in D_TCP['S_TCP_Keep_Alive']:
D_TCP['S_TCP_Keep_Alive'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
else:
D_TCP['S_TCP_Keep_Alive'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
else:
## 已经避开了纯消息包和保持连接的包
TCP_req_or_ack = ' S_ack' # 备注标识为:服务端响应请求
## 记录TCP有效数据的包信息(S)发起的
if Ack in D_TCP['D_S_ACK_DATA']:
D_TCP['D_S_ACK_DATA'][Ack].append((时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号))
else:
D_TCP['D_S_ACK_DATA'][Ack] = [(时间戳, Seq, Ack, TCP_DATA_LEN, TCP_Window, 编号)]
## 重复的情况
if (Seq,Ack) not in D_TCP['P_S_Tx_Seq_Ack']:
D_TCP['P_S_Tx_Seq_Ack'].add((Seq,Ack))
D_TCP['S_Tx_DATA_ALL'] += TCP_DATA_LEN # 累计S发送数据(也可以用于识别S发的keep-alive)
else:
PS = PS + 'S重发数据(Seq,Ack)完全重复'
elif FLAGE in ('ACK FIN', 'A.P.F'): ## 终止连接
if DstPort == D_TCP['CLIENT'][1]: ## 是 S 发起的 ACK FIN
PS = PS + '(S)发起终止连接'
if Seq in D_TCP['S_ACK_FIN_SeqKey']:
D_TCP['S_ACK_FIN_SeqKey'][Seq].append((时间戳, Seq, Ack, TCP_DATA_LEN))
else:
D_TCP['S_ACK_FIN_SeqKey'][Seq] = [(时间戳, Seq, Ack, TCP_DATA_LEN)]
else: ## 是 C 发起的 ACK FIN
PS = PS + '(C)发起终止连接'
D_TCP['C_ACK_FIN'] = (时间戳, Seq, Ack, TCP_DATA_LEN)
elif FLAGE in ('ACK RST', 'RST', 'A.P.R'): ## 强制断开连接
if DstPort == D_TCP['CLIENT'][1]:
if Ack in D_TCP['S_ACK_RST']:
PS = PS + 'S强制终止连接(重复)'
else:
D_TCP['S_ACK_RST'][Ack] = (时间戳, Seq, Ack, TCP_DATA_LEN)
PS = PS + 'S强制终止连接'
else:
## C终止自己的请求 C_ACK_RST_AckKey 以Ack为KEY,给没有数据回应情况用请求Ack匹配RST的ACK
if Ack in D_TCP['C_ACK_RST_AckKey']:
PS = PS + 'C强制终止连接(重复)'
else:
PS = PS + 'C强制终止连接'
D_TCP['C_ACK_RST_AckKey'][Ack] = (时间戳, Seq, Ack, TCP_DATA_LEN) # 记录
## C终止自己的请求 C_ACK_RST_SeqKey 以Seq为KEY,期望S响应的Ack=这个Seq,给有回应的匹配用
if Seq in D_TCP['C_ACK_RST_SeqKey']:
PS = PS + '(匹配有回应)'
else:
D_TCP['C_ACK_RST_SeqKey'][Seq] = (时间戳, Seq, Ack, TCP_DATA_LEN) # 记录
PS = PS + '(记录)'
elif FLAGE == 'SYN': ## 发起连接方是客户端
if D_TCP['CLIENT'] != ():
if D_TCP['CLIENT'] == (SrcIP, SrcPort):
PS = PS + f"重复SYN【C 新建TCP连接】初始Seq={D_TCP['C_Tx_DATA_ALL']} MSS={MSS} 窗口扩大系数={WSOPT}"
else:
D_TCP['CLIENT'] = (SrcIP, SrcPort)
D_TCP['SERVER'] = (DstIP, DstPort)
D_TCP['C_SYN'] = (时间戳, Seq, Ack, 0)
D_TCP['C_Tx_DATA_ALL'] = Seq+1 ## 客户端初始数据编号
PS = PS + f"前面判断谁是客户端错误或端口被复用,重置【C 新建TCP连接】初始Seq={D_TCP['C_Tx_DATA_ALL']} MSS={MSS} 窗口扩大系数={WSOPT}"
else:
D_TCP['CLIENT'] = (SrcIP, SrcPort)
D_TCP['SERVER'] = (DstIP, DstPort)
D_TCP['C_SYN'] = (时间戳, Seq, Ack, 0)
D_TCP['C_Tx_DATA_ALL'] = Seq+1 ## 客户端初始数据编号
PS = PS + f"【C 新建TCP连接】初始Seq={D_TCP['C_Tx_DATA_ALL']} MSS={MSS} 窗口扩大系数={WSOPT}"
elif FLAGE == 'ACK SYN': ## 回应SYN请求,是本次连接的服务端,Seq随机,Ack为发起方Seq+1
if D_TCP['CLIENT'] == ():
D_TCP['CLIENT'] = (DstIP, DstPort)
D_TCP['SERVER'] = (SrcIP, SrcPort)
if D_TCP['C_SYN'] != ():
if Ack == D_TCP['C_SYN'][1] + 1:
D_TCP['S_ACK_SYN'] = (时间戳, Seq, Ack, 0)
D_TCP['S_Tx_DATA_ALL'] = Seq+1 ## 服务端初始数据编号
PS = PS + f"【S 响应TCP连接】初始Seq={D_TCP['S_Tx_DATA_ALL']} MSS={MSS} 窗口扩大系数={WSOPT}"
else:
PS = PS + 'SYN_ACK错误 或 SYN重连 或 端口被复用'
else:
DEBUG = f"【DEBUG】编号={编号} ACK SYN 前面没有SYN"
Log.debug(DEBUG)
else:
Log.error(f"【ERROR】未定义怎么处理的 FLAGE {FLAGE} 忽略")
PS = PS + f"【ERROR】未定义怎么处理的 FLAGE {FLAGE} 忽略"
## 保存信息用于显示(TCP校验通过的包/忽略TCP校验的包)
D_TCP['L_DATA'].append((编号,时间戳,SrcIP,DstIP,SrcPort,DstPort,Seq,Ack,FLAGE,TCP_DATA_LEN,TCP_req_or_ack,TCP_Window,hex(TCP_Checksum),PS))
elif IP_Protocol == 17:
Log.error("【ERROR】UDP PASS")
else:
Log.error("【ERROR】NOT TCP/UDP PASS")
else:
Log.error("【ERROR】NOT IPv4 Ethernet")
## 记录数据包信息
def SHOW_PACK_INFO(L_DATA):
Log.info('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-5s %5s %s %s' % ('ID','TIME','SrcIP','DstIP','SPort','DPort','Seq','Ack','FLAGE','LEN','请求/响应','WIN','校验码','备注说明'))
for i in L_DATA:
Log.info('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s' % i)
Log.info('')
#############################
## 分析请求/响应的交互时间 ##
#############################
## 以客户端视角(在客户端处抓包)或服务端视角(在服务端处抓包)分析每个请求/响应的交互时间,记录各过程耗时,返回列表
## 请求完成耗时 COMP
## 发完请求耗时 TX
## 处理请求耗时 SYS
## 传回结果耗时 RX
## 设置了一下可以反应问题的自定义响应码:
# 响应状态码 = 0 # 自定义初始值,如果后面解析不出,就响应码设置为0
# 响应状态码 = 1 # 自定义,两种方法都找不到响应信息,且发现了C发的RST信息
# 响应状态码 = 2 # 自定义,两种方法都找不到响应信息,且没有发现C发的RST信息,应该是真没有响应
# 响应状态码 = 3 # 自定义,两种方法都找不到响应信息,且找到服务端发起了FIN终止
# 响应状态码 = 4 # 自定义,有响应的情况下表示C发起了RST,强制断开连接
# 响应状态码 = 5 # 自定义,两种方法都找不到响应信息,且发现了S发的RST信息
## 分析请求的结果及用时信息,返回分析结果 L_TCP_TIME
def TCP_SESSION_TIME(D_TCP):
## 客户端发起的请求都在 D_TCP['D_C_ACK_DATA'] 中,有数据,大请求分成多个包的话seq最小那个是第一个包(可在乱序中找到实际第一个包)
## 在 D_TCP['D_C_ACK_DATA'] 中找出起始请求包,生成包信息列表
L_C_ACK = []
for K in D_TCP['D_C_ACK_DATA']: # {1653587983: [(1625798739.757637, 1741930757, 1653587983, 87, 8212, 5)], ...}
L = D_TCP['D_C_ACK_DATA'][K]
MIN_INFO = L[0]
MIN_SEQ = L[0][1]
for Ln in range(1, len(L)):
if L[Ln][1] < MIN_SEQ:
MIN_SEQ = L[Ln][1]
MIN_INFO = L[Ln]
L_C_ACK.append(MIN_INFO)
## 服务端响应生成一个以Seq为Key,Ack为值的字典,方便客户端以自己Ack去匹配服务端Seq然后找出相应服务端Ack
D_S_ACK_DATA_SeqKey = {}
for K in D_TCP['D_S_ACK_DATA']:
L = D_TCP['D_S_ACK_DATA'][K]
MIN_SEQ = L[0][1]
ACK = L[0][2]
for Ln in range(1, len(L)):
if L[Ln][1] < MIN_SEQ:
MIN_SEQ = L[Ln][1]
ACK = L[Ln][2]
D_S_ACK_DATA_SeqKey[MIN_SEQ] = ACK
L_TCP_TIME = [] # 记录用时分析结果
for C_REQ in L_C_ACK:
C发送请求时间戳 = C_REQ[0]
REQ_seq = C_REQ[1]
REQ_ack = C_REQ[2]
REQ_len = C_REQ[3]
## 设置初始值
响应状态码 = 0 # 正常响应设置为0
请求类型 = "TCP"
## 处理 C 发送的请求包
Log.debug(f"C 发送 {请求类型} 请求 ACK={REQ_ack}")
C发送请求包列表 = D_TCP['D_C_ACK_DATA'][REQ_ack]
## 检查一下有没有异常的数据包(乱序/编号有问题)
if 检查数据包编号(C发送请求包列表) == 1:
Log.debug(" 【WARNING】数据包编号有乱序")
else:
Log.debug(" 数据包编号正常(单包请求忽略检查)")
## 开始分析请求时间信息(没有处理重发/乱序问题)
REQ_DATA_LEN = 0 # 累计请求数据总长度(字节)
C发送请求尾包 = C发送请求包列表[-1] # 请求包按时间最后一个
C发完请求耗时 = C发送请求尾包[0] - C发送请求时间戳 # 最后一个减去第一个包的时间,单包请求结果为0
for i in C发送请求包列表:
Log.debug(f' {请求类型} 请求信息 {i}')
REQ_DATA_LEN += i[3]
累计计算预估S响应ACK = REQ_seq+REQ_DATA_LEN
## C 发送的重复GET/POST包:(Seq,Ack)重复的包
C重发数据包数量 = 0
#if REQ_ack in D_TCP['D_C_ACK_DATA_RPT']:
# for i in D_TCP['D_C_ACK_DATA_RPT'][REQ_ack]:
# Log.debug(f' 重发 {请求类型} 请求信息 {i}')
# C重发数据包数量 += 1
Log.debug(f" C 发送 {请求类型} 数据统计:正常 {len(C发送请求包列表)} 个,共 {REQ_DATA_LEN} 字节,重发 {C重发数据包数量} 个")
Log.debug(f" C 发送 {请求类型} 时间 {C发送请求时间戳}")
Log.debug(f" C 发完 {请求类型} 时间 {C发送请求尾包[0]}")
Log.debug(" C 发完 %s 耗时 %.6f 秒 (不含重发数据的时间)" % (请求类型, C发完请求耗时))
if REQ_ack in D_S_ACK_DATA_SeqKey: ## D_S_ACK_DATA_SeqKey 的KEY是HTTP首回应包的seq,值等于请求的ack
C_REQ提取S_ACK = D_S_ACK_DATA_SeqKey[REQ_ack]
else:
C_REQ提取S_ACK = -1
if C_REQ提取S_ACK == 累计计算预估S响应ACK:
Log.debug(f" 【I】期望 S 回应 ACK {C_REQ提取S_ACK}(请求ACK查响应SEQ) == {累计计算预估S响应ACK}(发数据累计预估)")
else:
Log.debug(f" 【W】期望 S 回应 ACK {C_REQ提取S_ACK}(请求ACK查响应SEQ) != {累计计算预估S响应ACK}(发数据累计预估)")
## 检查有没有响应数据
if C_REQ提取S_ACK in D_TCP['D_S_ACK_DATA']: ## 初始值是负数,没有找到可用值时使用初始可以保证这个判断失败
Log.debug(f" 【I】使用(请求ACK查响应SEQ)找到的响应信息")
S响应ACK = C_REQ提取S_ACK
elif 累计计算预估S响应ACK in D_TCP['D_S_ACK_DATA']:
Log.debug(f" 【I】使用(累计计算预估S响应ACK)找到的响应信息")
S响应ACK = 累计计算预估S响应ACK
elif 累计计算预估S响应ACK in D_TCP['S_ACK_RST']:
Log.debug(f" 【E】使用(累计计算预估S响应ACK)找到S强制终止连接的响应信息")
响应状态码 = 5 ## 自定义,两种方法都找不到响应信息,且发现了S发的RST信息
L_TCP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, 0, C发完请求耗时, 0, 0, C重发数据包数量, 0))
continue
elif REQ_ack in D_TCP['C_ACK_RST_AckKey']:
Log.debug(f" 【E】找不到响应数据,找到C主动终止了连接:发现ACK={REQ_ack} 在 D_TCP['C_ACK_RST_AckKey']出现")
响应状态码 = 1 ## 自定义,两种方法都找不到响应信息,且发现了C发的RST信息
L_TCP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, 0, C发完请求耗时, 0, 0, C重发数据包数量, 0))
continue
elif REQ_ack in D_TCP['S_ACK_FIN_SeqKey']:
Log.debug(f" 【E】找不到响应数据,找到服务端发送FIN终止连接:{D_TCP['S_ACK_FIN_SeqKey'][REQ_ack]}")
响应状态码 = 3 ## 自定义,两种方法都找不到响应信息,且找到服务端发起了FIN终止
L_TCP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, 0, C发完请求耗时, 0, 0, C重发数据包数量, 0))
continue
else:
Log.debug(" 【E】两种方法都找不到响应信息,且没有发现C发的RST信息,应该是真没有响应")
响应状态码 = 2 ## 自定义,两种方法都找不到响应信息,且没有发现C发的RST信息,应该是真没有响应
L_TCP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, 0, C发完请求耗时, 0, 0, C重发数据包数量, 0))
continue
## 2个方法中任意成功一个就继续分析
S发送响应数据包列表 = D_TCP['D_S_ACK_DATA'][S响应ACK]
## 有响应响应信息会继续下面的分析
## 当客户端发起RST,服务器依据可能返回响应数据,为了区分出这种情况,最后再查一下这个,修改响应码标识
if S响应ACK in D_TCP['C_ACK_RST_SeqKey']:
Log.debug(f" 【W】有响应的情况下C主动终止了连接:RST_Seq=S响应ACK={S响应ACK} 在 D_TCP['C_ACK_RST_SeqKey']出现(设置自定义SC=4)")
响应状态码 = 4 # 自定义,有响应的情况下表示C发起了RST,强制断开连接
## 检查一下有没有异常的数据包(乱序/编号有问题)
if 检查数据包编号(S发送响应数据包列表) == 1:
Log.debug(" 【WARNING】数据包编号有乱序")
else:
Log.debug(" 数据包编号正常(单包请求忽略检查)")
## 接到的正常响应包
R_REQ_DATA_LEN = 0
for i in S发送响应数据包列表:
Log.debug(f" {请求类型} 响应信息 {i}")
R_REQ_DATA_LEN += i[3]
## S重发响应数据包,(Seq,Ack)重复的包
S重发数据包数量 = 0
#if S响应ACK in D_TCP['D_S_ACK_DATA_RPT']:
# for i in D_TCP['D_S_ACK_DATA_RPT'][S响应ACK]:
# Log.debug(f" 重发 {请求类型} 响应信息 {i}")
# S重发数据包数量 += 1
Log.debug(" S 响应数据统计:正常 %d 个,共 %d 字节,响应状态码 %d,重发 %d 个" % (len(S发送响应数据包列表), R_REQ_DATA_LEN, 响应状态码, S重发数据包数量))
S发送响应数据包首数据 = S发送响应数据包列表[0]
S发送响应数据包尾数据 = S发送响应数据包列表[-1]
S处理请求耗时 = S发送响应数据包首数据[0] - C发送请求尾包[0]
Log.debug(f' S 发送响应数据时间 {S发送响应数据包首数据[0]}')
Log.debug(f' S 发完响应数据时间 {S发送响应数据包尾数据[0]}')
S发完响应数据耗时 = S发送响应数据包尾数据[0] - S发送响应数据包首数据[0]
Log.debug(" S 发完响应数据耗时 %.6f (不含重发数据的时间)" % (S发完响应数据耗时))
Log.debug(" S 处理 %s 耗时 %.6f 秒 (S发送响应数据首包时间 - C发送请求数据尾包时间)" % (请求类型, S处理请求耗时))
C完成请求耗时 = S发送响应数据包尾数据[0] - C发送请求时间戳
Log.debug(" 完成 %s 总耗时 %.6f 秒 (S发送响应数据尾包时间 - C发送请求数据首包时间)" % (请求类型, C完成请求耗时))
## 记录耗时(正常情况)
L_TCP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, C完成请求耗时, C发完请求耗时, S处理请求耗时, S发完响应数据耗时, C重发数据包数量, S重发数据包数量))
## 查看重复的首个请求信息
#if K in D_TCP['C_HTTP_REQ_RPT']:
# Log.warning(f"【WARNING】请求首包有重发 {D_TCP['C_HTTP_REQ_RPT'][K]}")
Log.debug('')
return(L_TCP_TIME)
## 请求耗时信息汇总 L_TCP_TIME
def SHOW_TCP_ALTERNATELY_TIME(L_TCP_TIME, 视角='S'):
Log.debug('')
if L_TCP_TIME != []:
if 视角 == 'S':
Log.debug("【每个请求发出到收完响应数据的耗时信息(服务端抓包)单位秒】")
Log.debug(" %-9s %-9s %4s %3s %5s %7s %7s %7s %4s %4s" % ('请求(ack)','时间戳','类型','SC','COMP','C_req','SYS','S_send','C_RPT','S_RPT'))
else:
Log.debug("【每个请求发出到收完响应数据的耗时信息(客户端抓包)单位秒】")
Log.debug(" %-9s %-9s %4s %3s %5s %7s %7s %7s %4s %4s" % ('请求(ack)','时间戳','类型','SC','COMP','C_req','SYS_NET','S_send','C_RPT','S_RPT'))
for i in L_TCP_TIME:
Log.debug(" %10d %.2f %4s %-4d %7.3f %7.3f %7.3f %7.3f %4d %4d" % i)
Log.debug('')
## 正常返回0 错误返回1
def 检查数据包编号(L):
#Log.debug(f" L{L}")
#Log.debug(f" S 0 {L[0]}")
数量 = len(L)
if 数量 > 1:
SEQ = L[0][1]
LEN = L[0][3]
for i in range(1, 数量):
if L[i][1] == SEQ + LEN:
SEQ = SEQ + LEN
LEN = L[i][3]
#Log.debug(f" V {i} {L[i]}")
else:
#Log.debug(f" X {i} {L[i]}")
Log.debug(f" 【DEBUG】错误包{L[i]}")
return(1)
return(0)
## 整个TCP交互的开始和结束时间
def 整个TCP交互的开始和结束时间(D_TCP, L_DATA):
if D_TCP['C_SYN'] == ():
C_发起开始时间 = L_DATA[0][1] # 第一个包的时间戳
else:
C_发起开始时间 = D_TCP['C_SYN'][0]
if D_TCP['C_ACK_FIN'] == ():
C_发起结束时间 = L_DATA[-1][1] # 最后一个包的时间戳
else:
C_发起结束Ack = D_TCP['C_ACK_FIN'][2] ## D_TCP['C_ACK_FIN'] = (时间戳, Seq, Ack, TCP数据长度)
if C_发起结束Ack in D_TCP['D_C_ACK']:
C_发起结束时间 = D_TCP['D_C_ACK'][C_发起结束Ack][0][0] ## C FIN 前的ack纯确认包作为实际交互结束时间点
else:
C_发起结束Seq = D_TCP['C_ACK_FIN'][1]
C_发起结束时间 = D_TCP['D_S_ACK_DATA'][C_发起结束Seq][0][0] ## C FIN 前的服务端最后数据包作为交互结束时间点(客户端直接结束交互的情况)
return(C_发起开始时间, C_发起结束时间)
## 从TCP会话字典信息D_TCP中统计分析 Keep-Alive 时间(分服务端原因造成的和客户端原因造成的情况)
## L_C_KeepAlive_RANGE (客户端原因等待的时间戳区间列表:可能是客户端在等待什么,如等待用户输入,等待其他任务完成)
## L_S_KeepAlive_RANGE (服务端原因等待的时间戳区间列表:可能是服务端在处理耗时的请求)
def 分析KeepAlive耗时(D_TCP):
L_C_KeepAlive_RANGE = []
L_S_KeepAlive_RANGE = []
## 先分析C首先发起KeepAlive的情况
Log.debug(f" 遍历 D_TCP['C_TCP_Keep_Alive']")
for Ack in D_TCP['C_TCP_Keep_Alive']: # 每个客户端发起每段保持连接信息(相同Ack是同一KeepAlive区间)
Log.debug(f" {Ack}:{D_TCP['C_TCP_Keep_Alive'][Ack]}")
归属标识 = '' # 需要判断是客户端还是服务器原因造成的KeepAlive
C_time = D_TCP['C_TCP_Keep_Alive'][Ack][0][0] # 本段C发起KeepAlive的第一个包的时间戳
C_seq = D_TCP['C_TCP_Keep_Alive'][Ack][0][1] # 本段C发起KeepAlive的第一个包的seq
if C_seq+1 in D_TCP['S_TCP_Keep_Alive'] and D_TCP['S_TCP_Keep_Alive'][C_seq+1][0][0] < C_time: # S比C早发起保持连接,忽略C,按S的算(防止后面处理S发起KeepAlive时重复计算)
Log.debug(f" 忽略{D_TCP['C_TCP_Keep_Alive'][Ack][0]}")
else:
Log.debug(f" 确认{D_TCP['C_TCP_Keep_Alive'][Ack][0]}")
## 根据 KeepAlive 前后的数据包判断 KeepAlive 是谁的原因造成的
## 往前找开始KeepAlive的包(比KeepAlive包提前一个才是真实开始等待的时间)
if C_seq+1 in D_TCP['D_S_ACK_DATA'] and C_time>=D_TCP['D_S_ACK_DATA'][C_seq+1][-1][0]: # 客户端发KeepAlive,客户端seq+1会是服务端发数据Ack,所以在D_TCP['D_S_ACK_DATA']中找
Log.debug(f" (始)前包服务器响应数据,归客户端原因 ACK(C_seq+1)={C_seq+1} in D_TCP['D_S_ACK_DATA']={D_TCP['D_S_ACK_DATA'][C_seq+1]}")
选择开始保持连接的数据包 = D_TCP['D_S_ACK_DATA'][C_seq+1][-1] # 开始部分要找最后一个数据包
归属标识 = 'C'
elif Ack in D_TCP['D_C_ACK_DATA'] and C_time>=D_TCP['D_C_ACK_DATA'][Ack][-1][0]: # 客户端发KeepAlive,但是客户端发数据在等待服务端,所以在D_TCP['D_C_ACK_DATA']中找,ACK相同
Log.debug(f" (始)前包客户端响应数据,归服务端原因ACK={Ack} in D_TCP['D_C_ACK_DATA']={D_TCP['D_C_ACK_DATA'][Ack]}")
选择开始保持连接的数据包 = D_TCP['D_C_ACK_DATA'][Ack][-1] # 开始部分要找最后一个数据包
归属标识 = 'S'
else:
if D_TCP['C_TCP_Keep_Alive'][Ack][0][5] == D_TCP['L_DATA'][0][0]: # 在会话抓到的开头的包就是KeepAlive(第一个包的第一个编号判断)
## 往后找
Log.debug(f" 开头的包就是KeepAlive(C发起),根据KeepAlive结束后是C发请求还是S响应数据来判断是谁的原因造成本次KeepAlive")
if Ack in D_TCP['D_C_ACK_DATA'] and C_time <= D_TCP['D_C_ACK_DATA'][Ack][0][0]:
Log.debug(f" (始)后包客户端请求数据,归客户端原因 ACK={Ack} in D_TCP['D_C_ACK_DATA']={D_TCP['D_C_ACK_DATA'][Ack]} 本身为开始计时包{D_TCP['C_TCP_Keep_Alive'][Ack]}")
归属标识 = 'C'
elif C_seq+1 in D_TCP['D_S_ACK_DATA'] and C_time <= D_TCP['D_S_ACK_DATA'][C_seq+1][0][0]:
Log.debug(f" (始)后包服务器响应数据,归服务端原因 ACK(C_seq+1)={C_seq+1} in D_TCP['D_S_ACK_DATA']={D_TCP['D_S_ACK_DATA'][C_seq+1]} 本身为开始计时包{D_TCP['C_TCP_Keep_Alive'][Ack]}")
归属标识 = 'S'
else:
Log.debug(f" 判断不出KeepAlive是哪方原因造成的")
continue
选择开始保持连接的数据包 = D_TCP['C_TCP_Keep_Alive'][Ack][0]
else:
Log.debug(f" 不在D_S_ACK_DATA及D_C_ACK_DATA中,有问题")
continue
等待开始时间 = 选择开始保持连接的数据包[0]
## 找结束KeepAlive,开始发有效数据的第一个包(比KeepAlive包晚一个才是真实结束等待的时间)
if Ack in D_TCP['D_C_ACK_DATA'] and C_time<=D_TCP['D_C_ACK_DATA'][Ack][0][0]:
Log.debug(f" (终)ACK={Ack} in D_TCP['D_C_ACK_DATA']={D_TCP['D_C_ACK_DATA'][Ack]}")
选择标识结束保持连接的数据包 = D_TCP['D_C_ACK_DATA'][Ack][0]
elif C_seq+1 in D_TCP['D_S_ACK_DATA'] and C_time<=D_TCP['D_S_ACK_DATA'][C_seq+1][0][0]:
Log.debug(f" (终)ACK(C_seq+1)={C_seq+1} in D_TCP['D_S_ACK_DATA']={D_TCP['D_S_ACK_DATA'][C_seq+1]}")
选择标识结束保持连接的数据包 = D_TCP['D_S_ACK_DATA'][C_seq+1][0]
else:
## 有可能等待最后都没有后续数据,找最后一个保持连接的包
Log.debug(" (W)找不到后续数据包")
X1 = D_TCP['C_TCP_Keep_Alive'][Ack][-1]
if C_seq+1 in D_TCP['S_TCP_Keep_Alive']:
X2 = D_TCP['S_TCP_Keep_Alive'][C_seq+1][-1]
if X1[0] > X2[0]:
选择标识结束保持连接的数据包 = X1
else:
选择标识结束保持连接的数据包 = X2
else:
选择标识结束保持连接的数据包 = X1
Log.debug(f" (终)选择标识结束保持连接的数据包={选择标识结束保持连接的数据包}")
等待结束时间 = 选择标识结束保持连接的数据包[0]
Log.debug(f" {选择开始保持连接的数据包} <-{归属标识} KeepAlive 时间区间-> {选择标识结束保持连接的数据包}")
if 归属标识 == 'C':
L_C_KeepAlive_RANGE.append((等待开始时间, 等待结束时间))
elif 归属标识 == 'S':
L_S_KeepAlive_RANGE.append((等待开始时间, 等待结束时间))
else:
pass
#Log.debug("")
## 再分析S首先发起KeepAlive的情况
Log.debug(f" 遍历 D_TCP['S_TCP_Keep_Alive']")
for Ack in D_TCP['S_TCP_Keep_Alive']: # 每个服务端发起每段保持连接信息(相同Ack是同一KeepAlive区间)
Log.debug(f" {Ack}:{D_TCP['S_TCP_Keep_Alive'][Ack]}")
归属标识 = '' # 需要判断是客户端还是服务器原因造成的KeepAlive
C_time = D_TCP['S_TCP_Keep_Alive'][Ack][0][0] # 本段S发起KeepAlive的第一个包的时间戳
C_seq = D_TCP['S_TCP_Keep_Alive'][Ack][0][1] # 本段S发起KeepAlive的第一个包的seq
if C_seq+1 in D_TCP['C_TCP_Keep_Alive'] and D_TCP['C_TCP_Keep_Alive'][C_seq+1][0][0] < C_time: # C比S早发起保持连接,忽略S,按C的算(前面已经处理防止重复计算)
Log.debug(f" 忽略{D_TCP['S_TCP_Keep_Alive'][Ack][0]}")
else:
Log.debug(f" 确认{D_TCP['S_TCP_Keep_Alive'][Ack][0]}")
## 根据 KeepAlive 前后的数据包判断 KeepAlive 是谁的原因造成的
## 往前找开始KeepAlive的包(比KeepAlive包提前一个才是真实开始等待的时间)
if C_seq+1 in D_TCP['D_C_ACK_DATA'] and C_time>=D_TCP['D_C_ACK_DATA'][C_seq+1][-1][0]: # 客户端发KeepAlive,客户端seq+1会是服务端发数据Ack,所以在D_TCP['D_C_ACK_DATA']中找
Log.debug(f" (始)前包客户端请求数据,归服务端原因 ACK(C_seq+1)={C_seq+1} in D_TCP['D_C_ACK_DATA']={D_TCP['D_C_ACK_DATA'][C_seq+1]}")
选择开始保持连接的数据包 = D_TCP['D_C_ACK_DATA'][C_seq+1][-1]
归属标识 = 'S'
elif Ack in D_TCP['D_S_ACK_DATA'] and C_time>=D_TCP['D_S_ACK_DATA'][Ack][-1][0]: # 客户端发KeepAlive,但是客户端发数据在等待服务端,所以在D_TCP['D_S_ACK_DATA']中找,ACK相同
Log.debug(f" (始)前包服务端响应数据,归客户端原因ACK={Ack} in D_TCP['D_S_ACK_DATA']={D_TCP['D_S_ACK_DATA'][Ack]}")
选择开始保持连接的数据包 = D_TCP['D_S_ACK_DATA'][Ack][-1]
归属标识 = 'C'
else:
if D_TCP['S_TCP_Keep_Alive'][Ack][0][5] == D_TCP['L_DATA'][0][0]: # 在会话抓到的开头的包就是KeepAlive(第一个包的第一个编号判断)
Log.debug(f" 开头的包就是KeepAlive(S发起),根据KeepAlive结束后是C发请求还是S响应数据来判断是谁的原因造成本次KeepAlive")
## 往后找
if C_seq+1 in D_TCP['D_C_ACK_DATA'] and C_time <= D_TCP['D_C_ACK_DATA'][C_seq+1][0][0]: # C请求
Log.debug(f" (始)后包客户端请求数据,归客户端原因 ACK={Ack} in D_TCP['D_C_ACK_DATA']={D_TCP['D_C_ACK_DATA'][Ack]} 本身为开始计时包{D_TCP['S_TCP_Keep_Alive'][Ack]}")
归属标识 = 'C'
elif Ack in D_TCP['D_S_ACK_DATA'] and C_time <= D_TCP['D_S_ACK_DATA'][Ack][0][0]: # S响应
Log.debug(f" (始)后包服务器响应数据,归服务端原因 ACK(C_seq+1)={C_seq+1} in D_TCP['D_S_ACK_DATA']={D_TCP['D_S_ACK_DATA'][C_seq+1]} 本身为开始计时包{D_TCP['C_TCP_Keep_Alive'][Ack]}")
归属标识 = 'S'
else:
Log.debug(f" 判断不出KeepAlive是哪方原因造成的")
continue
选择开始保持连接的数据包 = D_TCP['S_TCP_Keep_Alive'][Ack][0]
else:
Log.error(f" 不在D_C_ACK_DATA及D_S_ACK_DATA中,有问题")
continue
等待开始时间 = 选择开始保持连接的数据包[0]
## 找结束KeepAlive,开始发有效数据的第一个包(比KeepAlive包晚一个才是真实结束等待的时间)
if Ack in D_TCP['D_S_ACK_DATA'] and C_time<=D_TCP['D_S_ACK_DATA'][Ack][0][0]:
Log.debug(f" (终)ACK={Ack} in D_TCP['D_S_ACK_DATA']={D_TCP['D_S_ACK_DATA'][Ack]}")
选择标识结束保持连接的数据包 = D_TCP['D_S_ACK_DATA'][Ack][0]
elif C_seq+1 in D_TCP['D_C_ACK_DATA'] and C_time<=D_TCP['D_C_ACK_DATA'][C_seq+1][0][0]:
Log.debug(f" (终)ACK(C_seq+1)={C_seq+1} in D_TCP['D_C_ACK_DATA']={D_TCP['D_C_ACK_DATA'][C_seq+1]}")
选择标识结束保持连接的数据包 = D_TCP['D_C_ACK_DATA'][C_seq+1][0]
else:
## 有可能等待最后都没有后续数据,找最后一个保持连接的包
Log.debug(" (W)找不到后续数据包")
X1 = D_TCP['S_TCP_Keep_Alive'][Ack][-1]
if C_seq+1 in D_TCP['C_TCP_Keep_Alive']:
X2 = D_TCP['C_TCP_Keep_Alive'][C_seq+1][-1]
if X1[0] > X2[0]:
选择标识结束保持连接的数据包 = X1
else:
选择标识结束保持连接的数据包 = X2
else:
选择标识结束保持连接的数据包 = X1
Log.debug(f" (终)选择标识结束保持连接的数据包={选择标识结束保持连接的数据包}")
等待结束时间 = 选择标识结束保持连接的数据包[0]
Log.debug(f" {选择开始保持连接的数据包} <-{归属标识} KeepAlive 时间区间-> {选择标识结束保持连接的数据包}")
if 归属标识 == 'C':
L_C_KeepAlive_RANGE.append((等待开始时间, 等待结束时间))
elif 归属标识 == 'S':
L_S_KeepAlive_RANGE.append((等待开始时间, 等待结束时间))
else:
pass
#Log.debug("")
return(L_C_KeepAlive_RANGE, L_S_KeepAlive_RANGE)
## 客户端响应ACK消息和下次客户端发送请求的时间间隔(这两个seq和ack相同的情况是没有Keep-Alive的,可区分)
def 客户端发起请求间隔耗时(D_TCP):
'''
28 1625539397.99094 192.168.100.252 192.168.100.13 9577 1433 3622347657 1299493386 ACK PSH 45 C_req 256 0x5e0
29 1625539397.991202 192.168.100.13 192.168.100.252 1433 9577 1299493386 3622347702 ACK PSH 26 S_ack 260 0xe40
30 1625539398.031743 192.168.100.252 192.168.100.13 9577 1433 3622347702 1299493412 ACK 0 256 0x6ddb 上次交互完成(响应以接完S数据包)
31 1625539414.326215 192.168.100.252 192.168.100.13 9577 1433 3622347702 1299493412 ACK PSH 275 C_req 256 0x3438 下次交互开始
32 1625539414.326322 192.168.100.13 192.168.100.252 1433 9577 1299493412 3622347977 ACK PSH 68 S_ack 258 0x4780
'''
L_TIME_Client_WAIT = [] # 客户端发起请求间隔耗时的每段时间戳
for ACK in D_TCP['D_C_ACK']: # 遍历C响应收到S数据的包(先响应后发数据,说明本地数据处理要时间或等待用户输入指令等)
if ACK not in D_TCP['C_TCP_Keep_Alive']: # 不是 Keep-Alive 的情况才计算相邻两次请求的间隔时间
TIME1, Seq1, Ack1, A1, B1 = D_TCP['D_C_ACK'][ACK][0] # 客户端响应服务器对上次请求收到服务器的响应【多个包只取第一个】
if Ack1 in D_TCP['D_C_ACK_DATA']: # 找到紧接着的下一次请求
for j in D_TCP['D_C_ACK_DATA'][Ack1]:
TIME2, Seq2, Ack2, A2, B2, C2 = j
if Seq1 == Seq2 and Ack1 == Ack2:
#Log.debug(f"[TIME]= {TIME2-TIME1} 下次交互开始={j} - 上次交互完成={D_TCP['D_C_ACK'][ACK][0]}")
L_TIME_Client_WAIT.append((TIME1, TIME2))
break
# else:
# Log.debug("找不到下次请求信息,Ack1 不在 D_TCP['D_C_ACK_DATA'] 中")
#else:
# Log.debug(f"D_TCP['D_C_ACK'][ACK] 有保持连接记录,忽略计算 {D_TCP['D_C_ACK'][ACK]}")
#Log.debug('')
return(L_TIME_Client_WAIT)
## 从TCP会话字典信息D_TCP中统计分析 接收窗口大小=0 造成的时间消耗(分服务端原因造成的和客户端原因造成的情况)
## 返回 L_C_WIN_0_RANGE 客户端接收窗口满时间段列表 [(TimeStart, TimeEnd),]
## 返回 L_S_WIN_0_RANGE 服务端接收窗口满时间段列表 [(TimeStart, TimeEnd),]
def 分析接收窗口满耗时(D_TCP):
#print("D_TCP['D_C_WIN_0']", D_TCP['D_C_WIN_0'])
#print("D_TCP['D_S_WIN_0']", D_TCP['D_S_WIN_0'])
L_C_WIN_0_RANGE = []
L_S_WIN_0_RANGE = []
for K in D_TCP['D_C_WIN_0']:
TimeStart = D_TCP['D_C_WIN_0'][K][0][0]
if K in D_TCP['D_C_ACK']:
TimeEnd = D_TCP['D_C_ACK'][K][-1][0]
else:
TimeEnd = D_TCP['D_C_WIN_0'][K][-1][0]
L_C_WIN_0_RANGE.append((TimeStart, TimeEnd))
for K in D_TCP['D_S_WIN_0']:
TimeStart = D_TCP['D_S_WIN_0'][K][0][0]
if K in D_TCP['D_S_ACK']:
TimeEnd = D_TCP['D_S_ACK'][K][-1][0]
else:
TimeEnd = D_TCP['D_S_WIN_0'][K][-1][0]
L_S_WIN_0_RANGE.append((TimeStart, TimeEnd))
return(L_C_WIN_0_RANGE, L_S_WIN_0_RANGE)
## 画图,TCP会话交互中各种用时信息画成图(开始时间结束时间、保持连接时间和客户端请求间隔等待时间等)
def TCP会话用时信息画图(TCP_START_TIME_MIN, TCP_END_TIME_MAX, D_C_KeepAlive, D_C_WAIT, D_S_KeepAlive, D_C_WIN_0_TIME, D_S_WIN_0_TIME):
开始时间点 = int(TCP_START_TIME_MIN)
结束时间点 = int(TCP_END_TIME_MAX)
X = [i for i in range(开始时间点, 结束时间点+1)]
X_TEXT = [time.strftime('%H:%M:%S', time.localtime(i)) for i in range(开始时间点, 结束时间点+1)]
#print(f"X轴时间戳 X[0]={X[0]} X[-1]={X[-1]}")
#print(X_TEXT)
D_Y = {}
#print("D_C_KeepAlive", D_C_KeepAlive)
for K in D_C_KeepAlive:
L_S_KeepAlive_RANGE = D_S_KeepAlive[K] # 提取这个key下的KeepAlive时间区间列表
P_S_KeepAlive_TIME = set() # 把所有时间按秒生成集合,方便匹配查找
for TIMEs in L_S_KeepAlive_RANGE: # 遍历每一个KeepAlive时间区间
t1 = TIMEs[0] # 开始时间
t2 = TIMEs[1] # 结束时间
L = [i for i in range(int(t1), int(t2))] # 以秒为单位生成时间区间列表
LP = set(L) # 时间区间列表 转 时间区间集合
P_S_KeepAlive_TIME = P_S_KeepAlive_TIME | LP # 并集
L_C_KeepAlive_RANGE = D_C_KeepAlive[K]
P_C_KeepAlive_TIME = set()
for TIMEs in L_C_KeepAlive_RANGE:
L = [i for i in range(int(TIMEs[0]), int(TIMEs[1]))]
P_C_KeepAlive_TIME = P_C_KeepAlive_TIME | set(L)
L_TIME_Client_WAIT = D_C_WAIT[K]
P_C_WAIT_TIME = set()
for TIMEs in L_TIME_Client_WAIT:
L = [i for i in range(int(TIMEs[0]), int(TIMEs[1]))]
P_C_WAIT_TIME = P_C_WAIT_TIME | set(L)
L_C_WIN_0_RANGE = D_C_WIN_0_TIME[K]
P_C_WIN_0_TIME = set()
for TIMEs in L_C_WIN_0_RANGE:
L = [i for i in range(int(TIMEs[0]), int(TIMEs[1]))]
P_C_WIN_0_TIME = P_C_WIN_0_TIME | set(L)
L_S_WIN_0_RANGE = D_S_WIN_0_TIME[K]
P_S_WIN_0_TIME = set()
for TIMEs in L_S_WIN_0_RANGE:
L = [i for i in range(int(TIMEs[0]), int(TIMEs[1]))]
P_S_WIN_0_TIME = P_S_WIN_0_TIME | set(L)
Y = [] # 为每个Key对应的交互生成各自Y值
## ax.set_yticklabels(['-2','Start/End(-1)','RUN(0)','Client-Wait(1)','(C)Keep-Alive(2)','(S)Keep-Alive(3)','(C)windows=0(4)','(S)windows=0(5)']) # 自定义Y轴刻度
for i in X: # 遍历整个时间区间(所有交互统一的时间区间)
if i < 开始时间点 or i > 结束时间点:
Y.append(-2) # 超出开始和结束时间
elif i in (开始时间点, 结束时间点):
Y.append(-1) # 标记开始和结束时间
elif i in P_C_KeepAlive_TIME:
Y.append(2) # C发起保持连接造成的等待
elif i in P_S_KeepAlive_TIME:
Y.append(3) # S发起保持连接造成的等待
elif i in P_C_WAIT_TIME:
Y.append(1) # 等待客户端发包造成的等待
elif i in P_C_WIN_0_TIME:
Y.append(4) # 客户端接收窗口满造成的等待
elif i in P_S_WIN_0_TIME:
Y.append(5) # 服务端接收窗口满造成的等待
else:
Y.append(0) # 正在运行的时间
#Log.debug(f"RUN {i}")
D_Y[K] = Y
## 分成多个图显示(通用、可控)
X轴刻度数量 = 10 # X轴只显示10个刻度
X轴间隔 = len(X)//X轴刻度数量
if X轴间隔 == 0: # 按秒合并的时间刻度,可能会少于10个
X轴间隔 = 1 # 显示每一个刻度
图数量 = len(D_Y)
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker # 解决时间为X轴可能会造成刻度太密集,看不清问题
if 图数量 == 1:
L_KEY = [KEY for KEY in D_Y]
fig, ax = plt.subplots(1,1)
ax.xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔)) # X轴间隔刻度
ax.yaxis.set_major_locator(ticker.MultipleLocator(1)) # 强制Y轴每个刻度都显示
ax.plot(X_TEXT, D_Y[L_KEY[0]], '.', label=L_KEY[0], markersize=2)
ax.set_yticklabels(['-2','Start/End(-1)','RUN(0)','Client-Wait(1)','(C)Keep-Alive(2)','(S)Keep-Alive(3)','(C)windows=0(4)','(S)windows=0(5)']) # 自定义Y轴刻度
ax.legend() # 显示图例label
plt.ylim(-1.5,5.5) # 控制y轴显示范围,隐藏-1值
#plt.grid() # 使用网格,方便查看XY对应
plt.xlabel('Time') # X轴标注
#plt.title('TITLE') # 标题
plt.show()
else:
fig, ax = plt.subplots(图数量, 1, sharex=True, sharey=True, figsize=(20,10)) # n行1列,统一X和Y轴刻度
plt.ylim(-1.5,3.5) # 控制y轴显示范围,隐藏-1值
AX_List = ax.ravel() # 子图列表:AX_List[0]第一个子图,AX_List[1]第二个子图...
L_KEY = [KEY for KEY in D_Y]
for n in range(0, 图数量):
AX_List[n].plot(X_TEXT,D_Y[L_KEY[n]], '.', label=L_KEY[n], markersize=2) # 第1个图的第1根线
AX_List[n].legend() # 显示图例label
AX_List[n].set_yticklabels(['-2','Start/End(-1)','RUN(0)','Client-Wait(1)','(C)Keep-Alive(2)','(S)Keep-Alive(3)','(C)windows=0(4)','(S)windows=0(5)']) # 自定义Y轴刻度
AX_List[n].xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔)) # X轴间隔N标一个刻度
AX_List[n].yaxis.set_major_locator(ticker.MultipleLocator(1)) # 强制Y轴每个刻度都显示
plt.show()
## 记录日志
def SHOW_TIME_WAIT(C_发起开始时间, C_发起结束时间, L_TIME_Client_WAIT, L_C_KeepAlive_RANGE, L_S_KeepAlive_RANGE, L_C_WIN_0_RANGE, L_S_WIN_0_RANGE, D_TCP):
会话总用时 = C_发起结束时间 - C_发起开始时间
开始时间 = time.strftime('%H:%M:%S', time.localtime(C_发起开始时间))
结束时间 = time.strftime('%H:%M:%S', time.localtime(C_发起结束时间))
源地址, 源端口 = D_TCP['CLIENT']
目的地址, 目的端口 = D_TCP['SERVER']
## 时间合计
C_KeepAlive时间合计 = sum([i[1]-i[0] for i in L_C_KeepAlive_RANGE])
C_KeepAlive间隔明细 = [(time.strftime('%H:%M:%S', time.localtime(i[0])), time.strftime('%H:%M:%S', time.localtime(i[1]))) for i in L_C_KeepAlive_RANGE]
S_KeepAlive时间合计 = sum([i[1]-i[0] for i in L_S_KeepAlive_RANGE])
S_KeepAlive间隔明细 = [(time.strftime('%H:%M:%S', time.localtime(i[0])), time.strftime('%H:%M:%S', time.localtime(i[1]))) for i in L_S_KeepAlive_RANGE]
C间隔发包时间合计 = sum([i[1]-i[0] for i in L_TIME_Client_WAIT])
C接收窗口满耗时合计 = sum([i[1]-i[0] for i in L_C_WIN_0_RANGE])
C接收窗口满耗时明细 = [(time.strftime('%H:%M:%S', time.localtime(i[0])), time.strftime('%H:%M:%S', time.localtime(i[1]))) for i in L_C_WIN_0_RANGE]
S接收窗口满耗时合计 = sum([i[1]-i[0] for i in L_S_WIN_0_RANGE])
S接收窗口满耗时明细 = [(time.strftime('%H:%M:%S', time.localtime(i[0])), time.strftime('%H:%M:%S', time.localtime(i[1]))) for i in L_S_WIN_0_RANGE]
程序正常交互用时合计 = 会话总用时 - C_KeepAlive时间合计 - S_KeepAlive时间合计 - C间隔发包时间合计 - C接收窗口满耗时合计 - S接收窗口满耗时合计
T_DATA = (源地址, 源端口, 目的地址, 目的端口, 开始时间, 结束时间, round(会话总用时,2), round(C_KeepAlive时间合计,2), round(S_KeepAlive时间合计,2), round(C间隔发包时间合计,2), round(C接收窗口满耗时合计,2), round(S接收窗口满耗时合计,2), round(程序正常交互用时合计,2))
Log.info("%-15s %-5s <--> %-15s %-5s %8s %8s %10s %10s %9s %13s %13s %13s %9s" % T_DATA)
Log.debug(f" C_KeepAlive间隔明细={C_KeepAlive间隔明细}")
Log.debug(f" S_KeepAlive间隔明细={S_KeepAlive间隔明细}")
Log.debug(f" C接收窗口满耗时明细={C接收窗口满耗时明细}")
Log.debug(f" S接收窗口满耗时明细={S接收窗口满耗时明细}")
Log.debug(f" C_发包间隔明细")
for i in L_TIME_Client_WAIT:
TIME1,TIME2 = i
#if TIME2-TIME1 > 0.5: # 间隔大于0.5秒的才记录明细,太短没有显示必要
Log.debug(f" {i}={time.strftime('%H:%M:%S', time.localtime(TIME1))} -> {time.strftime('%H:%M:%S', time.localtime(TIME2))} 时间间隔={round(TIME2-TIME1, 6)}秒")
return(T_DATA)
## 统计整个会话中收发数据情况
def 统计整个会话中收发数据情况(D_TCP):
C_Tx_DATA_ALL = D_TCP['C_Tx_DATA_ALL']
S_Tx_DATA_ALL = D_TCP['S_Tx_DATA_ALL']
try:
C_FIN_SEQ = D_TCP['C_ACK_FIN'][1]
except:
Log.debug('无C_FIN_SEQ')
Log.debug(f"C_Tx_DATA_ALL={C_Tx_DATA_ALL}")
Log.debug(f"S_Tx_DATA_ALL={S_Tx_DATA_ALL}")
else:
if C_Tx_DATA_ALL == C_FIN_SEQ:
Log.debug(f"C_Tx_DATA_ALL={C_Tx_DATA_ALL} 正常")
else:
Log.debug(f"C_Tx_DATA_ALL={C_Tx_DATA_ALL} 异常")
for K in D_TCP['S_ACK_FIN_SeqKey']:
S_FIN_SEQ = D_TCP['S_ACK_FIN_SeqKey'][K][0][1]
if S_Tx_DATA_ALL == S_FIN_SEQ:
Log.debug(f"S_Tx_DATA_ALL={S_Tx_DATA_ALL} 正常")
else:
Log.debug(f"S_Tx_DATA_ALL={S_Tx_DATA_ALL} 异常")
Log.debug('')
## 返回 D_TIME_TCP_SESSION_PACKET 字典:Key=时间戳(精确到秒),Value=这一秒内包数据和大小统计信息
def 提取PCAP中指定TCP会话双向流量信息(PCAP_File_OR_Dir, SIP, SPORT, DIP, DPORT):
#print((PCAP_File_OR_Dir, SaveDir, 匹配内容, UserIP, UserPort))
## 统计数据包信息的字典
D_TIME_TCP_SESSION_PACKET = {} # {时间戳:{(SIP,SPORT,DIP,DPORT):{'TCP_Packets':0, 'TCP_Bytes':0}, (DIP,DPORT,SIP,SPORT):{'TCP_Packets':0, 'TCP_Bytes':0}}}
## 开始处理单个或多个PCAP文件
E = 0
if os.path.isfile(PCAP_File_OR_Dir):
PATH,FileName = os.path.split(PCAP_File_OR_Dir) # 拆分路径和文件名
#print(PATH,FileName)
PCAP_File_OR_Dir = PATH # 这个后面但目录用
L_FileName = ['\\'+FileName] # 文件名
elif os.path.isdir(PCAP_File_OR_Dir):
L_FileName = os.listdir(PCAP_File_OR_Dir) # 目录内内容列表
else:
print(f"{PCAP_File_OR_Dir} 不存在")
E = 1
if E == 0:
文件数量 = len(L_FileName)
TCP会话数量 = 0
计数 = 1
for F in L_FileName:
FullFileName = PCAP_File_OR_Dir+str(F)
print(f"拆分PCAP文件:{FullFileName} 进度:{计数}/{文件数量}") ## 根据源目的IP和端口分成多个小pcap文件
f = open(FullFileName, 'rb') # 以二进制方式读取pcap格式文件
PCAP_DATA = f.read(24) # 读取前24字节头信息,忽略
计数 += 1
N = 0
while 1:
包头 = f.read(16)
if not 包头: # 判断 包头 是否为空(读完或者本身为空时 S 为空)
break
N += 1
#if N == 14:
# break
#print("N", N)
PacketHeader = struct.unpack('IIII', 包头)
时间戳 = PacketHeader[0]
#微秒 = PacketHeader[1]
#抓取数据包长度 = PacketHeader[2] # 所抓获的数据包保存在pcap文件中的实际长度,以字节为单位。
实际数据包长度 = PacketHeader[3] # 所抓获的数据包的真实长度,如果文件中保存不是完整的数据包,那么这个值可能要比前面的数据包长度的值大。
PacketData = f.read(实际数据包长度)
# 以太部首
FrameType = PacketData[12:14] # 帧类型
# IP数据报头
if FrameType == b'\x08\x00': # 普通包
pass
elif FrameType == b'\x81\x00': # VLAN包
PacketData = PacketData[0:12] + b'\x08\x00' + PacketData[18:] ## 剔除VLAN数据
包头 = struct.pack('IIII', PacketHeader[0], PacketHeader[1], PacketHeader[2]-4, PacketHeader[3]-4) ## 修改包头
else:
#print("其他包忽略")
pass
## SrcIP SrcPort 和 DstIP DstPort 互换位置是同个TCP交互,要存入同个对象
IP_Protocol = PacketData[23:24]
## 只要TCP协议
if IP_Protocol == b'\x06': # 协议(TCP 6)(UDP 17)
SrcIP_Bytes = PacketData[26:30] # 源IP地址
DstIP_Bytes = PacketData[30:34] # 目的IP地址
SrcIP = socket.inet_ntoa(SrcIP_Bytes)
DstIP = socket.inet_ntoa(DstIP_Bytes)
SrcPort_Bytes = PacketData[34:36]
DstPort_Bytes = PacketData[36:38]
SrcPort = struct.unpack('>H', SrcPort_Bytes)[0]
DstPort = struct.unpack('>H', DstPort_Bytes)[0]
## 每一次TCP发数据生成2个Key
TCP_KEY_SD = (SrcIP,SrcPort,DstIP,DstPort) # 以当前包的源到目的生成一个Key
#TCP_KEY_DS = (DstIP,DstPort,SrcIP,SrcPort) # 以当前包的目的到源生成一个Key
## 只有分离出指定的IP的交互内容
if SrcIP == SIP and SrcPort == SPORT:
SAVE = 1
elif DstIP == SIP and DstPort == SPORT:
SAVE = 1
else:
SAVE = 0
## 匹配到是需要分析的数据包
if SAVE == 1:
if 时间戳 in D_TIME_TCP_SESSION_PACKET: ## 全部流量统计
D_TIME_TCP_SESSION_PACKET[时间戳][TCP_KEY_SD]['Packets_Count'] += 1 ## 全部流量统计:全部数据包数量累计加1
D_TIME_TCP_SESSION_PACKET[时间戳][TCP_KEY_SD]['Packets_Bytes'] += 实际数据包长度 ## 全部流量统计:全部数据包大小累计
else:
D_TIME_TCP_SESSION_PACKET[时间戳] = {TCP_KEY_SD:{'Packets_Count': 1, 'Packets_Bytes':实际数据包长度}}
f.close()
print(f"【Done {time.strftime('%Y-%m-%d %H:%M:%S')}】")
else:
print("有错误终止操作")
return(D_TIME_TCP_SESSION_PACKET)
## 返回 D_TCP_ALL 字典:Key=(源IP,源端口,目的IP,目的端口),Value=TCP信息字典
## 返回 D_UDP_ALL 字典:Key=(源IP,源端口,目的IP,目的端口),Value=()
## 返回 D_TIME_PACKET 字典:Key=时间戳(精确到秒),Value=这一秒内包数据和大小统计信息
## 返回 D_TCP_PACKET 字典:Key=TCP数据流方向,Value=这个方向上的数据包统计信息
def 提取PCAP中指定TCP会话数据(PCAP_File_OR_Dir, SaveDir, 匹配内容, UserIP, UserPort):
#print((PCAP_File_OR_Dir, SaveDir, 匹配内容, UserIP, UserPort))
## 每个会话解析为TCP会话信息字典
D_TCP_ALL = {}
## 存储每个UDP会话信息
D_UDP_ALL = {}
## 统计数据包信息的字典
D_TIME_PACKET = {} # {时间戳:{'Packets_Count':0, 'Packets_Bytes':0, 'TCP_Count':0, 'TCP_Bytes':0, 'UDP_Count':0, 'UDP_Bytes':0, 'OTHER_Count':0, 'OTHER_Bytes':0}}
D_TCP_PACKET = {} # {(源IP,源端口,目的IP,目的端口):{'Packets':包数量, 'Bytes_Len':网络传输字节数量, 'Time_End':最后一个包的时间戳}}
## 开始处理单个或多个PCAP文件
E = 0
if os.path.isfile(PCAP_File_OR_Dir):
PATH,FileName = os.path.split(PCAP_File_OR_Dir) # 拆分路径和文件名
#print(PATH,FileName)
PCAP_File_OR_Dir = PATH # 这个后面但目录用
L_FileName = ['\\'+FileName] # 文件名
elif os.path.isdir(PCAP_File_OR_Dir):
L_FileName = os.listdir(PCAP_File_OR_Dir) # 目录内内容列表
else:
print(f"{PCAP_File_OR_Dir} 不存在")
E = 1
try:
if not os.path.isdir(SaveDir):
os.makedirs(SaveDir)
os.chdir(SaveDir) ## 切换到存放拆分后文件的目录
except Exception as e:
print(e)
E = 1
else:
pass
if E == 0:
文件数量 = len(L_FileName)
TCP会话数量 = 0
UDP会话数量 = 0
计数 = 1
for F in L_FileName:
FullFileName = PCAP_File_OR_Dir+str(F)
print(f" 拆分PCAP文件:{FullFileName} 进度:{计数}/{文件数量}") ## 根据源目的IP和端口分成多个小pcap文件
f = open(FullFileName, 'rb') # 以二进制方式读取pcap格式文件
PCAP_DATA = f.read(24) # 读取前24字节头信息,忽略
计数 += 1
N = 0
while 1:
包头 = f.read(16)
if not 包头: # 判断 包头 是否为空(读完或者本身为空时 S 为空)
break
N += 1
#if N == 14:
# break
#print("N", N)
PacketHeader = struct.unpack('IIII', 包头)
时间戳 = PacketHeader[0]
#微秒 = PacketHeader[1]
#抓取数据包长度 = PacketHeader[2] # 所抓获的数据包保存在pcap文件中的实际长度,以字节为单位。
实际数据包长度 = PacketHeader[3] # 所抓获的数据包的真实长度,如果文件中保存不是完整的数据包,那么这个值可能要比前面的数据包长度的值大。
PacketData = f.read(实际数据包长度)
## ==全部流量统计==
if 时间戳 in D_TIME_PACKET: ## 全部流量统计
D_TIME_PACKET[时间戳]['Packets_Count'] += 1 ## 全部流量统计:全部数据包数量累计加1
D_TIME_PACKET[时间戳]['Packets_Bytes'] += 实际数据包长度 ## 全部流量统计:全部数据包大小累计
else:
D_TIME_PACKET[时间戳] = {'Packets_Count': 1, 'Packets_Bytes':实际数据包长度, 'TCP_Count':0, 'TCP_Bytes':0, 'UDP_Count':0, 'UDP_Bytes':0, 'OTHER_Count':0, 'OTHER_Bytes':0, '筛选出的TCP包数量合计':0, '筛选出的TCP包大小合计':0}
## ==全部流量统计==
# 以太部首
FrameType = PacketData[12:14] # 帧类型
# IP数据报头
if FrameType == b'\x08\x00': # 普通包
pass
elif FrameType == b'\x81\x00': # VLAN包
PacketData = PacketData[0:12] + b'\x08\x00' + PacketData[18:] ## 剔除VLAN数据
包头 = struct.pack('IIII', PacketHeader[0], PacketHeader[1], PacketHeader[2]-4, PacketHeader[3]-4) ## 修改包头
else:
#print("其他包忽略")
pass
## SrcIP_SrcPort 和 DstIP_DstPort 互换位置是同个TCP交互,要存入同个文件对象
IP_Protocol = PacketData[23:24]
## 只要TCP协议
if IP_Protocol == b'\x06': # 协议(TCP 6)(UDP 17)
D_TIME_PACKET[时间戳]['TCP_Count'] += 1 ## 全部流量统计:TCP数据包数量累计加1
D_TIME_PACKET[时间戳]['TCP_Bytes'] += 实际数据包长度 ## 全部流量统计:TCP数据包大小累计
SrcIP_Bytes = PacketData[26:30] # 源IP地址
DstIP_Bytes = PacketData[30:34] # 目的IP地址
SrcIP = socket.inet_ntoa(SrcIP_Bytes)
DstIP = socket.inet_ntoa(DstIP_Bytes)
SrcPort_Bytes = PacketData[34:36]
DstPort_Bytes = PacketData[36:38]
SrcPort = struct.unpack('>H', SrcPort_Bytes)[0]
DstPort = struct.unpack('>H', DstPort_Bytes)[0]
## 每一次TCP发数据生成2个Key
TCP_KEY_SD = (SrcIP,SrcPort,DstIP,DstPort) # 以当前包的源到目的生成一个Key
TCP_KEY_DS = (DstIP,DstPort,SrcIP,SrcPort) # 以当前包的目的到源生成一个Key
## 只有分离出指定的IP的交互内容
if 匹配内容 == 'IP':
if UserIP in (SrcIP, DstIP):
SAVE = 1
else:
SAVE = 0
elif 匹配内容 == 'PORT':
if UserPort in (SrcPort, DstPort):
SAVE = 1
else:
SAVE = 0
elif 匹配内容 == 'IP+PORT':
if SrcIP == UserIP and SrcPort == UserPort:
SAVE = 1
elif DstIP == UserIP and DstPort == UserPort:
SAVE = 1
else:
SAVE = 0
elif 匹配内容 == 'ALL':
SAVE = 1 # 全都要
else:
SAVE = 0 # 这个包不需要
## 匹配到是需要分析的数据包
if SAVE == 1:
## 筛选出的TCP流量统计
D_TIME_PACKET[时间戳]['筛选出的TCP包数量合计'] += 1 ## 筛选出的TCP流量统计:TCP数据包数量累计加1
D_TIME_PACKET[时间戳]['筛选出的TCP包大小合计'] += 实际数据包长度 ## 筛选出的TCP流量统计:TCP数据包大小累计
## TCP会话统计流量
if TCP_KEY_SD in D_TCP_PACKET: ## TCP会话统计流量
D_TCP_PACKET[TCP_KEY_SD]['Packets'] +=1 ## TCP会话统计流量:TCP会话数据包数量累计加1
D_TCP_PACKET[TCP_KEY_SD]['Bytes_Len'] += 实际数据包长度 ## TCP会话统计流量:TCP会话数据包大小累计
else:
D_TCP_PACKET[TCP_KEY_SD] = {'Packets':1, 'Bytes_Len':实际数据包长度}
## 对TCP交互数据包进行分析保存
if TCP_KEY_SD in D_TCP_ALL:
D_SAVE(D_TCP_ALL[TCP_KEY_SD], 包头, PacketData, N)
else:
Log.debug(f" TCP {TCP_KEY_SD}")
TCP会话数量 += 1
## 两个方向对应同一个文件
D_TCP = {}
D_TCP['CLIENT'] = () ## (HOST, PORT) # 客户端IP和端口
D_TCP['SERVER'] = () ## (HOST, PORT) # 服务端IP和端口
D_TCP['C_SYN'] = () ## (时间戳, seq, ack), # 客户端发起连接
D_TCP['S_ACK_SYN'] = () ## (时间戳, seq, ack), # 服务端确认连接
D_TCP['C_ACK'] = () ## (时间戳, seq, ack), # 客户端确认连接
D_TCP['S_ACK_FIN_SeqKey'] = {} ## 服务端发起的终止信息,以Seq为KEY,因为Seq=对应请求的Ack
D_TCP['C_ACK_FIN'] = () ## C已经确认S接收完全部数据的信息
D_TCP['S_ACK_RST'] = {} ## 服务端强制断开TCP连接
D_TCP['C_ACK_RST_AckKey'] = {} ## 客户端强制断开TCP连接
D_TCP['C_ACK_RST_SeqKey'] = {} ## 客户端强制断开TCP连接
D_TCP['C_CLIENT_SEND_DATA'] = {} ## 客户端发起数据(用于判断重发的情况)以ACK为Key,是在响应S某部分交互的时候重复发,要以Ack为依据
D_TCP['TCP_CS_ERR'] = [] ## TCP校验失败的包编号
D_TCP['D_C_ACK'] = {} # C 发送的ACK信息(纯ACK确认消息,不含数据)
D_TCP['D_S_ACK'] = {} # S 发送的ACK信息(纯ACK确认消息,不含数据)
D_TCP['D_C_ACK_DATA'] = {} # C 发送的ACK及数据(一般都是客户端发起请求,包信息保存到 C_TCP_REQ 字典 Key=Ack Value=[(包信息1),(包信息2)] 一个请求可能会分成多个包发送)
D_TCP['D_S_ACK_DATA'] = {} # S 发送的ACK及数据(一般为服务端响应数据)
D_TCP['C_TCP_Keep_Alive'] = {} # C 发起的请求保持连接包
D_TCP['S_TCP_Keep_Alive'] = {} # S 发起的请求保持连接包
D_TCP['C_TCP_Keep_Alive_ACK'] = {} # C 响应请求保持连接包
D_TCP['S_TCP_Keep_Alive_ACK'] = {} # S 响应请求保持连接包
D_TCP['C_ACK_WIN_0'] = [] ## 记录C发的ACK且窗口为0的包信息(Seq,Ack)说明C缓存满,S会发ACK数据为1的包探测查看状态,S的(Seq,Ack)和C的刚好相反
D_TCP['S_ACK_WIN_0'] = [] ## 记录S发的ACK且窗口为0的包信息(Seq,Ack)说明S缓存满
D_TCP['D_C_WIN_0'] = {} # 记录C接收窗口满的信息,用于计算因C窗口满消耗的时间
D_TCP['D_S_WIN_0'] = {} # 记录S接收窗口满的信息,用于计算因S窗口满消耗的时间
D_TCP['C_Tx_DATA_ALL'] = 0 ## 在最后的时候更新到主字典里
D_TCP['S_Tx_DATA_ALL'] = 0 ## 在最后的时候更新到主字典里
D_TCP['P_C_Tx_Seq_Ack'] = set() # C发的含数据的ACK包的(Seq,Ack)信息集合,遇到重复说明是重发的包,在S处抓包这个就是 L_C_Rx_Info 接收到C发数据,重复说明重收,接不到对方丢的包的
D_TCP['P_S_Tx_Seq_Ack'] = set() # S发的含数据的ACK包的(Seq,Ack)信息集合,遇到重复说明是重发的包,在C处抓包这个就是 L_S_Rx_Info 接收到S发数据,重复说明重收,接不到对方丢的包的
D_TCP['L_DATA'] = []
D_TCP_ALL[TCP_KEY_SD] = D_TCP # 方向1
D_TCP_ALL[TCP_KEY_DS] = D_TCP # 方向2
D_SAVE(D_TCP_ALL[TCP_KEY_SD], 包头, PacketData, N)
elif IP_Protocol == b'\x11': ## 协议(TCP 6)(UDP 17)
D_TIME_PACKET[时间戳]['UDP_Count'] += 1 ## 全部流量统计:UDP数据包数量累计加1
D_TIME_PACKET[时间戳]['UDP_Bytes'] += 实际数据包长度 ## 全部流量统计:UDP数据包大小累计
SrcIP_Bytes = PacketData[26:30] # 源IP地址
DstIP_Bytes = PacketData[30:34] # 目的IP地址
SrcIP = socket.inet_ntoa(SrcIP_Bytes)
DstIP = socket.inet_ntoa(DstIP_Bytes)
SrcPort_Bytes = PacketData[34:36]
DstPort_Bytes = PacketData[36:38]
SrcPort = struct.unpack('>H', SrcPort_Bytes)[0]
DstPort = struct.unpack('>H', DstPort_Bytes)[0]
## 每一次UDP发数据生成2个Key
UDP_KEY_SD = (SrcIP,SrcPort,DstIP,DstPort) # 以当前包的源到目的生成一个Key
UDP_KEY_DS = (DstIP,DstPort,SrcIP,SrcPort) # 以当前包的目的到源生成一个Key
## 只有分离出指定的IP的交互内容
if 匹配内容 == 'IP':
if UserIP in (SrcIP, DstIP):
SAVE = 1
else:
SAVE = 0
elif 匹配内容 == 'PORT':
if UserPort in (SrcPort, DstPort):
SAVE = 1
else:
SAVE = 0
elif 匹配内容 == 'IP+PORT':
if SrcIP == UserIP and SrcPort == UserPort:
SAVE = 1
elif DstIP == UserIP and DstPort == UserPort:
SAVE = 1
else:
SAVE = 0
elif 匹配内容 == 'ALL':
SAVE = 1 # 全都要
else:
SAVE = 0 # 这个包不需要
## 匹配到是需要分析的数据包
if SAVE == 1:
## 对UDP交互数据包进行分析保存
if UDP_KEY_SD in D_UDP_ALL:
pass
else:
Log.debug(f" UDP {UDP_KEY_SD}")
UDP会话数量 += 1
## 两个方向对应同一个文件
D_UDP_ALL[UDP_KEY_SD] = () # 方向1
D_UDP_ALL[UDP_KEY_DS] = () # 方向2
else:
D_TIME_PACKET[时间戳]['OTHER_Count'] += 1 ## 全部流量统计:其它数据包数量累计加1
D_TIME_PACKET[时间戳]['OTHER_Bytes'] += 实际数据包长度 ## 全部流量统计:其它数据包大小累计
f.close()
print(f" 完成 {time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f" TCP会话数量: {TCP会话数量}")
print(f" UDP会话数量: {UDP会话数量}")
else:
print("【ERROR】有错误终止操作")
return(D_TCP_ALL, D_TIME_PACKET, D_TCP_PACKET, D_UDP_ALL)
## 去除重复内容的Key 返回 L_TCP_SESSION_KEY
def TCP会话Key去重(D_TCP_ALL):
L_TCP_SESSION_KEY = []
P = set()
for k in D_TCP_ALL:
if k in P:
pass
else:
L_TCP_SESSION_KEY.append(k)
P.add(k)
P.add((k[2],k[3],k[0],k[1]))
#L_DATA = D_TCP_ALL[k]['L_DATA']
return(L_TCP_SESSION_KEY)
## 去除重复内容的Key 返回 L_UDP_SESSION_KEY
def UDP会话Key去重(D_UDP_ALL):
L_UDP_SESSION_KEY = []
P = set()
for k in D_UDP_ALL:
if k in P:
pass
else:
L_UDP_SESSION_KEY.append(k)
P.add(k)
P.add((k[2],k[3],k[0],k[1]))
return(L_UDP_SESSION_KEY)
## 按时间戳存储数据包大小的字典转成画图可用的XY轴列表
def D_TIME_PACKET_2_XY(D_TIME_PACKET):
L_TIME_KEY = [i for i in D_TIME_PACKET] # 时间戳Key列表
Y_Packets_Count = []
Y_Packets_Bytes = []
Y_TCP_Count = []
Y_TCP_Bytes = []
Y_UDP_Count = []
Y_UDP_Bytes = []
Y_TCP刷选出部分_Count = []
Y_TCP刷选出部分_Bytes = []
for i in L_TIME_KEY:
if i in D_TIME_PACKET:
Y_Packets_Count.append(D_TIME_PACKET[i]['Packets_Count'])
Y_Packets_Bytes.append(D_TIME_PACKET[i]['Packets_Bytes'])
Y_TCP_Count.append(D_TIME_PACKET[i]['TCP_Count'])
Y_TCP_Bytes.append(D_TIME_PACKET[i]['TCP_Bytes'])
Y_UDP_Count.append(D_TIME_PACKET[i]['UDP_Count'])
Y_UDP_Bytes.append(D_TIME_PACKET[i]['UDP_Bytes'])
Y_TCP刷选出部分_Count.append(D_TIME_PACKET[i]['筛选出的TCP包数量合计'])
Y_TCP刷选出部分_Bytes.append(D_TIME_PACKET[i]['筛选出的TCP包大小合计'])
else:
Y_Packets_Count.append(0)
Y_Packets_Bytes.append(0)
Y_TCP_Count.append(0)
Y_TCP_Bytes.append(0)
Y_UDP_Count.append(0)
Y_UDP_Bytes.append(0)
Y_TCP刷选出部分_Count.append(0)
Y_TCP刷选出部分_Bytes.append(0)
return(L_TIME_KEY, Y_Packets_Count, Y_Packets_Bytes, Y_TCP_Count, Y_TCP_Bytes, Y_UDP_Count, Y_UDP_Bytes, Y_TCP刷选出部分_Count, Y_TCP刷选出部分_Bytes)
## 流量画图
def 画流量图(D_TIME_PACKET):
X, Y_Packets_Count, Y_Packets_Bytes, Y_TCP_Count, Y_TCP_Bytes, Y_UDP_Count, Y_UDP_Bytes, Y_TCP刷选出部分_Count, Y_TCP刷选出部分_Bytes = D_TIME_PACKET_2_XY(D_TIME_PACKET)
X_TEXT = [time.strftime('%H:%M:%S', time.localtime(i)) for i in X] # 文本时间
#print("X", X)
#print("X_TEXT", X_TEXT)
## 分成多个图显示(通用、可控)
X轴刻度数量 = 10 # X轴只显示10个刻度
X轴间隔 = len(X)//X轴刻度数量
if X轴间隔 == 0: # 按秒合并的时间刻度,可能会少于10个
X轴间隔 = 1 # 显示每一个刻度
Log.debug(f"原X轴刻度数={len(X)} 设置X轴间隔={X轴间隔}")
图数量 = 3
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker # 解决时间为X轴可能会造成刻度太密集,看不清问题
fig, ax = plt.subplots(图数量, 1, sharex=True, sharey=False, figsize=(20,10)) # n行1列,统一X轴刻度
AX_List = ax.ravel() # 子图列表:AX_List[0]第一个子图,AX_List[1]第二个子图...
## 子图1每秒总流量,用左边y轴
AX_List[0].plot(X_TEXT, Y_Packets_Count, '-', label='ALL Packets Count', markersize=2) # 第1个图的第1根线
AX_List[0].legend(loc=2) # 显示图例label(位置左上)
#AX_List[0].xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔)) # X轴间隔N标一个刻度
AX_List[0].set_ylabel('Packets')
## 子图1每秒包数量,用右边y轴
AX_0_Y2 = AX_List[0].twinx()
AX_0_Y2.plot(X_TEXT, Y_Packets_Bytes, ':r', label='ALL Packets Bytes', markersize=1) # 第1个图的第2根线
AX_0_Y2.legend(loc=1) # 显示图例label(位置右上)
#AX_0_Y2.xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))
AX_0_Y2.set_ylabel('Bytes')
AX_0_Y2.get_yaxis().get_major_formatter().set_scientific(False) # y轴值不使用科学记数法
## 子图2 总TCP和UDP的包数量和流量情况
AX_List[1].plot(X_TEXT, Y_TCP_Count, ':', label='All TCP Count', markersize=2)
AX_List[1].plot(X_TEXT, Y_UDP_Count, '-', label='All UDP Count', markersize=1)
AX_List[1].legend(loc=2)
#AX_List[1].xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))
AX_List[1].set_ylabel('Packets')
AX_1_Y2 = AX_List[1].twinx()
AX_1_Y2.plot(X_TEXT, Y_TCP_Bytes, ':', label='ALL TCP Bytes', markersize=2)
AX_1_Y2.plot(X_TEXT, Y_UDP_Bytes, '-', label='ALL UDP Bytes', markersize=1)
AX_1_Y2.legend(loc=1)
AX_1_Y2.set_ylabel('Bytes')
AX_1_Y2.xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))
AX_1_Y2.get_yaxis().get_major_formatter().set_scientific(False)
## 子图3 筛选出的TCP会话的流量图
## 子图3每秒总流量,用左边y轴
AX_List[2].plot(X_TEXT, Y_TCP刷选出部分_Count, '-', label='SELECT TCP Packets Count', markersize=2)
AX_List[2].legend(loc=2)
AX_List[2].xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))
AX_List[2].set_ylabel('Packets')
## 子图3每秒包数量,用右边y轴
AX_2_Y2 = AX_List[2].twinx()
AX_2_Y2.plot(X_TEXT, Y_TCP刷选出部分_Bytes, ':r', label='SELECT TCP Packets Bytes', markersize=1)
AX_2_Y2.legend(loc=1)
AX_2_Y2.xaxis.set_major_locator(ticker.MultipleLocator(X轴间隔))
AX_2_Y2.set_ylabel('Bytes')
AX_2_Y2.get_yaxis().get_major_formatter().set_scientific(False)
plt.xlabel('Time')
plt.show()
def 合并会话(D_TCP_PACKET):
D_TCP_SESSION = {}
for Key in D_TCP_PACKET:
if Key in D_TCP_SESSION:
print("重复")
else:
SrcIP, SrcPort, DstIP, DstPort = Key
Key_New = (DstIP, DstPort, SrcIP, SrcPort)
if Key_New in D_TCP_SESSION:
#print("合并")
D_TCP_SESSION[Key_New]['Packets'] += D_TCP_PACKET[Key]['Packets'] # 发包数量累加
D_TCP_SESSION[Key_New]['Bytes_Len'] += D_TCP_PACKET[Key]['Bytes_Len'] # 内容长度累加
else:
D_TCP_SESSION[Key] = D_TCP_PACKET[Key] # 复制
return(D_TCP_SESSION)
## 从PCAP文件中提取出指定会话的内容另存为PCAP文件
## 参数 PCAP_File_OR_Dir PCAP文件名或PCAP文件目录(多个PCAP文件需要按时间顺序存放)
## 参数 SaveDir 提取出的PCAP文件存放的目录
## 参数 D_SESSION_INFO 需要提取的会话信息(IP和端口正反方向都要写入) {'TCP':[(源IP,源端口,目的IP,目的端口),(目的IP,目的端口,源IP,源端口),], 'UDP':[(源IP,源端口,目的IP,目的端口),(目的IP,目的端口,源IP,源端口),]}
def 提取PCAP中指定会话数据另存为独立PCAP(PCAP_File_OR_Dir, SaveDir, D_SESSION_INFO):
print(f"【I】源PCAP文件或目录 {PCAP_File_OR_Dir}")
print(f"【I】保存目录 {SaveDir}")
E = 0
if os.path.isfile(PCAP_File_OR_Dir):
PATH,FileName = os.path.split(PCAP_File_OR_Dir) # 拆分路径和文件名
#print(PATH,FileName)
PCAP_File_OR_Dir = PATH # 这个后面但目录用
L_FileName = ['\\'+FileName] # 文件名
elif os.path.isdir(PCAP_File_OR_Dir):
L_FileName = os.listdir(PCAP_File_OR_Dir) # 目录内内容列表
else:
print(f"【E】{PCAP_File_OR_Dir} 不存在")
E = 1
try:
if not os.path.isdir(SaveDir):
os.makedirs(SaveDir)
os.chdir(SaveDir) ## 切换到存放拆分后文件的目录
except Exception as e:
print(e)
E = 1
else:
pass
if E == 0:
## 本地文件名保存到字典中
D_Local_File_Name = {}
文件数量 = len(L_FileName)
新建PCAP文件数量 = 0
计数 = 1
for F in L_FileName:
FullFileName = PCAP_File_OR_Dir+str(F)
print(f"【I】拆分PCAP文件:{FullFileName} 进度:{计数}/{文件数量}") ## 根据源目的IP和端口分成多个小pcap文件
f = open(FullFileName, 'rb') # 以二进制方式读取pcap格式文件
PCAP_DATA = f.read(24) # 读取前24字节头信息,忽略
计数 += 1
while 1:
包头 = f.read(16)
if not 包头: # 判断 包头 是否为空(读完或者本身为空时 S 为空)
break
PacketHeader = struct.unpack('IIII', 包头)
PacketData = f.read(PacketHeader[3])
# 以太部首
FrameType = PacketData[12:14] # 帧类型
# IP数据报头
if FrameType == b'\x08\x00': # 普通包
pass
elif FrameType == b'\x81\x00': # VLAN包
PacketData = PacketData[0:12] + b'\x08\x00' + PacketData[18:] ## 剔除VLAN数据
包头 = struct.pack('IIII', PacketHeader[0], PacketHeader[1], PacketHeader[2]-4, PacketHeader[3]-4) ## 修改包头
else:
#print("其他包忽略")
pass
## SrcIP_SrcPort 和 DstIP_DstPort 互换位置是同个TCP交互,要存入同个文件对象
IP_Protocol = PacketData[23:24]
## 只要TCP协议
if IP_Protocol == b'\x06': # 协议(TCP 6)(UDP 17)
SrcIP_Bytes = PacketData[26:30] # 源IP地址
DstIP_Bytes = PacketData[30:34] # 目的IP地址
SrcIP = socket.inet_ntoa(SrcIP_Bytes)
DstIP = socket.inet_ntoa(DstIP_Bytes)
SrcPort_Bytes = PacketData[34:36]
DstPort_Bytes = PacketData[36:38]
SrcPort = struct.unpack('>H', SrcPort_Bytes)[0]
DstPort = struct.unpack('>H', DstPort_Bytes)[0]
## 每一次连接生成2个文件名
SRC = SrcIP+'_'+ str(SrcPort)
DST = DstIP+'_'+ str(DstPort)
TCP_F_NAME_SD = 'TCP_'+SRC+'-'+DST+'.pcap' # 以当前包的源到目的生成一个文件
TCP_F_NAME_DS = 'TCP_'+DST+'-'+SRC+'.pcap' # 以当前包的目的到源生成一个文件
#print("TCP_F_NAME_SD", TCP_F_NAME_SD)
#print("TCP_F_NAME_DS", TCP_F_NAME_DS)
## 另存为小文件
if (SrcIP, SrcPort, DstIP, DstPort) in D_SESSION_INFO['TCP']:
if TCP_F_NAME_SD in D_Local_File_Name:
#print(" 归属", TCP_F_NAME_SD)
fp = open(D_Local_File_Name[TCP_F_NAME_SD], 'ba')
fp.write(包头)
fp.write(PacketData)
fp.close()
else:
print("【I】 新建", TCP_F_NAME_SD)
fp = open(TCP_F_NAME_SD, 'ba') # 新建一个文件对象,名字用客户端到服务端方向
fp.write(PCAP_DATA)
fp.write(包头)
fp.write(PacketData)
fp.close()
新建PCAP文件数量 += 1
## 两个方向对应同一个文件
D_Local_File_Name[TCP_F_NAME_SD] = TCP_F_NAME_SD # 方向1
D_Local_File_Name[TCP_F_NAME_DS] = TCP_F_NAME_SD # 方向2
elif IP_Protocol == b'\x11': # 协议(TCP 6)(UDP 17)
SrcIP_Bytes = PacketData[26:30] # 源IP地址
DstIP_Bytes = PacketData[30:34] # 目的IP地址
SrcIP = socket.inet_ntoa(SrcIP_Bytes)
DstIP = socket.inet_ntoa(DstIP_Bytes)
SrcPort_Bytes = PacketData[34:36]
DstPort_Bytes = PacketData[36:38]
SrcPort = struct.unpack('>H', SrcPort_Bytes)[0]
DstPort = struct.unpack('>H', DstPort_Bytes)[0]
## 每一次连接生成2个文件名
SRC = SrcIP+'_'+ str(SrcPort)
DST = DstIP+'_'+ str(DstPort)
UDP_F_NAME_SD = 'UDP_'+SRC+'-'+DST+'.pcap' # 以当前包的源到目的生成一个文件
UDP_F_NAME_DS = 'UDP_'+DST+'-'+SRC+'.pcap' # 以当前包的目的到源生成一个文件
## 另存为小文件
if (SrcIP, SrcPort, DstIP, DstPort) in D_SESSION_INFO['UDP']:
if UDP_F_NAME_SD in D_Local_File_Name:
#print(" 归属", UDP_F_NAME_SD)
fp = open(D_Local_File_Name[UDP_F_NAME_SD], 'ba')
fp.write(包头)
fp.write(PacketData)
fp.close()
else:
print("【I】 新建", UDP_F_NAME_SD)
fp = open(UDP_F_NAME_SD, 'ba') # 新建一个文件对象,名字用客户端到服务端方向
fp.write(PCAP_DATA)
fp.write(包头)
fp.write(PacketData)
fp.close()
新建PCAP文件数量 += 1
## 两个方向对应同一个文件
D_Local_File_Name[UDP_F_NAME_SD] = UDP_F_NAME_SD # 方向1
D_Local_File_Name[UDP_F_NAME_DS] = UDP_F_NAME_SD # 方向2
else:
#print("IP_Protocol", IP_Protocol)
pass
f.close()
print(f"【I】完成 {time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"【I】新建PCAP文件数量: {新建PCAP文件数量}")
else:
print("【E】有错误,终止 提取PCAP中指定会话数据() 操作")
## 交互操作提示内容
选择日志等级_TEXT = '''
##############################################
## 0 DEBUG 日志记录详细信息 ##
## 1 INFO 日志记录主要信息并在终端显示 ##
## 2 WARNING ##
## 3 ERROR ##
## 4 CRITICAL ##
## q 退出 ##
##############################################
【0】选择日志等级(写入日志文件): '''
选择过滤规则_TEXT = '''
##############################################
## 0 IP ##
## 1 PORT ##
## 2 IP+PORT ##
## 3 ALL(全部提取分析) ##
## q 返回上级 ##
##############################################
【1】选择过滤规则: '''
选择TCP或UDP_TEXT = '''
##############################################
## 0 查看TCP ##
## 1 查看UDP ##
## 2 每个TCP会话导出为单独PCAP文件 ##
## 3 每个UDP会话导出为单独PCAP文件 ##
## q 返回上级 ##
##############################################
【2】选择TCP或UDP: '''
TCP选择功能_TEXT = '''
##############################################
## 0 TCP会话交互过程用时分析(无图) ##
## 1 TCP会话交互过程用时分析(画图) ##
## 2 选中TCP会话导出为PCAP文件 ##
## 3 全部TCP会话导出为PCAP文件 ##
## q 返回上级 ##
##############################################
【4】选择功能: '''
UDP选择功能_TEXT = '''
##############################################
## 0 选中UDP会话导出为PCAP文件 ##
## q 返回上级 ##
##############################################
【4】选择功能: '''
def 选择TCP会话编号(L_TCP_SESSION_KEY, 筛选=''):
N = 0
print("-------------------------------------------------------------")
print("| 编号 TCP交互信息")
if 筛选 == '':
for i in L_TCP_SESSION_KEY:
print(f"| {N} \t {i}")
N += 1
print(f"| TCP会话总数 {N}")
print("-------------------------------------------------------------")
print("##############################################")
print("## x 画流量图 ##")
print("## s 筛选结果 ##")
print("## q 返回上级 ##")
print("## 输入会话编号分析会话用时信息(例 0,1,3-5) ##")
else:
匹配规则 = f"(.*){筛选}(.*)"
n = 0
for i in L_TCP_SESSION_KEY:
X = re.search(匹配规则, str(i))
if X:
print(f"- {N} \t {i}")
n += 1
N += 1
print(f"| 匹配字符串={筛选}")
print(f"| 筛选出TCP会话数 {n}")
print("-------------------------------------------------------------")
print("##############################################")
print("## x 画流量图 ##")
print("## s 筛选结果 ##")
print("## q 返回上级 ##")
print("## 输入会话编号分析会话用时信息(例 0,1,3-5) ##")
print("##############################################")
def 选择UDP会话编号(L_UDP_SESSION_KEY, 筛选=''):
N = 0
print("-------------------------------------------------------------")
print("| 编号 UDP交互信息")
if 筛选 == '':
for i in L_UDP_SESSION_KEY:
print(f"| {N} \t {i}")
N += 1
print(f"| UDP会话总数 {N}")
print("-------------------------------------------------------------")
print("##############################################")
#print("## x 画流量图 ##")
print("## s 筛选结果 ##")
print("## q 返回上级 ##")
print("## 输入会话编号分析会话用时信息(例 0,1,3-5) ##")
else:
匹配规则 = f"(.*){筛选}(.*)"
n = 0
for i in L_UDP_SESSION_KEY:
X = re.search(匹配规则, str(i))
if X:
print(f"| {N} \t {i}")
n += 1
N += 1
print(f"| 匹配字符串={筛选}")
print(f"| 筛选出UDP会话数 {n}")
print("-------------------------------------------------------------")
print("##############################################")
#print("## x 画流量图 ##")
print("## s 筛选结果 ##")
print("## q 返回上级 ##")
print("## 输入会话编号分析会话用时信息(例 0,1,3-5) ##")
print("##############################################")
def 解析用户输入的编号(TEXT):
TEXT = TEXT.replace(' ', '')
L = []
LSPa = TEXT.split(',')
#print("LSPa", LSPa)
for i in LSPa:
if i != '':
LSPb = i.split('-')
if len(LSPb) == 2:
A = int(LSPb[0])
B = int(LSPb[1])
if A <= B:
for j in range(A, B+1):
L.append(j)
elif len(LSPb) == 1:
#print("LSPb", LSPb)
A = int(LSPb[0])
L.append(A)
else:
pass
return(L)
## 返回传入元组对象的第二个值,排序要用
def T2(X):
return(X[1])
def 过滤出的包数据画流量图(D_TCP_PACKET, D_TIME_PACKET):
## 单向TCP会话统计 D_TCP_PACKET
Log.info("单向分别统计")
L_TCP_PACKET = [(Key,D_TCP_PACKET[Key]['Bytes_Len']) for Key in D_TCP_PACKET]
L_TCP_PACKET.sort(key=T2)
#for Key in D_TCP_PACKET:
# Log.info(f" {Key} {D_TCP_PACKET[Key]}")
for i in L_TCP_PACKET:
Log.info(f" {i[0]} {D_TCP_PACKET[i[0]]}")
## 双向TCP会话统计 D_TCP_SESSION
D_TCP_SESSION = 合并会话(D_TCP_PACKET)
Log.info("双向合并统计")
#for Key in D_TCP_SESSION:
# Log.info(f" {Key} {D_TCP_SESSION[Key]}")
L_TCP_SESSION = [(Key,D_TCP_SESSION[Key]['Bytes_Len']) for Key in D_TCP_SESSION]
L_TCP_SESSION.sort(key=T2)
for i in L_TCP_SESSION:
Log.info(f" {i[0]} {D_TCP_SESSION[i[0]]}")
画流量图(D_TIME_PACKET)
def TCP会话交互过程用时分析(L_会话编号, L_TCP_SESSION_KEY, D_TCP_ALL, 图, 选择日志等级):
## TCP交互中用时分析并画图
Log.info("-----------------------------------------------------------------------------------------------------------------------------------------------------------------")
TCP_START_TIME = []
TCP_END_TIME = []
D_S_KeepAlive = {} # S发起KeepAlive信息字典,Key=交互信息,Value=[(KeepAlive每次开始, 结束时间), ]
D_C_KeepAlive = {} # {'Key标识数据归属':[('10:18:29', '10:20:24'), [('10:19:50', '10:24:40')]],}
D_C_WAIT = {}
D_C_WIN_0_TIME = {}
D_S_WIN_0_TIME = {}
头行 = ('客户端', '端口', '服务端', '端口', '开始时间', '结束时间', '会话总用时', 'C_Keep时间', 'S_Keep时间', 'C间隔发包合时', 'C缓冲窗满合时', 'S缓冲窗满合时', '程序其它用时合计')
Log.info("%s %s %s %s %s %s %s %s %s %s %s %s %s" % 头行)
for 会话编号 in L_会话编号:
选择会话KEY = L_TCP_SESSION_KEY[会话编号]
L_DATA = D_TCP_ALL[选择会话KEY]['L_DATA']
C_发起开始时间 = L_DATA[0][1] # TCP会话第一个包的时间戳
C_发起结束时间 = L_DATA[-1][1] # TCP会话最后一个包的时间戳
TCP_START_TIME.append(C_发起开始时间)
TCP_END_TIME.append(C_发起结束时间)
L_TIME_Client_WAIT = 客户端发起请求间隔耗时(D_TCP_ALL[选择会话KEY]) # 客户端发起新请求和上次完成交互之间的间隔时间(下次交互开始-上次交互完成)
L_C_KeepAlive_RANGE, L_S_KeepAlive_RANGE = 分析KeepAlive耗时(D_TCP_ALL[选择会话KEY]) # 因保持连接造成的等待耗时
L_C_WIN_0_RANGE, L_S_WIN_0_RANGE = 分析接收窗口满耗时(D_TCP_ALL[选择会话KEY]) # 因接收窗口满造成的时间消耗
#print("L_C_WIN_0_RANGE", L_C_WIN_0_RANGE)
#print("L_S_WIN_0_RANGE", L_S_WIN_0_RANGE)
源地址, 源端口 = D_TCP_ALL[选择会话KEY]['CLIENT']
目的地址, 目的端口 = D_TCP_ALL[选择会话KEY]['SERVER']
D_C_KeepAlive[f"{源地址}:{源端口}<=>{目的地址}:{目的端口}"] = L_C_KeepAlive_RANGE
D_S_KeepAlive[f"{源地址}:{源端口}<=>{目的地址}:{目的端口}"] = L_S_KeepAlive_RANGE
D_C_WAIT[f"{源地址}:{源端口}<=>{目的地址}:{目的端口}"] = L_TIME_Client_WAIT
D_C_WIN_0_TIME[f"{源地址}:{源端口}<=>{目的地址}:{目的端口}"] = L_C_WIN_0_RANGE
D_S_WIN_0_TIME[f"{源地址}:{源端口}<=>{目的地址}:{目的端口}"] = L_S_WIN_0_RANGE
T_DATA = SHOW_TIME_WAIT(C_发起开始时间, C_发起结束时间, L_TIME_Client_WAIT, L_C_KeepAlive_RANGE, L_S_KeepAlive_RANGE, L_C_WIN_0_RANGE, L_S_WIN_0_RANGE, D_TCP_ALL[选择会话KEY])
## 每次交互的详细信息
L_TCP_TIME = TCP_SESSION_TIME(D_TCP_ALL[选择会话KEY]) # 分析TCP每次交互用时信息
SHOW_TCP_ALTERNATELY_TIME(L_TCP_TIME, 视角) # 记录每次客户端发请求到收完响应数据的统计信息
TCP交互用时合计 = sum([i[4] for i in L_TCP_TIME])
客户端发请求用时合计 = sum([i[5] for i in L_TCP_TIME])
服务端处理用时合计 = sum([i[6] for i in L_TCP_TIME])
服务端发包用时合计 = sum([i[7] for i in L_TCP_TIME])
Log.info(f" 程序其它用时合计包含: TCP交互用时合计={round(TCP交互用时合计,3)}(秒) (客户端发请求用时合计={round(客户端发请求用时合计,3)}, 服务端处理用时合计={round(服务端处理用时合计,3)}, 服务端发包用时合计={round(服务端发包用时合计,3)})")
## == debug == 标记用时分析的起止点
if 选择日志等级 == '0':
L_C_KeepAlive_RANGE_start = [i[0] for i in L_C_KeepAlive_RANGE]
L_C_KeepAlive_RANGE_end = [i[1] for i in L_C_KeepAlive_RANGE]
L_S_KeepAlive_RANGE_start = [i[0] for i in L_S_KeepAlive_RANGE]
L_S_KeepAlive_RANGE_end = [i[1] for i in L_S_KeepAlive_RANGE]
L_TIME_Client_WAIT_start = [i[0] for i in L_TIME_Client_WAIT]
L_TIME_Client_WAIT_end = [i[1] for i in L_TIME_Client_WAIT]
L_C_WIN_0_RANGE_start = [i[0] for i in L_C_WIN_0_RANGE]
L_C_WIN_0_RANGE_end = [i[1] for i in L_C_WIN_0_RANGE]
L_S_WIN_0_RANGE_start = [i[0] for i in L_S_WIN_0_RANGE]
L_S_WIN_0_RANGE_end = [i[1] for i in L_S_WIN_0_RANGE]
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-5s %5s %s %s' % ('ID','TIME','SrcIP','DstIP','SPort','DPort','Seq','Ack','FLAGE','LEN','请求/响应','WIN','校验码','备注说明'))
for i in L_DATA:
if i[1] in L_C_KeepAlive_RANGE_start:
X = i + ('[T]L_C_KeepAlive_RANGE_start',)
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
elif i[1] in L_C_KeepAlive_RANGE_end:
X = i + ('[T]L_C_KeepAlive_RANGE_end',)
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
elif i[1] in L_S_KeepAlive_RANGE_start:
X = i + ('[T]L_S_KeepAlive_RANGE_start',)
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
elif i[1] in L_S_KeepAlive_RANGE_end:
X = i + ('[T]L_S_KeepAlive_RANGE_end',)
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
elif i[1] in L_TIME_Client_WAIT_start:
X = i + ('[T]L_TIME_Client_WAIT_start',)
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
elif i[1] in L_TIME_Client_WAIT_end:
X = i + ('[T]L_TIME_Client_WAIT_end',)
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
elif i[1] in L_C_WIN_0_RANGE_start:
X = i + ('[T]L_C_WIN_0_RANGE_start',)
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
elif i[1] in L_C_WIN_0_RANGE_end:
X = i + ('[T]L_C_WIN_0_RANGE_end',)
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
elif i[1] in L_S_WIN_0_RANGE_start:
X = i + ('[T]L_S_WIN_0_RANGE_start',)
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
elif i[1] in L_S_WIN_0_RANGE_end:
X = i + ('[T]L_S_WIN_0_RANGE_end',)
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s %s' % X)
else:
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-9s %5s %6s %s' % i)
Log.debug('')
## == debug ==
print(f"\n结果已写入日志文件:{LOG_FILE}\n\n")
## 画图:TCP会话交互中各种用时信息画成图
## 计算最小开始时间和最大结束时间,统一X时间轴用
if 图 == 1:
TCP_START_TIME_MIN = min(TCP_START_TIME)
TCP_END_TIME_MAX = max(TCP_END_TIME)
TCP会话用时信息画图(TCP_START_TIME_MIN, TCP_END_TIME_MAX, D_C_KeepAlive, D_C_WAIT, D_S_KeepAlive, D_C_WIN_0_TIME, D_S_WIN_0_TIME)
## 交互运行
def RUN(LOG_DIR):
while 1:
选择日志等级 = input(选择日志等级_TEXT)
if 选择日志等级 == '0':
Log.setLevel(logging.DEBUG)
elif 选择日志等级 == '1':
Log.setLevel(logging.INFO)
elif 选择日志等级 == '2':
Log.setLevel(logging.WARNING)
elif 选择日志等级 == '3':
Log.setLevel(logging.ERROR)
elif 选择日志等级 == '4':
Log.setLevel(logging.CRITICAL)
elif 选择日志等级 == 'q':
break
else:
print("【W】请重新选择")
continue
while 1:
UserIP = ''
UserPort = 0
选择过滤规则 = input(选择过滤规则_TEXT)
if 选择过滤规则 == '0':
匹配内容 = 'IP'
UserIP = input(" 匹配IP: ")
elif 选择过滤规则 == '1':
匹配内容 = 'PORT'
UserPort = input(" 匹配PORT: ")
UserPort = int(UserPort)
elif 选择过滤规则 == '2':
匹配内容 = 'IP+PORT'
UserIP = input(" 匹配IP: ")
UserPort = input(" 匹配PORT: ")
UserPort = int(UserPort)
elif 选择过滤规则 == '3':
匹配内容 = 'ALL'
elif 选择过滤规则 == 'q':
break
else:
print("【W】请重新选择")
continue
print(" 选择过滤规则:", 选择过滤规则, 匹配内容)
## 每个会话解析为TCP会话信息字典
D_TCP_ALL, D_TIME_PACKET, D_TCP_PACKET, D_UDP_ALL = 提取PCAP中指定TCP会话数据(PCAP_File_OR_Dir, SaveDir, 匹配内容, UserIP, UserPort)
L_TCP_SESSION_KEY = TCP会话Key去重(D_TCP_ALL)
L_UDP_SESSION_KEY = UDP会话Key去重(D_UDP_ALL)
## 循环多次查看分析结果
while 1:
匹配字符串 = '' # 初始化筛选匹配字符串
选择TCP或UDP = input(选择TCP或UDP_TEXT)
if 选择TCP或UDP == 'q':
print("返回上级")
break
elif 选择TCP或UDP == '0':
while 1:
## 选择会话编号或操作指令
if '匹配字符串' not in locals(): # 判断变量是否存在
匹配字符串 = ''
选择TCP会话编号(L_TCP_SESSION_KEY, 匹配字符串)
选择会话编号或操作指令 = input("【3】选择要分析的会话编号: ")
if 选择会话编号或操作指令 == 'q':
print("返回上级")
break
elif 选择会话编号或操作指令 == 's':
匹配字符串 = input("匹配字符串: ")
elif 选择会话编号或操作指令 == 'x':
过滤出的包数据画流量图(D_TCP_PACKET, D_TIME_PACKET)
else:
## 检查用户输入
L_会话编号 = 解析用户输入的编号(选择会话编号或操作指令)
可用编号范围 = [i for i in range(0, len(L_TCP_SESSION_KEY))]
L_可用会话编号 = []
print("-------------------------------------------------------------")
print("| 已选编号")
for n in L_会话编号:
if n in 可用编号范围:
print(f"| {n}\t{L_TCP_SESSION_KEY[n]}")
L_可用会话编号.append(n)
else:
print(f"| {n}\t编号不存在,忽略")
print("-------------------------------------------------------------")
while 1:
TCP选择功能 = input(TCP选择功能_TEXT)
if TCP选择功能 == 'q':
print("返回上级")
break
elif TCP选择功能 == '0':
图 = 0
TCP会话交互过程用时分析(L_可用会话编号, L_TCP_SESSION_KEY, D_TCP_ALL, 图, 选择日志等级)
elif TCP选择功能 == '1':
图 = 1
TCP会话交互过程用时分析(L_可用会话编号, L_TCP_SESSION_KEY, D_TCP_ALL, 图, 选择日志等级)
elif TCP选择功能 == '2':
D_SESSION_INFO = {'TCP':[], 'UDP':[]}
for i in L_可用会话编号:
SIP,SPORT,DIP,DPORT = L_TCP_SESSION_KEY[i]
D_SESSION_INFO['TCP'].append(L_TCP_SESSION_KEY[i])
D_SESSION_INFO['TCP'].append((DIP,DPORT,SIP,SPORT))
提取PCAP中指定会话数据另存为独立PCAP(PCAP_File_OR_Dir, SaveDir, D_SESSION_INFO)
else:
print("【W】请重新选择")
continue
elif 选择TCP或UDP == '1':
while 1:
## 选择会话编号或操作指令
if '匹配字符串' not in locals(): # 判断变量是否存在
匹配字符串 = ''
选择UDP会话编号(L_UDP_SESSION_KEY, 匹配字符串)
选择会话编号或操作指令 = input("【3】选择要分析的会话编号: ")
if 选择会话编号或操作指令 == 'q':
print("返回上级")
break
elif 选择会话编号或操作指令 == 's':
匹配字符串 = input("匹配字符串: ")
else:
## 检查用户输入
L_会话编号 = 解析用户输入的编号(选择会话编号或操作指令)
可用编号范围 = [i for i in range(0, len(L_UDP_SESSION_KEY))]
L_可用会话编号 = []
print("-------------------------------------------------------------")
print("| 已选编号")
for n in L_会话编号:
if n in 可用编号范围:
print(f"| {n}\t{L_UDP_SESSION_KEY[n]}")
L_可用会话编号.append(n)
else:
print(f"| {n}\t编号不存在,忽略")
print("-------------------------------------------------------------")
while 1:
UDP选择功能 = input(UDP选择功能_TEXT)
if UDP选择功能 == 'q':
print("返回上级")
break
elif UDP选择功能 == '0':
D_SESSION_INFO = {'TCP':[], 'UDP':[]}
for i in L_可用会话编号:
SIP,SPORT,DIP,DPORT = L_UDP_SESSION_KEY[i]
D_SESSION_INFO['UDP'].append(L_UDP_SESSION_KEY[i])
D_SESSION_INFO['UDP'].append((DIP,DPORT,SIP,SPORT))
提取PCAP中指定会话数据另存为独立PCAP(PCAP_File_OR_Dir, SaveDir, D_SESSION_INFO)
else:
print("【W】请重新选择")
continue
elif 选择TCP或UDP == '2':
print("【W】数量多时比较耗时且执行不稳定")
D_SESSION_INFO = {'TCP':[], 'UDP':[]}
for i in D_TCP_ALL: # 取全部TCP
D_SESSION_INFO['TCP'].append(i) # 取全部TCP
提取PCAP中指定会话数据另存为独立PCAP(PCAP_File_OR_Dir, SaveDir, D_SESSION_INFO)
elif 选择TCP或UDP == '3':
print("【W】数量多时比较耗时且执行不稳定")
D_SESSION_INFO = {'TCP':[], 'UDP':[]}
for i in D_UDP_ALL: # 取全部UDP
D_SESSION_INFO['UDP'].append(i) # 取全部UDP
提取PCAP中指定会话数据另存为独立PCAP(PCAP_File_OR_Dir, SaveDir, D_SESSION_INFO)
else:
print("【W】请重新选择")
continue
#LOG_DIR = 'A:\\LOG\\'
LOG_DIR = '' # 设置日志文件存放目录,默认和程序相同目录
Log = logging.getLogger("Main")
LOG_FILE = LOG_DIR + time.strftime('%Y-%m-%d_%H%M%S')+'.log'
formatter = logging.Formatter('%(message)s') # 指定logger输出格式
file_handler = logging.FileHandler(LOG_FILE,encoding='UTF8') # 日志文件路径和编码
file_handler.setFormatter(formatter) # 可以通过setFormatter指定输出格式
Log.addHandler(file_handler)
#Log.setLevel(logging.DEBUG) # 日志等级 DEBUG INFO WARNING ERROR CRITICAL 在交互时设置
## 日志内容同时在终端显示
console = logging.StreamHandler()
console.setLevel(logging.INFO) # INFO级日志打印到终端
Log.addHandler(console)
PCAP_File_OR_Dir = 'A:\\test.pcap' # PCAP文件或目录位置
#PCAP_File_OR_Dir = 'Z:\\B\\'
SaveDir = 'A:\\PCAP_Save\\' # 拆分PCAP文件时存放的目录位置
视角 = 'S'
print(ReadMe)
RUN(LOG_DIR)
|