Sniffing network packets in Python has always been tricky β especially on Windows, where raw sockets behave differently than Linux or macOS. I faced this challenge while trying to capture packets on specific ports, parse them, and analyze their payloads programmatically. Existing libraries like PyShark depend on TShark/Wireshark, which adds setup overhead and reduces portability.
Thatβs why I created packetpy β a pure-Python, zero-dependency packet sniffer that works natively on Windows and gives fine-grained control over IPv4, TCP, and UDP packets.
How packetpy Works?
packetpy uses raw sockets to capture packets directly from the network interface. Hereβs a breakdown of the main components:
1. Raw Socket Capture
The WinRawSniffer class creates a raw socket:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
s.bind((iface_ip, 0))
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
Key points:
IP_HDRINCL ensures captured packets include the full IP header.
RCVALL_ON (via ioctl) enables promiscuous mode to capture all packets.
The socket runs in a background daemon thread, allowing your script to continue executing while sniffing packets.
2. IPv4 Parsing
parse_ipv4(pkt) converts raw bytes into a structured dictionary:
{'version': ..., 'ihl': ..., 'len': ..., 'proto': ..., 'src': ..., 'dst': ..., 'payload': ...}
The first 20 bytes of the packet are unpacked using struct.unpack('!BBHHHBBH4s4s', ...).
The IHL field determines the IP header length.
Payload extraction is done carefully: pkt[ihl:total_len].
The result is a dictionary containing source/destination IPs, protocol number, header length, and raw payload.
3. TCP and UDP Parsing:
For TCP, parse_tcp(seg) reads:
src_port, dst_port, seq, ack, offset, payload
TCP header offset is extracted from the data offset field.
Payload starts at the calculated offset.
For UDP, parse_udp(seg) reads:
src_port, dst_port, length, payload
UDP headers are always 8 bytes.
Payload is extracted based on the length field.
This gives full visibility into the packet structure without any external library.
4. Match Callbacks
make_match_callback(sniffer, target_ip, target_port) allows you to stop sniffing when a specific packet is detected:
if ip['dst'] == target_ip and pkt['tcp']['dst_port'] == target_port:
print("=== MATCH FOUND (TCP) ===")
Supports TCP and UDP.
Prints a 512-byte hexdump of the payload for inspection.
Stops the sniffer automatically once a match is found.
5. Hexdump Utility
hexdump(payload[:512]) formats the payload like this:
0000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
0010 ...
Displays both hex values and ASCII equivalents.
Useful for quick debugging of raw payload data.
6. Threaded Sniffing Loop
The sniffer runs in a daemon thread:
self.thread = threading.Thread(target=loop, daemon=True)
self.thread.start()
Ensures your main program is not blocked.
Supports a timeout, after which sniffing stops automatically.
Exceptions are caught to keep the loop running smoothly.
Key Advantages of packetpy
Pure Python, zero dependencies β no TShark or Wireshark required.
Lightweight and fast β ideal for direct packet inspection on Windows.
TCP/UDP parsing with payload inspection built-in.
Callback-based matching β perfect for monitoring specific IPs/ports.
Threaded design β non-blocking sniffing for your scripts.
Installation
pip install packetpy
Start sniffing immediately with:
sniffer = WinRawSniffer(iface_ip="192.168.1.100", timeout=10)
callback = make_match_callback(sniffer, target_ip="192.168.1.105", target_port=8080)
sniffer.start(callback)
Next Steps
Iβm planning to add:
TLS packet detection
Cross-platform support (Linux/macOS)
Packet blocking simulations
packetpy taught me a lot about raw sockets, network byte order, and protocol structures. Python gives you amazing low-level control when you dig deep.
π‘ Try it out and share feedback!
Network enthusiasts, Python devs, and security researchers β what features should come next?
Top comments (0)