High-performance QUIC packet processing combining kernel XDP filtering with userspace AF_XDP packet handling. This project demonstrates the complete pipeline from NIC to userspace application using Linux kernel's fastest data path.
This project consists of two main components that work together:
- XDP Program (kernel): Filters and redirects QUIC packets
- AF_XDP Application (userspace): Receives and processes redirected packets
Packet Flow:
NIC → XDP → AF_XDP Socket → Userspace Application
Runs in the kernel, attached directly to the NIC. Processes every packet at line rate:
- Parses Ethernet, IP, and UDP headers
- Filters QUIC traffic on port 4433
- Extracts QUIC Destination Connection ID (DCID) from long headers
- Tracks packet counts per Connection ID in BPF map
- Backend ID Encoding: Parses CIDv1 format to extract backend_id from DCID
- Validates CID version byte (must be 1)
- Extracts 16-bit backend_id from bytes 2-3 (big-endian)
- Maps backend_id to AF_XDP queue:
queue = backend_id % MAX_QUEUES
- Redirects packets to appropriate AF_XDP socket based on backend_id
BPF Maps:
cid_counter: Tracks packet counts for each Connection IDcid_backend_map: Maps Connection IDs to backend queues (65536 entries)xsks_map: Maps queue IDs to AF_XDP sockets (64 entries)
Userspace Rust application that receives packets redirected by XDP:
- Creates AF_XDP socket bound to network interface queue
- Allocates UMEM (User Memory) for zero-copy packet buffers
- Sets up ring buffers (RX ring, Fill queue)
- Registers socket in XDP's xsks_map for packet redirection
- Receives and processes packets in tight polling loop
- Prints packet information for inspection
- XDP Program filters packets in kernel (fastest possible path)
- XDP redirects selected packets using
bpf_redirect_map(&xsks_map, queue_id, 0) - AF_XDP socket registered in xsks_map receives the redirected packets
- Userspace application processes packets from AF_XDP socket without kernel network stack overhead
This provides kernel-bypass networking with the ability to do custom packet processing in kernel (XDP) before userspace delivery (AF_XDP).
For a comprehensive deep-dive into AF_XDP and the implementation details of this project, read the complete tutorial:
Featured in Medium: The Ultimate Guide to AF_XDP: High-Performance Networking in Rust
- Linux kernel (with AF_XDP support)
- BPF/XDP toolchain: clang, llvm
- Rust toolchain (edition 2024)
- libbpf and libxdp development libraries
- Root privileges
- BPF filesystem mounted at /sys/fs/bpf
clang -O2 -g -target bpf -c xdp/xdp_filter.c -o xdp_filter.osudo bpftool prog load xdp_filter.o /sys/fs/bpf/xdp_prog type xdp pinmaps /sys/fs/bpf
sudo ip link set dev eth xdp pinned /sys/fs/bpf/xdp_progVerify XDP is attached:
sudo ip link show dev eth0
# Should show "xdp" in the outputcd afxdp
cargo build --releasesudo ./afxdp/target/release/afxdpThe application will:
- Create AF_XDP socket on eth0 queue 0
- Register itself in the XDP's xsks_map
- Start receiving packets redirected by XDP
- Print packet information as they arrive
For realistic testing, run a QUIC server on another machine. A simple QUIC server is available in the Async-Chat repository.
-
On another machine, clone and run the QUIC server:
git clone https://github.com/Shradhesh71/Async-Chat cd Async-Chat # Follow setup instructions and run server on port 4433
-
With AF_XDP application running, send QUIC traffic from client to 10.0.0.1:4433
-
Watch the AF_XDP application output to see intercepted packets
The AF_XDP application should receive and display the packet.
Check XDP statistics and connection tracking:
# View packet counts per Connection ID
sudo bpftool map dump name cid_counter
# View registered AF_XDP sockets
sudo bpftool map dump name xsks_map
# View backend mappings
sudo bpftool map dump name cid_backend_map- Protocol: QUIC over UDP
- Port: 4433
- Max CID Length: 20 bytes
- CID Version: 1 (CIDv1 format with backend encoding)
- Backend ID Encoding:
- 16-bit backend_id stored in DCID bytes 2-3 (big-endian)
- Queue selection:
backend_id % 64(MAX_QUEUES) - Enables load balancing across AF_XDP sockets
- BPF Map Sizes:
cid_counter: 1024 entries (hash map)cid_backend_map: 65536 entries (hash map)xsks_map: 64 entries (XSK map)
- Processing: QUIC long header packets only
- Redirect: Routes packets to specific queue based on backend_id
- Interface: eth0
- Queue ID: 0
- UMEM Size: 8 MB (4096 frames × 2048 bytes)
- Frame Size: 2048 bytes
- Ring Sizes: 1024 entries (RX, Fill, Completion)
- Mode: Copy or zero-copy (kernel chooses)
- Packet arrives at NIC on port 4433
- XDP program examines packet in kernel:
- Validates headers (Ethernet → IP → UDP)
- Checks UDP destination port (4433)
- Parses QUIC long header
- Extracts DCID (Destination Connection ID)
- Updates
cid_countermap - Decodes backend_id from CIDv1 format:
- Verifies CID version is 1
- Extracts backend_id from bytes 2-3 (16-bit big-endian)
- Calculates target queue:
backend_id % 64
- XDP redirects packet to corresponding AF_XDP socket using
bpf_redirect_map() - Packet is placed in AF_XDP RX ring (zero-copy to UMEM)
- Userspace application polls RX ring
- Application reads packet from UMEM and processes it
- Frame is returned to Fill queue for reuse
xdp/
xdp_filter.c # XDP BPF program (attaches to NIC)
afxdp/
src/main.rs # AF_XDP userspace application
Cargo.toml # Dependencies (libc, libbpf-rs)
This project is for educational and research purposes.