Packet Sniffing on Mirrored Port - Missing Packets when UDP Message > 50kb (Python, Scapy)

25 Views Asked by At

I have very little network programming experience, and am running into an issue with packet sniffing on a mirrored port, where the sniffer seems to be missing packets:

I have two computers (A and B) communicating over ethernet socket via UDP. Computer A sends an integer (n) to B, and B sends a pickled (n x 3) numpy array back to A. Since the array can get quite large, computer A first sends the size of the outgoing array, and then sends the array in 1024 byte chunks. B reads the incoming array size, and continues receiving until the full message is received. This all works with no issues.

Now computer C is introduced. C is supposed to "intercept" the array. I have A's port mirrored onto C's, and C is reading packets with Scapy. If the packet is 8 bytes, C assumes this is the message size, and then starts concatenating subsequent packets until the size is reached. This works for arrays up to 2000x3 (approx 48 kb), but after that C appears to miss packets. A and B have no issues still, just C is not collecting the complete message.

Potentially relevant info:

  • The communication between A and B has no issues. Packets are not dropped.
  • I have everything connected with gigabit ethernet.
  • I am accessing all computers from computer A using Visual Studio Code SSH.
  • When I put a time.sleep(0.001) after each 1024 byte chunk, no packets are missed, even for larger arrays (transmission is however too slow).
  • The managed switch does not report any errors or lost packets or anything in its log.
  • For 2500x3 array C typically gets 57 of 59 packets. For 5000x3 array C typically gets 61 of 116. For 10000x3 its 79 of 235.

Here is the server, running on B:

import numpy as np
import pickle
from struct import pack, unpack
import socket
import time

class TestUDPServer():
    def __init__(self):
        self.server_host = "192.168.0.101"
        self.server_port = 8080
        self.start_server()
        self.main_loop()

    def receive(self):
        data, client_address = self.server_socket.recvfrom(1024)
        data_r = pickle.loads(data)

        return data_r, client_address

    def send(self, var, client_address):
        pickled_data = pickle.dumps(var)

        # Send the size of the message first
        size = len(pickled_data)
        self.server_socket.sendto(pack(">Q", size), client_address)

        # Send message in chunks
        chunk_size = 1024
        for i in range(0, len(pickled_data), chunk_size):
            chunk = pickled_data[i:i + chunk_size]

            self.server_socket.sendto(chunk, client_address)
            #time.sleep(0.001)

    def start_server(self):
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.server_socket.bind((self.server_host, self.server_port))
        print(f"UDP Server listening on {self.server_host}:{self.server_port}")

    def main_loop(self):
        while True:
            pc_size, client_address = self.receive()
            if pc_size>0:
                break
            pc = np.random.random((pc_size, 3))
            print("received size:", pc_size)
            self.send(pc, client_address)

if __name__ == "__main__":
    TestUDPServer()

Here is the client running on A:

import numpy as np
import pickle
import socket
from struct import pack, unpack


class TestUDPClient():
    def __init__(self):
        self.server_host = "192.168.0.101"
        self.server_port = 8080
        self.start_client()
        self.await_trigger()

    def receive(self):
        # Receive the size of the message first
        size_data, _ = self.client_socket.recvfrom(8)
        size = unpack(">Q", size_data)[0]

        received_data = b""
        chunk_size = 1024

        # Receive message in chunks
        n_chunks = 0
        while size > 0:
            n_chunks += 1
            chunk, _ = self.client_socket.recvfrom(min(size, chunk_size))
            if not chunk:
                break
            received_data += chunk
            size -= len(chunk)
        print("n_chunks", n_chunks)
        data_r = pickle.loads(received_data)
        return data_r

    def send(self, var):
        pickled_data = pickle.dumps(var)
        self.client_socket.sendto(pickled_data, (self.server_host, self.server_port))

    def start_client(self):
        self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        print(f"UDP Client connected to {self.server_host}:{self.server_port}")

    def await_trigger(self):
        while True:
            size = int(input("Array length? (n x 3) : ")) # User inputs integer           
            self.send(size)
            rec = self.receive()
            print("received shape:", rec.shape)

        self.client_socket.close()

if __name__ == "__main__":
    TestUDPClient()

Here is the packet sniffer running on C:

from scapy.all import sniff, IP, TCP, UDP, ICMP
from struct import pack, unpack
import pickle
import numpy as np
import time

class PortMirror():
    def __init__(self):
        self.source_ip = '192.168.0.101'
        self.size = 0
        self.n_packets = 0
        self.data = b''
        self.array = None

        sniff(iface='enp1s0', prn=self.packet_callback, store=0, filter='udp')


    def packet_callback(self, packet):
        if packet.haslayer('IP') and packet.haslayer('UDP'): # FIlter for IP/UDP
            src_ip = packet['IP'].src
            dst_ip = packet['IP'].dst
            src_port = packet['UDP'].sport
            dst_port = packet['UDP'].dport

            if src_port!=22 and dst_port!=22 and src_ip==self.source_ip: # Filter SSH and source ip


                if len(bytes(packet['Raw'].load))==8:
                    self.size = unpack('>Q', bytes(packet['Raw'].load))[0]
                    self.n_packets = 0
                    self.data = b''

                elif len(self.data) < self.byte_size:
                    self.n_packets += 1
                    print("n_packets", self.n_packets)
                    self.data += bytes(packet['UDP'].payload)
                    
                    if len(self.data) == self.byte_size:
                        self.array = pickle.loads(self.data)
                        print("Success.", self.array.shape)
                        self.n_packets = 0
                        self.byte_size = 0

if __name__ == "__main__":
    PortMirror()
0

There are 0 best solutions below