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.
- 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).
IMPORTANT: This crate requires the Netmap C library to be installed on your system. Without it, the sys feature will not work.
-
Install build dependencies:
# Ubuntu/Debian sudo apt-get update sudo apt-get install build-essential git linux-headers-$(uname -r) # CentOS/RHEL sudo yum install gcc git kernel-devel-$(uname -r)
-
Download and build netmap:
git clone https://github.com/luigirizzo/netmap.git cd netmap/LINUX ./configure make sudo make install -
Load the kernel module:
sudo insmod netmap.ko # Verify it's loaded ls /dev/netmap
Netmap is included by default in FreeBSD 11+. No additional installation is required.
If you installed netmap in a non-standard location, set the NETMAP_LOCATION environment variable:
export NETMAP_LOCATION=/opt/netmap
# Then build your projectTo 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.
[dependencies]
netmap-rs = { version = "0.3", features = ["sys"] }If you intend to use netmap-rs with Tokio for asynchronous operations, you should also enable the tokio-async feature:
[dependencies]
netmap-rs = { version = "0.3", features = ["sys", "tokio-async"] }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 netmap_rs::prelude::*;
use std::thread::sleep;
use std::time::Duration;
fn main() -> Result<(), Error> {
// Ensure you have enabled the "sys" feature for netmap-rs in your Cargo.toml
// e.g., netmap-rs = { version = "...", features = ["sys"] }
// Attempt to open a netmap interface.
// Replace "eth0" with your desired interface.
// NetmapBuilder will prefix with "netmap:" if needed.
// Use "eth0^" to access host stack rings.
let nm = NetmapBuilder::new("eth0") // Or "netmap:eth0"
.num_tx_rings(1) // Configure one transmission ring
.num_rx_rings(1) // Configure one reception ring
.build()?;
// Get handles to the first transmission and reception rings.
let mut tx_ring = nm.tx_ring(0)?;
let mut rx_ring = nm.rx_ring(0)?;
// Prepare a packet to send.
let packet_data = b"hello netmap!";
// Send the packet.
// The `send` method queues the packet.
tx_ring.send(packet_data)?;
// `sync` ensures that queued packets are made available to the hardware.
tx_ring.sync();
println!("Sent packet: {:?}", packet_data);
// Attempt to receive the packet.
let mut received = false;
for _ in 0..5 { // Try a few times with a delay
// `sync` on the rx_ring tells the kernel we are done with previously received packets
// and updates the ring's state to see new packets.
rx_ring.sync();
while let Some(frame) = rx_ring.recv() {
println!("Received packet: {:?}", frame.payload());
assert_eq!(frame.payload(), packet_data);
received = true;
break;
}
if received {
break;
}
sleep(Duration::from_millis(100)); // Wait a bit for the packet to arrive
}
if !received {
eprintln!("Failed to receive the packet back.");
// Depending on the setup (e.g. loopback interface), this might indicate an issue.
}
Ok(())
}This section provides a detailed overview of the public API of netmap-rs.
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 netmap_rs::NetmapBuilder; let builder = NetmapBuilder::new("eth0");
-
num_tx_rings(self, num: usize) -> SelfSets the desired number of transmission (TX) rings.
use netmap_rs::NetmapBuilder; let builder = NetmapBuilder::new("eth0").num_tx_rings(2);
-
num_rx_rings(self, num: usize) -> SelfSets the desired number of reception (RX) rings.
use netmap_rs::NetmapBuilder; let builder = NetmapBuilder::new("eth0").num_rx_rings(2);
-
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 netmap_rs::NetmapBuilder; let nm = NetmapBuilder::new("eth0").build();
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.
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.
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.
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.
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.
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(frame) = rx_ring.recv() { println!("Received packet of length {}: {:?}", frame.len(), frame.payload()); }
When the tokio-async feature is enabled, you can use the following async wrappers for non-blocking I/O with Tokio.
The TokioNetmap is the entry point for async operations.
-
TokioNetmap::new(netmap: Netmap) -> io::Result<Self>Creates a new
TokioNetmapby wrapping aNetmapinstance.use netmap_rs::NetmapBuilder; use netmap_rs::tokio_async::TokioNetmap; # async fn run() -> Result<(), Box<dyn std::error::Error>> { let nm = NetmapBuilder::new("eth0").build()?; let tokio_nm = TokioNetmap::new(nm)?; # Ok(()) # }
-
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.
An AsyncRead implementation for a Netmap RX ring.
-
You can use the methods from
tokio::io::AsyncReadExtto read from the ring, for exampleread().# use netmap_rs::NetmapBuilder; # use netmap_rs::tokio_async::TokioNetmap; # use tokio::io::AsyncReadExt; # async fn run() -> Result<(), Box<dyn std::error::Error>> { # let nm = NetmapBuilder::new("eth0").build()?; # let tokio_nm = TokioNetmap::new(nm)?; let mut rx_ring = tokio_nm.rx_ring(0)?; let mut buf = [0; 1500]; let n = rx_ring.read(&mut buf).await?; # Ok(()) # }
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 netmap_rs::NetmapBuilder; # use netmap_rs::tokio_async::TokioNetmap; # use tokio::io::AsyncWriteExt; # async fn run() -> Result<(), Box<dyn std::error::Error>> { # let nm = NetmapBuilder::new("eth0").build()?; # let tokio_nm = TokioNetmap::new(nm)?; let mut tx_ring = tokio_nm.tx_ring(0)?; tx_ring.write_all(b"hello async netmap").await?; tx_ring.flush().await?; # Ok(()) # }
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.
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 netmap_rs::fallback::create_fallback_channel; let (tx, rx) = create_fallback_channel(64); tx.send(b"hello fallback").unwrap(); if let Some(frame) = rx.recv() { assert_eq!(frame.payload(), b"hello fallback"); }
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
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
If you get errors like "NetmapBuilder not found", make sure you have enabled the sys feature:
[dependencies]
netmap-rs = { version = "0.3", features = ["sys"] }Common causes:
-
Permission issues: You need root/sudo access to use netmap
sudo ./your_program
-
Interface doesn't exist: Check available interfaces with:
ip link show
-
Netmap kernel module not loaded:
sudo insmod netmap.ko
-
Driver not supported: Not all network drivers support netmap. Check supported drivers:
cd netmap/LINUX ./configure --show-drivers
This is normal behavior when the ring buffer is full or empty. Implement proper retry logic in your application.
For maximum performance, dedicate threads to individual rings:
use netmap_rs::prelude::*;
use std::thread;
fn main() -> Result<(), Error> {
let nm = NetmapBuilder::new("eth0")
.num_tx_rings(4)
.num_rx_rings(4)
.build()?;
let mut handles = vec![];
// Spawn RX threads
for i in 0..nm.num_rx_rings() {
let rx_ring = nm.rx_ring(i)?;
let handle = thread::spawn(move || {
// Process packets on this ring
// ...
});
handles.push(handle);
}
// Spawn TX threads
for i in 0..nm.num_tx_rings() {
let tx_ring = nm.tx_ring(i)?;
let handle = thread::spawn(move || {
// Send packets on this ring
// ...
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
Ok(())
}Enable the tokio-async feature for async/await support:
use netmap_rs::tokio_async::*;
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let nm = TokioNetmap::new("eth0").await?;
let mut rx_ring = nm.async_rx_ring(0).await?;
loop {
if let Some(frame) = rx_ring.recv().await? {
println!("Received: {:?}", frame.payload());
}
sleep(Duration::from_millis(10)).await;
}
}The examples/ directory contains several complete examples:
ping_pong.rs- Basic send/receive examplesliding_window_arq.rs- Reliable delivery with ARQfec.rs- Forward Error Correctionthread_per_ring.rs- Thread-per-ring pattern
Run examples with:
cargo run --example ping_pong --features sys- 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
This project is licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
-
Meshack Bahati Ouma - CS major (Maseno University (Kenya))
-
Email: bahatikylemeshack@gmail.com
Contributions are welcome! Please feel free to submit a Pull Request.
- The Netmap project for the excellent kernel-bypass networking framework
- The Rust community for the safe systems programming language