Skip to content

Commit

Permalink
Implementation of force-sync command, based in part on the earlier work
Browse files Browse the repository at this point in the history
by rnijveld.
  • Loading branch information
davidv1992 authored and rnijveld committed Aug 22, 2024
1 parent b482cc9 commit a4f75d1
Show file tree
Hide file tree
Showing 8 changed files with 383 additions and 7 deletions.
7 changes: 7 additions & 0 deletions docs/man/ntp-ctl.8.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ title: NTP-CTL(8) ntpd-rs 1.2.3 | ntpd-rs

`ntp-ctl` validate [`-c` *path*] \
`ntp-ctl` status [`-f` *format*] [`-c` *path*] \
`ntp-ctl` force-sync [`-c` *path*] \
`ntp-ctl` `-h` \
`ntp-ctl` `-v`

Expand Down Expand Up @@ -48,6 +49,12 @@ with the daemon.
: Returns status information about the current state of the ntp-daemon that
the client connects to.

`force-sync`
: Interactively run a single synchronization of your clock. This command can
be used to do a one-off synchronization to the time sources configured in
your configuration file. This command should never be used without any
validation by a human operator.

# SEE ALSO

[ntp-daemon(8)](ntp-daemon.8.md),
Expand Down
11 changes: 11 additions & 0 deletions docs/precompiled/man/ntp-ctl.8
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
.PD 0
.P
.PD
\f[V]ntp-ctl\f[R] force-sync [\f[V]-c\f[R] \f[I]path\f[R]]
.PD 0
.P
.PD
\f[V]ntp-ctl\f[R] \f[V]-h\f[R]
.PD 0
.P
Expand Down Expand Up @@ -69,6 +73,13 @@ Checks if the configuration specified (or
\f[V]status\f[R]
Returns status information about the current state of the ntp-daemon
that the client connects to.
.TP
\f[V]force-sync\f[R]
Interactively run a single synchronization of your clock.
This command can be used to do a one-off synchronization to the time
sources configured in your configuration file.
This command should never be used without any validation by a human
operator.
.SH SEE ALSO
.PP
ntp-daemon(8), ntp-metrics-exporter(8), ntp.toml(5)
8 changes: 4 additions & 4 deletions ntp-proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ mod exports {
ServerResponse, ServerStatHandler, SubnetParseError,
};
#[cfg(feature = "__internal-test")]
pub use super::source::{source_snapshot, Measurement};
pub use super::source::source_snapshot;
pub use super::source::{
AcceptSynchronizationError, NtpSource, NtpSourceAction, NtpSourceActionIterator,
NtpSourceSnapshot, NtpSourceUpdate, ObservableSourceState, ProtocolVersion, Reach,
SourceNtsData,
AcceptSynchronizationError, Measurement, NtpSource, NtpSourceAction,
NtpSourceActionIterator, NtpSourceSnapshot, NtpSourceUpdate, ObservableSourceState,
ProtocolVersion, Reach, SourceNtsData,
};
pub use super::system::{
System, SystemAction, SystemActionIterator, SystemSnapshot, SystemSourceUpdate,
Expand Down
3 changes: 1 addition & 2 deletions ntp-proto/src/time_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,8 +556,7 @@ impl std::fmt::Debug for PollInterval {
}

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

#[cfg(test)]
pub fn test_new(value: i8) -> Self {
Expand Down
14 changes: 13 additions & 1 deletion ntpd/src/ctl.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use std::{path::PathBuf, process::ExitCode};

use crate::daemon::{config::CliArg, tracing::LogLevel, Config, ObservableState};
use crate::{
daemon::{config::CliArg, tracing::LogLevel, Config, ObservableState},
force_sync,
};
use tracing_subscriber::util::SubscriberInitExt;

const USAGE_MSG: &str = "\
usage: ntp-ctl validate [-c PATH]
ntp-ctl status [-f FORMAT] [-c PATH]
ntp-ctl force-sync [-c PATH]
ntp-ctl -h | ntp-ctl -v";

const DESCRIPTOR: &str = "ntp-ctl - ntp-daemon monitoring";
Expand Down Expand Up @@ -34,6 +38,7 @@ pub enum NtpCtlAction {
Version,
Validate,
Status,
ForceSync,
}

#[derive(Debug, Default)]
Expand All @@ -44,6 +49,7 @@ pub(crate) struct NtpCtlOptions {
version: bool,
validate: bool,
status: bool,
force_sync: bool,
action: NtpCtlAction,
}

Expand Down Expand Up @@ -104,6 +110,9 @@ impl NtpCtlOptions {
"status" => {
options.status = true;
}
"force-sync" => {
options.force_sync = true;
}
unknown => {
eprintln!("Warning: Unknown command {unknown}");
}
Expand All @@ -129,6 +138,8 @@ impl NtpCtlOptions {
self.action = NtpCtlAction::Validate;
} else if self.status {
self.action = NtpCtlAction::Status;
} else if self.force_sync {
self.action = NtpCtlAction::ForceSync;
} else {
self.action = NtpCtlAction::Help;
}
Expand Down Expand Up @@ -172,6 +183,7 @@ pub async fn main() -> std::io::Result<ExitCode> {
Ok(ExitCode::SUCCESS)
}
NtpCtlAction::Validate => validate(options.config).await,
NtpCtlAction::ForceSync => force_sync::force_sync(options.config).await,
NtpCtlAction::Status => {
let config = Config::from_args(options.config, vec![], vec![]).await;

Expand Down
180 changes: 180 additions & 0 deletions ntpd/src/force_sync/algorithm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use std::collections::HashMap;

use ntp_proto::{
Measurement, NtpClock, NtpDuration, PollInterval, SourceController, TimeSyncController,
};
use serde::Deserialize;

use crate::daemon::spawn::SourceId;

pub(crate) struct SingleShotController<C> {
pub(super) clock: C,
sources: HashMap<SourceId, Measurement>,
min_poll_interval: PollInterval,
min_agreeing: usize,
}

#[derive(Debug, Copy, Clone, Deserialize)]
pub(crate) struct SingleShotControllerConfig {
pub expected_sources: usize,
}

pub(crate) struct SingleShotSourceController {
min_poll_interval: PollInterval,
done: bool,
}

#[derive(Debug, Copy, Clone)]
pub(crate) enum SingleShotControllerMessage {}

impl<C: NtpClock> SingleShotController<C> {
const ASSUMED_UNCERTAINTY: NtpDuration = NtpDuration::from_exponent(-1);

fn try_steer(&self) {
if self.sources.len() < self.min_agreeing {
return;
}

struct Event {
offset: NtpDuration,
count: isize,
}
let mut events: Vec<_> = self
.sources
.values()
.flat_map(|m| {
[
Event {
offset: m.offset - Self::ASSUMED_UNCERTAINTY,
count: 1,
},
Event {
offset: m.offset + Self::ASSUMED_UNCERTAINTY,
count: -1,
},
]
.into_iter()
})
.collect();
events.sort_by(|a, b| a.offset.cmp(&b.offset));

let mut peak = 0;
let mut peak_offset = events[0].offset;
let mut cur = 0;
for ev in events {
cur += ev.count;
if cur > peak {
peak = cur;
peak_offset = ev.offset;
}
}

if peak as usize >= self.min_agreeing {
let mut sum = 0.0;
let mut count = 0;
for source in self.sources.values() {
if source.offset.abs_diff(peak_offset) < Self::ASSUMED_UNCERTAINTY {
count += 1;
sum += source.offset.to_seconds()
}
}

let avg_offset = NtpDuration::from_seconds(sum / (count as f64));
self.offer_clock_change(avg_offset);

std::process::exit(0);
}
}
}

impl<C: NtpClock> TimeSyncController for SingleShotController<C> {
type Clock = C;
type SourceId = SourceId;
type AlgorithmConfig = SingleShotControllerConfig;
type ControllerMessage = SingleShotControllerMessage;
type SourceMessage = Measurement;
type SourceController = SingleShotSourceController;

fn new(
clock: Self::Clock,
synchronization_config: ntp_proto::SynchronizationConfig,
source_defaults_config: ntp_proto::SourceDefaultsConfig,
algorithm_config: Self::AlgorithmConfig,
) -> Result<Self, <Self::Clock as ntp_proto::NtpClock>::Error> {
Ok(SingleShotController {
clock,
sources: HashMap::new(),
min_poll_interval: source_defaults_config.poll_interval_limits.min,
min_agreeing: synchronization_config
.minimum_agreeing_sources
.max(algorithm_config.expected_sources / 2),
})
}

fn take_control(&mut self) -> Result<(), <Self::Clock as ntp_proto::NtpClock>::Error> {
//no need for actions
Ok(())
}

fn add_source(&mut self, _id: Self::SourceId) -> Self::SourceController {
SingleShotSourceController {
min_poll_interval: self.min_poll_interval,
done: false,
}
}

fn remove_source(&mut self, id: Self::SourceId) {
self.sources.remove(&id);
}

fn source_update(&mut self, id: Self::SourceId, usable: bool) {
if !usable {
self.sources.remove(&id);
}
}

fn source_message(
&mut self,
id: Self::SourceId,
message: Self::SourceMessage,
) -> ntp_proto::StateUpdate<Self::SourceId, Self::ControllerMessage> {
self.sources.insert(id, message);
// TODO, check and update time once we have sufficient sources
self.try_steer();
Default::default()
}

fn time_update(&mut self) -> ntp_proto::StateUpdate<Self::SourceId, Self::ControllerMessage> {
// no need for action
Default::default()
}
}

impl SourceController for SingleShotSourceController {
type ControllerMessage = SingleShotControllerMessage;
type SourceMessage = Measurement;

fn handle_message(&mut self, _message: Self::ControllerMessage) {
//ignore
}

fn handle_measurement(
&mut self,
measurement: ntp_proto::Measurement,
) -> Option<Self::SourceMessage> {
self.done = true;
Some(measurement)
}

fn desired_poll_interval(&self) -> ntp_proto::PollInterval {
if self.done {
PollInterval::NEVER
} else {
self.min_poll_interval
}
}

fn observe(&self) -> ntp_proto::ObservableSourceTimedata {
ntp_proto::ObservableSourceTimedata::default()
}
}
Loading

0 comments on commit a4f75d1

Please sign in to comment.