use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::Arc;
use crate::seccomp::ctx::SupervisorCtx;
use crate::seccomp::notif::NotifAction;
use crate::sys::structs::{SeccompNotif, ECONNREFUSED};
use super::materialize::{
named_unix_socket_path, parse_ip_from_sockaddr, parse_port_from_sockaddr,
set_port_in_sockaddr, sockaddr_is_ipv6,
};
use super::unix::connect_named_unix_on_behalf;
use super::verdict::{destination_verdict, path_under_any};
use super::query_socket_protocol;
pub(super) async fn connect_on_behalf(
notif: &SeccompNotif,
ctx: &Arc<SupervisorCtx>,
notif_fd: RawFd,
) -> NotifAction {
let args = ¬if.data.args;
let sockfd = args[0] as i32;
let addr_ptr = args[1];
let addr_len = args[2] as u32;
let addr_bytes =
match super::read_sockaddr(notif_fd, notif.id, notif.pid, addr_ptr, addr_len as usize) {
Ok(b) => b,
Err(e) => return NotifAction::Errno(e),
};
if let Some(ip) = parse_ip_from_sockaddr(&addr_bytes) {
if !ctx.policy.ip_connect_supervised(ip.is_loopback()) {
return NotifAction::Continue;
}
let dest_port = parse_port_from_sockaddr(&addr_bytes);
let dup_fd = match crate::seccomp::notif::dup_fd_from_pid(notif.pid, sockfd) {
Ok(fd) => fd,
Err(e) => return NotifAction::Errno(e.raw_os_error().unwrap_or(libc::EBADF)),
};
let protocol = match query_socket_protocol(dup_fd.as_raw_fd()) {
Some(p) => p,
None => return NotifAction::Errno(ECONNREFUSED),
};
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(notif.pid, protocol, live_policy.as_ref());
if let Err(e) = destination_verdict(&effective, ip, dest_port) {
return NotifAction::Errno(e);
}
let proxy = ns
.http_acl_addr
.filter(|_| dest_port.map_or(false, |p| ns.http_acl_ports.contains(&p)));
let remap_port = if ctx.policy.port_remap && ip.is_loopback() {
dest_port.and_then(|p| ns.port_map.get_real(p))
} else {
None
};
let orig_dest_map = ns.http_acl_orig_dest.clone();
drop(ns);
let plan = match plan_connect_target(&addr_bytes, proxy, remap_port) {
Ok(p) => p,
Err(e) => return NotifAction::Errno(e),
};
if plan.record_orig_dest {
if let Some(ref map) = orig_dest_map {
record_orig_dest(map, dup_fd.as_raw_fd(), sockaddr_is_ipv6(&addr_bytes), ip);
}
}
connect_dup(dup_fd.as_raw_fd(), &plan.addr)
} else {
match named_unix_socket_path(&addr_bytes) {
Some(path) if ctx.policy.has_unix_fs_gate => {
if ctx.policy.chroot_root.is_some() {
if path_under_any(&path, &ctx.policy.chroot_writable) {
NotifAction::Continue
} else {
NotifAction::Errno(libc::EACCES)
}
} else {
connect_named_unix_on_behalf(
notif.pid,
sockfd,
&path,
&ctx.policy.chroot_writable,
)
}
}
_ => NotifAction::Continue,
}
}
}
struct ConnectPlan {
addr: Vec<u8>,
record_orig_dest: bool,
}
fn plan_connect_target(
addr_bytes: &[u8],
proxy: Option<std::net::SocketAddr>,
remap_port: Option<u16>,
) -> Result<ConnectPlan, i32> {
let is_ipv6 = sockaddr_is_ipv6(addr_bytes);
if let Some(proxy_addr) = proxy {
let addr = if is_ipv6 {
let mut sa6: libc::sockaddr_in6 = unsafe { std::mem::zeroed() };
sa6.sin6_family = libc::AF_INET6 as u16;
sa6.sin6_port = proxy_addr.port().to_be();
let mapped = match proxy_addr {
std::net::SocketAddr::V4(v4) => v4.ip().to_ipv6_mapped(),
std::net::SocketAddr::V6(v6) => *v6.ip(),
};
sa6.sin6_addr.s6_addr = mapped.octets();
unsafe {
std::slice::from_raw_parts(
&sa6 as *const _ as *const u8,
std::mem::size_of::<libc::sockaddr_in6>(),
)
}
.to_vec()
} else {
let mut sa: libc::sockaddr_in = unsafe { std::mem::zeroed() };
sa.sin_family = libc::AF_INET as u16;
sa.sin_port = proxy_addr.port().to_be();
match proxy_addr {
std::net::SocketAddr::V4(v4) => {
sa.sin_addr.s_addr = u32::from_ne_bytes(v4.ip().octets());
}
std::net::SocketAddr::V6(_) => {
return Err(libc::EAFNOSUPPORT);
}
}
unsafe {
std::slice::from_raw_parts(
&sa as *const _ as *const u8,
std::mem::size_of::<libc::sockaddr_in>(),
)
}
.to_vec()
};
return Ok(ConnectPlan {
addr,
record_orig_dest: true,
});
}
let mut addr = addr_bytes.to_vec();
if let Some(real_port) = remap_port {
set_port_in_sockaddr(&mut addr, real_port);
}
Ok(ConnectPlan {
addr,
record_orig_dest: false,
})
}
fn record_orig_dest(
map: &crate::transparent_proxy::OrigDestMap,
fd: RawFd,
is_ipv6: bool,
orig_ip: IpAddr,
) {
let local_addr = if is_ipv6 {
let mut bind_sa6: libc::sockaddr_in6 = unsafe { std::mem::zeroed() };
bind_sa6.sin6_family = libc::AF_INET6 as u16;
unsafe {
libc::bind(
fd,
&bind_sa6 as *const _ as *const libc::sockaddr,
std::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t,
);
}
let mut local_sa6: libc::sockaddr_in6 = unsafe { std::mem::zeroed() };
let mut local_len: libc::socklen_t =
std::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t;
let gs_ret = unsafe {
libc::getsockname(
fd,
&mut local_sa6 as *mut _ as *mut libc::sockaddr,
&mut local_len,
)
};
if gs_ret != 0 {
return;
}
let local_port = u16::from_be(local_sa6.sin6_port);
let local_ip = Ipv6Addr::from(local_sa6.sin6_addr.s6_addr);
std::net::SocketAddr::V6(std::net::SocketAddrV6::new(local_ip, local_port, 0, 0))
} else {
let mut bind_sa: libc::sockaddr_in = unsafe { std::mem::zeroed() };
bind_sa.sin_family = libc::AF_INET as u16;
unsafe {
libc::bind(
fd,
&bind_sa as *const _ as *const libc::sockaddr,
std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
);
}
let mut local_sa: libc::sockaddr_in = unsafe { std::mem::zeroed() };
let mut local_len: libc::socklen_t =
std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t;
let gs_ret = unsafe {
libc::getsockname(
fd,
&mut local_sa as *mut _ as *mut libc::sockaddr,
&mut local_len,
)
};
if gs_ret != 0 {
return;
}
let local_port = u16::from_be(local_sa.sin_port);
let local_ip = Ipv4Addr::from(u32::from_be(local_sa.sin_addr.s_addr));
std::net::SocketAddr::V4(std::net::SocketAddrV4::new(local_ip, local_port))
};
if let Ok(mut m) = map.write() {
m.insert(local_addr, orig_ip);
}
}
fn connect_dup(fd: RawFd, addr: &[u8]) -> NotifAction {
let ret = unsafe {
libc::connect(
fd,
addr.as_ptr() as *const libc::sockaddr,
addr.len() as libc::socklen_t,
)
};
if ret == 0 {
NotifAction::ReturnValue(0)
} else {
NotifAction::Errno(unsafe { *libc::__errno_location() })
}
}
#[cfg(test)]
mod tests {
use super::*;
fn v4_sockaddr(ip: [u8; 4], port: u16) -> Vec<u8> {
let mut sa: libc::sockaddr_in = unsafe { std::mem::zeroed() };
sa.sin_family = libc::AF_INET as u16;
sa.sin_port = port.to_be();
sa.sin_addr.s_addr = u32::from_ne_bytes(ip);
unsafe {
std::slice::from_raw_parts(
&sa as *const _ as *const u8,
std::mem::size_of::<libc::sockaddr_in>(),
)
}
.to_vec()
}
fn v6_sockaddr(port: u16) -> Vec<u8> {
let mut sa6: libc::sockaddr_in6 = unsafe { std::mem::zeroed() };
sa6.sin6_family = libc::AF_INET6 as u16;
sa6.sin6_port = port.to_be();
sa6.sin6_addr.s6_addr = std::net::Ipv6Addr::LOCALHOST.octets();
unsafe {
std::slice::from_raw_parts(
&sa6 as *const _ as *const u8,
std::mem::size_of::<libc::sockaddr_in6>(),
)
}
.to_vec()
}
#[test]
fn plan_passthrough_keeps_original_bytes() {
let a = v4_sockaddr([10, 0, 0, 1], 443);
let plan = plan_connect_target(&a, None, None).unwrap();
assert_eq!(plan.addr, a);
assert!(!plan.record_orig_dest);
}
#[test]
fn plan_remap_rewrites_only_the_port() {
let a = v4_sockaddr([127, 0, 0, 1], 8080);
let plan = plan_connect_target(&a, None, Some(41234)).unwrap();
assert_eq!(parse_port_from_sockaddr(&plan.addr), Some(41234));
assert_eq!(
parse_ip_from_sockaddr(&plan.addr),
parse_ip_from_sockaddr(&a)
);
assert!(!plan.record_orig_dest);
}
#[test]
fn plan_v4_proxy_redirects_v4_destination() {
let a = v4_sockaddr([93, 184, 216, 34], 80);
let proxy: std::net::SocketAddr = "127.0.0.1:3128".parse().unwrap();
let plan = plan_connect_target(&a, Some(proxy), None).unwrap();
assert_eq!(
parse_ip_from_sockaddr(&plan.addr),
Some("127.0.0.1".parse().unwrap())
);
assert_eq!(parse_port_from_sockaddr(&plan.addr), Some(3128));
assert!(plan.record_orig_dest);
}
#[test]
fn plan_proxy_on_v6_destination_uses_mapped_address() {
let a = v6_sockaddr(80);
let proxy: std::net::SocketAddr = "127.0.0.1:3128".parse().unwrap();
let plan = plan_connect_target(&a, Some(proxy), None).unwrap();
let family = u16::from_ne_bytes([plan.addr[0], plan.addr[1]]);
assert_eq!(family, libc::AF_INET6 as u16);
assert_eq!(
plan.addr[8..24],
"::ffff:127.0.0.1".parse::<std::net::Ipv6Addr>().unwrap().octets()
);
assert_eq!(
parse_ip_from_sockaddr(&plan.addr),
Some("127.0.0.1".parse().unwrap())
);
assert_eq!(parse_port_from_sockaddr(&plan.addr), Some(3128));
assert!(plan.record_orig_dest);
}
#[test]
fn plan_v6_proxy_on_v4_destination_fails_closed() {
let a = v4_sockaddr([93, 184, 216, 34], 80);
let proxy: std::net::SocketAddr = "[::1]:3128".parse().unwrap();
assert_eq!(
plan_connect_target(&a, Some(proxy), None).map(|p| p.addr),
Err(libc::EAFNOSUPPORT)
);
}
#[test]
fn plan_proxy_on_v6_socket_with_mapped_v4_destination_keeps_v6_family() {
let mut a = v6_sockaddr(80);
let mapped: std::net::Ipv6Addr = "::ffff:93.184.216.34".parse().unwrap();
a[8..24].copy_from_slice(&mapped.octets());
let proxy: std::net::SocketAddr = "127.0.0.1:3128".parse().unwrap();
let plan = plan_connect_target(&a, Some(proxy), None).unwrap();
let family = u16::from_ne_bytes([plan.addr[0], plan.addr[1]]);
assert_eq!(family, libc::AF_INET6 as u16);
assert_eq!(parse_port_from_sockaddr(&plan.addr), Some(3128));
assert!(plan.record_orig_dest);
}
#[test]
fn plan_remap_does_not_apply_to_redirect() {
let a = v4_sockaddr([127, 0, 0, 1], 8080);
let proxy: std::net::SocketAddr = "127.0.0.1:3128".parse().unwrap();
let plan = plan_connect_target(&a, Some(proxy), Some(41234)).unwrap();
assert_eq!(parse_port_from_sockaddr(&plan.addr), Some(3128));
assert!(plan.record_orig_dest);
}
}