用python分析HTTP请求中各过程用时情况(通过分析抓包文件pcap实现)
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 = 0for i in X:SUM += iwhile(SUM > 65535): ## 值大于 65535 说明二进制位数超过16bitH16 = SUM >> 16 ## 取高16位L16 = SUM & 0xffff ## 取低16位SUM = H16 + L16校验和 = SUM ^ 0xffffLog.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 = 时间戳 + 微秒/1000000return(时间戳_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) # BIP_Version = IP_B >> 4 # 取1字节的前4位IP_Header_Length = IP_B & 0xF # 取1字节的后4位IP_Flags = IP_H >> 13IP_Fragment_offset = IP_H & 0b0001111111111111return(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 >> 6TCP_Reserved = 先去掉后6位 & 0b0000111111TCP_Flags = TCP_H & 0b0000000000111111return(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,最大65535WSOPT = 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 +=1elif Kind == b'\x00':## 选项列表结束#print(" Kind: EOL (选项列表结束)")N +=1elif 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 += LengthWSOPT = Valueelif 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 += Lengthelif 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 += Lengthelif 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 += Lengthelif 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 += Lengthelif 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 += Lengthelif 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 += Lengthelse:print("【W】未知 TCP Option,终止")breakreturn(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 = 0LR = 0字节当前位置 = 0CRLR位置记录 = -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 # 每运行一次,位置加1return(字节当前位置, 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, 协议版本 = 请求行SPD_HTTP_Header['Request'] = 请求方法D_HTTP_Header['URL'] = URLD_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\nLog.debug(f" 【DEBUG】信息部分 {HTTP_INFO}")HTTP_INFO_SP = HTTP_INFO.decode('ASCII', errors='ignore').split('\r\n')## 解析请求行请求行SP = HTTP_INFO_SP[0].split(' ')请求方法, URL, 协议版本 = 请求行SPD_HTTP_Header['Request'] = 请求方法if 请求方法 == 'GET':URL = unquote(URL)D_HTTP_Header['URL'] = URLD_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\nLog.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\nLog.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\nLog.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'' # 二进制类型ChunkSizeMBF_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) # 转成十进制数字类型ChunkSizeexcept 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:breakChunkSizeBytes = b''ChunkData = MBF_R.read(ChunkSize) # 读取实际数据MBF_R.read(2) # 再向后读掉\r\n这两个字节#print("ChunkData", ChunkData)MBF_W.write(ChunkData) # 写入实际数据else:ChunkSizeBytes += Byelse:ChunkSizeBytes += Bxreturn(0, MBF_W)################################
## 解析数据包计算请求时间部分 ##
################################## 读取pcap格式文件,返回1个字典和一个列表
## D_HTTP 包信息字典
## L_DATA 包信息
## 开头第一个包必须是SYN包,不能含VLAN信息,这些由前置拆分PCAP文件的函数解决
def IPv4_TCP_HTTP_SESSION(File, 校验):## 记录HTTP各过程信息字典 D_HTTPD_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=对应请求的AckD_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格式头,跳过编号 = 0while 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*4if 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*4TCP部首固定长度 = 20TCP选项长度 = TCP部首实际长度 - TCP部首固定长度## 初始化TCP选项信息MSS = 0 # 本端可以接受的最大实际数据长度(单位字节,不含TCP Header),默认值536,最大65535WSOPT = 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/POSTHTTP_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_PortDstPort = TCP_Destination_PortSeq = TCP_Sequence_NumberAck = TCP_Acknowledgment_Numberif TCP_Flags == 24: # 24 011000 URG ACK PSH RST SYN FINFLAGE = 'ACK PSH'elif TCP_Flags == 16: # 16 010000 URG ACK PSH RST SYN FINFLAGE = 'ACK'elif TCP_Flags == 17: # 17 010001 URG ACK PSH RST SYN FINFLAGE = 'ACK FIN'elif TCP_Flags == 18: # 18 010010 URG ACK PSH RST SYN FINFLAGE = 'ACK SYN'elif TCP_Flags == 2: # 2 000010 URG ACK PSH RST SYN FINFLAGE = 'SYN'elif TCP_Flags == 20: # 20 010100 URG ACK PSH RST SYN FINFLAGE = 'ACK RST'elif TCP_Flags == 4: # 4 000100 URG ACK PSH RST SYN FINFLAGE = 'RST'elif TCP_Flags == 25: # 25 011001 URG ACK PSH RST SYN FINFLAGE = 'A.P.F'elif TCP_Flags == 28: # 28 011100 URG ACK PSH RST SYN FINFLAGE = '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/HTTPHTTP_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'):## 先区分是哪端发的ACKif 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_RPTif (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 FINif 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 FIND_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的ACKif 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+1if 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)breakelse: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 # 如果后面解析不出,就响应码设置为0CL计算期望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':passelif 请求类型 == '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发送请求时间戳 # 单包请求结果为0for 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重发数据包数量 = 0if 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重发数据包数量 += 1Log.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,值等于请求的ackC_REQ提取S_ACK = D_HTTP['D_HTTP_INFO_SEQ'][REQ_ack][0][2]else:C_REQ提取S_ACK = -1Log.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_ACKelif CL计算期望S回应ACK in D_HTTP['D_S_ACK_DATA']: ## 初始值是负数,没有找到可用值时使用初始可以保证这个判断失败Log.debug(f" 【I】使用(Content-Length计算)找到的响应信息")S响应ACK = CL计算期望S回应ACKelif 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))continueelif 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))continueelse: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 = 0for i in S发送响应数据包列表:Log.debug(f" {请求类型} 响应信息 {i}")R_REQ_DATA_LEN += i[3]## S重发响应数据包,(Seq,Ack)重复的包S重发数据包数量 = 0if 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重发数据包数量 += 1Log.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 给 Sif D_HTTP['C_SYN'] != ():C发起TCP连接请求时间戳 = D_HTTP['C_SYN'][0]Log.debug(f"C发起TCP连接请求时间戳 {C发起TCP连接请求时间戳}")## 第2步,S 发 ACK SYN 给 Cif D_HTTP['S_ACK_SYN'] != ():S发出TCP连接确认时间戳 = D_HTTP['S_ACK_SYN'][0]Log.debug(f"S发出TCP连接确认时间戳 {S发出TCP连接确认时间戳}")## 第3步,C 回应 S ACKS_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+1if 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'] = iLog.debug(f"C发出TCP连接确认时间戳 {i[0]}")breakTCP建立连接耗时 = 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 + LENLEN = 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+LENfor i in range(1, 数量):if L[i][1] == SEQ_NEXT:SEQ_NEXT += L[i][3] # LENLV.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 + Lenfor j in range(0, LEN):if LL[j][0][1] == NEXT: # j 第一个元素 Seqfor x in LL[j]: # LL[j] 的元素全部追加到 LL[i] 的后面LL[i].append(x)del(LL[j]) # 然后删除 LL[j]STOP = 1break # 跳出内层循环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 = 0for j in LL: # 遍历二维列表的每一行列表if j[ID] == 0: # 这行的这个位置空着j[ID] = i # 填入X = 1 # 标记有空位,已经填入breakif 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"元素个数列表 {元素个数列表}")## 一行一种组合,组合数等于各种元素数量相乘组合数量 = 1for i in 元素个数列表:组合数量 = 组合数量*iLog.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:编号 += 1PacketHeader_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*4if 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*4TCP部首固定长度 = 20TCP选项长度 = 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]:breakN+=1f.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 += RLog.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 = 0while 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': # IPpasselif FrameType == b'\x81\x00': # VLANPacketData = 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 FINTCP_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 # 32ACK = TCP_Flags_B_int & 0b00010000 # 16#PSH = TCP_Flags_B_int & 0b00001000 # 8RST = TCP_Flags_B_int & 0b00000100 # 4SYN = TCP_Flags_B_int & 0b00000010 # 2FIN = 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(" 忽略")passelse: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)计数 = 1for 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字节头信息,忽略计数 += 1N = 0while 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': # 普通包passelif 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 FINTCP_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 # 32ACK = TCP_Flags_B_int & 0b00010000 # 16#PSH = TCP_Flags_B_int & 0b00001000 # 8RST = TCP_Flags_B_int & 0b00000100 # 4SYN = TCP_Flags_B_int & 0b00000010 # 2FIN = 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(" 忽略")passelse: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 = 时间戳 + 微秒/1000000return(时间戳_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) # BIP_Version = IP_B >> 4 # 取1字节的前4位IP_Header_Length = IP_B & 0xF # 取1字节的后4位IP_Flags = IP_H >> 13IP_Fragment_offset = IP_H & 0b0001111111111111SrcIP_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 >> 12TCP_Flags = TCP_H & 0b0000000000111111## 根据TCP部首长度计算是否有TCP可选字段TCP部首实际长度 = TCP_Data_Offset*4TCP部首固定长度 = 20TCP选项长度 = 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 ## 固定为4N += Length ## 更新N为实际已经读取了的字数elif Kind == b'\x01':## 补位填充N +=1elif Kind == b'\x00':## 选项列表结束N +=1elif Kind == b'\x03':## 窗口扩大系数Length = 3 ## 固定为3N += Lengthelif Kind == b'\x04':## 支持SACKLength = 2 ## 固定为2N += Lengthelif 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 += Lengthelif Kind == b'\x08':## Timestamps(随时间单调递增的值)Length = 10 ## 固定为10N += Lengthelif Kind == b'\x13': # 19## MD5认证Length = 18 ## 固定为18N += Lengthelif Kind == b'\x1c': # 28## 超过一定闲置时间后拆除连接Length = 4 ## 固定为4N += Lengthelif Kind == b'\x1d': # 29## 认证(可选用各种算法)## 长度不固定,存在后续1个字节中Length = struct.unpack('B', BytesData[N+1:N+2])[0]N += Lengthelif Kind == b'\xfe': # 254## 科研实验保留## 长度不固定,存在后续1个字节中Length = struct.unpack('B', BytesData[N+1:N+2])[0]N += Lengthelse:breakreturn(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 & 0b111111111111return(VID)## 返回文本信息格式列表
def P2S(File):L_PACK_INFO = []f = open(File,'rb')PCAP_DATA = f.read(24) # 读取24字节P2S_Pcap_Header(PCAP_DATA)编号 = 0while 1:DATA = f.read(16) # packet数据包头if not DATA:break编号 += 1#if 编号 == 6:# breakL_SACK_INFO = []VID = 0HTTP_REQ = '----' # HTTP/GET/POSTHTTP_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## FLAGEif TCP_Flags == 24: # 24 011000 URG ACK PSH RST SYN FINFLAGE = 'ACK PSH'elif TCP_Flags == 16: # 16 010000 URG ACK PSH RST SYN FINFLAGE = 'ACK'elif TCP_Flags == 17: # 17 010001 URG ACK PSH RST SYN FINFLAGE = 'ACK FIN'elif TCP_Flags == 18: # 18 010010 URG ACK PSH RST SYN FINFLAGE = 'ACK SYN'elif TCP_Flags == 2: # 2 000010 URG ACK PSH RST SYN FINFLAGE = 'SYN'elif TCP_Flags == 20: # 20 010100 URG ACK PSH RST SYN FINFLAGE = 'ACK RST'elif TCP_Flags == 4: # 4 000100 URG ACK PSH RST SYN FINFLAGE = 'RST'elif TCP_Flags == 25: # 25 011001 URG ACK PSH RST SYN FINFLAGE = 'A.P.F'elif TCP_Flags == 28: # 28 011100 URG ACK PSH RST SYN FINFLAGE = '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 = 0for SQL_CMD in L_SQL_CMD:try:游标对象.execute(SQL_CMD)except Exception as e:失败信息 = 'SQL语句 ' + SQL_CMD + ' 执行失败 ' + str(e)breakelse: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_HTTPD_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=对应请求的AckD_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'):## 先区分是哪端发的ACKif SrcPort == D_HTTP['CLIENT'][1]:## 客户端发ACK## Seq = C发的这个包的数据编号(累计值,不含当次数据,不含重发)## Ack = C确认已经接到S数据的编号(丢包/乱序信息放在TCP选项SACK中发送)## 判断是否有数据if TCP数据长度 == 0:passelif 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_RPTif (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 FINif 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 FIND_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的ACKif 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+1if 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)breakelse: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发送请求时间戳 # 单包请求结果为0for 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重发数据包数量 = 0if 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重发数据包数量 += 1Log.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,值等于请求的ackC_REQ提取S_ACK = D_HTTP['D_HTTP_INFO_SEQ'][REQ_ack][0][2]else:C_REQ提取S_ACK = -1Log.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_ACKelif 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))continueelif 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))continueelse: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 = 0for i in S发送响应数据包列表:Log.debug(f" {请求类型} 响应信息 {i}")R_REQ_DATA_LEN += i[3]## S重发响应数据包,(Seq,Ack)重复的包S重发数据包数量 = 0if 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重发数据包数量 += 1Log.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] # 客户端IPC_Port = PACK_INFO[1] # 客户端PORTS_IP = PACK_INFO[2] # 服务器IPS_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] # 客户端IPC_Port = PACK_INFO[1] # 客户端PORTS_IP = PACK_INFO[2] # 服务器IPS_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 = 0File = '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 指定要查看的请求/回应第一个数据包的AckFileName = '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连接的:客户端IPC_Port = 50021 # 6 要查询TCP连接的:客户端PORTS_IP = '172.16.1.80' # 6 要查询TCP连接的:服务器IPS_Port = 80 # 6 要查询TCP连接的:服务器PORTTABLE_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_PORTwhile 1:if os.path.exists(WorkDirName):WorkDirName += 'A'else:os.makedirs(WorkDirName)breakprint(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_PORTwhile 1:if os.path.exists(WorkDirName):WorkDirName += 'X'else:os.makedirs(WorkDirName)breakprint(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)文件计数 = 1for 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)文件计数 = 1for FileName in L_FileName:print(f"解析PCAP文件:{FileName} 进度:{文件计数}/{文件数量}")文件计数 += 1L_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=2132538915URL /a8/aos_xprint_export.jscript?version=1575020160000Content-Length = -1数据包编号正常(单包请求忽略检查)GET 请求信息 (1610681582.616766, 2477791999, 2132538915, 702, 257, 11)C 发送 GET 数据统计:正常 1 个,共 702 字节,重发 0 个C 发送 GET 时间 1610681582.616766C 发完 GET 时间 1610681582.616766C 发完 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.675485S 发完响应数据时间 1610681582.675485S 发完响应数据耗时 0.000000 (不含重发数据的时间)
S 处理 GET 耗时 0.058719 秒 (S发送响应数据首包时间 - C发送请求数据尾包时间)
完成 GET 总耗时 0.058719 秒 (S发送响应数据尾包时间 - C发送请求数据首包时间)C_REQ_Header {'Request': 'POST', 'URL': 请求行和请求头信息,很长的,示例不写了}
C 发送 POST 请求 ACK=1112405539URL /a8/submitContent-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.875227C 发完 POST 时间 1610721407.875259C 发完 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.097313S 发完响应数据时间 1610721408.097613S 发完响应数据耗时 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;
用python分析HTTP请求中各过程用时情况(通过分析抓包文件pcap实现)相关推荐
- 使用代码从抓包文件中提取H264码流
1 从抓包文件中过滤出包含单条流的RTP包 使用ssrc 或者 payload type过滤皆可,具体如下图: 2 使用wireshark可以提前分析下本条码流到底有没有丢包 这样就可以预先知道这条流 ...
- 服务网关zuul之二:过滤器--请求过滤执行过程(源码分析)
Zuul的核心是一系列的过滤器,这些过滤器可以完成以下功能: 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求. 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生成 ...
- 精尽Spring MVC源码分析 - 一个请求的旅行过程
我们先来了解一个请求是如何被 Spring MVC 处理的,由于整个流程涉及到的代码非常多,所以本文的重点在于解析整体的流程,主要讲解 DispatcherServlet 这个核心类,弄懂了这个流程后 ...
- 使用Python来分离或者直接抓取pcap抓包文件中的HTTP流
Python是世界上最好的语言!它使用不可见的制表键作为其语法的一部分! Vim和Emacs的区别在于,它可以帮助乌干达的儿童... 不讨论哲学,不看第一印象,也没有KPI相逼,但是 Python真的 ...
- python自动化办公第二节_自动化测试第二节-jmeter关联+抓包+python基础
1.jmeter关联:从上一个请求中获取返回值提供给下一个请求使用 2.解决jmeter乱码问题: 打开apache-jmeter-2.11\bin\jmeter.properties文件,搜索&qu ...
- zigbee入网过程深入解析(Ubiqua抓包)
一.抓包工具Ubiqua的Traffic介绍 添加或者隐藏Traffic View中的内容:在抓包左上角中选择"tools" → "options"→ &quo ...
- python获取post请求中的所有参数_Django从POST reques获取请求参数
我有一个表单,你需要填写使用一个POST请求,但我希望结果是一个重定向到我的网站上的不同页面.有时我希望用户被重定向到配置文件页,有时重定向到购买页.在 因此,我将重定向URI放入发布的表单页面URL ...
- python的post请求中加参数_Python 模拟post请求
# coding:utf-8 import requests url = "https://passport.cnblogs.com/user/signin" # 接口地址 # 消 ...
- 软件测试中什么是正交分析法,软件测试中正交法设计测试用例实例分析
说明:首先分析第一个要素"用户权限",其取值只有2个,即"用户权限"的值只有可能是1或2,这样3和4的取值所在的情况就可以删除,之后在考虑"用户权限& ...
最新文章
- Zepto源码分析-event模块
- Oracle数据库----函数
- 解读 Q_D, Q_Q 指针
- matlab中矩阵的左除右除
- Dreamweaver 2020安装教程
- [BZOJ2125]最短路(圆方树DP)
- 请设计一个栈,实现十进制数转任意进制数。
- 基于混合云存储系统的电影推荐引擎小结
- 利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持
- LeetCode 1057. 校园自行车分配(map有序+贪心)
- tocmat linux搭建测试环境,Apache+Tomcat 环境搭建(JK部署过程)
- 记录——《C Primer Plus (第五版)》第十一章编程练习第四题
- java运行 .class文件_运行java的class文件方法详解
- 修改动态表情包【保姆级教学】
- pkg打包node项目
- matlab的textscan与textread区别(转)
- [代码审计]DuxCMS 2.0审计
- linux下安装点歌系统,V8点歌系统
- linux c 删除文件,linux c remove 删除文件或目录函数
- 汉诺塔详解(超详细)