Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added options to support socket timestamping #504

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
223 changes: 223 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,229 @@ 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(all(
feature = "all",
any(target_os = "linux", target_os = "android", target_os = "windows")
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
feature = "all",
any(target_os = "linux", target_os = "android", target_os = "windows")
)))
)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these lists are so long, can we make it "opt-in" instead of excluding basically everything.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, since it's not available on all OS, we need to use the all feature.

Copy link
Author

@tglane tglane Apr 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you are right. I will change that to reduce the overall noise in the code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 08e843b I changed the cfgattributes to be opt-in. Thanks for this remark, its so much cleaner now.

#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
pub struct TimestampingFlags(sys::c_uint);

#[cfg(all(
feature = "all",
any(target_os = "linux", target_os = "android", target_os = "windows")
))]
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
Expand Down
140 changes: 140 additions & 0 deletions src/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ use std::time::Duration;
use crate::sys::{self, c_int, getsockopt, setsockopt, Bool};
#[cfg(all(unix, not(target_os = "redox")))]
use crate::MsgHdrMut;
#[cfg(all(
feature = "all",
any(target_os = "linux", target_os = "android", target_os = "windows")
))]
use crate::TimestampingFlags;
use crate::{Domain, Protocol, SockAddr, TcpKeepalive, Type};
#[cfg(not(target_os = "redox"))]
use crate::{MaybeUninitSlice, MsgHdr, RecvFlags};
Expand Down Expand Up @@ -1109,6 +1114,141 @@ impl Socket {
pub fn set_write_timeout(&self, duration: Option<Duration>) -> 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<bool> {
unsafe {
getsockopt::<c_int>(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: <https://docs.kernel.org/networking/timestamping.html>
#[cfg(all(
feature = "all",
not(any(target_os = "redox", target_os = "hurd", target_os = "windows"))
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
feature = "all",
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(all(feature = "all", any(target_os = "linux", target_os = "android")))]
#[cfg_attr(
docsrs,
doc(cfg(all(feature = "all", any(target_os = "linux", target_os = "android"))))
)]
pub fn timestamp_ns(&self) -> io::Result<bool> {
unsafe {
getsockopt::<c_int>(self.as_raw(), sys::SOL_SOCKET, sys::SO_TIMESTAMPNS)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any value in having both SO_TIMESTAMPNS and SO_TIMESTAMP? Ideally we can make a wrapper that maps it to Duration regardless of the underlying option.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I understand what you are proposing here. This API should not allow the user to set timestamps or some duration. The timestamping API allows the user to receive query timestamps for when packets were send or received (depending on the flags).

With SO_TIMESTAMP you can query packet timestamps as struct timeval with microsecond accuracy while SO_TIMESTAMPNS allows for query packet timestamps as struct timespec with nanosecond accuracy. So yes, we could probably merge them into one function with an enum to specify the accuracy but we definitely need some way to set either of these.

.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: <https://docs.kernel.org/networking/timestamping.html>
#[cfg(all(feature = "all", any(target_os = "linux", target_os = "android")))]
#[cfg_attr(
docsrs,
doc(cfg(all(feature = "all", any(target_os = "linux", target_os = "android"))))
)]
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(all(feature = "all", any(target_os = "linux", target_os = "android")))]
#[cfg_attr(
docsrs,
doc(cfg(all(feature = "all", any(target_os = "linux", target_os = "android"))))
)]
pub fn timestamping(&self) -> io::Result<TimestampingFlags> {
unsafe {
getsockopt::<sys::c_uint>(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: <https://docs.kernel.org/networking/timestamping.html>
/// * Windows: <https://learn.microsoft.com/en-us/windows/win32/winsock/winsock-timestamping>
#[cfg(all(
feature = "all",
any(target_os = "linux", target_os = "android", target_os = "windows")
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
feature = "all",
any(target_os = "linux", target_os = "android", target_os = "windows")
)))
)]
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<Duration> {
Expand Down