Scapy解析PCAP时answers方法无法匹配全部HTTP请求响应对
问题描述
现象更新
我尝试使用zeek解析PCAP文件(执行命令
zeek -C -r sample.pcap),生成的http.log文件共有7条记录,由此可确定Scapy存在解析异常,推测问题出在数据包解封装、识别HTTPResponse层的环节。
核心需求为从pcap文件中完成HTTP请求与对应响应的映射匹配,最初实现时调用搭配TCPSession参数的sniff()函数,代码如下:
from scapy.all import * load_layer("http") pcap_file = "sample.pcap" def callback_method(packet): if packet.haslayer('HTTPRequest'): request_list.append(packet) elif packet.haslayer('HTTPResponse'): response_list.append(packet) else: logging.debug("other") logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S') request_list = [] response_list = [] sessions = sniff(offline=pcap_file, prn=callback_method, session=TCPSession).sessions() logging.info(f"Number of HTTPRequest: {len(request_list)}") logging.info(f"Number of HTTPResponse: {len(response_list)}") for http_request in request_list: for http_response in response_list: if http_response.answers(http_request) == 1: logging.info(f"HTTPResponse found !!!") break
代码运行输出如下:
06-Jun-22 14:46:04 - INFO - Number of HTTPRequest: 7 06-Jun-22 14:46:04 - INFO - Number of HTTPResponse : 7 06-Jun-22 14:46:04 - INFO - HTTPResponse found !!! 06-Jun-22 14:46:04 - INFO - HTTPResponse found !!! 06-Jun-22 14:46:04 - INFO - HTTPResponse found !!! 06-Jun-22 14:46:04 - INFO - HTTPResponse found !!!
运行结果不符合预期:pcap文件中实际存在7条HTTP请求与对应的7条HTTP响应,但代码仅匹配到4组请求-响应对,因此怀疑http_response.answers(http_request)方法存在偶发的匹配失效问题。
验证过程
使用tshark执行如下命令校验TCP流与HTTP事务对应关系:
tshark -r "$1" -2 -R "tcp and (http.request or http.response)" -T fields -e tcp.stream | sort -n | uniq
命令输出结果为:
20 20 90 90 91 91 99 100 100 99 108 108 122 122
该结果证实pcap内确实存在7组完整的HTTP请求-响应对,分别对应TCP流编号20、90、91、99、100、108、122。
问题根因
answers()方法不存在偶发匹配失效的bug,匹配不全的核心原因有两点:
- 匹配逻辑设计缺陷:最初实现把所有请求、响应分别存入两个全局列表做双层全量循环,一旦某个响应匹配到靠前的请求就直接break,但
answers()本身仅校验五元组、TCP序列号/确认号的基础对应关系,不会识别HTTP层事务边界,很容易出现同流内多个请求误匹配同一个响应、后续请求找不到对应响应的问题。 - Scapy HTTP解析兼容性问题:虽然开启了
TCPSession做TCP流重组,但Scapy自带的HTTP层解析对跨TCP段拆分的HTTP头、HTTP chunked编码场景兼容不足,部分HTTPResponse层的TCP序列号、确认号字段没有被正确赋值,会直接导致answers()校验不通过,这也和zeek能正常解析出7条完整HTTP记录的现象吻合。
修复方案
不要用全局列表全量遍历匹配,直接基于TCP会话维度做同流顺序匹配,匹配逻辑完全贴合HTTP 1.1同TCP流串行处理事务的特性,可靠性更高:
- 遍历
sniff()返回的sessions对象,过滤非TCP类会话 - 对单条TCP流内的所有数据包按时间戳排序,还原真实传输顺序
- 同流内按顺序遍历,遇到HTTPRequest就标记为待匹配请求,遇到的第一个符合
answers()校验的HTTPResponse就是该请求的对应响应,匹配完成后清空待匹配标记
修正后的核心实现代码如下:
from scapy.all import * import logging load_layer("http") pcap_file = "sample.pcap" logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S') matched_pairs = [] sessions = sniff(offline=pcap_file, session=TCPSession).sessions() for session_key, packets in sessions.items(): # 过滤非TCP会话 if "TCP" not in session_key: continue # 按时间戳重排同流数据包,保证顺序和实际传输一致 sorted_pkts = sorted(packets, key=lambda x: x.time) pending_req = None for pkt in sorted_pkts: if pkt.haslayer('HTTPRequest'): # 遇到新请求,更新待匹配请求标记 pending_req = pkt elif pkt.haslayer('HTTPResponse') and pending_req is not None: if pkt.answers(pending_req): matched_pairs.append((pending_req, pkt)) logging.info("HTTPResponse found !!!") # 匹配完成后清空待匹配标记,避免误匹配后续响应 pending_req = None logging.info(f"Total matched HTTP request-response pairs: {len(matched_pairs)}")
内容的提问来源于stack exchange,提问作者Jacques Coeur
相关产品推荐
相关产品推荐

