3

I am trying to get the NIC hardware receive timestamps for TcpStream messages. I found some code examples in C (https://stackoverflow.com/a/42210708, and https://stackoverflow.com/a/47329376/9518712) but I'm struggling to implement them in Rust.

I also wondered if there is a better abstraction for it through something like socket ancillary data https://doc.rust-lang.org/std/os/unix/net/struct.SocketAncillary.html

I would be grateful for any simple working code examples.

let (mut socket, response) = connect(Url::parse("wss://myurl.com/ws")?)?;

let stream = socket.get_mut();
let tcp_stream = match stream {
    MaybeTlsStream::Rustls(ref s) => &s.sock,
    _ => panic!(),
};
let fd = tcp_stream.as_raw_fd();

let msg = socket.read_message().expect("Error reading message");
println!("Received: {}", msg);

// ??? code to get socket timestamp ???
lookaside
  • 325
  • 3
  • 9
  • Does this crate https://crates.io/crates/sntpc provide the functionality you seek? – jthulhu Apr 16 '22 at 07:15
  • @BlackBeans I don't think that gives me the accuracy/precision I'm interested in. I'm trying to get the timestamp a TCP message arrives on the wire with at least microsecond precision. – lookaside Apr 21 '22 at 12:42

1 Answers1

0

To use SO_TIMESTAMPING as described in the linked answer, you will probably need to use the nix crate.

It happens to contain the following example:

// Set up
let message = "Ohayō!".as_bytes();
let in_socket = socket(
    AddressFamily::Inet,
    SockType::Datagram,
    SockFlag::empty(),
    None).unwrap();
setsockopt(in_socket, sockopt::ReceiveTimestamp, &true).unwrap();
let localhost = SockaddrIn::from_str("127.0.0.1:0").unwrap();
bind(in_socket, &localhost).unwrap();
let address: SockaddrIn = getsockname(in_socket).unwrap();
// Get initial time
let time0 = SystemTime::now();
// Send the message
let iov = [IoSlice::new(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 = cmsg_space!(TimeVal);
let mut iov = [IoSliceMut::new(&mut buffer)];
let r = recvmsg::<SockaddrIn>(in_socket, &mut iov, Some(&mut cmsgspace), flags)
    .unwrap();
let rtime = match r.cmsgs().next() {
    Some(ControlMessageOwned::ScmTimestamp(rtime)) => rtime,
    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 rduration = Duration::new(rtime.tv_sec() as u64,
                              rtime.tv_usec() as u32 * 1000);
assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration);
assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap());
// Close socket
nix::unistd::close(in_socket).unwrap();

To adapt this to your code skeleton that uses the standard library:

use std::io::IoSliceMut;
use nix::sys::socket::{setsockopt, TimestampingFlag};
use nix::sys::socket::sockopt::Timestamping;
let fd = tcp_stream.as_raw_fd();

// get both software and hardware timing data for received packets
let timestamp_options = 
    TimestampingFlag::SOF_TIMESTAMPING_RX_HARDWARE 
    | TimestampingFlag::SOF_TIMESTAMPING_RX_SOFTWARE;
setsockopt(fd, Timestamping, &timestamping_options)?;

// assuming your packet can be as large as 32 bytes, update if needed
let mut buffer = vec![0u8; 32];
let mut cmsgspace = nix::cmsg_space!(TimeVal);
let mut iov = [IoSliceMut::new(&mut buffer)];

let r = recvmsg::<SockaddrStorage>(
    tcp_stream.as_raw_fd(), 
    &mut iov, 
    Some(&mut cmsgspace), 
    MsgFlags::empty())?;

if let Some(ControlMessageOwned::ScmTimestamp(rtime)) = r.cmsgs().next() {
    // use rtime as you like
};

Basically, the main thing you must note is that you must use the syscall recvmsg to read from your socket.

Akshat Mahajan
  • 9,543
  • 4
  • 35
  • 44