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¶
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¶
Enable or disable promiscuous mode on the bound interface.
raw_socket_setup_mmap¶
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¶
Close the socket, disable promiscuous mode if active, and unmap any PACKET_MMAP rings.
Send / Receive (Basic)¶
raw_socket_send¶
Send a complete Ethernet frame (including header). Returns bytes sent or -errno.
raw_socket_recv¶
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¶
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¶
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:
- Network interface must be up:
sudo ip link set eth0 up - For testing without hardware: use a
vethpair 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