diff --git a/changelog/2187.added.md b/changelog/2187.added.md new file mode 100644 index 0000000000..a13ef70990 --- /dev/null +++ b/changelog/2187.added.md @@ -0,0 +1,2 @@ +- Added the `::nix::sys::socket::SocketTimestamp` enum for configuring the `TsClock` (a.k.a `SO_TS_CLOCK`) sockopt +- Added FreeBSD's `ScmRealtime` and `ScmMonotonic` as new options in `::nix::sys::socket::ControlMessageOwned` \ No newline at end of file diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index 2d94b47aea..0c9bac99c8 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -1,7 +1,11 @@ //! Socket interface functions //! //! [Further reading](https://man7.org/linux/man-pages/man7/socket.7.html) -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "linux" +))] #[cfg(feature = "uio")] use crate::sys::time::TimeSpec; #[cfg(not(target_os = "redox"))] @@ -410,6 +414,25 @@ libc_bitflags! { } } +#[cfg(target_os = "freebsd")] +libc_enum! { + /// A selector for which clock to use when generating packet timestamps. + /// Used when setting [`TsClock`](crate::sys::socket::sockopt::TsClock) on a socket. + /// (For more details, see [setsockopt(2)](https://man.freebsd.org/cgi/man.cgi?setsockopt)). + #[repr(i32)] + #[non_exhaustive] + pub enum SocketTimestamp { + /// Microsecond resolution, realtime. This is the default. + SO_TS_REALTIME_MICRO, + /// Sub-nanosecond resolution, realtime. + SO_TS_BINTIME, + /// Nanosecond resolution, realtime. + SO_TS_REALTIME, + /// Nanosecond resolution, monotonic. + SO_TS_MONOTONIC, + } +} + cfg_if! { if #[cfg(any(target_os = "android", target_os = "linux"))] { /// Unix credentials of the sending process. @@ -779,6 +802,16 @@ pub enum ControlMessageOwned { #[cfg(any(target_os = "android", target_os = "linux"))] #[cfg_attr(docsrs, doc(cfg(all())))] ScmTimestampns(TimeSpec), + /// Realtime clock timestamp + /// + /// [Further reading](https://man.freebsd.org/cgi/man.cgi?setsockopt) + #[cfg(target_os = "freebsd")] + ScmRealtime(TimeSpec), + /// Monotonic clock timestamp + /// + /// [Further reading](https://man.freebsd.org/cgi/man.cgi?setsockopt) + #[cfg(target_os = "freebsd")] + ScmMonotonic(TimeSpec), #[cfg(any( target_os = "android", apple_targets, @@ -958,6 +991,16 @@ impl ControlMessageOwned { let ts: libc::timespec = ptr::read_unaligned(p as *const _); ControlMessageOwned::ScmTimestampns(TimeSpec::from(ts)) } + #[cfg(target_os = "freebsd")] + (libc::SOL_SOCKET, libc::SCM_REALTIME) => { + let ts: libc::timespec = unsafe { ptr::read_unaligned(p as *const _) }; + ControlMessageOwned::ScmRealtime(TimeSpec::from(ts)) + } + #[cfg(target_os = "freebsd")] + (libc::SOL_SOCKET, libc::SCM_MONOTONIC) => { + let ts: libc::timespec = unsafe { ptr::read_unaligned(p as *const _) }; + ControlMessageOwned::ScmMonotonic(TimeSpec::from(ts)) + } #[cfg(any(target_os = "android", target_os = "linux"))] (libc::SOL_SOCKET, libc::SCM_TIMESTAMPING) => { let tp = p as *const libc::timespec; diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index 07a0ac4ed5..c47cbb6ca9 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -721,7 +721,7 @@ sockopt_impl!( Both, libc::SOL_SOCKET, libc::SO_TS_CLOCK, - i32 + super::SocketTimestamp ); #[cfg(any(target_os = "android", target_os = "linux"))] #[cfg(feature = "net")] diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index ddd4f7c730..dccc666024 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -72,6 +72,126 @@ pub fn test_timestamping() { assert!(std::time::Duration::from(diff).as_secs() < 60); } +#[cfg(target_os = "freebsd")] +#[test] +pub fn test_timestamping_realtime() { + use nix::sys::socket::{ + recvmsg, sendmsg, setsockopt, socket, sockopt::ReceiveTimestamp, + sockopt::TsClock, ControlMessageOwned, MsgFlags, SockFlag, SockType, + SockaddrIn, SocketTimestamp, + }; + use std::io::{IoSlice, IoSliceMut}; + + let sock_addr = SockaddrIn::from_str("127.0.0.1:6792").unwrap(); + + 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.as_raw_fd(), &sock_addr).unwrap(); + + setsockopt(&rsock, ReceiveTimestamp, &true).unwrap(); + setsockopt(&rsock, TsClock, &SocketTimestamp::SO_TS_REALTIME).unwrap(); + + let sbuf = [0u8; 2048]; + let mut rbuf = [0u8; 2048]; + let flags = MsgFlags::empty(); + let iov1 = [IoSlice::new(&sbuf)]; + let mut iov2 = [IoSliceMut::new(&mut rbuf)]; + + let mut cmsg = cmsg_space!(nix::sys::time::TimeVal); + sendmsg(ssock.as_raw_fd(), &iov1, &[], flags, Some(&sock_addr)).unwrap(); + let recv = + recvmsg::<()>(rsock.as_raw_fd(), &mut iov2, Some(&mut cmsg), flags) + .unwrap(); + + let mut ts = None; + for c in recv.cmsgs() { + if let ControlMessageOwned::ScmRealtime(timeval) = c { + ts = Some(timeval); + } + } + let ts = ts.expect("ScmRealtime 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); +} + +#[cfg(target_os = "freebsd")] +#[test] +pub fn test_timestamping_monotonic() { + use nix::sys::socket::{ + recvmsg, sendmsg, setsockopt, socket, sockopt::ReceiveTimestamp, + sockopt::TsClock, ControlMessageOwned, MsgFlags, SockFlag, SockType, + SockaddrIn, SocketTimestamp, + }; + use std::io::{IoSlice, IoSliceMut}; + + let sock_addr = SockaddrIn::from_str("127.0.0.1:6803").unwrap(); + + 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.as_raw_fd(), &sock_addr).unwrap(); + + setsockopt(&rsock, ReceiveTimestamp, &true).unwrap(); + setsockopt(&rsock, TsClock, &SocketTimestamp::SO_TS_MONOTONIC).unwrap(); + + let sbuf = [0u8; 2048]; + let mut rbuf = [0u8; 2048]; + let flags = MsgFlags::empty(); + let iov1 = [IoSlice::new(&sbuf)]; + let mut iov2 = [IoSliceMut::new(&mut rbuf)]; + + let mut cmsg = cmsg_space!(nix::sys::time::TimeVal); + sendmsg(ssock.as_raw_fd(), &iov1, &[], flags, Some(&sock_addr)).unwrap(); + let recv = + recvmsg::<()>(rsock.as_raw_fd(), &mut iov2, Some(&mut cmsg), flags) + .unwrap(); + + let mut ts = None; + for c in recv.cmsgs() { + if let ControlMessageOwned::ScmMonotonic(timeval) = c { + ts = Some(timeval); + } + } + let ts = ts.expect("ScmMonotonic is present"); + let sys_time = + ::nix::time::clock_gettime(::nix::time::ClockId::CLOCK_MONOTONIC) + .unwrap(); + let diff = sys_time - ts; // Monotonic clock sys_time must be greater + assert!(std::time::Duration::from(diff).as_secs() < 60); +} + #[test] pub fn test_path_to_sock_addr() { let path = "/foo/bar"; diff --git a/test/sys/test_sockopt.rs b/test/sys/test_sockopt.rs index a3100c99a8..0107deec36 100644 --- a/test/sys/test_sockopt.rs +++ b/test/sys/test_sockopt.rs @@ -436,3 +436,116 @@ fn test_ipv6_tclass() { setsockopt(&fd, sockopt::Ipv6TClass, &class).unwrap(); assert_eq!(getsockopt(&fd, sockopt::Ipv6TClass).unwrap(), class); } + +#[test] +#[cfg(target_os = "freebsd")] +fn test_receive_timestamp() { + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap(); + assert!(getsockopt(&fd, sockopt::ReceiveTimestamp).unwrap()); +} + +#[test] +#[cfg(target_os = "freebsd")] +fn test_ts_clock_realtime_micro() { + use nix::sys::socket::SocketTimestamp; + + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + + // FreeBSD setsockopt docs say to set SO_TS_CLOCK after setting SO_TIMESTAMP. + setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap(); + + setsockopt( + &fd, + sockopt::TsClock, + &SocketTimestamp::SO_TS_REALTIME_MICRO, + ) + .unwrap(); + assert_eq!( + getsockopt(&fd, sockopt::TsClock).unwrap(), + SocketTimestamp::SO_TS_REALTIME_MICRO + ); +} + +#[test] +#[cfg(target_os = "freebsd")] +fn test_ts_clock_bintime() { + use nix::sys::socket::SocketTimestamp; + + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + + // FreeBSD setsockopt docs say to set SO_TS_CLOCK after setting SO_TIMESTAMP. + setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap(); + + setsockopt(&fd, sockopt::TsClock, &SocketTimestamp::SO_TS_BINTIME).unwrap(); + assert_eq!( + getsockopt(&fd, sockopt::TsClock).unwrap(), + SocketTimestamp::SO_TS_BINTIME + ); +} + +#[test] +#[cfg(target_os = "freebsd")] +fn test_ts_clock_realtime() { + use nix::sys::socket::SocketTimestamp; + + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + + // FreeBSD setsockopt docs say to set SO_TS_CLOCK after setting SO_TIMESTAMP. + setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap(); + + setsockopt(&fd, sockopt::TsClock, &SocketTimestamp::SO_TS_REALTIME) + .unwrap(); + assert_eq!( + getsockopt(&fd, sockopt::TsClock).unwrap(), + SocketTimestamp::SO_TS_REALTIME + ); +} + +#[test] +#[cfg(target_os = "freebsd")] +fn test_ts_clock_monotonic() { + use nix::sys::socket::SocketTimestamp; + + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + + // FreeBSD setsockopt docs say to set SO_TS_CLOCK after setting SO_TIMESTAMP. + setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap(); + + setsockopt(&fd, sockopt::TsClock, &SocketTimestamp::SO_TS_MONOTONIC) + .unwrap(); + assert_eq!( + getsockopt(&fd, sockopt::TsClock).unwrap(), + SocketTimestamp::SO_TS_MONOTONIC + ); +}