Table of Contents
TCP Stream Sniffer & Rebroadcaster
I guess rebroadcaster isn't quite the right term, but what this script does is capture a TCP stream from a mirrored switch port and make it available on a local socket for use by other tools. For example, streaming to an NTRIP mountpoint.
Usage
Deploying this script has a few requirements. In my example, a network device with IP address 172.16.0.108 is transmitting a TCP stream on port 2201. On the switch this device is connected to, its port is mirrored to another, so any traffic sent or received by that device can be transparrently rebroadcasted to another device. You'll need another device with a network interface that supports being run in promiscious mode so that it can capture the traffic. This should be separate from its primary network interface. The second network interface will need to be put into promiscious mode - an example systemd service definition is provided below.
Services
- promisc-enp2s0.service
[Unit] Description=Control promiscuous mode for interface enp2s0 After=network.target [Service] Type=oneshot ExecStart=/usr/bin/ip link set enp2s0 up ExecStart=/usr/bin/ip link set enp2s0 promisc on ExecStop=/usr/bin/ip link set enp2s0 promisc off RemainAfterExit=yes [Install] WantedBy=multi-user.target
- sniffedstream2newstream.service
[Unit] Description=Hyfix2TCPStream After=network.target Wants=network-online.target After=network-online.target [Service] ExecStart=/usr/bin/hyfix2tcpstream.py Restart=always RuntimeMaxSec=43200 RestartSec=10 TimeoutStartSec=30 User=root [Install] WantedBy=default.target
Script
- sniffedstream2newstream.py
#!/usr/bin/env python3 import socket import sys import signal import threading from scapy.all import sniff, IP, TCP # Capture Configuration CAPTURE_INTERFACE = "enp2s0" # Your promiscuous interface SOURCE_IP = "172.16.0.108" # Source IP of the device sending data TARGET_PORT = 2201 # The port you're capturing LOCAL_PORT = 3000 # Local port to serve the captured data on # Global variables clients = [] clients_lock = threading.Lock() def signal_handler(sig, frame): print('Stopping capture and closing connections...') # Close local client connections with clients_lock: for client in clients: try: client.close() except: pass sys.exit(0) def packet_handler(packet): """Process each captured packet and forward its payload""" if IP in packet and TCP in packet: if packet[IP].src == SOURCE_IP and packet[TCP].dport == TARGET_PORT: if packet[TCP].payload: payload = bytes(packet[TCP].payload) if payload: # Forward payload to all connected clients with clients_lock: disconnected = [] for client in clients: try: client.sendall(payload) except: disconnected.append(client) # Remove disconnected clients for client in disconnected: clients.remove(client) def client_handler(client_socket, addr): """Handle a connected client""" print(f"New client connected: {addr}") with clients_lock: clients.append(client_socket) # Keep connection open until client disconnects try: while True: data = client_socket.recv(1024) if not data: break except: pass finally: with clients_lock: if client_socket in clients: clients.remove(client_socket) client_socket.close() print(f"Client disconnected: {addr}") def start_server(): """Start a TCP server to serve captured data""" server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(('0.0.0.0', LOCAL_PORT)) server.listen(5) print(f"Listening for connections on port {LOCAL_PORT}") while True: client_sock, address = server.accept() client_thread = threading.Thread(target=client_handler, args=(client_sock, address)) client_thread.daemon = True client_thread.start() if __name__ == "__main__": # Set up signal handler for clean shutdown signal.signal(signal.SIGINT, signal_handler) # Start TCP server in a separate thread server_thread = threading.Thread(target=start_server) server_thread.daemon = True server_thread.start() print(f"Starting payload capture from {SOURCE_IP}:{TARGET_PORT}") print(f"Sniffing on {CAPTURE_INTERFACE}...") # Start packet capture sniff( iface=CAPTURE_INTERFACE, filter=f"tcp and src host {SOURCE_IP} and dst port {TARGET_PORT}", prn=packet_handler, store=0 )