Skip to main content

netring/config/
mod.rs

1//! Configuration types: fanout, BPF filters, timestamps, ring profiles.
2
3use crate::afpacket::ffi;
4
5pub mod bpf;
6pub mod bpf_builder;
7pub(crate) mod bpf_compile;
8pub(crate) mod bpf_humanize;
9pub mod bpf_interp;
10pub mod busy_poll;
11pub mod ipnet;
12
13pub use bpf::{BpfFilter, BpfInsn, BuildError};
14pub use bpf_builder::BpfFilterBuilder;
15pub use busy_poll::BusyPollConfig;
16pub use ipnet::{IpNet, ParseIpNetError};
17
18/// Fanout distribution mode for multi-socket packet sharing.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum FanoutMode {
21    /// Distribute by flow hash (src/dst IP+port).
22    Hash,
23    /// Round-robin across sockets.
24    LoadBalance,
25    /// Route to the CPU that received the NIC interrupt.
26    Cpu,
27    /// Fill one socket, overflow when backlogged.
28    Rollover,
29    /// Random distribution.
30    Random,
31    /// Based on `skb->queue_mapping`.
32    QueueMapping,
33    /// eBPF program selects the target socket.
34    ///
35    /// The program receives the packet and returns the socket index
36    /// (0-based) within the fanout group. After building the socket with
37    /// `.fanout(FanoutMode::Ebpf, group_id)`, attach the program via
38    /// [`Capture::attach_fanout_ebpf()`](crate::Capture::attach_fanout_ebpf).
39    Ebpf,
40}
41
42impl FanoutMode {
43    /// Kernel constant for this mode.
44    pub(crate) const fn as_raw(self) -> u32 {
45        match self {
46            Self::Hash => ffi::PACKET_FANOUT_HASH,
47            Self::LoadBalance => ffi::PACKET_FANOUT_LB,
48            Self::Cpu => ffi::PACKET_FANOUT_CPU,
49            Self::Rollover => ffi::PACKET_FANOUT_ROLLOVER,
50            Self::Random => ffi::PACKET_FANOUT_RND,
51            Self::QueueMapping => ffi::PACKET_FANOUT_QM,
52            Self::Ebpf => ffi::PACKET_FANOUT_EBPF,
53        }
54    }
55}
56
57bitflags::bitflags! {
58    /// Flags OR'd with the fanout mode.
59    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60    pub struct FanoutFlags: u16 {
61        /// Rollover to next socket if selected one's ring is full.
62        const ROLLOVER        = 0x1000;
63        /// Kernel assigns a unique group ID.
64        const UNIQUE_ID       = 0x2000;
65        /// Don't deliver outgoing packets.
66        const IGNORE_OUTGOING = 0x4000;
67        /// Defragment IP before hashing (ensures correct flow distribution).
68        const DEFRAG          = 0x8000;
69    }
70}
71
72/// Kernel timestamp source.
73#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
74pub enum TimestampSource {
75    /// Software timestamp (default).
76    #[default]
77    Software,
78    /// Raw hardware timestamp from NIC.
79    RawHardware,
80    /// System-adjusted hardware timestamp.
81    SysHardware,
82}
83
84impl TimestampSource {
85    /// Kernel constant for `PACKET_TIMESTAMP` setsockopt.
86    pub(crate) const fn as_raw(self) -> libc::c_int {
87        match self {
88            Self::Software => 0,
89            Self::RawHardware => 1,
90            Self::SysHardware => 2,
91        }
92    }
93}
94
95// ── Ring Profiles ──────────────────────────────────────────────────────────
96
97/// Pre-configured ring buffer profiles for common workloads.
98///
99/// Use with [`CaptureBuilder::profile()`](crate::CaptureBuilder::profile)
100/// to set block_size, block_count, frame_size, and block_timeout_ms in one call.
101/// Individual settings can be overridden after applying a profile.
102///
103/// # Examples
104///
105/// ```no_run
106/// use netring::{Capture, RingProfile};
107///
108/// let cap = Capture::builder()
109///     .interface("eth0")
110///     .profile(RingProfile::LowLatency)
111///     .build()
112///     .unwrap();
113/// ```
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
115pub enum RingProfile {
116    /// Balanced defaults. 4 MiB blocks × 64 (256 MiB), 60ms timeout.
117    /// Good for general-purpose capture up to ~500 Kpps.
118    Default,
119
120    /// Maximum throughput. 4 MiB blocks × 256 (1 GiB), 60ms timeout.
121    /// Pair with [`FanoutMode::Cpu`] for multi-core capture.
122    HighThroughput,
123
124    /// Minimal latency. 256 KiB blocks × 64 (16 MiB), 1ms timeout.
125    /// Smaller blocks retire faster. Pair with `busy_poll_us()`.
126    LowLatency,
127
128    /// Minimal memory. 1 MiB blocks × 16 (16 MiB), 100ms timeout.
129    /// For memory-constrained environments.
130    LowMemory,
131
132    /// Large frames / jumbo MTU. 4 MiB blocks × 64, frame_size=65536.
133    /// For interfaces with MTU > 1500 or GRO/GSO enabled.
134    JumboFrames,
135}
136
137impl RingProfile {
138    /// Returns `(block_size, block_count, frame_size, block_timeout_ms)`.
139    #[inline]
140    pub(crate) fn params(self) -> (usize, usize, usize, u32) {
141        match self {
142            Self::Default => (1 << 22, 64, 2048, 60),
143            Self::HighThroughput => (1 << 22, 256, 2048, 60),
144            Self::LowLatency => (1 << 18, 64, 2048, 1),
145            Self::LowMemory => (1 << 20, 16, 2048, 100),
146            Self::JumboFrames => (1 << 22, 64, 65536, 60),
147        }
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn fanout_mode_as_raw() {
157        assert_eq!(FanoutMode::Hash.as_raw(), 0);
158        assert_eq!(FanoutMode::LoadBalance.as_raw(), 1);
159        assert_eq!(FanoutMode::Cpu.as_raw(), 2);
160        assert_eq!(FanoutMode::Rollover.as_raw(), 3);
161        assert_eq!(FanoutMode::Random.as_raw(), 4);
162        assert_eq!(FanoutMode::QueueMapping.as_raw(), 5);
163    }
164
165    #[test]
166    fn fanout_flags_bitwise() {
167        let flags = FanoutFlags::ROLLOVER | FanoutFlags::DEFRAG;
168        assert_eq!(flags.bits(), 0x9000);
169        assert!(flags.contains(FanoutFlags::ROLLOVER));
170        assert!(flags.contains(FanoutFlags::DEFRAG));
171        assert!(!flags.contains(FanoutFlags::UNIQUE_ID));
172    }
173
174    #[test]
175    fn timestamp_source_default() {
176        assert_eq!(TimestampSource::default(), TimestampSource::Software);
177        assert_eq!(TimestampSource::Software.as_raw(), 0);
178    }
179
180    #[test]
181    fn ring_profile_params_valid() {
182        for profile in [
183            RingProfile::Default,
184            RingProfile::HighThroughput,
185            RingProfile::LowLatency,
186            RingProfile::LowMemory,
187            RingProfile::JumboFrames,
188        ] {
189            let (block_size, block_count, frame_size, timeout_ms) = profile.params();
190            assert!(block_size.is_power_of_two(), "{profile:?} block_size");
191            assert!(block_size.is_multiple_of(4096), "{profile:?} page-aligned");
192            assert!(block_count > 0, "{profile:?} block_count");
193            assert!(
194                frame_size >= 68,
195                "{profile:?} frame_size >= TPACKET3_HDRLEN"
196            );
197            assert!(
198                frame_size.is_multiple_of(16),
199                "{profile:?} frame_size aligned"
200            );
201            assert!(
202                frame_size <= block_size,
203                "{profile:?} frame_size <= block_size"
204            );
205            let _ = timeout_ms;
206        }
207    }
208}