An ultra low latency and high throughput networking stack for Linux, built on AF_XDP.
voidnet is a userspace networking stack that bypasses the Linux kernel's network stack entirely. Packets flow directly between the NIC and your application via shared memory — no copies, no syscall overhead, no kernel queuing delays.
On top of this foundation, voidnet provides a full protocol stack — TCP, UDP, HTTP/1.1 — with familiar async socket APIs. You get the ergonomics of TcpListener::accept() and UdpSocket::recv_from() with the performance of kernel-bypass networking.
Application Layer
- HTTP/1.1 server with connection keep-alive and chunked transfer encoding
HttpListener::serve()convenience API with per-connection task spawning
Transport Layer
- TCP — Full implementation with SACK, CUBIC congestion control, ECN, delayed ACK, keep-alive, Nagle algorithm, and retransmission timers
- UDP — Bind/send/recv with split socket halves and streaming interfaces
Network Layer
- IPv4 and IPv6 with full fragmentation and reassembly
- Path MTU discovery with TTL-based cache (RFC 1191)
- SIMD-accelerated checksums (ARM NEON)
Link Layer
- ARP and NDP neighbor discovery with TTL-based cache eviction
- Ethernet frame handling
Core
- Zero-copy packet I/O via AF_XDP shared UMEM regions
- Async-first design with built-in runtimes
- Single-threaded (
LocalRuntime) and multi-threaded (Runtime) execution modes with shared-nothing architecture — each thread owns its own XDP socket, UMEM, and protocol state, eliminating cross-thread synchronization - Builder patterns throughout for fine-grained configuration
- Linux kernel ≥ 5.4 (for full AF_XDP feature support)
- Rust ≥ 1.85 (2024 edition)
- Root privileges or
CAP_NET_RAW+CAP_BPFcapabilities - XDP-compatible NIC (most modern drivers:
i40e,mlx5,ice,veth, etc.)
All major cloud providers are supported, including AWS, GCP, and Azure.
Add voidnet to your Cargo.toml:
[dependencies]
voidnet = "0.1"TCP Echo Server
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use libvoid::net::{socket::TcpListener, wire::ip::IpAddress};
use libvoid::rt::{LocalRuntime, spawn};
fn main() {
let mut runtime = LocalRuntime::builder("eth0", 0)
.build()
.expect("Failed to create runtime");
// Set up Ctrl-C handler for clean shutdown
let exit = Arc::new(AtomicBool::new(false));
ctrlc::set_handler({
let exit = exit.clone();
move || exit.store(true, Ordering::Relaxed)
}).unwrap();
runtime.run(exit, async move {
let addr: IpAddress = "fc00:dead:cafe:1::1".parse().unwrap();
let listener = TcpListener::listen(addr, 8080)
.expect("Failed to listen");
loop {
let stream = listener.accept().await;
spawn(async move {
// Zero-copy splice: moves data directly from recv to send buffer
loop {
match stream.splice(65535).await {
Ok(0) | Err(_) => break,
Ok(_) => {}
}
}
});
}
}).expect("Failed to run");
}See the full example for the complete implementation.
TCP Echo Client
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use libvoid::net::{socket::TcpStream, wire::ip::IpAddress};
use libvoid::rt::LocalRuntime;
fn main() {
let mut runtime = LocalRuntime::builder("eth0", 0)
.build()
.expect("Failed to create runtime");
let exit = Arc::new(AtomicBool::new(false));
ctrlc::set_handler({
let exit = exit.clone();
move || exit.store(true, Ordering::Relaxed)
}).unwrap();
runtime.run(exit, async move {
let local: IpAddress = "fc00:dead:cafe:1::2".parse().unwrap();
let remote: IpAddress = "fc00:dead:cafe:1::1".parse().unwrap();
let stream = TcpStream::connect(local, 9000, remote, 8080)
.expect("Failed to initiate connection")
.await
.expect("Connection failed");
let payload = vec![0xABu8; 64];
let mut buf = vec![0u8; 64];
loop {
stream.write(&payload).await.expect("Write failed");
let mut total = 0;
while total < payload.len() {
match stream.read(&mut buf[total..]).await {
Ok(0) => return,
Ok(n) => total += n,
Err(_) => return,
}
}
}
}).expect("Failed to run");
}See the full example for the complete implementation.
UDP Server
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use libvoid::net::{socket::UdpSocket, wire::ip::IpAddress};
use libvoid::rt::LocalRuntime;
fn main() {
let mut runtime = LocalRuntime::builder("eth0", 0)
.build()
.expect("Failed to create runtime");
let exit = Arc::new(AtomicBool::new(false));
ctrlc::set_handler({
let exit = exit.clone();
move || exit.store(true, Ordering::Relaxed)
}).unwrap();
runtime.run(exit, async move {
let addr: IpAddress = "fc00:dead:cafe:1::1".parse().unwrap();
let mut socket = UdpSocket::new(addr, 8080)
.expect("Failed to bind");
let (recv, mut send) = socket.split();
loop {
let mut packet = recv.recv_from().await;
// Echo: swap src/dst and send back
packet.swap_addresses();
send.echo_immediate(packet);
}
}).expect("Failed to run");
}See the full example for the complete implementation.
UDP Client
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use libvoid::net::{socket::UdpSocket, wire::ip::IpAddress};
use libvoid::rt::LocalRuntime;
fn main() {
let mut runtime = LocalRuntime::builder("eth0", 0)
.build()
.expect("Failed to create runtime");
let exit = Arc::new(AtomicBool::new(false));
ctrlc::set_handler({
let exit = exit.clone();
move || exit.store(true, Ordering::Relaxed)
}).unwrap();
runtime.run(exit, async move {
let local: IpAddress = "fc00:dead:cafe:1::2".parse().unwrap();
let mut socket = UdpSocket::new(local, 8080)
.expect("Failed to bind");
let remote: IpAddress = "fc00:dead:cafe:1::1".parse().unwrap();
loop {
socket.send_to(remote, 8080, b"Hello, world!").await;
}
}).expect("Failed to run");
}See the full example for the complete implementation.
HTTP Server
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use libvoid::net::{http::HttpListener, wire::ip::IpAddress};
use libvoid::rt::{LocalRuntime, spawn};
fn main() {
let mut runtime = LocalRuntime::builder("eth0", 0)
.build()
.expect("Failed to create runtime");
let exit = Arc::new(AtomicBool::new(false));
ctrlc::set_handler({
let exit = exit.clone();
move || exit.store(true, Ordering::Relaxed)
}).unwrap();
runtime.run(exit, async move {
let addr: IpAddress = "fc00:dead:cafe:1::1".parse().unwrap();
let listener = HttpListener::listen(addr, 8080)
.expect("Failed to listen");
loop {
let mut conn = listener.accept().await.expect("Failed to accept");
spawn(async move {
loop {
match conn.next_request().await {
Ok(Some(req)) => {
let path = conn.request_path(&req);
let body = match path {
b"/" => b"Hello, World!\n" as &[u8],
_ => b"Not Found\n",
};
let mut writer = conn.respond(&req);
if writer.write_body(body).await.is_err() { break; }
if writer.finish().await.is_err() { break; }
}
_ => break,
}
}
});
}
}).expect("Failed to run");
}See the full example for the complete implementation.
Low-level XDP
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use libvoid::xdp::{
context::XdpContext,
frame::{BasicFrameBuffer, FrameBuffer},
socket::Socket,
umem::Umem,
};
fn main() {
let mut xdp_ctx = XdpContext::builder("eth0")
.build()
.expect("Failed to create XDP context");
let mut umem = Umem::builder()
.build()
.expect("Failed to create UMEM");
let mut socket = Socket::builder("eth0", 0)
.build(&mut xdp_ctx, umem.owner().clone())
.expect("Failed to create socket");
let exit = Arc::new(AtomicBool::new(false));
ctrlc::set_handler({
let exit = exit.clone();
move || exit.store(true, Ordering::Relaxed)
}).unwrap();
// Prime the fill queue with buffers for the kernel
umem.maybe_wake_fill_queue(socket.fd()).unwrap();
let mut frames = umem.init_buffer::<BasicFrameBuffer>().unwrap();
umem.process_fill_queue(&mut frames).unwrap();
while !exit.load(Ordering::Relaxed) {
if socket.recv(&mut frames).is_ok() {
for frame in frames.iter_frames() {
// Process raw L2 frames directly
let _data: &[u8] = &frame;
}
umem.maybe_wake_fill_queue(socket.fd()).unwrap();
umem.process_fill_queue(&mut frames).unwrap();
}
}
}See the full example for the complete implementation.
graph TD
subgraph Application
HTTP["HTTP/1.1<br/>HttpListener · HttpConnection"]
end
subgraph Transport
TCP["TCP<br/>TcpListener · TcpStream<br/>SACK · CUBIC · ECN"]
UDP["UDP<br/>UdpSocket · SendHalf · RecvHalf"]
end
subgraph Network
IPv4["IPv4<br/>Fragmentation · Reassembly"]
IPv6["IPv6<br/>Extension Headers · Fragmentation"]
PMTU["PMTU Cache"]
Checksum["Checksums<br/>SIMD / NEON"]
end
subgraph Link
ETH["Ethernet"]
ARP["ARP"]
NDP["NDP"]
Neighbor["Neighbor Cache"]
end
subgraph "AF_XDP"
Runtime["Runtime · LocalRuntime"]
Socket["XDP Socket"]
UMEM["UMEM<br/>Fill Queue · Completion Queue"]
end
NIC["NIC — Shared Memory (mmap)"]
HTTP --> TCP
TCP --> IPv4
TCP --> IPv6
UDP --> IPv4
UDP --> IPv6
IPv4 --> PMTU
IPv6 --> PMTU
IPv4 --> Checksum
IPv6 --> Checksum
IPv4 --> ETH
IPv6 --> ETH
ETH --> ARP
ETH --> NDP
ARP --> Neighbor
NDP --> Neighbor
ETH --> Runtime
Runtime --> Socket
Socket --> UMEM
UMEM --> NIC
Runtime/LocalRuntime— Multi-threaded and single-threaded async runtimes with shared-nothing architectureXdpContext— Loads and attaches the XDP eBPF program to the network interfaceUmem— Manages shared memory regions containing packet frame buffersSocket— Low-level AF_XDP socket for zero-copy packet I/O