Reputation: 805
I am trying to capture network traffic with tcpdump from a machine in the network and send each packet over the network to another device. I cannot save the packets captured by tcpdump in a file, because I need to process the packets as a stream (one by one) in real-time.
I am using the following Python script to capture and send the packets over socket to another machine:
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()
And in the receiving part I am using the following code to receive and process the packets (write packets to a pcap file for example):
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()
But the problem is, on the receiving side, the packets are not received correctly. Some packets are missing and some packets are corrupted when opened in Wireshark. I tested this by storing the captured packets in a file before sending them over the socket. That file contains all the packets and everything is ok, I am not sure what I am doing wrong that makes the packets missing or corrupted.
In a nutshell, I need to capture packets on a machine, send them over the network to another node and be able to parse the packets one by one and process them. I am not sure if this is the best practice to do this.
Any help is highly appreciated.
Upvotes: 0
Views: 939
Reputation: 805
Here is the Idea: the problem is we don't know the size of the packet sent, and because the length of the packet can vary, we are not able to get the whole packet on the receiving side. So, the idea is to put the length of the packet at the beginning (as a fixed length number), and at the receiving side, first get the number indicating the length of the packet, and read from the buffer for that amount.
Here is the code for it:
Sender:
import socket
import subprocess
import dpkt
import struct
HOST = "localhost"
PORT = 65432
# Define the tcpdump command
command = ['sudo', 'tcpdump', '-c', '1000', '-i', 'eth0', '--packet-buffered', '-w', '-']
# Start the tcpdump process and capture its output
process = subprocess.Popen(command, stdout=subprocess.PIPE)
# Create a TCP socket and connect to the receiver
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (HOST, PORT)
sock.connect(server_address)
output_file = 'pcaps/sender_packets.pcap'
output_pcap = open(output_file, 'wb')
pcap_writer = dpkt.pcap.Writer(output_pcap)
# Loop through the packets in the output and send them over the socket
i = 0
for ts, msg_bytes in dpkt.pcap.Reader(process.stdout):
msg_len = len(msg_bytes)
len_bytes = struct.pack('>I', msg_len)
prefixed_msg = len_bytes + msg_bytes
print(i, msg_len)
sock.sendall(prefixed_msg)
pkt = dpkt.ethernet.Ethernet(msg_bytes)
pcap_writer.writepkt(pkt)
i += 1
# Close the socket
sock.close()
Receiver:
import socket
import dpkt
import struct
HOST = "localhost"
PORT = 65432
BUF = 2048
# Create a TCP socket and listen for connections
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (HOST, PORT)
sock.bind(server_address)
sock.listen(1)
# Accept a connection from a sender
print('Waiting for a connection...')
connection, sender_address = sock.accept()
print('Connected from:', sender_address)
# Create a PcapWriter to write the received packets to a file
file_name = 'pcaps/received_packets.pcap'
pcap_file = open(file_name, 'wb')
pcap_writer = dpkt.pcap.Writer(pcap_file)
i = 0
# Loop through the packets received over the socket and write them to the pcap file
while True:
# Receive data from the socket
len_bytes = connection.recv(4)
if not len_bytes:
break
msg_len = struct.unpack('>I', len_bytes)[0]
msg_bytes = b''
print(i, msg_len)
while len(msg_bytes) < msg_len:
chunk = connection.recv(msg_len - len(msg_bytes))
if not chunk:
raise ValueError("Incomplete message")
msg_bytes += chunk
if not msg_bytes:
break
i += 1
try:
packet = dpkt.ethernet.Ethernet(msg_bytes)
pcap_writer.writepkt(packet)
except Exception as e:
print(e)
# Close the PcapWriter and the socket
pcap_file.close()
connection.close()
It's worth mentioning that due to the length of the packet, you may not get the whole packet by reading once from the buffer. So, we need to read chunks
of buffer and calculate the remaining chunk length (msg_len - len(msg_bytes)
) and do it over again once we have the full packet.
The code has a part to write captured packets into a pcap file at the sender and the receiving part. Using these files you can easily validate number of received packets to ensure you don't have any missing/dropped packets.
Upvotes: 0
Reputation: 312400
You need a way of framing your data so that you can recognize packet boundaries and only deliver complete packets to dpkt.ethernet.Ethernet.
One example would be to replace your TCP connection -- which is just a stream of bytes -- with a higher level protocol. In this example, we use zeromq which is a message-based protocol with a socket-like API. The code looks remarkably similar to your current example, but because it operates on messages rather than bytes we don't need to worry about framing things ourselves.
from contextlib import closing
import dpkt
import subprocess
import zmq
HOST = "localhost"
PORT = 65432
COUNT = 100
command = ['sudo', 'tcpdump', '-c', f'{COUNT}', '-i', 'enp12s0u1', '--packet-buffered', '-w', '-']
ctx = zmq.Context()
sock = ctx.socket(zmq.REQ)
sock.connect(f'tcp://{HOST}:{PORT}')
# We use the return from subprocess.Popen() as a context manager so that we
# wait() on the process after it exits.
with subprocess.Popen(command, stdout=subprocess.PIPE) as process, closing(sock):
for (i, (ts, packet_data)) in enumerate(dpkt.pcap.Reader(process.stdout)):
eth_packet = dpkt.ethernet.Ethernet(packet_data)
packet_bytes = eth_packet.pack()
print(f'sending packet {i}')
sock.send(packet_bytes)
# With a ZMQ REQ/REP pair, we need to call sock.recv() after sock.send() even
# if we're discarding the data.
sock.recv()
# Send an empty message to indicate we've finished sending packets.
sock.send(b'')
ack = sock.recv()
from contextlib import closing
import dpkt
import zmq
HOST = "127.0.0.1"
PORT = 65432
ctx = zmq.Context()
sock = ctx.socket(zmq.REP)
sock.bind(f"tcp://{HOST}:{PORT}")
with open("packets.pcap", "wb") as fd, closing(sock):
pcap_writer = dpkt.pcap.Writer(fd, nano=True)
pkt_count = 0
while True:
data = sock.recv()
# With a ZMQ REQ/REP pair we must call sock.send() after sock.recv().
sock.send(b"")
# Exit the loop when we receive an empty message.
if not data:
break
packet = dpkt.ethernet.Ethernet(data)
pcap_writer.writepkt(packet)
Upvotes: 1