Understanding low-level network traffic flow requires bypassing standard OS socket abstractions (SOCK_STREAM, SOCK_DGRAM) and intercepting raw layer-2 Ethernet frames directly from wireless interface drivers.

In Linux, opening a raw socket with socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) allows software to capture every packet received or transmitted over a network card—including wlan0. In this article, we write a complete C packet sniffer to decode Ethernet headers, IPv4 headers, and TCP/UDP ports in real time.

Quick Command Sequence

Enable promiscuous mode on your wireless interface, compile the C sniffer, and execute with root privileges:

Terminal Commandsbash
# 1. Enable promiscuous mode on wlan0 interface
sudo ip link set wlan0 promisc on
 
# 2. Compile C packet sniffer
gcc -O2 wifi_sniffer.c -o wifi_sniffer
 
# 3. Run packet sniffer (requires root privileges for raw sockets)
sudo ./wifi_sniffer wlan0
 
# 4. Disable promiscuous mode when finished
sudo ip link set wlan0 promisc off

How Linux Raw Sockets (AF_PACKET) Work

  • Domain (`AF_PACKET`): Instructs the kernel to bypass higher-level TCP/IP stack processing and expose raw device driver frame buffers.

  • Type (`SOCK_RAW`): Delivers full raw Ethernet frames including link-layer headers rather than stripped transport payloads.

  • Protocol (`ETH_P_ALL`): Captures all network protocols (IP, ARP, RARP, IPv6) hitting the network interface.

  • Interface Binding: Using SO_BINDTODEVICE or bind() with struct sockaddr_ll locks the sniffer specifically to wlan0.

Complete C Wi-Fi Packet Sniffer (wifi_sniffer.c)

This C program creates an AF_PACKET raw socket, binds to wlan0, reads incoming frame buffers, and decodes MAC, IP, and port headers:

wifi_sniffer.cc
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <net/if.h>
#include <sys/ioctl.h>
 
#define BUFFER_SIZE 65536
 
void process_packet(unsigned char *buffer, int size) {
    struct ethhdr *eth = (struct ethhdr *)buffer;
 
    // Check if packet contains IPv4 header (ETH_P_IP = 0x0800)
    if (ntohs(eth->h_proto) == ETH_P_IP) {
        struct iphdr *ip = (struct iphdr *)(buffer + sizeof(struct ethhdr));
        
        struct in_addr src_ip, dst_ip;
        src_ip.s_addr = ip->saddr;
        dst_ip.s_addr = ip->daddr;
 
        printf("
================ [ IP PACKET ] ================
");
        printf("Source MAC      : %02X:%02X:%02X:%02X:%02X:%02X
",
               eth->h_source[0], eth->h_source[1], eth->h_source[2],
               eth->h_source[3], eth->h_source[4], eth->h_source[5]);
        printf("Destination MAC : %02X:%02X:%02X:%02X:%02X:%02X
",
               eth->h_dest[0], eth->h_dest[1], eth->h_dest[2],
               eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]);
        printf("Source IP       : %s
", inet_ntoa(src_ip));
        printf("Destination IP  : %s
", inet_ntoa(dst_ip));
        printf("Protocol        : ");
 
        if (ip->protocol == IPPROTO_TCP) {
            struct tcphdr *tcp = (struct tcphdr *)(buffer + sizeof(struct ethhdr) + (ip->ihl * 4));
            printf("TCP (Src Port: %d, Dst Port: %d)
", ntohs(tcp->source), ntohs(tcp->dest));
        } else if (ip->protocol == IPPROTO_UDP) {
            struct udphdr *udp = (struct udphdr *)(buffer + sizeof(struct ethhdr) + (ip->ihl * 4));
            printf("UDP (Src Port: %d, Dst Port: %d)
", ntohs(udp->source), ntohs(udp->dest));
        } else if (ip->protocol == IPPROTO_ICMP) {
            printf("ICMP
");
        } else {
            printf("Other (%d)
", ip->protocol);
        }
    }
}
 
int main(int argc, char *argv[]) {
    const char *iface = (argc > 1) ? argv[1] : "wlan0";
 
    // 1. Create AF_PACKET raw socket
    int raw_socket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (raw_socket < 0) {
        perror("Socket creation failed (Root privileges required)");
        return 1;
    }
 
    // 2. Bind socket to target interface (e.g. wlan0)
    if (setsockopt(raw_socket, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface)) < 0) {
        perror("Failed to bind socket to interface");
        close(raw_socket);
        return 1;
    }
 
    printf("Started Wi-Fi Raw Packet Sniffer on interface '%s'...
", iface);
 
    unsigned char buffer[BUFFER_SIZE];
 
    while (1) {
        int data_size = recvfrom(raw_socket, buffer, BUFFER_SIZE, 0, NULL, NULL);
        if (data_size < 0) {
            perror("Recvfrom error");
            break;
        }
        process_packet(buffer, data_size);
    }
 
    close(raw_socket);
    return 0;
}

Byte Ordering and Header Pointer Arithmetic

  • Network Byte Order (`ntohs` / `ntohl`): Network headers store multi-byte integers in Big-Endian format. Always wrap 16-bit ports and protocols in ntohs() for correct x86/ARM Little-Endian rendering.

  • Variable IP Header Length (`ip->ihl`): Never assume IPv4 headers are 20 bytes. Calculate transport header offsets dynamically using sizeof(struct ethhdr) + (ip->ihl * 4).