3 min read

Python Network Packet Sniffer

This guide demonstrates how to build a network packet sniffer using raw sockets. It captures traffic flowing through the network interface and unpacks the binary data to reveal Ethernet, IP, TCP, UDP, and ICMP headers.

Note: This script requires Administrator (Windows) or Root (Linux/macOS) privileges.

Modules Used:

  • socket: To create raw sockets.
  • struct: To unpack binary data structures (headers).
  • os: To detect the operating system (Windows handles raw sockets differently).

The Code

Save this as sniffer.py.

import socket
import struct
import textwrap
import os
import sys

def get_mac_addr(bytes_addr):
    """Converts bytes to MAC address string (e.g., AA:BB:CC...)"""
    bytes_str = map('{:02x}'.format, bytes_addr)
    return ':'.join(bytes_str).upper()

def get_ipv4(addr):
    """Converts raw IP bytes to string (e.g., 192.168.1.1)"""
    return '.'.join(map(str, addr))

def ethernet_frame(data):
    """Unpacks Ethernet Frame"""
    dest_mac, src_mac, proto = struct.unpack('! 6s 6s H', data[:14])
    return get_mac_addr(dest_mac), get_mac_addr(src_mac), socket.htons(proto), data[14:]

def ipv4_packet(data):
    """Unpacks IPv4 Packet"""
    version_header_length = data[0]
    version = version_header_length >> 4
    header_length = (version_header_length & 15) * 4
    ttl, proto, src, target = struct.unpack('! 8x B B 2x 4s 4s', data[:20])
    return version, header_length, ttl, proto, get_ipv4(src), get_ipv4(target), data[header_length:]

def tcp_segment(data):
    """Unpacks TCP Segment"""
    (src_port, dest_port, sequence, acknowledgment, offset_reserved_flags) = struct.unpack('! H H L L H', data[:14])
    offset = (offset_reserved_flags >> 12) * 4
    return src_port, dest_port, sequence, acknowledgment, data[offset:]

def udp_segment(data):
    """Unpacks UDP Segment"""
    src_port, dest_port, size = struct.unpack('! H H 2x H', data[:8])
    return src_port, dest_port, size, data[8:]

def main():
    conn = None

    # 1. Setup Socket
    try:
        if os.name == 'nt':
            # Windows: Captures IP packets (no Ethernet header)
            host = socket.gethostbyname(socket.gethostname())
            conn = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
            conn.bind((host, 0))
            conn.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
            conn.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
            print(f"Sniffing on {host} (Windows mode - IP only)...")
        else:
            # Linux/Unix: Captures full Ethernet frames
            # socket.ntohs(0x0003) = ETH_P_ALL (Capture all protocols)
            conn = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(3))
            print("Sniffing on interface (Linux mode - Full Ethernet)...")

    except PermissionError:
        print("Error: Access denied. Run as Administrator/Root.")
        sys.exit(1)
    except Exception as e:
        print(f"Error setting up socket: {e}")
        sys.exit(1)

    # 2. Capture Loop
    try:
        while True:
            raw_data, addr = conn.recvfrom(65535)

            ip_data = raw_data

            # If Linux, parse Ethernet header first
            if os.name != 'nt':
                dest_mac, src_mac, eth_proto, data = ethernet_frame(raw_data)
                print(f'\n[Ethernet] {src_mac} -> {dest_mac} | Proto: {eth_proto}')

                # 8 is IPv4
                if eth_proto == 8:
                    ip_data = data
                else:
                    continue # Skip non-IPv4 for this example

            # Parse IPv4
            version, header_length, ttl, proto, src, target, data = ipv4_packet(ip_data)
            print(f'\t[IPv4] {src} -> {target} | Proto: {proto}')

            # Parse Transport Layer
            # TCP
            if proto == 6:
                src_port, dest_port, seq, ack, payload = tcp_segment(data)
                print(f'\t\t[TCP] Port {src_port} -> {dest_port}')

            # UDP
            elif proto == 17:
                src_port, dest_port, length, payload = udp_segment(data)
                print(f'\t\t[UDP] Port {src_port} -> {dest_port} | Len: {length}')

            # ICMP
            elif proto == 1:
                print(f'\t\t[ICMP] Packet')

    except KeyboardInterrupt:
        # Windows: Turn off promiscuous mode
        if os.name == 'nt':
             conn.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
        print("\nSniffer stopped.")

if __name__ == "__main__":
    main()

Usage

  1. Run with Privileges:

    • Windows: Open Command Prompt as Administrator.
    • Linux/macOS: Use sudo.
    # Linux
    sudo python sniffer.py
    
    # Windows (Admin CMD)
    python sniffer.py
  2. Generate Traffic: Open a web browser or ping a website to see packets appearing in the terminal.

programming/python/python