Skip to content

Raw Socket (AF_PACKET)

Overview

Direct Layer-2 Ethernet frame send/receive using Linux AF_PACKET sockets. Provides zero-overhead access to the network wire with no kernel protocol stack processing. Used as the foundation for AFDX, custom protocols, and latency-sensitive network applications.

Status: Implemented

Full implementation with PACKET_MMAP zero-copy receive, promiscuous mode, and frame construction helpers.

Protocol Specification

Ethernet Frame Format

┌──────────────────────────────────────────────────────────────────────┐
│ Byte:  0     1     2     3     4     5     6     7     8     9    10│
├──────────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────┬─────────────────────────────┬──────┐│
│ │     Destination MAC (6B)    │      Source MAC (6B)         │Ether ││
│ │  dst[0] dst[1] ... dst[5]  │  src[0] src[1] ... src[5]   │Type  ││
│ │                             │                             │(2B)  ││
│ └─────────────────────────────┴─────────────────────────────┴──────┘│
│ ┌──────────────────────────────────────────────────────────────────┐│
│ │                      Payload (46–1500 bytes)                      ││
│ └──────────────────────────────────────────────────────────────────┘│
│ Offset 0          6         12    14                         1514   │
└──────────────────────────────────────────────────────────────────────┘
Field Offset Size Description
Destination MAC 0 6 bytes Target hardware address
Source MAC 6 6 bytes Sender hardware address
EtherType 12 2 bytes Protocol identifier (network byte order)
Payload 14 46–1500 bytes Frame data

PACKET_MMAP Ring Buffer

┌───────────────────────────────────────────────────────────────────┐
│  PACKET_MMAP Ring Buffer (Kernel ↔ Userspace shared memory):       │
│                                                                    │
│  ┌────────────┬────────────┬────────────┬────────────┬───────┐    │
│  │TP_STATUS   │TP_STATUS   │TP_STATUS   │TP_STATUS   │       │    │
│  │ _KERNEL    │ _KERNEL    │ _USER      │ _USER      │  ...  │    │
│  └────────────┴────────────┴────────────┴────────────┴───────┘    │
│       ↑ kernel writes here         ↑ user reads here               │
│                                                                    │
│  Frame lifecycle:                                                   │
│    1. Kernel marks frame TP_STATUS_USER (data available)            │
│    2. Userspace reads frame directly from mmap'd region             │
│    3. Userspace marks frame TP_STATUS_KERNEL (return to kernel)     │
│    4. Memory barrier ensures ordering                               │
└───────────────────────────────────────────────────────────────────┘

API Reference

Data Structures

typedef struct {
    int       fd;               /* Socket file descriptor */
    int       if_index;         /* Interface index */
    uint8_t   if_mac[ETH_ALEN]; /* Interface MAC address */
    char      if_name[IFNAMSIZ]; /* Interface name */

    /* PACKET_MMAP ring (optional, NULL if not used) */
    uint8_t  *rx_ring;          /* mmap'd receive ring */
    uint8_t  *tx_ring;          /* mmap'd transmit ring */
    size_t    ring_size;        /* Total mapped size */
    uint32_t  rx_ring_offset;   /* Current read position */
    uint32_t  tx_ring_offset;   /* Current write position */
    uint32_t  frame_count;      /* Number of frames in ring */
    uint32_t  frame_size;       /* Size of each frame slot */

    /* Configuration */
    bool      promiscuous;      /* Promiscuous mode active */
    bool      mmap_enabled;     /* Using PACKET_MMAP */
} raw_socket_t;

typedef struct __attribute__((packed)) {
    uint8_t  dst_mac[ETH_ALEN];   /* Destination MAC */
    uint8_t  src_mac[ETH_ALEN];   /* Source MAC */
    uint16_t ethertype;            /* Protocol ID (network byte order) */
    uint8_t  payload[];            /* Variable-length payload */
} eth_frame_t;

