From 11f6d33fa9f2f7b28aa69f3a9a3671be883e7b3c Mon Sep 17 00:00:00 2001 From: Nils Andreas Svee Date: Fri, 25 Sep 2026 17:12:01 +0700 Subject: [PATCH] fix(icmp): handle RFC 792 timestamp rollover --- src/pinger_icmp_ts.rs | 53 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/src/pinger_icmp_ts.rs b/src/pinger_icmp_ts.rs index c34cbf1..47f0daf 100644 --- a/src/pinger_icmp_ts.rs +++ b/src/pinger_icmp_ts.rs @@ -19,6 +19,19 @@ pub struct PingerICMPTimestampListener {} pub struct PingerICMPTimestampSender {} +const MS_PER_DAY: i64 = 86_400_000; + +fn timestamp_delta(later: i64, earlier: i64) -> i64 { + let delta = later - earlier; + if delta < -MS_PER_DAY / 2 { + delta + MS_PER_DAY + } else if delta > MS_PER_DAY / 2 { + delta - MS_PER_DAY + } else { + delta + } +} + impl PingListener for PingerICMPTimestampListener { // Result: RTT, down time, up time fn parse_packet( @@ -46,12 +59,18 @@ impl PingListener for PingerICMPTimestampListener { }); } - let time_now = Time::new(ClockId::Realtime); - let time_since_midnight = time_now.get_time_since_midnight(); + if receive & 0x8000_0000 != 0 || transmit & 0x8000_0000 != 0 { + return Err(PingError::InvalidPacket( + "non-standard RFC 792 timestamp".into(), + )); + } - let rtt = (time_since_midnight - originate as i64) as f64; - let dl_time = (time_since_midnight - transmit as i64) as f64; - let ul_time = (receive as i64 - originate as i64) as f64; + let time_since_midnight = + Time::new(ClockId::Realtime).get_time_since_midnight(); + + let rtt = timestamp_delta(time_since_midnight, originate as i64) as f64; + let dl_time = timestamp_delta(time_since_midnight, transmit as i64) as f64; + let ul_time = timestamp_delta(receive as i64, originate as i64) as f64; Ok(PingReply { reflector, @@ -88,3 +107,27 @@ impl PingSender for PingerICMPTimestampSender { ) } } + +#[cfg(test)] +mod tests { + use super::*; + use icmp_socket2::packet::WithTimestampReply; + + #[test] + fn timestamps_wrap_at_midnight_in_both_directions() { + assert_eq!(timestamp_delta(20, 86_399_900), 120); + assert_eq!(timestamp_delta(86_399_900, 20), -120); + } + + #[test] + fn non_standard_rfc_792_timestamps_are_rejected() { + let packet = Icmpv4Packet::with_timestamp_reply(7, 1, 10, 0x8000_0001, 20).unwrap(); + let result = PingerICMPTimestampListener {}.parse_packet( + 7, + "192.0.2.1".parse().unwrap(), + MeasurementType::IcmpTimestamps, + packet, + ); + assert!(matches!(result, Err(PingError::InvalidPacket(_)))); + } +}