1use anyhow::Result;
27use rustnet_core::network::types::{Connection, Protocol};
28use std::borrow::Cow;
29use std::collections::HashMap;
30use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
31
32#[derive(Debug, Clone, PartialEq, Default)]
34pub enum DegradationReason {
35 #[default]
37 None,
38 #[cfg(target_os = "linux")]
41 MissingCapBpf,
42 #[cfg(target_os = "linux")]
44 MissingCapPerfmon,
45 #[cfg(target_os = "linux")]
47 MissingBpfCapabilities,
48 #[cfg(all(target_os = "linux", not(feature = "ebpf")))]
50 EbpfFeatureDisabled,
51 #[cfg(target_os = "linux")]
53 KernelUnsupported,
54 #[cfg(target_os = "linux")]
57 BpfPermissionDenied,
58 #[cfg(target_os = "linux")]
61 KprobeAttachFailed(String),
62 #[cfg(target_os = "linux")]
64 BtfUnavailable,
65 #[cfg(target_os = "linux")]
67 EbpfLoadFailed(String),
68 #[cfg(target_os = "linux")]
72 BinaryOnNosuidMount,
73 #[cfg(target_os = "macos")]
76 MissingRootPrivileges,
77 #[cfg(target_os = "macos")]
79 NoBpfDeviceAccess,
80 #[cfg(target_os = "macos")]
82 BpfFilterIncompatible,
83 #[cfg(target_os = "macos")]
85 InterfaceSpecified,
86}
87
88impl DegradationReason {
89 pub fn description(&self) -> Cow<'_, str> {
91 match self {
92 Self::None => Cow::Borrowed(""),
93 #[cfg(target_os = "linux")]
94 Self::MissingCapBpf => Cow::Borrowed("needs CAP_BPF"),
95 #[cfg(target_os = "linux")]
96 Self::MissingCapPerfmon => Cow::Borrowed("needs CAP_PERFMON"),
97 #[cfg(target_os = "linux")]
98 Self::MissingBpfCapabilities => Cow::Borrowed("needs CAP_BPF+CAP_PERFMON"),
99 #[cfg(all(target_os = "linux", not(feature = "ebpf")))]
100 Self::EbpfFeatureDisabled => Cow::Borrowed("eBPF feature disabled"),
101 #[cfg(target_os = "linux")]
102 Self::KernelUnsupported => Cow::Borrowed("kernel unsupported"),
103 #[cfg(target_os = "linux")]
104 Self::BpfPermissionDenied => Cow::Borrowed(
105 "BPF denied (check perf_event_paranoid / AppArmor / unprivileged_bpf_disabled)",
106 ),
107 #[cfg(target_os = "linux")]
108 Self::KprobeAttachFailed(sym) => {
109 if sym.is_empty() {
110 Cow::Borrowed("kprobe attach failed")
111 } else {
112 Cow::Owned(format!("kprobe attach failed: {sym}"))
113 }
114 }
115 #[cfg(target_os = "linux")]
116 Self::BtfUnavailable => Cow::Borrowed("kernel BTF unavailable"),
117 #[cfg(target_os = "linux")]
118 Self::EbpfLoadFailed(s) => Cow::Owned(format!("eBPF load failed: {s}")),
119 #[cfg(target_os = "linux")]
120 Self::BinaryOnNosuidMount => {
121 Cow::Borrowed("file caps ignored: binary on a nosuid mount")
122 }
123 #[cfg(target_os = "macos")]
124 Self::MissingRootPrivileges => Cow::Borrowed("needs root"),
125 #[cfg(target_os = "macos")]
126 Self::NoBpfDeviceAccess => Cow::Borrowed("no BPF device access"),
127 #[cfg(target_os = "macos")]
128 Self::BpfFilterIncompatible => Cow::Borrowed("BPF filter incompatible"),
129 #[cfg(target_os = "macos")]
130 Self::InterfaceSpecified => Cow::Borrowed("interface specified"),
131 }
132 }
133
134 pub fn unavailable_feature(&self) -> Option<&str> {
136 match self {
137 Self::None => None,
138 #[cfg(target_os = "linux")]
139 Self::MissingCapBpf
140 | Self::MissingCapPerfmon
141 | Self::MissingBpfCapabilities
142 | Self::KernelUnsupported
143 | Self::BpfPermissionDenied
144 | Self::KprobeAttachFailed(_)
145 | Self::BtfUnavailable
146 | Self::EbpfLoadFailed(_)
147 | Self::BinaryOnNosuidMount => Some("eBPF"),
148 #[cfg(all(target_os = "linux", not(feature = "ebpf")))]
149 Self::EbpfFeatureDisabled => Some("eBPF"),
150 #[cfg(target_os = "macos")]
151 Self::MissingRootPrivileges
152 | Self::NoBpfDeviceAccess
153 | Self::BpfFilterIncompatible
154 | Self::InterfaceSpecified => Some("PKTAP"),
155 }
156 }
157}
158
159#[cfg(target_os = "freebsd")]
161mod freebsd;
162#[cfg(target_os = "linux")]
163mod linux;
164#[cfg(target_os = "macos")]
165mod macos;
166#[cfg(target_os = "windows")]
167mod windows;
168
169#[cfg(target_os = "freebsd")]
171pub use freebsd::create_process_lookup;
172#[cfg(target_os = "linux")]
173pub use linux::create_process_lookup;
174#[cfg(target_os = "macos")]
175pub use macos::{create_process_lookup, report_pktap_degradation};
176#[cfg(target_os = "windows")]
177pub use windows::create_process_lookup;
178
179pub trait ProcessLookup: Send + Sync {
181 fn get_process_for_connection(&self, conn: &Connection) -> Option<(u32, String)>;
184
185 fn refresh(&self) -> Result<()> {
187 Ok(()) }
189
190 fn get_detection_method(&self) -> &str;
192
193 fn get_degradation_reason(&self) -> DegradationReason {
196 DegradationReason::None }
198
199 fn fallback_lookup(
210 map: &HashMap<ConnectionKey, (u32, String)>,
211 key: &ConnectionKey,
212 ) -> Option<(u32, String)>
213 where
214 Self: Sized,
215 {
216 if !matches!(key.protocol, Protocol::Tcp | Protocol::Udp) {
219 return None;
220 }
221
222 let zero = |addr: SocketAddr| -> IpAddr {
223 match addr {
224 SocketAddr::V4(_) => IpAddr::V4(Ipv4Addr::UNSPECIFIED),
225 SocketAddr::V6(_) => IpAddr::V6(Ipv6Addr::UNSPECIFIED),
226 }
227 };
228
229 let lip = key.local_addr.ip();
230 let lport = key.local_addr.port();
231 let rip = key.remote_addr.ip();
232 let rport = key.remote_addr.port();
233 let zlip = zero(key.local_addr);
234 let zrip = zero(key.remote_addr);
235
236 let candidates: [(IpAddr, u16, IpAddr, u16); 3] = [
237 (lip, lport, zrip, 0), (zlip, lport, rip, rport), (zlip, lport, zrip, 0), ];
241
242 let mut found: Option<(u32, String)> = None;
246 for (l_ip, l_port, r_ip, r_port) in candidates {
247 let candidate = ConnectionKey {
248 protocol: key.protocol,
249 local_addr: SocketAddr::new(l_ip, l_port),
250 remote_addr: SocketAddr::new(r_ip, r_port),
251 };
252 if let Some(entry) = map.get(&candidate) {
253 match &found {
254 None => found = Some(entry.clone()),
255 Some(existing) if existing == entry => {} Some(_) => return None, }
258 }
259 }
260 found
261 }
262}
263
264#[derive(Debug, Clone, Hash, PartialEq, Eq)]
266pub struct ConnectionKey {
267 pub protocol: Protocol,
268 pub local_addr: SocketAddr,
269 pub remote_addr: SocketAddr,
270}
271
272impl ConnectionKey {
273 pub fn from_connection(conn: &Connection) -> Self {
274 Self {
275 protocol: conn.protocol,
276 local_addr: conn.local_addr,
277 remote_addr: conn.remote_addr,
278 }
279 }
280}