1use std::fmt;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Backend {
22 Xdp,
24 Sendmmsg,
26 Pnet,
28}
29
30impl fmt::Display for Backend {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 f.write_str(match self {
33 Backend::Xdp => "xdp",
34 Backend::Sendmmsg => "sendmmsg",
35 Backend::Pnet => "pnet",
36 })
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct ProbeReport {
46 pub xdp_compiled: bool,
48 pub sendmmsg_compiled: bool,
50 pub pnet_compiled: bool,
52 pub kernel: Option<(u32, u32)>,
54 pub cap_net_raw: bool,
56 pub libbpf_present: bool,
58 pub selected: Backend,
60}
61
62impl ProbeReport {
63 pub fn render_table(&self) -> String {
65 let yes = "✓";
66 let no = "✗";
67 let yn = |b: bool| if b { yes } else { no };
68 let kernel = self
69 .kernel
70 .map(|(maj, min)| format!("{maj}.{min}"))
71 .unwrap_or_else(|| "n/a".to_string());
72 format!(
73 concat!(
74 "engine probe:\n",
75 " selected backend: {selected}\n",
76 " kernel: {kernel}\n",
77 " CAP_NET_RAW: {cnr}\n",
78 " libbpf present: {bpf}\n",
79 " features: xdp={xdp} sendmmsg={smm} pnet={pnet}\n",
80 ),
81 selected = self.selected,
82 kernel = kernel,
83 cnr = yn(self.cap_net_raw),
84 bpf = yn(self.libbpf_present),
85 xdp = yn(self.xdp_compiled),
86 smm = yn(self.sendmmsg_compiled),
87 pnet = yn(self.pnet_compiled),
88 )
89 }
90}
91
92fn detect_kernel() -> Option<(u32, u32)> {
93 if !cfg!(target_os = "linux") {
94 return None;
95 }
96 let raw = std::fs::read_to_string("/proc/sys/kernel/osrelease").ok()?;
97 let head = raw.trim().split('-').next()?;
98 let mut parts = head.split('.');
99 let maj: u32 = parts.next()?.parse().ok()?;
100 let min: u32 = parts.next()?.parse().ok()?;
101 Some((maj, min))
102}
103
104fn detect_cap_net_raw() -> bool {
105 #[cfg(target_os = "linux")]
109 unsafe {
110 libc::geteuid() == 0
111 }
112 #[cfg(not(target_os = "linux"))]
113 {
114 false
115 }
116}
117
118fn detect_libbpf() -> bool {
119 for path in [
122 "/usr/lib/x86_64-linux-gnu/libbpf.so.1",
123 "/usr/lib/x86_64-linux-gnu/libbpf.so.0",
124 "/usr/lib64/libbpf.so.1",
125 "/usr/lib64/libbpf.so.0",
126 ] {
127 if std::path::Path::new(path).exists() {
128 return true;
129 }
130 }
131 false
132}
133
134pub fn probe() -> ProbeReport {
141 let xdp_compiled = cfg!(feature = "xdp");
142 let sendmmsg_compiled = cfg!(target_os = "linux");
144 let pnet_compiled = true;
145
146 let kernel = detect_kernel();
147 let cap_net_raw = detect_cap_net_raw();
148 let libbpf_present = detect_libbpf();
149
150 let xdp_runnable = xdp_compiled
152 && cap_net_raw
153 && libbpf_present
154 && kernel.map_or(false, |(maj, min)| maj > 5 || (maj == 5 && min >= 10));
155 let sendmmsg_runnable = sendmmsg_compiled && cap_net_raw;
156
157 let selected = if xdp_runnable {
158 Backend::Xdp
159 } else if sendmmsg_runnable {
160 Backend::Sendmmsg
161 } else {
162 Backend::Pnet
163 };
164
165 ProbeReport {
166 xdp_compiled,
167 sendmmsg_compiled,
168 pnet_compiled,
169 kernel,
170 cap_net_raw,
171 libbpf_present,
172 selected,
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn probe_does_not_panic() {
182 let _ = probe();
183 }
184
185 #[test]
186 fn render_table_contains_all_rows() {
187 let r = probe();
188 let s = r.render_table();
189 for needle in [
190 "selected backend",
191 "kernel",
192 "CAP_NET_RAW",
193 "libbpf present",
194 "features",
195 ] {
196 assert!(s.contains(needle), "table missing row `{needle}`: {s}");
197 }
198 }
199
200 #[test]
201 fn unprivileged_never_picks_xdp() {
202 let r = probe();
206 if !r.cap_net_raw {
207 assert_ne!(r.selected, Backend::Xdp);
208 }
209 }
210
211 #[test]
212 fn pnet_is_the_universal_fallback() {
213 let r = probe();
214 if !r.cap_net_raw {
215 assert_eq!(r.selected, Backend::Pnet);
216 }
217 }
218
219 #[test]
220 fn selected_backend_displays_lowercase() {
221 assert_eq!(format!("{}", Backend::Xdp), "xdp");
222 assert_eq!(format!("{}", Backend::Sendmmsg), "sendmmsg");
223 assert_eq!(format!("{}", Backend::Pnet), "pnet");
224 }
225
226 #[test]
227 fn kernel_parsing_handles_release_strings() {
228 let r = probe();
232 if cfg!(target_os = "linux") {
233 assert!(r.kernel.is_some(), "kernel must parse on linux");
234 } else {
235 assert!(r.kernel.is_none(), "kernel must be None off linux");
236 }
237 }
238}