teto-dpdk
Rust bindings for F-Stack — high-performance userspace TCP/UDP networking via DPDK, bypassing the Linux kernel network stack entirely.
Named after Nausicaä's fox-squirrel companion: small, fast, and fiercely reliable.
Overview
teto-dpdk eliminates syscall overhead and kernel-to-userspace copies by running a full FreeBSD TCP/IP stack in userspace on top of DPDK's poll-mode driver. It is suitable for latency-sensitive or high-throughput network workloads where kernel socket overhead is a bottleneck.
Two crates are provided:
| Crate | Description |
|---|---|
teto-dpdk |
Low-level F-Stack bindings (cxx bridge, raw callbacks). Use this with mio or other frameworks that manage their own event loop. |
teto-tokio |
Async adapter: TetoTcpListener, TetoTcpStream (AsyncRead + AsyncWrite), TetoUdpSocket. Familiar tokio-style API over teto-dpdk's F-Stack thread. |
Architecture
Kernel Userspace (DPDK)
┌────────────────┐ ┌──────────────────────────────┐
│ sender │ │ DPDK TAP PMD (port 0) │
│ (nc/app) │ TAP fd │ MAC: aa:bb:cc:dd:ee:ff │
│ │ │ │ │ │
│ ▼ │ │ F-Stack (FreeBSD TCP/IP) │
│ dtap0 ────────┼──────────────────▶ ff_socket / ff_recvfrom │
│ 10.0.0.2 │◀─────────────────┼─ ff_sendto (echo reply) │
│ MAC: 02:00:* │ │ │ │
└────────────────┘ │ Rust callback (via cxx) │
└──────────────────────────────┘
MACs must differ — FreeBSD drops frames with src MAC == own MAC.
The Rust layer interfaces with F-Stack through a C++ wrapper (cxx_layer/) using the cxx bridge. The C++ wrapper calls F-Stack's ff_* APIs and delivers received packets to Rust callbacks.
Quick Start
DPDK requires specific kernel modules and hugepage configuration that are complex to set up on a host. The included Docker image handles all of this.
# Inside the container — choose one:
See docs/testing-docker.md for the full walkthrough, expected startup output, and a diagnostic checklist.
Async API (teto-tokio)
The teto-tokio crate provides familiar async/await wrappers. A dedicated F-Stack thread runs the DPDK poll loop and bridges data to tokio tasks via async channels.
[]
= "0.1"
= "0.1"
use TetoTcpListener;
use ;
use ;
async
TetoTcpStream implements AsyncRead + AsyncWrite, so it works with BufReader, copy, LinesCodec, and all other tokio I/O utilities. TetoUdpSocket provides async recv_from/send_to.
Low-Level API (teto-dpdk)
The raw callback API is available for users who need zero-overhead access or want to integrate with mio or other frameworks.
[]
= "0.1"
use FStackConfig;
use ;
Configuration
F-Stack is configured via config.ini. The FStackConfig builder generates the correct arguments for different environments:
// Docker / TAP device (development)
let cfg = for_docker;
// Bare metal / AWS with a real NIC bound via VFIO
let cfg = for_bare_metal;
// Custom
let cfg = new
.with_eal_arg
.with_eal_arg;
Project Structure
teto-dpdk/ (Cargo workspace root)
├── src/
│ ├── lib.rs # Re-exports fstack + config modules
│ ├── config.rs # FStackConfig + TcpSocketOptions builders
│ └── fstack.rs # cxx bridge definition (Rust ↔ C++)
├── examples/
│ ├── udp_echo.rs # Low-level UDP echo
│ └── tcp_echo.rs # Low-level TCP echo
├── cxx_layer/
│ ├── fstack_wrapper.h # C++ header: UDP + TCP types and classes
│ └── fstack_wrapper.cpp # F-Stack API calls, DPDK init, TCP listener
├── teto-tokio/ # Async adapter crate
│ ├── src/
│ │ ├── lib.rs # Re-exports TetoTcpListener, TetoTcpStream, TetoUdpSocket
│ │ ├── runtime.rs # Channel bridge between F-Stack thread and tokio
│ │ ├── tcp_listener.rs # Async TCP listener (mirrors tokio::net::TcpListener)
│ │ ├── tcp_stream.rs # Async TCP stream (AsyncRead + AsyncWrite)
│ │ └── udp_socket.rs # Async UDP socket (mirrors tokio::net::UdpSocket)
│ └── examples/
│ ├── tcp_echo_async.rs # Async TCP echo (cargo run -p teto-tokio --example tcp_echo_async)
│ └── udp_echo_async.rs # Async UDP echo (cargo run -p teto-tokio --example udp_echo_async)
├── build.rs # Links F-Stack, DPDK, and the cxx layer
├── config.ini # F-Stack / DPDK configuration (Docker/TAP)
├── Dockerfile # Builds DPDK + F-Stack from source
├── entrypoint.sh # Configures the kernel-side TAP device
└── docs/
├── testing-docker.md # Docker testing guide and diagnostics
├── bare-metal-setup.md # Bare metal and AWS setup guide
└── config-reference.md # All config.ini keys explained
How It Works
-
DPDK TAP PMD creates a virtual network device pair: a DPDK ethdev (polled in userspace) and a kernel-visible TAP interface (
dtap0). -
F-Stack runs a FreeBSD TCP/IP stack on top of the DPDK ethdev. It processes Ethernet frames, handles ARP, and delivers payload to
ff_socketdescriptors. -
The entrypoint configures the kernel side of
dtap0with IP10.0.0.2/24and a different MAC address from the DPDK side. The distinct MAC is critical: FreeBSD'sether_inputdrops frames whose source MAC matches the interface MAC (anti-loop), so if both sides of the TAP share the same MAC, F-Stack silently drops all ARP replies. -
cargo runinitializes F-Stack, creates a socket bound to0.0.0.0:8080, and enters the DPDK poll loop. When a packet or connection arrives, the Rust callback fires.
Documentation
| Document | Description |
|---|---|
| docs/testing-docker.md | Step-by-step Docker testing guide, startup output walkthrough, and a full diagnostic checklist |
| docs/bare-metal-setup.md | Setting up hugepages, IOMMU, NIC binding with VFIO, and AWS SR-IOV configuration |
| docs/config-reference.md | Every config.ini key explained, including common pitfalls and silently-ignored keys |
Requirements
- Docker testing: Docker with
--privilegedsupport, Linux or WSL2 - Bare metal / AWS: hugepages, IOMMU/VT-d enabled, NIC bound via
vfio-pci— see docs/bare-metal-setup.md
License
Dual-licensed: AGPL-3.0-only for open-source use, or a commercial license — see NOTICE for third-party attributions.