diff --git a/Cargo.toml b/Cargo.toml index 4c8971685..b743d6d14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ compiler_builtins = { version = '0.1.49', optional = true } # libc backend can be selected via adding `--cfg=rustix_use_libc` to # `RUSTFLAGS` or enabling the `use-libc` cargo feature. [target.'cfg(all(not(rustix_use_libc), not(miri), target_os = "linux", any(target_endian = "little", any(target_arch = "s390x", target_arch = "powerpc")), any(target_arch = "arm", all(target_arch = "aarch64", target_pointer_width = "64"), target_arch = "riscv64", all(rustix_use_experimental_asm, target_arch = "powerpc"), all(rustix_use_experimental_asm, target_arch = "powerpc64"), all(rustix_use_experimental_asm, target_arch = "s390x"), all(rustix_use_experimental_asm, target_arch = "mips"), all(rustix_use_experimental_asm, target_arch = "mips32r6"), all(rustix_use_experimental_asm, target_arch = "mips64"), all(rustix_use_experimental_asm, target_arch = "mips64r6"), target_arch = "x86", all(target_arch = "x86_64", target_pointer_width = "64"))))'.dependencies] -linux-raw-sys = { version = "0.9.2", default-features = false, features = ["general", "errno", "ioctl", "no_std", "elf"] } +linux-raw-sys = { version = "0.9.3", default-features = false, features = ["general", "errno", "ioctl", "no_std", "elf"] } libc_errno = { package = "errno", version = "0.3.10", default-features = false, optional = true } libc = { version = "0.2.168", default-features = false, optional = true } diff --git a/src/backend/libc/net/sockopt.rs b/src/backend/libc/net/sockopt.rs index 29ceaa84c..145da1364 100644 --- a/src/backend/libc/net/sockopt.rs +++ b/src/backend/libc/net/sockopt.rs @@ -50,9 +50,13 @@ use crate::net::Protocol; use crate::net::RawProtocol; #[cfg(any(linux_kernel, target_os = "fuchsia"))] use crate::net::SocketAddrV4; +#[cfg(all(target_os = "linux", feature = "time"))] +use crate::net::TxTimeFlags; use crate::net::{Ipv4Addr, Ipv6Addr, SocketType}; #[cfg(linux_kernel)] use crate::net::{SocketAddrV6, UCred}; +#[cfg(all(target_os = "linux", feature = "time"))] +use crate::time::ClockId; use crate::utils::as_mut_ptr; #[cfg(feature = "alloc")] #[cfg(any( @@ -1048,6 +1052,35 @@ pub(crate) fn socket_peercred(fd: BorrowedFd<'_>) -> io::Result { getsockopt(fd, c::SOL_SOCKET, c::SO_PEERCRED) } +#[cfg(all(target_os = "linux", feature = "time"))] +#[inline] +pub(crate) fn set_txtime( + fd: BorrowedFd<'_>, + clockid: ClockId, + flags: TxTimeFlags, +) -> io::Result<()> { + setsockopt( + fd, + c::SOL_SOCKET, + c::SO_TXTIME, + c::sock_txtime { + clockid: clockid as _, + flags: flags.bits(), + }, + ) +} + +#[cfg(all(target_os = "linux", feature = "time"))] +#[inline] +pub(crate) fn get_txtime(fd: BorrowedFd<'_>) -> io::Result<(ClockId, TxTimeFlags)> { + let txtime: c::sock_txtime = getsockopt(fd, c::SOL_SOCKET, c::SO_TXTIME)?; + + Ok(( + txtime.clockid.try_into().map_err(|_| io::Errno::RANGE)?, + TxTimeFlags::from_bits(txtime.flags).ok_or(io::Errno::RANGE)?, + )) +} + #[cfg(target_os = "linux")] #[inline] pub(crate) fn set_xdp_umem_reg(fd: BorrowedFd<'_>, value: XdpUmemReg) -> io::Result<()> { diff --git a/src/backend/linux_raw/c.rs b/src/backend/linux_raw/c.rs index 755dadcde..1689507b5 100644 --- a/src/backend/linux_raw/c.rs +++ b/src/backend/linux_raw/c.rs @@ -114,6 +114,19 @@ pub(crate) use linux_raw_sys::{ }, }; +#[cfg(feature = "time")] +pub use linux_raw_sys::general::__kernel_clockid_t as clockid_t; + +#[cfg(all(feature = "net", feature = "time"))] +pub use linux_raw_sys::net::{sock_txtime, SCM_TXTIME, SO_TXTIME}; + +#[cfg(all(feature = "net", feature = "time"))] +pub(crate) const SOF_TXTIME_DEADLINE_MODE: u32 = + linux_raw_sys::net::txtime_flags::SOF_TXTIME_DEADLINE_MODE as _; +#[cfg(all(feature = "net", feature = "time"))] +pub(crate) const SOF_TXTIME_REPORT_ERRORS: u32 = + linux_raw_sys::net::txtime_flags::SOF_TXTIME_REPORT_ERRORS as _; + // 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 bdb26841d..63a6cc73c 100644 --- a/src/backend/linux_raw/net/sockopt.rs +++ b/src/backend/linux_raw/net/sockopt.rs @@ -7,6 +7,8 @@ use crate::backend::c; use crate::backend::conv::{by_mut, c_uint, ret, socklen_t}; +#[cfg(all(target_os = "linux", feature = "time"))] +use crate::clockid::ClockId; use crate::fd::BorrowedFd; #[cfg(feature = "alloc")] use crate::ffi::CStr; @@ -14,6 +16,8 @@ use crate::io; use crate::net::sockopt::Timeout; #[cfg(target_os = "linux")] use crate::net::xdp::{XdpMmapOffsets, XdpOptionsFlags, XdpRingOffset, XdpStatistics, XdpUmemReg}; +#[cfg(all(target_os = "linux", feature = "time"))] +use crate::net::TxTimeFlags; use crate::net::{ AddressFamily, Ipv4Addr, Ipv6Addr, Protocol, RawProtocol, SocketAddrBuf, SocketAddrV4, SocketAddrV6, SocketType, UCred, @@ -848,6 +852,35 @@ pub(crate) fn socket_peercred(fd: BorrowedFd<'_>) -> io::Result { getsockopt(fd, c::SOL_SOCKET, linux_raw_sys::net::SO_PEERCRED) } +#[cfg(all(target_os = "linux", feature = "time"))] +#[inline] +pub(crate) fn set_txtime( + fd: BorrowedFd<'_>, + clockid: ClockId, + flags: TxTimeFlags, +) -> io::Result<()> { + setsockopt( + fd, + c::SOL_SOCKET, + c::SO_TXTIME, + c::sock_txtime { + clockid: clockid as _, + flags: flags.bits(), + }, + ) +} + +#[cfg(all(target_os = "linux", feature = "time"))] +#[inline] +pub(crate) fn get_txtime(fd: BorrowedFd<'_>) -> io::Result<(ClockId, TxTimeFlags)> { + let txtime: c::sock_txtime = getsockopt(fd, c::SOL_SOCKET, c::SO_TXTIME)?; + + Ok(( + txtime.clockid.try_into().map_err(|_| io::Errno::RANGE)?, + TxTimeFlags::from_bits(txtime.flags).ok_or(io::Errno::RANGE)?, + )) +} + #[cfg(target_os = "linux")] #[inline] pub(crate) fn set_xdp_umem_reg(fd: BorrowedFd<'_>, value: XdpUmemReg) -> io::Result<()> { diff --git a/src/clockid.rs b/src/clockid.rs index ae300f2bc..f66e61be1 100644 --- a/src/clockid.rs +++ b/src/clockid.rs @@ -1,5 +1,6 @@ use crate::backend::c; use crate::fd::BorrowedFd; +use crate::io; /// `CLOCK_*` constants for use with [`clock_gettime`]. /// @@ -97,6 +98,51 @@ pub enum ClockId { BoottimeAlarm = bitcast!(c::CLOCK_BOOTTIME_ALARM), } +#[cfg(not(any(apple, target_os = "wasi")))] +impl TryFrom for ClockId { + type Error = io::Errno; + + fn try_from(value: c::clockid_t) -> Result { + match value { + c::CLOCK_REALTIME => Ok(ClockId::Realtime), + c::CLOCK_MONOTONIC => Ok(ClockId::Monotonic), + #[cfg(any(freebsdlike, target_os = "openbsd"))] + c::CLOCK_UPTIME => Ok(ClockId::Uptime), + #[cfg(not(any( + solarish, + target_os = "horizon", + target_os = "netbsd", + target_os = "redox", + target_os = "vita" + )))] + c::CLOCK_PROCESS_CPUTIME_ID => Ok(ClockId::ProcessCPUTime), + #[cfg(not(any( + solarish, + target_os = "horizon", + target_os = "netbsd", + target_os = "redox", + target_os = "vita" + )))] + c::CLOCK_THREAD_CPUTIME_ID => Ok(ClockId::ThreadCPUTime), + #[cfg(any(linux_kernel, target_os = "freebsd"))] + c::CLOCK_REALTIME_COARSE => Ok(ClockId::RealtimeCoarse), + #[cfg(any(linux_kernel, target_os = "freebsd"))] + c::CLOCK_MONOTONIC_COARSE => Ok(ClockId::MonotonicCoarse), + #[cfg(linux_kernel)] + c::CLOCK_MONOTONIC_RAW => Ok(ClockId::MonotonicRaw), + #[cfg(linux_kernel)] + c::CLOCK_REALTIME_ALARM => Ok(ClockId::RealtimeAlarm), + #[cfg(all(linux_kernel, feature = "linux_4_11"))] + c::CLOCK_TAI => Ok(ClockId::Tai), + #[cfg(any(linux_kernel, target_os = "fuchsia", target_os = "openbsd"))] + c::CLOCK_BOOTTIME => Ok(ClockId::Boottime), + #[cfg(any(linux_kernel, target_os = "fuchsia"))] + c::CLOCK_BOOTTIME_ALARM => Ok(ClockId::BoottimeAlarm), + _ => Err(io::Errno::RANGE), + } + } +} + /// `CLOCK_*` constants for use with [`clock_gettime`]. /// /// These constants are always supported at runtime, so `clock_gettime` never @@ -127,6 +173,21 @@ pub enum ClockId { ThreadCPUTime = c::CLOCK_THREAD_CPUTIME_ID, } +#[cfg(apple)] +impl TryFrom for ClockId { + type Error = io::Errno; + + fn try_from(value: c::clockid_t) -> Result { + match value { + c::CLOCK_REALTIME => Ok(ClockId::Realtime), + c::CLOCK_MONOTONIC => Ok(ClockId::Monotonic), + c::CLOCK_PROCESS_CPUTIME_ID => Ok(ClockId::ProcessCPUTime), + c::CLOCK_THREAD_CPUTIME_ID => Ok(ClockId::ThreadCPUTime), + _ => Err(io::Errno::RANGE), + } + } +} + /// `CLOCK_*` constants for use with [`clock_gettime_dynamic`]. /// /// These constants may be unsupported at runtime, depending on the OS version, diff --git a/src/net/send_recv/msg.rs b/src/net/send_recv/msg.rs index 360e29acd..13f1425c9 100644 --- a/src/net/send_recv/msg.rs +++ b/src/net/send_recv/msg.rs @@ -66,6 +66,11 @@ macro_rules! cmsg_space { $len * ::core::mem::size_of::<$crate::net::UCred>(), ) }; + (TxTime($len:expr)) => { + $crate::net::__cmsg_space( + $len * ::core::mem::size_of::(), + ) + }; // Combo Rules ($firstid:ident($firstex:expr), $($restid:ident($restex:expr)),*) => {{ @@ -94,6 +99,11 @@ macro_rules! cmsg_aligned_space { $len * ::core::mem::size_of::<$crate::net::UCred>(), ) }; + (TxTime($len:expr)) => { + $crate::net::__cmsg_aligned_space( + $len * ::core::mem::size_of::(), + ) + }; // Combo Rules ($firstid:ident($firstex:expr), $($restid:ident($restex:expr)),*) => {{ @@ -138,6 +148,13 @@ pub enum SendAncillaryMessage<'slice, 'fd> { #[cfg(linux_kernel)] #[doc(alias = "SCM_CREDENTIAL")] ScmCredentials(UCred), + /// Transmission time, in nanoseconds. The value will be interpreted by + /// whichever clock was configured on the socket with [`set_txtime`]. + /// + /// [`set_txtime`]: crate::net::sockopt::set_txtime + #[cfg(target_os = "linux")] + #[doc(alias = "SCM_TXTIME")] + TxTime(u64), } impl SendAncillaryMessage<'_, '_> { @@ -149,6 +166,8 @@ impl SendAncillaryMessage<'_, '_> { Self::ScmRights(slice) => cmsg_space!(ScmRights(slice.len())), #[cfg(linux_kernel)] Self::ScmCredentials(_) => cmsg_space!(ScmCredentials(1)), + #[cfg(target_os = "linux")] + Self::TxTime(_) => cmsg_space!(TxTime(1)), } } } @@ -290,6 +309,13 @@ impl<'buf, 'slice, 'fd> SendAncillaryBuffer<'buf, 'slice, 'fd> { }; self.push_ancillary(ucred_bytes, c::SOL_SOCKET as _, c::SCM_CREDENTIALS as _) } + #[cfg(target_os = "linux")] + SendAncillaryMessage::TxTime(tx_time) => { + let tx_time_bytes = unsafe { + slice::from_raw_parts(addr_of!(tx_time).cast::(), size_of_val(&tx_time)) + }; + self.push_ancillary(tx_time_bytes, c::SOL_SOCKET as _, c::SO_TXTIME as _) + } } } diff --git a/src/net/sockopt.rs b/src/net/sockopt.rs index 3379fb028..c0daa8efd 100644 --- a/src/net/sockopt.rs +++ b/src/net/sockopt.rs @@ -143,6 +143,8 @@ #![doc(alias = "getsockopt")] #![doc(alias = "setsockopt")] +#[cfg(all(target_os = "linux", feature = "time"))] +use crate::clockid::ClockId; #[cfg(target_os = "linux")] use crate::net::xdp::{XdpMmapOffsets, XdpOptionsFlags, XdpStatistics, XdpUmemReg}; #[cfg(not(any( @@ -172,6 +174,8 @@ use crate::net::Protocol; use crate::net::SocketAddrV4; #[cfg(linux_kernel)] use crate::net::SocketAddrV6; +#[cfg(all(target_os = "linux", feature = "time"))] +use crate::net::TxTimeFlags; use crate::net::{Ipv4Addr, Ipv6Addr, SocketType}; use crate::{backend, io}; #[cfg(feature = "alloc")] @@ -1524,6 +1528,20 @@ pub fn socket_peercred(fd: Fd) -> io::Result { backend::net::sockopt::socket_peercred(fd.as_fd()) } +/// `getsockopt(fd, SOL_SOCKET, SO_TXTIME)` — Get transmission timing configuration. +#[cfg(all(target_os = "linux", feature = "time"))] +#[doc(alias = "SO_TXTIME")] +pub fn get_txtime(fd: Fd) -> io::Result<(ClockId, TxTimeFlags)> { + backend::net::sockopt::get_txtime(fd.as_fd()) +} + +/// `setsockopt(fd, SOL_SOCKET, SO_TXTIME)` — Configure transmission timing. +#[cfg(all(target_os = "linux", feature = "time"))] +#[doc(alias = "SO_TXTIME")] +pub fn set_txtime(fd: Fd, clockid: ClockId, flags: TxTimeFlags) -> io::Result<()> { + backend::net::sockopt::set_txtime(fd.as_fd(), clockid, flags) +} + /// `setsockopt(fd, SOL_XDP, XDP_UMEM_REG, value)` /// /// On kernel versions only supporting v1, the flags are ignored. diff --git a/src/net/types.rs b/src/net/types.rs index 057f944d0..a570f62e4 100644 --- a/src/net/types.rs +++ b/src/net/types.rs @@ -1722,6 +1722,21 @@ bitflags! { } } +#[cfg(all(target_os = "linux", feature = "time"))] +bitflags! { + /// Flags for use with [`set_txtime`]. + /// + /// [`set_txtime`]: crate::net::sockopt::set_txtime + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] + pub struct TxTimeFlags: u32 { + /// `SOF_TXTIME_DEADLINE_MODE` + const DEADLINE_MODE = bitcast!(c::SOF_TXTIME_DEADLINE_MODE); + /// `SOF_TXTIME_REPORT_ERRORS` + const REPORT_ERRORS = bitcast!(c::SOF_TXTIME_REPORT_ERRORS); + } +} + /// `AF_XDP` related types and constants. #[cfg(target_os = "linux")] pub mod xdp { diff --git a/tests/net/sockopt.rs b/tests/net/sockopt.rs index a668afad9..80d9ac3e5 100644 --- a/tests/net/sockopt.rs +++ b/tests/net/sockopt.rs @@ -9,7 +9,11 @@ use rustix::io; target_env = "newlib" ))] use rustix::net::ipproto; +#[cfg(target_os = "linux")] +use rustix::net::TxTimeFlags; use rustix::net::{sockopt, AddressFamily, SocketType}; +#[cfg(target_os = "linux")] +use rustix::time::ClockId; use std::net::Ipv4Addr; use std::time::Duration; @@ -625,3 +629,24 @@ fn test_sockopts_multicast_ifv6() { Err(e) => panic!("{e}"), } } + +#[test] +#[cfg(target_os = "linux")] +fn test_sockopts_txtime() { + crate::init(); + + let s = rustix::net::socket(AddressFamily::INET, SocketType::DGRAM, None).unwrap(); + + match sockopt::set_txtime(&s, ClockId::Monotonic, TxTimeFlags::DEADLINE_MODE) { + Ok(()) => { + assert_eq!( + sockopt::get_txtime(&s).unwrap(), + (ClockId::Monotonic, TxTimeFlags::DEADLINE_MODE) + ); + } + Err(e) if e.to_string().contains("Protocol not available") => { + // Skip test on unsupported platforms + } + Err(e) => panic!("{e}"), + } +} diff --git a/tests/net/v4.rs b/tests/net/v4.rs index bb11a5293..0f87ea459 100644 --- a/tests/net/v4.rs +++ b/tests/net/v4.rs @@ -287,3 +287,41 @@ fn test_v4_sendmmsg() { client.join().unwrap(); server.join().unwrap(); } + +#[test] +#[cfg(all(target_os = "linux", feature = "time"))] +fn test_v4_txtime() { + crate::init(); + + use std::mem::MaybeUninit; + use std::time; + + use rustix::io::IoSlice; + use rustix::net::{sendmsg, sockopt, SendAncillaryBuffer, SendAncillaryMessage, TxTimeFlags}; + use rustix::time::ClockId; + + let data_socket = socket(AddressFamily::INET, SocketType::DGRAM, None).unwrap(); + let addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 12345); + connect(&data_socket, &addr).unwrap(); + + match sockopt::set_txtime(&data_socket, ClockId::Monotonic, TxTimeFlags::empty()) { + Ok(_) => (), + // Skip on unsupported platforms. + Err(e) if e.to_string().contains("Protocol not available") => return, + Err(e) => panic!("{e}"), + } + + let mut space = [MaybeUninit::uninit(); rustix::cmsg_space!(TxTime(1))]; + let mut cmsg_buffer = SendAncillaryBuffer::new(&mut space); + + let t = time::UNIX_EPOCH.elapsed().unwrap() + time::Duration::from_millis(100); + cmsg_buffer.push(SendAncillaryMessage::TxTime(t.as_nanos() as u64)); + + sendmsg( + &data_socket, + &[IoSlice::new(b"hello, world")], + &mut cmsg_buffer, + SendFlags::empty(), + ) + .unwrap(); +} diff --git a/tests/net/v6.rs b/tests/net/v6.rs index 4016c18ad..b42477e7f 100644 --- a/tests/net/v6.rs +++ b/tests/net/v6.rs @@ -286,3 +286,41 @@ fn test_v6_sendmmsg() { client.join().unwrap(); server.join().unwrap(); } + +#[test] +#[cfg(all(target_os = "linux", feature = "time"))] +fn test_v6_txtime() { + crate::init(); + + use std::mem::MaybeUninit; + use std::time; + + use rustix::io::IoSlice; + use rustix::net::{sendmsg, sockopt, SendAncillaryBuffer, SendAncillaryMessage, TxTimeFlags}; + use rustix::time::ClockId; + + let data_socket = socket(AddressFamily::INET6, SocketType::DGRAM, None).unwrap(); + let addr = SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 12345, 0, 0); + connect(&data_socket, &addr).unwrap(); + + match sockopt::set_txtime(&data_socket, ClockId::Monotonic, TxTimeFlags::empty()) { + Ok(_) => (), + // Skip on unsupported platforms. + Err(e) if e.to_string().contains("Protocol not available") => return, + Err(e) => panic!("{e}"), + } + + let mut space = [MaybeUninit::uninit(); rustix::cmsg_space!(TxTime(1))]; + let mut cmsg_buffer = SendAncillaryBuffer::new(&mut space); + + let t = time::UNIX_EPOCH.elapsed().unwrap() + time::Duration::from_millis(100); + cmsg_buffer.push(SendAncillaryMessage::TxTime(t.as_nanos() as u64)); + + sendmsg( + &data_socket, + &[IoSlice::new(b"hello, world")], + &mut cmsg_buffer, + SendFlags::empty(), + ) + .unwrap(); +}