From e13c295ffe4f9655e3a05affc29ab021b49cebe3 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 4 Dec 2024 16:35:42 -0700 Subject: [PATCH] Make sockopt_impl! public This allows users to define their own sockopts, instead of needing to make a PR to Nix for every single one. Fixes #577 --- changelog/2556.added.md | 2 + src/sys/socket/sockopt.rs | 220 +++++++++++++++++++++++--------------- test/sys/test_sockopt.rs | 48 +++++++++ 3 files changed, 184 insertions(+), 86 deletions(-) create mode 100644 changelog/2556.added.md diff --git a/changelog/2556.added.md b/changelog/2556.added.md new file mode 100644 index 0000000000..6226921e04 --- /dev/null +++ b/changelog/2556.added.md @@ -0,0 +1,2 @@ +Added `sockopt_impl!` to the public API. It's now possible for users to define +their own sockopts without needing to make a PR to Nix. diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index 10704bbdcf..e18ebc412a 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -1,18 +1,19 @@ //! Socket options as used by `setsockopt` and `getsockopt`. -use super::{GetSockOpt, SetSockOpt}; -use crate::errno::Errno; +#[cfg(linux_android)] +use super::SetSockOpt; +#[cfg(linux_android)] +use crate::{errno::Errno, Result}; use crate::sys::time::TimeVal; -use crate::Result; use cfg_if::cfg_if; use libc::{self, c_int, c_void, socklen_t}; -#[cfg(apple_targets)] use std::ffi::{CStr, CString}; -#[cfg(any(target_os = "freebsd", linux_android))] use std::ffi::{OsStr, OsString}; use std::mem::{self, MaybeUninit}; #[cfg(any(target_os = "freebsd", linux_android))] use std::os::unix::ffi::OsStrExt; -use std::os::unix::io::{AsFd, AsRawFd}; +use std::os::unix::io::AsRawFd; +#[cfg(linux_android)] +use std::os::unix::io::AsFd; // Constants // TCP_CA_NAME_MAX isn't defined in user space include files @@ -26,8 +27,8 @@ const TCP_CA_NAME_MAX: usize = 16; /// This macro aims to help implementing `SetSockOpt` for different socket options that accept /// different kinds of data to be used with `setsockopt`. /// -/// Instead of using this macro directly consider using `sockopt_impl!`, especially if the option -/// you are implementing represents a simple type. +/// Instead of using this macro directly consider using [`sockopt_impl!`](crate::sockopt_impl), +/// especially if the option you are implementing represents a simple type. /// /// # Arguments /// @@ -42,25 +43,33 @@ const TCP_CA_NAME_MAX: usize = 16; /// * Type of the value that you are going to set. /// * Type that implements the `Set` trait for the type from the previous item (like `SetBool` for /// `bool`, `SetUsize` for `usize`, etc.). +#[macro_export] macro_rules! setsockopt_impl { ($name:ident, $level:expr, $flag:path, $ty:ty, $setter:ty) => { #[allow(deprecated)] // to allow we have deprecated socket option - impl SetSockOpt for $name { + impl $crate::sys::socket::SetSockOpt for $name { type Val = $ty; - fn set(&self, fd: &F, val: &$ty) -> Result<()> { - unsafe { - let setter: $setter = Set::new(val); - - let res = libc::setsockopt( + fn set( + &self, + fd: &F, + val: &$ty, + ) -> $crate::Result<()> { + use $crate::sys::socket::sockopt::Set; + let setter: $setter = + $crate::sys::socket::sockopt::Set::new(val); + let level = $level; + let flag = $flag; + let res = unsafe { + libc::setsockopt( fd.as_fd().as_raw_fd(), - $level, - $flag, + level, + flag, setter.ffi_ptr(), setter.ffi_len(), - ); - Errno::result(res).map(drop) - } + ) + }; + $crate::errno::Errno::result(res).map(drop) } } }; @@ -72,8 +81,8 @@ macro_rules! setsockopt_impl { /// This macro aims to help implementing `GetSockOpt` for different socket options that accept /// different kinds of data to be use with `getsockopt`. /// -/// Instead of using this macro directly consider using `sockopt_impl!`, especially if the option -/// you are implementing represents a simple type. +/// Instead of using this macro directly consider using [`sockopt_impl!`](crate::sockopt_impl), +/// especially if the option you are implementing represents a simple type. /// /// # Arguments /// @@ -88,43 +97,52 @@ macro_rules! setsockopt_impl { /// * Type of the value that you are going to get. /// * Type that implements the `Get` trait for the type from the previous item (`GetBool` for /// `bool`, `GetUsize` for `usize`, etc.). +#[macro_export] macro_rules! getsockopt_impl { ($name:ident, $level:expr, $flag:path, $ty:ty, $getter:ty) => { #[allow(deprecated)] // to allow we have deprecated socket option - impl GetSockOpt for $name { + impl $crate::sys::socket::GetSockOpt for $name { type Val = $ty; - fn get(&self, fd: &F) -> Result<$ty> { - unsafe { - let mut getter: $getter = Get::uninit(); - - let res = libc::getsockopt( + fn get( + &self, + fd: &F, + ) -> $crate::Result<$ty> { + use $crate::sys::socket::sockopt::Get; + let mut getter: $getter = + $crate::sys::socket::sockopt::Get::uninit(); + let level = $level; + let flag = $flag; + let res = unsafe { + libc::getsockopt( fd.as_fd().as_raw_fd(), - $level, - $flag, + level, + flag, getter.ffi_ptr(), getter.ffi_len(), - ); - Errno::result(res)?; - - match <$ty>::try_from(getter.assume_init()) { - // In most `getsockopt_impl!` implementations, `assume_init()` - // returns `$ty`, so calling `$ty`::try_from($ty) will always - // succeed. which makes the following `Err(_)` branch - // unreachable. - // - // However, there is indeed one exception, `sockopt::SockType`, - // `assume_init()` returns an `i32`, but `$ty` is `super::SockType`, - // this exception necessitates the use of that `try_from()`, - // and we have to allow the unreachable pattern wraning. - // - // For the reason why we are using `i32` as the underlying - // buffer type for this socket option, see issue: - // https://github.com/nix-rust/nix/issues/1819 - #[allow(unreachable_patterns)] - Err(_) => Err(Errno::EINVAL), - Ok(r) => Ok(r), - } + ) + }; + $crate::errno::Errno::result(res)?; + + // getter is definitely initialized now + let gotten = unsafe{ getter.assume_init() }; + match <$ty>::try_from(gotten) { + // In most `getsockopt_impl!` implementations, `assume_init()` + // returns `$ty`, so calling `$ty`::try_from($ty) will always + // succeed. which makes the following `Err(_)` branch + // unreachable. + // + // However, there is indeed one exception, `sockopt::SockType`, + // `assume_init()` returns an `i32`, but `$ty` is `super::SockType`, + // this exception necessitates the use of that `try_from()`, + // and we have to allow the unreachable pattern wraning. + // + // For the reason why we are using `i32` as the underlying + // buffer type for this socket option, see issue: + // https://github.com/nix-rust/nix/issues/1819 + #[allow(unreachable_patterns)] + Err(_) => Err($crate::errno::Errno::EINVAL), + Ok(r) => Ok(r), } } } @@ -158,58 +176,59 @@ macro_rules! getsockopt_impl { /// * `$setter:ty`: `Set` implementation; optional; only for `SetOnly` and `Both`. // Some targets don't use all rules. #[allow(unused_macro_rules)] +#[macro_export] macro_rules! sockopt_impl { ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, bool) => { sockopt_impl!($(#[$attr])* - $name, GetOnly, $level, $flag, bool, GetBool); + $name, GetOnly, $level, $flag, bool, $crate::sys::socket::sockopt::GetBool); }; ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, u8) => { - sockopt_impl!($(#[$attr])* $name, GetOnly, $level, $flag, u8, GetU8); + sockopt_impl!($(#[$attr])* $name, GetOnly, $level, $flag, u8, $crate::sys::socket::sockopt::GetU8); }; ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, usize) => { sockopt_impl!($(#[$attr])* - $name, GetOnly, $level, $flag, usize, GetUsize); + $name, GetOnly, $level, $flag, usize, $crate::sys::socket::sockopt::GetUsize); }; ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, bool) => { sockopt_impl!($(#[$attr])* - $name, SetOnly, $level, $flag, bool, SetBool); + $name, SetOnly, $level, $flag, bool, $crate::sys::socket::sockopt::SetBool); }; ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, u8) => { - sockopt_impl!($(#[$attr])* $name, SetOnly, $level, $flag, u8, SetU8); + sockopt_impl!($(#[$attr])* $name, SetOnly, $level, $flag, u8, $crate::sys::socket::sockopt::SetU8); }; ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, usize) => { sockopt_impl!($(#[$attr])* - $name, SetOnly, $level, $flag, usize, SetUsize); + $name, SetOnly, $level, $flag, usize, $crate::sys::socket::sockopt::SetUsize); }; ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, bool) => { sockopt_impl!($(#[$attr])* - $name, Both, $level, $flag, bool, GetBool, SetBool); + $name, Both, $level, $flag, bool, $crate::sys::socket::sockopt::GetBool, $crate::sys::socket::sockopt::SetBool); }; ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, u8) => { sockopt_impl!($(#[$attr])* - $name, Both, $level, $flag, u8, GetU8, SetU8); + $name, Both, $level, $flag, u8, $crate::sys::socket::sockopt::GetU8, $crate::sys::socket::sockopt::SetU8); }; ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, usize) => { sockopt_impl!($(#[$attr])* - $name, Both, $level, $flag, usize, GetUsize, SetUsize); + $name, Both, $level, $flag, usize, $crate::sys::socket::sockopt::GetUsize, $crate::sys::socket::sockopt::SetUsize); }; ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, OsString<$array:ty>) => { sockopt_impl!($(#[$attr])* - $name, Both, $level, $flag, OsString, GetOsString<$array>, - SetOsString); + $name, Both, $level, $flag, std::ffi::OsString, $crate::sys::socket::sockopt::GetOsString<$array>, + $crate::sys::socket::sockopt::SetOsString); }; /* @@ -219,7 +238,7 @@ macro_rules! sockopt_impl { ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty) => { sockopt_impl!($(#[$attr])* - $name, GetOnly, $level, $flag, $ty, GetStruct<$ty>); + $name, GetOnly, $level, $flag, $ty, $crate::sys::socket::sockopt::GetStruct<$ty>); }; ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty, @@ -235,7 +254,7 @@ macro_rules! sockopt_impl { ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty) => { sockopt_impl!($(#[$attr])* - $name, SetOnly, $level, $flag, $ty, SetStruct<$ty>); + $name, SetOnly, $level, $flag, $ty, $crate::sys::socket::sockopt::SetStruct<$ty>); }; ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty, @@ -261,8 +280,8 @@ macro_rules! sockopt_impl { ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, $ty:ty) => { sockopt_impl!($(#[$attr])* - $name, Both, $level, $flag, $ty, GetStruct<$ty>, - SetStruct<$ty>); + $name, Both, $level, $flag, $ty, $crate::sys::socket::sockopt::GetStruct<$ty>, + $crate::sys::socket::sockopt::SetStruct<$ty>); }; } @@ -1437,7 +1456,9 @@ impl SetSockOpt for TcpTlsRx { */ /// Helper trait that describes what is expected from a `GetSockOpt` getter. -trait Get { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +pub trait Get { /// Returns an uninitialized value. fn uninit() -> Self; /// Returns a pointer to the stored value. This pointer will be passed to the system's @@ -1451,7 +1472,9 @@ trait Get { } /// Helper trait that describes what is expected from a `SetSockOpt` setter. -trait Set<'a, T> { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +pub trait Set<'a, T> { /// Initialize the setter with a given value. fn new(val: &'a T) -> Self; /// Returns a pointer to the stored value. This pointer will be passed to the system's @@ -1463,7 +1486,10 @@ trait Set<'a, T> { } /// Getter for an arbitrary `struct`. -struct GetStruct { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Debug)] +pub struct GetStruct { len: socklen_t, val: MaybeUninit, } @@ -1495,7 +1521,10 @@ impl Get for GetStruct { } /// Setter for an arbitrary `struct`. -struct SetStruct<'a, T: 'static> { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Debug)] +pub struct SetStruct<'a, T: 'static> { ptr: &'a T, } @@ -1514,7 +1543,10 @@ impl<'a, T> Set<'a, T> for SetStruct<'a, T> { } /// Getter for a boolean value. -struct GetBool { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug)] +pub struct GetBool { len: socklen_t, val: MaybeUninit, } @@ -1546,7 +1578,10 @@ impl Get for GetBool { } /// Setter for a boolean value. -struct SetBool { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SetBool { val: c_int, } @@ -1568,7 +1603,10 @@ impl<'a> Set<'a, bool> for SetBool { /// Getter for an `u8` value. #[cfg(feature = "net")] -struct GetU8 { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug)] +pub struct GetU8 { len: socklen_t, val: MaybeUninit, } @@ -1601,8 +1639,10 @@ impl Get for GetU8 { } /// Setter for an `u8` value. -#[cfg(feature = "net")] -struct SetU8 { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SetU8 { val: u8, } @@ -1622,7 +1662,10 @@ impl<'a> Set<'a, u8> for SetU8 { } /// Getter for an `usize` value. -struct GetUsize { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug)] +pub struct GetUsize { len: socklen_t, val: MaybeUninit, } @@ -1654,7 +1697,10 @@ impl Get for GetUsize { } /// Setter for an `usize` value. -struct SetUsize { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SetUsize { val: c_int, } @@ -1673,13 +1719,14 @@ impl<'a> Set<'a, usize> for SetUsize { } /// Getter for a `OsString` value. -#[cfg(any(target_os = "freebsd", linux_android))] -struct GetOsString> { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Debug)] +pub struct GetOsString> { len: socklen_t, val: MaybeUninit, } -#[cfg(any(target_os = "freebsd", linux_android))] impl> Get for GetOsString { fn uninit() -> Self { GetOsString { @@ -1704,8 +1751,10 @@ impl> Get for GetOsString { } /// Setter for a `OsString` value. -#[cfg(any(target_os = "freebsd", linux_android))] -struct SetOsString<'a> { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SetOsString<'a> { val: &'a OsStr, } @@ -1727,15 +1776,14 @@ impl<'a> Set<'a, OsString> for SetOsString<'a> { } /// Getter for a `CString` value. -#[cfg(apple_targets)] -#[cfg(feature = "net")] -struct GetCString> { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Debug)] +pub struct GetCString> { len: socklen_t, val: MaybeUninit, } -#[cfg(apple_targets)] -#[cfg(feature = "net")] impl> Get for GetCString { fn uninit() -> Self { GetCString { diff --git a/test/sys/test_sockopt.rs b/test/sys/test_sockopt.rs index f75255413d..33a634b875 100644 --- a/test/sys/test_sockopt.rs +++ b/test/sys/test_sockopt.rs @@ -1047,3 +1047,51 @@ fn test_ipv6_recv_traffic_class_opts() { "unsetting IPV6_RECVTCLASS on an inet6 datagram socket should succeed", ); } + +/// Users should be able to define their own sockopts. +mod sockopt_impl { + use nix::sys::socket::{ + getsockopt, setsockopt, socket, AddressFamily, SockFlag, + SockProtocol, SockType, + }; + use std::os::unix::io::AsRawFd; + + sockopt_impl!( + Linger, + Both, + libc::SOL_SOCKET, + libc::SO_LINGER, + libc::linger + ); + #[test] + fn test_linger() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + + let set_linger = libc::linger{ l_onoff: 1, l_linger: 42}; + setsockopt(&fd, Linger, &set_linger).unwrap(); + + let get_linger = getsockopt(&fd, Linger).unwrap(); + assert_eq!(get_linger.l_linger, set_linger.l_linger); + } + + sockopt_impl!(KeepAlive, Both, libc::SOL_SOCKET, libc::SO_KEEPALIVE, bool); + + #[test] + fn test_so_tcp_keepalive() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, KeepAlive, &true).unwrap(); + assert!(getsockopt(&fd, KeepAlive).unwrap()); + } +}