diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f5c5d122e..a256d0a7a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -472,6 +472,7 @@ jobs: patch -p1 < $GITHUB_WORKSPACE/ci/s390x-stat-have-nsec.patch patch -p1 < $GITHUB_WORKSPACE/ci/aarch64-o-largefile.patch patch -p1 < $GITHUB_WORKSPACE/ci/tcgets2-tcsets2.patch + patch -p1 < $GITHUB_WORKSPACE/ci/more-sockopts.patch ./configure --target-list=${{ matrix.qemu_target }} --prefix=${{ runner.tool_cache }}/qemu --disable-tools --disable-slirp --disable-fdt --disable-capstone --disable-docs ninja -C build install if: matrix.qemu != '' && matrix.os == 'ubuntu-latest' @@ -605,6 +606,7 @@ jobs: patch -p1 < $GITHUB_WORKSPACE/ci/s390x-stat-have-nsec.patch patch -p1 < $GITHUB_WORKSPACE/ci/aarch64-o-largefile.patch patch -p1 < $GITHUB_WORKSPACE/ci/tcgets2-tcsets2.patch + patch -p1 < $GITHUB_WORKSPACE/ci/more-sockopts.patch ./configure --target-list=${{ matrix.qemu_target }} --prefix=${{ runner.tool_cache }}/qemu --disable-tools --disable-slirp --disable-fdt --disable-capstone --disable-docs ninja -C build install if: matrix.qemu != '' && matrix.os == 'ubuntu-latest' @@ -696,6 +698,7 @@ jobs: patch -p1 < $GITHUB_WORKSPACE/ci/s390x-stat-have-nsec.patch patch -p1 < $GITHUB_WORKSPACE/ci/aarch64-o-largefile.patch patch -p1 < $GITHUB_WORKSPACE/ci/tcgets2-tcsets2.patch + patch -p1 < $GITHUB_WORKSPACE/ci/more-sockopts.patch ./configure --target-list=${{ matrix.qemu_target }} --prefix=${{ runner.tool_cache }}/qemu --disable-tools --disable-slirp --disable-fdt --disable-capstone --disable-docs ninja -C build install if: matrix.qemu != '' && matrix.os == 'ubuntu-latest' diff --git a/ci/more-sockopts.patch b/ci/more-sockopts.patch new file mode 100644 index 000000000..54eaab5e9 --- /dev/null +++ b/ci/more-sockopts.patch @@ -0,0 +1,79 @@ +From Dan Gohman +Subject: [PATCH] Implement various socket options. + +This implements the `SO_INCOMING_CPU`, `SO_COOKIE`, and `SO_PROTOCOL` +socket options. + +diff -ur -x roms -x build a/linux-user/generic/sockbits.h b/linux-user/generic/sockbits.h +--- a/linux-user/generic/sockbits.h ++++ b/linux-user/generic/sockbits.h +@@ -60,4 +60,10 @@ + + #define TARGET_SO_PROTOCOL 38 + #define TARGET_SO_DOMAIN 39 ++#ifndef TARGET_SO_INCOMING_CPU ++#define TARGET_SO_INCOMING_CPU 49 ++#endif ++#ifndef TARGET_SO_COOKIE ++#define TARGET_SO_COOKIE 57 ++#endif + #endif +diff -ur -x roms -x build a/linux-user/syscall.c b/linux-user/syscall.c +--- a/linux-user/syscall.c ++++ b/linux-user/syscall.c +@@ -2476,6 +2476,9 @@ + case TARGET_SO_RCVLOWAT: + optname = SO_RCVLOWAT; + break; ++ case TARGET_SO_INCOMING_CPU: ++ optname = SO_INCOMING_CPU; ++ break; + default: + goto unimplemented; + } +@@ -2534,6 +2537,7 @@ + { + abi_long ret; + int len, val; ++ int64_t val64; + socklen_t lv; + + switch(level) { +@@ -2733,6 +2737,27 @@ + case TARGET_SO_DOMAIN: + optname = SO_DOMAIN; + goto int_case; ++ case TARGET_SO_INCOMING_CPU: ++ optname = SO_INCOMING_CPU; ++ goto int_case; ++ case TARGET_SO_COOKIE: ++ optname = SO_COOKIE; ++ if (get_user_u32(len, optlen)) ++ return -TARGET_EFAULT; ++ if (len < 0) ++ return -TARGET_EINVAL; ++ lv = sizeof(val64); ++ ret = get_errno(getsockopt(sockfd, level, optname, &val64, &lv)); ++ if (ret < 0) ++ return ret; ++ if (len > lv) ++ len = lv; ++ assert(len == 8); ++ if (put_user_u64(val64, optval_addr)) ++ return -TARGET_EFAULT; ++ if (put_user_u32(len, optlen)) ++ return -TARGET_EFAULT; ++ break; + default: + goto int_case; + } +@@ -2756,6 +2781,9 @@ + case SO_ERROR: + val = host_to_target_errno(val); + break; ++ case SO_PROTOCOL: ++ val = host_to_target_errno(val); ++ break; + } + if (level == SOL_SOCKET && optname == SO_ERROR) { + val = host_to_target_errno(val); diff --git a/src/backend/libc/net/sockopt.rs b/src/backend/libc/net/sockopt.rs index 29c87f541..2319c7cda 100644 --- a/src/backend/libc/net/sockopt.rs +++ b/src/backend/libc/net/sockopt.rs @@ -4,6 +4,14 @@ use super::ext::{in6_addr_new, in_addr_new}; use crate::backend::c; use crate::backend::conv::{borrowed_fd, ret}; use crate::fd::BorrowedFd; +#[cfg(feature = "alloc")] +#[cfg(any( + linux_like, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos" +))] +use crate::ffi::CStr; use crate::io; use crate::net::sockopt::Timeout; #[cfg(not(any( @@ -18,13 +26,52 @@ use crate::net::sockopt::Timeout; target_os = "nto", )))] use crate::net::AddressFamily; +#[cfg(any( + linux_kernel, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "redox", + target_env = "newlib" +))] +use crate::net::Protocol; +#[cfg(any( + linux_kernel, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "redox", + target_env = "newlib" +))] +use crate::net::RawProtocol; +#[cfg(linux_kernel)] +use crate::net::SocketAddrV6; use crate::net::{Ipv4Addr, Ipv6Addr, SocketType}; -use crate::utils::{as_mut_ptr, as_ptr}; +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +use crate::net::{SocketAddrAny, SocketAddrStorage, SocketAddrV4}; +use crate::utils::as_mut_ptr; +#[cfg(feature = "alloc")] +#[cfg(any( + linux_like, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos" +))] +use alloc::borrow::ToOwned; +#[cfg(feature = "alloc")] +#[cfg(any( + linux_like, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos" +))] +use alloc::string::String; #[cfg(apple)] use c::TCP_KEEPALIVE as TCP_KEEPIDLE; #[cfg(not(any(apple, target_os = "openbsd", target_os = "haiku", target_os = "nto")))] use c::TCP_KEEPIDLE; use core::mem::size_of; +use core::mem::MaybeUninit; use core::time::Duration; #[cfg(windows)] use windows_sys::Win32::Foundation::BOOL; @@ -37,25 +84,38 @@ fn getsockopt(fd: BorrowedFd<'_>, level: i32, optname: i32) -> io::Resu "Socket APIs don't ever use `bool` directly" ); + let mut value = MaybeUninit::::uninit(); + getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?; + + // On Windows at least, `getsockopt` has been observed writing 1 + // byte on at least (`IPPROTO_TCP`, `TCP_NODELAY`), even though + // Windows' documentation says that should write a 4-byte `BOOL`. + // So, we initialize the memory to zeros above, and just assert + // that `getsockopt` doesn't write too many bytes here. + assert!( + optlen as usize <= size_of::(), + "unexpected getsockopt size" + ); + + unsafe { Ok(value.assume_init()) } +} + +#[inline] +fn getsockopt_raw( + fd: BorrowedFd<'_>, + level: i32, + optname: i32, + value: &mut MaybeUninit, + optlen: &mut c::socklen_t, +) -> io::Result<()> { unsafe { - let mut value = core::mem::zeroed::(); ret(c::getsockopt( borrowed_fd(fd), level, optname, - as_mut_ptr(&mut value).cast(), - &mut optlen, - ))?; - // On Windows at least, `getsockopt` has been observed writing 1 - // byte on at least (`IPPROTO_TCP`, `TCP_NODELAY`), even though - // Windows' documentation says that should write a 4-byte `BOOL`. - // So, we initialize the memory to zeros above, and just assert - // that `getsockopt` doesn't write too many bytes here. - assert!( - optlen as usize <= size_of::(), - "unexpected getsockopt size" - ); - Ok(value) + as_mut_ptr(value).cast(), + optlen, + )) } } @@ -66,13 +126,23 @@ fn setsockopt(fd: BorrowedFd<'_>, level: i32, optname: i32, value: T) - optlen as usize >= core::mem::size_of::(), "Socket APIs don't ever use `bool` directly" ); + setsockopt_raw(fd, level, optname, &value, optlen) +} +#[inline] +fn setsockopt_raw( + fd: BorrowedFd<'_>, + level: i32, + optname: i32, + ptr: *const T, + optlen: c::socklen_t, +) -> io::Result<()> { unsafe { ret(c::setsockopt( borrowed_fd(fd), level, optname, - as_ptr(&value).cast(), + ptr.cast(), optlen, )) } @@ -321,6 +391,67 @@ pub(crate) fn get_socket_oobinline(fd: BorrowedFd<'_>) -> io::Result { getsockopt(fd, c::SOL_SOCKET, c::SO_OOBINLINE).map(to_bool) } +#[cfg(not(any(solarish, windows)))] +#[inline] +pub(crate) fn set_socket_reuseport(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT, from_bool(value)) +} + +#[cfg(not(any(solarish, windows)))] +#[inline] +pub(crate) fn get_socket_reuseport(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT).map(to_bool) +} + +#[cfg(target_os = "freebsd")] +#[inline] +pub(crate) fn set_socket_reuseport_lb(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT_LB, from_bool(value)) +} + +#[cfg(target_os = "freebsd")] +#[inline] +pub(crate) fn get_socket_reuseport_lb(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT_LB).map(to_bool) +} + +#[cfg(any( + linux_kernel, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "redox", + target_env = "newlib" +))] +#[inline] +pub(crate) fn get_socket_protocol(fd: BorrowedFd<'_>) -> io::Result> { + getsockopt(fd, c::SOL_SOCKET, c::SO_PROTOCOL).map(|raw| { + if let Some(raw) = RawProtocol::new(raw) { + Some(Protocol::from_raw(raw)) + } else { + None + } + }) +} + +#[cfg(target_os = "linux")] +#[inline] +pub(crate) fn get_socket_cookie(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::SOL_SOCKET, c::SO_COOKIE) +} + +#[cfg(target_os = "linux")] +#[inline] +pub(crate) fn get_socket_incoming_cpu(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::SOL_SOCKET, c::SO_INCOMING_CPU) +} + +#[cfg(target_os = "linux")] +#[inline] +pub(crate) fn set_socket_incoming_cpu(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> { + setsockopt(fd, c::SOL_SOCKET, c::SO_INCOMING_CPU, value) +} + #[inline] pub(crate) fn set_ip_ttl(fd: BorrowedFd<'_>, ttl: u32) -> io::Result<()> { setsockopt(fd, c::IPPROTO_IP, c::IP_TTL, ttl) @@ -604,6 +735,76 @@ pub(crate) fn get_ipv6_recvtclass(fd: BorrowedFd<'_>) -> io::Result { getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_RECVTCLASS).map(to_bool) } +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +#[inline] +pub(crate) fn set_ip_freebind(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt(fd, c::IPPROTO_IP, c::IP_FREEBIND, from_bool(value)) +} + +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +#[inline] +pub(crate) fn get_ip_freebind(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::IPPROTO_IP, c::IP_FREEBIND).map(to_bool) +} + +#[cfg(linux_kernel)] +#[inline] +pub(crate) fn set_ipv6_freebind(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND, from_bool(value)) +} + +#[cfg(linux_kernel)] +#[inline] +pub(crate) fn get_ipv6_freebind(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND).map(to_bool) +} + +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +#[inline] +pub(crate) fn get_ip_original_dst(fd: BorrowedFd<'_>) -> io::Result { + let level = c::IPPROTO_IP; + let optname = c::SO_ORIGINAL_DST; + let mut value = MaybeUninit::::uninit(); + let mut optlen = core::mem::size_of_val(&value).try_into().unwrap(); + + getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?; + + let any = unsafe { SocketAddrAny::read(value.as_ptr(), optlen as usize)? }; + match any { + SocketAddrAny::V4(v4) => Ok(v4), + _ => unreachable!(), + } +} + +#[cfg(linux_kernel)] +#[inline] +pub(crate) fn get_ipv6_original_dst(fd: BorrowedFd<'_>) -> io::Result { + let level = c::IPPROTO_IPV6; + let optname = c::IP6T_SO_ORIGINAL_DST; + let mut value = MaybeUninit::::uninit(); + let mut optlen = core::mem::size_of_val(&value).try_into().unwrap(); + + getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?; + + let any = unsafe { SocketAddrAny::read(value.as_ptr(), optlen as usize)? }; + match any { + SocketAddrAny::V6(v6) => Ok(v6), + _ => unreachable!(), + } +} + +#[cfg(not(any(solarish, windows, target_os = "espidf", target_os = "haiku")))] +#[inline] +pub(crate) fn set_ipv6_tclass(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> { + setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_TCLASS, value) +} + +#[cfg(not(any(solarish, windows, target_os = "espidf", target_os = "haiku")))] +#[inline] +pub(crate) fn get_ipv6_tclass(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_TCLASS) +} + #[inline] pub(crate) fn set_tcp_nodelay(fd: BorrowedFd<'_>, nodelay: bool) -> io::Result<()> { setsockopt(fd, c::IPPROTO_TCP, c::TCP_NODELAY, from_bool(nodelay)) @@ -666,6 +867,88 @@ pub(crate) fn get_tcp_user_timeout(fd: BorrowedFd<'_>) -> io::Result { getsockopt(fd, c::IPPROTO_TCP, c::TCP_USER_TIMEOUT) } +#[cfg(any(linux_like, target_os = "fuchsia"))] +#[inline] +pub(crate) fn set_tcp_quickack(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt(fd, c::IPPROTO_TCP, c::TCP_QUICKACK, from_bool(value)) +} + +#[cfg(any(linux_like, target_os = "fuchsia"))] +#[inline] +pub(crate) fn get_tcp_quickack(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::IPPROTO_TCP, c::TCP_QUICKACK).map(to_bool) +} + +#[cfg(any( + linux_like, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos" +))] +#[inline] +pub(crate) fn set_tcp_congestion(fd: BorrowedFd<'_>, value: &str) -> io::Result<()> { + let level = c::IPPROTO_TCP; + let optname = c::TCP_CONGESTION; + let optlen = value.len().try_into().unwrap(); + setsockopt_raw(fd, level, optname, value.as_ptr(), optlen) +} + +#[cfg(feature = "alloc")] +#[cfg(any( + linux_like, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos" +))] +#[inline] +pub(crate) fn get_tcp_congestion(fd: BorrowedFd<'_>) -> io::Result { + let level = c::IPPROTO_TCP; + let optname = c::TCP_CONGESTION; + const OPTLEN: c::socklen_t = 16; + let mut value = MaybeUninit::<[MaybeUninit; OPTLEN as usize]>::uninit(); + let mut optlen = OPTLEN; + getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?; + unsafe { + let value = value.assume_init(); + let slice: &[u8] = core::mem::transmute(&value[..optlen as usize]); + assert!(slice.iter().any(|b| *b == b'\0')); + Ok( + core::str::from_utf8(CStr::from_ptr(slice.as_ptr().cast()).to_bytes()) + .unwrap() + .to_owned(), + ) + } +} + +#[cfg(any(linux_like, target_os = "fuchsia"))] +#[inline] +pub(crate) fn set_tcp_thin_linear_timeouts(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt( + fd, + c::IPPROTO_TCP, + c::TCP_THIN_LINEAR_TIMEOUTS, + from_bool(value), + ) +} + +#[cfg(any(linux_like, target_os = "fuchsia"))] +#[inline] +pub(crate) fn get_tcp_thin_linear_timeouts(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::IPPROTO_TCP, c::TCP_THIN_LINEAR_TIMEOUTS).map(to_bool) +} + +#[cfg(any(linux_like, solarish, target_os = "fuchsia"))] +#[inline] +pub(crate) fn set_tcp_cork(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt(fd, c::IPPROTO_TCP, c::TCP_CORK, from_bool(value)) +} + +#[cfg(any(linux_like, solarish, target_os = "fuchsia"))] +#[inline] +pub(crate) fn get_tcp_cork(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::IPPROTO_TCP, c::TCP_CORK).map(to_bool) +} + #[inline] fn to_ip_mreq(multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> c::ip_mreq { c::ip_mreq { diff --git a/src/backend/libc/process/types.rs b/src/backend/libc/process/types.rs index 96b2886ea..dcec328e3 100644 --- a/src/backend/libc/process/types.rs +++ b/src/backend/libc/process/types.rs @@ -1,3 +1,4 @@ +#[cfg(not(target_os = "espidf"))] use crate::backend::c; /// A command for use with [`membarrier`] and [`membarrier_cpu`]. diff --git a/src/backend/linux_raw/c.rs b/src/backend/linux_raw/c.rs index 80fbbc141..73467534b 100644 --- a/src/backend/linux_raw/c.rs +++ b/src/backend/linux_raw/c.rs @@ -59,21 +59,27 @@ pub(crate) use linux_raw_sys::{ AF_NETBEUI, AF_NETLINK, AF_NETROM, AF_PACKET, AF_PHONET, AF_PPPOX, AF_RDS, AF_ROSE, AF_RXRPC, AF_SECURITY, AF_SNA, AF_TIPC, AF_UNIX, AF_UNSPEC, AF_WANPIPE, AF_X25, IPPROTO_FRAGMENT, IPPROTO_ICMPV6, IPPROTO_MH, IPPROTO_ROUTING, IPV6_ADD_MEMBERSHIP, - IPV6_DROP_MEMBERSHIP, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_LOOP, IPV6_RECVTCLASS, - IPV6_UNICAST_HOPS, IPV6_V6ONLY, IP_ADD_MEMBERSHIP, IP_ADD_SOURCE_MEMBERSHIP, - IP_DROP_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP, IP_MULTICAST_LOOP, IP_MULTICAST_TTL, - IP_RECVTOS, IP_TOS, IP_TTL, MSG_CMSG_CLOEXEC, MSG_CONFIRM, MSG_DONTROUTE, MSG_DONTWAIT, - MSG_EOR, MSG_ERRQUEUE, MSG_MORE, MSG_NOSIGNAL, MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL, - SCM_CREDENTIALS, SCM_RIGHTS, SHUT_RD, SHUT_RDWR, SHUT_WR, SOCK_DGRAM, SOCK_RAW, SOCK_RDM, - SOCK_SEQPACKET, SOCK_STREAM, SOL_SOCKET, SO_ACCEPTCONN, SO_BROADCAST, SO_DOMAIN, SO_ERROR, - SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, SO_PASSCRED, SO_RCVBUF, SO_RCVTIMEO_NEW, - SO_RCVTIMEO_NEW as SO_RCVTIMEO, SO_RCVTIMEO_OLD, SO_REUSEADDR, SO_SNDBUF, SO_SNDTIMEO_NEW, - SO_SNDTIMEO_NEW as SO_SNDTIMEO, SO_SNDTIMEO_OLD, SO_TYPE, TCP_KEEPCNT, TCP_KEEPIDLE, - TCP_KEEPINTVL, TCP_NODELAY, TCP_USER_TIMEOUT, + IPV6_DROP_MEMBERSHIP, IPV6_FREEBIND, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_LOOP, + IPV6_RECVTCLASS, IPV6_TCLASS, IPV6_UNICAST_HOPS, IPV6_V6ONLY, IP_ADD_MEMBERSHIP, + IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP, IP_FREEBIND, + IP_MULTICAST_LOOP, IP_MULTICAST_TTL, IP_RECVTOS, IP_TOS, IP_TTL, MSG_CMSG_CLOEXEC, + MSG_CONFIRM, MSG_DONTROUTE, MSG_DONTWAIT, MSG_EOR, MSG_ERRQUEUE, MSG_MORE, MSG_NOSIGNAL, + MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL, SCM_CREDENTIALS, SCM_RIGHTS, SHUT_RD, SHUT_RDWR, + SHUT_WR, SOCK_DGRAM, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET, SOCK_STREAM, SOL_SOCKET, + SO_ACCEPTCONN, SO_BROADCAST, SO_COOKIE, SO_DOMAIN, SO_ERROR, SO_INCOMING_CPU, SO_KEEPALIVE, + SO_LINGER, SO_OOBINLINE, SO_PASSCRED, SO_PROTOCOL, SO_RCVBUF, SO_RCVTIMEO_NEW, + SO_RCVTIMEO_NEW as SO_RCVTIMEO, SO_RCVTIMEO_OLD, SO_REUSEADDR, SO_REUSEPORT, SO_SNDBUF, + SO_SNDTIMEO_NEW, SO_SNDTIMEO_NEW as SO_SNDTIMEO, SO_SNDTIMEO_OLD, SO_TYPE, TCP_CONGESTION, + TCP_CORK, TCP_KEEPCNT, TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_NODELAY, TCP_QUICKACK, + TCP_THIN_LINEAR_TIMEOUTS, TCP_USER_TIMEOUT, }, netlink::*, }; +// TODO: Modify linux-raw-sys to include these. +pub(crate) const IP6T_SO_ORIGINAL_DST: u32 = 80; +pub(crate) const SO_ORIGINAL_DST: u32 = 80; + // Cast away bindgen's `enum` type to make these consistent with the other // `setsockopt`/`getsockopt` level values. #[cfg(feature = "net")] diff --git a/src/backend/linux_raw/net/sockopt.rs b/src/backend/linux_raw/net/sockopt.rs index 24cabbe5e..00aa83faf 100644 --- a/src/backend/linux_raw/net/sockopt.rs +++ b/src/backend/linux_raw/net/sockopt.rs @@ -6,11 +6,20 @@ #![allow(unsafe_code, clippy::undocumented_unsafe_blocks)] use crate::backend::c; -use crate::backend::conv::{by_mut, by_ref, c_uint, ret, socklen_t}; +use crate::backend::conv::{by_mut, c_uint, ret, socklen_t}; use crate::fd::BorrowedFd; +#[cfg(feature = "alloc")] +use crate::ffi::CStr; use crate::io; use crate::net::sockopt::Timeout; -use crate::net::{AddressFamily, Ipv4Addr, Ipv6Addr, SocketType}; +use crate::net::{ + AddressFamily, Ipv4Addr, Ipv6Addr, Protocol, RawProtocol, SocketAddrAny, SocketAddrStorage, + SocketAddrV4, SocketAddrV6, SocketType, +}; +#[cfg(feature = "alloc")] +use alloc::borrow::ToOwned; +#[cfg(feature = "alloc")] +use alloc::string::String; use core::mem::MaybeUninit; use core::time::Duration; use linux_raw_sys::general::{__kernel_old_timeval, __kernel_sock_timeval}; @@ -23,34 +32,45 @@ use { #[inline] fn getsockopt(fd: BorrowedFd<'_>, level: u32, optname: u32) -> io::Result { - let mut optlen = core::mem::size_of::(); + let mut optlen: c::socklen_t = core::mem::size_of::().try_into().unwrap(); debug_assert!( optlen as usize >= core::mem::size_of::(), "Socket APIs don't ever use `bool` directly" ); + let mut value = MaybeUninit::::uninit(); + getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?; + + assert_eq!( + optlen as usize, + core::mem::size_of::(), + "unexpected getsockopt size" + ); + + unsafe { Ok(value.assume_init()) } +} + +#[inline] +fn getsockopt_raw( + fd: BorrowedFd<'_>, + level: u32, + optname: u32, + value: &mut MaybeUninit, + optlen: &mut c::socklen_t, +) -> io::Result<()> { #[cfg(not(target_arch = "x86"))] unsafe { - let mut value = MaybeUninit::::uninit(); ret(syscall!( __NR_getsockopt, fd, c_uint(level), c_uint(optname), - &mut value, - by_mut(&mut optlen) - ))?; - - assert_eq!( - optlen as usize, - core::mem::size_of::(), - "unexpected getsockopt size" - ); - Ok(value.assume_init()) + value, + by_mut(optlen) + )) } #[cfg(target_arch = "x86")] unsafe { - let mut value = MaybeUninit::::uninit(); ret(syscall!( __NR_socketcall, x86_sys(SYS_GETSOCKOPT), @@ -58,16 +78,10 @@ fn getsockopt(fd: BorrowedFd<'_>, level: u32, optname: u32) -> io::Resu fd.into(), c_uint(level), c_uint(optname), - (&mut value).into(), - by_mut(&mut optlen), + value.into(), + by_mut(optlen), ]) - ))?; - assert_eq!( - optlen as usize, - core::mem::size_of::(), - "unexpected getsockopt size" - ); - Ok(value.assume_init()) + )) } } @@ -78,7 +92,17 @@ fn setsockopt(fd: BorrowedFd<'_>, level: u32, optname: u32, value: T) - optlen as usize >= core::mem::size_of::(), "Socket APIs don't ever use `bool` directly" ); + setsockopt_raw(fd, level, optname, &value, optlen) +} +#[inline] +fn setsockopt_raw( + fd: BorrowedFd<'_>, + level: u32, + optname: u32, + ptr: *const T, + optlen: c::socklen_t, +) -> io::Result<()> { #[cfg(not(target_arch = "x86"))] unsafe { ret(syscall_readonly!( @@ -86,7 +110,7 @@ fn setsockopt(fd: BorrowedFd<'_>, level: u32, optname: u32, value: T) - fd, c_uint(level), c_uint(optname), - by_ref(&value), + ptr, socklen_t(optlen) )) } @@ -99,7 +123,7 @@ fn setsockopt(fd: BorrowedFd<'_>, level: u32, optname: u32, value: T) - fd.into(), c_uint(level), c_uint(optname), - by_ref(&value), + ptr.into(), socklen_t(optlen), ]) )) @@ -360,6 +384,42 @@ pub(crate) fn get_socket_oobinline(fd: BorrowedFd<'_>) -> io::Result { getsockopt(fd, c::SOL_SOCKET, c::SO_OOBINLINE).map(to_bool) } +#[inline] +pub(crate) fn set_socket_reuseport(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT, from_bool(value)) +} + +#[inline] +pub(crate) fn get_socket_reuseport(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT).map(to_bool) +} + +#[inline] +pub(crate) fn get_socket_protocol(fd: BorrowedFd<'_>) -> io::Result> { + getsockopt(fd, c::SOL_SOCKET, c::SO_PROTOCOL).map(|raw: u32| { + if let Some(raw) = RawProtocol::new(raw) { + Some(Protocol::from_raw(raw)) + } else { + None + } + }) +} + +#[inline] +pub(crate) fn get_socket_cookie(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::SOL_SOCKET, c::SO_COOKIE) +} + +#[inline] +pub(crate) fn get_socket_incoming_cpu(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::SOL_SOCKET, c::SO_INCOMING_CPU) +} + +#[inline] +pub(crate) fn set_socket_incoming_cpu(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> { + setsockopt(fd, c::SOL_SOCKET, c::SO_INCOMING_CPU, value) +} + #[inline] pub(crate) fn set_ip_ttl(fd: BorrowedFd<'_>, ttl: u32) -> io::Result<()> { setsockopt(fd, c::IPPROTO_IP, c::IP_TTL, ttl) @@ -559,6 +619,68 @@ pub(crate) fn get_ipv6_recvtclass(fd: BorrowedFd<'_>) -> io::Result { getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_RECVTCLASS).map(to_bool) } +#[inline] +pub(crate) fn set_ip_freebind(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt(fd, c::IPPROTO_IP, c::IP_FREEBIND, from_bool(value)) +} + +#[inline] +pub(crate) fn get_ip_freebind(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::IPPROTO_IP, c::IP_FREEBIND).map(to_bool) +} + +#[inline] +pub(crate) fn set_ipv6_freebind(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND, from_bool(value)) +} + +#[inline] +pub(crate) fn get_ipv6_freebind(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND).map(to_bool) +} + +#[inline] +pub(crate) fn get_ip_original_dst(fd: BorrowedFd<'_>) -> io::Result { + let level = c::IPPROTO_IP; + let optname = c::SO_ORIGINAL_DST; + let mut value = MaybeUninit::::uninit(); + let mut optlen = core::mem::size_of_val(&value).try_into().unwrap(); + + getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?; + + let any = unsafe { SocketAddrAny::read(value.as_ptr(), optlen as usize)? }; + match any { + SocketAddrAny::V4(v4) => Ok(v4), + _ => unreachable!(), + } +} + +#[inline] +pub(crate) fn get_ipv6_original_dst(fd: BorrowedFd<'_>) -> io::Result { + let level = c::IPPROTO_IPV6; + let optname = c::IP6T_SO_ORIGINAL_DST; + let mut value = MaybeUninit::::uninit(); + let mut optlen = core::mem::size_of_val(&value).try_into().unwrap(); + + getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?; + + let any = unsafe { SocketAddrAny::read(value.as_ptr(), optlen as usize)? }; + match any { + SocketAddrAny::V6(v6) => Ok(v6), + _ => unreachable!(), + } +} + +#[inline] +pub(crate) fn set_ipv6_tclass(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> { + setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_TCLASS, value) +} + +#[inline] +pub(crate) fn get_ipv6_tclass(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_TCLASS) +} + #[inline] pub(crate) fn set_tcp_nodelay(fd: BorrowedFd<'_>, nodelay: bool) -> io::Result<()> { setsockopt(fd, c::IPPROTO_TCP, c::TCP_NODELAY, from_bool(nodelay)) @@ -613,6 +735,69 @@ pub(crate) fn get_tcp_user_timeout(fd: BorrowedFd<'_>) -> io::Result { getsockopt(fd, c::IPPROTO_TCP, c::TCP_USER_TIMEOUT) } +#[inline] +pub(crate) fn set_tcp_quickack(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt(fd, c::IPPROTO_TCP, c::TCP_QUICKACK, from_bool(value)) +} + +#[inline] +pub(crate) fn get_tcp_quickack(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::IPPROTO_TCP, c::TCP_QUICKACK).map(to_bool) +} + +#[inline] +pub(crate) fn set_tcp_congestion(fd: BorrowedFd<'_>, value: &str) -> io::Result<()> { + let level = c::IPPROTO_TCP; + let optname = c::TCP_CONGESTION; + let optlen = value.len().try_into().unwrap(); + setsockopt_raw(fd, level, optname, value.as_ptr(), optlen) +} + +#[cfg(feature = "alloc")] +#[inline] +pub(crate) fn get_tcp_congestion(fd: BorrowedFd<'_>) -> io::Result { + let level = c::IPPROTO_TCP; + let optname = c::TCP_CONGESTION; + const OPTLEN: c::socklen_t = 16; + let mut value = MaybeUninit::<[MaybeUninit; OPTLEN as usize]>::uninit(); + let mut optlen = OPTLEN; + getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?; + unsafe { + let value = value.assume_init(); + let slice: &[u8] = core::mem::transmute(&value[..optlen as usize]); + assert!(slice.iter().any(|b| *b == b'\0')); + Ok( + core::str::from_utf8(CStr::from_ptr(slice.as_ptr().cast()).to_bytes()) + .unwrap() + .to_owned(), + ) + } +} + +#[inline] +pub(crate) fn set_tcp_thin_linear_timeouts(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt( + fd, + c::IPPROTO_TCP, + c::TCP_THIN_LINEAR_TIMEOUTS, + from_bool(value), + ) +} + +#[inline] +pub(crate) fn get_tcp_thin_linear_timeouts(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::IPPROTO_TCP, c::TCP_THIN_LINEAR_TIMEOUTS).map(to_bool) +} + +#[inline] +pub(crate) fn set_tcp_cork(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> { + setsockopt(fd, c::IPPROTO_TCP, c::TCP_CORK, from_bool(value)) +} + +#[inline] +pub(crate) fn get_tcp_cork(fd: BorrowedFd<'_>) -> io::Result { + getsockopt(fd, c::IPPROTO_TCP, c::TCP_CORK).map(to_bool) +} #[inline] fn to_ip_mreq(multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> c::ip_mreq { c::ip_mreq { diff --git a/src/net/sockopt.rs b/src/net/sockopt.rs index ea736c2af..05e407f78 100644 --- a/src/net/sockopt.rs +++ b/src/net/sockopt.rs @@ -155,8 +155,29 @@ target_os = "nto", )))] use crate::net::AddressFamily; +#[cfg(any( + linux_kernel, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "redox", + target_env = "newlib" +))] +use crate::net::Protocol; +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +use crate::net::SocketAddrV4; +#[cfg(linux_kernel)] +use crate::net::SocketAddrV6; use crate::net::{Ipv4Addr, Ipv6Addr, SocketType}; use crate::{backend, io}; +#[cfg(feature = "alloc")] +#[cfg(any( + linux_like, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos" +))] +use alloc::string::String; use backend::c; use backend::fd::AsFd; use core::time::Duration; @@ -207,7 +228,7 @@ pub fn get_socket_reuseaddr(fd: Fd) -> io::Result { backend::net::sockopt::get_socket_reuseaddr(fd.as_fd()) } -/// `setsockopt(fd, SOL_SOCKET, SO_BROADCAST, broadcast)` +/// `setsockopt(fd, SOL_SOCKET, SO_BROADCAST, value)` /// /// See the [module-level documentation] for more. /// @@ -275,7 +296,7 @@ pub fn get_socket_passcred(fd: Fd) -> io::Result { backend::net::sockopt::get_socket_passcred(fd.as_fd()) } -/// `setsockopt(fd, SOL_SOCKET, id, timeout)`—Set the sending or receiving +/// `setsockopt(fd, SOL_SOCKET, id, value)`—Set the sending or receiving /// timeout. /// /// See the [module-level documentation] for more. @@ -287,9 +308,9 @@ pub fn get_socket_passcred(fd: Fd) -> io::Result { pub fn set_socket_timeout( fd: Fd, id: Timeout, - timeout: Option, + value: Option, ) -> io::Result<()> { - backend::net::sockopt::set_socket_timeout(fd.as_fd(), id, timeout) + backend::net::sockopt::set_socket_timeout(fd.as_fd(), id, value) } /// `getsockopt(fd, SOL_SOCKET, id)`—Get the sending or receiving timeout. @@ -461,6 +482,110 @@ pub fn get_socket_oobinline(fd: Fd) -> io::Result { backend::net::sockopt::get_socket_oobinline(fd.as_fd()) } +/// `setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, value)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions +#[cfg(not(any(solarish, windows)))] +#[cfg(not(windows))] +#[inline] +#[doc(alias = "SO_REUSEPORT")] +pub fn set_socket_reuseport(fd: Fd, value: bool) -> io::Result<()> { + backend::net::sockopt::set_socket_reuseport(fd.as_fd(), value) +} + +/// `getsockopt(fd, SOL_SOCKET, SO_REUSEPORT)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions +#[cfg(not(any(solarish, windows)))] +#[inline] +#[doc(alias = "SO_REUSEPORT")] +pub fn get_socket_reuseport(fd: Fd) -> io::Result { + backend::net::sockopt::get_socket_reuseport(fd.as_fd()) +} + +/// `setsockopt(fd, SOL_SOCKET, SO_REUSEPORT_LB, value)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions +#[cfg(target_os = "freebsd")] +#[inline] +#[doc(alias = "SO_REUSEPORT_LB")] +pub fn set_socket_reuseport_lb(fd: Fd, value: bool) -> io::Result<()> { + backend::net::sockopt::set_socket_reuseport_lb(fd.as_fd(), value) +} + +/// `getsockopt(fd, SOL_SOCKET, SO_REUSEPORT_LB)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions +#[cfg(target_os = "freebsd")] +#[inline] +#[doc(alias = "SO_REUSEPORT_LB")] +pub fn get_socket_reuseport_lb(fd: Fd) -> io::Result { + backend::net::sockopt::get_socket_reuseport_lb(fd.as_fd()) +} + +/// `getsockopt(fd, SOL_SOCKET, SO_PROTOCOL)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions +#[cfg(any( + linux_kernel, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "redox", + target_env = "newlib" +))] +#[inline] +#[doc(alias = "SO_PROTOCOL")] +pub fn get_socket_protocol(fd: Fd) -> io::Result> { + backend::net::sockopt::get_socket_protocol(fd.as_fd()) +} + +/// `getsockopt(fd, SOL_SOCKET, SO_COOKIE)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions +#[cfg(target_os = "linux")] +#[inline] +#[doc(alias = "SO_COOKIE")] +pub fn get_socket_cookie(fd: Fd) -> io::Result { + backend::net::sockopt::get_socket_cookie(fd.as_fd()) +} + +/// `getsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions +#[cfg(target_os = "linux")] +#[inline] +#[doc(alias = "SO_INCOMING_CPU")] +pub fn get_socket_incoming_cpu(fd: Fd) -> io::Result { + backend::net::sockopt::get_socket_incoming_cpu(fd.as_fd()) +} + +/// `setsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, value)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions +#[cfg(target_os = "linux")] +#[inline] +#[doc(alias = "SO_INCOMING_CPU")] +pub fn set_socket_incoming_cpu(fd: Fd, value: u32) -> io::Result<()> { + backend::net::sockopt::set_socket_incoming_cpu(fd.as_fd(), value) +} + /// `setsockopt(fd, IPPROTO_IP, IP_TTL, value)` /// /// See the [module-level documentation] for more. @@ -483,7 +608,7 @@ pub fn get_ip_ttl(fd: Fd) -> io::Result { backend::net::sockopt::get_ip_ttl(fd.as_fd()) } -/// `setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, only_v6)` +/// `setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, value)` /// /// See the [module-level documentation] for more. /// @@ -664,7 +789,7 @@ pub fn set_ip_add_membership_with_ifindex( ) } -/// `setsockopt(fd, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP)` +/// `setsockopt(fd, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, value)` /// /// See the [module-level documentation] for more. /// @@ -686,7 +811,7 @@ pub fn set_ip_add_source_membership( ) } -/// `setsockopt(fd, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP)` +/// `setsockopt(fd, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP, value)` /// /// See the [module-level documentation] for more. /// @@ -889,6 +1014,108 @@ pub fn get_ipv6_recvtclass(fd: Fd) -> io::Result { backend::net::sockopt::get_ipv6_recvtclass(fd.as_fd()) } +/// `setsockopt(fd, IPPROTO_IP, IP_FREEBIND, value)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +#[inline] +#[doc(alias = "IP_FREEBIND")] +pub fn set_ip_freebind(fd: Fd, value: bool) -> io::Result<()> { + backend::net::sockopt::set_ip_freebind(fd.as_fd(), value) +} + +/// `getsockopt(fd, IPPROTO_IP, IP_FREEBIND)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +#[inline] +#[doc(alias = "IP_FREEBIND")] +pub fn get_ip_freebind(fd: Fd) -> io::Result { + backend::net::sockopt::get_ip_freebind(fd.as_fd()) +} + +/// `setsockopt(fd, IPPROTO_IPV6, IPV6_FREEBIND, value)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions +#[cfg(linux_kernel)] +#[inline] +#[doc(alias = "IPV6_FREEBIND")] +pub fn set_ipv6_freebind(fd: Fd, value: bool) -> io::Result<()> { + backend::net::sockopt::set_ipv6_freebind(fd.as_fd(), value) +} + +/// `getsockopt(fd, IPPROTO_IPV6, IPV6_FREEBIND)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions +#[cfg(linux_kernel)] +#[inline] +#[doc(alias = "IPV6_FREEBIND")] +pub fn get_ipv6_freebind(fd: Fd) -> io::Result { + backend::net::sockopt::get_ipv6_freebind(fd.as_fd()) +} + +/// `getsockopt(fd, IPPROTO_IP, SO_ORIGINAL_DST)` +/// +/// Even though this corresponnds to a `SO_*` constant, it is an +/// `IPPROTO_IP` option. +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions +#[cfg(any(linux_kernel, target_os = "fuchsia"))] +#[inline] +#[doc(alias = "SO_ORIGINAL_DST")] +pub fn get_ip_original_dst(fd: Fd) -> io::Result { + backend::net::sockopt::get_ip_original_dst(fd.as_fd()) +} + +/// `getsockopt(fd, IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST)` +/// +/// Even though this corresponnds to a `IP6T_*` constant, it is an +/// `IPPROTO_IPV6` option. +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions +#[cfg(linux_kernel)] +#[inline] +#[doc(alias = "IP6T_SO_ORIGINAL_DST")] +pub fn get_ipv6_original_dst(fd: Fd) -> io::Result { + backend::net::sockopt::get_ipv6_original_dst(fd.as_fd()) +} + +/// `setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, value)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions +#[cfg(not(any(solarish, windows, target_os = "espidf", target_os = "haiku")))] +#[inline] +#[doc(alias = "IPV6_TCLASS")] +pub fn set_ipv6_tclass(fd: Fd, value: u32) -> io::Result<()> { + backend::net::sockopt::set_ipv6_tclass(fd.as_fd(), value) +} + +/// `getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions +#[cfg(not(any(solarish, windows, target_os = "espidf", target_os = "haiku")))] +#[inline] +#[doc(alias = "IPV6_TCLASS")] +pub fn get_ipv6_tclass(fd: Fd) -> io::Result { + backend::net::sockopt::get_ipv6_tclass(fd.as_fd()) +} + /// `setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, value)` /// /// See the [module-level documentation] for more. @@ -916,8 +1143,8 @@ pub fn get_tcp_nodelay(fd: Fd) -> io::Result { /// See the [module-level documentation] for more. /// /// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions -#[inline] #[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))] +#[inline] #[doc(alias = "TCP_KEEPCNT")] pub fn set_tcp_keepcnt(fd: Fd, value: u32) -> io::Result<()> { backend::net::sockopt::set_tcp_keepcnt(fd.as_fd(), value) @@ -928,8 +1155,8 @@ pub fn set_tcp_keepcnt(fd: Fd, value: u32) -> io::Result<()> { /// See the [module-level documentation] for more. /// /// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions -#[inline] #[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))] +#[inline] #[doc(alias = "TCP_KEEPCNT")] pub fn get_tcp_keepcnt(fd: Fd) -> io::Result { backend::net::sockopt::get_tcp_keepcnt(fd.as_fd()) @@ -942,8 +1169,8 @@ pub fn get_tcp_keepcnt(fd: Fd) -> io::Result { /// See the [module-level documentation] for more. /// /// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions -#[inline] #[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))] +#[inline] #[doc(alias = "TCP_KEEPIDLE")] pub fn set_tcp_keepidle(fd: Fd, value: Duration) -> io::Result<()> { backend::net::sockopt::set_tcp_keepidle(fd.as_fd(), value) @@ -956,8 +1183,8 @@ pub fn set_tcp_keepidle(fd: Fd, value: Duration) -> io::Result<()> { /// See the [module-level documentation] for more. /// /// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions -#[inline] #[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))] +#[inline] #[doc(alias = "TCP_KEEPIDLE")] pub fn get_tcp_keepidle(fd: Fd) -> io::Result { backend::net::sockopt::get_tcp_keepidle(fd.as_fd()) @@ -968,8 +1195,8 @@ pub fn get_tcp_keepidle(fd: Fd) -> io::Result { /// See the [module-level documentation] for more. /// /// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions -#[inline] #[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))] +#[inline] #[doc(alias = "TCP_KEEPINTVL")] pub fn set_tcp_keepintvl(fd: Fd, value: Duration) -> io::Result<()> { backend::net::sockopt::set_tcp_keepintvl(fd.as_fd(), value) @@ -980,8 +1207,8 @@ pub fn set_tcp_keepintvl(fd: Fd, value: Duration) -> io::Result<()> { /// See the [module-level documentation] for more. /// /// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions -#[inline] #[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))] +#[inline] #[doc(alias = "TCP_KEEPINTVL")] pub fn get_tcp_keepintvl(fd: Fd) -> io::Result { backend::net::sockopt::get_tcp_keepintvl(fd.as_fd()) @@ -992,8 +1219,8 @@ pub fn get_tcp_keepintvl(fd: Fd) -> io::Result { /// See the [module-level documentation] for more. /// /// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions -#[inline] #[cfg(any(linux_like, target_os = "fuchsia"))] +#[inline] #[doc(alias = "TCP_USER_TIMEOUT")] pub fn set_tcp_user_timeout(fd: Fd, value: u32) -> io::Result<()> { backend::net::sockopt::set_tcp_user_timeout(fd.as_fd(), value) @@ -1004,13 +1231,120 @@ pub fn set_tcp_user_timeout(fd: Fd, value: u32) -> io::Result<()> { /// See the [module-level documentation] for more. /// /// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions -#[inline] #[cfg(any(linux_like, target_os = "fuchsia"))] +#[inline] #[doc(alias = "TCP_USER_TIMEOUT")] pub fn get_tcp_user_timeout(fd: Fd) -> io::Result { backend::net::sockopt::get_tcp_user_timeout(fd.as_fd()) } +/// `setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, value)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions +#[cfg(any(linux_like, target_os = "fuchsia"))] +#[inline] +#[doc(alias = "TCP_QUICKACK")] +pub fn set_tcp_quickack(fd: Fd, value: bool) -> io::Result<()> { + backend::net::sockopt::set_tcp_quickack(fd.as_fd(), value) +} + +/// `getsockopt(fd, IPPROTO_TCP, TCP_QUICKACK)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions +#[cfg(any(linux_like, target_os = "fuchsia"))] +#[inline] +#[doc(alias = "TCP_QUICKACK")] +pub fn get_tcp_quickack(fd: Fd) -> io::Result { + backend::net::sockopt::get_tcp_quickack(fd.as_fd()) +} + +/// `setsockopt(fd, IPPROTO_TCP, TCP_CONGESTION, value)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions +#[cfg(any( + linux_like, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos" +))] +#[inline] +#[doc(alias = "TCP_CONGESTION")] +pub fn set_tcp_congestion(fd: Fd, value: &str) -> io::Result<()> { + backend::net::sockopt::set_tcp_congestion(fd.as_fd(), value) +} + +/// `getsockopt(fd, IPPROTO_TCP, TCP_CONGESTION)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions +#[cfg(feature = "alloc")] +#[cfg(any( + linux_like, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos" +))] +#[inline] +#[doc(alias = "TCP_CONGESTION")] +pub fn get_tcp_congestion(fd: Fd) -> io::Result { + backend::net::sockopt::get_tcp_congestion(fd.as_fd()) +} + +/// `setsockopt(fd, IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, value)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions +#[cfg(any(linux_like, target_os = "fuchsia"))] +#[inline] +#[doc(alias = "TCP_THIN_LINEAR_TIMEOUTS")] +pub fn set_tcp_thin_linear_timeouts(fd: Fd, value: bool) -> io::Result<()> { + backend::net::sockopt::set_tcp_thin_linear_timeouts(fd.as_fd(), value) +} + +/// `getsockopt(fd, IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions +#[cfg(any(linux_like, target_os = "fuchsia"))] +#[inline] +#[doc(alias = "TCP_THIN_LINEAR_TIMEOUTS")] +pub fn get_tcp_thin_linear_timeouts(fd: Fd) -> io::Result { + backend::net::sockopt::get_tcp_thin_linear_timeouts(fd.as_fd()) +} + +/// `setsockopt(fd, IPPROTO_TCP, TCP_CORK, value)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions +#[cfg(any(linux_like, solarish, target_os = "fuchsia"))] +#[inline] +#[doc(alias = "TCP_CORK")] +pub fn set_tcp_cork(fd: Fd, value: bool) -> io::Result<()> { + backend::net::sockopt::set_tcp_cork(fd.as_fd(), value) +} + +/// `getsockopt(fd, IPPROTO_TCP, TCP_CORK)` +/// +/// See the [module-level documentation] for more. +/// +/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions +#[cfg(any(linux_like, solarish, target_os = "fuchsia"))] +#[inline] +#[doc(alias = "TCP_CORK")] +pub fn get_tcp_cork(fd: Fd) -> io::Result { + backend::net::sockopt::get_tcp_cork(fd.as_fd()) +} + #[test] fn test_sizes() { use c::c_int; diff --git a/tests/net/sockopt.rs b/tests/net/sockopt.rs index 69b7be6aa..c4406bd07 100644 --- a/tests/net/sockopt.rs +++ b/tests/net/sockopt.rs @@ -1,6 +1,15 @@ use rustix::fd::OwnedFd; -use rustix::net::sockopt; -use rustix::net::{AddressFamily, SocketType}; +use rustix::io; +#[cfg(any( + linux_kernel, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "redox", + target_env = "newlib" +))] +use rustix::net::ipproto; +use rustix::net::{sockopt, AddressFamily, SocketType}; use std::time::Duration; // Test `socket` socket options. @@ -10,6 +19,20 @@ fn test_sockopts_socket(s: &OwnedFd) { .unwrap() .is_none()); assert_eq!(sockopt::get_socket_type(&s).unwrap(), SocketType::STREAM); + #[cfg(any( + linux_kernel, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "redox", + target_env = "newlib" + ))] + { + assert_eq!( + sockopt::get_socket_protocol(&s).unwrap(), + Some(ipproto::TCP) + ); + } assert!(!sockopt::get_socket_reuseaddr(&s).unwrap()); #[cfg(not(windows))] assert!(!sockopt::get_socket_broadcast(&s).unwrap()); @@ -87,7 +110,7 @@ fn test_sockopts_socket(s: &OwnedFd) { sockopt::set_socket_linger(&s, Some(Duration::new(1, 1))).unwrap(); // Check that we have a linger of at least the time we set. - assert!(dbg!(sockopt::get_socket_linger(&s).unwrap().unwrap()) >= Duration::new(1, 1)); + assert!(sockopt::get_socket_linger(&s).unwrap().unwrap() >= Duration::new(1, 1)); #[cfg(linux_kernel)] { @@ -120,6 +143,40 @@ fn test_sockopts_socket(s: &OwnedFd) { // Check that the oobinline flag is set. assert!(sockopt::get_socket_oobinline(&s).unwrap()); + + // Check the initial value of SO_REUSEPORT, set it, and check it. + #[cfg(not(any(solarish, windows)))] + { + assert!(!sockopt::get_socket_reuseport(&s).unwrap()); + sockopt::set_socket_reuseport(&s, true).unwrap(); + assert!(sockopt::get_socket_reuseport(&s).unwrap()); + } + + // Check the initial value of SO_REUSEPORT_LB, set it, and check it. + #[cfg(target_os = "freebsd")] + { + assert!(!sockopt::get_socket_reuseport_lb(&s).unwrap()); + sockopt::set_socket_reuseport_lb(&s, true).unwrap(); + assert!(sockopt::get_socket_reuseport_lb(&s).unwrap()); + } + + // Not much we can check with `get_socket_cookie`, but make sure we can + // call it and that it returns the same value if called twice. + #[cfg(target_os = "linux")] + { + assert_eq!( + sockopt::get_socket_cookie(&s).unwrap(), + sockopt::get_socket_cookie(&s).unwrap() + ); + } + + // Check the initial value of SO_INCOMING_CPU, set it, and check it. + #[cfg(target_os = "linux")] + { + assert_eq!(sockopt::get_socket_incoming_cpu(&s).unwrap(), u32::MAX); + sockopt::set_socket_incoming_cpu(&s, 3).unwrap(); + assert_eq!(sockopt::get_socket_incoming_cpu(&s).unwrap(), 3); + } } // Test `tcp` socket options. @@ -164,6 +221,53 @@ fn test_sockopts_tcp(s: &OwnedFd) { Duration::from_secs(61) ); } + + // Check the initial value of TCP_QUICKACK, set it, and check it. + #[cfg(any(linux_like, target_os = "fuchsia"))] + { + assert!(sockopt::get_tcp_quickack(&s).unwrap()); + sockopt::set_tcp_quickack(&s, false).unwrap(); + assert!(!sockopt::get_tcp_quickack(&s).unwrap()); + } + + // Check the initial value of TCP_CONGESTION, set it, and check it. + // + // Temporarily disable this test on non-x86 as qemu isn't yet aware of + // TCP_CONGESTION. + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + #[cfg(any( + linux_like, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos" + ))] + #[cfg(feature = "alloc")] + { + let algo = sockopt::get_tcp_congestion(&s).unwrap(); + assert!(!algo.is_empty()); + #[cfg(linux_like)] + { + sockopt::set_tcp_congestion(&s, "reno").unwrap(); + assert_eq!(sockopt::get_tcp_congestion(&s).unwrap(), "reno"); + } + } + + // Check the initial value of TCP_THIN_LINEAR_TIMEOUTS, set it, and check + // it. + #[cfg(any(linux_like, target_os = "fuchsia"))] + { + assert!(!sockopt::get_tcp_thin_linear_timeouts(&s).unwrap()); + sockopt::set_tcp_thin_linear_timeouts(&s, true).unwrap(); + assert!(sockopt::get_tcp_thin_linear_timeouts(&s).unwrap()); + } + + // Check the initial value of TCP_CORK, set it, and check it. + #[cfg(any(linux_like, solarish, target_os = "fuchsia"))] + { + assert!(!sockopt::get_tcp_cork(&s).unwrap()); + sockopt::set_tcp_cork(&s, true).unwrap(); + assert!(sockopt::get_tcp_cork(&s).unwrap()); + } } #[test] @@ -233,6 +337,23 @@ fn test_sockopts_ipv4() { assert!(sockopt::get_ip_recvtos(&s).unwrap()); } + // Check the initial value of IP_FREEBIND, set it, and check it. + #[cfg(any(linux_kernel, target_os = "fuchsia"))] + { + assert!(!sockopt::get_ip_freebind(&s).unwrap()); + sockopt::set_ip_freebind(&s, true).unwrap(); + assert!(sockopt::get_ip_freebind(&s).unwrap()); + } + + // Check that we can query SO_ORIGINAL_DST. + #[cfg(any(linux_kernel, target_os = "fuchsia"))] + { + assert!(matches!( + sockopt::get_ip_original_dst(&s), + Err(io::Errno::NOENT | io::Errno::NOPROTOOPT) + )); + } + test_sockopts_tcp(&s); } @@ -260,9 +381,9 @@ fn test_sockopts_ipv6() { assert_ne!(sockopt::get_ipv6_unicast_hops(&s).unwrap(), 0); match sockopt::get_ipv6_multicast_loop(&s) { Ok(multicast_loop) => assert!(multicast_loop), - Err(rustix::io::Errno::OPNOTSUPP) => (), - Err(rustix::io::Errno::INVAL) => (), - Err(rustix::io::Errno::NOPROTOOPT) => (), + Err(io::Errno::OPNOTSUPP) => (), + Err(io::Errno::INVAL) => (), + Err(io::Errno::NOPROTOOPT) => (), Err(err) => Err(err).unwrap(), } assert_ne!(sockopt::get_ipv6_unicast_hops(&s).unwrap(), 0); @@ -272,8 +393,8 @@ fn test_sockopts_ipv6() { #[cfg(not(target_os = "netbsd"))] match sockopt::get_ipv6_multicast_hops(&s) { Ok(hops) => assert_eq!(hops, 0), - Err(rustix::io::Errno::NOPROTOOPT) => (), - Err(rustix::io::Errno::INVAL) => (), + Err(io::Errno::NOPROTOOPT) => (), + Err(io::Errno::INVAL) => (), Err(err) => Err(err).unwrap(), }; @@ -293,9 +414,9 @@ fn test_sockopts_ipv6() { Err(err) => Err(err).unwrap(), } } - Err(rustix::io::Errno::OPNOTSUPP) => (), - Err(rustix::io::Errno::INVAL) => (), - Err(rustix::io::Errno::NOPROTOOPT) => (), + Err(io::Errno::OPNOTSUPP) => (), + Err(io::Errno::INVAL) => (), + Err(io::Errno::NOPROTOOPT) => (), Err(err) => Err(err).unwrap(), } @@ -325,5 +446,30 @@ fn test_sockopts_ipv6() { assert!(sockopt::get_ipv6_recvtclass(&s).unwrap()); } + // Check the initial value of IPV6_FREEBIND, set it, and check it. + #[cfg(linux_kernel)] + { + assert!(!sockopt::get_ipv6_freebind(&s).unwrap()); + sockopt::set_ipv6_freebind(&s, true).unwrap(); + assert!(sockopt::get_ipv6_freebind(&s).unwrap()); + } + + // Check the initial value of IPV6_TCLASS, set it, and check it. + #[cfg(not(any(solarish, windows, target_os = "espidf", target_os = "haiku")))] + { + assert_eq!(sockopt::get_ipv6_tclass(&s).unwrap(), 0); + sockopt::set_ipv6_tclass(&s, 12).unwrap(); + assert_eq!(sockopt::get_ipv6_tclass(&s).unwrap(), 12); + } + + // Check that we can query IP6T_SO_ORIGINAL_DST. + #[cfg(linux_kernel)] + { + assert!(matches!( + sockopt::get_ipv6_original_dst(&s), + Err(io::Errno::NOENT | io::Errno::NOPROTOOPT) + )); + } + test_sockopts_tcp(&s); }