diff --git a/changelog/2620.added.md b/changelog/2620.added.md new file mode 100644 index 0000000000..25e3e0010f --- /dev/null +++ b/changelog/2620.added.md @@ -0,0 +1 @@ +Add `PeerPidfd` (`SO_PEERPIDFD`) to `socket::sockopt` for Linux diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index f30844881d..28ad17a359 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -10,6 +10,7 @@ use libc::{self, c_int, c_void, socklen_t}; use std::ffi::CString; use std::ffi::{CStr, OsStr, OsString}; use std::mem::{self, MaybeUninit}; +use std::os::fd::OwnedFd; use std::os::unix::ffi::OsStrExt; #[cfg(any(linux_android, target_os = "illumos"))] use std::os::unix::io::{AsFd, AsRawFd}; @@ -192,6 +193,12 @@ macro_rules! sockopt_impl { $name, GetOnly, $level, $flag, usize, $crate::sys::socket::sockopt::GetUsize); }; + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, OwnedFd) => + { + sockopt_impl!($(#[$attr])* + $name, GetOnly, $level, $flag, OwnedFd, $crate::sys::socket::sockopt::GetOwnedFd); + }; + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, bool) => { sockopt_impl!($(#[$attr])* $name, SetOnly, $level, $flag, bool, $crate::sys::socket::sockopt::SetBool); @@ -207,6 +214,12 @@ macro_rules! sockopt_impl { $name, SetOnly, $level, $flag, usize, $crate::sys::socket::sockopt::SetUsize); }; + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, OwnedFd) => + { + sockopt_impl!($(#[$attr])* + $name, SetOnly, $level, $flag, OwnedFd, $crate::sys::socket::sockopt::SetOwnedFd); + }; + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, bool) => { sockopt_impl!($(#[$attr])* $name, Both, $level, $flag, bool, $crate::sys::socket::sockopt::GetBool, $crate::sys::socket::sockopt::SetBool); @@ -222,6 +235,11 @@ macro_rules! sockopt_impl { $name, Both, $level, $flag, usize, $crate::sys::socket::sockopt::GetUsize, $crate::sys::socket::sockopt::SetUsize); }; + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, OwnedFd) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, OwnedFd, $crate::sys::socket::sockopt::GetOwnedFd, $crate::sys::socket::sockopt::SetOwnedFd); + }; + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, OsString<$array:ty>) => { @@ -662,6 +680,15 @@ sockopt_impl!( libc::SO_PEERCRED, super::UnixCredentials ); +#[cfg(target_os = "linux")] +sockopt_impl!( + /// Return the pidfd of the foreign process connected to this socket. + PeerPidfd, + GetOnly, + libc::SOL_SOCKET, + libc::SO_PEERPIDFD, + OwnedFd +); #[cfg(target_os = "freebsd")] #[cfg(feature = "net")] sockopt_impl!( @@ -1804,6 +1831,68 @@ impl<'a> Set<'a, usize> for SetUsize { } } + +/// Getter for a `OwnedFd` value. +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug)] +pub struct GetOwnedFd { + len: socklen_t, + val: MaybeUninit, +} + +impl Get for GetOwnedFd { + fn uninit() -> Self { + GetOwnedFd { + len: mem::size_of::() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr().cast() + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> OwnedFd { + use std::os::fd::{FromRawFd, RawFd}; + + assert_eq!( + self.len as usize, + mem::size_of::(), + "invalid getsockopt implementation" + ); + unsafe { OwnedFd::from_raw_fd(self.val.assume_init() as RawFd) } + } +} + +/// Setter for an `OwnedFd` value. +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SetOwnedFd { + val: c_int, +} + +impl<'a> Set<'a, OwnedFd> for SetOwnedFd { + fn new(val: &'a OwnedFd) -> SetOwnedFd { + use std::os::fd::AsRawFd; + + SetOwnedFd { val: val.as_raw_fd() as c_int } + } + + fn ffi_ptr(&self) -> *const c_void { + &self.val as *const c_int as *const c_void + } + + fn ffi_len(&self) -> socklen_t { + mem::size_of_val(&self.val) as socklen_t + } +} + /// Getter for a `OsString` value. // Hide the docs, because it's an implementation detail of `sockopt_impl!` #[doc(hidden)] diff --git a/test/sys/test_sockopt.rs b/test/sys/test_sockopt.rs index 27e08411b5..11f961448c 100644 --- a/test/sys/test_sockopt.rs +++ b/test/sys/test_sockopt.rs @@ -753,6 +753,46 @@ fn can_get_peercred_on_unix_socket() { assert_ne!(a_cred.pid(), 0); } +#[cfg(target_os = "linux")] +fn pid_from_pidfd(pidfd: OwnedFd) -> u32 { + use std::fs::read_to_string; + + let fd = pidfd.as_raw_fd(); + let fdinfo = read_to_string(format!("/proc/self/fdinfo/{fd}")).unwrap(); + let pidline = fdinfo.split('\n').find(|s| s.starts_with("Pid:")).unwrap(); + pidline.split('\t').next_back().unwrap().parse().unwrap() +} + +#[cfg(target_os = "linux")] +#[test] +fn can_get_peerpidfd_on_unix_socket() { + use nix::sys::socket::{socketpair, sockopt, SockFlag, SockType}; + + let (a, b) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + + match ( + getsockopt(&a, sockopt::PeerPidfd), + getsockopt(&b, sockopt::PeerPidfd), + ) { + (Ok(a_pidfd), Ok(b_pidfd)) => { + let a_pid = pid_from_pidfd(a_pidfd); + let b_pid = pid_from_pidfd(b_pidfd); + assert_eq!(a_pid, b_pid); + assert_ne!(a_pid, 0); + } + (Err(nix::Error::ENOPROTOOPT), Err(nix::Error::ENOPROTOOPT)) => { + // Pidfd can still be unsupported on some CI runners + } + (Err(err), _) | (_, Err(err)) => panic!("{err:?}"), + }; +} + #[test] fn is_socket_type_unix() { use nix::sys::socket::{socketpair, sockopt, SockFlag, SockType};