Constants

#define RAW_SOCK_MAX_FRAME    1514   /* ETH_FRAME_LEN (no VLAN) */
#define RAW_SOCK_MAX_JUMBO    9014   /* Jumbo frame */
#define RAW_SOCK_RING_FRAMES  256    /* PACKET_MMAP ring size */
#define RAW_SOCK_FRAME_SIZE   2048   /* Per-frame buffer in ring */
#define ETH_HDR_SIZE          14     /* 6 + 6 + 2 */

Socket Lifecycle

raw_socket_open

int raw_socket_open(raw_socket_t *sock, const char *if_name, uint16_t ethertype);

Open a raw AF_PACKET socket bound to a specific interface.

Parameter Type Description
sock raw_socket_t * Socket handle to initialize
if_name const char * Network interface name (e.g., "eth0")
ethertype uint16_t Protocol filter (ETH_P_ALL for all, or specific like ETH_P_IP)

Returns: 0 on success, -errno on failure.

raw_socket_set_promisc

int raw_socket_set_promisc(raw_socket_t *sock, bool enable);

Enable or disable promiscuous mode on the bound interface.

raw_socket_setup_mmap

int raw_socket_setup_mmap(raw_socket_t *sock, uint32_t frame_count, uint32_t frame_size);

Setup PACKET_MMAP receive ring for zero-copy reception.

Parameter Type Description
sock raw_socket_t * Socket handle
frame_count uint32_t Number of frames in ring (power of 2 recommended)
frame_size uint32_t Size of each frame slot (≥ MTU + overhead)

raw_socket_close

void raw_socket_close(raw_socket_t *sock);

Close the socket, disable promiscuous mode if active, and unmap any PACKET_MMAP rings.

Send / Receive (Basic)

raw_socket_send

ssize_t raw_socket_send(raw_socket_t *sock, const void *frame, size_t len);

Send a complete Ethernet frame (including header). Returns bytes sent or -errno.

raw_socket_recv

ssize_t raw_socket_recv(raw_socket_t *sock, void *buf, size_t buf_len, int timeout_ms);

Receive a raw Ethernet frame with optional timeout.

Parameter Type Description
timeout_ms int -1 = infinite block, 0 = non-blocking, >0 = milliseconds

Returns: Bytes received, 0 on timeout, -errno on error.

Zero-Copy Receive (PACKET_MMAP)

raw_socket_mmap_recv

const void *raw_socket_mmap_recv(raw_socket_t *sock, uint32_t *len);

Poll the PACKET_MMAP ring for the next available frame. Returns a pointer directly into the mmap'd kernel memory no copy required.

Returns: Pointer to frame data (valid until raw_socket_mmap_release()), or NULL if no frame ready.

raw_socket_mmap_release

void raw_socket_mmap_release(raw_socket_t *sock);

Release the current frame back to the kernel. Sets TP_STATUS_KERNEL with a full memory barrier.

Frame Construction

raw_socket_build_frame

size_t raw_socket_build_frame(uint8_t *buf, const uint8_t *dst_mac,
                              const uint8_t *src_mac, uint16_t ethertype,
                              const void *payload, size_t payload_len);

Build a complete Ethernet frame in buf. EtherType is automatically converted to network byte order.

Returns: Total frame length (header + payload).

Usage Examples

Basic Frame Send/Receive

#include "raw_socket.h"
#include <stdio.h>

