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()