Skip to main content

netmap_min_sys/
lib.rs

1//! Raw FFI bindings and helper shims for the [Netmap](https://netmap.org/)
2//! kernel-bypass networking framework.
3//!
4//! The bulk of the API is generated with `bindgen` from the Netmap C headers
5//! (`<net/netmap_user.h>` and `<net/netmap.h>`) and re-exported here. On top of
6//! the raw bindings this crate also provides:
7//!
8//! * [`nm_open`] / [`nm_close`] — the standard Netmap descriptor helpers. These
9//!   are `static` functions in the C headers (so no shared/static library
10//!   exports them); this crate compiles them in via a small C shim.
11//! * [`NETMAP_TXRING`] / [`NETMAP_RXRING`] / [`NETMAP_IF`] / [`NETMAP_BUF`] —
12//!   Rust implementations of the function-like offset macros from
13//!   `netmap_user.h`.
14//! * The Netmap ioctl numbers ([`NIOCRXSYNC`], [`NIOCTXSYNC`], [`NIOCREGIF`],
15//!   [`NIOCCTRL`]) which bindgen cannot evaluate from the `_IO`/`_IOWR` macros.
16//!
17//! # Build configuration
18//!
19//! The netmap headers are discovered through the `NETMAP_LOCATION` environment
20//! variable (defaulting to `/usr/local`), looking for headers in
21//! `$NETMAP_LOCATION/include`. When Netmap is not needed, enable the
22//! `disable-netmap-kernel` feature (or set `DISABLE_NETMAP_KERNEL`) to build an
23//! empty crate. If the headers cannot be found at all (for example when the
24//! crate is built by crates.io or docs.rs, which never have Netmap installed),
25//! the build degrades to the same empty state automatically instead of failing.
26
27#![allow(non_upper_case_globals)]
28#![allow(non_camel_case_types)]
29#![allow(non_snake_case)]
30#![allow(dead_code)]
31#![allow(clippy::missing_safety_doc)]
32
33#[cfg(not(netmap_disabled))]
34mod bindings {
35    include!(concat!(env!("OUT_DIR"), "/binding.rs"));
36}
37
38#[cfg(not(netmap_disabled))]
39pub use bindings::*;
40
41/// Export the helper types only when the netmap bindings are available.
42#[cfg(not(netmap_disabled))]
43pub mod exports {
44    pub use super::bindings::NR_REG_ALL_NIC;
45    pub use super::bindings::NR_REG_NIC_SW;
46    pub use super::bindings::NR_REG_ONE_NIC;
47    pub use super::bindings::NR_REG_PIPE_MASTER;
48    pub use super::bindings::NR_REG_PIPE_SLAVE;
49    pub use super::bindings::NR_REG_SW;
50}
51
52// ---------------------------------------------------------------------------
53// Hand-written core structs.
54//
55// bindgen emits nm_desc (and its nm_pkthdr/nm_stat constituents) with an
56// incorrect opaque layout because of the mutually-recursive nm_desc <->
57// nm_pkthdr type cycle in netmap_user.h. Their layout is defined below to
58// match the C headers exactly (verified with `offsetof`/`sizeof` on x86-64).
59// ---------------------------------------------------------------------------
60
61#[cfg(not(netmap_disabled))]
62/// A Netmap descriptor (`struct nm_desc` in `netmap_user.h`).
63///
64/// This is the main handle returned by [`nm_open`] and used to reach the
65/// `netmap_if` (via the [`nifp`](Self::nifp) field) and the underlying file
66/// descriptor (via the [`fd`](Self::fd) field).
67#[repr(C)]
68#[derive(Debug)]
69pub struct nm_desc {
70    /// Points to itself when the descriptor is open; used for sanity checks.
71    pub self_: *mut nm_desc,
72    /// File descriptor of the `/dev/netmap` device.
73    pub fd: i32,
74    /// Base address of the mapped Netmap shared-memory region.
75    pub mem: *mut core::ffi::c_void,
76    /// Size of the mapped region.
77    pub memsize: usize,
78    /// Non-zero if `mem` is the result of `mmap`.
79    pub done_mmap: i32,
80    /// Immutable pointer to the `netmap_if` at the start of the region.
81    pub nifp: *const netmap_if,
82    pub first_tx_ring: u16,
83    pub last_tx_ring: u16,
84    pub cur_tx_ring: u16,
85    pub first_rx_ring: u16,
86    pub last_rx_ring: u16,
87    pub cur_rx_ring: u16,
88    /// The registration request used to open the port.
89    pub req: nmreq,
90    /// Header of the last packet read via `nm_nextpkt`.
91    pub hdr: nm_pkthdr,
92    /// A pointer to one of the rings; used to translate buffer indices.
93    pub some_ring: *const netmap_ring,
94    /// Start of the buffer area within the mapped region.
95    pub buf_start: *const core::ffi::c_void,
96    /// End of the buffer area within the mapped region.
97    pub buf_end: *const core::ffi::c_void,
98    pub snaplen: i32,
99    pub promisc: i32,
100    pub to_ms: i32,
101    /// Buffer used by the `nm_*` helpers to report the last error.
102    pub errbuf: *mut core::ffi::c_char,
103    pub if_flags: u32,
104    pub if_reqcap: u32,
105    pub if_curcap: u32,
106    pub st: nm_stat,
107    /// Last error message written by the `nm_*` helpers.
108    pub msg: [core::ffi::c_char; NM_ERRBUF_SIZE as usize],
109}
110
111#[cfg(not(netmap_disabled))]
112/// Netmap packet header returned by the `nm_*` convenience helpers
113/// (`struct nm_pkthdr` in `netmap_user.h`).
114#[repr(C)]
115#[derive(Debug, Copy, Clone)]
116pub struct nm_pkthdr {
117    pub ts: timeval,
118    pub caplen: core::ffi::c_uint,
119    pub len: core::ffi::c_uint,
120    pub flags: u64,
121    pub d: *mut nm_desc,
122    pub slot: *mut netmap_slot,
123    pub buf: *mut core::ffi::c_uchar,
124}
125
126#[cfg(not(netmap_disabled))]
127/// Netmap statistics (`struct nm_stat` in `netmap_user.h`).
128#[repr(C)]
129#[derive(Debug, Copy, Clone)]
130pub struct nm_stat {
131    pub ps_recv: core::ffi::c_uint,
132    pub ps_drop: core::ffi::c_uint,
133    pub ps_ifdrop: core::ffi::c_uint,
134}
135
136// ---------------------------------------------------------------------------
137// C shim linkage
138// ---------------------------------------------------------------------------
139
140#[cfg(not(netmap_disabled))]
141extern "C" {
142    /// Open a Netmap port (see `netmap_user.h` `nm_open(4)`).
143    ///
144    /// Returns a pointer to a `nm_desc` on success, or a null pointer on
145    /// failure (with `errno` set).
146    #[link_name = "ffi_nm_open"]
147    pub fn nm_open(
148        ifname: *const core::ffi::c_char,
149        req: *const nmreq,
150        flags: u64,
151        arg: *const nm_desc,
152    ) -> *mut nm_desc;
153
154    /// Close a Netmap descriptor opened with [`nm_open`].
155    #[link_name = "ffi_nm_close"]
156    pub fn nm_close(d: *mut nm_desc) -> core::ffi::c_int;
157
158    /// Map the Netmap memory region for a descriptor (see `nm_mmap` in
159    /// `netmap_user.h`).
160    #[link_name = "ffi_nm_mmap"]
161    pub fn nm_mmap(d: *mut nm_desc, parent: *const nm_desc) -> core::ffi::c_int;
162
163    /// Inject a single packet into the TX ring of a descriptor (see `nm_inject`
164    /// in `netmap_user.h`).
165    #[link_name = "ffi_nm_inject"]
166    pub fn nm_inject(
167        d: *mut nm_desc,
168        buf: *const core::ffi::c_void,
169        size: usize,
170    ) -> core::ffi::c_int;
171}
172
173// ---------------------------------------------------------------------------
174// ioctl numbers (bindgen cannot expand the _IO/_IOWR macros)
175// ---------------------------------------------------------------------------
176
177/// Synchronize the TX rings (`_IO('i', 148)` on Linux).
178pub const NIOCTXSYNC: std::os::raw::c_ulong = 0x6994;
179/// Synchronize the RX rings (`_IO('i', 149)` on Linux).
180pub const NIOCRXSYNC: std::os::raw::c_ulong = 0x6995;
181/// Register a Netmap interface with the kernel (`_IOWR('i', 146, struct nmreq)`).
182pub const NIOCREGIF: std::os::raw::c_ulong = 0xc03c_6992;
183/// Control-device request header (`_IOWR('i', 151, struct nmreq_header)`).
184pub const NIOCCTRL: std::os::raw::c_ulong = 0xc058_6997;
185
186// ---------------------------------------------------------------------------
187// Registration-mode constants. The C header exposes the NR_REG_* values as an
188// anonymous enum, which bindgen emits under a generated type name. The aliases
189// below keep the older netmap-rs public names working and map onto the current
190// API using `u32` values.
191// ---------------------------------------------------------------------------
192
193#[cfg(not(netmap_disabled))]
194/// Attach only the host (SW) rings. Legacy name for `NR_REG_SW`.
195pub const NR_REG_SW_ONLY: u32 = NR_REG_SW as u32;
196#[cfg(not(netmap_disabled))]
197/// Attach all NIC rings. Legacy name for `NR_REG_ALL_NIC`.
198pub const NR_REG_NIC_ONLY: u32 = NR_REG_ALL_NIC as u32;
199#[cfg(not(netmap_disabled))]
200/// Attach both NIC and host rings. Legacy name for `NR_REG_NIC_SW`.
201pub const NR_REG_NIC_AND_SW: u32 = NR_REG_NIC_SW as u32;
202
203// ---------------------------------------------------------------------------
204// Function-like macros from netmap_user.h (bindgen does not emit macros).
205// ---------------------------------------------------------------------------
206
207#[cfg(not(netmap_disabled))]
208/// `NETMAP_TXRING(nifp, index)` — the TX ring at `index`, offset from the
209/// `netmap_if` pointer.
210///
211/// # Safety
212/// `nifp` must point to a valid `netmap_if`; `index` must be lower than
213/// `nifp.ni_tx_rings`.
214#[inline]
215pub unsafe fn NETMAP_TXRING(nifp: *const netmap_if, index: u32) -> *mut netmap_ring {
216    let offset = (*nifp).ring_ofs.as_ptr().add(index as usize).read();
217    (nifp as *const u8).add(offset as usize) as *mut netmap_ring
218}
219
220#[cfg(not(netmap_disabled))]
221/// `NETMAP_RXRING(nifp, index)` — the RX ring at `index`, offset from the
222/// `netmap_if` pointer. RX rings start after all TX rings (HW + host).
223///
224/// # Safety
225/// `nifp` must point to a valid `netmap_if`; `index` must be lower than
226/// `nifp.ni_rx_rings`.
227#[inline]
228pub unsafe fn NETMAP_RXRING(nifp: *const netmap_if, index: u32) -> *mut netmap_ring {
229    let base = (*nifp).ni_tx_rings + (*nifp).ni_host_tx_rings;
230    let offset = (*nifp)
231        .ring_ofs
232        .as_ptr()
233        .add((base + index) as usize)
234        .read();
235    (nifp as *const u8).add(offset as usize) as *mut netmap_ring
236}
237
238#[cfg(not(netmap_disabled))]
239/// `NETMAP_IF(base, ofs)` — the `netmap_if` at `ofs` bytes into the mapped
240/// memory region `base`.
241///
242/// # Safety
243/// `base` must point into a valid Netmap shared-memory region.
244#[inline]
245pub unsafe fn NETMAP_IF(base: *const core::ffi::c_void, ofs: isize) -> *mut netmap_if {
246    (base as *const u8).offset(ofs) as *mut netmap_if
247}
248
249#[cfg(not(netmap_disabled))]
250/// `NETMAP_BUF(ring, index)` — pointer to buffer `index` inside `ring`'s buffer
251/// pool.
252///
253/// # Safety
254/// `ring` must point to a valid `netmap_ring`; `index` must be a valid buffer
255/// index for that ring.
256#[inline]
257pub unsafe fn NETMAP_BUF(ring: *const netmap_ring, index: u32) -> *mut core::ffi::c_void {
258    (ring as *const u8)
259        .offset((*ring).buf_ofs as isize)
260        .add(index as usize * (*ring).nr_buf_size as usize) as *mut core::ffi::c_void
261}
262
263#[cfg(netmap_disabled)]
264mod disabled {
265    /// Dummy stamp emitted only when the crate is built without Netmap (via
266    /// `disable-netmap-kernel`, `DISABLE_NETMAP_KERNEL`, or missing headers) so
267    /// the crate is not completely empty; use nothing from this module.
268    pub const NETMAP_DISABLED: bool = true;
269}
270
271#[cfg(all(test, not(netmap_disabled)))]
272mod tests {
273    use super::*;
274    use core::mem;
275
276    #[test]
277    fn struct_sizes() {
278        // Values verified against `gcc -I <netmap include>` on x86-64 Linux.
279        assert_eq!(mem::size_of::<netmap_slot>(), 16);
280        assert_eq!(mem::align_of::<netmap_slot>(), 8);
281        // netmap_ring has a flexible array member; its fixed part is 256 bytes.
282        assert_eq!(mem::size_of::<netmap_ring>(), 256);
283        assert_eq!(mem::size_of::<nmreq>(), 60);
284        // A netmap_if has a fixed header of 56 bytes before the flexible
285        // ring offset array.
286        assert_eq!(mem::size_of::<netmap_if>(), 56);
287        assert_eq!(mem::size_of::<nm_desc>(), 760);
288        assert_eq!(mem::size_of::<nm_pkthdr>(), 56);
289        assert_eq!(mem::size_of::<nm_stat>(), 12);
290    }
291
292    #[test]
293    fn ioctl_constants() {
294        assert_eq!(NIOCTXSYNC, 0x6994);
295        assert_eq!(NIOCRXSYNC, 0x6995);
296        assert_eq!(NETMAP_API, 14);
297        assert_eq!(NR_REG_ALL_NIC as u32, 1);
298        assert_eq!(NR_REG_SW as u32, 2);
299    }
300
301    #[test]
302    fn reg_mode_constants_make_sense() {
303        let all_nic = NR_REG_ALL_NIC as u32;
304        let sw = NR_REG_SW as u32;
305        let nic_sw = NR_REG_NIC_SW as u32;
306        assert!(all_nic < sw);
307        assert!(sw < nic_sw);
308        assert_eq!(NR_REG_SW_ONLY, NR_REG_SW as u32);
309        assert_eq!(NR_REG_NIC_ONLY, NR_REG_ALL_NIC as u32);
310    }
311
312    #[test]
313    fn ring_macro_shims_are_unsafe_fn() {
314        // Compile-time check that the macros are exposed as unsafe functions.
315        fn _assert_fn_ptr(_f: unsafe fn(*const netmap_if, u32) -> *mut netmap_ring) {}
316        _assert_fn_ptr(NETMAP_TXRING);
317        _assert_fn_ptr(NETMAP_RXRING);
318    }
319}