Skip to content

Commit ef3ac08

Browse files
committed
Adding packet receipt timestamp collection into RecvMeta for linux
1 parent 56ae894 commit ef3ac08

File tree

6 files changed

+116
-2
lines changed

6 files changed

+116
-2
lines changed

quinn-udp/src/cmsg/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ impl<M: MsgHdr> Drop for Encoder<'_, M> {
8383
/// `cmsg` must refer to a native cmsg containing a payload of type `T`
8484
pub(crate) unsafe fn decode<T: Copy, C: CMsgHdr>(cmsg: &impl CMsgHdr) -> T {
8585
assert!(mem::align_of::<T>() <= mem::align_of::<C>());
86-
debug_assert_eq!(cmsg.len(), C::cmsg_len(mem::size_of::<T>()));
86+
debug_assert_eq!(
87+
cmsg.len(),
88+
C::cmsg_len(mem::size_of::<T>()),
89+
"cmsg truncated -- you might need to raise the CMSG_LEN constant for this platform"
90+
);
8791
ptr::read(cmsg.cmsg_data() as *const T)
8892
}
8993

quinn-udp/src/fallback.rs

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ impl UdpSocketState {
7272
addr: addr.as_socket().unwrap(),
7373
ecn: None,
7474
dst_ip: None,
75+
#[cfg(not(wasm_browser))]
76+
timestamp: None,
7577
};
7678
Ok(1)
7779
}

quinn-udp/src/lib.rs

+9
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ pub struct RecvMeta {
115115
/// Populated on platforms: Windows, Linux, Android (API level > 25),
116116
/// FreeBSD, OpenBSD, NetBSD, macOS, and iOS.
117117
pub dst_ip: Option<IpAddr>,
118+
/// A timestamp for when the given packet was received by the operating system.
119+
/// Controlled by the `set_recv_timestamping` option on the source socket where available.
120+
///
121+
/// Populated on platforms with varying clock sources, as follows:
122+
/// - Linux: `CLOCK_REALTIME` (see `SO_TIMESTAMPNS` in `man 7 socket` for more information)
123+
#[cfg(not(wasm_browser))]
124+
pub timestamp: Option<Duration>,
118125
}
119126

120127
impl Default for RecvMeta {
@@ -126,6 +133,8 @@ impl Default for RecvMeta {
126133
stride: 0,
127134
ecn: None,
128135
dst_ip: None,
136+
#[cfg(not(wasm_browser))]
137+
timestamp: None,
129138
}
130139
}
131140
}

quinn-udp/src/unix.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ use std::{
1212
time::Instant,
1313
};
1414

15+
#[cfg(target_os = "linux")]
16+
use std::time::Duration;
17+
1518
use socket2::SockRef;
1619

1720
use super::{
@@ -96,6 +99,11 @@ impl UdpSocketState {
9699
unsafe { libc::CMSG_SPACE(mem::size_of::<libc::in6_pktinfo>() as _) as usize };
97100
}
98101

102+
if cfg!(target_os = "linux") {
103+
cmsg_platform_space +=
104+
unsafe { libc::CMSG_SPACE(mem::size_of::<libc::timeval>() as _) as usize };
105+
}
106+
99107
assert!(
100108
CMSG_LEN
101109
>= unsafe { libc::CMSG_SPACE(mem::size_of::<libc::c_int>() as _) as usize }
@@ -257,6 +265,18 @@ impl UdpSocketState {
257265
self.may_fragment
258266
}
259267

268+
/// Sets the socket to receive packet receipt timestamps from the operating system.
269+
/// These can be accessed via [`RecvMeta::timestamp`] on packets when enabled.
270+
#[cfg(target_os = "linux")]
271+
pub fn set_recv_timestamping(&self, sock: UdpSockRef<'_>, enabled: bool) -> io::Result<()> {
272+
let enabled = match enabled {
273+
true => OPTION_ON,
274+
false => OPTION_OFF,
275+
};
276+
277+
set_socket_option(&*sock.0, libc::SOL_SOCKET, libc::SO_TIMESTAMPNS, enabled)
278+
}
279+
260280
/// Returns true if we previously got an EINVAL error from `sendmsg` syscall.
261281
fn sendmsg_einval(&self) -> bool {
262282
self.sendmsg_einval.load(Ordering::Relaxed)
@@ -543,7 +563,7 @@ fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) ->
543563
Ok(1)
544564
}
545565

546-
const CMSG_LEN: usize = 88;
566+
const CMSG_LEN: usize = 96;
547567

548568
fn prepare_msg(
549569
transmit: &Transmit<'_>,
@@ -681,6 +701,8 @@ fn decode_recv(
681701
let mut dst_ip = None;
682702
#[allow(unused_mut)] // only mutable on Linux
683703
let mut stride = len;
704+
#[allow(unused_mut)] // only mutable on Linux
705+
let mut timestamp = None;
684706

685707
let cmsg_iter = unsafe { cmsg::Iter::new(hdr) };
686708
for cmsg in cmsg_iter {
@@ -725,6 +747,11 @@ fn decode_recv(
725747
(libc::SOL_UDP, gro::UDP_GRO) => unsafe {
726748
stride = cmsg::decode::<libc::c_int, libc::cmsghdr>(cmsg) as usize;
727749
},
750+
#[cfg(target_os = "linux")]
751+
(libc::SOL_SOCKET, libc::SCM_TIMESTAMPNS) => {
752+
let tv = unsafe { cmsg::decode::<libc::timespec, libc::cmsghdr>(cmsg) };
753+
timestamp = Some(Duration::new(tv.tv_sec as u64, tv.tv_nsec as u32));
754+
}
728755
_ => {}
729756
}
730757
}
@@ -759,6 +786,7 @@ fn decode_recv(
759786
addr,
760787
ecn: EcnCodepoint::from_bits(ecn_bits),
761788
dst_ip,
789+
timestamp,
762790
}
763791
}
764792

@@ -907,6 +935,8 @@ fn set_socket_option(
907935
}
908936
}
909937

938+
#[allow(dead_code)]
939+
const OPTION_OFF: libc::c_int = 0;
910940
const OPTION_ON: libc::c_int = 1;
911941

912942
#[cfg(not(any(target_os = "linux", target_os = "android")))]

quinn-udp/src/windows.rs

+1
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ impl UdpSocketState {
268268
addr: addr.unwrap(),
269269
ecn: EcnCodepoint::from_bits(ecn_bits as u8),
270270
dst_ip,
271+
timestamp: None,
271272
};
272273
Ok(1)
273274
}

