use std::io::Write;
#[cfg(target_os = "android")]
use std::os::android::net::SocketAddrExt;
use std::os::fd::{AsFd, OwnedFd};
#[cfg(target_os = "linux")]
use std::os::linux::net::SocketAddrExt;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::net::SocketAddr as UnixSocketAddr;
use std::os::unix::net::UnixStream;
use std::path::Path;
use super::{read_frame, OutQueue, PeerIdentity, ReadDeadline, RpcTransport, MAX_FRAME_LEN};
use crate::rpc::{RpcError, RpcResult};
pub(crate) const MAX_FDS_PER_FRAME: usize = 64;
#[cfg(not(target_vendor = "apple"))]
pub(super) const SEND_FLAGS: rustix::net::SendFlags = rustix::net::SendFlags::NOSIGNAL;
#[cfg(target_vendor = "apple")]
pub(super) const SEND_FLAGS: rustix::net::SendFlags = rustix::net::SendFlags::empty();
#[cfg(not(target_vendor = "apple"))]
const RECV_FLAGS: rustix::net::RecvFlags = rustix::net::RecvFlags::CMSG_CLOEXEC;
#[cfg(target_vendor = "apple")]
const RECV_FLAGS: rustix::net::RecvFlags = rustix::net::RecvFlags::empty();
const FD_SPACE: usize = rustix::cmsg_space!(ScmRights(MAX_FDS_PER_FRAME));
pub(crate) fn send_raw_nosignal(sock: std::os::fd::BorrowedFd<'_>, buf: &[u8]) -> RpcResult<()> {
struct NoSignal<'a>(std::os::fd::BorrowedFd<'a>);
impl Write for NoSignal<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
rustix::net::send(self.0, buf, SEND_FLAGS).map_err(std::io::Error::from)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
super::write_all_reporting(&mut NoSignal(sock), buf)
}
pub(crate) fn send_frame_vectored(
sock: std::os::fd::BorrowedFd<'_>,
buf: &[u8],
fds: &[std::os::fd::BorrowedFd<'_>],
) -> RpcResult<()> {
use rustix::net::{SendAncillaryBuffer, SendAncillaryMessage};
use std::io::IoSlice;
use std::mem::MaybeUninit;
if fds.len() > MAX_FDS_PER_FRAME {
return Err(RpcError::Protocol("too many fds in one RPC frame"));
}
if buf.len() > MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: buf.len(),
max: MAX_FRAME_LEN,
});
}
let header = (buf.len() as u32).to_le_bytes();
let mut slices = [IoSlice::new(&header), IoSlice::new(buf)];
let mut rest: &mut [IoSlice<'_>] = &mut slices;
let total = header.len() + buf.len();
let mut space = [MaybeUninit::uninit(); FD_SPACE];
let mut sent = 0;
while sent < total {
let mut anc = SendAncillaryBuffer::new(&mut space);
if sent == 0 && !fds.is_empty() && !anc.push(SendAncillaryMessage::ScmRights(fds)) {
return Err(RpcError::Protocol(
"failed to attach SCM_RIGHTS ancillary data",
));
}
match rustix::net::sendmsg(sock, rest, &mut anc, SEND_FLAGS) {
Ok(0) => return Err(std::io::Error::from(std::io::ErrorKind::WriteZero).into()),
Ok(n) => {
sent += n;
IoSlice::advance_slices(&mut rest, n);
}
Err(rustix::io::Errno::INTR) => continue,
Err(e) => return Err(std::io::Error::from(e).into()),
}
}
Ok(())
}
pub(crate) fn send_nonblocking<T>(
sock: std::os::fd::BorrowedFd<'_>,
send: impl FnOnce(rustix::net::SendFlags) -> rustix::io::Result<T>,
) -> rustix::io::Result<T> {
#[cfg(not(target_vendor = "apple"))]
{
let _ = sock;
send(SEND_FLAGS | rustix::net::SendFlags::DONTWAIT)
}
#[cfg(target_vendor = "apple")]
{
use rustix::fs::{fcntl_getfl, fcntl_setfl, OFlags};
let flags = fcntl_getfl(sock)?;
if flags.contains(OFlags::NONBLOCK) {
return send(SEND_FLAGS);
}
fcntl_setfl(sock, flags | OFlags::NONBLOCK)?;
let sent = send(SEND_FLAGS);
fcntl_setfl(sock, flags)?;
sent
}
}
pub(crate) const WAITING_SENDS: bool = cfg!(any(target_os = "linux", target_os = "android"));
pub(crate) fn send_waiting(
sock: std::os::fd::BorrowedFd<'_>,
queue: &OutQueue,
slices: &mut [std::io::IoSlice<'_>],
fds: &[std::os::fd::BorrowedFd<'_>],
mut drain: Option<&mut dyn FnMut() -> RpcResult<()>>,
) -> RpcResult<()> {
use rustix::net::{SendAncillaryBuffer, SendAncillaryMessage};
use std::io::IoSlice;
use std::mem::MaybeUninit;
if fds.len() > MAX_FDS_PER_FRAME {
return Err(RpcError::Protocol("too many fds in one RPC frame"));
}
let total: usize = slices.iter().map(|s| s.len()).sum();
if !fds.is_empty() && total == 0 {
return Err(RpcError::Protocol(
"cannot attach fds to an empty RPC frame",
));
}
let mut rest: &mut [IoSlice<'_>] = slices;
let mut space = [MaybeUninit::uninit(); FD_SPACE];
let mut sent = 0;
let mut waiting = SendWait::new(sock, queue);
while sent < total {
let mut anc = SendAncillaryBuffer::new(&mut space);
if sent == 0 && !fds.is_empty() && !anc.push(SendAncillaryMessage::ScmRights(fds)) {
return Err(RpcError::Protocol(
"failed to attach SCM_RIGHTS ancillary data",
));
}
match send_nonblocking(sock, |flags| {
rustix::net::sendmsg(sock, rest, &mut anc, flags)
}) {
Ok(0) => return Err(std::io::Error::from(std::io::ErrorKind::WriteZero).into()),
Ok(n) => {
sent += n;
IoSlice::advance_slices(&mut rest, n);
waiting.progressed();
}
Err(rustix::io::Errno::INTR) => continue,
Err(rustix::io::Errno::AGAIN) => match waiting.wait(drain.is_some())? {
Some(true) => {
if let Some(drain) = drain.as_mut() {
drain().map_err(super::read_side_failure)?;
}
}
Some(false) => {}
None if sent == 0 => return Err(RpcError::Timeout),
None => return Err(RpcError::Io(std::io::ErrorKind::WouldBlock.into())),
},
Err(e) => return Err(std::io::Error::from(e).into()),
}
}
Ok(())
}
pub(crate) struct SendWait<'a> {
sock: std::os::fd::BorrowedFd<'a>,
queue: &'a OutQueue,
since: Option<std::time::Instant>,
limit: Option<Option<std::time::Duration>>,
queued: Option<u32>,
}
impl<'a> SendWait<'a> {
pub(crate) fn new(sock: std::os::fd::BorrowedFd<'a>, queue: &'a OutQueue) -> Self {
SendWait {
sock,
queue,
since: None,
limit: None,
queued: None,
}
}
pub(crate) fn progressed(&mut self) {
self.since = None;
self.queued = None;
}
fn left(&mut self) -> Option<Option<std::time::Duration>> {
use rustix::net::sockopt::{socket_timeout, Timeout};
let sock = self.sock;
let limit = *self
.limit
.get_or_insert_with(|| socket_timeout(sock, Timeout::Send).ok().flatten());
let Some(d) = limit else {
return Some(None);
};
match self.since {
Some(_) => self.look_for_peer_progress(),
None => {
self.queued = self.queue.bytes(sock);
self.since = Some(std::time::Instant::now());
}
}
let since = *self.since.get_or_insert_with(std::time::Instant::now);
match d.checked_sub(since.elapsed()) {
Some(left) if !left.is_zero() => Some(Some(left)),
_ => None,
}
}
fn look_for_peer_progress(&mut self) {
let Some(before) = self.queued else { return };
let Some(now) = self.queue.bytes(self.sock) else {
return;
};
self.queued = Some(now);
if now < before {
self.since = Some(std::time::Instant::now());
}
}
#[cfg(feature = "rpc-tls")]
pub(crate) fn expired(&mut self) -> bool {
self.left().is_none()
}
pub(crate) fn wait(&mut self, input: bool) -> RpcResult<Option<bool>> {
use rustix::event::{poll, PollFd, PollFlags, Timespec};
let events = if input {
PollFlags::IN | PollFlags::OUT
} else {
PollFlags::OUT
};
loop {
let Some(left) = self.left() else {
return Ok(None);
};
let pause = match (left, self.limit, self.queued) {
(Some(left), Some(Some(d)), Some(_)) => Some(left.min(super::look_every(d))),
(left, _, _) => left,
};
let ts = pause.map(|d| Timespec {
tv_sec: d.as_secs().try_into().unwrap_or(i64::MAX),
tv_nsec: d.subsec_nanos() as _,
});
let mut fds = [PollFd::from_borrowed_fd(self.sock, events)];
match poll(&mut fds, ts.as_ref()) {
Ok(0) => {}
Ok(_) => return Ok(Some(fds[0].revents().contains(PollFlags::IN))),
Err(rustix::io::Errno::INTR) => continue,
Err(e) => return Err(std::io::Error::from(e).into()),
}
}
}
}
pub struct UnixTransport {
stream: UnixStream,
peer: PeerIdentity,
desc: String,
fd_recv_lock: std::sync::Mutex<()>,
reads: ReadDeadline,
queue: OutQueue,
}
impl UnixTransport {
pub fn from_stream(stream: UnixStream) -> RpcResult<Self> {
#[cfg(target_vendor = "apple")]
rustix::net::sockopt::set_socket_nosigpipe(&stream, true).map_err(|e| match e {
rustix::io::Errno::INVAL => RpcError::EndOfStream,
e => std::io::Error::from(e).into(),
})?;
let peer = resolve_peer(&stream);
let desc = match stream.peer_addr() {
Ok(a) => format!("unix:{a:?}"),
Err(_) => "unix:socketpair".to_string(),
};
Ok(UnixTransport {
stream,
peer,
desc,
fd_recv_lock: std::sync::Mutex::new(()),
reads: ReadDeadline::default(),
queue: OutQueue::default(),
})
}
pub fn pair() -> RpcResult<(Self, Self)> {
use rustix::net::{AddressFamily, SocketFlags, SocketType};
#[cfg(not(target_vendor = "apple"))]
let flags = SocketFlags::CLOEXEC;
#[cfg(target_vendor = "apple")]
let flags = SocketFlags::empty();
let (a, b) = rustix::net::socketpair(AddressFamily::UNIX, SocketType::STREAM, flags, None)
.map_err(std::io::Error::from)?;
#[cfg(target_vendor = "apple")]
for fd in [&a, &b] {
rustix::io::fcntl_setfd(fd, rustix::io::FdFlags::CLOEXEC)
.map_err(std::io::Error::from)?;
}
Ok((
Self::from_stream(UnixStream::from(a))?,
Self::from_stream(UnixStream::from(b))?,
))
}
pub fn from_owned_fd(fd: OwnedFd) -> RpcResult<Self> {
Self::from_stream(UnixStream::from(fd))
}
pub fn connect(path: impl AsRef<Path>) -> RpcResult<Self> {
let stream = UnixStream::connect(path)?;
Self::from_stream(stream)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn connect_abstract(name: &[u8]) -> RpcResult<Self> {
let addr = UnixSocketAddr::from_abstract_name(name)?;
Self::from_stream(UnixStream::connect_addr(&addr)?)
}
}
fn resolve_peer(stream: &UnixStream) -> PeerIdentity {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
use std::os::fd::AsRawFd;
let mut uc = libc::ucred {
pid: 0,
uid: 0,
gid: 0,
};
let mut len = std::mem::size_of::<libc::ucred>() as libc::socklen_t;
let rc = unsafe {
libc::getsockopt(
stream.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_PEERCRED,
(&mut uc as *mut libc::ucred).cast(),
&mut len,
)
};
if rc != 0 {
return PeerIdentity::Anonymous;
}
PeerIdentity::Local {
uid: uc.uid,
pid: if uc.pid > 0 { uc.pid } else { -1 },
}
}
#[cfg(all(unix, not(target_os = "linux"), not(target_os = "android")))]
{
resolve_peer_bsd(stream)
}
#[cfg(not(unix))]
{
let _ = stream;
PeerIdentity::Anonymous
}
}
#[cfg(all(unix, not(target_os = "linux"), not(target_os = "android")))]
fn resolve_peer_bsd(stream: &UnixStream) -> PeerIdentity {
use std::os::fd::{AsFd, AsRawFd};
let fd = stream.as_raw_fd();
match rustix::net::getsockname(stream.as_fd()) {
Ok(local) if local.address_family() == rustix::net::AddressFamily::UNIX => {}
Ok(_) => {
log::warn!("RPC unix peer-cred: fd is not AF_UNIX; reporting Anonymous");
return PeerIdentity::Anonymous;
}
Err(e) => {
log::warn!("RPC unix peer-cred: getsockname failed ({e}); reporting Anonymous");
return PeerIdentity::Anonymous;
}
}
let mut euid: libc::uid_t = 0;
let mut egid: libc::gid_t = 0;
let rc = unsafe { libc::getpeereid(fd, &mut euid, &mut egid) };
if rc != 0 {
let errno = std::io::Error::last_os_error().raw_os_error();
return match errno {
Some(libc::ENOTCONN) | Some(libc::EINVAL) => super::mem::self_identity(),
_ => {
log::warn!(
"RPC unix peer-cred unavailable (getpeereid errno={errno:?}); \
reporting Anonymous — no peer ACL is possible"
);
PeerIdentity::Anonymous
}
};
}
PeerIdentity::Local {
uid: euid as u32,
pid: peer_pid(fd),
}
}
#[cfg(target_os = "macos")]
fn peer_pid(fd: std::os::fd::RawFd) -> i32 {
let mut pid: libc::pid_t = -1;
let mut len = std::mem::size_of::<libc::pid_t>() as libc::socklen_t;
let rc = unsafe {
libc::getsockopt(
fd,
libc::SOL_LOCAL,
libc::LOCAL_PEERPID,
&mut pid as *mut libc::pid_t as *mut libc::c_void,
&mut len,
)
};
if rc == 0 {
pid
} else {
-1
}
}
#[cfg(all(
unix,
not(target_os = "linux"),
not(target_os = "macos"),
not(target_os = "android")
))]
fn peer_pid(_fd: std::os::fd::RawFd) -> i32 {
-1
}
impl RpcTransport for UnixTransport {
fn send_frame(&self, buf: &[u8]) -> RpcResult<()> {
send_frame_vectored(self.stream.as_fd(), buf, &[])
}
fn recv_frame(&self) -> RpcResult<Vec<u8>> {
let mut r = &self.stream;
read_frame(&mut r)
}
fn send_raw(&self, buf: &[u8]) -> RpcResult<()> {
if WAITING_SENDS {
let slices = &mut [std::io::IoSlice::new(buf)];
return send_waiting(self.stream.as_fd(), &self.queue, slices, &[], None);
}
send_raw_nosignal(self.stream.as_fd(), buf)
}
fn recv_raw(&self, buf: &mut [u8]) -> RpcResult<usize> {
let sock = Some(self.stream.as_fd());
self.reads.read(sock, &self.queue, || {
super::read_raw(&mut &self.stream, buf)
})
}
fn send_raw_with_fds(&self, buf: &[u8], fds: &[std::os::fd::BorrowedFd<'_>]) -> RpcResult<()> {
use rustix::net::{SendAncillaryBuffer, SendAncillaryMessage};
use std::io::IoSlice;
use std::mem::MaybeUninit;
if fds.is_empty() {
return self.send_raw(buf);
}
if WAITING_SENDS {
let slices = &mut [IoSlice::new(buf)];
return send_waiting(self.stream.as_fd(), &self.queue, slices, fds, None);
}
if buf.is_empty() {
return Err(RpcError::Protocol(
"cannot attach fds to an empty RPC frame",
));
}
if fds.len() > MAX_FDS_PER_FRAME {
return Err(RpcError::Protocol("too many fds in one RPC frame"));
}
let mut space = vec![MaybeUninit::uninit(); rustix::cmsg_space!(ScmRights(fds.len()))];
let mut sent = 0;
while sent < buf.len() {
let mut anc = SendAncillaryBuffer::new(&mut space);
if sent == 0 {
if !anc.push(SendAncillaryMessage::ScmRights(fds)) {
return Err(RpcError::Protocol(
"failed to attach SCM_RIGHTS ancillary data",
));
}
}
let n = match rustix::net::sendmsg(
&self.stream,
&[IoSlice::new(&buf[sent..])],
&mut anc,
SEND_FLAGS,
) {
Ok(n) => n,
Err(rustix::io::Errno::INTR) => continue,
Err(e) => {
let e = std::io::Error::from(e);
return Err(if sent == 0 && super::is_timeout(&e) {
RpcError::Timeout
} else {
e.into()
});
}
};
if n == 0 {
return Err(RpcError::EndOfStream);
}
sent += n;
}
Ok(())
}
fn recv_raw_with_fds(&self, buf: &mut [u8]) -> RpcResult<(usize, Vec<std::os::fd::OwnedFd>)> {
use rustix::net::RecvAncillaryBuffer;
use std::io::IoSliceMut;
use std::mem::MaybeUninit;
let mut fds: Vec<std::os::fd::OwnedFd> = Vec::new();
let mut space =
vec![MaybeUninit::uninit(); rustix::cmsg_space!(ScmRights(MAX_FDS_PER_FRAME))];
let mut anc = RecvAncillaryBuffer::new(&mut space);
let r = self
.reads
.read(Some(self.stream.as_fd()), &self.queue, || loop {
match rustix::net::recvmsg(
&self.stream,
&mut [IoSliceMut::new(buf)],
&mut anc,
RECV_FLAGS,
) {
Ok(r) => return Ok(r),
Err(rustix::io::Errno::INTR) => continue,
Err(e) => {
let io_err = std::io::Error::from(e);
if super::is_timeout(&io_err) {
return Err(RpcError::Timeout);
}
return Err(io_err.into());
}
}
})?;
take_scm_rights(r.flags, &mut anc, &mut fds)?;
Ok((r.bytes, fds))
}
fn peer_identity(&self) -> PeerIdentity {
self.peer.clone()
}
fn describe(&self) -> &str {
&self.desc
}
fn set_read_timeout(&self, timeout: Option<std::time::Duration>) -> RpcResult<()> {
self.stream.set_read_timeout(timeout)?;
self.reads.note(timeout);
Ok(())
}
fn set_write_timeout(&self, timeout: Option<std::time::Duration>) -> RpcResult<()> {
self.stream.set_write_timeout(timeout)?;
Ok(())
}
fn shutdown(&self) -> RpcResult<()> {
super::absorb_already_shut(self.stream.shutdown(std::net::Shutdown::Both))
}
fn peer_closed(&self) -> Option<bool> {
use std::os::fd::AsFd;
super::socket_peer_closed(self.stream.as_fd(), super::SocketKind::UnixDomain)
}
fn shutdown_handle(&self) -> Option<Box<dyn FnOnce() + Send>> {
super::socket_shutdown_handle(self.stream.as_fd())
}
fn supports_fd_passing(&self) -> bool {
true
}
fn send_frame_with_fds(
&self,
buf: &[u8],
fds: &[std::os::fd::BorrowedFd<'_>],
) -> RpcResult<()> {
send_frame_vectored(self.stream.as_fd(), buf, fds)
}
fn send_raw_draining(
&self,
buf: &[u8],
fds: &[std::os::fd::BorrowedFd<'_>],
drain: &mut dyn FnMut() -> RpcResult<()>,
) -> RpcResult<()> {
send_waiting(
self.stream.as_fd(),
&self.queue,
&mut [std::io::IoSlice::new(buf)],
fds,
Some(drain),
)
}
fn recv_frame_with_fds(&self) -> RpcResult<(Vec<u8>, Vec<std::os::fd::OwnedFd>)> {
use std::mem::MaybeUninit;
let _reader = self
.fd_recv_lock
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut fds: Vec<std::os::fd::OwnedFd> = Vec::new();
let mut space = [MaybeUninit::uninit(); FD_SPACE];
let mut header = [0u8; 4];
let mut filled = 0;
while filled < header.len() {
filled += self.recv_fd_part(&mut header[filled..], &mut space, &mut fds, filled > 0)?;
}
let len = u32::from_le_bytes(header) as usize;
if len > MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: len,
max: MAX_FRAME_LEN,
});
}
let mut frame = vec![0u8; len];
let mut filled = 0;
while filled < len {
filled += self.recv_fd_part(&mut frame[filled..], &mut space, &mut fds, true)?;
}
Ok((frame, fds))
}
}
impl UnixTransport {
fn recv_fd_part(
&self,
buf: &mut [u8],
space: &mut [std::mem::MaybeUninit<u8>],
fds: &mut Vec<OwnedFd>,
started: bool,
) -> RpcResult<usize> {
use rustix::net::RecvAncillaryBuffer;
use std::io::IoSliceMut;
let consumed = started || !fds.is_empty();
let mut anc = RecvAncillaryBuffer::new(space);
let r = loop {
match rustix::net::recvmsg(
&self.stream,
&mut [IoSliceMut::new(buf)],
&mut anc,
RECV_FLAGS,
) {
Ok(r) => break r,
Err(rustix::io::Errno::INTR) => continue,
Err(e) => {
let io_err = std::io::Error::from(e);
if super::is_timeout(&io_err) {
return Err(if consumed {
RpcError::DeadlineMidFrame
} else {
RpcError::Timeout
});
}
return Err(match RpcError::from(io_err) {
RpcError::EndOfStream if consumed => RpcError::Truncated,
other => other,
});
}
}
};
take_scm_rights(r.flags, &mut anc, fds)?;
if r.bytes == 0 {
return Err(if consumed || !fds.is_empty() {
RpcError::Truncated
} else {
RpcError::EndOfStream
});
}
Ok(r.bytes)
}
}
fn take_scm_rights(
flags: rustix::net::ReturnFlags,
anc: &mut rustix::net::RecvAncillaryBuffer<'_>,
fds: &mut Vec<OwnedFd>,
) -> RpcResult<()> {
use rustix::net::{RecvAncillaryMessage, ReturnFlags};
if flags.contains(ReturnFlags::CTRUNC) {
return Err(RpcError::Protocol(
"SCM_RIGHTS control message truncated (too many fds in one message)",
));
}
for msg in anc.drain() {
if let RecvAncillaryMessage::ScmRights(iter) = msg {
for fd in iter {
#[cfg(target_vendor = "apple")]
rustix::io::fcntl_setfd(&fd, rustix::io::FdFlags::CLOEXEC)
.map_err(std::io::Error::from)?;
fds.push(fd);
if fds.len() > MAX_FDS_PER_FRAME {
return Err(RpcError::Protocol("too many fds in one RPC frame"));
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rpc::RpcError;
use std::sync::Arc;
#[test]
fn unix_roundtrip_all_sizes() {
let (a, b) = UnixTransport::pair().expect("socketpair");
let a = Arc::new(a);
for size in [0usize, 1, 64 * 1024, 1 << 20, (1 << 20) + 1] {
let payload: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
let sender = {
let a = a.clone();
let p = payload.clone();
std::thread::spawn(move || a.send_frame(&p).unwrap())
};
assert_eq!(b.recv_frame().expect("recv"), payload, "size {size}");
sender.join().unwrap();
}
}
#[test]
fn fd_mode_roundtrip_all_sizes() {
use std::os::fd::AsFd;
let (a, b) = UnixTransport::pair().expect("socketpair");
let a = Arc::new(a);
for size in [0usize, 1, 8191, 8192, 8193, 64 * 1024, (1 << 20) + 1] {
let payload: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
let sender = {
let a = a.clone();
let p = payload.clone();
std::thread::spawn(move || {
let (fd, _keep) = UnixStream::pair().expect("an fd to pass");
a.send_frame_with_fds(&p, &[fd.as_fd()]).unwrap()
})
};
let (got, fds) = b.recv_frame_with_fds().expect("recv");
assert_eq!(got, payload, "size {size}");
assert_eq!(fds.len(), 1, "size {size}");
sender.join().unwrap();
}
}
#[test]
fn fd_mode_frames_queued_back_to_back_keep_their_own_fds() {
use rustix::net::sockopt;
use std::os::fd::AsFd;
let (a, b) = UnixTransport::pair().expect("socketpair");
sockopt::set_socket_send_buffer_size(&a.stream, 1 << 20).expect("SO_SNDBUF");
sockopt::set_socket_recv_buffer_size(&b.stream, 1 << 20).expect("SO_RCVBUF");
a.set_write_timeout(Some(std::time::Duration::from_secs(5)))
.expect("a full buffer fails the test instead of hanging it");
let inode = |fd: std::os::fd::BorrowedFd<'_>| {
let st = rustix::fs::fstat(fd).expect("fstat");
(st.st_dev, st.st_ino)
};
let (first_fd, _keep1) = UnixStream::pair().expect("fd for the first frame");
let (second_fd, _keep2) = UnixStream::pair().expect("fd for the second frame");
let first: Vec<u8> = (0..20 * 1024).map(|i| (i % 251) as u8).collect();
let second = b"second frame".to_vec();
a.send_frame_with_fds(&first, &[first_fd.as_fd()])
.expect("send the first frame");
a.send_frame_with_fds(&second, &[second_fd.as_fd()])
.expect("send the second frame");
for (payload, sent) in [(&first, &first_fd), (&second, &second_fd)] {
let (got, fds) = b.recv_frame_with_fds().expect("recv");
assert_eq!(&got, payload, "frame of {} bytes", payload.len());
let got_inodes: Vec<_> = fds.iter().map(|fd| inode(fd.as_fd())).collect();
assert_eq!(
got_inodes,
[inode(sent.as_fd())],
"the {}-byte frame must carry its own fd and no other",
payload.len()
);
}
}
#[cfg(all(
any(target_os = "linux", target_os = "android"),
not(target_arch = "s390x")
))]
#[test]
fn an_fd_send_to_a_closed_peer_is_an_error_not_a_signal() {
use std::os::fd::AsFd;
use std::os::unix::process::ExitStatusExt;
const CHILD: &str = "RSB_UNIX_SIGPIPE_CHILD";
const NAME: &str =
"rpc::transport::unix::tests::an_fd_send_to_a_closed_peer_is_an_error_not_a_signal";
if std::env::var_os(CHILD).is_some() {
unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL) };
let (a, b) = UnixTransport::pair().expect("socketpair");
drop(b);
let file = std::fs::File::open("/dev/null").expect("/dev/null");
let fds = [file.as_fd()];
assert!(a.send_frame_with_fds(b"frame", &fds).is_err());
assert!(a.send_raw_with_fds(b"raw", &fds).is_err());
assert!(a.send_raw(b"raw").is_err());
#[cfg(feature = "rpc-tls")]
{
let (s, peer) = UnixStream::pair().expect("socketpair");
drop(peer);
assert!(crate::rpc::transport::TlsStream::write(&s, b"tls record").is_err());
}
return;
}
let out = std::process::Command::new(std::env::current_exe().expect("current_exe"))
.args(["--exact", NAME, "--test-threads=1"])
.env(CHILD, "1")
.output()
.expect("spawn the child");
assert_eq!(out.status.signal(), None, "a send raised a signal");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
out.status.success() && stdout.contains("1 passed"),
"{:?}\n{stdout}\n{}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn unix_peer_identity_is_this_process_for_socketpair() {
let (a, _b) = UnixTransport::pair().expect("socketpair");
let id = a.peer_identity();
assert_eq!(
id,
PeerIdentity::Local {
uid: rustix::process::getuid().as_raw(),
pid: std::process::id() as i32,
},
"socketpair peer must be this process (got {id})"
);
assert!(a.describe().starts_with("unix:"));
}
#[test]
fn unix_peer_closed_on_drop() {
let (a, b) = UnixTransport::pair().expect("socketpair");
drop(b);
assert!(matches!(a.recv_frame(), Err(RpcError::EndOfStream)));
}
#[test]
fn unix_fd_mode_cut_frame_leaves_nothing_behind() {
use std::io::Write;
let (a, b) = UnixTransport::pair().expect("socketpair");
b.set_read_timeout(Some(std::time::Duration::from_millis(50)))
.expect("deadline");
let mut partial = 8u32.to_le_bytes().to_vec();
partial.extend_from_slice(&[1, 2, 3]);
(&a.stream).write_all(&partial).expect("partial frame");
assert!(matches!(
b.recv_frame_with_fds(),
Err(RpcError::DeadlineMidFrame)
));
b.shutdown().expect("shutdown");
let after = b.recv_frame_with_fds();
assert!(
matches!(after, Err(RpcError::EndOfStream)),
"the cut frame must not come back after shutdown, got {after:?}"
);
b.shutdown().expect("a second shutdown is Ok (idempotent)");
}
#[test]
fn unix_mid_frame_deadline_is_our_own_cut() {
use std::io::Write;
let (a, b) = UnixTransport::pair().expect("socketpair");
b.set_read_timeout(Some(std::time::Duration::from_millis(50)))
.expect("deadline");
assert!(matches!(b.recv_frame(), Err(RpcError::Timeout)));
assert!(matches!(b.recv_frame_with_fds(), Err(RpcError::Timeout)));
let mut partial = 8u32.to_le_bytes().to_vec();
partial.extend_from_slice(&[1, 2, 3]);
let mut w = &a.stream;
w.write_all(&partial).expect("partial frame");
assert!(
matches!(b.recv_frame(), Err(RpcError::DeadlineMidFrame)),
"the framed reader must name our own deadline, not a truncation"
);
w.write_all(&partial).expect("partial frame");
assert!(
matches!(b.recv_frame_with_fds(), Err(RpcError::DeadlineMidFrame)),
"the fd-mode reader must classify the same way"
);
}
#[test]
fn unix_from_owned_fd_roundtrip() {
use rustix::net::{AddressFamily, SocketFlags, SocketType};
let (a, b) = rustix::net::socketpair(
AddressFamily::UNIX,
SocketType::STREAM,
SocketFlags::empty(),
None,
)
.expect("socketpair");
let client = UnixTransport::from_owned_fd(a).expect("adopt a");
let server = UnixTransport::from_owned_fd(b).expect("adopt b");
let payload = b"hello-accessor".to_vec();
let client = Arc::new(client);
let sender = {
let c = client.clone();
let p = payload.clone();
std::thread::spawn(move || c.send_frame(&p).unwrap())
};
assert_eq!(server.recv_frame().expect("recv"), payload);
sender.join().unwrap();
}
#[test]
fn unix_partial_header_then_close_is_truncated() {
let (a, b) = UnixTransport::pair().expect("socketpair");
{
use std::io::Write;
let mut s = &a.stream;
s.write_all(&[1u8, 0]).unwrap(); }
drop(a);
let r = b.recv_frame();
assert!(
matches!(r, Err(RpcError::Truncated)),
"expected Truncated (2-of-4 header consumed before EOF), got {r:?}"
);
}
#[test]
fn a_max_frame_len_body_is_not_refused_for_its_header() {
use crate::rpc::wire_android13::write_aosp_message_with_fds;
use std::os::fd::AsFd;
let (a, b) = UnixTransport::pair().expect("socketpair");
let msg = vec![0u8; 16 + MAX_FRAME_LEN];
let total = 2 * msg.len();
let reader = std::thread::spawn(move || {
let mut buf = vec![0u8; 1 << 20];
let mut got = 0;
while got < total {
match b.recv_raw_with_fds(&mut buf) {
Ok((0, _)) | Err(_) => break,
Ok((n, _)) => got += n,
}
}
got
});
let (fd, _keep) = std::os::unix::net::UnixStream::pair().expect("an fd");
write_aosp_message_with_fds(&a, &msg, &[], Some(&mut || Ok(()))).expect("draining");
write_aosp_message_with_fds(&a, &msg, &[fd.as_fd()], None).expect("with an fd");
assert_eq!(reader.join().expect("reader"), total);
}
#[test]
fn a_wait_without_a_deadline_does_not_look_at_the_queue() {
let null = std::fs::File::open("/dev/null").expect("/dev/null");
let queue = OutQueue::default();
let mut waiting = SendWait::new(null.as_fd(), &queue);
assert!(matches!(waiting.wait(false), Ok(Some(false))));
assert!(matches!(waiting.wait(true), Ok(Some(_))));
assert!(
!queue.refused.load(std::sync::atomic::Ordering::Relaxed),
"a wait with no deadline asked for SIOCOUTQ"
);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn a_wait_woken_by_input_still_sees_the_peer_take_bytes() {
use rustix::net::{recv, send, RecvFlags, SendFlags};
use std::os::fd::AsFd;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
let d = Duration::from_millis(400);
let (ours, theirs) = UnixStream::pair().expect("socketpair");
ours.set_write_timeout(Some(d)).expect("SO_SNDTIMEO");
while send(&ours, &[0u8; 4096], SendFlags::DONTWAIT).is_ok() {}
let stop = Arc::new(AtomicBool::new(false));
let peer = {
let stop = stop.clone();
std::thread::spawn(move || {
let mut buf = [0u8; 4096];
for i in 0u32.. {
if stop.load(Ordering::SeqCst) {
return;
}
let _ = send(&theirs, b"x", SendFlags::DONTWAIT);
if i % 8 == 0 {
let _ = recv(&theirs, &mut buf, RecvFlags::DONTWAIT);
}
std::thread::sleep(Duration::from_millis(5));
}
})
};
let queue = OutQueue::default();
let mut waiting = SendWait::new(ours.as_fd(), &queue);
let t0 = Instant::now();
let mut verdict = Ok(Some(true));
while t0.elapsed() < d * 5 / 2 {
verdict = waiting.wait(true);
match verdict {
Ok(Some(true)) => {
let mut sink = [0u8; 256];
let _ = recv(&ours, &mut sink, RecvFlags::DONTWAIT);
}
Ok(Some(false)) => {}
_ => break,
}
}
stop.store(true, Ordering::SeqCst);
peer.join().expect("peer");
assert!(
matches!(verdict, Ok(Some(_))),
"a peer taking bytes every 40 ms expired a {d:?} wait after {:?}: {verdict:?}",
t0.elapsed()
);
}
}