From 9019ee1369f5c1549971eceac74d89430feefbeb Mon Sep 17 00:00:00 2001 From: Timo Glane Date: Mon, 15 Apr 2024 10:24:22 +0200 Subject: [PATCH] Added support for options SO_TIMESTAMP, SO_TIMESTAMPNS and SO_TIMESTAMPING --- src/lib.rs | 265 ++++++++++++++++++++++++++++++++++++++++++ src/socket.rs | 284 +++++++++++++++++++++++++++++++++++++++++++++ src/sys/unix.rs | 88 ++++++++++++++ src/sys/windows.rs | 36 +++++- tests/socket.rs | 81 +++++++++++++ 5 files changed, 750 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4f6bd789..dfb70b6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -383,6 +383,271 @@ impl RecvFlags { } } +/// Flags for network package timestamping configuration of a socket +/// +/// On Unix flags are set at the `SOL_SOCKET` level and the `SO_TIMESTAMPING` +/// option +/// On Windows flags are set using `WSAIoctl` and the `SOI_TIMESTAMPING` option +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", +)))] +#[cfg_attr( + docsrs, + doc(cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", + )))) +)] +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] +pub struct TimestampingFlags(sys::c_uint); + +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", +)))] +impl TimestampingFlags { + /// Creates a new instance of `TimestampingFlags` with no flags set + pub fn new() -> Self { + Self(0) + } + + /// This flags controls if transmit timestamp reception should be enabled + /// + /// This flag is only used for datagram-based sockets, + /// not for stream sockets. + /// + /// On Unix this corresponds to the `TIMESTAMPING_FLAG_RX` flag. + #[cfg(target_os = "windows")] + #[cfg_attr(docsrs, doc(cfg(target_os = "windows")))] + pub fn set_rx(&mut self, active: bool) { + self.set_flag(sys::TIMESTAMPING_FLAG_RX, active); + } + + /// This flags controls if receive timestamp reception should be enabled + /// + /// This flag is only used for datagram-based sockets, + /// not for stream sockets. + /// + /// On Windows this corresponds to the `TIMESTAMPING_FLAG_TX` flag. + #[cfg(target_os = "windows")] + #[cfg_attr(docsrs, doc(cfg(target_os = "windows")))] + pub fn set_tx(&mut self, active: bool) { + self.set_flag(sys::TIMESTAMPING_FLAG_TX, active); + } + + /// This flags controls if rx timestamps should be generated by the network + /// adapter + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_RX_HARDWARE` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_rx_hardware_gen(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_RX_HARDWARE, active) + } + + /// This flags controls if rx timestamps should be generated when a package + /// enters the kernel. These timestamps are generated just after a device + /// driver hands a packet to the kernel receive stack. + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_RX_SOFTWARE` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_rx_software_gen(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_RX_SOFTWARE, active) + } + + /// This flags controls if tx timestamps should be generated by the network + /// adapter + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_TX_HARDWARE` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_tx_hardware_gen(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_TX_HARDWARE, active) + } + + /// This flags controls if tx timestamps should be generated when a package + /// enters the kernel. These timestamps are generated just after a device + /// driver hands a packet to the kernel receive stack. + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_TX_SOFTWARE` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_tx_software_gen(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_TX_SOFTWARE, active) + } + + /// This flags controls if tx timestamps should be generated prior to + /// entering the packet scheduler. + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_TX_SCHED` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_tx_sched_gen(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_TX_SCHED, active) + } + + /// This flag controls if tx timestamps when all data in the send buffer + /// has been acknowledged + /// + /// This flag is only used for stream-based sockets, + /// not for datagram sockets. + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_TX_ACK` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_tx_ack_gen(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_TX_ACK, active) + } + + /// This flag controls if any software generated timestamps should be + /// reported when available. + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_SOFTWARE` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_software_reporting(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_SOFTWARE, active) + } + + /// This flag controls if any hardware generated timestamps such as + /// SOF_TIMESTAMPING_TX_HARDWARE should be reported when available. + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_RAW_HARDWARE` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_raw_hardware_reporting(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_RAW_HARDWARE, active) + } + + /// This flag controls if a unique identifier should be generated for each + /// packet. For datagram sockets, the counter increments with each sent + /// packet. For stream sockets, it increments with every byte. + /// + /// This option is only implemented for transmit timestamps. + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_OPT_ID` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_opt_id(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_OPT_ID, active) + } + + /// This flag controls if control messages should be supported for all + /// timestamped packets. This option enables `recv()` `cmsg` support for + /// IPv4 packets with transmit timestamps. + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_OPT_CMSG` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_opt_cmsg(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_OPT_CMSG, active) + } + + /// This flag controls if the timestamp should be returned as a `cmsg` + /// alongside an empty packet, as opposed to alognside the original + /// packet. + /// + /// This option is only implemented for transmit timestamps. + /// This option disables `SOF_TIMESTAMPING_OPT_CMSG` flag set by + /// `set_opt_cmsg(bool)` + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_OPT_TSONLY` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_opt_tsonly(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_OPT_TSONLY, active) + } + + /// This flag controls if optional stats should be obtained along with the + /// transmit timestamps. + /// + /// This option must be used together with `SOF_TIMESTAMPING_OPT_TSONLY` set + /// by `set_opt_tsonly(bool)`. + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_OPT_STATS` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_opt_stats(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_OPT_STATS, active) + } + + /// This flag enables the `SCM_TIMESTAMPING_PKTINFO` control message for + /// incoming packets with hardware timestamps. + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_OPT_PKTINFO` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_opt_pktinfo(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_OPT_PKTINFO, active) + } + + /// This flag enables both hardware and software timestamps for outgoing + /// packets when `SOF_TIMESTAMPING_TX_HARDWARE` and + /// `SOF_TIMESTAMPING_TX_SOFTWARE` are enabled at the same time. If both + /// timestamps are generated, two separate messages will be looped to the + /// socket’s error queue, each containing just one timestamp. + /// + /// On Unix this corresponds to the `SOF_TIMESTAMPING_OPT_TX_SWHW` flag. + #[cfg(not(target_os = "windows"))] + #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))] + pub fn set_opt_tx_swhw(&mut self, active: bool) { + self.set_flag(sys::SOF_TIMESTAMPING_OPT_TX_SWHW, active) + } + + #[inline(always)] + fn set_flag(&mut self, flag: sys::c_uint, active: bool) { + if active { + self.0 |= flag; + } else { + self.0 &= !flag; + } + } +} + /// A version of [`IoSliceMut`] that allows the buffer to be uninitialised. /// /// [`IoSliceMut`]: std::io::IoSliceMut diff --git a/src/socket.rs b/src/socket.rs index 3291707c..97a18198 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -23,6 +23,25 @@ use std::time::Duration; use crate::sys::{self, c_int, getsockopt, setsockopt, Bool}; #[cfg(all(unix, not(target_os = "redox")))] use crate::MsgHdrMut; +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", +)))] +use crate::TimestampingFlags; use crate::{Domain, Protocol, SockAddr, TcpKeepalive, Type}; #[cfg(not(target_os = "redox"))] use crate::{MaybeUninitSlice, MsgHdr, RecvFlags}; @@ -1078,6 +1097,271 @@ impl Socket { pub fn set_write_timeout(&self, duration: Option) -> io::Result<()> { sys::set_timeout_opt(self.as_raw(), sys::SOL_SOCKET, sys::SO_SNDTIMEO, duration) } + + /// Get value for `SO_TIMESTAMP` option on this socket. + /// + /// For more information about this option, see [`set_timestamp`]. + /// + /// [`set_timestamp`]: Socket::set_timestamp + #[cfg(not(any(target_os = "redox", target_os = "hurd", target_os = "windows")))] + #[cfg_attr( + docsrs, + doc(cfg(not(any(target_os = "redox", target_os = "hurd", target_os = "windows")))) + )] + pub fn timestamp(&self) -> io::Result { + unsafe { + getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_TIMESTAMP) + .map(|active| active != 0) + } + } + + /// Set value for the `SO_TIMESTAMP` option on this socket. + /// + /// This indicates that timestamps should be generated for each incoming + /// packet in system time. The timestamp is reported via `recvmsg`. The + /// timestamp is represented by a `timeval`. + /// + /// Additional documentation can be found in documentation of the OS. + /// * Linux: + #[cfg(not(any(target_os = "redox", target_os = "hurd", target_os = "windows")))] + #[cfg_attr( + docsrs, + doc(cfg(not(any(target_os = "redox", target_os = "hurd", target_os = "windows")))) + )] + pub fn set_timestamp(&self, active: bool) -> io::Result<()> { + unsafe { + setsockopt( + self.as_raw(), + sys::SOL_SOCKET, + sys::SO_TIMESTAMP, + active as c_int, + ) + } + } + + /// Get value for `SO_TIMESTAMPNS` option on this socket. + /// + /// For more information about this option, see [`set_timestamp_ns`]. + /// + /// [`set_timestamp_ns`]: Socket::set_timestamp_ns + #[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", + target_os = "windows", + )))] + #[cfg_attr( + docsrs, + doc(cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", + target_os = "windows", + )))) + )] + pub fn timestamp_ns(&self) -> io::Result { + unsafe { + getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_TIMESTAMPNS) + .map(|active| active != 0) + } + } + + /// Set value for the `SO_TIMESTAMPNS` option on this socket. + /// + /// This indicates that timestamps should be generated for each incoming + /// packet in system time. The timestamp is reported via `recvmsg`. The + /// timestamp is represented by a `timespec` with nsec resolution. + /// + /// Additional documentation can be found in documentation of the OS. + /// * Linux: + #[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", + target_os = "windows", + )))] + #[cfg_attr( + docsrs, + doc(cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", + target_os = "windows", + )))) + )] + pub fn set_timestamp_ns(&self, active: bool) -> io::Result<()> { + unsafe { + setsockopt( + self.as_raw(), + sys::SOL_SOCKET, + sys::SO_TIMESTAMPNS, + active as c_int, + ) + } + } + + /// On Unix this gets the value for the `SO_TIMESTAMPING` options and on + /// Windows this gets the value for the `SOI_TIMESTAMPING` option on this + /// socket. + /// + /// For more information about this option, see [`set_timestamping`]. + /// + /// [`set_timestamping`]: Socket::set_timestamping + #[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", + target_os = "windows" + )))] + #[cfg_attr( + docsrs, + doc(cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", + target_os = "windows", + )))) + )] + pub fn timestamping(&self) -> io::Result { + unsafe { + getsockopt::(self.as_raw(), sys::SOL_SOCKET, sys::SO_TIMESTAMPING) + .map(TimestampingFlags) + } + } + + /// On Unix this sets the value for the `SO_TIMESTAMPING` options and on + /// Windows this sets the value for the `SOI_TIMESTAMPING` option on this + /// socket. + /// + /// With this timestamps can be configured to be generated on reception, + /// transmission or both. It supports hardware and software sources for + /// timestamp generation and it also allows generating timestamps for + /// stream sockets. The configuration depends on the flags that are set on + /// the input parameter of type [`TimestampingFlags`]. + /// + /// Additional documentation can be found in documentation of the OS. + /// * Linux: + /// * Windows: + #[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", + )))] + #[cfg_attr( + docsrs, + doc(cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", + )))) + )] + pub fn set_timestamping(&self, flags: TimestampingFlags) -> io::Result<()> { + sys::set_timestamping_opt(self.as_raw(), flags) + } } const fn from_linger(linger: sys::linger) -> Option { diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 562cd60f..bd56442c 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -70,11 +70,49 @@ use std::{io, slice}; use libc::ssize_t; use libc::{in6_addr, in_addr}; +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "windows", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", +)))] +use crate::TimestampingFlags; use crate::{Domain, Protocol, SockAddr, TcpKeepalive, Type}; #[cfg(not(target_os = "redox"))] use crate::{MsgHdr, MsgHdrMut, RecvFlags}; pub(crate) use libc::c_int; +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", +)))] +pub(crate) use libc::c_uint; // Used in `Domain`. pub(crate) use libc::{AF_INET, AF_INET6, AF_UNIX}; @@ -172,6 +210,8 @@ pub(crate) use libc::SO_LINGER; target_os = "watchos", ))] pub(crate) use libc::SO_LINGER_SEC as SO_LINGER; +#[cfg(not(any(target_os = "redox", target_os = "hurd")))] +pub(crate) use libc::SO_TIMESTAMP; pub(crate) use libc::{ ip_mreq as IpMreq, linger, IPPROTO_IP, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF, IPV6_MULTICAST_LOOP, IPV6_UNICAST_HOPS, IPV6_V6ONLY, IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, @@ -225,6 +265,31 @@ pub(crate) use libc::{IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP}; pub(crate) use libc::{ IPV6_JOIN_GROUP as IPV6_ADD_MEMBERSHIP, IPV6_LEAVE_GROUP as IPV6_DROP_MEMBERSHIP, }; +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", +)))] +pub(crate) use libc::{ + SOF_TIMESTAMPING_OPT_CMSG, SOF_TIMESTAMPING_OPT_ID, SOF_TIMESTAMPING_OPT_PKTINFO, + SOF_TIMESTAMPING_OPT_STATS, SOF_TIMESTAMPING_OPT_TSONLY, SOF_TIMESTAMPING_OPT_TX_SWHW, + SOF_TIMESTAMPING_RAW_HARDWARE, SOF_TIMESTAMPING_RX_HARDWARE, SOF_TIMESTAMPING_RX_SOFTWARE, + SOF_TIMESTAMPING_SOFTWARE, SOF_TIMESTAMPING_TX_ACK, SOF_TIMESTAMPING_TX_HARDWARE, + SOF_TIMESTAMPING_TX_SCHED, SOF_TIMESTAMPING_TX_SOFTWARE, SO_TIMESTAMPING, SO_TIMESTAMPNS, +}; #[cfg(all( feature = "all", any( @@ -1187,6 +1252,29 @@ fn into_timeval(duration: Option) -> libc::timeval { } } +/// Wrapper around `setsockopt` to deal with platfrom specific timestamping option +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", +)))] +pub(crate) fn set_timestamping_opt(fd: Socket, flags: TimestampingFlags) -> io::Result<()> { + unsafe { setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, flags.0) } +} + #[cfg(all( feature = "all", not(any(target_os = "haiku", target_os = "openbsd", target_os = "vita")) diff --git a/src/sys/windows.rs b/src/sys/windows.rs index 4c5d9879..7bfdff34 100644 --- a/src/sys/windows.rs +++ b/src/sys/windows.rs @@ -25,15 +25,17 @@ use windows_sys::Win32::Networking::WinSock::SO_PROTOCOL_INFOW; use windows_sys::Win32::Networking::WinSock::{ self, tcp_keepalive, FIONBIO, IN6_ADDR, IN6_ADDR_0, INVALID_SOCKET, IN_ADDR, IN_ADDR_0, POLLERR, POLLHUP, POLLRDNORM, POLLWRNORM, SD_BOTH, SD_RECEIVE, SD_SEND, SIO_KEEPALIVE_VALS, - SOCKET_ERROR, WSABUF, WSAEMSGSIZE, WSAESHUTDOWN, WSAPOLLFD, WSAPROTOCOL_INFOW, - WSA_FLAG_NO_HANDLE_INHERIT, WSA_FLAG_OVERLAPPED, + SIO_TIMESTAMPING, SOCKET_ERROR, TIMESTAMPING_CONFIG, WSABUF, WSAEMSGSIZE, WSAESHUTDOWN, + WSAPOLLFD, WSAPROTOCOL_INFOW, WSA_FLAG_NO_HANDLE_INHERIT, WSA_FLAG_OVERLAPPED, }; use windows_sys::Win32::System::Threading::INFINITE; -use crate::{MsgHdr, RecvFlags, SockAddr, TcpKeepalive, Type}; +use crate::{MsgHdr, RecvFlags, SockAddr, TcpKeepalive, TimestampingFlags, Type}; #[allow(non_camel_case_types)] pub(crate) type c_int = std::os::raw::c_int; +#[allow(non_camel_case_types)] +pub(crate) type c_uint = std::os::raw::c_uint; /// Fake MSG_TRUNC flag for the [`RecvFlags`] struct. /// @@ -77,7 +79,7 @@ pub(crate) use windows_sys::Win32::Networking::WinSock::{ IP_MREQ_SOURCE as IpMreqSource, IP_MULTICAST_IF, IP_MULTICAST_LOOP, IP_MULTICAST_TTL, IP_RECVTOS, IP_TOS, IP_TTL, LINGER as linger, MSG_OOB, MSG_PEEK, SO_BROADCAST, SO_ERROR, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, SO_RCVBUF, SO_RCVTIMEO, SO_REUSEADDR, SO_SNDBUF, - SO_SNDTIMEO, SO_TYPE, TCP_NODELAY, + SO_SNDTIMEO, SO_TYPE, TCP_NODELAY, TIMESTAMPING_FLAG_RX, TIMESTAMPING_FLAG_TX, }; pub(crate) const IPPROTO_IP: c_int = windows_sys::Win32::Networking::WinSock::IPPROTO_IP as c_int; pub(crate) const SOL_SOCKET: c_int = windows_sys::Win32::Networking::WinSock::SOL_SOCKET as c_int; @@ -708,6 +710,32 @@ pub(crate) fn set_timeout_opt( unsafe { setsockopt(socket, level, optname, duration) } } +/// Wrapper around `WSAIoctl` to deal with platfrom specific timestamping option +pub(crate) fn set_timestamping_opt(socket: Socket, flags: TimestampingFlags) -> io::Result<()> { + let mut out = 0; + let mut config = TIMESTAMPING_CONFIG { + Flags: flags.0 as u32, + TxTimestampsBuffered: 0, + }; + + syscall!( + WSAIoctl( + socket, + SIO_TIMESTAMPING, + &mut config as *mut _ as *mut _, + size_of::() as _, + ptr::null_mut(), + 0, + &mut out, + ptr::null_mut(), + None, + ), + PartialEq::eq, + SOCKET_ERROR + ) + .map(|_| ()) +} + fn into_ms(duration: Option) -> u32 { // Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the // timeouts in windows APIs are typically u32 milliseconds. To translate, we diff --git a/tests/socket.rs b/tests/socket.rs index 1c83321a..710962da 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -55,6 +55,25 @@ use windows_sys::Win32::Foundation::{GetHandleInformation, HANDLE_FLAG_INHERIT}; use socket2::MaybeUninitSlice; #[cfg(not(target_os = "vita"))] use socket2::TcpKeepalive; +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", +)))] +use socket2::TimestampingFlags; use socket2::{Domain, Protocol, SockAddr, Socket, Type}; #[test] @@ -1476,6 +1495,68 @@ test!(IPv4 multicast_all_v4, set_multicast_all_v4(false)); #[cfg(all(feature = "all", target_os = "linux"))] test!(IPv6 multicast_all_v6, set_multicast_all_v6(false)); +#[cfg(not(any(target_os = "redox", target_os = "hurd", target_os = "windows")))] +test!(timestamp, set_timestamp(true)); + +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", + target_os = "windows", +)))] +test!(timestamp_ns, set_timestamp_ns(true)); + +#[test] +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "nto", + target_os = "openbsd", + target_os = "solaris", + target_os = "tvos", + target_os = "watchos", + target_os = "redox", + target_os = "fuchsia", + target_os = "vita", + target_os = "hurd", +)))] +fn timestamping() { + let mut t_flags = TimestampingFlags::default(); + #[cfg(target_os = "windows")] + t_flags.set_rx(true); + #[cfg(not(target_os = "windows"))] + t_flags.set_rx_software_gen(true); + + let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap(); + socket.set_timestamping(t_flags).unwrap(); + + #[cfg(not(target_os = "windows"))] + { + // Windows socket api does not support getting the currently set flags + // for timestamping so we only query this on supported unix systems + let queried_t_flags = socket.timestamping().unwrap(); + assert_eq!(t_flags, queried_t_flags); + } +} + #[test] #[cfg(not(any( target_os = "haiku",