Skip to content

Commit

Permalink
Added per-source configuration of poll interval.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidv1992 committed Dec 13, 2024
1 parent 221e0b5 commit f521943
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 59 deletions.
21 changes: 17 additions & 4 deletions docs/man/ntp.toml.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,20 @@ with any of these options:
# CONFIGURATION

## `[source-defaults]`
Some values are shared between all sources in the daemon. You can configure
these in the `[source-defaults]` section.
Some of the behavior of a source is configurable. You can set defaults for those
settings in the `[source-defaults]` section.

`poll-interval-limits` = { `min` = *min*, `max` = *max* } (**{ min = 4, max = 10}**)
: Specifies the limit on how often a source is queried for a new time. For
most instances the defaults will be adequate. The min and max are given as
the log2 of the number of seconds (i.e. two to the power of the interval).
An interval of 4 equates to 32 seconds, 10 results in an interval of 1024
An interval of 4 equates to 16 seconds, 10 results in an interval of 1024
seconds. If specified, both min and max must be specified.

`initial-poll-interval` = *interval* (**4**)
: Initial poll interval used on startup. The value is given as the log2 of
the number of seconds (i.e. two to the power of the interval). The default
value of 4 results in an interval of 32 seconds.
value of 4 results in an interval of 16 seconds.

## `[[source]]`
Each `[[source]]` is a set of one or more time sources for the daemon to
Expand Down Expand Up @@ -101,6 +101,19 @@ sources.
: `pool` mode only. Specifies a list of IP addresses of servers in the pool
which should not be used. For example: `["127.0.0.1"]`. Empty by default.

`poll-interval-limits` = { `min` = *min*, `max` = *max* } (defaults from `[source-defaults]`)
: Specifies the limit on how often a source is queried for a new time. For
most instances the defaults will be adequate. The min and max are given as
the log2 of the number of seconds (i.e. two to the power of the interval).
An interval of 4 equates to 16 seconds, 10 results in an interval of 1024
seconds. If only one of the two boundaries is specified, the other is
inherited from `[source-defaults]`

`initial-poll-interval` = *interval* (defaults from `[source-defaults]`)
: Initial poll interval used on startup. The value is given as the log2 of
the number of seconds (i.e. two to the power of the interval). The default
value of 4 results in an interval of 16 seconds.

