use anyhow::Result;
use rustnet_core::network::types::{Connection, Protocol};
use std::borrow::Cow;
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
#[derive(Debug, Clone, PartialEq, Default)]
pub enum DegradationReason {
#[default]
None,
#[cfg(target_os = "linux")]
MissingCapBpf,
#[cfg(target_os = "linux")]
MissingCapPerfmon,
#[cfg(target_os = "linux")]
MissingBpfCapabilities,
#[cfg(all(target_os = "linux", not(feature = "ebpf")))]
EbpfFeatureDisabled,
#[cfg(target_os = "linux")]
KernelUnsupported,
#[cfg(target_os = "linux")]
BpfPermissionDenied,
#[cfg(target_os = "linux")]
KprobeAttachFailed(String),
#[cfg(target_os = "linux")]
BtfUnavailable,
#[cfg(target_os = "linux")]
EbpfLoadFailed(String),
#[cfg(target_os = "linux")]
BinaryOnNosuidMount,
#[cfg(target_os = "macos")]
MissingRootPrivileges,
#[cfg(target_os = "macos")]
NoBpfDeviceAccess,
#[cfg(target_os = "macos")]
BpfFilterIncompatible,
#[cfg(target_os = "macos")]
InterfaceSpecified,
}
impl DegradationReason {
pub fn description(&self) -> Cow<'_, str> {
match self {
Self::None => Cow::Borrowed(""),
#[cfg(target_os = "linux")]
Self::MissingCapBpf => Cow::Borrowed("needs CAP_BPF"),
#[cfg(target_os = "linux")]
Self::MissingCapPerfmon => Cow::Borrowed("needs CAP_PERFMON"),
#[cfg(target_os = "linux")]
Self::MissingBpfCapabilities => Cow::Borrowed("needs CAP_BPF+CAP_PERFMON"),
#[cfg(all(target_os = "linux", not(feature = "ebpf")))]
Self::EbpfFeatureDisabled => Cow::Borrowed("eBPF feature disabled"),
#[cfg(target_os = "linux")]
Self::KernelUnsupported => Cow::Borrowed("kernel unsupported"),
#[cfg(target_os = "linux")]
Self::BpfPermissionDenied => Cow::Borrowed(
"BPF denied (check perf_event_paranoid / AppArmor / unprivileged_bpf_disabled)",
),
#[cfg(target_os = "linux")]
Self::KprobeAttachFailed(sym) => {
if sym.is_empty() {
Cow::Borrowed("kprobe attach failed")
} else {
Cow::Owned(format!("kprobe attach failed: {sym}"))
}
}
#[cfg(target_os = "linux")]
Self::BtfUnavailable => Cow::Borrowed("kernel BTF unavailable"),
#[cfg(target_os = "linux")]
Self::EbpfLoadFailed(s) => Cow::Owned(format!("eBPF load failed: {s}")),
#[cfg(target_os = "linux")]
Self::BinaryOnNosuidMount => {
Cow::Borrowed("file caps ignored: binary on a nosuid mount")
}
#[cfg(target_os = "macos")]
Self::MissingRootPrivileges => Cow::Borrowed("needs root"),
#[cfg(target_os = "macos")]
Self::NoBpfDeviceAccess => Cow::Borrowed("no BPF device access"),
#[cfg(target_os = "macos")]
Self::BpfFilterIncompatible => Cow::Borrowed("BPF filter incompatible"),
#[cfg(target_os = "macos")]
Self::InterfaceSpecified => Cow::Borrowed("interface specified"),
}
}
pub fn unavailable_feature(&self) -> Option<&str> {
match self {
Self::None => None,
#[cfg(target_os = "linux")]
Self::MissingCapBpf
| Self::MissingCapPerfmon
| Self::MissingBpfCapabilities
| Self::KernelUnsupported
| Self::BpfPermissionDenied
| Self::KprobeAttachFailed(_)
| Self::BtfUnavailable
| Self::EbpfLoadFailed(_)
| Self::BinaryOnNosuidMount => Some("eBPF"),
#[cfg(all(target_os = "linux", not(feature = "ebpf")))]
Self::EbpfFeatureDisabled => Some("eBPF"),
#[cfg(target_os = "macos")]
Self::MissingRootPrivileges
| Self::NoBpfDeviceAccess
| Self::BpfFilterIncompatible
| Self::InterfaceSpecified => Some("PKTAP"),
}
}
}
#[cfg(target_os = "freebsd")]
mod freebsd;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "freebsd")]
pub use freebsd::create_process_lookup;
#[cfg(target_os = "linux")]
pub use linux::create_process_lookup;
#[cfg(target_os = "macos")]
pub use macos::{create_process_lookup, report_pktap_degradation};
#[cfg(target_os = "windows")]
pub use windows::create_process_lookup;
pub trait ProcessLookup: Send + Sync {
fn get_process_for_connection(&self, conn: &Connection) -> Option<(u32, String)>;
fn refresh(&self) -> Result<()> {
Ok(()) }
fn get_detection_method(&self) -> &str;
fn get_degradation_reason(&self) -> DegradationReason {
DegradationReason::None }
fn fallback_lookup(
map: &HashMap<ConnectionKey, (u32, String)>,
key: &ConnectionKey,
) -> Option<(u32, String)>
where
Self: Sized,
{
if !matches!(key.protocol, Protocol::Tcp | Protocol::Udp) {
return None;
}
let zero = |addr: SocketAddr| -> IpAddr {
match addr {
SocketAddr::V4(_) => IpAddr::V4(Ipv4Addr::UNSPECIFIED),
SocketAddr::V6(_) => IpAddr::V6(Ipv6Addr::UNSPECIFIED),
}
};
let lip = key.local_addr.ip();
let lport = key.local_addr.port();
let rip = key.remote_addr.ip();
let rport = key.remote_addr.port();
let zlip = zero(key.local_addr);
let zrip = zero(key.remote_addr);
let candidates: [(IpAddr, u16, IpAddr, u16); 3] = [
(lip, lport, zrip, 0), (zlip, lport, rip, rport), (zlip, lport, zrip, 0), ];
let mut found: Option<(u32, String)> = None;
for (l_ip, l_port, r_ip, r_port) in candidates {
let candidate = ConnectionKey {
protocol: key.protocol,
local_addr: SocketAddr::new(l_ip, l_port),
remote_addr: SocketAddr::new(r_ip, r_port),
};
if let Some(entry) = map.get(&candidate) {
match &found {
None => found = Some(entry.clone()),
Some(existing) if existing == entry => {} Some(_) => return None, }
}
}
found
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct ConnectionKey {
pub protocol: Protocol,
pub local_addr: SocketAddr,
pub remote_addr: SocketAddr,
}
impl ConnectionKey {
pub fn from_connection(conn: &Connection) -> Self {
Self {
protocol: conn.protocol,
local_addr: conn.local_addr,
remote_addr: conn.remote_addr,
}
}
}