之前用 FIFO 匹配(响应匹配第一个 pending 请求),但 TCP 分段导致响应包交错时,响应2可能先于响应1到达,FIFO 就会错配。
修复方案:ack 精确匹配
TCP 可靠传输保证: 响应的 ack = 请求的 seq + 请求 payload 长度 (ack 指向下一个期望接收的字节)。
有个小问题,作者只是解析的ipv4,没解析ipv6,如果你nginx设置的localhost然后localhost里边的hosts设置有ipv4和ipv6 2种,可能你访问的nginx的https会轮询匹配你的ipv4和v62种地址这样你会怀疑你抓包的2个接口只抓到1个,这样需要你改nginx的地址为localhost改为127.0.0.1,这样nginx只会代理到ipv4,然后解析就正常了,或者尝试改我给你的解析代码支持ipv6,因为作者只是测试,就不改了。
如果你嫌debug打印的多,可以把代码种的debug去掉,作者是为了更直观的看输出,解决问题打印的。
最终代码:
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import psutil
import time
import socket
import struct
import json
import gzip
import io
from datetime import datetime
from collections import defaultdict, deque
import re
class FullAPITrafficAnalyzer:
"""修复版 - 使用TCP序列号匹配同一连接的多个请求"""
def __init__(self, target_interface=None, monitor_ports=None, target_api_paths=None):
self.target_interface = target_interface
# 监控端口列表,默认常见HTTP端口,可通过参数传入
self.monitor_ports = monitor_ports if monitor_ports else [80, 443, 8080, 8000, 8111, 8443, 81]
# 目标API路径前缀列表,支持多个,命中任一即捕获
self.target_api_paths = target_api_paths if target_api_paths else ['/yjz_api_audit', '/webserver/api']
# 存储未匹配的请求 - 使用 (src_ip, src_port, dest_ip, dest_port, seq) 作为key
self.pending_requests = {} # key: (src_ip, src_port, dest_ip, dest_port, seq)
# 存储每个连接的最后seq,用于匹配响应
self.connection_seq = defaultdict(dict) # (src_ip, src_port, dest_ip, dest_port) -> {req_seq: request_data}
self.http_methods = {
'GET', 'POST', 'PUT', 'DELETE', 'PATCH',
'HEAD', 'OPTIONS', 'CONNECT', 'TRACE'
}
self.running = True
self.request_counter = 0
# 统计
self.stats = {
'total_requests': 0,
'api_requests': 0,
'by_method': defaultdict(int),
'by_status': defaultdict(int),
'by_endpoint': defaultdict(int),
'request_response_pairs': deque(maxlen=100),
'start_time': datetime.now()
}
def _decode_chunked(self, body_bytes):
"""解码chunked传输编码,拼接所有chunk的数据部分(字节层面操作)"""
try:
result = b''
remaining = body_bytes
while remaining:
# 找到chunk长度行(字节层面查找\r\n)
line_end = remaining.find(b'\r\n')
if line_end == -1:
break
# 解析chunk长度(十六进制)
size_str = remaining[:line_end].strip()
# 忽略chunk扩展(分号后的内容)
sep = size_str.find(b';')
if sep != -1:
size_str = size_str[:sep].strip()
try:
chunk_size = int(size_str, 16)
except:
# 不是合法的chunk长度行,当作普通body返回
return body_bytes
if chunk_size == 0:
break # 结束chunk
# 跳过长度行,取chunk数据
remaining = remaining[line_end + 2:]
chunk_data = remaining[:chunk_size]
result += chunk_data
# 跳过chunk数据 + \r\n
remaining = remaining[chunk_size + 2:]
return result
except:
return body_bytes
def _decode_gzip(self, body_bytes):
"""解压gzip压缩的响应体,还原明文(字节层面操作)"""
try:
decompressed = gzip.decompress(body_bytes)
return decompressed
except:
# 解压失败(可能数据不完整或被TCP分段),返回原始bytes
return body_bytes
def parse_http_messages(self, data):
"""解析payload中可能包含的多个HTTP消息(同一TCP包里可能有多个响应)
在字节层面扫描,只检查行首是否为HTTP方法或HTTP/版本,避免二进制body内容误匹配"""
if not data:
return []
# 在字节层面按\r\n分割,但保留原始字节引用
# 找到所有HTTP消息起始位置(字节偏移)
positions = []
for method in self.http_methods:
method_bytes = method.encode('ascii') + b' '
start = 0
while True:
pos = data.find(method_bytes, start)
if pos == -1:
break
# 确保是行首(开头或前面是\r\n)
if pos == 0 or data[pos-2:pos] == b'\r\n':
positions.append(pos)
start = pos + 1
# 查找响应起始位置 HTTP/x.x
start = 0
while True:
pos = data.find(b'HTTP/', start)
if pos == -1:
break
if pos == 0 or data[pos-2:pos] == b'\r\n':
positions.append(pos)
start = pos + 1
positions = sorted(set(positions))
if not positions:
return []
messages = []
for i, start_pos in enumerate(positions):
end_pos = positions[i + 1] if i + 1 < len(positions) else len(data)
segment = data[start_pos:end_pos]
msg = self._parse_single_http_message(segment)
if msg:
messages.append(msg)
# 调试: 打印解析结果
if messages:
print(f"\n[DEBUG parse_http_messages] 找到 {len(messages)} 个HTTP消息:")
for i, m in enumerate(messages):
if m['type'] == 'request':
print(f" [{i}] 请求: {m['method']} {m.get('path', '?')}")
else:
print(f" [{i}] 响应: {m.get('status_code', '?')}")
return messages
def _parse_single_http_message(self, data_bytes):
"""解析单个HTTP消息(请求或响应),在字节层面操作以正确处理二进制body"""
try:
# 在字节层面找header和body的分界 \r\n\r\n
header_end = data_bytes.find(b'\r\n\r\n')
if header_end == -1:
# 没有完整的header结束标记,尝试整个当作header
header_part = data_bytes
body_bytes = b''
else:
header_part = data_bytes[:header_end]
body_bytes = data_bytes[header_end + 4:]
# header部分是ASCII,可以安全decode
try:
header_text = header_part.decode('ascii', errors='ignore')
except:
header_text = header_part.decode('utf-8', errors='ignore')
lines = header_text.split('\r\n')
if not lines:
return None
first_line = lines[0]
parts = first_line.split(' ')
if len(parts) < 3:
return None
# 判断是请求还是响应
if parts[0] in self.http_methods:
method, path, version = parts[0], parts[1], parts[2]
path_part = path
query_string = ''
if '?' in path:
path_part, query_string = path.split('?', 1)
result = {
'type': 'request',
'method': method,
'path': path_part,
'full_path': path,
'query_string': query_string,
'version': version
}
elif parts[0].startswith('HTTP/'):
version, status_code = parts[0], parts[1]
status_text = ' '.join(parts[2:]) if len(parts) > 2 else ''
try:
status_code_int = int(status_code)
except:
return None
result = {
'type': 'response',
'version': version,
'status_code': status_code_int,
'status_text': status_text
}
else:
return None
# 解析headers
headers = {}
for line in lines[1:]:
if ': ' in line:
key, value = line.split(': ', 1)
headers[key.lower()] = value
result['headers'] = headers
# 判断响应body是否完整(TCP分段可能导致body不完整)
is_body_complete = True
if result['type'] == 'response':
if headers.get('transfer-encoding', '').lower() == 'chunked':
# chunked: 检查结束标记 0\r\n\r\n
if body_bytes.find(b'\r\n0\r\n\r\n') == -1 and body_bytes.find(b'\r\n0\r\n') == -1:
is_body_complete = False
elif 'content-length' in headers:
# 有content-length: 检查body长度
try:
expected_len = int(headers['content-length'])
if len(body_bytes) < expected_len:
is_body_complete = False
except:
pass
elif headers.get('connection', '').lower() == 'close':
# 连接关闭模式,body到连接结束,单包无法判断
pass
result['is_body_complete'] = is_body_complete
# 处理chunked编码 - 在字节层面解码
if headers.get('transfer-encoding', '').lower() == 'chunked':
body_bytes = self._decode_chunked(body_bytes)
# 处理gzip压缩 - 在字节层面解压
if headers.get('content-encoding', '').lower() == 'gzip' and body_bytes:
body_bytes = self._decode_gzip(body_bytes)
# body转成字符串显示
try:
body = body_bytes.decode('utf-8', errors='ignore')
except:
body = body_bytes.decode('latin-1', errors='ignore')
result['body'] = body
# 尝试解析JSON
if body:
try:
json_body = json.loads(body)
result['json_body'] = json_body
except:
pass
# 检查是否包含目标API路径(匹配任一前缀)
is_target_api = False
if result['type'] == 'request':
for api_path in self.target_api_paths:
if api_path in path:
is_target_api = True
break
result['is_target_api'] = is_target_api
return result
except Exception as e:
return None
def parse_ip_packet(self, data):
"""解析IP包 - 自动识别链路层类型(Ethernet 14字节 / LINUX_SLL 16字节)"""
try:
if len(data) < 34:
return None
# 自动识别链路层类型
# Ethernet: data[12:14] == 0x0800 (IPv4), IP头从14开始
# LINUX_SLL: data[14:16] == 0x0800 (IPv4), IP头从16开始
link_offset = None
if len(data) >= 16:
eth_type = struct.unpack('!H', data[12:14])[0]
if eth_type == 0x0800:
link_offset = 14 # Ethernet
else:
sll_type = struct.unpack('!H', data[14:16])[0]
if sll_type == 0x0800:
link_offset = 16 # LINUX_SLL
if link_offset is None:
return None
ip_header = data[link_offset:link_offset + 20]
if len(ip_header) < 20:
return None
iph = struct.unpack('!BBHHHBBH4s4s', ip_header)
version_ihl = iph[0]
ihl = version_ihl & 0xF
return {
'src_ip': socket.inet_ntoa(iph[8]),
'dest_ip': socket.inet_ntoa(iph[9]),
'protocol': iph[6],
'header_len': ihl * 4,
'total_len': iph[2],
'link_offset': link_offset # 传给传输层解析用
}
except:
return None
def parse_transport(self, data, ip_info):
"""解析传输层 - 特别注意获取seq"""
try:
# 使用链路层偏移 + IP头长度,而非硬编码14
offset = ip_info['link_offset'] + ip_info['header_len']
if ip_info['protocol'] == 6: # TCP
tcp_header = data[offset:offset+20]
tcp = struct.unpack('!HHLLBBHHH', tcp_header)
return {
'src_port': tcp[0],
'dest_port': tcp[1],
'seq': tcp[2], # 序列号 - 用于匹配
'ack': tcp[3], # 确认号
'header_len': (tcp[4] >> 4) * 4,
'flags': tcp[5] & 0x3F
}
return None
except:
return None
def extract_payload(self, data, ip_info, transport_info):
"""提取payload数据"""
try:
# 使用链路层偏移 + IP头长度 + TCP头长度
offset = ip_info['link_offset'] + ip_info['header_len'] + transport_info['header_len']
return data[offset:]
except:
return None
def print_api_request(self, http_info, ip_info, transport_info, request_id):
"""打印API请求"""
endpoint = http_info['path'].split('/')[-1] if http_info['path'] else 'unknown'
print(f"\n{'='*120}")
print(f"📤 请求 #{request_id} [{datetime.now().strftime('%H:%M:%S.%f')[:-3]}]")
print(f"{'='*120}")
print(f" 方法: {http_info['method']}")
print(f" 端点: {endpoint}")
print(f" 路径: {http_info['full_path']}")
print(f" 来源: {ip_info['src_ip']}:{transport_info['src_port']}")
print(f" 目标: {ip_info['dest_ip']}:{transport_info['dest_port']}")
print(f" TCP Seq: {transport_info['seq']}") # 显示seq用于调试
# 显示Headers
important_headers = ['host', 'user-agent', 'content-type', 'cookie']
for key in important_headers:
if key in http_info.get('headers', {}):
value = http_info['headers'][key]
if key == 'cookie':
value = value[:30] + '...' if len(value) > 30 else value
print(f" {key}: {value}")
# 显示JSON Body
if http_info.get('json_body'):
print(f"\n 📦 请求JSON Body:")
print(f" {json.dumps(http_info['json_body'], ensure_ascii=False, indent=4)}")
print(f"{'='*120}")
def print_api_response(self, http_info, ip_info, transport_info, request_info=None):
"""打印API响应"""
endpoint = request_info.get('request', {}).get('path', '').split('/')[-1] if request_info else 'unknown'
print(f"\n{'='*120}")
print(f"📥 响应 [{datetime.now().strftime('%H:%M:%S.%f')[:-3]}]")
print(f"{'='*120}")
print(f" 状态码: {http_info['status_code']} {http_info.get('status_text', '')}")
print(f" 端点: {endpoint}")
print(f" 来源: {ip_info['src_ip']}:{transport_info['src_port']}")
print(f" 目标: {ip_info['dest_ip']}:{transport_info['dest_port']}")
# 显示响应Headers
if 'content-type' in http_info.get('headers', {}):
print(f" content-type: {http_info['headers']['content-type']}")
# 显示响应JSON
if http_info.get('json_body'):
print(f"\n 📦 响应JSON Body (完整):")
print(f" {json.dumps(http_info['json_body'], ensure_ascii=False, indent=4)}")
elif http_info.get('body'):
clean_body = http_info['body'].strip()
try:
if clean_body and (clean_body.startswith('{') or clean_body.startswith('[')):
json_data = json.loads(clean_body)
print(f"\n 📦 响应JSON Body (完整):")
print(f" {json.dumps(json_data, ensure_ascii=False, indent=4)}")
else:
print(f"\n 📄 响应Body:")
print(f" {clean_body[:500]}")
except:
print(f"\n 📄 响应Body:")
print(f" {clean_body[:500]}")
# 显示响应时间
if request_info:
req_time = request_info.get('timestamp')
if req_time:
elapsed = (datetime.now() - req_time).total_seconds() * 1000
print(f"\n ⏱️ 响应时间: {elapsed:.2f}ms")
req = request_info.get('request', {})
print(f" 🔗 对应请求: #{request_info.get('id')} {req.get('method')} {endpoint}")
print(f"{'='*120}\n")
def capture_packets(self, count=0):
"""捕获数据包"""
try:
sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(3))
if self.target_interface:
sock.bind((self.target_interface, 0))
print(f"\n{'='*120}")
print(f" 🎯 API流量监控 (使用TCP Seq匹配)")
print(f" 监控网卡: {self.target_interface or '所有网卡'}")
print(f" 目标路径: {', '.join(self.target_api_paths)}")
print(f" 监控端口: 80, 443, 8080, 8000, 8111, 8443")
print(f"{'='*120}\n")
captured = 0
while self.running and (count == 0 or captured < count):
try:
data, addr = sock.recvfrom(65535)
ip_info = self.parse_ip_packet(data)
if not ip_info:
continue
if ip_info['protocol'] == 6:
transport_info = self.parse_transport(data, ip_info)
if transport_info:
if (transport_info['src_port'] in self.monitor_ports or
transport_info['dest_port'] in self.monitor_ports):
payload = self.extract_payload(data, ip_info, transport_info)
if payload and len(payload) > 10:
# 调试: 打印每个通过过滤的payload开头
preview = payload[:120].decode('utf-8', errors='ignore').replace('\r', '\\r').replace('\n', '\\n')
print(f"[DEBUG payload] {ip_info['src_ip']}:{transport_info['src_port']} -> {ip_info['dest_ip']}:{transport_info['dest_port']} seq={transport_info['seq']} len={len(payload)}")
print(f" 开头: {preview}")
payload_len = len(payload)
http_messages = self.parse_http_messages(payload)
for msg_idx, http_info in enumerate(http_messages):
self.process_message(http_info, ip_info, transport_info, msg_idx, payload_len)
if http_info['type'] == 'request' and http_info.get('is_target_api'):
captured += 1
if count > 0 and captured >= count:
print(f"\n✅ 已捕获 {captured} 个API请求")
break
except KeyboardInterrupt:
print("\n\n⏹️ 捕获已停止")
break
except:
pass
sock.close()
self.print_summary()
except PermissionError:
print("❌ 错误: 需要root权限! 请使用: sudo python3 api_analyzer.py")
except Exception as e:
print(f"❌ 错误: {e}")
def process_message(self, http_info, ip_info, transport_info, msg_idx=0, payload_len=0):
"""处理HTTP消息 - 使用seq匹配"""
if http_info['type'] == 'request':
# 只处理目标API请求(匹配任一前缀)
if http_info.get('is_target_api'):
# 使用TCP序列号+msg_idx作为唯一标识
# msg_idx区分同一TCP包里的多个HTTP消息
conn_key = (
ip_info['src_ip'],
transport_info['src_port'],
ip_info['dest_ip'],
transport_info['dest_port'],
transport_info['seq'], # 使用seq区分同一连接的不同请求
msg_idx # 区分同一TCP包里的多个HTTP消息
)
# 请求级别去重 - loopback上同一个包会被抓到两次(EGRESS+INGRESS)
# 如果conn_key已存在,说明是重复抓包,跳过
if conn_key in self.pending_requests:
print(f"[DEBUG dedup] 跳过重复请求: {http_info['method']} {http_info.get('path', '?')} seq={transport_info['seq']} msg_idx={msg_idx}")
return
self.stats['api_requests'] += 1
self.request_counter += 1
request_id = self.request_counter
endpoint = http_info['path'].split('/')[-1]
self.stats['by_endpoint'][endpoint] += 1
self.stats['by_method'][http_info['method']] += 1
# 存储请求 - 记录seq和payload_len,用于响应的ack精确匹配
# 期望的响应ack = 请求seq + payload_len(TCP可靠传输保证)
self.pending_requests[conn_key] = {
'id': request_id,
'request': http_info,
'ip_info': ip_info,
'transport_info': transport_info,
'timestamp': datetime.now(),
'req_seq': transport_info['seq'],
'payload_len': payload_len
}
# 打印请求
self.print_api_request(http_info, ip_info, transport_info, request_id)
# 清理过期请求
current_time = datetime.now()
for key in list(self.pending_requests.keys()):
if (current_time - self.pending_requests[key]['timestamp']).total_seconds() > 30:
del self.pending_requests[key]
elif http_info['type'] == 'response':
# 匹配请求 - 用TCP ack精确匹配请求seq + payload_len
matched_request = None
matched_key = None
# 响应的来源 = 请求的目标, 响应的目标 = 请求的来源
# 响应的ack = 请求的seq + 请求payload长度(TCP可靠传输保证)
response_ack = transport_info.get('ack', 0)
for stored_key, request_data in list(self.pending_requests.items()):
if len(stored_key) >= 6:
s_ip, s_port, d_ip, d_port, seq, m_idx = stored_key
# 检查IP和端口是否匹配(反向)
if (s_ip == ip_info['dest_ip'] and
s_port == transport_info['dest_port'] and
d_ip == ip_info['src_ip'] and
d_port == transport_info['src_port']):
# 用ack精确匹配: 响应的ack应该等于请求seq + payload_len
req_seq = request_data.get('req_seq', 0)
payload_len = request_data.get('payload_len', 0)
expected_ack = req_seq + payload_len
if response_ack == expected_ack:
# 精确匹配
matched_request = request_data
matched_key = stored_key
break
# 如果精确匹配失败,回退到FIFO(同连接同端口最早存入的)
if not matched_request:
for stored_key, request_data in list(self.pending_requests.items()):
if len(stored_key) >= 6:
s_ip, s_port, d_ip, d_port, seq, m_idx = stored_key
if (s_ip == ip_info['dest_ip'] and
s_port == transport_info['dest_port'] and
d_ip == ip_info['src_ip'] and
d_port == transport_info['src_port']):
matched_request = request_data
matched_key = stored_key
break
# 如果匹配到请求
if matched_request:
req_info = matched_request['request']
if req_info.get('is_target_api'):
self.stats['by_status'][http_info['status_code']] += 1
# 检查响应body是否完整
if http_info.get('is_body_complete', True):
# body完整,正常打印响应
self.print_api_response(http_info, ip_info, transport_info, matched_request)
else:
# body不完整(TCP分段截断),标记截断但仍消费请求保证后续配对正确
endpoint = req_info.get('path', '').split('/')[-1]
print(f"\n{'='*120}")
print(f"📥 响应 [截断] [{datetime.now().strftime('%H:%M:%S.%f')[:-3]}]")
print(f"{'='*120}")
print(f" 状态码: {http_info['status_code']} {http_info.get('status_text', '')}")
print(f" 端点: {endpoint}")
print(f" ⚠️ 响应body不完整(TCP分段截断),跳过body显示")
print(f" 🔗 对应请求: #{matched_request['id']} {req_info.get('method')} {endpoint}")
print(f"{'='*120}\n")
# 保存配对
self.stats['request_response_pairs'].append({
'request_id': matched_request['id'],
'request': req_info,
'response': http_info,
'status_code': http_info['status_code'],
'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
})
# 删除已匹配的请求
if matched_key in self.pending_requests:
del self.pending_requests[matched_key]
def print_summary(self):
"""打印统计摘要"""
runtime = datetime.now() - self.stats['start_time']
print(f"\n\n{'='*120}")
print(f" 📊 API监控统计报告")
print(f"{'='*120}")
print(f" 运行时间: {runtime}")
print(f" API请求数: {self.stats['api_requests']}")
if self.stats['by_endpoint']:
print(f"\n 📌 API端点统计:")
for endpoint, count in sorted(self.stats['by_endpoint'].items(), key=lambda x: x[1], reverse=True):
print(f" {endpoint}: {count} 次")
if self.stats['by_status']:
print(f"\n 📌 响应状态码分布:")
for status, count in sorted(self.stats['by_status'].items(), key=lambda x: x[1], reverse=True):
print(f" {status}: {count} 次")
if self.stats['request_response_pairs']:
print(f"\n 🔄 请求-响应配对:")
for pair in list(self.stats['request_response_pairs']):
req = pair['request']
endpoint = req.get('path', '').split('/')[-1]
print(f" #{pair['request_id']} {req['method']} {endpoint} -> {pair['status_code']}")
print(f"{'='*120}\n")
if __name__ == "__main__":
print("=" * 120)
print(" 🎯 API流量监控 (使用TCP Seq匹配同一连接的多个请求)")
print("=" * 120)
# 显示可用网卡
print("\n📡 可用网卡:")
net_io = psutil.net_io_counters(pernic=True)
for iface in net_io.keys():
counters = net_io[iface]
def format_size(b):
for unit in ['B', 'KB', 'MB', 'GB']:
if b < 1024.0:
return f"{b:.2f} {unit}"
b /= 1024.0
return f"{b:.2f} TB"
print(f" • {iface}: 发送 {format_size(counters.bytes_sent)}, 接收 {format_size(counters.bytes_recv)}")
print("\n💡 注意: 如果API在本地(127.0.0.1),选择 lo 网卡")
print("💡 如果是远程API,选择 ens224 或其他物理网卡")
interface = input("\n输入网卡名称: ").strip() or None
ports_input = input("监控端口 (逗号分隔,留空用默认 80,443,81,8080,8000,8111,8443): ").strip()
if ports_input:
monitor_ports = [int(p.strip()) for p in ports_input.split(',') if p.strip()]
else:
monitor_ports = None
count = input("捕获数量 (留空持续捕获): ").strip()
count = int(count) if count else 0
analyzer = FullAPITrafficAnalyzer(interface, monitor_ports)
analyzer.capture_packets(count)
以上是完整的代码,前置条件
安装python3
安装psutil
如果安装psutil出问题,可以试试
sudo yum groupinstall -y "Development Tools"
sudo yum install -y python3-devel openssl-devel bzip2-devel libffi-devel zlib-devel
pip3 install --upgrade pip setuptools wheel
pip3 install psutil
pip install psutil
使用演示
- 启动分析程序
python api_analyzer.py
输入你的监控网卡,不输入监控所有,这个时候会有点多,有点乱
输入你的端口,不输入就是我默认给的端口
然后继续回车开始监控