## `[[server]]`
The NTP daemon can be configured to distribute time via any number of
`[[server]]` sections. If no such sections have been defined, the daemon runs in
Expand Down
25 changes: 21 additions & 4 deletions docs/precompiled/man/ntp.toml.5
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,24 @@ a rough idea of the current time.
.SH CONFIGURATION
.SS \f[V][source-defaults]\f[R]
.PP
Some values are shared between all sources in the daemon.
You can configure these in the \f[V][source-defaults]\f[R] section.
Some of the behavior of a source is configurable.
You can set defaults for those settings in the
\f[V][source-defaults]\f[R] section.
.TP
\f[V]poll-interval-limits\f[R] = { \f[V]min\f[R] = \f[I]min\f[R], \f[V]max\f[R] = \f[I]max\f[R] } (\f[B]{ min = 4, max = 10}\f[R])
Specifies the limit on how often a source is queried for a new time.
For most instances the defaults will be adequate.
The min and max are given as the log2 of the number of seconds
(i.e.\ two to the power of the interval).
An interval of 4 equates to 32 seconds, 10 results in an interval of
An interval of 4 equates to 16 seconds, 10 results in an interval of
1024 seconds.
If specified, both min and max must be specified.
.TP
\f[V]initial-poll-interval\f[R] = \f[I]interval\f[R] (\f[B]4\f[R])
Initial poll interval used on startup.
The value is given as the log2 of the number of seconds (i.e.\ two to
the power of the interval).
The default value of 4 results in an interval of 32 seconds.
The default value of 4 results in an interval of 16 seconds.
.SS \f[V][[source]]\f[R]
.PP
Each \f[V][[source]]\f[R] is a set of one or more time sources for the
Expand Down Expand Up @@ -132,6 +133,22 @@ Specifies a list of IP addresses of servers in the pool which should not
be used.
For example: \f[V][\[dq]127.0.0.1\[dq]]\f[R].
Empty by default.
.TP
\f[V]poll-interval-limits\f[R] = { \f[V]min\f[R] = \f[I]min\f[R], \f[V]max\f[R] = \f[I]max\f[R] } (defaults from \f[V][source-defaults]\f[R])
Specifies the limit on how often a source is queried for a new time.
For most instances the defaults will be adequate.
The min and max are given as the log2 of the number of seconds
(i.e.\ two to the power of the interval).
An interval of 4 equates to 16 seconds, 10 results in an interval of
1024 seconds.
If only one of the two boundaries is specified, the other is inherited
from \f[V][source-defaults]\f[R]
.TP
\f[V]initial-poll-interval\f[R] = \f[I]interval\f[R] (defaults from \f[V][source-defaults]\f[R])
Initial poll interval used on startup.
The value is given as the log2 of the number of seconds (i.e.\ two to
the power of the interval).
The default value of 4 results in an interval of 16 seconds.
.SS \f[V][[server]]\f[R]
.PP
The NTP daemon can be configured to distribute time via any number of
Expand Down
75 changes: 49 additions & 26 deletions ntpd/src/daemon/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,9 +441,9 @@ impl Config {
match source {
NtpSourceConfig::Standard(_) => count += 1,
NtpSourceConfig::Nts(_) => count += 1,
NtpSourceConfig::Pool(config) => count += config.count,
NtpSourceConfig::Pool(config) => count += config.first.count,
#[cfg(feature = "unstable_nts-pool")]
NtpSourceConfig::NtsPool(config) => count += config.count,
NtpSourceConfig::NtsPool(config) => count += config.first.count,
NtpSourceConfig::Sock(_) => count += 1,
}
}
Expand Down Expand Up @@ -477,11 +477,19 @@ impl Config {
#[cfg(feature = "unstable_ntpv5")]
if self.sources.iter().any(|config| match config {
NtpSourceConfig::Sock(_) => false,
NtpSourceConfig::Standard(config) => matches!(config.ntp_version, Some(NtpVersion::V5)),
NtpSourceConfig::Nts(config) => matches!(config.ntp_version, Some(NtpVersion::V5)),
NtpSourceConfig::Pool(config) => matches!(config.ntp_version, Some(NtpVersion::V5)),
NtpSourceConfig::Standard(config) => {
matches!(config.first.ntp_version, Some(NtpVersion::V5))
}
NtpSourceConfig::Nts(config) => {
matches!(config.first.ntp_version, Some(NtpVersion::V5))
}
NtpSourceConfig::Pool(config) => {
matches!(config.first.ntp_version, Some(NtpVersion::V5))
}
#[cfg(feature = "unstable_nts-pool")]
NtpSourceConfig::NtsPool(config) => matches!(config.ntp_version, Some(NtpVersion::V5)),
NtpSourceConfig::NtsPool(config) => {
matches!(config.first.ntp_version, Some(NtpVersion::V5))
}
}) {
warn!("Forcing a source into NTPv5, which is still a draft. There is no guarantee that the server will remain compatible with this or future versions of ntpd-rs.");
ok = false;
Expand Down Expand Up @@ -532,10 +540,13 @@ mod tests {
toml::from_str("[[source]]\nmode = \"server\"\naddress = \"example.com\"").unwrap();
assert_eq!(
config.sources,
vec![NtpSourceConfig::Standard(StandardSource {
address: NormalizedAddress::new_unchecked("example.com", 123).into(),
#[cfg(feature = "unstable_ntpv5")]
ntp_version: None,
vec![NtpSourceConfig::Standard(FlattenedPair {
first: StandardSource {
address: NormalizedAddress::new_unchecked("example.com", 123).into(),
#[cfg(feature = "unstable_ntpv5")]
ntp_version: None,
},
second: Default::default()
})]
);
assert!(config.observability.log_level.is_none());
Expand All @@ -547,10 +558,13 @@ mod tests {
assert_eq!(config.observability.log_level, Some(LogLevel::Info));
assert_eq!(
config.sources,
vec![NtpSourceConfig::Standard(StandardSource {
address: NormalizedAddress::new_unchecked("example.com", 123).into(),
#[cfg(feature = "unstable_ntpv5")]
ntp_version: None,
vec![NtpSourceConfig::Standard(FlattenedPair {
first: StandardSource {
address: NormalizedAddress::new_unchecked("example.com", 123).into(),
#[cfg(feature = "unstable_ntpv5")]
ntp_version: None,
},
second: Default::default()
})]
);

Expand All @@ -560,10 +574,13 @@ mod tests {
.unwrap();
assert_eq!(
config.sources,
vec![NtpSourceConfig::Standard(StandardSource {
address: NormalizedAddress::new_unchecked("example.com", 123).into(),
#[cfg(feature = "unstable_ntpv5")]
ntp_version: None,
vec![NtpSourceConfig::Standard(FlattenedPair {
first: StandardSource {
address: NormalizedAddress::new_unchecked("example.com", 123).into(),
#[cfg(feature = "unstable_ntpv5")]
ntp_version: None,
},
second: Default::default()
})]
);
assert_eq!(
Expand All @@ -589,10 +606,13 @@ mod tests {
.unwrap();
assert_eq!(
config.sources,
vec![NtpSourceConfig::Standard(StandardSource {
address: NormalizedAddress::new_unchecked("example.com", 123).into(),
#[cfg(feature = "unstable_ntpv5")]
ntp_version: None,
vec![NtpSourceConfig::Standard(FlattenedPair {
first: StandardSource {
address: NormalizedAddress::new_unchecked("example.com", 123).into(),
#[cfg(feature = "unstable_ntpv5")]
ntp_version: None,
},
second: Default::default()
})]
);
assert!(config
Expand Down Expand Up @@ -633,10 +653,13 @@ mod tests {

assert_eq!(
config.sources,
vec![NtpSourceConfig::Standard(StandardSource {
address: NormalizedAddress::new_unchecked("example.com", 123).into(),
#[cfg(feature = "unstable_ntpv5")]
ntp_version: None,
vec![NtpSourceConfig::Standard(FlattenedPair {
first: StandardSource {
address: NormalizedAddress::new_unchecked("example.com", 123).into(),
#[cfg(feature = "unstable_ntpv5")]
ntp_version: None,
},
second: Default::default()
})]
);

Expand Down
105 changes: 93 additions & 12 deletions ntpd/src/daemon/config/ntp_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use std::{
};

use ntp_proto::tls_utils::Certificate;
use ntp_proto::NtpDuration;
#[cfg(feature = "unstable_ntpv5")]
use ntp_proto::NtpVersion;
use ntp_proto::{NtpDuration, PollInterval, PollIntervalLimits, SourceConfig};
use serde::{de, Deserialize, Deserializer};

use super::super::keyexchange::certificates_from_file;
Expand Down Expand Up @@ -117,18 +117,65 @@ pub struct SockSourceConfig {
pub measurement_noise_estimate: NtpDuration,
}

#[derive(Deserialize, Debug, PartialEq, Eq, Clone, Default)]
#[serde(deny_unknown_fields)]
pub struct PartialPollIntervalLimits {
pub min: Option<PollInterval>,
pub max: Option<PollInterval>,
}

#[derive(Deserialize, Debug, PartialEq, Eq, Clone, Default)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct PartialSourceConfig {
/// Minima and maxima for the poll interval of clients
#[serde(default)]
pub poll_interval_limits: PartialPollIntervalLimits,

/// Initial poll interval of the system
pub initial_poll_interval: Option<PollInterval>,
}

impl PartialSourceConfig {
pub fn with_defaults(self, defaults: SourceConfig) -> SourceConfig {
SourceConfig {
poll_interval_limits: PollIntervalLimits {
min: self
.poll_interval_limits
.min
.unwrap_or(defaults.poll_interval_limits.min),
max: self
.poll_interval_limits
.max
.unwrap_or(defaults.poll_interval_limits.max),
},
initial_poll_interval: self
.initial_poll_interval
.unwrap_or(defaults.initial_poll_interval),
}
}
}

#[derive(Deserialize, Debug, PartialEq, Eq, Clone, Default)]
#[serde(deny_unknown_fields)]
pub struct FlattenedPair<T, U> {
#[serde(flatten)]
pub first: T,
#[serde(flatten)]
pub second: U,
}

#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
#[serde(tag = "mode")]
pub enum NtpSourceConfig {
#[serde(rename = "server")]
Standard(StandardSource),
Standard(FlattenedPair<StandardSource, PartialSourceConfig>),
#[serde(rename = "nts")]
Nts(NtsSourceConfig),
Nts(FlattenedPair<NtsSourceConfig, PartialSourceConfig>),
#[serde(rename = "pool")]
Pool(PoolSourceConfig),
Pool(FlattenedPair<PoolSourceConfig, PartialSourceConfig>),
#[cfg(feature = "unstable_nts-pool")]
#[serde(rename = "nts-pool")]
NtsPool(NtsPoolSourceConfig),
NtsPool(FlattenedPair<NtsPoolSourceConfig, PartialSourceConfig>),
#[serde(rename = "sock")]
Sock(SockSourceConfig),
}
Expand Down Expand Up @@ -359,7 +406,12 @@ impl<'a> TryFrom<&'a str> for NtpSourceConfig {
type Error = std::io::Error;

fn try_from(value: &'a str) -> Result<Self, Self::Error> {
StandardSource::try_from(value).map(Self::Standard)
StandardSource::try_from(value).map(|first| {
Self::Standard(FlattenedPair {
first,
second: Default::default(),
})
})
}
}

Expand All @@ -369,11 +421,11 @@ mod tests {

fn source_addr(config: &NtpSourceConfig) -> String {
match config {
NtpSourceConfig::Standard(c) => c.address.to_string(),
NtpSourceConfig::Nts(c) => c.address.to_string(),
NtpSourceConfig::Pool(c) => c.addr.to_string(),
NtpSourceConfig::Standard(c) => c.first.address.to_string(),
NtpSourceConfig::Nts(c) => c.first.address.to_string(),
NtpSourceConfig::Pool(c) => c.first.addr.to_string(),
#[cfg(feature = "unstable_nts-pool")]
NtpSourceConfig::NtsPool(c) => c.addr.to_string(),
NtpSourceConfig::NtsPool(c) => c.first.addr.to_string(),
NtpSourceConfig::Sock(_c) => "".to_string(),
}
}
Expand Down Expand Up @@ -429,7 +481,7 @@ mod tests {
assert!(matches!(test.source, NtpSourceConfig::Pool(_)));
assert_eq!(source_addr(&test.source), "example.com:123");
if let NtpSourceConfig::Pool(config) = test.source {
assert_eq!(config.count, 4);
assert_eq!(config.first.count, 4);
}

let test: TestConfig = toml::from_str(
Expand All @@ -444,7 +496,7 @@ mod tests {
assert!(matches!(test.source, NtpSourceConfig::Pool(_)));
assert_eq!(source_addr(&test.source), "example.com:123");
if let NtpSourceConfig::Pool(config) = test.source {
assert_eq!(config.count, 42);
assert_eq!(config.first.count, 42);
}

let test: TestConfig = toml::from_str(
Expand Down Expand Up @@ -509,6 +561,35 @@ mod tests {
assert!(matches!(source, NtpSourceConfig::Standard(_)));
}

#[test]
fn test_source_config_parsing() {
#[derive(Deserialize, Debug)]
struct TestConfig {
#[allow(unused)]
source: NtpSourceConfig,
}

let test: Result<TestConfig, _> = toml::from_str(
r#"
[source]
mode = "server"
address = "example.com"
initial-poll-interval = 7
"#,
);
assert!(test.is_ok());

let test2: Result<TestConfig, _> = toml::from_str(
r#"
[source]
mode = "server"
address = "example.com"
does-not-exist = 7
"#,
);
assert!(test2.is_err());
}

#[test]
fn test_normalize_addr() {
let addr = NormalizedAddress::from_string_ntp("[::1]:456".into()).unwrap();
Expand Down
Loading

0 comments on commit f521943

Please sign in to comment.