netmap-rs
netmap-rs provides safe, zero-cost abstractions for Netmap kernel-bypass networking in Rust. It aims to offer high-performance packet I/O by leveraging Netmap's efficient memory-mapped ring buffers.
Features
- Zero-copy packet I/O: Directly access packet buffers in memory shared with the kernel.
- High Performance: Designed for low-latency and high-throughput applications.
- Safe Abstractions: Provides a safe Rust API over the underlying
netmapC structures. - Feature Flags: Customizable build via feature flags (e.g.,
sysfor core Netmap functionality,tokio-asyncfor Tokio integration).
Prerequisites
System Requirements
IMPORTANT: This crate requires the Netmap C library to be installed on your system. Without it, the sys feature will not work.
Installing Netmap C Library
On Linux
-
Install build dependencies:
# Ubuntu/Debian # CentOS/RHEL -
Download and build netmap:
-
Load the kernel module:
# Verify it's loaded
On FreeBSD
Netmap is included by default in FreeBSD 11+. No additional installation is required.
Custom Installation Paths
If you installed netmap in a non-standard location, set the NETMAP_LOCATION environment variable:
# Then build your project
Adding netmap-rs to your project
To use netmap-rs in your project, add it to your Cargo.toml.
Crucially, for most use cases, you will need to enable the sys feature. This feature compiles and links against the necessary netmap C libraries and enables the core structures like NetmapBuilder, Netmap, TxRing, and RxRing.
[]
= { = "0.3", = ["sys"] }
If you intend to use netmap-rs with Tokio for asynchronous operations, you should also enable the tokio-async feature:
[]
= { = "0.3", = ["sys", "tokio-async"] }
Basic Usage Example
Here's a basic example of how to open a Netmap interface, send, and receive a packet. This example assumes you have a loopback interface or a setup where packets sent on an interface can be received on it.
use *;
use sleep;
use Duration;
Public API
This section provides a detailed overview of the public API of netmap-rs.
NetmapBuilder
The NetmapBuilder is used to configure and create a Netmap instance.
-
NetmapBuilder::new(ifname_str: &str) -> SelfCreates a new builder for the given Netmap interface name.
ifname_strcan be a simple interface name like"eth0", or"eth0^"to access the host stack.use NetmapBuilder; let builder = new; -
num_tx_rings(self, num: usize) -> SelfSets the desired number of transmission (TX) rings.
use NetmapBuilder; let builder = new.num_tx_rings; -
num_rx_rings(self, num: usize) -> SelfSets the desired number of reception (RX) rings.
use NetmapBuilder; let builder = new.num_rx_rings; -
flags(self, flags: u32) -> SelfSets additional flags for the Netmap request. See
<net/netmap_user.h>for available flags. -
build(self) -> Result<Netmap, Error>Consumes the builder and attempts to open the Netmap interface, returning a
Netmapinstance.use NetmapBuilder; let nm = new.build;
Netmap
A Netmap instance represents an open Netmap interface.
-
num_tx_rings(&self) -> usizeReturns the number of configured TX rings.
-
num_rx_rings(&self) -> usizeReturns the number of configured RX rings.
-
is_host_if(&self) -> boolReturns
trueif theNetmapinstance is configured for host stack rings. -
tx_ring(&self, index: usize) -> Result<TxRing, Error>Returns a handle to a specific TX ring.
-
rx_ring(&self, index: usize) -> Result<RxRing, Error>Returns a handle to a specific RX ring.
Ring
Represents a generic Netmap ring.
-
index(&self) -> usizeReturns the index of the ring.
-
num_slots(&self) -> usizeReturns the total number of slots in the ring.
-
sync(&self)Synchronizes the ring with the NIC, making sent packets available to the hardware and updating the ring's state to see new packets.
TxRing
A handle to a transmission (TX) ring.
-
send(&mut self, buf: &[u8]) -> Result<(), Error>Sends a single packet. The data in
bufis copied to a slot in the ring. -
max_payload_size(&self) -> usizeReturns the maximum payload size for a single packet in this ring.
-
reserve_batch(&mut self, count: usize) -> Result<BatchReservation, Error>Reserves space for sending a batch of packets. Returns a
BatchReservationinstance.
BatchReservation
A reservation for a batch of packets to be sent.
-
packet(&mut self, index: usize, len: usize) -> Result<&mut [u8], Error>Gets a mutable slice for a packet in the batch. You can write your packet data to this slice.
-
commit(self)Commits the batch, making the packets visible to the NIC.
RxRing
A handle to a reception (RX) ring.
-
recv(&mut self) -> Option<Frame>Receives a single packet from the ring. Returns a
Frameif a packet is available. -
recv_batch(&mut self, batch: &mut [Frame]) -> usizeReceives a batch of packets. The
batchslice is filled with available frames, and the number of received frames is returned.
Frame
A Frame represents a received packet. It can be either a zero-copy view of a packet buffer (from a Netmap ring) or an owned buffer (in fallback mode).
-
new(data: &'a [u8]) -> Self: Creates a new frame from a borrowed byte slice (zero-copy). -
new_owned(data: Vec<u8>) -> Self: Creates a new frame from an owned vector of bytes (for fallback). -
len(&self) -> usize: Returns the length of the frame. -
is_empty(&self) -> bool: Returnstrueif the frame is empty. -
payload(&self) -> &[u8]: Returns a slice containing the packet's payload.if let Some = rx_ring.recv
Async API (tokio-async feature)
When the tokio-async feature is enabled, you can use the following async wrappers for non-blocking I/O with Tokio.
TokioNetmap
The TokioNetmap is the entry point for async operations.
-
TokioNetmap::new(netmap: Netmap) -> io::Result<Self>Creates a new
TokioNetmapby wrapping aNetmapinstance.use NetmapBuilder; use TokioNetmap; # async -
rx_ring(&self, ring_idx: usize) -> Result<AsyncNetmapRxRing, Error>Returns an async wrapper for a specific RX ring.
-
tx_ring(&self, ring_idx: usize) -> Result<AsyncNetmapTxRing, Error>Returns an async wrapper for a specific TX ring.
AsyncNetmapRxRing
An AsyncRead implementation for a Netmap RX ring.
-
You can use the methods from
tokio::io::AsyncReadExtto read from the ring, for exampleread().# use NetmapBuilder; # use TokioNetmap; # use AsyncReadExt; # async
AsyncNetmapTxRing
An AsyncWrite implementation for a Netmap TX ring.
-
You can use the methods from
tokio::io::AsyncWriteExtto write to the ring, for examplewrite_all()andflush().# use NetmapBuilder; # use TokioNetmap; # use AsyncWriteExt; # async
Error Enum
The Error enum represents all possible errors that can occur in netmap-rs.
Io(io::Error): An I/O error from the underlying system.WouldBlock: The operation would block.BindFail(String): Failed to bind to a Netmap interface.InvalidRingIndex(usize): The specified ring index is out of bounds.PacketTooLarge(usize): The packet is too large for the ring buffer.InsufficientSpace: There is not enough space in the ring buffer.UnsupportedPlatform(String): The platform is not supported.FallbackUnsupported(String): The feature is not supported in fallback mode.
Fallback API
For platforms without Netmap support, a fallback implementation is provided.
-
create_fallback_channel(max_size: usize) -> (FallbackTxRing, FallbackRxRing)Creates a connected pair of fallback TX and RX rings that simulate a Netmap pipe.
use create_fallback_channel; let = create_fallback_channel; tx.send.unwrap; if let Some = rx.recv
Troubleshooting
Build Errors
"netmap_user.h not found"
This means the Netmap C library is not installed or not found. Make sure to:
- Install the Netmap C library (see Prerequisites section)
- Set
NETMAP_LOCATIONif installed in a non-standard path
"undefined reference to nm_open"
This indicates the Netmap library is not being linked properly. Ensure:
- The
sysfeature is enabled in Cargo.toml - Netmap is properly installed with the library files
Feature Flag Issues
If you get errors like "NetmapBuilder not found", make sure you have enabled the sys feature:
[]
= { = "0.3", = ["sys"] }
Runtime Errors
"Failed to open interface"
Common causes:
-
Permission issues: You need root/sudo access to use netmap
-
Interface doesn't exist: Check available interfaces with:
-
Netmap kernel module not loaded:
-
Driver not supported: Not all network drivers support netmap. Check supported drivers:
"Operation would block"
This is normal behavior when the ring buffer is full or empty. Implement proper retry logic in your application.
Advanced Usage
Thread-per-Ring Pattern
For maximum performance, dedicate threads to individual rings:
use *;
use thread;
Async Support
Enable the tokio-async feature for async/await support. Build a Netmap with
NetmapBuilder, wrap it in TokioNetmap, then drive the async ring wrappers
(AsyncNetmapRxRing implements AsyncRead, AsyncNetmapTxRing implements
AsyncWrite) from within a Tokio runtime:
use NetmapBuilder;
use TokioNetmap;
use ;
async
Tip: For local, non-blocking experimentation you do not need a physical NIC — netmap pipes (
pipe{name}/pipe}name) and VALE ports (vale0:port) are virtual ports managed entirely in kernel memory.
Testing without a physical NIC (the "virtual lab")
You do not need to touch any real network interface to try netmap-rs.
Netmap provides two kinds of purely virtual, in-memory ports that never touch
your physical adapters or the host network stack:
- VALE ports (
vale0:port_name) — an in-kernel Ethernet switch. Attach two ports on the same switch and they can exchange frames like on a real switch. Note VALE enforces the Ethernet minimum frame size (14 bytes), so keep payloads at least 14 bytes long. - Netmap pipes (
pipe{name/pipe}name) — a bidirectional byte channel between two endpoints. Use the master endpoint in one process and the slave endpoint in another (or both in one process).
# Nothing to set up: opening a VALE port or pipe registers it on demand.
The crate's integration test suite runs entirely against VALE ports and pipes, so it never attaches to (or disrupts) your live NICs.
Examples and apps
The examples/ directory contains small, self-contained demos:
ping_pong.rs- Basic send/receive examplesliding_window_arq.rs- Reliable delivery with ARQfec.rs- Forward Error Correctionthread_per_ring.rs- Thread-per-ring patterntokio_pipe_async.rs- Async I/O over a netmap pipe with Tokio
The apps/ directory mirrors the layout of the upstream netmap C repo and
holds ready-to-run utilities:
apps/pkt-gen- Packet generator / drainer (netmap'spkt-genequivalent)apps/vale-ctl- VALE switch and port managementapps/ping- Send and echo packets over a pipeapps/bridge- Forward frames between two VALE portsapps/tokio-proxy- Async pipe proxy using Tokio
Run examples and apps with:
Performance Tips
- Use batch operations where possible to amortize system call overhead
- Pin threads to cores using
core_affinityfor consistent performance - Pre-allocate buffers to avoid allocation during packet processing
- Use multiple rings to leverage multi-core systems
- Consider NUMA topology when pinning threads to cores
License
This project is licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
AUTHOR
-
Meshack Bahati Ouma - CS major (Maseno University (Kenya))
-
Email: bahatikylemeshack@gmail.com
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Acknowledgments
- The Netmap project for the excellent kernel-bypass networking framework
- The Rust community for the safe systems programming language