From a7b46407b9bb210f47030f4bd52d36990d853437 Mon Sep 17 00:00:00 2001 From: Michael Baikov Date: Wed, 29 Sep 2021 11:32:03 +0800 Subject: [PATCH] feat: Add glibc::SOF_TIMESTAMPING_* support --- src/sys/socket/mod.rs | 50 +++++++++++++++++++++++++++++++++ src/sys/socket/sockopt.rs | 11 ++++++-- test/sys/test_socket.rs | 58 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 3 deletions(-) diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index 97eea3dcb1..a6581b2769 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -170,6 +170,24 @@ pub enum SockProtocol { NetlinkCrypto = libc::NETLINK_CRYPTO, } +#[cfg(any(target_os = "linux"))] +libc_bitflags!{ + pub struct TimestampingFlag: c_uint { + /// Report any software timestamps when available. + SOF_TIMESTAMPING_SOFTWARE; + /// Report hardware timestamps as generated by SOF_TIMESTAMPING_TX_HARDWARE when available. + SOF_TIMESTAMPING_RAW_HARDWARE; + /// Collect transmiting timestamps as reported by hardware + SOF_TIMESTAMPING_TX_HARDWARE; + /// Collect transmiting timestamps as reported by software + SOF_TIMESTAMPING_TX_SOFTWARE; + /// Collect receiving timestamps as reported by hardware + SOF_TIMESTAMPING_RX_HARDWARE; + /// Collect receiving timestamps as reported by software + SOF_TIMESTAMPING_RX_SOFTWARE; + } +} + libc_bitflags!{ /// Additional socket options pub struct SockFlag: c_int { @@ -591,6 +609,14 @@ pub enum ControlMessageOwned { /// # } /// ``` ScmTimestamp(TimeVal), + /// A set of nanosecond resolution timestamps + /// + /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) + #[cfg(all(target_os = "linux"))] + ScmTimestampsns(Timestamps), + #[cfg(any( + target_os = "linux", + ))] /// Nanoseconds resolution timestamp /// /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) @@ -666,6 +692,18 @@ pub enum ControlMessageOwned { Unknown(UnknownCmsg), } +/// For representing packet timestamps via `SO_TIMESTAMPING` interface +#[cfg(all(target_os = "linux"))] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Timestamps { + /// software based timestamp, usually one containing data + pub system: TimeSpec, + /// legacy timestamp, usually empty + pub hw_trans: TimeSpec, + /// hardware based timestamp + pub hw_raw: TimeSpec, +} + impl ControlMessageOwned { /// Decodes a `ControlMessageOwned` from raw bytes. /// @@ -710,6 +748,18 @@ impl ControlMessageOwned { let ts: libc::timespec = ptr::read_unaligned(p as *const _); ControlMessageOwned::ScmTimestampns(TimeSpec::from(ts)) } + #[cfg(all(target_os = "linux"))] + (libc::SOL_SOCKET, libc::SCM_TIMESTAMPING) => { + let tp = p as *const libc::timespec; + let ts: libc::timespec = ptr::read_unaligned(tp); + let system = TimeSpec::from(ts); + let ts: libc::timespec = ptr::read_unaligned(tp.add(1)); + let hw_trans = TimeSpec::from(ts); + let ts: libc::timespec = ptr::read_unaligned(tp.add(2)); + let hw_raw = TimeSpec::from(ts); + let timestamping = Timestamps { system, hw_trans, hw_raw }; + ControlMessageOwned::ScmTimestampsns(timestamping) + } #[cfg(any( target_os = "android", target_os = "freebsd", diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index efa43517ef..ee37b91806 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -16,7 +16,7 @@ use std::os::unix::ffi::OsStrExt; // Constants // TCP_CA_NAME_MAX isn't defined in user space include files -#[cfg(any(target_os = "freebsd", target_os = "linux"))] +#[cfg(any(target_os = "freebsd", target_os = "linux"))] const TCP_CA_NAME_MAX: usize = 16; /// Helper for implementing `SetSockOpt` for a given socket option. See @@ -432,7 +432,12 @@ sockopt_impl!( #[allow(missing_docs)] // Not documented by Linux! Ip6tOriginalDst, GetOnly, libc::SOL_IPV6, libc::IP6T_SO_ORIGINAL_DST, libc::sockaddr_in6); -sockopt_impl!( +#[cfg(any(target_os = "linux"))] +sockopt_impl!( + /// Specifies exact type of timestamping information collected by the kernel + /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) + Timestamping, Both, libc::SOL_SOCKET, libc::SO_TIMESTAMPING, super::TimestampingFlag); +sockopt_impl!( /// Enable or disable the receiving of the `SO_TIMESTAMP` control message. ReceiveTimestamp, Both, libc::SOL_SOCKET, libc::SO_TIMESTAMP, bool); #[cfg(all(target_os = "linux"))] @@ -463,7 +468,7 @@ sockopt_impl!( /// Enable or disable the receiving of the `SCM_CREDENTIALS` control /// message. PassCred, Both, libc::SOL_SOCKET, libc::SO_PASSCRED, bool); -#[cfg(any(target_os = "freebsd", target_os = "linux"))] +#[cfg(any(target_os = "freebsd", target_os = "linux"))] sockopt_impl!( /// This option allows the caller to set the TCP congestion control /// algorithm to be used, on a per-socket basis. diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index 0f6fac6664..8f7b0e6790 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -57,6 +57,64 @@ pub fn test_inetv4_addr_roundtrip_sockaddr_storage_to_addr() { assert_eq!(from_storage, sockaddr); } +#[cfg(any(target_os = "linux"))] +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[test] +pub fn test_timestamping() { + use nix::sys::socket::{ + recvmsg, sendmsg, setsockopt, socket, sockopt::Timestamping, ControlMessageOwned, MsgFlags, + SockFlag, SockType, TimestampingFlag, + }; + use nix::sys::uio::IoVec; + + let std_sa = SocketAddr::from_str("127.0.0.1:6790").unwrap(); + let inet_addr = InetAddr::from_std(&std_sa); + let sock_addr = SockAddr::new_inet(inet_addr); + + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + nix::sys::socket::bind(rsock, &sock_addr).unwrap(); + + setsockopt(rsock, Timestamping, &TimestampingFlag::all()).unwrap(); + + let sbuf = [0u8; 2048]; + let mut rbuf = [0u8; 2048]; + let flags = MsgFlags::empty(); + let iov1 = [IoVec::from_slice(&sbuf)]; + let iov2 = [IoVec::from_mut_slice(&mut rbuf)]; + let mut cmsg = cmsg_space!(nix::sys::socket::Timestamps); + sendmsg(ssock, &iov1, &[], flags, Some(&sock_addr)).unwrap(); + let recv = recvmsg(rsock, &iov2, Some(&mut cmsg), flags).unwrap(); + + let mut ts = None; + for c in recv.cmsgs() { + if let ControlMessageOwned::ScmTimestampsns(timestamps) = c { + ts = Some(timestamps.system); + } + } + let ts = ts.expect("ScmTimestampns is present"); + let sys_time = ::nix::time::clock_gettime(::nix::time::ClockId::CLOCK_REALTIME).unwrap(); + let diff = if ts > sys_time { + ts - sys_time + } else { + sys_time - ts + }; + assert!(std::time::Duration::from(diff).as_secs() < 60); +} + #[test] pub fn test_inetv6_addr_to_sock_addr() { let port: u16 = 3000;