Skip to content

Commit 8b6c101

Browse files
committed
Adding packet receipt timestamp collection into RecvMeta for linux
1 parent 2ba600b commit 8b6c101

File tree

6 files changed

+101
-2
lines changed

6 files changed

+101
-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
@@ -267,6 +267,7 @@ impl UdpSocketState {
267267
addr: addr.unwrap(),
268268
ecn: EcnCodepoint::from_bits(ecn_bits as u8),
269269
dst_ip,
270+
timestamp: None,
270271
};
271272
Ok(1)
272273
}

quinn-udp/tests/tests.rs

+53
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,59 @@ fn gso() {
186186
);
187187
}
188188

189+
#[test]
190+
#[cfg(target_os = "linux")]
191+
fn receive_timestamp() {
192+
let send = UdpSocket::bind((Ipv6Addr::LOCALHOST, 0))
193+
.or_else(|_| UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)))
194+
.unwrap();
195+
let recv = UdpSocket::bind((Ipv6Addr::LOCALHOST, 0))
196+
.or_else(|_| UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)))
197+
.unwrap();
198+
let dst_addr = recv.local_addr().unwrap();
199+
200+
let send_state = UdpSocketState::new((&send).into()).unwrap();
201+
let recv_state = UdpSocketState::new((&recv).into()).unwrap();
202+
203+
if recv_state
204+
.set_recv_timestamping((&recv).into(), true)
205+
.is_err()
206+
{
207+
return; // Not supported on this machine -- skip this test
208+
}
209+
210+
// Reverse non-blocking flag set by `UdpSocketState` to make the test non-racy
211+
recv.set_nonblocking(false).unwrap();
212+
213+
let mut buf = [0; u16::MAX as usize];
214+
let mut meta = RecvMeta::default();
215+
216+
send_state
217+
.try_send(
218+
(&send).into(),
219+
&Transmit {
220+
destination: dst_addr,
221+
ecn: None,
222+
contents: b"hello",
223+
segment_size: None,
224+
src_ip: None,
225+
},
226+
)
227+
.unwrap();
228+
229+
recv_state
230+
.recv(
231+
(&recv).into(),
232+
&mut [IoSliceMut::new(&mut buf)],
233+
slice::from_mut(&mut meta),
234+
)
235+
.unwrap();
236+
237+
// We can't really validate the actual value of the timestamp because lots of local factors
238+
// can mess with a CLOCK_REALTIME timestamp here, but we should at least have something.
239+
assert!(meta.timestamp.is_some());
240+
}
241+
189242
fn test_send_recv(send: &Socket, recv: &Socket, transmit: Transmit) {
190243
let send_state = UdpSocketState::new(send.into()).unwrap();
191244
let recv_state = UdpSocketState::new(recv.into()).unwrap();

0 commit comments

Comments
 (0)