Skip to content

Commit

Permalink
Updates to bring ntpv5 in line with draft 2.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidv1992 authored and rnijveld committed Jun 26, 2024
1 parent a2fd965 commit 1ab7b9b
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 45 deletions.
2 changes: 1 addition & 1 deletion ntp-proto/src/packet/extension_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ impl<'a> ExtensionField<'a> {
) -> std::io::Result<()> {
Self::encode_framing(
&mut w,
ExtensionFieldTypeId::DraftIdentification,
ExtensionFieldTypeId::Padding,
length - Self::HEADER_LENGTH,
minimum_size,
version,
Expand Down
72 changes: 43 additions & 29 deletions ntp-proto/src/packet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,15 +587,14 @@ impl<'a> NtpPacket<'a> {
let (mut header, id) = NtpHeaderV3V4::poll_message(poll_interval);

header.reference_timestamp = v5::UPGRADE_TIMESTAMP;
let draft_id = ExtensionField::DraftIdentification(Cow::Borrowed(v5::DRAFT_VERSION));

(
NtpPacket {
header: NtpHeader::V4(header),
efdata: ExtensionFieldData {
authenticated: vec![],
encrypted: vec![],
untrusted: vec![draft_id],
untrusted: vec![],
},
mac: None,
},
Expand Down Expand Up @@ -644,19 +643,13 @@ impl<'a> NtpPacket<'a> {
NtpHeader::V4(header) => {
let mut response_header =
NtpHeaderV3V4::timestamp_response(system, *header, recv_timestamp, clock);
let mut extra_ef = None;

#[cfg(feature = "ntpv5")]
{
// Respond with the upgrade timestamp (NTP5NTP5) iff the input had it and the packet
// had the correct draft identification
if let (v5::UPGRADE_TIMESTAMP, Some(v5::DRAFT_VERSION)) =
(header.reference_timestamp, input.draft_id())
{
if header.reference_timestamp == v5::UPGRADE_TIMESTAMP {
response_header.reference_timestamp = v5::UPGRADE_TIMESTAMP;
extra_ef = Some(ExtensionField::DraftIdentification(Cow::Borrowed(
v5::DRAFT_VERSION,
)));
};
}

Expand All @@ -672,7 +665,6 @@ impl<'a> NtpPacket<'a> {
.into_iter()
.chain(input.efdata.authenticated)
.filter(|ef| matches!(ef, ExtensionField::UniqueIdentifier(_)))
.chain(extra_ef)
.collect(),
},
mac: None,
Expand Down Expand Up @@ -1179,37 +1171,59 @@ impl<'a> NtpPacket<'a> {
NtpHeader::V3(header) => header.stratum == 0,
NtpHeader::V4(header) => header.stratum == 0,
#[cfg(feature = "ntpv5")]
NtpHeader::V5(header) => header.flags.status_message,
NtpHeader::V5(header) => header.stratum == 0,
}
}

pub fn is_kiss_deny(&self) -> bool {
self.is_kiss() && self.kiss_code().is_deny()
self.is_kiss()
&& match self.header {
NtpHeader::V3(_) | NtpHeader::V4(_) => self.kiss_code().is_deny(),
#[cfg(feature = "ntpv5")]
NtpHeader::V5(header) => header.poll == PollInterval::NEVER,
}
}

pub fn is_kiss_rate(&self) -> bool {
self.is_kiss() && self.kiss_code().is_rate()
pub fn is_kiss_rate(
&self,
#[cfg_attr(not(feature = "ntpv5"), allow(unused))] own_interval: PollInterval,
) -> bool {
self.is_kiss()
&& match self.header {
NtpHeader::V3(_) | NtpHeader::V4(_) => self.kiss_code().is_rate(),
#[cfg(feature = "ntpv5")]
NtpHeader::V5(header) => {
header.poll > own_interval && header.poll != PollInterval::NEVER
}
}
}

pub fn is_kiss_rstr(&self) -> bool {
self.is_kiss() && self.kiss_code().is_rstr()
self.is_kiss()
&& match self.header {
NtpHeader::V3(_) | NtpHeader::V4(_) => self.kiss_code().is_rstr(),
#[cfg(feature = "ntpv5")]
NtpHeader::V5(_) => false,
}
}

pub fn is_kiss_ntsn(&self) -> bool {
self.is_kiss() && self.kiss_code().is_ntsn()
self.is_kiss()
&& match self.header {
NtpHeader::V3(_) | NtpHeader::V4(_) => self.kiss_code().is_ntsn(),
#[cfg(feature = "ntpv5")]
NtpHeader::V5(header) => header.flags.authnak,
}
}

#[cfg(feature = "ntpv5")]
pub fn is_upgrade(&self) -> bool {
matches!(
(self.header, self.draft_id()),
(
NtpHeader::V4(NtpHeaderV3V4 {
reference_timestamp: v5::UPGRADE_TIMESTAMP,
..
}),
Some(v5::DRAFT_VERSION),
)
self.header,
NtpHeader::V4(NtpHeaderV3V4 {
reference_timestamp: v5::UPGRADE_TIMESTAMP,
..
}),
)
}

Expand Down Expand Up @@ -1806,7 +1820,7 @@ mod tests {

assert_eq!(
header.reference_timestamp,
NtpTimestamp::from_fixed_int(0x4E5450354E545035)
NtpTimestamp::from_fixed_int(0x4E54503544524654)
);
}

Expand Down Expand Up @@ -2239,7 +2253,7 @@ mod tests {
.unwrap();
assert_eq!(packet_id, response_id);
assert_eq!(response.new_cookies().count(), 0);
assert!(response.is_kiss_rate());
assert!(response.is_kiss_rate(PollIntervalLimits::default().min));

let (mut packet, _) =
NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
Expand Down Expand Up @@ -2274,7 +2288,7 @@ mod tests {
.unwrap();
assert_eq!(packet_id, response_id);
assert_eq!(response.new_cookies().count(), 0);
assert!(response.is_kiss_rate());
assert!(response.is_kiss_rate(PollIntervalLimits::default().min));

let (packet, _) =
NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
Expand Down Expand Up @@ -2305,7 +2319,7 @@ mod tests {
.unwrap();
assert_eq!(packet_id, response_id);
assert_eq!(response.new_cookies().count(), 0);
assert!(response.is_kiss_rate());
assert!(response.is_kiss_rate(PollIntervalLimits::default().min));

let (mut packet, _) =
NtpPacket::nts_poll_message(&cookie, 1, PollIntervalLimits::default().min);
Expand All @@ -2327,7 +2341,7 @@ mod tests {
})
.is_none());
assert_eq!(response.new_cookies().count(), 0);
assert!(response.is_kiss_rate());
assert!(response.is_kiss_rate(PollIntervalLimits::default().min));
}

#[test]
Expand Down
39 changes: 25 additions & 14 deletions ntp-proto/src/packet/v5/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ pub use error::V5Error;
use super::RequestIdentifier;

#[allow(dead_code)]
pub(crate) const DRAFT_VERSION: &str = "draft-ietf-ntp-ntpv5-01";
pub(crate) const UPGRADE_TIMESTAMP: NtpTimestamp = NtpTimestamp::from_bits(*b"NTP5NTP5");
pub(crate) const DRAFT_VERSION: &str = "draft-ietf-ntp-ntpv5-02";
pub(crate) const UPGRADE_TIMESTAMP: NtpTimestamp = NtpTimestamp::from_bits(*b"NTP5DRFT");

#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
Expand Down Expand Up @@ -82,7 +82,7 @@ pub struct NtpEra(pub u8);
pub struct NtpFlags {
pub unknown_leap: bool,
pub interleaved_mode: bool,
pub status_message: bool,
pub authnak: bool,
}

impl NtpFlags {
Expand All @@ -94,7 +94,7 @@ impl NtpFlags {
Ok(Self {
unknown_leap: bits[1] & 0b01 != 0,
interleaved_mode: bits[1] & 0b10 != 0,
status_message: bits[1] & 0b100 != 0,
authnak: bits[1] & 0b100 != 0,
})
}

Expand All @@ -109,7 +109,7 @@ impl NtpFlags {
flags |= 0b10;
}

if self.status_message {
if self.authnak {
flags |= 0b100;
}

Expand Down Expand Up @@ -182,7 +182,7 @@ impl NtpHeaderV5 {
flags: NtpFlags {
unknown_leap: false,
interleaved_mode: false,
status_message: false,
authnak: false,
},
server_cookie: NtpServerCookie([0; 8]),
client_cookie: NtpClientCookie([0; 8]),
Expand Down Expand Up @@ -210,7 +210,7 @@ impl NtpHeaderV5 {
flags: NtpFlags {
unknown_leap: false,
interleaved_mode: false,
status_message: false,
authnak: false,
},
root_delay: system.time_snapshot.root_delay,
root_dispersion: system.time_snapshot.root_dispersion,
Expand All @@ -221,33 +221,44 @@ impl NtpHeaderV5 {
}
}

fn kiss_response(packet_from_client: Self, code: [u8; 4]) -> Self {
fn kiss_response(packet_from_client: Self) -> Self {
Self {
mode: NtpMode::Response,
flags: NtpFlags {
unknown_leap: false,
interleaved_mode: false,
status_message: true,
authnak: false,
},
server_cookie: NtpServerCookie([code[0], code[1], code[2], code[3], 0, 0, 0, 0]),
server_cookie: NtpServerCookie::new_random(),
client_cookie: packet_from_client.client_cookie,
stratum: 0,
..Self::new()
}
}

pub(crate) fn rate_limit_response(packet_from_client: Self) -> Self {
Self {
poll: packet_from_client.poll.force_inc(),
..Self::kiss_response(packet_from_client, *b"RATE")
..Self::kiss_response(packet_from_client)
}
}

pub(crate) fn deny_response(packet_from_client: Self) -> Self {
Self::kiss_response(packet_from_client, *b"DENY")
Self {
poll: PollInterval::NEVER,
..Self::kiss_response(packet_from_client)
}
}

pub(crate) fn nts_nak_response(packet_from_client: Self) -> Self {
Self::kiss_response(packet_from_client, *b"NTSN")
Self {
flags: NtpFlags {
unknown_leap: false,
interleaved_mode: false,
authnak: true,
},
..Self::kiss_response(packet_from_client)
}
}

const WIRE_LENGTH: usize = 48;
Expand Down Expand Up @@ -510,7 +521,7 @@ mod tests {
flags: NtpFlags {
unknown_leap: i % 3 == 0,
interleaved_mode: i % 4 == 0,
status_message: i % 5 == 0,
authnak: i % 5 == 0,
},
root_delay: NtpDuration::from_bits_time32([i; 4]),
root_dispersion: NtpDuration::from_bits_time32([i.wrapping_add(1); 4]),
Expand Down
2 changes: 1 addition & 1 deletion ntp-proto/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ impl<Controller: SourceController> NtpSource<Controller> {
// to denial of service attacks.
debug!("Received old/unexpected packet from source");
actions!()
} else if message.is_kiss_rate() {
} else if message.is_kiss_rate(self.last_poll_interval) {
// KISS packets may not have correct timestamps at all, handle them anyway
self.remote_min_poll_interval = Ord::max(
self.remote_min_poll_interval
Expand Down
3 changes: 3 additions & 0 deletions ntp-proto/src/time_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,9 @@ impl std::fmt::Debug for PollInterval {
}

impl PollInterval {
#[cfg(feature = "ntpv5")]
pub(crate) const NEVER: PollInterval = PollInterval(i8::MAX);

#[cfg(test)]
pub fn test_new(value: i8) -> Self {
Self(value)
Expand Down

0 comments on commit 1ab7b9b

Please sign in to comment.