int main(void)
{
    raw_socket_t sock;
    int rc = raw_socket_open(&sock, "eth0", ETH_P_ALL);
    if (rc < 0) {
        fprintf(stderr, "Failed to open: %s\n", strerror(-rc));
        return 1;
    }

    /* Enable promiscuous mode for full traffic capture */
    raw_socket_set_promisc(&sock, true);

    /* Receive a frame */
    uint8_t buf[2048];
    ssize_t n = raw_socket_recv(&sock, buf, sizeof(buf), 5000);
    if (n > 0) {
        eth_frame_t *frame = (eth_frame_t *)buf;
        printf("Received %zd bytes, EtherType: 0x%04x\n",
               n, ntohs(frame->ethertype));
        printf("Src: %02x:%02x:%02x:%02x:%02x:%02x\n",
               frame->src_mac[0], frame->src_mac[1], frame->src_mac[2],
               frame->src_mac[3], frame->src_mac[4], frame->src_mac[5]);
    }

    raw_socket_close(&sock);
    return 0;
}

PACKET_MMAP Zero-Copy Receive Loop

#include "raw_socket.h"
#include <stdio.h>
#include <poll.h>

int main(void)
{
    raw_socket_t sock;
    raw_socket_open(&sock, "eth0", ETH_P_ALL);
    raw_socket_setup_mmap(&sock, 256, 2048);

    struct pollfd pfd = { .fd = sock.fd, .events = POLLIN };

    for (int i = 0; i < 1000; i++) {
        uint32_t len;
        const void *frame = raw_socket_mmap_recv(&sock, &len);

        if (!frame) {
            poll(&pfd, 1, 100);  /* Wait for data */
            continue;
        }

        const eth_frame_t *eth = (const eth_frame_t *)frame;
        printf("[%d] %u bytes, EtherType 0x%04x (zero-copy)\n",
               i, len, ntohs(eth->ethertype));

        raw_socket_mmap_release(&sock);
    }

    raw_socket_close(&sock);
    return 0;
}

Custom Frame Injection

#include "raw_socket.h"

int main(void)
{
    raw_socket_t sock;
    raw_socket_open(&sock, "eth0", ETH_P_ALL);

    uint8_t dst[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; /* Broadcast */
    const char *payload = "Hello from raw socket!";

    uint8_t frame[ETH_FRAME_LEN];
    size_t len = raw_socket_build_frame(frame, dst, sock.if_mac,
                                        0x88B5, /* IEEE Local Experimental */
                                        payload, strlen(payload));

    ssize_t sent = raw_socket_send(&sock, frame, len);
    printf("Sent %zd bytes\n", sent);

    raw_socket_close(&sock);
    return 0;
}

Build & Run

# Compile
gcc -Wall -Wextra -Werror -O3 -std=c11 -o raw_socket_demo raw_socket.c

# Option 1: Run as root
sudo ./raw_socket_demo

# Option 2: Grant CAP_NET_RAW capability
sudo setcap cap_net_raw+ep ./raw_socket_demo
./raw_socket_demo

Prerequisites

Required Privileges

Raw socket operations require either root or CAP_NET_RAW:

sudo setcap cap_net_raw+ep ./build/raw_socket_demo

  • Network interface must be up: sudo ip link set eth0 up
  • For testing without hardware: use a veth pair or network namespace

Performance Characteristics

Operation Latency Notes
raw_socket_send (sendto) ~2–5 µs Kernel path, single frame
raw_socket_recv (recvfrom) ~2–5 µs Kernel copy to userspace
raw_socket_mmap_recv < 1 µs Zero-copy from shared ring
raw_socket_mmap_release < 100 ns Single store + barrier

Test Output

$ sudo ./build/raw_socket_demo
[raw_socket] Opened eth0 (index=2, MAC=aa:bb:cc:dd:ee:ff)
[raw_socket] Promiscuous mode enabled
[raw_socket] PACKET_MMAP ring: 256 frames × 2048 bytes = 524288 bytes mapped
[raw_socket] Received frame: 64 bytes, EtherType=0x0800 (IPv4)
[raw_socket] Received frame: 342 bytes, EtherType=0x0800 (IPv4)
[raw_socket] Sent custom frame: 36 bytes on eth0
[raw_socket] MMAP zero-copy: 1000 frames received in 847 µs (avg 847 ns/frame)
[PASS] All raw_socket tests passed