Skip to content

Commit aae01e9

Browse files
committed
feat: implement kernel receive timestamps on Linux/Android
Closes #2574. Enable SO_TIMESTAMPNS on Linux and Android, parse SCM_TIMESTAMPNS ancillary messages, and expose timestamps via RecvMeta::timestamp. Increase CMSG_LEN from 88 to 96 bytes on Linux and Android.
1 parent c9b40f1 commit aae01e9

4 files changed

Lines changed: 46 additions & 0 deletions

File tree

quinn-udp/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ pub struct RecvMeta {
118118
pub dst_ip: Option<IpAddr>,
119119
/// The interface index of the interface on which the datagram was received
120120
pub interface_index: Option<u32>,
121+
/// Kernel receive timestamp in nanoseconds since the Unix epoch
122+
///
123+
/// Populated on platforms: Linux, Android.
124+
pub timestamp: Option<u64>,
121125
}
122126

123127
impl Default for RecvMeta {
@@ -130,6 +134,7 @@ impl Default for RecvMeta {
130134
ecn: None,
131135
dst_ip: None,
132136
interface_index: None,
137+
timestamp: None,
133138
}
134139
}
135140
}

quinn-udp/src/unix.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ impl UdpSocketState {
149149
// https://github.com/quinn-rs/quinn/pull/1354.
150150
gro_segments = 64
151151
}
152+
153+
if let Err(_err) =
154+
set_socket_option(&*io, libc::SOL_SOCKET, libc::SO_TIMESTAMPNS, OPTION_ON)
155+
{
156+
crate::log::debug!("Ignoring error setting SO_TIMESTAMPNS on socket: {_err:?}");
157+
}
152158
}
153159
#[cfg(any(target_os = "freebsd", apple))]
154160
{
@@ -696,6 +702,12 @@ fn recv_single(
696702
Ok(1)
697703
}
698704

705+
// Space for packet info, ECN, GRO, and SCM_TIMESTAMPNS ancillary data.
706+
// 96 bytes covers the worst-case currently supported Unix control messages.
707+
#[cfg(any(target_os = "linux", target_os = "android"))]
708+
const CMSG_LEN: usize = 96;
709+
710+
#[cfg(not(any(target_os = "linux", target_os = "android")))]
699711
const CMSG_LEN: usize = 88;
700712

701713
#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled
@@ -894,6 +906,7 @@ fn decode_recv<M: cmsg::MsgHdr<ControlMessage = libc::cmsghdr>>(
894906
dst_ip: None,
895907
interface_index: None,
896908
stride: len,
909+
timestamp: None,
897910
};
898911

899912
let cmsg_iter = unsafe { cmsg::Iter::new(hdr) };
@@ -908,6 +921,7 @@ fn decode_recv<M: cmsg::MsgHdr<ControlMessage = libc::cmsghdr>>(
908921
ecn: EcnCodepoint::from_bits(ctrl.ecn_bits),
909922
dst_ip: ctrl.dst_ip,
910923
interface_index: ctrl.interface_index,
924+
timestamp: ctrl.timestamp,
911925
})
912926
}
913927

@@ -917,6 +931,7 @@ struct ControlMetadata {
917931
dst_ip: Option<IpAddr>,
918932
interface_index: Option<u32>,
919933
stride: usize,
934+
timestamp: Option<u64>,
920935
}
921936

922937
impl ControlMetadata {
@@ -973,6 +988,11 @@ impl ControlMetadata {
973988
(libc::SOL_UDP, libc::UDP_GRO) => unsafe {
974989
self.stride = cmsg::decode::<libc::c_int, libc::cmsghdr>(cmsg) as usize;
975990
},
991+
#[cfg(any(target_os = "linux", target_os = "android"))]
992+
(libc::SOL_SOCKET, libc::SCM_TIMESTAMPNS) => {
993+
let ts = unsafe { cmsg::decode::<libc::timespec, libc::cmsghdr>(cmsg) };
994+
self.timestamp = Some((ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64));
995+
}
976996
_ => {}
977997
}
978998
}

quinn-udp/src/windows.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ impl UdpSocketState {
326326
ecn: EcnCodepoint::from_bits(ecn_bits as u8),
327327
dst_ip,
328328
interface_index,
329+
timestamp: None,
329330
};
330331
Ok(1)
331332
}

quinn-udp/tests/tests.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,26 @@ fn test_send_recv(send: &Socket, recv: &Socket, transmit: Transmit<'_>) {
359359
} else {
360360
assert_eq!(meta.ecn, transmit.ecn);
361361
}
362+
363+
// On Linux and Android, we expect the kernel to provide a receive timestamp
364+
// since we explicitly enabled `SO_TIMESTAMPNS`.
365+
#[cfg(any(target_os = "linux", target_os = "android"))]
366+
{
367+
assert!(
368+
meta.timestamp.is_some(),
369+
"Kernel timestamp should be present on Linux/Android"
370+
);
371+
assert!(
372+
meta.timestamp.unwrap() > 0,
373+
"Kernel timestamp should be non-zero"
374+
);
375+
}
376+
377+
// On other platforms, the timestamp should remain `None`.
378+
#[cfg(not(any(target_os = "linux", target_os = "android")))]
379+
{
380+
assert!(meta.timestamp.is_none());
381+
}
362382
}
363383
assert_eq!(datagrams, expected_datagrams);
364384
}

0 commit comments

Comments
 (0)