use std::net::IpAddr;
use std::sync::Arc;
use crate::seccomp::ctx::SupervisorCtx;
use crate::seccomp::notif::NetworkPolicy;
use crate::sys::structs::ECONNREFUSED;
use super::Protocol;
pub(crate) fn destination_verdict(
effective: &NetworkPolicy,
ip: IpAddr,
port: Option<u16>,
) -> Result<(), i32> {
if matches!(effective, NetworkPolicy::Unrestricted) {
return Ok(());
}
match port {
Some(p) if effective.allows(ip, p) => Ok(()),
_ => Err(ECONNREFUSED),
}
}
pub(crate) async fn check_ip_destination(
ctx: &Arc<SupervisorCtx>,
pid: u32,
protocol: Protocol,
ip: IpAddr,
port: Option<u16>,
) -> Result<(), i32> {
let ns = ctx.network.lock().await;
let live_policy = {
let pfs = ctx.policy_fn.lock().await;
pfs.live_policy.clone()
};
let effective = ns.effective_network_policy(pid, protocol, live_policy.as_ref());
drop(ns);
destination_verdict(&effective, ip, port)
}
pub(crate) fn real_path_under_any(real: &std::path::Path, prefixes: &[std::path::PathBuf]) -> bool {
prefixes.iter().any(|p| {
let canon = std::fs::canonicalize(p);
real.starts_with(canon.as_deref().unwrap_or(p))
})
}
pub(crate) fn path_under_any(path: &std::path::Path, prefixes: &[std::path::PathBuf]) -> bool {
let norm = crate::chroot::resolve::confine(&path.to_string_lossy());
prefixes.iter().any(|p| norm.starts_with(p))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::seccomp::notif::PortAllow;
use std::collections::{HashMap, HashSet};
fn allowlist_for(ip: &str, port: u16) -> NetworkPolicy {
let mut per_ip = HashMap::new();
per_ip.insert(
ip.parse::<IpAddr>().unwrap(),
PortAllow::Specific(HashSet::from([port])),
);
NetworkPolicy::AllowList {
per_ip,
cidrs: Vec::new(),
any_ip_ports: HashSet::new(),
}
}
#[test]
fn verdict_unrestricted_allows_everything() {
let p = NetworkPolicy::Unrestricted;
let ip: IpAddr = "1.2.3.4".parse().unwrap();
assert_eq!(destination_verdict(&p, ip, Some(80)), Ok(()));
assert_eq!(destination_verdict(&p, ip, None), Ok(()));
}
#[test]
fn verdict_allowlist_matches_ip_and_port() {
let p = allowlist_for("10.0.0.1", 443);
let allowed: IpAddr = "10.0.0.1".parse().unwrap();
let other: IpAddr = "10.0.0.2".parse().unwrap();
assert_eq!(destination_verdict(&p, allowed, Some(443)), Ok(()));
assert_eq!(destination_verdict(&p, allowed, Some(80)), Err(ECONNREFUSED));
assert_eq!(destination_verdict(&p, other, Some(443)), Err(ECONNREFUSED));
}
#[test]
fn verdict_fails_closed_on_unparseable_port() {
let p = allowlist_for("10.0.0.1", 443);
let allowed: IpAddr = "10.0.0.1".parse().unwrap();
assert_eq!(destination_verdict(&p, allowed, None), Err(ECONNREFUSED));
}
}