#![forbid(unsafe_code)]
use std::os::fd::AsFd;
use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, sys::socket::SockaddrStorage};
use crate::{
cache::UnixVal,
compat::{sockaddr_family, AddressFamily},
cookie::safe_connect,
fd::{fd_inode, has_send_timeout, peer_inode},
ip::{IpProto, SockInfo},
kernel::net::{handle_safe_bind, NetAddr},
path::XPath,
proc::util::proc_tgid,
req::UNotifyEventRequest,
unix::unix_path_bytes,
};
#[expect(clippy::cognitive_complexity)]
pub(crate) fn handle_connect(
request: &UNotifyEventRequest,
sock: SockInfo,
addr: (SockaddrStorage, SockaddrStorage),
target: (NetAddr, Option<IpProto>),
allow_safe_bind: bool,
is_nonblock: bool,
) -> Result<ScmpNotifResp, Errno> {
let (addr, addr_arg) = addr;
let (target, ip_proto) = target;
let unix_ids = if sockaddr_family(&addr) == AddressFamily::Unix {
match (fd_inode(sock.fd()), proc_tgid(request.scmpreq.pid())) {
(_, Err(_)) => return Err(Errno::ESRCH),
(Ok(_), Ok(_)) if !request.is_valid() => return Err(Errno::ESRCH),
(Ok(inode), Ok(pid)) => Some((inode, pid)),
_ => None,
}
} else {
None
};
if let Some((ino, pid)) = unix_ids {
let _ = request.add_unix2(
ino,
pid, UnixVal::default(),
);
}
let result = do_connect(request, sock.fd(), addr, is_nonblock);
if result.is_err() {
if let Some((ino, _)) = unix_ids {
request.cache.unix_map.remove_if_mut_sync(&ino, |entry| {
entry.self_pid = None;
entry.addr.is_none() && entry.dest.is_empty() && entry.peer_pid.is_none()
});
}
}
if result.is_ok() {
if sockaddr_family(&addr) == AddressFamily::Unspec {
if let Ok(ino) = fd_inode(sock.fd()) {
request.cache.unix_map.remove_if_mut_sync(&ino, |entry| {
entry.self_pid = None;
entry.addr.is_none() && entry.dest.is_empty() && entry.peer_pid.is_none()
});
}
}
match target {
NetAddr::Inet(ip, port) => request
.get_sandbox()
.move_on_connect_inet(ip, port, ip_proto),
NetAddr::Unix(name) => request.get_sandbox().move_on_connect_unix(&name),
NetAddr::UnixPath(root) => request.get_sandbox().move_on_connect_unix(root.abs()),
NetAddr::UnixUnnamed => request
.get_sandbox()
.move_on_connect_unix(XPath::from_bytes(b"!unnamed")),
NetAddr::None => {}
}
if allow_safe_bind
&& matches!(
sockaddr_family(&addr),
AddressFamily::Inet | AddressFamily::Inet6
)
{
let _ = handle_safe_bind(request, sock.fd());
} else if sockaddr_family(&addr) == AddressFamily::Unix {
if let Some((inode, client_pid)) = unix_ids {
let unix_peer = addr_arg.as_unix_addr().filter(|u| u.path().is_some());
let (ddev, dino) = unix_peer
.and_then(unix_path_bytes)
.map(XPath::from_bytes)
.and_then(|path| request.lookup_unix_vfs_id(path).ok())
.map_or((None, None), |(dev, ino)| (Some(dev), Some(ino)));
let mut unix_val = UnixVal::default();
if let (Some(dev), Some(ino)) = (ddev, dino) {
if unix_val.dest.try_reserve(1).is_ok() {
unix_val.dest.push((dev, ino));
}
}
let _ = request.add_unix2(inode, client_pid, unix_val);
let server_pid = unix_peer
.and_then(unix_path_bytes)
.map(XPath::from_bytes)
.and_then(|p| request.unix_owner(p));
if let (Some(server_pid), Ok(peer)) = (server_pid, peer_inode(inode)) {
if request.is_valid() {
let _ = request.set_unix_peer(peer, server_pid, client_pid);
}
}
}
}
}
result.map(|_| request.return_syscall(0))
}
fn do_connect<Fd: AsFd>(
request: &UNotifyEventRequest,
fd: Fd,
addr: SockaddrStorage,
is_nonblock: bool,
) -> Result<(), Errno> {
let req = request.scmpreq;
let is_blocking = if !is_nonblock {
let ignore_restart = has_send_timeout(&fd)?;
request.cache.add_sys_block(req, ignore_restart)?;
true
} else {
false
};
let result = safe_connect(fd, &addr);
if is_blocking {
request.cache.del_sys_block(req.id)?;
}
result
}