quinn-udp/tests/tests.rs

+68
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use std::{
55
net::{IpAddr, Ipv4Addr, Ipv6Addr, UdpSocket},
66
slice,
77
};
8+
#[cfg(target_os = "linux")]
9+
use std::{mem::MaybeUninit, time::Duration};
810

911
use quinn_udp::{EcnCodepoint, RecvMeta, Transmit, UdpSocketState};
1012
use socket2::Socket;
@@ -186,6 +188,72 @@ fn gso() {
186188
);
187189
}
188190

191+
#[test]
192+
#[cfg(target_os = "linux")]
193+
fn receive_timestamp() {
194+
let send = UdpSocket::bind((Ipv6Addr::LOCALHOST, 0))
195+
.or_else(|_| UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)))
196+
.unwrap();
197+
let recv = UdpSocket::bind((Ipv6Addr::LOCALHOST, 0))
198+
.or_else(|_| UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)))
199+
.unwrap();
200+
let dst_addr = recv.local_addr().unwrap();
201+
202+
let send_state = UdpSocketState::new((&send).into()).unwrap();
203+
let recv_state = UdpSocketState::new((&recv).into()).unwrap();
204+
205+
recv_state
206+
.set_recv_timestamping((&recv).into(), true)
207+
.expect("failed to set sockopt -- unsupported?");
208+
209+
// Reverse non-blocking flag set by `UdpSocketState` to make the test non-racy
210+
recv.set_nonblocking(false).unwrap();
211+
212+
let mut buf = [0; u16::MAX as usize];
213+
let mut meta = RecvMeta::default();
214+
215+
let mut time_start = MaybeUninit::<libc::timespec>::uninit();
216+
let mut time_end = MaybeUninit::<libc::timespec>::uninit();
217+
218+
// Use the actual CLOCK_REALTIME clock source in linux, rather than relying on SystemTime
219+
let errno = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, time_start.as_mut_ptr()) };
220+
assert_eq!(errno, 0, "failed to read CLOCK_REALTIME");
221+
let time_start = unsafe { time_start.assume_init() };
222+
let time_start = Duration::new(time_start.tv_sec as u64, time_start.tv_nsec as u32);
223+
224+
send_state
225+
.try_send(
226+
(&send).into(),
227+
&Transmit {
228+
destination: dst_addr,
229+
ecn: None,
230+
contents: b"hello",
231+
segment_size: None,
232+
src_ip: None,
233+
},
234+
)
235+
.unwrap();
236+
237+
recv_state
238+
.recv(
239+
(&recv).into(),
240+
&mut [IoSliceMut::new(&mut buf)],
241+
slice::from_mut(&mut meta),
242+
)
243+
.unwrap();
244+
245+
let errno = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, time_end.as_mut_ptr()) };
246+
assert_eq!(errno, 0, "failed to read CLOCK_REALTIME");
247+
let time_end = unsafe { time_end.assume_init() };
248+
let time_end = Duration::new(time_end.tv_sec as u64, time_end.tv_nsec as u32);
249+
250+
// Note: there's a very slim chance that the clock could jump (via ntp, etc.) and throw off
251+
// these two checks, but it's still better to validate the timestamp result with that risk
252+
let timestamp = meta.timestamp.unwrap();
253+
assert!(time_start <= timestamp);
254+
assert!(timestamp <= time_end);
255+
}
256+
189257
fn test_send_recv(send: &Socket, recv: &Socket, transmit: Transmit) {
190258
let send_state = UdpSocketState::new(send.into()).unwrap();
191259
let recv_state = UdpSocketState::new(recv.into()).unwrap();

0 commit comments

Comments
 (0)