使用tcpdump+Python Socket实时传输网络流量的异常问题求助
实时TCPDUMP数据包远程传输问题排查与修复
我需要在一台机器上用tcpdump抓包,实时逐个把数据包传到另一台设备,且不能存本地文件。我写了发送端和接收端的Python脚本,但接收端要么丢包,要么数据包损坏。测试直接存本地文件是正常的,求解决方法。
原发送端代码
HOST = "192.168.xx.xx" PORT = 65432 command = ['sudo', 'tcpdump', '-c', '1000', '-i', 'br0', '--packet-buffered', '-w', '-'] process = subprocess.Popen(command, stdout=subprocess.PIPE) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = (HOST, PORT) sock.connect(server_address) for ts, packet_data in dpkt.pcap.Reader(process.stdout): eth_packet = dpkt.ethernet.Ethernet(packet_data) packet_bytes = eth_packet.pack() eth_packet.time = ts sock.sendall(packet_bytes) sock.close()
原接收端代码
HOST = "192.168.xx.xx" PORT = 65432 BUF = 4096 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = (HOST, PORT) sock.bind(server_address) sock.listen(1) print('Waiting for a connection...') connection, sender_address = sock.accept() while True: # Receive data from the socket data = connection.recv(BUF) if not data: break # Parse the received data as a packet with dpkt and write it to the pcap file packet = dpkt.ethernet.Ethernet(data) pcap_writer.writepkt(packet) # Close the PcapWriter and the socket pcap_file.close() connection.close()
问题根源
- TCP粘包/拆包:TCP是流式协议,
recv(BUF)可能一次收到多个数据包片段,或一个数据包被拆成多次接收,直接解析会导致数据损坏。 - 冗余处理错误:发送端用
dpkt.pcap.Reader解析后重新pack(),完全没必要,反而可能引入格式错误,直接转发原始packet_data即可。 - 无数据包边界:接收端无法区分数据包的起始/结束位置,无法判断什么时候是完整的一个包。
- 时间戳丢失:发送端没有传递数据包的时间戳,导致生成的pcap文件时间信息错误。
修复方案
方案1:直接转发原始PCAP数据流(最高效)
发送端直接把tcpdump输出的原始PCAP格式数据转发,无需解析,避免处理错误;接收端直接写入文件,和本地抓包格式完全一致。
修改后的发送端
import socket import subprocess HOST = "192.168.xx.xx" PORT = 65432 # tcpdump直接输出PCAP格式到stdout,--packet-buffered确保实时输出 command = ['sudo', 'tcpdump', '-c', '1000', '-i', 'br0', '--packet-buffered', '-w', '-'] process = subprocess.Popen(command, stdout=subprocess.PIPE) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = (HOST, PORT) sock.connect(server_address) try: # 直接转发tcpdump的原始输出 while True: chunk = process.stdout.read(4096) if not chunk: break sock.sendall(chunk) finally: sock.close() process.wait()
修改后的接收端
import socket HOST = "192.168.xx.xx" PORT = 65432 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = (HOST, PORT) sock.bind(server_address) sock.listen(1) print('Waiting for a connection...') connection, sender_address = sock.accept() try: # 直接将数据流写入PCAP文件 with open('received.pcap', 'wb') as pcap_file: while True: data = connection.recv(4096) if not data: break pcap_file.write(data) finally: connection.close() sock.close()
方案2:带边界标识的逐个数据包传输(需实时处理场景)
如果需要对数据包做实时处理,必须用长度前缀标识每个数据包的边界,同时传递时间戳。
修改后的发送端
import socket import subprocess import struct import dpkt HOST = "192.168.xx.xx" PORT = 65432 command = ['sudo', 'tcpdump', '-c', '1000', '-i', 'br0', '--packet-buffered', '-w', '-'] process = subprocess.Popen(command, stdout=subprocess.PIPE) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = (HOST, PORT) sock.connect(server_address) try: for ts, packet_data in dpkt.pcap.Reader(process.stdout): # 先发送4字节大端序的数据包长度 sock.sendall(struct.pack('!I', len(packet_data))) # 发送数据包内容 sock.sendall(packet_data) # 发送8字节大端序的时间戳(double类型) sock.sendall(struct.pack('!d', ts)) finally: sock.close() process.wait()
修改后的接收端
import socket import struct import dpkt HOST = "192.168.xx.xx" PORT = 65432 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = (HOST, PORT) sock.bind(server_address) sock.listen(1) print('Waiting for a connection...') connection, sender_address = sock.accept() try: # 初始化PCAP Writer,链路类型和抓包接口一致(以太网为DLT_EN10MB) with open('received.pcap', 'wb') as pcap_file: pcap_writer = dpkt.pcap.Writer(pcap_file, linktype=dpkt.pcap.DLT_EN10MB) while True: # 接收4字节的数据包长度 len_data = connection.recv(4) if not len_data: break packet_len = struct.unpack('!I', len_data)[0] # 接收完整的数据包内容 packet_data = b'' while len(packet_data) < packet_len: chunk = connection.recv(packet_len - len(packet_data)) if not chunk: break packet_data += chunk if len(packet_data) != packet_len: break # 数据包不完整,终止 # 接收时间戳 ts_data = connection.recv(8) if not ts_data: break ts = struct.unpack('!d', ts_data)[0] # 写入PCAP文件 pcap_writer.writepkt(packet_data, ts=ts) finally: connection.close() sock.close()
最佳实践建议
- 若仅需远程抓包保存,优先用方案1,直接转发原始PCAP数据流,效率最高且无格式错误。
- 需实时处理数据包时,必须用长度前缀解决TCP粘包问题,确保每个数据包完整接收。
- 抓包时保留
--packet-buffered参数,避免tcpdump缓冲区堆积导致延迟或丢包。 - 尽量减少发送端的数据包处理环节,直接转发原始数据能最大程度保证完整性。
内容的提问来源于stack exchange,提问作者Firouziam
相关产品推荐
相关产品推荐

