#![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_recv_timeout, peer_inode, SafeOwnedFd},
ip::IpProto,
kernel::net::{handle_safe_bind, NetAddr},
path::XPath,
proc::proc_tgid,
req::UNotifyEventRequest,
unix::unix_path_bytes,
};
pub(crate) fn handle_connect(
request: &UNotifyEventRequest,
fd: SafeOwnedFd,
addr: (SockaddrStorage, SockaddrStorage),
target: (NetAddr, Option<IpProto>),
allow_safe_bind: bool,
is_nonblock: bool,
) -> Result<ScmpNotifResp, Errno> {
let (addr, argaddr) = addr;
let (target, sock_proto) = target;
let ino = if sockaddr_family(&addr) == AddressFamily::Unix {
let peer = argaddr.as_unix_addr().filter(|u| u.path().is_some());
if let (Ok(ino), Ok(pid)) = (fd_inode(&fd), proc_tgid(request.scmpreq.pid())) {
let _ = request.add_unix2(
ino,
pid,
UnixVal {
peer: peer.copied(),
..UnixVal::default()
},
);
Some(ino)
} else {
None
}
} else {
None
};
let result = do_connect(&fd, addr, request, is_nonblock);
if result.is_err() {
if let Some(ino) = ino {
request.cache.unix_map.remove_if_sync(&ino, |entry| {
entry.peer = None;
entry.self_pid = None;
entry.addr.is_none() && entry.dest.is_empty() && entry.peer_pid.is_none()
});
}
}
if result.is_ok() {
match target {
NetAddr::Inet(ip, port) => request
.get_sandbox()
.move_on_connect_inet(ip, port, sock_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, &fd);
} else if sockaddr_family(&addr) == AddressFamily::Unix {
let unix_peer = argaddr.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 {
peer: unix_peer.copied(),
..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_unix(&fd, request.scmpreq.pid(), unix_val);
if let (Ok(inode), Ok(client_pid)) = (fd_inode(&fd), proc_tgid(request.scmpreq.pid())) {
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)) {
let _ = request.set_unix_peer(peer, server_pid, client_pid);
}
}
}
}
result.map(|_| request.return_syscall(0))
}
fn do_connect<Fd: AsFd>(
fd: Fd,
addr: SockaddrStorage,
request: &UNotifyEventRequest,
is_nonblock: bool,
) -> Result<(), Errno> {
let req = request.scmpreq;
let is_blocking = if !is_nonblock {
let ignore_restart = has_recv_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
}