- 开始打包
执行你tcpdump抓到的流量包
tcpreplay -i ens192 -t /tmp/api_capture.pcap
这里的ens192是你刚启动监听的网卡,这个pcap包是你抓的某个机器的http流量的包。

- 查看分析程序



python
[DEBUG payload] 192.168.200.34:56702 -> 192.168.200.227:81 seq=3222663193 len=696
开头: GET / HTTP/1.1\r\nHost: 192.168.200.227:81\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 请求: GET /
[DEBUG payload] 192.168.200.227:81 -> 192.168.200.34:56702 seq=2708098702 len=173
开头: HTTP/1.1 304 Not Modified\r\nServer: nginx\r\nDate: Thu, 20 Aug 2026 01:52:48 GMT\r\nLast-Modified: Mon, 03 Aug 2026 07:02:36
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 响应: 304
[DEBUG payload] 192.168.200.34:56702 -> 192.168.200.227:81 seq=3222663889 len=643
开头: POST /webserver/api/v3/nodeConfig/config/isPlatform HTTP/1.1\r\nHost: 192.168.200.227:81\r\nConnection: keep-alive\r\nContent-
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 请求: POST /webserver/api/v3/nodeConfig/config/isPlatform
========================================================================================================================
📤 请求 #1 [11:05:29.725]
========================================================================================================================
方法: POST
端点: isPlatform
路径: /webserver/api/v3/nodeConfig/config/isPlatform
来源: 192.168.200.34:56702
目标: 192.168.200.227:81
TCP Seq: 3222663889
host: 192.168.200.227:81
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
cookie: dbAudit.sid=b6ee336c-59fb-4635...
========================================================================================================================
[DEBUG payload] 192.168.200.34:62980 -> 192.168.200.227:81 seq=2089186009 len=520
开头: GET /oemAssets/logo.png?t=1787191664928 HTTP/1.1\r\nHost: 192.168.200.227:81\r\nConnection: keep-alive\r\nUser-Agent: Mozilla/
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 请求: GET /oemAssets/logo.png
[DEBUG payload] 192.168.200.227:81 -> 192.168.200.34:56702 seq=2708098875 len=751
开头: HTTP/1.1 200 \r\nServer: nginx\r\nDate: Thu, 20 Aug 2026 01:52:48 GMT\r\nContent-Type: application/json;charset=UTF-8\r\nTransfe
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 响应: 200
========================================================================================================================
📥 响应 [11:05:29.725]
========================================================================================================================
状态码: 200
端点: isPlatform
来源: 192.168.200.227:81
目标: 192.168.200.34:56702
content-type: application/json;charset=UTF-8
📦 响应JSON Body (完整):
{
"code": 200,
"tips": "成功",
"attribute": {},
"data": false,
"timestamp": 1787190768296,
"success": true
}
⏱️ 响应时间: 0.78ms
🔗 对应请求: #1 POST isPlatform
========================================================================================================================
[DEBUG payload] 192.168.200.227:81 -> 192.168.200.34:62980 seq=2024175248 len=1260
开头: 9~|#-YO'+ۗN˼j>+;]29<rLM__NkKyݶw9IvEYV.yE4-+ZlE&˹#>լF avŰiJ
[DEBUG payload] 192.168.200.34:62980 -> 192.168.200.227:81 seq=2089186529 len=708
开头: POST /webserver/api/v3/my/info HTTP/1.1\r\nHost: 192.168.200.227:81\r\nConnection: keep-alive\r\nContent-Length: 36\r\nUser-Agen
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 请求: POST /webserver/api/v3/my/info
========================================================================================================================
📤 请求 #2 [11:05:29.726]
========================================================================================================================
方法: POST
端点: info
路径: /webserver/api/v3/my/info
来源: 192.168.200.34:62980
目标: 192.168.200.227:81
TCP Seq: 2089186529
host: 192.168.200.227:81
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
content-type: application/x-www-form-urlencoded
cookie: dbAudit.sid=b6ee336c-59fb-4635...
========================================================================================================================
[DEBUG payload] 192.168.200.227:81 -> 192.168.200.34:62980 seq=2024179428 len=1048
开头: X Y}KZnF\r=(>[[bDO"\ro:Eɗ<zxWrDfC
6fEҝ݈wpM>xo
ׁ]
[DEBUG payload] 192.168.200.34:62980 -> 192.168.200.227:81 seq=2089187237 len=573
开头: GET /webserver/api/v3/global/config HTTP/1.1\r\nHost: 192.168.200.227:81\r\nConnection: keep-alive\r\nUser-Agent: Mozilla/5.0
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 请求: GET /webserver/api/v3/global/config
========================================================================================================================
📤 请求 #3 [11:05:29.726]
========================================================================================================================
方法: GET
端点: config
路径: /webserver/api/v3/global/config
来源: 192.168.200.34:62980
目标: 192.168.200.227:81
TCP Seq: 2089187237
host: 192.168.200.227:81
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
cookie: dbAudit.sid=b6ee336c-59fb-4635...
========================================================================================================================
[DEBUG payload] 192.168.200.227:81 -> 192.168.200.34:62980 seq=2024180476 len=667
开头: HTTP/1.1 200 \r\nServer: nginx\r\nDate: Thu, 20 Aug 2026 01:52:48 GMT\r\nContent-Type: application/json;charset=UTF-8\r\nTransfe
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 响应: 200
========================================================================================================================
📥 响应 [11:05:29.727]
========================================================================================================================
状态码: 200
端点: config
来源: 192.168.200.227:81
目标: 192.168.200.34:62980
content-type: application/json;charset=UTF-8
📦 响应JSON Body (完整):
{
"code": 200,
"tips": "成功",
"attribute": {},
"data": {
"minPasswordLength": 8,
"maxPasswordLength": 32,
"pageSessionTimeInMinute": 15,
"passwordComplexity": 3
},
"timestamp": 1787190768540,
"success": true
}
⏱️ 响应时间: 0.64ms
🔗 对应请求: #3 GET config
========================================================================================================================
[DEBUG payload] 192.168.200.34:54105 -> 192.168.200.227:81 seq=1039551939 len=647
开头: GET /webserver/websocket/v3/installCount HTTP/1.1\r\nHost: 192.168.200.227:81\r\nConnection: Upgrade\r\nPragma: no-cache\r\nCach
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 请求: GET /webserver/websocket/v3/installCount
[DEBUG payload] 192.168.200.227:81 -> 192.168.200.34:54105 seq=262463226 len=502
开头: HTTP/1.1 101 \r\nServer: nginx\r\nDate: Thu, 20 Aug 2026 01:52:48 GMT\r\nConnection: upgrade\r\nAccess-control-Allow-Origin: *\r\n
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 响应: 101
[DEBUG payload] 192.168.200.34:62980 -> 192.168.200.227:81 seq=2089187810 len=592
开头: GET /webserver/api/v3/database/setting/serverGroupList HTTP/1.1\r\nHost: 192.168.200.227:81\r\nConnection: keep-alive\r\nUser-
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 请求: GET /webserver/api/v3/database/setting/serverGroupList
========================================================================================================================
📤 请求 #4 [11:05:29.728]
========================================================================================================================
方法: GET
端点: serverGroupList
路径: /webserver/api/v3/database/setting/serverGroupList
来源: 192.168.200.34:62980
目标: 192.168.200.227:81
TCP Seq: 2089187810
host: 192.168.200.227:81
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
cookie: dbAudit.sid=b6ee336c-59fb-4635...
========================================================================================================================
[DEBUG payload] 192.168.200.34:55569 -> 192.168.200.227:81 seq=2884300098 len=639
开头: GET /webserver/websocket/v3/date HTTP/1.1\r\nHost: 192.168.200.227:81\r\nConnection: Upgrade\r\nPragma: no-cache\r\nCache-Contro
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 请求: GET /webserver/websocket/v3/date
[DEBUG payload] 192.168.200.227:81 -> 192.168.200.34:55569 seq=683040071 len=502
开头: HTTP/1.1 101 \r\nServer: nginx\r\nDate: Thu, 20 Aug 2026 01:52:48 GMT\r\nConnection: upgrade\r\nAccess-control-Allow-Origin: *\r\n
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 响应: 101
[DEBUG payload] 192.168.200.227:81 -> 192.168.200.34:62980 seq=2024181143 len=898
开头: HTTP/1.1 200 \r\nServer: nginx\r\nDate: Thu, 20 Aug 2026 01:52:48 GMT\r\nContent-Type: application/json;charset=UTF-8\r\nTransfe
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 响应: 200
========================================================================================================================
📥 响应 [11:05:29.728]
========================================================================================================================
状态码: 200
端点: serverGroupList
来源: 192.168.200.227:81
目标: 192.168.200.34:62980
content-type: application/json;charset=UTF-8
📦 响应JSON Body (完整):
{
"code": 200,
"tips": "成功",
"attribute": {},
"data": [
{
"id": 1,
"name": "自己"
},
{
"id": 2,
"name": "192.168.101.240_3306"
},
{
"id": 3,
"name": "192.168.200.127_40001"
},
],
"timestamp": 1787190768687,
"success": true
}
⏱️ 响应时间: 0.99ms
🔗 对应请求: #4 GET serverGroupList
========================================================================================================================
..........................
省略了一部分
📤 请求 #6 [11:05:29.730]
========================================================================================================================
方法: POST
端点: riskList
路径: /webserver/api/v3/risk/riskList
来源: 192.168.200.34:62980
目标: 192.168.200.227:81
TCP Seq: 2089189153
host: 192.168.200.227:81
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
content-type: application/json;charset=UTF-8
cookie: dbAudit.sid=b6ee336c-59fb-4635...
📦 请求JSON Body:
{
"dbIdList": [],
"page": {
"current": 1,
"pageSize": 20
}
}
========================================================================================================================
[DEBUG payload] 192.168.200.227:81 -> 192.168.200.34:62980 seq=2024182851 len=762
开头: HTTP/1.1 200 \r\nServer: nginx\r\nDate: Thu, 20 Aug 2026 01:52:49 GMT\r\nContent-Type: application/json;charset=UTF-8\r\nTransfe
[DEBUG parse_http_messages] 找到 1 个HTTP消息:
[0] 响应: 200
========================================================================================================================
📥 响应 [11:05:29.730]
========================================================================================================================
状态码: 200
端点: riskList
来源: 192.168.200.227:81
目标: 192.168.200.34:62980
content-type: application/json;charset=UTF-8
📦 响应JSON Body (完整):
{
"code": 500,
"tips": "服务器错误",
"attribute": {},
"data": null,
"timestamp": 1787190769170,
"success": false
}
⏱️ 响应时间: 0.64ms
🔗 对应请求: #6 POST riskList
========================================================================================================================
[DEBUG payload] 192.168.200.227:81 -> 192.168.200.34:55569 seq=683040607 len=11
开头: )kjT
[DEBUG payload] 192.168.200.227:81 -> 192.168.200.34:54105 seq=262463728 len=16
开头: VJ/+Q26
按ctrl +c
捕获已停止

========================================================================================================================
📊 API监控统计报告
========================================================================================================================
运行时间: 0:05:15.860342
API请求数: 6
📌 API端点统计:
isPlatform: 1 次
info: 1 次
config: 1 次
serverGroupList: 1 次
listDataBaseGroups: 1 次
riskList: 1 次
📌 响应状态码分布:
200: 5 次
🔄 请求-响应配对:
#1 POST isPlatform -> 200
#3 GET config -> 200
#4 GET serverGroupList -> 200
#5 POST listDataBaseGroups -> 200
#6 POST riskList -> 200
========================================================================================================================
抓任意个部署http服务的机器的包的命令,这个时候要登录那个机器执行这个
tcpdump -i ens192 -w /tmp/api_capture.pcap 'tcp port 81' -s 0
执行后,访问这个机器的网页,产生http流量,然后产生了一些,就在机器上执行ctrl+c停止抓包。

拷贝到192.168.200.228的机器,让这个机器分析网络包。
scp /tmp/api_capture.pcap root@192.168.200.228:/tmp/

看看有没有抓到包,下边显示的78就是抓到了78个包。
tcpdump -r /tmp/api_capture.pcap | wc -l
