用python分析HTTP请求中各过程用时情况(通过分析抓包文件pcap实现)
想通过抓包看HTTP过程中详细的用时情况,如:建立TCP连接用了多少时间?客户端发送GET/POST请求数据到服务器用了多少时间?服务器接收完客户端请求数据用了多少时间?服务器接到请求后系统处理运行用了多少时间?服务器系统处理完后发送回应数据到客户端用了多少时间?客户端接收服务端发来的结果数据用了多少时间?传输过程中有没有丢包重传乱序?用简单三步就能搞定第一步,抓包用tcpdump抓包(Linux
·
HTTP卡扯皮终结工具,到底是哪个部分造成的卡?客户端?服务器?网络?
想通过抓包看HTTP过程中详细的用时情况,如:
建立TCP连接用了多少时间?
客户端发送GET/POST请求数据到服务器用了多少时间?
服务器接收完客户端请求数据用了多少时间?
服务器接到请求后系统处理运行用了多少时间?
服务器系统处理完后发送回应数据到客户端用了多少时间?
客户端接收服务端发来的结果数据用了多少时间?
传输过程中有没有丢包重传乱序?
用简单三步就能搞定
全部函数化,方便修改代码DEF_ALL_V19.py全部的自定义函数,Main.py入口,功能选择和说明
第一步,抓包
用tcpdump抓包(Linux)
tcpdump -i eth0 -w X.pcap
tcpdump -i eth0 ‘tcp and host 172.16.1.80’ -s0 -G 600 -w %s # 每600秒保存一个pcap文件,文件名是时间戳
或用wireshark抓包(Windows)
保存为pcap格式
第二步,保存要用到的函数文件 DEF_ALL_V19.py
和Main.py同一目录,或者自己修改应用位置
DEF_ALL_V19.py
import socket, time, struct, os, gzip, shutil
from binascii import b2a_hex, a2b_hex
from io import BytesIO
from urllib.parse import unquote
import logging
Log = logging.getLogger("Main.sub")
####################
## 解析数据包函数 ##
####################
## 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'))
## 查找CRLRCRLR位置(请求头部信息和实际数据的分界点)
## 16 16 10
## 回车 CR \r 0d 13 19 0x13 = 19
## 换行 LR \n 0a 10 16 0x13 = 16
## 空格 20 32
## 返回 CRLRx2_Index(连续2次CRLR位置,定位实际数据开始位置用), L_CRLR_index(CRLR位置列表,给解析行和头定位用)
def HTTP_CRLRx2_Index(BytesData):
CR = 0
LR = 0
字节当前位置 = 0
CRLR位置记录 = -2 # 记录找到的连续2次CRLR位置,定位实际数据开始位置用,初始为-2是网络避开第一次匹配
L_CRLR_index = [] # 保存找到的CRLR位置,给解析行和头定位用
for B in BytesData:
if B == 13:
CR = 字节当前位置
if B == 10:
LR = 字节当前位置
if CR+1 == LR: # 前一个CR的位置在当前LR位置的前一字节,说明CRLR连续
if 字节当前位置 == CRLR位置记录 + 2:
#print("连续2次CRLR", 字节当前位置)
return(字节当前位置, L_CRLR_index) # 找到连续2次CRLR位置,直接终止返回结果
else:
CRLR位置记录 = 字节当前位置
L_CRLR_index.append(字节当前位置)
字节当前位置 += 1 # 每运行一次,位置加1
return(字节当前位置, L_CRLR_index) # 没有找到连续2次CRLR位置
## 根据前5字节内容尝试判断HTTP类型
def HTTP_Request(BytesData):
if BytesData == b'\x50\x4f\x53\x54\x20': # POST空格
return('POST')
elif BytesData == b'\x48\x54\x54\x50\x2f': # HTTP/
return('HTTP')
elif BytesData[0:4] == b'\x47\x45\x54\x20': # GET空格
return('GET')
else:
return('DATA') # 其他请求其他情况全当数据
## 解析HTTP请求行和请求头(只尝试解析请求的第一个数据包,可能不完整,主要提取URL看看和提取post数据长度信息)
## 请求报文:请求行 + 请求头 + 请求体
## 请求行: 请求方法 + ' ' + URL + ' ' + 协议版本 + \r\n
## 请求方法有8种:GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS/TRACE
## 请求头: 头部字段名:值\r\n + ... + 头部字段名:值\r\n + \r\n
## 请求体: 请求数据
## 响应报: 响应行 + 响应头 + 响应体
## 响应行: 报文协议及版本 + ' ' + 状态码 + ' ' + 状态描述 + \r\n
## 响应头: 头部字段名:值\r\n + ... + 头部字段名:值\r\n + \r\n
## 响应体: 实际数据
def TRY_HTTP_REQ_INFO(BytesData):
## 请求信息做成字典返回
D_HTTP_Header = {'Request':'NA', 'URL':'NA', 'Version':'NA'} # 设置初始值
分段点, L_CRLR_index = HTTP_CRLRx2_Index(BytesData) # 分出信息和实际内容的位置下标
#print(" ", L_CRLR_index, 分段点)
if 分段点 > 2:
L_CR_index = [i-1 for i in L_CRLR_index] # CRLR的下标-1变成CR的下标,方便取信息内容
L_CR_index.append(分段点-1)
else:
L_CR_index = [i-1 for i in L_CRLR_index]
#print(" ", L_CR_index)
## 提取请求行
if len(L_CR_index) != 0:
请求行 = BytesData[0:L_CR_index[0]] # 取回车换行的第一段
elif len(L_CR_index) == 0:
请求行 = BytesData # 可能是很大的请求包,整段试试
#print(" 请求行(Bytes)", 请求行)
## 解析请求行
请求行SP = 请求行.decode('ASCII', errors='ignore').split(' ')
if len(请求行SP) == 3: # 正常是3段
请求方法, URL, 协议版本 = 请求行SP
D_HTTP_Header['Request'] = 请求方法
D_HTTP_Header['URL'] = URL
D_HTTP_Header['Version'] = 协议版本
elif len(请求行SP) == 2: # 可能请求报文如GET实在太长被分成多个包,能取多少取多少
#print("len请求行SP==2", 请求行SP)
D_HTTP_Header['Request'] = 请求行SP[0]
D_HTTP_Header['URL'] = 请求行SP[1]
else: # 1段,不能用空格分段,肯定不对
ERROR = "【ERROR】不是请求报文"
print(ERROR)
Log.error(ERROR)
return(1, D_HTTP_Header)
## 提取请求头,不能拿到就算了,可能不是请求报文,可能请求报文如GET实在太长被分成多个包
if len(L_CR_index) > 2: ## 正常起码有3个(请求行结尾1个,请求头结尾2个)
请求头 = BytesData[L_CR_index[0]+2:L_CR_index[-1]]
#print(" 请求头(Bytes)", 请求头)
## 解析请求头
请求头SP = 请求头.decode('ASCII', errors='ignore').split('\r\n')
for i in 请求头SP:
#print(i)
SP = i.split(': ')
#print(SP)
if len(SP) == 2:
D_HTTP_Header[SP[0]] = SP[1]
return(0, D_HTTP_Header)
## 解析HTTP请求数据(拼接数据包后的完整数据),直接写入日志
def MBF_HTTP_TEXT_C_ALL(MBF):
## 请求信息做成字典
D_HTTP_Header = {}
BytesData = MBF.getvalue()
CRLRx2_Index, L_CRLR_index = HTTP_CRLRx2_Index(BytesData)
Log.debug(f" 【DEBUG】CRLR结果:信息和数据分隔点={CRLRx2_Index} 信息内分隔点{L_CRLR_index}")
if CRLRx2_Index > 0:
## 信息部分
HTTP_INFO = BytesData[0:CRLRx2_Index-1] # -1可以把结尾的\r\n只留前一个\r\n
Log.debug(f" 【DEBUG】信息部分 {HTTP_INFO}")
HTTP_INFO_SP = HTTP_INFO.decode('ASCII', errors='ignore').split('\r\n')
## 解析请求行
请求行SP = HTTP_INFO_SP[0].split(' ')
请求方法, URL, 协议版本 = 请求行SP
D_HTTP_Header['Request'] = 请求方法
if 请求方法 == 'GET':
URL = unquote(URL)
D_HTTP_Header['URL'] = URL
D_HTTP_Header['Version'] = 协议版本
## 解析请求头
Log.info(f" ===[ 请求信息 ]===")
for n in range(1, len(HTTP_INFO_SP)):
#Log.info(f" {HTTP_INFO_SP[n]}")
SP = HTTP_INFO_SP[n].split(': ')
if len(SP) == 2:
if SP[0] == 'Referer':
D_HTTP_Header['Referer'] = unquote(SP[1]) ## 解码
else:
D_HTTP_Header[SP[0]] = SP[1]
for K in D_HTTP_Header:
Log.info(f" {K} : {D_HTTP_Header[K]}")
## 数据部分
HTTP_DATA = BytesData[CRLRx2_Index+1:] # +1可以省去开头的\r\n
Log.info(" ===[ 请求数据 ]===")
if len(HTTP_DATA) != 0:
if 'Content-Type' in D_HTTP_Header:
MIME = D_HTTP_Header['Content-Type']
if MIME in ('application/x-www-form-urlencoded; charset=UTF-8'):
REQ_DATA = unquote(HTTP_DATA.decode('ASCII', errors='ignore')) # 解码请求数据
Log.info(REQ_DATA)
else:
Log.info(f" 放弃解析 {MIME}")
else:
Log.info(' 没有 Content-Type 显示原始数据')
Log.info(f"{HTTP_DATA}")
else:
Log.info(" 无数据")
else:
Log.error(f"【ERROR】没有找到连续2个CRLR {CRLRx2_Index} {L_CRLR_index}")
Log.debug(" ===[ LEN ]===")
Log.debug(f" TCP DATA LEN: {len(BytesData)} (信息+数据)")
Log.debug(f" HTTP DATA LEN: {len(HTTP_DATA)} (数据)")
if 'Content-Length' in D_HTTP_Header:
Log.debug(f" Content-Length:{D_HTTP_Header['Content-Length']}")
## 解析HTTP响应数据(拼接数据包后的完整数据),直接写入日志
def MBF_HTTP_TEXT_S_ALL(MBF):
D_HTTP_Header = {} ## 请求信息做成字典
BytesData = MBF.getvalue()
CRLRx2_Index, L_CRLR_index = HTTP_CRLRx2_Index(BytesData)
Log.debug(f" 【DEBUG】CRLR结果:信息和数据分隔点={CRLRx2_Index} 信息内分隔点{L_CRLR_index}")
if CRLRx2_Index > 0:
## 信息部分
HTTP_INFO = BytesData[0:CRLRx2_Index-1] ## -1可以把结尾的\r\n只留前一个\r\n
Log.debug(f" 【DEBUG】信息部分 {HTTP_INFO}")
HTTP_INFO_SP = HTTP_INFO.decode('ASCII', errors='ignore').split('\r\n')
## 解析响应行
响应行SP = HTTP_INFO_SP[0].split(' ')
D_HTTP_Header['HTTP_INFO'] = 响应行SP
## 解析响应头
for n in range(1, len(HTTP_INFO_SP)):
#Log.info(f" {HTTP_INFO_SP[n]}")
SP = HTTP_INFO_SP[n].split(': ')
if len(SP) == 2:
D_HTTP_Header[SP[0]] = SP[1]
Log.info(f" ===[ 请求信息 ]===")
for K in D_HTTP_Header:
Log.info(f" {K} : {D_HTTP_Header[K]}")
## 数据部分
HTTP_DATA = BytesData[CRLRx2_Index+1:] ## +1可以省去开头的\r\n
Log.info(" ===[ 响应数据 ]===")
if len(HTTP_DATA) != 0:
字符集 = 'UTF8'
if 'Content-Type' in D_HTTP_Header:
MIME = D_HTTP_Header['Content-Type']
if MIME in ('text/plain;charset=UTF-8', 'text/html;charset=UTF-8'):
if 'Content-Encoding' in D_HTTP_Header and 'Transfer-Encoding' in D_HTTP_Header:
if D_HTTP_Header['Content-Encoding'] == 'gzip' and D_HTTP_Header['Transfer-Encoding'] == 'chunked':
Log.info(f" HTTP_TEXT chunked + gzip")
R, MBF = Chunked_BytesData_HTTP_DATA(HTTP_DATA) # chunked 数据拼接
if R == 0:
HTTP_TEXT = MBF_UNGZIP(MBF, 字符集) # gzip 解压
if HTTP_TEXT != '':
Log.info(f"{HTTP_TEXT}")
else:
Log.info(f"解码后为空")
else:
Log.info(f"{MBF}")
#elif MIME == 'application/javascript':
#elif MIME == 'text/css':
#elif MIME == 'image/jpeg'
elif MIME == 'application/x-msdownload;charset=UTF-8':
if 'Content-Disposition' in D_HTTP_Header:
FileName = unquote(D_HTTP_Header['Content-Disposition'].split('filename=')[1][1:-1])
Log.info(f" 文件 x-msdownload FileName={FileName}")
R, MBF = Chunked_BytesData_HTTP_DATA(HTTP_DATA)
if R == 0:
DIR = 'A:\\'
MBF_SAVE(MBF, DIR+FileName) # 保存到本地
else:
Log.info(f"{MBF}")
else:
Log.info(f"放弃解析 {MIME}")
if len(HTTP_DATA) < 100000:
Log.info(f"{HTTP_DATA}")
else:
Log.info(f"原始数据>100000不打印了")
else:
Log.info('没有Content-Type')
else:
Log.info('无数据')
else:
Log.error(f"【ERROR】没有找到连续2个CRLR {CRLRx2_Index} {L_CRLR_index}")
Log.debug(" ===[ LEN ]===")
Log.debug(f" TCP DATA LEN: {len(BytesData)} (信息+数据)")
Log.debug(f" HTTP DATA LEN: {len(HTTP_DATA)} (数据)")
if 'Content-Length' in D_HTTP_Header:
Log.debug(f" Content-Length:{D_HTTP_Header['Content-Length']}")
## MBF 保存到本地
def MBF_SAVE(MBF, FileName):
f = open(FileName, 'ba')
f.write(MBF.getvalue())
f.close()
Log.info(f"文件保存到 {FileName}")
## MBF gzip解压成文本
def MBF_UNGZIP(MBF, 字符集):
#print("GZIP", MBF.getvalue())
MBF.seek(0) ## 必须移动到MBF文件开头
f = gzip.open(MBF,'rb')
DATA_UNGZIP = f.read()
#print(f"DATA_UNGZIP {DATA_UNGZIP}")
try:
TEXT = DATA_UNGZIP.decode(字符集)
except Exception as e:
TEXT = f"用字符集{字符集}解码失败"
#print("TEXT", TEXT)
return(TEXT)
## 解析 HTTP Chunked 内容,传入BytesData,返回内存文件对象MBF
def Chunked_BytesData_HTTP_DATA(BytesData):
ChunkSizeBytes = b'' # 二进制类型ChunkSize
MBF_R = BytesIO()
MBF_R.write(BytesData)
MBF_R.seek(0)
MBF_W = BytesIO() # ChunkData 保存在内存文件中
while 1:
Bx = MBF_R.read(1)
if not Bx:
break
#Log.debug(f"Bx {Bx}")
if Bx == b'\r':
By = MBF_R.read(1)
if By == b'\n':
try:
ChunkSize = int(ChunkSizeBytes, 16) # 转成十进制数字类型ChunkSize
except Exception as e:
Log.debug(f" 【ERROR】CRLR ChunkSizeBytes {ChunkSizeBytes} LEN {len(ChunkSizeBytes)} E {e}")
return(1, '拼接数据异常')
else:
Log.debug(f" 【DEBUG】CRLR ChunkSizeBytes {ChunkSizeBytes} LEN {len(ChunkSizeBytes)} ChunkSize(int) {ChunkSize}")
if ChunkSize == 0:
break
ChunkSizeBytes = b''
ChunkData = MBF_R.read(ChunkSize) # 读取实际数据
MBF_R.read(2) # 再向后读掉\r\n这两个字节
#print("ChunkData", ChunkData)
MBF_W.write(ChunkData) # 写入实际数据
else:
ChunkSizeBytes += By
else:
ChunkSizeBytes += Bx
return(0, MBF_W)
################################
## 解析数据包计算请求时间部分 ##
################################
## 读取pcap格式文件,返回1个字典和一个列表
## D_HTTP 包信息字典
## L_DATA 包信息
## 开头第一个包必须是SYN包,不能含VLAN信息,这些由前置拆分PCAP文件的函数解决
def IPv4_TCP_HTTP_SESSION(File, 校验):
## 记录HTTP各过程信息字典 D_HTTP
D_HTTP = {} ## 最外层的字典
D_HTTP['CLIENT'] = () ## (HOST, PORT)
D_HTTP['SERVER'] = () ## (HOST, PORT)
D_HTTP['C_SYN'] = () ## (时间戳, seq, ack), # 客户端发起连接
D_HTTP['S_ACK_SYN'] = () ## (时间戳, seq, ack), # 服务端确认连接
D_HTTP['C_ACK'] = () ## (时间戳, seq, ack), # 客户端确认连接
D_HTTP['S_ACK_FIN_SeqKey'] = {} ## 服务端发起的终止信息,以Seq为KEY,因为Seq=对应请求的Ack
D_HTTP['C_ACK_FIN'] = () ## C已经确认S接收完全部数据的信息
D_HTTP['S_ACK_RST'] = {} ## 服务端强制断开TCP连接
D_HTTP['C_ACK_RST_AckKey'] = {} ## 客户端强制断开TCP连接
D_HTTP['C_ACK_RST_SeqKey'] = {} ## 客户端强制断开TCP连接
D_HTTP['C_HTTP_REQ'] = {} ## TCP数据中开头是GET/POST的包(客户端发起的请求第一个数据包)
D_HTTP['C_HTTP_REQ_RPT'] = {} ## 存放重复的第一个请求数据包(有可能后面发的重复请求才是正确的)
D_HTTP['HTTP_SC'] = {} ## {ACK:[]}, # 每个请求的HTTP状态码
D_HTTP['TCP_CS_ERR'] = [] ## TCP校验失败的包编号
D_HTTP['C_REQ_Header'] = {} ## 记录客户端GET/POST请求行和请求头信息 Ack:{'':''}
#D_HTTP['S_HTTP_Header'] = {} ## 记录服务端回复的请求行和请求头信息(暂时不用了)
D_HTTP['D_HTTP_INFO_SEQ'] = {} # 存放HTTP响应的第一个包信息,用于在找不到响应时候在这里暴力搜索试试,以Seq为key(HTTP回应的第一个包的seq=请求的ack)(请求的ack是服务器发是数据断点位置,回应就是从这个断点开始发包)
D_HTTP['D_C_KEEP'] = [] # 客户端发的keep-alive包
D_HTTP['D_S_KEEP'] = [] # 服务端回的keep-alive包
D_HTTP['D_C_ACK_DATA_RPT'] = {} # C重发的数据包
D_HTTP['D_S_ACK_DATA_RPT'] = {} # S重发的数据包
#D_HTTP['D_S_ACK'] = {} # S 发送的ACK信息
#D_HTTP['D_C_ACK'] = {} # C 发送的ACK信息
D_HTTP['D_S_ACK_DATA'] = {} # S 发送的ACK及数据
D_HTTP['D_C_ACK_DATA'] = {} # C 发送的ACK及数据
D_HTTP['S_HTTP_Header_RPT'] = {} # 重复响应信息头字典
D_HTTP['C_ACK_WIN_0'] = [] ## 记录C发的ACK且窗口为0的包信息(Seq,Ack)说明C缓存满,S会发ACK数据为1的包探测查看状态,S的(Seq,Ack)和C的刚好相反
D_HTTP['S_ACK_WIN_0'] = [] ## 记录S发的ACK且窗口为0的包信息(Seq,Ack)说明S缓存满
## 存放每个数据包信息基本信息
L_DATA = []
C_Tx_DATA_ALL = 0 # C已发数据编号,已发数据累计(不含重发的数据)遇到 SYN 包时设置
S_Tx_DATA_ALL = 0 # S已发数据编号,已发数据累计(不含重发的数据)遇到 SYN ACK 包时设置
P_C_Tx_Seq_Ack = set() # C发的含数据的ACK包的(Seq,Ack)信息集合,遇到重复说明是重发的包,在S处抓包这个就是 L_C_Rx_Info 接收到C发数据,重复说明重收,接不到对方丢的包的
P_S_Tx_Seq_Ack = set() # S发的含数据的ACK包的(Seq,Ack)信息集合,遇到重复说明是重发的包,在C处抓包这个就是 L_S_Rx_Info 接收到S发数据,重复说明重收,接不到对方丢的包的
KEEP_ALIVE = () # 当发现C发的是keep包时,存下 (包Ack, C发送总数据量) 用于识别出服务器的回应包
## 二进制方式读取pcap格式文件
f = open(File,'rb')
PCAP_DATA = f.read(24) # 读取24字节pcap格式头,跳过
编号 = 0
while 1:
PS = '' # 初始化每个包的备注信息
PacketHeader_Bytes = f.read(16) # 包头16字节
if not PacketHeader_Bytes: # 判断 PacketHeader_Bytes 是否为空(读完或者本身为空时)
break
编号 += 1
时间戳, 抓取数据包长度, 实际数据包长度 = Packet_Header(PacketHeader_Bytes)
PacketData = f.read(抓取数据包长度) # 继续读取抓取数据包长度字节数内容
DstMAC, SrcMAC, FrameType = EthernetII_Header(PacketData[0:14])
if FrameType == b'\x08\x00': # 表示IPv4
## 解析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
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数据长度 = IP_Total_Length - IP部首字节长度 - TCP部首固定长度 - TCP选项长度
## 计算数据长度
## 最小60字节,不足会填充
#剩余全部数据 = PacketData[54+TCP选项长度:]
剩余有效数据 = PacketData[54+TCP选项长度:54+TCP选项长度+TCP数据长度]
#print("剩余全部数据字节数(TCP选项后全部)", len(剩余全部数据)) ## 当不足最小长度会填充,这个会比下面的大
#print("剩余有效数据字节数(计算得到)", len(剩余有效数据))
## 是否进行TCP校验,校验错误的包可能可以正常使用
if 校验 == 1:
## 校验TCP是否正确
#print("提取 TCP_Checksum", hex(TCP_Checksum))
TCP_Pseudo_Total_Length = TCP部首实际长度 + TCP数据长度
#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)
else:
校验结果 = 0
## 分析数据包
if 校验结果 == 0:
#print("验证通过/忽略校验")
## 分析TCP内容
HTTP_REQ = '' # HTTP/GET/POST
HTTP_SC = 0 # HTTP状态码(HTTP Status Code)
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, 编号))
## 分类缓存数据包步骤1:数据的包里关键字 GET/POST/HTTPtext
## GET 请求信息保存到字典 D_HTTP['C_HTTP_REQ'] 以包 Ack 做字典KEY
## POST 请求信息保存到字典 D_HTTP['C_HTTP_REQ'] 以包 Ack 做字典KEY
## HTTP 回应信息(第一个含内容的回应包)保存到字典 D_HTTP['HTTP_SC'] 以包 Ack 做字典KEY(Ack=客户端上个请求数据发完后的数据编号)
if TCP数据长度 > 4: # 用前5字节尝试判断GET/POST/HTTP
HTTP_REQ = HTTP_Request(剩余有效数据[0:5]) # 先提取5字节看看是不是HTTP类型
if HTTP_REQ in ('POST','GET'):
#print("初步判断是 GET/POST 请求")
R,D_HTTP_Header = TRY_HTTP_REQ_INFO(剩余有效数据) # 尝试从首个请求数据中获取请求头信息
if R == 0:
## 记录GET信息
if Ack not in D_HTTP['C_REQ_Header']: # 只保留一个
D_HTTP['C_REQ_Header'][Ack] = D_HTTP_Header
## 不重复的POST/GET请求包信息保存到 C_HTTP_REQ 字典 Key=Ack Value=(包信息)
## 重复的都保存到 C_HTTP_REQ_RPT 字典 Key=Ack Value=[包信息,]
if Ack not in D_HTTP['C_HTTP_REQ']:
D_HTTP['C_HTTP_REQ'][Ack] = (时间戳, Seq, Ack, TCP数据长度)
else:
PS = "重复请求,保存到C_HTTP_REQ_RPT"
if Ack in D_HTTP['C_HTTP_REQ_RPT']:
D_HTTP['C_HTTP_REQ_RPT'][Ack].append((时间戳, Seq, Ack, TCP数据长度))
else:
D_HTTP['C_HTTP_REQ_RPT'][Ack] = [(时间戳, Seq, Ack, TCP数据长度)]
elif HTTP_REQ == 'HTTP': # HTTP回复第一个包信息
#print("初步判断是 HTTP响应")
HTTP_STATUS = 剩余有效数据[9:12]
try:
HTTP_SC = int(HTTP_STATUS)
except Exception as e:
print(f"稀有异常 HTTP_SC = int(HTTP_STATUS) ERROR PCAP文件:{File} 编号:{编号} 可能是重发的请求包从 HTTP/ 开始的,掉了请求行的 GET/POST 和 URL 这前两部分")
else:
## 在能解析出SC时才保存,防止非HTTP包乱入
if Seq in D_HTTP['D_HTTP_INFO_SEQ']:
D_HTTP['D_HTTP_INFO_SEQ'][Seq].append((时间戳, Seq, Ack, TCP数据长度, TCP_Window, 编号))
else:
D_HTTP['D_HTTP_INFO_SEQ'][Seq] = [(时间戳, Seq, Ack, TCP数据长度, TCP_Window, 编号)]
## 记录HTTP响应码,重复回应只保留第一个
if Ack in D_HTTP['HTTP_SC']:
PS = "【WARNING】有重复HTTP回应,只保留第一个"
else:
D_HTTP['HTTP_SC'][Ack] = HTTP_SC # 保存http状态码
## 根据FLAGE分类缓存数据包
if FLAGE in ('ACK', 'ACK PSH'):
## 先区分是哪端发的ACK
if SrcPort == D_HTTP['CLIENT'][1]:
## 客户端发ACK
## Seq = C发的这个包的数据编号(累计值,不含当次数据,不含重发)
## Ack = C确认已经接到S数据的编号(丢包/乱序信息放在TCP选项SACK中发送)
## 判断是否有数据
if TCP数据长度 == 0:
if TCP_Window == 0: # 记录C发的ACK且窗口为0的包信息(Seq,Ack)说明C接收缓存满
D_HTTP['C_ACK_WIN_0'].append((Seq,Ack)) # 记录S接收缓存满的包信息
PS = '接收缓冲区已满(C)'
## 保存到 D_C_ACK 无数据的消息包直接全部存起来(C确认收完S数据的纯回应包可能会在这里)
#if Ack in D_HTTP['D_C_ACK']:
# D_HTTP['D_C_ACK'][Ack].append((时间戳, Seq, Ack, TCP数据长度, TCP_Window))
#else:
# D_HTTP['D_C_ACK'][Ack] = [(时间戳, Seq, Ack, TCP数据长度, TCP_Window)]
elif TCP数据长度 == 1 and Seq == C_Tx_DATA_ALL -1:
## 数据长度为1,Seq(自发送累计) 主动变小1的,应该就是C发的keep-alive包,不累计大小
PS = 'C【keep-alive】保持连接包'
if KEEP_ALIVE != ():
KEEP_ALIVE = (Ack, C_Tx_DATA_ALL) ## 如果是第一次遇到,就记录一下,用于判断出服务端对应的回应包
D_HTTP['D_C_KEEP'].append((时间戳, Seq, Ack, TCP数据长度, TCP_Window)) ## 保存客户端发的keep-alive包
elif TCP数据长度 == 1 and (Ack,Seq) in D_HTTP['S_ACK_WIN_0']: # TCP数据长度是1且能和S缓存满的消息能对应上,说明是C发的探测S窗口的包
PS = '(C=>S Window=?)'
else:
## 不重复的保存到 D_C_ACK_DATA 重发的保存到 D_C_ACK_DATA_RPT
## 已经避开了纯消息包和保持连接的包
if (Seq,Ack) not in P_C_Tx_Seq_Ack:
P_C_Tx_Seq_Ack.add((Seq,Ack))
if Ack in D_HTTP['D_C_ACK_DATA']:
D_HTTP['D_C_ACK_DATA'][Ack].append((时间戳, Seq, Ack, TCP数据长度, TCP_Window, 编号))
else:
D_HTTP['D_C_ACK_DATA'][Ack] = [(时间戳, Seq, Ack, TCP数据长度, TCP_Window, 编号)]
else:
PS = 'C重发数据'
if Ack in D_HTTP['D_C_ACK_DATA_RPT']:
D_HTTP['D_C_ACK_DATA_RPT'][Ack].append((时间戳, Seq, Ack, TCP数据长度, TCP_Window, 编号))
else:
D_HTTP['D_C_ACK_DATA_RPT'][Ack] = [(时间戳, Seq, Ack, TCP数据长度, TCP_Window, 编号)]
else:
## 服务端发ACK
## Seq = S自己已经发数据的累计(不含本次的数据量,不累计重发数据)
## Ack = S已经接到了C发的多少数据(S告诉C端,S是接到C多少数据后的回应,在HTTP里这个可以用于区别这个回应是针对哪个请求的)
if TCP数据长度 == 0:
if (Seq, Ack) == KEEP_ALIVE:
#Log.debug(f" 【KEEP】和标记 KEEP_ALIVE {KEEP_ALIVE} 相同,C已经接到过这个包")
D_HTTP['D_S_KEEP'].append((时间戳, Seq, Ack, TCP数据长度, TCP_Window)) ## 保存服务端回应的keep-alive包
PS = 'S【keep-alive】保持连接包'
if TCP_Window == 0: # 记录S发的ACK且窗口为0的包信息(Seq,Ack)说明S接收缓存满
D_HTTP['S_ACK_WIN_0'].append((Seq,Ack)) # 记录S接收缓存满的包信息
PS = '接收缓冲区已满(S)'
# ## 保存到 D_S_ACK
# if Ack in D_HTTP['D_S_ACK']:
# D_HTTP['D_S_ACK'][Ack].append((时间戳, Seq, Ack, TCP数据长度, TCP_Window))
# else:
# D_HTTP['D_S_ACK'][Ack] = [(时间戳, Seq, Ack, TCP数据长度, TCP_Window)]
elif TCP数据长度 == 1 and (Ack,Seq) in D_HTTP['C_ACK_WIN_0']: ## TCP数据长度是1且能和C缓存满的消息能对应上,说明是S发的探测C窗口的包
PS = '(S=>C Window=?)'
else:
## HTTP协议中,一个tcp会话中一方在发数据的时间一方只能接和回应消息,不能回应数据
## 不重复的保存到 D_S_ACK_DATA 重发的保存到 D_S_ACK_DATA_RPT
if (Seq,Ack) not in P_S_Tx_Seq_Ack:
P_S_Tx_Seq_Ack.add((Seq,Ack))
if Ack in D_HTTP['D_S_ACK_DATA']:
D_HTTP['D_S_ACK_DATA'][Ack].append((时间戳, Seq, Ack, TCP数据长度, TCP_Window, 编号))
else:
D_HTTP['D_S_ACK_DATA'][Ack] = [(时间戳, Seq, Ack, TCP数据长度, TCP_Window, 编号)]
else:
PS = 'S重发数据'
if Ack in D_HTTP['D_S_ACK_DATA_RPT']:
D_HTTP['D_S_ACK_DATA_RPT'][Ack].append((时间戳, Seq, Ack, TCP数据长度, TCP_Window, 编号))
else:
D_HTTP['D_S_ACK_DATA_RPT'][Ack] = [(时间戳, Seq, Ack, TCP数据长度, TCP_Window, 编号)]
elif FLAGE in ('ACK FIN', 'A.P.F'): ## 终止连接
if DstPort == D_HTTP['CLIENT'][1]: ## 是 S 发起的 ACK FIN
if Seq in D_HTTP['S_ACK_FIN_SeqKey']:
D_HTTP['S_ACK_FIN_SeqKey'][Seq].append((时间戳, Seq, Ack, TCP数据长度))
else:
D_HTTP['S_ACK_FIN_SeqKey'][Seq] = [(时间戳, Seq, Ack, TCP数据长度)]
else: ## 是 C 发起的 ACK FIN
D_HTTP['C_ACK_FIN'] = (时间戳, Seq, Ack, TCP数据长度)
elif FLAGE in ('ACK RST', 'RST', 'A.P.R'): ## 强制断开连接
if DstPort == D_HTTP['CLIENT'][1]:
if Ack in D_HTTP['S_ACK_RST']:
PS = 'S强制终止连接(重复)'
else:
D_HTTP['S_ACK_RST'][Ack] = (时间戳, Seq, Ack, TCP数据长度)
PS = 'S强制终止连接'
else:
## C终止自己的请求 C_ACK_RST_AckKey 以Ack为KEY,给没有数据回应情况用请求Ack匹配RST的ACK
if Ack in D_HTTP['C_ACK_RST_AckKey']:
PS = '客户端重复强制终止连接'
else:
D_HTTP['C_ACK_RST_AckKey'][Ack] = (时间戳, Seq, Ack, TCP数据长度) # 记录
PS = '客户端强制终止连接'
## C终止自己的请求 C_ACK_RST_SeqKey 以Seq为KEY,期望S响应的Ack=这个Seq,给有回应的匹配用
if Seq in D_HTTP['C_ACK_RST_SeqKey']:
PS = 'C强制终止连接(重复)(S已经响应)'
else:
D_HTTP['C_ACK_RST_SeqKey'][Seq] = (时间戳, Seq, Ack, TCP数据长度) # 记录
PS = 'C强制终止连接(S已经响应)'
elif FLAGE == 'SYN':
D_HTTP['CLIENT'] = (SrcIP,SrcPort)
D_HTTP['SERVER'] = (DstIP,DstPort)
D_HTTP['C_SYN'] = (时间戳, Seq, Ack, 0)
C_Tx_DATA_ALL = Seq+1 ## 客户端初始数据编号
PS = f"C初始数据编号 {C_Tx_DATA_ALL} 新TCP连接"
elif FLAGE == 'ACK SYN': ## 回应SYN请求,是本次连接的服务端,Seq随机,Ack为发起方Seq+1
if D_HTTP['C_SYN'] != ():
if Ack == D_HTTP['C_SYN'][1] + 1:
D_HTTP['S_ACK_SYN'] = (时间戳, Seq, Ack, 0)
S_Tx_DATA_ALL = Seq+1 ## 服务端初始数据编号
PS = f"S初始数据编号 {S_Tx_DATA_ALL}"
else:
PS = '错误 ACK SYN'
else:
CRITICAL = f"【CRITICAL】开头的包不是SYN {File}"
Log.critical(CRITICAL)
print(CRITICAL)
break
else:
Log.error(f"【ERROR】未定义怎么处理的 FLAGE {FLAGE} 忽略")
PS = f"【ERROR】未定义怎么处理的 FLAGE {FLAGE} 忽略"
## 保存信息用于显示(TCP校验通过的包/忽略TCP校验的包)
L_DATA.append((编号,时间戳,SrcIP,DstIP,SrcPort,DstPort,Seq,Ack,FLAGE,TCP数据长度,HTTP_REQ,HTTP_SC,TCP_Window,PS))
else:
Log.error("【ERROR】TCP校验错误")
#print("【W】提取 TCP_Checksum", hex(TCP_Checksum))
D_HTTP['TCP_CS_ERR'].append(编号) # 记录TCP校验错误包的编号
elif IP_Protocol == 17:
Log.error("【ERROR】UDP PASS")
else:
Log.error("【ERROR】NOT TCP/UDP PASS")
else:
Log.error("【ERROR】NOT IPv4 Ethernet")
f.close() # 关闭 pcap 文件
return(D_HTTP, L_DATA)
## 以客户端视角(在客户端处抓包)或服务端视角(在服务端处抓包)分析每个请求(GET/POST),记录各过程耗时,返回列表
## 请求完成耗时 COMP
## 发完请求耗时 TX
## 处理请求耗时 SYS
## 传回结果耗时 RX
## 设置了一下可以反应问题的自定义响应码:
# 响应状态码 = 0 # 自定义初始值,如果后面解析不出,就响应码设置为0
# 响应状态码 = 1 # 自定义,两种方法都找不到响应信息,且发现了C发的RST信息
# 响应状态码 = 2 # 自定义,两种方法都找不到响应信息,且没有发现C发的RST信息,应该是真没有响应
# 响应状态码 = 3 # 自定义,请求方法解析不出,可能不是请求包,需要手动查原因
# 响应状态码 = xxx1 (正常响应状态码*10 + 1) # 自定义,表示C发起了RST,强制断开连接,抓到了响应HTTP,普通响应码增加1位1
# 响应状态码 = 5 # 请求包D_HTTP['C_HTTP_REQ']中有,但D_HTTP['D_C_ACK_DATA']中没有,请求方向错误,如服务端发起请求
# 响应状态码 = 6 # 自定义,两种方法都找不到响应信息,且找到服务端发起了FIN终止
## 直接从PCAP文件中获取的 GET/POST 请求信息,再分析请求的结果及用时信息,返回分析结果 L_HTTP_TIME
def PCAP_HTTP_SESSION_TIME(D_HTTP):
D_REQ = D_HTTP['C_HTTP_REQ']
L_HTTP_TIME = []
for K in D_REQ:
REQ_s = D_REQ[K][0]
REQ_seq = D_REQ[K][1]
REQ_ack = D_REQ[K][2]
REQ_len = D_REQ[K][3]
C发送请求时间戳 = REQ_s
## 设置初始值
响应状态码 = 0 # 如果后面解析不出,就响应码设置为0
CL计算期望S回应ACK = -1 # 根据请求找响应数据包用
CL = -1 # GET没有长度信息/POST提取失败(在一个请求包里找不到长度信息,可能请求数据太长被分包,且表示长度的不在第一个包里)
## 从请求头信息中获取请求类型,POST请求尝试获取请求数据长度
## REQ_ack 是同步存入 D_HTTP['C_HTTP_REQ'] 和 D_HTTP['C_REQ_Header'] 一个有另一个也一定有
Log.debug(f"C_REQ_Header {D_HTTP['C_REQ_Header'][REQ_ack]}")
请求类型 = D_HTTP['C_REQ_Header'][REQ_ack]['Request']
URL = ' URL ' + D_HTTP['C_REQ_Header'][REQ_ack]['URL'] # 提取URL显示
if 请求类型 == 'GET':
pass
elif 请求类型 == 'POST':
if 'Content-Length' in D_HTTP['C_REQ_Header'][REQ_ack]: # 可以在一个请求包里找到长度信息
CL = D_HTTP['C_REQ_Header'][REQ_ack]['Content-Length']
try:
CL计算期望S回应ACK = REQ_seq + REQ_len + int(CL)
except Exception as e:
Log.error(f" 【ERROR】REQ_seq + REQ_len + int(CL) {e}") # 稀有异常:有长度信息,但值非数字
else:
Log.error(f" 【ERROR】未定义怎么处理的请求 {请求类型}")
响应状态码 = 3 ## 自定义,请求方法解析不出,可能不是请求包,需要手动查原因
L_HTTP_TIME.append((REQ_ack, -1, 请求类型, 响应状态码, -1, 0, 0, 0, 0, 0))
continue
## 处理 C 发送的 GET/POST 包
Log.debug(f"C 发送 {请求类型} 请求 ACK={K}")
Log.debug(URL)
Log.debug(f" Content-Length = {CL}")
if REQ_ack in D_HTTP['D_C_ACK_DATA']:
C发送请求包列表 = D_HTTP['D_C_ACK_DATA'][REQ_ack]
## 检查一下有没有异常的数据包(乱序/编号有问题)
if 检查数据包编号(C发送请求包列表) == 1:
Log.debug(" 【WARNING】数据包编号有乱序")
else:
Log.debug(" 数据包编号正常(单包请求忽略检查)")
## 开始分析请求时间信息
REQ_DATA_LEN = 0 # 累计GET/POST数据总长度(字节)
## C发起的GET/POST包,是第一个含数据的包,直接用D_HTTP['C_HTTP_REQ']中的包时间
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_HTTP['D_C_ACK_DATA_RPT']:
for i in D_HTTP['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_HTTP['D_HTTP_INFO_SEQ']: ## D_HTTP['D_HTTP_INFO_SEQ'] 的KEY是HTTP首回应包的seq,值等于请求的ack
C_REQ提取S_ACK = D_HTTP['D_HTTP_INFO_SEQ'][REQ_ack][0][2]
else:
C_REQ提取S_ACK = -1
Log.debug(f" 期望 S 回应 ACK {C_REQ提取S_ACK}(请求ACK查响应SEQ) {CL计算期望S回应ACK}(Content-Length计算) {累计计算预估S响应ACK}(发数据累计预估)")
## 检查有没有响应数据
if C_REQ提取S_ACK in D_HTTP['D_S_ACK_DATA']: ## 初始值是负数,没有找到可用值时使用初始可以保证这个判断失败
Log.debug(f" 【I】使用(请求ACK查响应SEQ)找到的响应信息")
S响应ACK = C_REQ提取S_ACK
elif CL计算期望S回应ACK in D_HTTP['D_S_ACK_DATA']: ## 初始值是负数,没有找到可用值时使用初始可以保证这个判断失败
Log.debug(f" 【I】使用(Content-Length计算)找到的响应信息")
S响应ACK = CL计算期望S回应ACK
elif REQ_ack in D_HTTP['C_ACK_RST_AckKey']:
Log.debug(f" 【E】找不到响应数据,找到C主动终止了连接:发现ACK={REQ_ack} 在 D_HTTP['C_ACK_RST_AckKey']出现")
响应状态码 = 1 ## 自定义,两种方法都找不到响应信息,且发现了C发的RST信息
L_HTTP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, -1, C发完请求耗时, 0, 0, C重发数据包数量, 0))
continue
elif REQ_ack in D_HTTP['S_ACK_FIN_SeqKey']:
Log.debug(f" 【E】找不到响应数据,找到服务端发送FIN终止连接:{D_HTTP['S_ACK_FIN_SeqKey'][REQ_ack]}")
响应状态码 = 6 ## 自定义,两种方法都找不到响应信息,且找到服务端发起了FIN终止
L_HTTP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, -1, C发完请求耗时, 0, 0, C重发数据包数量, 0))
continue
else:
Log.debug(" 【E】两种方法都找不到响应信息,且没有发现C发的RST信息,应该是真没有响应")
响应状态码 = 2 ## 自定义,两种方法都找不到响应信息,且没有发现C发的RST信息,应该是真没有响应
L_HTTP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, -1, C发完请求耗时, 0, 0, C重发数据包数量, 0))
continue
## 2个方法中任意成功一个就继续分析
S发送响应数据包列表 = D_HTTP['D_S_ACK_DATA'][S响应ACK]
## 有响应响应信息会继续下面的分析
if S响应ACK in D_HTTP['HTTP_SC']:
响应状态码 = D_HTTP['HTTP_SC'][S响应ACK]
else:
Log.error(f" 【ERROR】找不到对请求的回应(使用自定义初始值SC=0)")
## 当客户端发起RST,服务器依据可能返回响应数据,为了区分出这种情况,最后再查一下这个,修改响应码标识
if S响应ACK in D_HTTP['C_ACK_RST_SeqKey']:
Log.debug(f" 【W】有响应的情况下C主动终止了连接:RST_Seq=S响应ACK={S响应ACK} 在 D_HTTP['C_ACK_RST_SeqKey']出现(设置自定义SC=响应状态码*10+1)")
响应状态码 = 响应状态码*10 + 1 # 自定义,表示C发起了RST,强制断开连接,普通响应码增加1位1
## 检查一下有没有异常的数据包(乱序/编号有问题)
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_HTTP['D_S_ACK_DATA_RPT']:
for i in D_HTTP['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_HTTP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, C完成请求耗时, C发完请求耗时, S处理请求耗时, S发完响应数据耗时, C重发数据包数量, S重发数据包数量))
else:
Log.critical(f"【CRITICAL】未找到C发送的{请求类型}包,请求包D_HTTP['C_HTTP_REQ']中有,但D_HTTP['D_C_ACK_DATA']中没有,包信息 {D_HTTP['C_HTTP_REQ'][REQ_ack]}")
Log.critical(f"【CRITICAL】非请求被当成请求/服务端对客户端发起请求/请求包是ACK.PSH.FIN标志归入FIN类,服务端先关闭了长连接")
响应状态码 = 5 ## 请求包D_HTTP['C_HTTP_REQ']中有,但D_HTTP['D_C_ACK_DATA']中没有,请求方向错误,如服务端发起请求
## 记录耗时(异常情况)
L_HTTP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, -1, 0, 0, 0, 0, 0))
## 查看重复的首个请求信息
if K in D_HTTP['C_HTTP_REQ_RPT']:
Log.warning(f"【WARNING】请求首包有重发 {D_HTTP['C_HTTP_REQ_RPT'][K]}")
Log.debug('')
return(L_HTTP_TIME)
## 请求耗时信息汇总 L_HTTP_TIME
def SHOW_HTTP_TIME(L_HTTP_TIME, 视角='S'):
if L_HTTP_TIME != []:
if 视角 == 'S':
Log.info("【请求耗时信息汇总(服务端抓包)单位秒】")
Log.info("%-9s %-9s %4s %3s %5s %7s %7s %7s %4s %4s" % ('请求(ack)','时间戳','类型','SC','COMP','C_req','SYS','S_http','C_RPT','S_RPT'))
else:
Log.info("【请求耗时信息汇总(客户端抓包)单位秒】")
Log.info("%-9s %-9s %4s %3s %5s %7s %7s %7s %4s %4s" % ('请求(ack)','时间戳','类型','SC','COMP','C_req','SYS_NET','S_http','C_RPT','S_RPT'))
for i in L_HTTP_TIME:
Log.info("%10d %.2f %4s %-4d %7.3f %7.3f %7.3f %7.3f %4d %4d" % i)
Log.info('')
## 打印Debug信息
def DEBUG_D_HTTP(D_HTTP):
Log.debug("【DEBUG D_HTTP 变量】")
Log.debug(f"CLIENT {D_HTTP['CLIENT']}")
Log.debug(f"SERVER {D_HTTP['SERVER']}")
Log.debug(f"C_MSS {D_HTTP['C_MSS']}")
Log.debug(f"C_WSOPT {D_HTTP['C_WSOPT']}")
Log.debug(f"S_MSS {D_HTTP['S_MSS']}")
Log.debug(f"S_WSOPT {D_HTTP['S_WSOPT']}")
Log.debug(f"C_SYN {D_HTTP['C_SYN']}")
Log.debug(f"S_ACK_SYN {D_HTTP['S_ACK_SYN']}")
Log.debug(f"C_ACK {D_HTTP['C_ACK']}")
Log.debug(f"S_ACK_FIN_SeqKey {D_HTTP['S_ACK_FIN_SeqKey']}")
Log.debug(f"C_ACK_FIN {D_HTTP['C_ACK_FIN']}")
Log.debug(f"S_ACK_RST {D_HTTP['S_ACK_RST']}")
Log.debug(f"C_ACK_RST {D_HTTP['C_ACK_RST']}")
Log.debug(f"HTTP_SC {D_HTTP['HTTP_SC']}") ## 每次请求结果的HTTP状态码
Log.debug(f"TCP_CS_ERR {D_HTTP['TCP_CS_ERR']}") ## TCP校验错误的包序号
Log.debug(f"C_REQ_Header {D_HTTP['C_GET_Header']}")
#Log.debug(f"S_HTTP_Header {D_HTTP['S_HTTP_Header']}")
Log.debug(f"D_C_ACK_DATA")
for k in D_HTTP['D_C_ACK_DATA']:
Log.debug(f" {k} {D_HTTP['D_C_ACK_DATA'][k]}")
Log.debug('')
Log.debug(f"D_S_ACK_DATA")
for k in D_HTTP['D_S_ACK_DATA']:
Log.debug(f" {k} {D_HTTP['D_S_ACK_DATA'][k]}")
Log.debug(f"C_HTTP_REQ {D_HTTP['C_HTTP_REQ']}")
Log.debug(f"C_HTTP_REQ_RPT {D_HTTP['C_HTTP_REQ_RPT']}")
Log.debug('')
'''
## 计算TCP建立时间
def TCP_SESSION_TIME(D_HTTP):
## 建立TCP时间(三次握手包时间)
## 断开TCP时间(以第一个 FIN 和 RST 为结束时间点)暂时不要
TCP建立连接耗时 = 0
## 第1步,C 发 SYN 给 S
if D_HTTP['C_SYN'] != ():
C发起TCP连接请求时间戳 = D_HTTP['C_SYN'][0]
Log.debug(f"C发起TCP连接请求时间戳 {C发起TCP连接请求时间戳}")
## 第2步,S 发 ACK SYN 给 C
if D_HTTP['S_ACK_SYN'] != ():
S发出TCP连接确认时间戳 = D_HTTP['S_ACK_SYN'][0]
Log.debug(f"S发出TCP连接确认时间戳 {S发出TCP连接确认时间戳}")
## 第3步,C 回应 S ACK
S_ACK_SYN_seq = D_HTTP['S_ACK_SYN'][1]
S_ACK_SYN_ack = D_HTTP['S_ACK_SYN'][2]
Key_seq = S_ACK_SYN_seq+1
if Key_seq in D_HTTP['D_C_ACK']:
for i in D_HTTP['D_C_ACK'][Key_seq]:
if i[3] == 0 and i[1] == S_ACK_SYN_ack:
D_HTTP['C_ACK'] = i
Log.debug(f"C发出TCP连接确认时间戳 {i[0]}")
break
TCP建立连接耗时 = D_HTTP['C_ACK'][0] - C发起TCP连接请求时间戳
Log.debug(f"TCP建立连接耗时 {TCP建立连接耗时}")
return(TCP建立连接耗时)
'''
## 解除TCP连接时间
def SHOW_TCP_断开(D_HTTP):
pass
## 是否有校验错误包
def DEBUG_TCP_校验错误包(D_HTTP):
if D_HTTP['TCP_CS_ERR'] == []:
print("TCP校验 全部正确 或 忽略校验")
else:
print(f"TCP校验 有错误包, 编号列表: {D_HTTP['TCP_CS_ERR']}")
print("可以选择设置忽略校验(校验错误的包可能可以正常使用)")
print('')
## 记录数据包信息
def DEBUG_PACK_INFO(L_DATA):
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-4s %3s %6s %s' % ('ID','TIME','SrcIP','DstIP','SPort','DPort','Seq','Ack','FLAGE','LEN','请求','SC', 'WIN(原值)', '备注说明'))
for i in L_DATA:
Log.debug('%3s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-4s %3s %6s %s' % 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)
## 整理 [(时间戳, Seq, Ack, Len, Win, 包编号), ] 为按包拼接顺序的列表
## 函数参数 LL 传入[]空列表
## 函数参数 L 传入需要整理的表列表
def 暴力拆分(LL,L):
if L == []:
return(LL)
LV = [L[0]]
LX = []
数量 = len(L)
SEQ = L[0][1]
LEN = L[0][3]
SEQ_NEXT = SEQ+LEN
for i in range(1, 数量):
if L[i][1] == SEQ_NEXT:
SEQ_NEXT += L[i][3] # LEN
LV.append(L[i])
else:
LX.append(L[i])
LL.append(LV)
return(暴力拆分(LL,LX))
## 合并暴力拆分后可以合并的部分
## 函数参数 LL 传入合并暴力拆分结果
def 暴力合并(LL):
LEN = len(LL)
STOP = 0 # 标记用于跳出最外的循环
for i in range(0, LEN):
NEXT = LL[i][-1][1] + LL[i][-1][3] # i 最后一个元素 Seq + Len
for j in range(0, LEN):
if LL[j][0][1] == NEXT: # j 第一个元素 Seq
for x in LL[j]: # LL[j] 的元素全部追加到 LL[i] 的后面
LL[i].append(x)
del(LL[j]) # 然后删除 LL[j]
STOP = 1
break # 跳出内层循环
if STOP == 1:
break # 跳出外层循环
if len(LL) == LEN: # 如果长度没有变化,说明已经不能继续合并
return(LL)
else: # 长度有变化,上一次操作是有合并的,再来一次看看能不能继续合并
R = 暴力合并(LL)
return(R)
def 二维列表排序数据包(L):
L_Seq = [i[1] for i in L] # 提取Seq值
L2P = set(L_Seq) # 用集合去重
P2L = list(L2P) # 转回没有重复的列表
P2L.sort() # 从小到大排序,这个列表作为判断位置的标尺
LEN = len(P2L) # 取顺序列表的长度
#print("P2L", P2L)
Lnull = [0 for i in range(0, LEN)] # 跟顺序列表一样长的全0列表
LL = [Lnull] # 全0列表作为二维列表的第一组
#print("LL", LL)
for i in L:
ID = P2L.index(i[1]) # 找出这个值应该放的位置
#print("ID", ID)
X = 0
for j in LL: # 遍历二维列表的每一行列表
if j[ID] == 0: # 这行的这个位置空着
j[ID] = i # 填入
X = 1 # 标记有空位,已经填入
break
if X == 0: # 每一行的这个位置都没有空
Lnew = [0 for i in range(0, len(P2L))] # 只能新建一个空行给这个值用
Lnew[ID] = i # 新行的这个位置填入
LL.append(Lnew) # 新行加入二维列表
#Log.debug(f"二维列表:列数={LEN} 行数={len(LL)}")
#Log.debug("查看值分布")
#for i in LL:
# Log.debug(f"{[0 if i==0 else 1 for i in i]}")
return(LL)
## 对[[1],[21,22],[31,32,33],[41,42]]生成全部组合
def 二维列表生成全部组合(L):
# 示例 L = [[1],[21,22],[31,32,33],[41,42]]
元素个数列表 = [len(i) for i in L] # 元素个数列表
Log.debug(f"元素个数列表 {元素个数列表}")
## 一行一种组合,组合数等于各种元素数量相乘
组合数量 = 1
for i in 元素个数列表:
组合数量 = 组合数量*i
Log.debug("组合数量 {组合数量}")
## 主列表有多个个子列表,就需要多少列
需要列数 = len(L)
Log.debug("需要列数 {需要列数}")
## 生成初始二维列表,行数=组合数量 列数=需要列数
LL = [[0 for j in range(需要列数)] for i in range(组合数量)]
for i in range(0,需要列数): # i=二维列表的列下标==源列表元素下标
子列表长度 = len(L[i])
需重复次数 = 组合数量//子列表长度
#print("需重复次数", 需重复次数, L[i])
for 重复行号 in range(0,需重复次数): #
if 需重复次数 == 组合数量: # 自身只有一个元素,需要排全部次数
LL[重复行号][i] = L[i][0]
else:
步长 = 重复行号*子列表长度
for j in range(0,子列表长度):
行号 = 步长 + j
#print(f"重复行号={重复行号} 步长={步长} 列i={i} j={j} 行号={行号} {L[i][j]}")
LL[行号][i] = L[i][j]
for i in LL:
Log.debug(f"{i}")
return(LL)
################################
## 解析数据包解码请求信息部分 ##
################################
## 遍历全部包,返回字典 Key=包编号 Value=b'' 对应数据部分
## File pcap格式文件
## D 字典 Key=Ack Value=[(时间戳,seq,ack,len,win,包编号),()]
## ACK 如果只要指定ACK的数据,就传入ACK
def FIND_HTTP_DATA(File, D, ACK):
f = open(File,'rb') ## 二进制方式读取pcap格式文件
PCAP_DATA = f.read(24) # 读取24字节pcap格式头,跳过
D_N_B = {} ## Key=包编号 Value=b'' # 通过包编号读取实际数据
## 整理出需要提取数据的编号
#print("D[ACK]", D[ACK])
LN = []
if ACK == 'ALL':
for i in D:
for j in D[i]:
LN.append(j[5])
else:
for j in D[ACK]:
LN.append(j[5])
LN.sort() ## 从小到大排序,方便一次遍历提取全部
N = 0 ## 记录LN的下标
编号 = 0 ## 记录pcap中包的编号
while 1:
编号 += 1
PacketHeader_Bytes = f.read(16) # 包头16字节
if not PacketHeader_Bytes: # 判断 PacketHeader_Bytes 是否为空(读完或者本身为空时)
break
#print(f'编号 {编号}')
时间戳, 抓取数据包长度, 实际数据包长度 = Packet_Header(PacketHeader_Bytes)
PacketData = f.read(抓取数据包长度) # 继续读取抓取数据包长度字节数内容
DstMAC, SrcMAC, FrameType = EthernetII_Header(PacketData[0:14])
if FrameType == b'\x08\x00': # 表示IPv4
## 解析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
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数据长度 = IP_Total_Length - IP部首字节长度 - TCP部首固定长度 - TCP选项长度
## 计算数据长度
## 最小60字节,不足会填充
剩余全部数据 = PacketData[54+TCP选项长度:]
剩余有效数据 = PacketData[54+TCP选项长度:54+TCP选项长度+TCP数据长度]
#print(f"编号={编号} N={N} L[N]={LN[N]}")
if 编号 == LN[N]:
#print("[FIND]")
#TCP_DATA(剩余有效数据)
D_N_B[编号] = 剩余有效数据
if 编号 == LN[-1]:
break
N+=1
f.close()
return(D_N_B)
## 按数据包的Seq信息强行组装数据(二进制数据,Seq已经顺序的情况)
## L # 要组装起来的数据包信息(自动整理)
## D_N_B # 包编号对应数据
def 根据SEQ整理组装数据为MBF(L, D_N_B):
## 检查数据包顺序
if 检查数据包编号(L) == 0:
Log.debug("包顺序正常")
else:
Log.debug("包顺序异常,先整理一下")
LL = 暴力拆分([],L)
RLL = 暴力合并(LL)
LEN = len(RLL)
Log.debug(f"合并后段数 {LEN}")
if LEN == 1:
Log.debug("整理后,包顺序正常")
else:
Log.debug("整理后,包顺序还是不正常,可能会解析失败")
for n in range(0, LEN):
Log.debug(f" RLL 第{n}小段 Len {len(RLL[n])} 总数据长 {RLL[n][-1][3] + RLL[n][-1][1] - RLL[n][0][1]} SN={RLL[n][0]} EN={RLL[n][-1]}")
L = RLL[0] ## 修改L为整理后第一段值,第一段应该是最长最完整的
MBF = BytesIO() # 内存文件,类似 f = open('new.data', 'ab')
LEN = 0 # 记录文件大小
for i in L:
ID = i[5]
R = MBF.write(D_N_B[ID])
LEN += R
Log.debug(f"拼接完大小 {LEN} 字节")
return(MBF)
def 查看请求数据(File, D_HTTP, ACK):
Log.info(f"======[ 解析HTTP内容 ACK = {ACK} ]======")
if ACK == 'ALL':
D_N_B = FIND_HTTP_DATA(File, D_HTTP['D_C_ACK_DATA'], ACK) # 获得编号对应二进制内容的字典
for i in D_HTTP['D_C_ACK_DATA']:
Log.info(f"======[ HTTP请求 ACK={i} ]======")
L = D_HTTP['D_C_ACK_DATA'][i] # 获取这个ACK对应的数据包编号信息列表
MBF = 根据SEQ整理组装数据为MBF(L, D_N_B)
MBF_HTTP_TEXT_C_ALL(MBF)
Log.info('')
else:
if ACK in D_HTTP['D_C_ACK_DATA']:
L = D_HTTP['D_C_ACK_DATA'][ACK]
elif ACK in D_HTTP['D_C_ACK_DATA_RPT']:
L = D_HTTP['D_C_ACK_DATA_RPT'][ACK]
else:
ERROR = f"找不到ACK={ACK}"
Log.error(ERROR)
print(ERROR)
exit()
D_N_B = FIND_HTTP_DATA(File, D_HTTP['D_C_ACK_DATA'], ACK)
MBF = 根据SEQ整理组装数据为MBF(L, D_N_B)
MBF_HTTP_TEXT_C_ALL(MBF)
def 查看响应数据(File, D_HTTP, ACK):
Log.info(f"======[ 解析HTTP内容 ACK = {ACK} ]======")
if ACK == 'ALL':
D_N_B = FIND_HTTP_DATA(File, D_HTTP['D_S_ACK_DATA'], ACK) # 获得编号对应二进制内容的字典
for i in D_HTTP['D_S_ACK_DATA']:
Log.info(f"======[ HTTP响应 ACK={i} ]======")
L = D_HTTP['D_S_ACK_DATA'][i] # 获取这个ACK对应的数据包编号信息列表
MBF = 根据SEQ整理组装数据为MBF(L, D_N_B)
MBF_HTTP_TEXT_S_ALL(MBF)
else:
if ACK in D_HTTP['D_S_ACK_DATA']:
L = D_HTTP['D_S_ACK_DATA'][ACK]
elif ACK in D_HTTP['D_S_ACK_DATA_RPT']:
L = D_HTTP['D_S_ACK_DATA_RPT'][ACK]
else:
ERROR = f"找不到ACK={ACK}"
Log.error(ERROR)
print(ERROR)
exit()
D_N_B = FIND_HTTP_DATA(File, D_HTTP['D_S_ACK_DATA'], ACK)
Log.info(f"======[ HTTP响应 ACK={ACK} ]======")
MBF = 根据SEQ整理组装数据为MBF(L, D_N_B)
MBF_HTTP_TEXT_S_ALL(MBF)
##################
## PCAP文件处理 ##
##################
## 读取1个PCAP文件,根据IP和端口信息分成小文件
def P1_单文件拆分(WorkDir, FileName):
os.chdir(WorkDir) # 切换到存放拆分后文件的目录
PF = set() # 集合,存放文件名
print(f"拆分PCAP文件{FileName}") # 根据源目的IP和端口分成多个小pcap文件
f = open(FileName, 'rb') # 以二进制方式读取pcap格式文件
PCAP_DATA = f.read(24) # 读取前24字节头信息,忽略
N = 0
while 1:
包头 = f.read(16)
if not 包头: # 判断 包头 是否为空(读完或者本身为空时 S 为空)
break
#N += 1
#if N == 14:
# break
#print("N", N)
PacketHeader = struct.unpack('IIII', 包头)
PacketData = f.read(PacketHeader[3])
# 以太部首
FrameType = PacketData[12:14] # 帧类型
# IP数据报头
if FrameType == b'\x08\x00': # IP
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("其他包忽略")
## 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]
## CWR ECN URG ACK PSH RST SYN FIN
TCP_Flags_B_int = struct.unpack('B', PacketData[47:48])[0]
#CWR = TCP_Flags_B_int & 0b10000000 # 128
#ECN = TCP_Flags_B_int & 0b01000000 # 63
#URG = TCP_Flags_B_int & 0b00100000 # 32
ACK = TCP_Flags_B_int & 0b00010000 # 16
#PSH = TCP_Flags_B_int & 0b00001000 # 8
RST = TCP_Flags_B_int & 0b00000100 # 4
SYN = TCP_Flags_B_int & 0b00000010 # 2
FIN = TCP_Flags_B_int & 0b00000001 # 1
## 每一次连接生成2个文件名
SRC = SrcIP+'_'+ str(SrcPort)
DST = DstIP+'_'+ str(DstPort)
F_NAME_SD = SRC+'-'+DST+'.pcap' # 以当前包的源到目的生成一个文件
F_NAME_DS = DST+'-'+SRC+'.pcap' # 以当前包的目的到源生成一个文件
#F_NAME_SD = str(SrcPort)+'-'+str(DstPort)+'.pcap' # 测试用,简单点
#F_NAME_DS = str(DstPort)+'-'+str(SrcPort)+'.pcap' # 测试用,简单点
## 分类另存为小文件
if F_NAME_SD in PF:
#print(" 归属", F_NAME_SD)
fp = open(F_NAME_SD, 'ba')
fp.write(包头)
fp.write(PacketData)
fp.close()
elif F_NAME_DS in PF: # 文件名要反的才能在字典里找到,说明这个是服务端发的包
#print(" 归属", F_NAME_DS)
fp = open(F_NAME_DS, 'ba')
fp.write(包头)
fp.write(PacketData)
fp.close()
else:
#print(" 没有归属")
if SYN == 2 and ACK != 16: # 只从SYN包开始保存,去除ACK SYN
#print(" 新建", F_NAME_SD)
fp = open(F_NAME_SD, 'ba') # 新建一个文件对象,名字用客户端到服务端方向
fp.write(PCAP_DATA) # PCAP文件头
fp.write(包头)
fp.write(PacketData)
fp.close()
PF.add(F_NAME_SD)
else:
#print(" 忽略")
pass
else:
print("非TCP", IP_Protocol)
f.close()
print(f"【P1】完成,生成{len(PF)}个文件")
## 读取多个PCAP文件(文件名是时间戳),根据IP和端口信息分成小文件
def P1_多时间戳文件名拆分(WorkDirName, DIR):
os.chdir(WorkDirName) ## 切换到存放拆分后文件的目录
PF = set() # 集合,存放文件名
## tcpdump -i eth0 'tcp and host 192.168.1.1' -s0 -G 60 -w %s ## 抓192.168.1.1的tcp包,每隔60秒保存为一个文件,文件名用时间戳
## 纯时间戳作为文件名,
L_FileName = os.listdir(DIR) # 目录内内容列表
L_FileName_int = []
for i in L_FileName:
try:
INT = int(i)
except Exception as e:
print(e)
else:
L_FileName_int.append(INT)
L_FileName_int.sort()
文件数量 = len(L_FileName_int)
计数 = 1
for F in L_FileName_int:
FileName = DIR+str(F)
print(f"拆分PCAP文件:{FileName} 进度:{计数}/{文件数量}") ## 根据源目的IP和端口分成多个小pcap文件
f = open(FileName, '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', 包头)
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("其他包忽略")
## 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]
## CWR ECN URG ACK PSH RST SYN FIN
TCP_Flags_B_int = struct.unpack('B', PacketData[47:48])[0]
#CWR = TCP_Flags_B_int & 0b10000000 # 128
#ECN = TCP_Flags_B_int & 0b01000000 # 63
#URG = TCP_Flags_B_int & 0b00100000 # 32
ACK = TCP_Flags_B_int & 0b00010000 # 16
#PSH = TCP_Flags_B_int & 0b00001000 # 8
RST = TCP_Flags_B_int & 0b00000100 # 4
SYN = TCP_Flags_B_int & 0b00000010 # 2
FIN = TCP_Flags_B_int & 0b00000001 # 1
## 每一次连接生成2个文件名
SRC = SrcIP+'_'+ str(SrcPort)
DST = DstIP+'_'+ str(DstPort)
F_NAME_SD = SRC+'-'+DST+'.pcap' # 以当前包的源到目的生成一个文件
F_NAME_DS = DST+'-'+SRC+'.pcap' # 以当前包的目的到源生成一个文件
#F_NAME_SD = str(SrcPort)+'-'+str(DstPort)+'.pcap' # 测试用,简单点
#F_NAME_DS = str(DstPort)+'-'+str(SrcPort)+'.pcap' # 测试用,简单点
## 分类另存为小文件
if F_NAME_SD in PF:
#print(" 归属", F_NAME_SD)
fp = open(F_NAME_SD, 'ba')
fp.write(包头)
fp.write(PacketData)
fp.close()
elif F_NAME_DS in PF: # 文件名要反的才能在字典里找到,说明这个是服务端发的包
#print(" 归属", F_NAME_DS)
fp = open(F_NAME_DS, 'ba')
fp.write(包头)
fp.write(PacketData)
fp.close()
else:
#print(" 没有归属")
if SYN == 2 and ACK != 16: # 只从SYN包开始保存,去除ACK SYN
#print(" 新建", F_NAME_SD)
fp = open(F_NAME_SD, 'ba') # 新建一个文件对象,名字用客户端到服务端方向
fp.write(PCAP_DATA)
fp.write(包头)
fp.write(PacketData)
fp.close()
PF.add(F_NAME_SD)
else:
#print(" 忽略")
pass
else:
print("IP_Protocol", IP_Protocol)
f.close()
print(f"【P1】Done {len(PF)}")
################################
## 把PCAP包解析成文本信息格式 ##
################################
## 'ID','TIME','SrcIP','DstIP','SPort','DPort','Seq','Ack','FLAGE','LEN','请求','SC','WIN','VID','SACK_INFO'
def P2S_Pcap_Header(BytesData):
文件头 = struct.unpack('IHHIIII', BytesData)
## 解析包头(16字节)
def P2S_Packet_Header(BytesData):
PacketHeader = struct.unpack('IIII', BytesData)
时间戳 = PacketHeader[0]
微秒 = PacketHeader[1]
抓取数据包长度 = PacketHeader[2] # 所抓获的数据包保存在pcap文件中的实际长度,以字节为单位。
实际数据包长度 = PacketHeader[3] # 所抓获的数据包的真实长度,如果文件中保存不是完整的数据包,那么这个值可能要比前面的数据包长度的值大。
时间戳_float = 时间戳 + 微秒/1000000
return(时间戳_float, 抓取数据包长度)
## 解析帧头(14字节)
## FrameType = b'\x81\x00' VLAN
## FrameType = b'\x08\x00' IP
def P2S_EthernetII_Header(BytesData):
FrameType = BytesData[12:14] # 帧类型
return(FrameType)
## 解析IP头(20字节)
def P2S_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
SrcIP_Bytes = BytesData[12:16] # 源IP地址
DstIP_Bytes = BytesData[16:20] # 目的IP地址
SrcIP = socket.inet_ntoa(SrcIP_Bytes)
DstIP = socket.inet_ntoa(DstIP_Bytes)
return(IP_Version,IP_Flags,IP_Header_Length*4,IP_Total_Length,SrcIP,DstIP)
## 解析TCP头(20字节)
def P2S_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)
TCP_Data_Offset = TCP_H >> 12
TCP_Flags = TCP_H & 0b0000000000111111
## 根据TCP部首长度计算是否有TCP可选字段
TCP部首实际长度 = TCP_Data_Offset*4
TCP部首固定长度 = 20
TCP选项长度 = TCP部首实际长度 - TCP部首固定长度
return(TCP_Source_Port, TCP_Destination_Port, TCP_Sequence_Number, TCP_Acknowledgment_Number, TCP_Flags, TCP_Window, TCP选项长度)
## 解析TCP头中Options(0字节或4字节的倍数)
def P2S_TCP_Options(BytesData):
## 格式 Kind/Type(1 Byte) + Length(1 Byte) + Value(X Bytes)
## EOL 和 NOP Option 只有 Kind/Type(1 Byte)
L_SACK_INFO = []
N = 0 # 记录已经读到的位置
while N < len(BytesData):
# 读取首字节判断 Kind/Type(1 Byte)
Kind = BytesData[N:N+1]
if Kind == b'\x02':
## 最大Segment长度(MSS)
Length = 4 ## 固定为4
N += Length ## 更新N为实际已经读取了的字数
elif Kind == b'\x01':
## 补位填充
N +=1
elif Kind == b'\x00':
## 选项列表结束
N +=1
elif Kind == b'\x03':
## 窗口扩大系数
Length = 3 ## 固定为3
N += Length
elif Kind == b'\x04':
## 支持SACK
Length = 2 ## 固定为2
N += Length
elif Kind == b'\x05':
## 乱序/丢包数据
## 长度不固定,存在后续1个字节中
Length = struct.unpack('B', BytesData[N+1:N+2])[0]
Value = BytesData[N+2:N+Length]
for i in range(0, len(Value)-2, 8):
T = struct.unpack('!II', Value[i:i+8])
L_SACK_INFO.append(T)
N += Length
elif Kind == b'\x08':
## Timestamps(随时间单调递增的值)
Length = 10 ## 固定为10
N += Length
elif Kind == b'\x13': # 19
## MD5认证
Length = 18 ## 固定为18
N += Length
elif Kind == b'\x1c': # 28
## 超过一定闲置时间后拆除连接
Length = 4 ## 固定为4
N += Length
elif Kind == b'\x1d': # 29
## 认证(可选用各种算法)
## 长度不固定,存在后续1个字节中
Length = struct.unpack('B', BytesData[N+1:N+2])[0]
N += Length
elif Kind == b'\xfe': # 254
## 科研实验保留
## 长度不固定,存在后续1个字节中
Length = struct.unpack('B', BytesData[N+1:N+2])[0]
N += Length
else:
break
return(L_SACK_INFO)
## 解析HTTP请求类型(传入前5字节内容尝试判断)
def P2S_HTTP_Request(BytesData):
if BytesData == b'\x50\x4f\x53\x54\x20': # POST空格
return('POST')
elif BytesData == b'\x48\x54\x54\x50\x2f': # HTTP/
return('HTTP')
elif BytesData[0:4] == b'\x47\x45\x54\x20': # GET空格
return('GET')
else:
return('DATA') # 其他请求和其他情况全当数据
## 解析VLAN信息
def P2S_VLAN_INFO(BytesData):
TCI = struct.unpack('!H', BytesData)[0]
VID = TCI & 0b111111111111
return(VID)
## 返回文本信息格式列表
def P2S(File):
L_PACK_INFO = []
f = open(File,'rb')
PCAP_DATA = f.read(24) # 读取24字节
P2S_Pcap_Header(PCAP_DATA)
编号 = 0
while 1:
DATA = f.read(16) # packet数据包头
if not DATA:
break
编号 += 1
#if 编号 == 6:
# break
L_SACK_INFO = []
VID = 0
HTTP_REQ = '----' # HTTP/GET/POST
HTTP_SC = 0 # HTTP状态码(HTTP Status Code)
时间戳_float, 抓取数据包长度 = P2S_Packet_Header(DATA)
PacketData = f.read(抓取数据包长度)
FrameType = P2S_EthernetII_Header(PacketData[0:14])
if FrameType == b'\x08\x00':
IP_Version,IP_Flags,IP部首字节长度,IP_Total_Length,SrcIP,DstIP = P2S_IPv4_Header(PacketData[14:34])
SrcPort, DstPort, Seq, Ack, TCP_Flags, TCP_Window, TCP选项长度 = P2S_TCP_Header(PacketData[34:54])
if TCP选项长度 > 0:
L_SACK_INFO = P2S_TCP_Options(PacketData[54:54+TCP选项长度])
TCP数据长度 = IP_Total_Length - IP部首字节长度 - 20 - TCP选项长度
剩余有效数据 = PacketData[54+TCP选项长度:54+TCP选项长度+TCP数据长度]
if TCP数据长度 > 4: # >4字节才有可能是HTTP数据
HTTP_REQ = P2S_HTTP_Request(剩余有效数据[0:5]) # 先提取5字节看看是不是HTTP类型
if HTTP_REQ == 'HTTP': # 是HTTP响应的再提取响应码试试
HTTP_STATUS = 剩余有效数据[9:12]
try:
HTTP_SC = int(HTTP_STATUS)
except Exception as e:
HTTP_REQ = '-H--' # 重置HTTP_REQ,可能是重发的请求包从 HTTP/ 开始的,掉了请求行的 GET/POST 和 URL 这前两部分
ERROR = f"稀有异常 HTTP_SC = int(HTTP_STATUS) ERROR PCAP文件:{File} 编号:{编号} 可能是重发的请求包从 HTTP/ 开始的,掉了请求行的 GET/POST 和 URL 这前两部分"
Log.error(ERROR)
print(ERROR)
elif FrameType == b'\x81\x00':
VID = P2S_VLAN_INFO(PacketData[14:16])
if PacketData[16:18] == b'\x08\x00': ## 再往后读2字节,如果是b'\x08\x00'就是IPv4包
IP_Version,IP_Flags,IP部首字节长度,IP_Total_Length,SrcIP,DstIP = P2S_IPv4_Header(PacketData[18:38])
SrcPort, DstPort, Seq, Ack, TCP_Flags, TCP_Window, TCP选项长度 = P2S_TCP_Header(PacketData[38:58])
if TCP选项长度 > 0:
L_SACK_INFO = P2S_TCP_Options(PacketData[58:58+TCP选项长度])
TCP数据长度 = IP_Total_Length - IP部首字节长度 - 20 - TCP选项长度
剩余有效数据 = PacketData[58+TCP选项长度:58+TCP选项长度+TCP数据长度]
if TCP数据长度 > 4:
HTTP_REQ = P2S_HTTP_Request(剩余有效数据[0:5])
if HTTP_REQ == 'HTTP':
HTTP_STATUS = 剩余有效数据[9:12]
try:
HTTP_SC = int(HTTP_STATUS)
except Exception as e:
HTTP_REQ = '-H--'
ERROR = f"稀有异常 HTTP_SC = int(HTTP_STATUS) ERROR PCAP文件:{File} 编号:{编号} 可能是重发的请求包从 HTTP/ 开始的,掉了请求行的 GET/POST 和 URL 这前两部分"
Log.error(ERROR)
print(ERROR)
else:
continue
## FLAGE
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 = 'UN'
## 显示
PACK_INFO = (编号,时间戳_float,SrcIP,DstIP,SrcPort,DstPort,Seq,Ack,FLAGE,TCP数据长度,HTTP_REQ,HTTP_SC,TCP_Window,VID,str(L_SACK_INFO))
#Log.info('%7s %-17s %-15s %-15s %5s %5s %10s %10s %-7s %4s %-2s %-3s %6s %4s %-4s' % PACK_INFO)
L_PACK_INFO.append(PACK_INFO)
f.close()
return(L_PACK_INFO)
#######################
## SQLite3数据库操作 ##
#######################
import sqlite3
## 打开数据库/创建数据库,返回连接对象
def DEV_SQLite3_OPEN(DB_File):
try:
conn = sqlite3.connect(DB_File) # 尝试打开数据库文件
except Exception as e:
E = '打开数据库文件失败 ' + str(e)
return(1, E) # 返回错误代码1和失败原因
else:
return(0, conn)
def DEF_SQL_执行多条_遇到错误终止(数据库连接对象, L_SQL_CMD):
try:
游标对象 = 数据库连接对象.cursor() # 创建一个游标
except Exception as e:
ERROR = '创建游标失败' + str(e)
print(ERROR)
return(1, ERROR)
else:
N = 0
for SQL_CMD in L_SQL_CMD:
try:
游标对象.execute(SQL_CMD)
except Exception as e:
失败信息 = 'SQL语句 ' + SQL_CMD + ' 执行失败 ' + str(e)
break
else:
N +=1
数据库连接对象.commit() # 提交更改
游标对象.close()
return(0, N)
## 执行SQL查询语句,返回执行状态和执行结果(数据列表)
def DEF_SQL_SELECT(数据库连接对象, SQL_CMD):
try:
游标对象 = 数据库连接对象.cursor() # 创建一个游标
except Exception as e:
ERROR = '创建游标失败' + str(e)
return(1, ERROR)
else:
try:
游标对象.execute(SQL_CMD)
except Exception as e:
游标对象.close()
ERROR = f'数据库 执行SQL语句 {SQL_CMD} 失败 {e}'
return(1, ERROR)
else:
全部记录 = 游标对象.fetchall()
游标对象.close()
return(0, 全部记录)
###############################
## 从SQLite3数据库中提取信息 ##
###############################
## 从数据库中获取的包信息列表,处理后返回1个字典和一个列表
## 传入 L_PACK_INFO # 从数据库中查出的相关包信息 [(时间戳, SrcPort, Seq, Ack, FLAGE, TCP数据长度, HTTP_请求/响应标识, HTTP_响应代码), ]
## 返回 D_HTTP # 整理后的包信息字典
## C_IP, C_Port, S_IP, S_Port 这些是为了确定数据包是客户端发的还是服务端发的(同个TCP会话中这些是固定不变的,可以只根据其中任意一个做判断)
def SelectDB_IPv4_TCP_HTTP_SESSION(L_PACK_INFO, C_IP, C_Port, S_IP, S_Port):
## 记录HTTP各过程信息字典 D_HTTP
D_HTTP = {} ## 最外层的字典
D_HTTP['CLIENT'] = (C_IP, C_Port) ## (HOST, PORT)
D_HTTP['SERVER'] = (S_IP, S_Port) ## (HOST, PORT)
D_HTTP['C_SYN'] = () ## (时间戳, seq, ack), # 客户端发起连接
D_HTTP['S_ACK_SYN'] = () ## (时间戳, seq, ack), # 服务端确认连接
D_HTTP['C_ACK'] = () ## (时间戳, seq, ack), # 客户端确认连接
D_HTTP['S_ACK_FIN_SeqKey'] = {} ## 服务端发起的终止信息,以Seq为KEY,因为Seq=对应请求的Ack
D_HTTP['C_ACK_FIN'] = () ## C已经确认S接收完全部数据的信息
D_HTTP['S_ACK_RST'] = {} ## 服务端强制断开TCP连接
D_HTTP['C_ACK_RST_AckKey'] = {} ## 客户端强制断开TCP连接
D_HTTP['C_ACK_RST_SeqKey'] = {} ## 客户端强制断开TCP连接
D_HTTP['C_HTTP_REQ'] = {} ## TCP数据中开头是GET/POST的包(客户端发起的请求第一个数据包)
D_HTTP['C_HTTP_REQ_RPT'] = {} ## 存放重复的第一个请求数据包(有可能后面发的重复请求才是正确的)
D_HTTP['HTTP_SC'] = {} ## {ACK:[]}, # 每个请求的HTTP状态码
D_HTTP['C_REQ_Header'] = {} ## 记录客户端GET/POST请求行和请求头信息 Ack:{'':''}
D_HTTP['D_HTTP_INFO_SEQ'] = {} # 存放HTTP响应的第一个包信息,用于在找不到响应时候在这里暴力搜索试试,以Seq为key(HTTP回应的第一个包的seq=请求的ack)(请求的ack是服务器发是数据断点位置,回应就是从这个断点开始发包)
D_HTTP['D_C_KEEP'] = [] # 客户端发的keep-alive包
D_HTTP['D_S_KEEP'] = [] # 服务端回的keep-alive包
D_HTTP['D_C_ACK_DATA_RPT'] = {} # C重发的数据包
D_HTTP['D_S_ACK_DATA_RPT'] = {} # S重发的数据包
D_HTTP['D_S_ACK_DATA'] = {} # S 发送的ACK及数据
D_HTTP['D_C_ACK_DATA'] = {} # C 发送的ACK及数据
C_Tx_DATA_ALL = 0 # C已发数据编号,已发数据累计(不含重发的数据)遇到 SYN 包时设置
S_Tx_DATA_ALL = 0 # S已发数据编号,已发数据累计(不含重发的数据)遇到 SYN ACK 包时设置
P_C_Tx_Seq_Ack = set() # C发的含数据的ACK包的(Seq,Ack)信息集合,遇到重复说明是重发的包,在S处抓包这个就是 L_C_Rx_Info 接收到C发数据,重复说明重收,接不到对方丢的包的
P_S_Tx_Seq_Ack = set() # S发的含数据的ACK包的(Seq,Ack)信息集合,遇到重复说明是重发的包,在C处抓包这个就是 L_S_Rx_Info 接收到S发数据,重复说明重收,接不到对方丢的包的
KEEP_ALIVE = () # 当发现C发的是keep包时,存下 (包Ack, C发送总数据量) 用于识别出服务器的回应包
for PACK_INFO in L_PACK_INFO:
时间戳 = PACK_INFO[0]
SrcIP = PACK_INFO[1]
DstIP = PACK_INFO[2]
SrcPort = PACK_INFO[3]
DstPort = PACK_INFO[4]
Seq = PACK_INFO[5]
Ack = PACK_INFO[6]
FLAGE = PACK_INFO[7]
TCP数据长度 = PACK_INFO[8]
HTTP_REQ = PACK_INFO[9]
HTTP_SC = PACK_INFO[10]
## 分类缓存数据包步骤1:数据的包里关键字 GET/POST/HTTP
## GET 请求信息保存到字典 D_HTTP['C_HTTP_REQ'] 以包 Ack 做字典KEY
## POST 请求信息保存到字典 D_HTTP['C_HTTP_REQ'] 以包 Ack 做字典KEY
## HTTP 回应信息(第一个含内容的回应包)保存到字典 D_HTTP['HTTP_SC'] 以包 Ack 做字典KEY(Ack=客户端上个请求数据发完后的数据编号)
if HTTP_REQ in ('POST','GET'):
## 记录GET信息
D_HTTP_Header = {}
D_HTTP_Header['Request'] = HTTP_REQ # 记录请求类型
if Ack not in D_HTTP['C_REQ_Header']: # 只保留一个
D_HTTP['C_REQ_Header'][Ack] = D_HTTP_Header
## 不重复的POST/GET请求包信息保存到 C_HTTP_REQ 字典 Key=Ack Value=(包信息)
## 重复的都保存到 C_HTTP_REQ_RPT 字典 Key=Ack Value=[包信息,]
if Ack not in D_HTTP['C_HTTP_REQ']:
D_HTTP['C_HTTP_REQ'][Ack] = (时间戳, Seq, Ack, TCP数据长度)
else:
if Ack in D_HTTP['C_HTTP_REQ_RPT']:
D_HTTP['C_HTTP_REQ_RPT'][Ack].append((时间戳, Seq, Ack, TCP数据长度))
else:
D_HTTP['C_HTTP_REQ_RPT'][Ack] = [(时间戳, Seq, Ack, TCP数据长度)]
elif HTTP_REQ == 'HTTP': # HTTP回复第一个包信息
if Seq in D_HTTP['D_HTTP_INFO_SEQ']:
D_HTTP['D_HTTP_INFO_SEQ'][Seq].append((时间戳, Seq, Ack, TCP数据长度))
else:
D_HTTP['D_HTTP_INFO_SEQ'][Seq] = [(时间戳, Seq, Ack, TCP数据长度)]
## 记录HTTP响应码,重复回应只保留第一个
if Ack in D_HTTP['HTTP_SC']:
Log.warning("【WARNING】有重复HTTP回应,只保留第一个")
else:
D_HTTP['HTTP_SC'][Ack] = HTTP_SC # 保存http状态码
## 根据FLAGE分类缓存数据包
if FLAGE in ('ACK', 'ACK PSH'):
## 先区分是哪端发的ACK
if SrcPort == D_HTTP['CLIENT'][1]:
## 客户端发ACK
## Seq = C发的这个包的数据编号(累计值,不含当次数据,不含重发)
## Ack = C确认已经接到S数据的编号(丢包/乱序信息放在TCP选项SACK中发送)
## 判断是否有数据
if TCP数据长度 == 0:
pass
elif TCP数据长度 == 1 and Seq == C_Tx_DATA_ALL -1:
## 数据长度为1,Seq(自发送累计) 主动变小1的,应该就是C发的keep-alive包
Log.debug(" 【keep-alive】保持连接包,不累计大小")
if KEEP_ALIVE != ():
KEEP_ALIVE = (Ack, C_Tx_DATA_ALL) # 如果是第一次遇到,就记录一下,用于判断出服务端对应的回应包
D_HTTP['D_C_KEEP'].append((时间戳, Seq, Ack, TCP数据长度)) ## 保存客户端发的keep-alive包
else:
## 不重复的保存到 D_C_ACK_DATA 重发的保存到 D_C_ACK_DATA_RPT
## 已经避开了纯消息包和保持连接的包
if (Seq,Ack) not in P_C_Tx_Seq_Ack:
P_C_Tx_Seq_Ack.add((Seq,Ack))
if Ack in D_HTTP['D_C_ACK_DATA']:
D_HTTP['D_C_ACK_DATA'][Ack].append((时间戳, Seq, Ack, TCP数据长度))
else:
D_HTTP['D_C_ACK_DATA'][Ack] = [(时间戳, Seq, Ack, TCP数据长度)]
else:
if Ack in D_HTTP['D_C_ACK_DATA_RPT']:
D_HTTP['D_C_ACK_DATA_RPT'][Ack].append((时间戳, Seq, Ack, TCP数据长度))
else:
D_HTTP['D_C_ACK_DATA_RPT'][Ack] = [(时间戳, Seq, Ack, TCP数据长度)]
else:
## 服务端发ACK
## Seq = S自己已经发数据的累计(不含本次的数据量,不累计重发数据)
## Ack = S已经接到了C发的多少数据(S告诉C端,S是接到C多少数据后的回应,在HTTP里这个可以用于区别这个回应是针对哪个请求的)
if TCP数据长度 == 0:
if (Seq, Ack) == KEEP_ALIVE:
Log.debug(f" 【KEEP】和标记 KEEP_ALIVE {KEEP_ALIVE} 相同,C已经接到过这个包")
D_HTTP['D_S_KEEP'].append((时间戳, Seq, Ack, TCP数据长度)) ## 保存服务端回应的keep-alive包
else:
## HTTP协议中,一个tcp会话中一方在发数据的时间一方只能接和回应消息,不能回应数据
## 不重复的保存到 D_S_ACK_DATA 重发的保存到 D_S_ACK_DATA_RPT
if (Seq,Ack) not in P_S_Tx_Seq_Ack:
P_S_Tx_Seq_Ack.add((Seq,Ack))
if Ack in D_HTTP['D_S_ACK_DATA']:
D_HTTP['D_S_ACK_DATA'][Ack].append((时间戳, Seq, Ack, TCP数据长度))
else:
D_HTTP['D_S_ACK_DATA'][Ack] = [(时间戳, Seq, Ack, TCP数据长度)]
else:
if Ack in D_HTTP['D_S_ACK_DATA_RPT']:
D_HTTP['D_S_ACK_DATA_RPT'][Ack].append((时间戳, Seq, Ack, TCP数据长度))
else:
D_HTTP['D_S_ACK_DATA_RPT'][Ack] = [(时间戳, Seq, Ack, TCP数据长度)]
elif FLAGE in ('ACK FIN', 'A.P.F'): ## 终止连接
if DstPort == D_HTTP['CLIENT'][1]: ## 是 S 发起的 ACK FIN
if Seq in D_HTTP['S_ACK_FIN_SeqKey']:
D_HTTP['S_ACK_FIN_SeqKey'][Seq].append((时间戳, Seq, Ack, TCP数据长度))
else:
D_HTTP['S_ACK_FIN_SeqKey'][Seq] = [(时间戳, Seq, Ack, TCP数据长度)]
else: ## 是 C 发起的 ACK FIN
D_HTTP['C_ACK_FIN'] = (时间戳, Seq, Ack, TCP数据长度)
elif FLAGE in ('ACK RST', 'RST', 'A.P.R'): ## 强制断开连接
if DstPort == D_HTTP['CLIENT'][1]:
if Ack not in D_HTTP['S_ACK_RST']:
D_HTTP['S_ACK_RST'][Ack] = (时间戳, Seq, Ack, TCP数据长度) # 服务端强制终止连接包的ACK,忽略重复信息
else:
## C终止自己的请求 C_ACK_RST_AckKey 以Ack为KEY,给没有数据回应情况用请求Ack匹配RST的ACK
if Ack not in D_HTTP['C_ACK_RST_AckKey']:
D_HTTP['C_ACK_RST_AckKey'][Ack] = (时间戳, Seq, Ack, TCP数据长度) # 客户端强制终止连接包的Ack,忽略重复信息
## C终止自己的请求 C_ACK_RST_SeqKey 以Seq为KEY,期望S响应的Ack=这个Seq,给有回应的匹配用
if Seq not in D_HTTP['C_ACK_RST_SeqKey']:
D_HTTP['C_ACK_RST_SeqKey'][Seq] = (时间戳, Seq, Ack, TCP数据长度) # 客户端强制终止连接包的Seq,忽略重复信息
elif FLAGE == 'SYN':
D_HTTP['CLIENT'] = (SrcIP,SrcPort)
D_HTTP['SERVER'] = (DstIP,DstPort)
D_HTTP['C_SYN'] = (时间戳, Seq, Ack, 0)
C_Tx_DATA_ALL = Seq+1 ## 客户端初始数据编号
#print("客户端初始数据编号 C_Tx_DATA_ALL", C_Tx_DATA_ALL)
elif FLAGE == 'ACK SYN': ## 回应SYN请求,是本次连接的服务端,Seq随机,Ack为发起方Seq+1
if D_HTTP['C_SYN'] != ():
if Ack == D_HTTP['C_SYN'][1] + 1:
D_HTTP['S_ACK_SYN'] = (时间戳, Seq, Ack, 0)
S_Tx_DATA_ALL = Seq+1 ## 服务端初始数据编号
#print("服务端初始数据编号 S_Tx_DATA_ALL", S_Tx_DATA_ALL)
else:
Log.error("【ERROR】错误 ACK SYN")
else:
CRITICAL = f"【CRITICAL】开头的包不是SYN {File}"
Log.critical(CRITICAL)
print(CRITICAL)
break
else:
Log.error(f"【ERROR】未定义怎么处理的 FLAGE {FLAGE} 忽略")
return(D_HTTP)
## 从数据库中获取的 GET/POST 请求信息,再分析请求的结果及用时信息,返回分析结果 L_HTTP_TIME
def SelectDB_HTTP_SESSION_TIME(D_HTTP):
D_REQ = D_HTTP['C_HTTP_REQ']
L_HTTP_TIME = []
for K in D_REQ:
REQ_s = D_REQ[K][0]
REQ_seq = D_REQ[K][1]
REQ_ack = D_REQ[K][2]
REQ_len = D_REQ[K][3]
C发送请求时间戳 = REQ_s
## 设置初始值
响应状态码 = 0 # 如果后面解析不出,就响应码设置为0
## 从请求头信息中获取请求类型,POST请求尝试获取请求数据长度
## REQ_ack 是同步存入 D_HTTP['C_HTTP_REQ'] 和 D_HTTP['C_REQ_Header'] 一个有另一个也一定有
Log.debug(f"C_REQ_Header {D_HTTP['C_REQ_Header'][REQ_ack]}")
请求类型 = D_HTTP['C_REQ_Header'][REQ_ack]['Request']
## 处理 C 发送的 GET/POST 包
Log.debug(f"C 发送 {请求类型} 请求 ACK={K}")
if REQ_ack in D_HTTP['D_C_ACK_DATA']:
C发送请求包列表 = D_HTTP['D_C_ACK_DATA'][REQ_ack]
## 检查一下有没有异常的数据包(乱序/编号有问题)
if 检查数据包编号(C发送请求包列表) == 1:
Log.debug(" 【WARNING】数据包编号有乱序")
else:
Log.debug(" 数据包编号正常(单包请求忽略检查)")
## 开始分析请求时间信息
REQ_DATA_LEN = 0 # 累计GET/POST数据总长度(字节)
## C发起的GET/POST包,是第一个含数据的包,直接用D_HTTP['C_HTTP_REQ']中的包时间
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_HTTP['D_C_ACK_DATA_RPT']:
for i in D_HTTP['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_HTTP['D_HTTP_INFO_SEQ']: ## D_HTTP['D_HTTP_INFO_SEQ'] 的KEY是HTTP首回应包的seq,值等于请求的ack
C_REQ提取S_ACK = D_HTTP['D_HTTP_INFO_SEQ'][REQ_ack][0][2]
else:
C_REQ提取S_ACK = -1
Log.debug(f" 期望 S 回应 ACK {C_REQ提取S_ACK}(请求ACK查响应SEQ) {累计计算预估S响应ACK}(发数据累计预估)")
## 检查有没有响应数据
if C_REQ提取S_ACK in D_HTTP['D_S_ACK_DATA']: ## 初始值是负数,没有找到可用值时使用初始可以保证这个判断失败
Log.debug(f" 【I】使用(请求ACK查响应SEQ)找到的响应信息")
S响应ACK = C_REQ提取S_ACK
elif REQ_ack in D_HTTP['C_ACK_RST_AckKey']:
Log.debug(f" 【E】找不到响应数据,找到C主动终止了连接:发现ACK={REQ_ack} 在 D_HTTP['C_ACK_RST_AckKey']出现")
响应状态码 = 1 ## 自定义,找不到响应信息,且发现了C发的RST信息
L_HTTP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, -1, C发完请求耗时, 0, 0, C重发数据包数量, 0))
continue
elif REQ_ack in D_HTTP['S_ACK_FIN_SeqKey']:
Log.debug(f" 【E】找不到响应数据,找到服务端发送FIN终止连接:{D_HTTP['S_ACK_FIN_SeqKey'][REQ_ack]}")
响应状态码 = 6 ## 自定义,找不到响应信息,且找到服务端发起了FIN终止
L_HTTP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, -1, C发完请求耗时, 0, 0, C重发数据包数量, 0))
continue
else:
Log.debug(" 【E】找不到响应信息,且没有发现C发的RST信息,应该是真没有响应")
响应状态码 = 2 ## 自定义,两种方法都找不到响应信息,且没有发现C发的RST信息,应该是真没有响应
L_HTTP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, -1, C发完请求耗时, 0, 0, C重发数据包数量, 0))
continue
## 找到响应数据包就继续分析
S发送响应数据包列表 = D_HTTP['D_S_ACK_DATA'][S响应ACK]
## 有响应响应信息会继续下面的分析
if S响应ACK in D_HTTP['HTTP_SC']:
响应状态码 = D_HTTP['HTTP_SC'][S响应ACK]
else:
Log.debug(f" 【ERROR】找不到对请求的回应(使用自定义初始值SC=0)")
## 当客户端发起RST,服务器依据可能返回响应数据,为了区分出这种情况,最后再查一下这个,修改响应码标识
if S响应ACK in D_HTTP['C_ACK_RST_SeqKey']:
Log.debug(f" 【DEBUG】有响应的情况下C主动终止了连接:RST_Seq=S响应ACK={S响应ACK} 在 D_HTTP['C_ACK_RST_SeqKey']出现(设置自定义SC=响应状态码*10+1)")
响应状态码 = 响应状态码*10 + 1 # 自定义,表示C发起了RST,强制断开连接,普通响应码增加1位1
## 检查一下有没有异常的数据包(乱序/编号有问题)
if 检查数据包编号(S发送响应数据包列表) == 1:
Log.debug(" 【WARNING】数据包编号有乱序")
else:
Log.debug(" 数据包编号正常(单包请求忽略检查)")
## S正常响应数据包
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_HTTP['D_S_ACK_DATA_RPT']:
for i in D_HTTP['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_HTTP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, C完成请求耗时, C发完请求耗时, S处理请求耗时, S发完响应数据耗时, C重发数据包数量, S重发数据包数量))
else:
Log.critical(f"【CRITICAL】未找到C发送的{请求类型}包,请求包D_HTTP['C_HTTP_REQ']中有,但D_HTTP['D_C_ACK_DATA']中没有,包信息 {D_HTTP['C_HTTP_REQ'][REQ_ack]}")
Log.critical(f"【CRITICAL】非请求被当成请求/服务端对客户端发起请求/请求包是ACK.PSH.FIN标志归入FIN类,服务端先关闭了长连接")
响应状态码 = 5 ## 请求包D_HTTP['C_HTTP_REQ']中有,但D_HTTP['D_C_ACK_DATA']中没有,请求方向错误,如服务端发起请求
## 记录耗时(异常情况)
L_HTTP_TIME.append((REQ_ack, C发送请求时间戳, 请求类型, 响应状态码, -1, 0, 0, 0, 0, 0))
## 查看重复的首个请求信息
if K in D_HTTP['C_HTTP_REQ_RPT']:
Log.debug(f"【WARNING】请求首包有重发 {D_HTTP['C_HTTP_REQ_RPT'][K]}")
Log.debug('')
return(L_HTTP_TIME)
## 查询数据库中PCAP表中某个TCP会话相关数据包计算HTTP用时结果再存入数据库
## PACK_INFO 相关包信息 (客户端IP, 客户端PORT, 服务端IP, 服务端PORT)
## TABLE_NAME 保存的数据表名,不存在会创建
## 视角 = 'C' 以客户端视角修改字段名的收发方向信息
## 视角 = 'S' 以服务端视角修改字段名的收发方向信息
def HTTP_SESSION_TIME_2_DB(PACK_INFO, TABLE_NAME, 数据库连接对象, 视角):
C_IP = PACK_INFO[0] # 客户端IP
C_Port = PACK_INFO[1] # 客户端PORT
S_IP = PACK_INFO[2] # 服务器IP
S_Port = PACK_INFO[3] # 服务器PORT
## 从数据库中查出跟本次会话有关的数据包信息(端口复用的也可以存在里面,不影响计算里面请求响应的时间计算)
SQL_CMD = f"SELECT TIME,SrcIP,DstIP,SPort,DPort,Seq,Ack,FLAGE,LEN,TYPE,SC FROM PCAP WHERE (SrcIP = '{C_IP}' AND SPort = '{C_Port}' AND DstIP = '{S_IP}' AND DPort = '{S_Port}') OR (SrcIP = '{S_IP}' AND SPort = '{S_Port}' AND DstIP = '{C_IP}' AND DPort = '{C_Port}');"
R = DEF_SQL_SELECT(数据库连接对象, SQL_CMD)
if R[0] == 0:
全部记录 = R[1]
#for i in 全部记录:
# print(i)
D_HTTP = SelectDB_IPv4_TCP_HTTP_SESSION(全部记录, C_IP, C_Port, S_IP, S_Port)
L_HTTP_TIME = SelectDB_HTTP_SESSION_TIME(D_HTTP) ## 日志等级为Debug会显示每个请求的详细分析过程信息
## 存入数据库
if 视角 == 'C':
SQL_CMD_CREATE_TABLE = f"CREATE TABLE IF NOT EXISTS {TABLE_NAME} (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ACK INT, TIME timestamp, TYPE CHAR(4), SC INT, COMP FLOAT, TX FLOAT, SYS_NET FLOAT, RX FLOAT, Tx_RPT INT, Rx_RPT INT);"
else:
SQL_CMD_CREATE_TABLE = f"CREATE TABLE IF NOT EXISTS {TABLE_NAME} (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ACK INT, TIME timestamp, TYPE CHAR(4), SC INT, COMP FLOAT, RX FLOAT, SYS FLOAT, TX FLOAT, Rx_RPT INT, Tx_RPT INT);"
RR = DEF_SQL_执行多条_遇到错误终止(数据库连接对象, [SQL_CMD_CREATE_TABLE])
if RR[0] == 0:
L_SQL_CMD = []
for i in L_HTTP_TIME:
if 视角 == 'C':
SQL = f'INSERT INTO {TABLE_NAME} (ACK, TIME, TYPE, SC, COMP, TX, SYS_NET, RX, Tx_RPT, Rx_RPT) VALUES ({i[0]}, {i[1]}, "{i[2]}",{i[3]}, {i[4]}, {i[5]}, {i[6]}, {i[7]}, {i[8]}, {i[9]})'
else:
SQL = f'INSERT INTO {TABLE_NAME} (ACK, TIME, TYPE, SC, COMP, RX, SYS, TX, Rx_RPT, Tx_RPT) VALUES ({i[0]}, {i[1]}, "{i[2]}",{i[3]}, {i[4]}, {i[5]}, {i[6]}, {i[7]}, {i[8]}, {i[9]})'
L_SQL_CMD.append(SQL)
if L_SQL_CMD != []:
RRR = DEF_SQL_执行多条_遇到错误终止(数据库连接对象, L_SQL_CMD)
print(RRR)
else:
print("创建数据表", TABLE_NAME, "失败", RR)
else:
print("数据库查询HTTP会话相关数据包", PACK_INFO, "失败", R)
## 查询数据库中PCAP表中某个TCP会话相关数据包计算HTTP用时结果写入日志
## PACK_INFO 相关包信息 (客户端IP, 客户端PORT, 服务端IP, 服务端PORT)
## 视角 = 'C' 以客户端视角修改字段名的收发方向信息
## 视角 = 'S' 以服务端视角修改字段名的收发方向信息
def HTTP_SESSION_TIME_2_LOG(PACK_INFO, DB_File):
R = DEV_SQLite3_OPEN(DB_File)
if R[0] == 0:
数据库连接对象 = R[1]
C_IP = PACK_INFO[0] # 客户端IP
C_Port = PACK_INFO[1] # 客户端PORT
S_IP = PACK_INFO[2] # 服务器IP
S_Port = PACK_INFO[3] # 服务器PORT
## 从数据库中查出跟本次会话有关的数据包信息(端口复用的也可以存在里面,不影响计算里面请求响应的时间计算)
SQL_CMD = f"SELECT TIME,SrcIP,DstIP,SPort,DPort,Seq,Ack,FLAGE,LEN,TYPE,SC FROM PCAP WHERE (SrcIP = '{C_IP}' AND SPort = '{C_Port}' AND DstIP = '{S_IP}' AND DPort = '{S_Port}') OR (SrcIP = '{S_IP}' AND SPort = '{S_Port}' AND DstIP = '{C_IP}' AND DPort = '{C_Port}');"
Log.debug(SQL_CMD)
print(SQL_CMD)
R = DEF_SQL_SELECT(数据库连接对象, SQL_CMD)
if R[0] == 0:
全部记录 = R[1]
if 全部记录 != []:
#for i in 全部记录:
# print(i)
D_HTTP = SelectDB_IPv4_TCP_HTTP_SESSION(全部记录, C_IP, C_Port, S_IP, S_Port)
L_HTTP_TIME = SelectDB_HTTP_SESSION_TIME(D_HTTP) ## 日志等级为Debug会显示每个请求的详细分析过程信息
SHOW_HTTP_TIME(L_HTTP_TIME, 视角='C')
else:
print("SQL 语句执行成功")
print("查询结果为空")
Log.debug("查询结果为空")
else:
print("数据库查询HTTP会话相关数据包", PACK_INFO, "失败", R)
else:
print("打开数据库文件", DB_File, "失败", R)
## 查询数据库中PCAP表中全部GET/POST请求的TCP会话相关数据包计算HTTP用时结果再存入数据库
## 量大很慢,慎用
def DB_PCAP_to_DB_HTTP_TIME(DB_File, TABLE_NAME, 视角):
SQL_CMD = "SELECT SrcIP,SPort,DstIP,DPort FROM PCAP WHERE TYPE = 'GET' or TYPE='POST';"
R = DEV_SQLite3_OPEN(DB_File)
if R[0] == 0:
数据库连接对象 = R[1]
RR = DEF_SQL_SELECT(数据库连接对象, SQL_CMD)
if RR[0] == 0:
全部记录 = RR[1]
print("去重前源端口数量", len(全部记录))
P_HTTP_REQ = set()
for i in 全部记录:
P_HTTP_REQ.add(i) # 根据(SrcIP,SPort,DstIP,DPort)去重(一次TCP连接中可以有多次HTTP请求/响应交互,合在一起查询可以减少查询次数)
print("去重后源端口数量", len(P_HTTP_REQ))
for PACK_INFO in P_HTTP_REQ:
HTTP_SESSION_TIME_2_DB(PACK_INFO, TABLE_NAME, 数据库连接对象, 视角)
数据库连接对象.close()
else:
print("执行数据库语句", SQL_CMD, "失败", RR)
else:
print("打开数据库文件", DB_File, "失败", R)
## 直接查询SQLite3数据库,把用时很大的会话对应文件保存起来,方便再详细查看
def Select_SQLite3_CP_TIME(DB_File, DIR_IP_PORT, DstDir, TIME):
if not os.path.isdir(DIR_IP_PORT):
print(f"目录 {DIR_IP_PORT} 不存在")
return(1)
if not os.path.isdir(DstDir):
print(f"目录 {DstDir} 不存在")
return(2)
if not os.path.isfile(DB_File):
print(f"文件 {DB_File} 不存在")
return(3)
R = DEV_SQLite3_OPEN(DB_File)
if R[0] == 0:
数据库连接对象 = R[1]
SQL_CMD = f"SELECT SrcIP,SPort,DstIP,DPort FROM PCAP WHERE Ack IN (SELECT ACK FROM HTTP WHERE COMP > {TIME})"
RR = DEF_SQL_SELECT(数据库连接对象, SQL_CMD)
if RR[0] == 0:
全部记录 = RR[1]
print(f"去重前数量 {len(全部记录)}")
P = set()
for i in 全部记录:
P.add(i) # 根据(SrcIP,SPort,DstIP,DPort)去重(一次TCP连接中可以有多次HTTP请求/响应交互)
print(f"去重后数量 {len(P)} 文件数量(响应时间>{TIME}s)")
for j in P:
FileName = f"{j[0]}_{j[1]}-{j[2]}_{j[3]}.pcap"
shutil.copy(DIR_IP_PORT+FileName, DstDir)
else:
print(RR[1])
else:
print(R[1])
## 直接查询SQLite3数据库,把特定HTTP响应代码的结果找出对应文件保存起来,方便再详细查看
def Select_SQLite3_CP_SC(DB_File, DIR_IP_PORT, DstDir, SC):
if not os.path.isdir(DIR_IP_PORT):
print(f"目录 {DIR_IP_PORT} 不存在")
return(1)
if not os.path.isdir(DstDir):
print(f"目录 {DstDir} 不存在")
return(2)
if not os.path.isfile(DB_File):
print(f"文件 {DB_File} 不存在")
return(3)
R = DEV_SQLite3_OPEN(DB_File)
if R[0] == 0:
数据库连接对象 = R[1]
SQL_CMD = f"SELECT SrcIP,SPort,DstIP,DPort FROM PCAP WHERE Ack IN (SELECT ACK FROM HTTP WHERE SC IN {SC})"
RR = DEF_SQL_SELECT(数据库连接对象, SQL_CMD)
if RR[0] == 0:
全部记录 = RR[1]
print(f"去重前数量 {len(全部记录)}")
P = set()
for i in 全部记录:
P.add(i) # 根据(SrcIP,SPort,DstIP,DPort)去重(一次TCP连接中可以有多次HTTP请求/响应交互)
print(f"去重后数量 {len(P)} 文件数量(响应代码<{SC})")
for j in P:
FileName = f"{j[0]}_{j[1]}-{j[2]}_{j[3]}.pcap"
shutil.copy(DIR_IP_PORT+FileName, DstDir)
else:
print(RR[1])
else:
print(R[1])
#################
## 测试及Debug ##
#################
第三步,运行Main.py分析每一个HTTP过程信息
选择功能,设置参数,运行
Main.py
from DEF_ALL_V19 import *
from multiprocessing import Queue # 队列
from threading import Thread # 线程
import logging
Log = logging.getLogger("Main")
LOG_FILE = 'A:\\' + 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)
#Log.setLevel(logging.INFO)
#Log.setLevel(logging.WARNING)
Log.setLevel(logging.ERROR)
#Log.setLevel(logging.CRITICAL)
校验 = 0 # 是否使用TCP校验,1开启(会忽略校验错误的包),0关闭(校验错误的包可能也能正常使用)
视角 = 'C' # S以服务端视角分析请求(在服务端附近抓的包)/C以客户端视角分析请求(在客户端附近抓的包)注:视角对于时间计算结果的可信度有很大影响,因为接收方是收不到发送方中途丢失的包的就对丢失部分的时间不能精确计算
if __name__ == '__main__':
# 0 查HTPP会话【时间】详细信息(HTTP过程中的发送、处理、接收用时情况)
# 1 查HTTP会话【内容】详细信息(HTTP请求数据内容/响应数据内容)
# 2 拆分单个PCAP文件为以TCP会话为单位的小PCAP文件(IP和PORT重复的会被保存为1个文件,但不影响时间计算)
# 3 拆分多个PCAP文件为以TCP会话为单位的小PCAP文件(IP和PORT重复的会被保存为1个文件,但不影响时间计算)
# 4 解析多个以TCP会话为单位的PCAP文件,计算出HTTP请求/响应时间存入Sqlite3数据库的HTTP表
# 5 解析多个以时间为顺序的PCAP文件,读取包基本信息存入Sqlite3数据库的PCAP表
# 6 直接查询SQLite3数据库中PCAP表中指定TCP会话相关数据包计算HTTP请求/响应用时结果写入日志文件
# 7 直接查询SQLite3数据库中PCAP表中全部GET/POST请求的TCP会话相关数据包计算HTTP请求/响应用时结果再存入SQLite3数据库自定义表名中(量大很慢,慎用)
# 8 直接查询SQLite3数据库,把特定SC响应代码的结果找出对应文件保存起来,方便再详细查看
# 9 直接查询SQLite3数据库,把用时很大的会话对应文件保存起来,方便再详细查看
# 9 使用MySQL数据库
RUN = 0
File = 'A:\\192.168.0.100_64868-172.16.1.80_80.pcap' # 0/1 解析PCAP文件(以IP和端口为文件名的以SYN开头交互数据包)
#解析HTTP内容 = 'C' # 1 'C'解析GET/POST请求内容
解析HTTP内容 = 'S' # 1 'S'解析HTTP/text响应内容
ACK = 'ALL' # 1 查看全部用ACK设置为 'ALL'
ACK = 2477812393 # 1 指定要查看的请求/回应第一个数据包的Ack
FileName = 'A:\\1610939210.pcap' # 2 拆分单个PCAP文件(以IP和端口为文件名的以SYN开头交互数据包)
DIR_IP_PORT = 'A:\\PCAP0115_IP_PORT\\' # 2/3/4/8/9 存放PCAP文件(以IP和端口为文件名的以SYN开头交互数据包)目录
SrcDIR_TIME = 'F:\\DATA\\PCAP2021115_TIME\\' # 3/5 存放PCAP文件(以时间戳为文件名且无扩展名)目录
DB_File = 'A:\\HTTP_0115.sqlite3' # 4/5/6/7/8/9 创建/打开数据库文件
C_IP = '192.168.0.100' # 6 要查询TCP连接的:客户端IP
C_Port = 50021 # 6 要查询TCP连接的:客户端PORT
S_IP = '172.16.1.80' # 6 要查询TCP连接的:服务器IP
S_Port = 80 # 6 要查询TCP连接的:服务器PORT
TABLE_NAME = 'DB2HTTP' # 7 保存数据的表名
#TABLE_NAME = time.strftime('%Y%m%d') # 7 保存数据的表名
SC = 5 # 8 复制指定响应代码的会话文件,指定HTTP响应代码
SCDir = 'A:\\SCX\\' # 8 复制指定响应代码的会话文件,保存目录
TIME = 200 # 9 用时超过指定秒数的会话对应文件保存
TIMEDir = 'A:\\LongTime\\' # 9 用时超过指定秒数的会话对应文件保存
if RUN == 0:
## 查HTPP会话【时间】详细信息(HTTP过程中的发送、处理、接收用时情况)
## PCAP文件内容必须是源目的端口固定的并以SYN包开头且无VLAN标签(可以通过RUN==2/3先自动整理再使用RUN==0详解指定的会话文件)
Log.setLevel(logging.DEBUG)
if os.path.isfile(File):
(D_HTTP, L_DATA) = IPv4_TCP_HTTP_SESSION(File, 校验)
DEBUG_PACK_INFO(L_DATA)
L_HTTP_TIME = PCAP_HTTP_SESSION_TIME(D_HTTP)
SHOW_HTTP_TIME(L_HTTP_TIME, 视角)
#Log.debug(D_HTTP['D_S_ACK_DATA'][981762388]) # 记录需要调试的变量信息
else:
print(File, "不存在")
print("RUN==0 完成 信息已经保存到日志")
elif RUN == 1:
## 查HTTP会话【内容】详细信息(HTTP请求数据内容/响应数据内容)
## PCAP文件内容必须是只存在一个TCP连接会话并以SYN包开头且无VLAN标签(可以通过RUN==2/3先自动整理再使用RUN==0详解指定的会话文件)
Log.setLevel(logging.DEBUG)
#Log.setLevel(logging.INFO)
if os.path.isfile(File):
## 先提取请求响应包信息
(D_HTTP, L_DATA) = IPv4_TCP_HTTP_SESSION(File, 校验)
#DEBUG_PACK_INFO(L_DATA)
#L_HTTP_TIME = PCAP_HTTP_SESSION_TIME(D_HTTP)
#SHOW_HTTP_TIME(L_HTTP_TIME, 视角)
## 再解析请求/响应内容
if 解析HTTP内容 == 'C':
查看请求数据(File, D_HTTP, ACK)
elif 解析HTTP内容 == 'S':
查看响应数据(File, D_HTTP, ACK)
else:
print(f"设置 解析HTTP内容='C/S' (C看GET/POST请求内容) | (S看HTTP响应内容)")
else:
print(File, "不存在")
print("RUN==1 完成 信息已经保存到日志")
elif RUN == 2:
## 读取单个PCAP文件
## 转成(以IP和端口为文件名的以SYN开头交互数据包PCAP文件,IP和端口复用的情况下直接追加进文件)
if os.path.isfile(FileName):
WorkDirName = DIR_IP_PORT
while 1:
if os.path.exists(WorkDirName):
WorkDirName += 'A'
else:
os.makedirs(WorkDirName)
break
print(f"源文件 {FileName} 分解到 {WorkDirName}")
#P = Thread(target=P1_单文件拆分,args=(WorkDirName, FileName)) # 用线程会快一点点
#P.start()
P1_单文件拆分(WorkDirName, FileName)
print("RUN==2 完成 信息已经保存到日志")
else:
print(FileName, "不存在")
elif RUN == 3:
## 遍历目录,逐个读取PCAP文件(以时间戳为文件名且无扩展名)
## 转成(以IP和端口为文件名的以SYN开头交互数据包PCAP文件,IP和端口复用的情况下直接追加进文件)
## 如果目录已经存在,就改目录名再创建
WorkDirName = DIR_IP_PORT
while 1:
if os.path.exists(WorkDirName):
WorkDirName += 'X'
else:
os.makedirs(WorkDirName)
break
print(f"源目录 {SrcDIR_TIME} 分解到 {WorkDirName} ...")
#P = Thread(target=P1_多时间戳文件名拆分,args=(WorkDirName,SrcDIR_TIME))
#P.start()
P1_多时间戳文件名拆分(WorkDirName,SrcDIR_TIME)
print("RUN==3 完成")
elif RUN == 4:
## 遍历目录,逐个解析PCAP文件(以IP和端口为文件名的以SYN开头交互数据包)
## 计算出每次请求应答过程时间消耗信息存入数据库 HTTP 表(不需要时间顺序)
Log.setLevel(logging.ERROR)
R = DEV_SQLite3_OPEN(DB_File)
if R[0] == 1:
print(f" 打开数据库失败 {R[1]}")
else:
数据库连接对象 = R[1]
if 视角 == 'C':
L_SQL_CMD = ["CREATE TABLE IF NOT EXISTS HTTP(ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ACK INT, TIME timestamp, TYPE CHAR(4), SC INT, COMP FLOAT, TX FLOAT, SYS_NET FLOAT, RX FLOAT, Tx_RPT INT, Rx_RPT INT);"]
else:
L_SQL_CMD = ["CREATE TABLE IF NOT EXISTS HTTP(ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ACK INT, TIME timestamp, TYPE CHAR(4), SC INT, COMP FLOAT, RX FLOAT, SYS FLOAT, TX FLOAT, Rx_RPT INT, Tx_RPT INT);"]
RR = DEF_SQL_执行多条_遇到错误终止(数据库连接对象, L_SQL_CMD)
print(f"创建/打开数据库成功,执行 {RR[1]} 条语句")
## 遍历目录
os.chdir(DIR_IP_PORT)
L_FileName = os.listdir(DIR_IP_PORT) # 目录内内容列表
文件数量 = len(L_FileName)
文件计数 = 1
for FileName in L_FileName:
进度信息 = f"解析PCAP文件:{FileName} 进度:{文件计数}/{文件数量}"
print(进度信息)
#Log.critical(进度信息) # 以最高日志等级记录一下
文件计数 += 1
(D_HTTP, L_DATA) = IPv4_TCP_HTTP_SESSION(FileName, 校验) # 原始数据包提取基本信息保存到字典
L_HTTP_TIME = PCAP_HTTP_SESSION_TIME(D_HTTP) # 解析字典信息计算请求耗时
## 存入数据库
R = DEV_SQLite3_OPEN(DB_File)
if R[0] == 1:
print(f" 打开数据库失败 {R[1]}")
else:
数据库连接对象 = R[1]
L_SQL_CMD = []
for i in L_HTTP_TIME:
if 视角 == 'C':
SQL = f'INSERT INTO HTTP (ACK, TIME, TYPE, SC, COMP, TX, SYS_NET, RX, Tx_RPT, Rx_RPT) VALUES ({i[0]}, {i[1]}, "{i[2]}",{i[3]}, {i[4]}, {i[5]}, {i[6]}, {i[7]}, {i[8]}, {i[9]})'
else:
SQL = f'INSERT INTO HTTP (ACK, TIME, TYPE, SC, COMP, RX, SYS, TX, Rx_RPT, Tx_RPT) VALUES ({i[0]}, {i[1]}, "{i[2]}",{i[3]}, {i[4]}, {i[5]}, {i[6]}, {i[7]}, {i[8]}, {i[9]})'
L_SQL_CMD.append(SQL)
if L_SQL_CMD != []:
RR = DEF_SQL_执行多条_遇到错误终止(数据库连接对象, L_SQL_CMD)
#print(RR)
数据库连接对象.close()
print("RUN==4 完成")
elif RUN == 5:
## 遍历目录(多个pcap文件的话要按时间顺序),
## 把PCAP包解析成文本信息格式存入数据库 PCAP 表(这样可以让每个数据包按抓到的时间顺序存入)
R = DEV_SQLite3_OPEN(DB_File)
if R[0] == 1:
print(f" 打开数据库失败 {R[1]}")
else:
数据库连接对象 = R[1]
L_SQL_CMD = ["CREATE TABLE IF NOT EXISTS PCAP (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, N INT, TIME timestamp, SrcIP CHAR(15), DstIP CHAR(15), SPort INT, DPort INT, Seq INT, Ack INT, FLAGE CHAR(10), LEN INT, TYPE CHAR(4), SC INT, WIN INT, VID INT, SACK_INFO CHAR(150));"]
RR = DEF_SQL_执行多条_遇到错误终止(数据库连接对象, L_SQL_CMD)
print(f" 创建/打开数据库成功,执行 {RR[1]} 条语句")
## 遍历目录
os.chdir(SrcDIR_TIME)
L_FileName = os.listdir(SrcDIR_TIME) # 目录内内容列表
L_FileName.sort() # 从小到大排序
文件数量 = len(L_FileName)
文件计数 = 1
for FileName in L_FileName:
print(f"解析PCAP文件:{FileName} 进度:{文件计数}/{文件数量}")
文件计数 += 1
L_PACK_INFO = P2S(FileName) ## PCAP格式解析成自定义文本格式
## 存入数据库
R = DEV_SQLite3_OPEN(DB_File)
if R[0] == 1:
print(f" 打开数据库失败 {R[1]}")
else:
数据库连接对象 = R[1]
L_SQL_CMD = []
for i in L_PACK_INFO:
SQL = f'INSERT INTO PCAP (N, TIME, SrcIP, DstIP, SPort, DPort, Seq, Ack, FLAGE, LEN, TYPE, SC, WIN, VID, SACK_INFO) VALUES ({i[0]}, {i[1]}, "{i[2]}", "{i[3]}", {i[4]}, {i[5]}, {i[6]}, {i[7]}, "{i[8]}", {i[9]}, "{i[10]}", {i[11]}, {i[12]}, {i[13]}, "{i[14]}")'
L_SQL_CMD.append(SQL)
#print(SQL)
if L_SQL_CMD != []:
RR = DEF_SQL_执行多条_遇到错误终止(数据库连接对象, L_SQL_CMD)
#print(RR)
数据库连接对象.close()
print("RUN==5 完成")
elif RUN == 6:
Log.setLevel(logging.DEBUG)
PACK_INFO = (C_IP, C_Port, S_IP, S_Port)
HTTP_SESSION_TIME_2_LOG(PACK_INFO, DB_File)
print("RUN==6 完成")
elif RUN == 7:
Log.setLevel(logging.ERROR)
DB_PCAP_to_DB_HTTP_TIME(DB_File, TABLE_NAME, 视角)
print("RUN==7 完成")
elif RUN == 8:
Log.setLevel(logging.ERROR)
Select_SQLite3_CP_SC(DB_File, DIR_IP_PORT, SCDir, SC)
print("RUN==8 完成")
elif RUN == 9:
Select_SQLite3_CP_TIME(DB_File, DIR_IP_PORT, TIMEDir, TIME)
print("RUN==9 完成")
else:
print("未定义执行代码")
完成,运行结果正常是这样的
【请求耗时信息汇总(客户端抓包)单位秒】
请求(ack) 时间戳 类型 SC COMP TX(GET/POST) SYS+NET RX(HTTP) C_err S_err
1974073265 1607932697.47 GET 304 0.067161 0.000000 0.067161 0.000000 0 0
1974072748 1607932695.25 POST 200 0.081112 0.000110 0.081002 0.000000 0 0
1974073358 1607932697.71 POST 200 0.075027 0.009557 0.065418 0.000052 0 0
1974073617 1607932697.85 POST 200 0.146792 0.083605 0.063187 0.000000 0 0
1974073900 1607932699.47 POST 200 3.786227 0.009972 3.517605 0.000028 0 0
1974074560 1607932703.32 POST 200 0.120926 0.000408 0.120452 0.000066 0 0
1974075042 1607932705.34 POST 200 0.530666 0.283515 0.094297 0.000838 0 0
DEBUG模式日志示例
C_REQ_Header {'Request': 'GET', 'URL': 请求行和请求头信息,很长的,示例不写了}
C 发送 GET 请求 ACK=2132538915
URL /a8/aos_xprint_export.jscript?version=1575020160000
Content-Length = -1
数据包编号正常(单包请求忽略检查)
GET 请求信息 (1610681582.616766, 2477791999, 2132538915, 702, 257, 11)
C 发送 GET 数据统计:正常 1 个,共 702 字节,重发 0 个
C 发送 GET 时间 1610681582.616766
C 发完 GET 时间 1610681582.616766
C 发完 GET 耗时 0.000000 秒 (不含重发数据的时间)
期望 S 回应 ACK 2477792701(请求ACK查响应SEQ) -1(Content-Length计算) 2477792701(发数据累计预估)
【I】使用(请求ACK查响应SEQ)找到的响应信息
数据包编号正常(单包请求忽略检查)
GET 响应信息 (1610681582.675485, 2132538915, 2477792701, 93, 65535, 12)
S 响应数据统计:正常 1 个,共 93 字节,响应状态码 304,重发 0 个
S 发送响应数据时间 1610681582.675485
S 发完响应数据时间 1610681582.675485
S 发完响应数据耗时 0.000000 (不含重发数据的时间)
S 处理 GET 耗时 0.058719 秒 (S发送响应数据首包时间 - C发送请求数据尾包时间)
完成 GET 总耗时 0.058719 秒 (S发送响应数据尾包时间 - C发送请求数据首包时间)
C_REQ_Header {'Request': 'POST', 'URL': 请求行和请求头信息,很长的,示例不写了}
C 发送 POST 请求 ACK=1112405539
URL /a8/submit
Content-Length = 312
数据包编号正常(单包请求忽略检查)
POST 请求信息 (1610721407.875227, 2294548029, 1112405539, 759, 16450, 1243)
POST 请求信息 (1610721407.875259, 2294548788, 1112405539, 312, 16450, 1244)
C 发送 POST 数据统计:正常 2 个,共 1071 字节,重发 0 个
C 发送 POST 时间 1610721407.875227
C 发完 POST 时间 1610721407.875259
C 发完 POST 耗时 0.000032 秒 (不含重发数据的时间)
期望 S 回应 ACK 2294549100(请求ACK查响应SEQ) 2294549100(Content-Length计算) 2294549100(发数据累计预估)
【I】使用(请求ACK查响应SEQ)找到的响应信息
【W】有响应的情况下C主动终止了连接:RST_Seq=S响应ACK=2294549100 在 D_HTTP['C_ACK_RST_SeqKey']出现(设置自定义SC=响应状态码*10+1)
数据包编号正常(单包请求忽略检查)
POST 响应信息 (1610721408.097313, 1112405539, 2294549100, 190, 65535, 1245)
POST 响应信息 (1610721408.097613, 1112405729, 2294549100, 86, 65535, 1246)
S 响应数据统计:正常 2 个,共 276 字节,响应状态码 2001,重发 0 个
S 发送响应数据时间 1610721408.097313
S 发完响应数据时间 1610721408.097613
S 发完响应数据耗时 0.000300 (不含重发数据的时间)
S 处理 POST 耗时 0.222054 秒 (S发送响应数据首包时间 - C发送请求数据尾包时间)
完成 POST 总耗时 0.222386 秒 (S发送响应数据尾包时间 - C发送请求数据首包时间)
【请求耗时信息汇总(服务端抓包)单位秒】
请求(ack) 时间戳 类型 SC COMP C_req SYS S_http C_RPT S_RPT
2132538915 1610681582.62 GET 304 0.059 0.000 0.059 0.000 0 0
1112405539 1610721407.88 POST 2001 0.222 0.000 0.222 0.000 0 0
数据库使用参考
# 响应状态码 = 0 # 自定义初始值,如果后面解析不出,就响应码设置为0
# 响应状态码 = 1 # 自定义,两种方法都找不到响应信息,且发现了C发的RST信息
# 响应状态码 = 2 # 自定义,两种方法都找不到响应信息,且没有发现C发的RST信息,应该是真没有响应
# 响应状态码 = 3 # 自定义,请求方法解析不出,可能不是请求包,需要手动查原因
# 响应状态码 = xxx1 (正常响应状态码*10 + 1) # 自定义,表示C发起了RST,强制断开连接,抓到了响应HTTP,普通响应码增加1位1
# 响应状态码 = 5 # 请求包D_HTTP['C_HTTP_REQ']中有,但D_HTTP['D_C_ACK_DATA']中没有,请求方向错误,如服务端发起请求,新:客户端发起A.P.F请求,服务端没有响应数据而是FIN关闭,客户端后面又发起了请求
# 响应状态码 = 6 # 自定义,两种方法都找不到响应信息,且找到服务端发起了FIN终止
## 响应代码统计
SELECT SC,COUNT(0) AS 出现次数 FROM HTTP GROUP BY SC HAVING COUNT(ID)>0
## 查看一次交互信息(客户端的ack和服务端的seq相同的为同一次HTTP交互)
SELECT * FROM PCAP WHERE ack= 3295760019 or seq= 3295760019
## 查看整个TCP交互(用客户端端口)
SELECT * FROM PCAP WHERE SPort= 60001 or DPort= 60001
## 未知 FLAGE
SELECT * FROM PCAP WHERE FLAGE='UN'
## 响应耗时统计
select
sum(case when comp < 0 then 1 else 0 end ) '无响应',
sum(case when comp >=0 and comp < 5 then 1 else 0 end ) '0-5秒',
sum(case when comp >= 5 and comp < 10 then 1 else 0 end ) '5-10秒',
sum(case when comp >= 10 and comp < 20 then 1 else 0 end ) '10-20秒',
sum(case when comp >= 20 and comp < 30 then 1 else 0 end ) '20-30秒',
sum(case when comp >= 30 and comp < 40 then 1 else 0 end ) '30-40秒',
sum(case when comp >= 40 and comp < 50 then 1 else 0 end ) '40-50秒',
sum(case when comp >= 50 and comp < 60 then 1 else 0 end ) '50-60秒',
sum(case when comp >= 60 and comp < 70 then 1 else 0 end ) '60-70秒',
sum(case when comp >= 70 and comp < 80 then 1 else 0 end ) '70-80秒',
sum(case when comp >= 80 and comp < 90 then 1 else 0 end ) '80-90秒',
sum(case when comp >= 90 and comp < 100 then 1 else 0 end ) '90-100秒',
sum(case when comp >= 100 and comp < 200 then 1 else 0 end ) '100-200秒',
sum(case when comp >= 200 and comp < 300 then 1 else 0 end ) '200-300秒',
sum(case when comp >= 300 and comp < 600 then 1 else 0 end ) '300-600秒',
sum(case when comp > 600 then 1 else 0 end ) '>600秒'
from HTTP;
更多推荐
已为社区贡献1条内容
所有评论(0)