Skip to content

Support linux TIMESTAMPING #1420

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

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added
- Added TIMESTAMPNS support for linux
(#[1402](https://github.com/nix-rust/nix/pull/1402))
- Added TIMESTAMPNING support for linux
(#[1420](https://github.com/nix-rust/nix/pull/1420))

### Changed
- Made `forkpty` unsafe, like `fork`
Expand Down
14 changes: 14 additions & 0 deletions src/sys/socket/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,14 @@ pub enum ControlMessageOwned {
/// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html)
#[cfg(all(target_os = "linux"))]
ScmTimestampns(TimeSpec),
/// Configurable nanoseconds resolution timestamps
Copy link
Member

Choose a reason for hiding this comment

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

Could you please explain what the wrapped type is? Why are there three timespecs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

A link to an external website is not sufficient documentation. Why is the wrapped value an array? What's special about "3"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The link was specifically for your question. Have you checked it out?

Copy link
Member

Choose a reason for hiding this comment

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

No; I'm asking for Nix's own documentation to describe why the value is a 3-member array. You can leave the external link in there, similar to how many Nix functions have man page links, but Nix's own documentation should have at least basic information about what the type is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. Added some documentation.

///
/// Three `TimeSpec`s are returned in the control message. At least one field is non-zero at
/// any time. Most timestamps are passed in ts[0]. Hardware timestamps are passed in ts[2].
Copy link
Member

Choose a reason for hiding this comment

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

So what is ts[1] for? Also, the second line of a multiline doc comment like this should be blank. That way, rustdoc will interpret the first line as a summary and the rest as extended details.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think it is necessary to specify every nuances in this short description. User of this feature needs to read the original linux documentation to have a reasonable idea in how to set and what to set in order to get either software and hardware timestamp (in hardware case, user also need to initiate in different way according to different hardware), and to know the historical reason for ts[1] being deprecated. I don't see the point to just copy large paragraphs of the original text here.

Copy link
Member

Choose a reason for hiding this comment

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

ts[1] is deprecated? In that case, there isn't any reason to expose it to the user at all. You should remove it in ControlMessageOwned::decode_from.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a pretty highly specific interface. It is more likely the user has learned the original linux interface first and try to use it through nix crate than people directly learn it through nix itself. Thus, preserving the original interface as much as possible (close to 1-1 map) can reduce the amount of surprise and avoid possible footguns.

///
/// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html)
#[cfg(all(target_os = "linux"))]
ScmTimestamping([TimeSpec; 3]),
#[cfg(any(
target_os = "android",
target_os = "ios",
Expand Down Expand Up @@ -658,6 +666,12 @@ 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 tss: [libc::timespec; 3] = ptr::read_unaligned(p as *const _);
let tss2 = [TimeSpec::from(tss[0]), TimeSpec::from(tss[1]), TimeSpec::from(tss[2])];
ControlMessageOwned::ScmTimestamping(tss2)
}
#[cfg(any(
target_os = "android",
target_os = "freebsd",
Expand Down
2 changes: 2 additions & 0 deletions src/sys/socket/sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ sockopt_impl!(GetOnly, OriginalDst, libc::SOL_IP, libc::SO_ORIGINAL_DST, libc::s
sockopt_impl!(Both, ReceiveTimestamp, libc::SOL_SOCKET, libc::SO_TIMESTAMP, bool);
#[cfg(all(target_os = "linux"))]
sockopt_impl!(Both, ReceiveTimestampns, libc::SOL_SOCKET, libc::SO_TIMESTAMPNS, bool);
#[cfg(all(target_os = "linux"))]
sockopt_impl!(Both, ReceiveTimestamping, libc::SOL_SOCKET, libc::SO_TIMESTAMPING, u32);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(Both, IpTransparent, libc::SOL_IP, libc::IP_TRANSPARENT, bool);
#[cfg(target_os = "openbsd")]
Expand Down
112 changes: 112 additions & 0 deletions test/sys/test_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1654,3 +1654,115 @@ fn test_recvmmsg_timestampns() {
// Close socket
nix::unistd::close(in_socket).unwrap();
}

// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack of QEMU
// support is suspected.
#[cfg_attr(not(any(target_arch = "x86_64")), ignore)]
#[cfg(all(target_os = "linux"))]
#[test]
fn test_recvmsg_timestamping() {
use nix::sys::socket::*;
use nix::sys::uio::IoVec;
use nix::sys::time::*;
use std::time::*;

// Set up
let message = "Ohayō!".as_bytes();
let in_socket = socket(
AddressFamily::Inet,
SockType::Datagram,
SockFlag::empty(),
None).unwrap();
let flag: u32 = libc::SOF_TIMESTAMPING_RX_SOFTWARE | libc::SOF_TIMESTAMPING_SOFTWARE;
setsockopt(in_socket, sockopt::ReceiveTimestamping, &flag).unwrap();
let localhost = InetAddr::new(IpAddr::new_v4(127, 0, 0, 1), 0);
bind(in_socket, &SockAddr::new_inet(localhost)).unwrap();
let address = getsockname(in_socket).unwrap();
// Get initial time
let time0 = SystemTime::now();
// Send the message
let iov = [IoVec::from_slice(message)];
let flags = MsgFlags::empty();
let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap();
assert_eq!(message.len(), l);
// Receive the message
let mut buffer = vec![0u8; message.len()];
let mut cmsgspace = nix::cmsg_space!([TimeSpec; 3]);
let iov = [IoVec::from_mut_slice(&mut buffer)];
let r = recvmsg(in_socket, &iov, Some(&mut cmsgspace), flags).unwrap();
let times = match r.cmsgs().next() {
Some(ControlMessageOwned::ScmTimestamping(times)) => times,
Some(_) => panic!("Unexpected control message"),
None => panic!("No control message")
};
// Check the final time
let time1 = SystemTime::now();
// the packet's received timestamp should lie in-between the two system
// times, unless the system clock was adjusted in the meantime.
let time = times[0];
let rduration = Duration::new(time.tv_sec() as u64,
time.tv_nsec() as u32);
assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration);
assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap());
// Close socket
nix::unistd::close(in_socket).unwrap();
}

// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack of QEMU
// support is suspected.
#[cfg_attr(not(any(target_arch = "x86_64")), ignore)]
#[cfg(all(target_os = "linux"))]
#[test]
fn test_recvmmsg_timestamping() {
use nix::sys::socket::*;
use nix::sys::uio::IoVec;
use nix::sys::time::*;
use std::time::*;

// Set up
let message = "Ohayō!".as_bytes();
let in_socket = socket(
AddressFamily::Inet,
SockType::Datagram,
SockFlag::empty(),
None).unwrap();
let flag: u32 = libc::SOF_TIMESTAMPING_RX_SOFTWARE | libc::SOF_TIMESTAMPING_SOFTWARE;
setsockopt(in_socket, sockopt::ReceiveTimestamping, &flag).unwrap();
let localhost = InetAddr::new(IpAddr::new_v4(127, 0, 0, 1), 0);
bind(in_socket, &SockAddr::new_inet(localhost)).unwrap();
let address = getsockname(in_socket).unwrap();
// Get initial time
let time0 = SystemTime::now();
// Send the message
let iov = [IoVec::from_slice(message)];
let flags = MsgFlags::empty();
let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap();
assert_eq!(message.len(), l);
// Receive the message
let mut buffer = vec![0u8; message.len()];
let mut cmsgspace = nix::cmsg_space!([TimeSpec; 3]);
let iov = [IoVec::from_mut_slice(&mut buffer)];
let mut data = vec![
RecvMmsgData {
iov,
cmsg_buffer: Some(&mut cmsgspace),
},
];
let r = recvmmsg(in_socket, &mut data, flags, None).unwrap();
let times = match r[0].cmsgs().next() {
Some(ControlMessageOwned::ScmTimestamping(times)) => times,
Some(_) => panic!("Unexpected control message"),
None => panic!("No control message")
};
// Check the final time
let time1 = SystemTime::now();
// the packet's received timestamp should lie in-between the two system
// times, unless the system clock was adjusted in the meantime.
let time = times[0];
let rduration = Duration::new(time.tv_sec() as u64,
time.tv_nsec() as u32);
assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration);
assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap());
// Close socket
nix::unistd::close(in_socket).unwrap();
}