Files
sqm-autorate-rust/src/pinger_icmp_ts.rs
T
Nils Andreas Svee 817cc304e6 refactor(time): Introduce typed clock sources for compile-time safety
This change replaces runtime `ClockId` parameters with distinct types to ensure correct clock usage at compile time.
2026-09-30 12:05:42 +07:00

134 lines
4.6 KiB
Rust

// SPDX-FileCopyrightText: 2022-Present Charles Corrigan mailto:chas-iot@runegate.org (github @chas-iot)
// SPDX-FileCopyrightText: 2022-Present Daniel Lakeland mailto:dlakelan@street-artists.org (github @dlakelan)
// SPDX-FileCopyrightText: 2022-Present Mark Baker mailto:mark@vpost.net (github @Fail-Safe)
// SPDX-FileCopyrightText: 2022-Present Nils Andreas Svee mailto:contact@lochnair.net (github @Lochnair)
//
// SPDX-License-Identifier: MPL-2.0
use crate::pinger::{PingError, PingListener, PingReply, PingSender};
use crate::settings::MeasurementType;
use crate::time::{ClockSample, Realtime};
use icmp_socket2::Icmpv4Message;
use icmp_socket2::Icmpv4Packet;
use icmp_socket2::packet::WithTimestampRequest;
use rustix::time::ClockId;
use std::net::IpAddr;
use std::time::Instant;
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(
&self,
id: u16,
reflector: IpAddr,
measurement_type: MeasurementType,
pkt: Icmpv4Packet,
) -> Result<PingReply, PingError> {
match pkt.typ {
// 14 = Timestamp reply
14 => {
if let Icmpv4Message::TimestampReply {
identifier,
sequence,
originate,
receive,
transmit,
} = pkt.message
{
if identifier != id {
return Err(PingError::WrongID {
expected: id,
found: identifier,
});
}
if receive & 0x8000_0000 != 0 || transmit & 0x8000_0000 != 0 {
return Err(PingError::InvalidPacket(
"non-standard RFC 792 timestamp".into(),
));
}
let time_since_midnight =
ClockSample::<Realtime>::now().as_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,
measurement_type,
seq: sequence,
rtt,
current_time: time_since_midnight,
down_time: dl_time,
up_time: ul_time,
originate_timestamp: originate as i64,
receive_timestamp: receive as i64,
transmit_timestamp: transmit as i64,
last_receive_time_s: Instant::now(),
})
} else {
Err(PingError::InvalidPacket(format!(
"Packet had type {:?}, but did not match the structure",
pkt.typ
)))
}
}
type_ => Err(PingError::InvalidType(format!("{:?}", type_))),
}
}
}
impl PingSender for PingerICMPTimestampSender {
fn craft_packet(&self, id: u16, seq: u16) -> (Icmpv4Packet, i64) {
let time_since_midnight = ClockSample::<Realtime>::now().as_time_since_midnight();
(
Icmpv4Packet::with_timestamp_request(id, seq, time_since_midnight as u32, 0, 0)
.unwrap(),
time_since_midnight as i64,
)
}
}
#[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(_))));
}
}