use std::net::SocketAddr;
use crate::daemon::ClockSyncAlgorithm;
use crate::daemon::ReceiverStream;
use crate::daemon::async_ring_buffer;
use crate::daemon::autodetect::{self, Autodetect};
use crate::daemon::clock_sync_algorithm::source;
use crate::daemon::io::SourceIO;
use crate::daemon::time::tsc::Skew;
use crate::vmclock::shm::VMCLOCK_SHM_DEFAULT_PATH;
use tracing::info;
pub struct SourceMutator<'a> {
io_front_end: &'a mut SourceIO,
clock_sync_algorithm: &'a mut ClockSyncAlgorithm,
receiver_stream: &'a mut ReceiverStream,
}
impl<'a> SourceMutator<'a> {
pub fn new(
io_front_end: &'a mut SourceIO,
clock_sync_algorithm: &'a mut ClockSyncAlgorithm,
receiver_stream: &'a mut ReceiverStream,
) -> Self {
Self {
io_front_end,
clock_sync_algorithm,
receiver_stream,
}
}
pub async fn init_from_autodetect_results(
&mut self,
autodetect: &Autodetect,
max_dispersion: Skew,
) {
match autodetect {
Autodetect::Amazon(amazon) => {
match amazon {
autodetect::Amazon::Nitro(instance_type) => info!(
"Amazon EC2 Nitro instance detected (instance type: {instance_type}). \
Installing Amazon-specific sources."
),
autodetect::Amazon::Xen => info!(
"Amazon EC2 Xen instance detected. Installing Amazon-specific sources."
),
}
self.init_amazon_sources(amazon, max_dispersion).await;
}
Autodetect::Other => {
info!("Non-Amazon platform detected. Skipping Amazon-specific sources.");
}
}
}
async fn init_amazon_sources(&mut self, amazon: &autodetect::Amazon, max_dispersion: Skew) {
self.set_amazon_time_sync(max_dispersion);
self.set_phc(max_dispersion).await;
if amazon.is_metal() {
info!("EC2 metal instance detected. VMClock not enabled.");
} else {
self.io_front_end
.create_vmclock(VMCLOCK_SHM_DEFAULT_PATH)
.await;
}
}
pub fn set_amazon_time_sync(&mut self, max_dispersion: Skew) {
self.clock_sync_algorithm
.set_amazon_time_sync(source::AmazonTimeSync::new(max_dispersion));
let (tx, rx) = async_ring_buffer::create(2);
self.receiver_stream.set_amazon_time_sync(rx);
self.io_front_end.create_amazon_time_sync(tx);
}
pub async fn set_phc(&mut self, max_dispersion_growth: Skew) {
let (tx, rx) = async_ring_buffer::create(2);
self.io_front_end.create_phc(tx).await;
let Some(phc) = self.io_front_end.phc() else {
tracing::info!(
"PHC device was not initialized. Not installing into clock sync algorithm."
);
return;
};
let phc_path = source::DevicePath::from(phc.device_path());
self.receiver_stream.set_phc(phc_path.clone(), rx);
self.clock_sync_algorithm
.set_phc(source::Phc::new(phc_path, max_dispersion_growth));
}
pub fn add_ntp_source(&mut self, socket_addr: SocketAddr, max_dispersion: Skew) {
self.clock_sync_algorithm
.add_ntp_source(source::NtpSource::new_ip_addr_source(
socket_addr,
max_dispersion,
));
let (tx, rx) = async_ring_buffer::create(2);
self.receiver_stream.add_ntp_source((socket_addr, rx));
self.io_front_end.create_ip_addr_source((socket_addr, tx));
}
pub fn add_pool(&mut self, pool_domain: String) {
const MAX_SOURCES: usize = 5;
self.io_front_end.create_pool(pool_domain, MAX_SOURCES);
}
pub fn add_domain_host(&mut self, domain: String) {
const MAX_SOURCES: usize = 1;
self.io_front_end.create_pool(domain, MAX_SOURCES);
}
pub fn add_pool_source(
&mut self,
pool_domain: &str,
socket_addr: SocketAddr,
max_dispersion: Skew,
) {
let source =
source::NtpSource::new_with_pool(socket_addr, pool_domain.to_owned(), max_dispersion);
self.clock_sync_algorithm.add_ntp_source(source);
let (tx, rx) = async_ring_buffer::create(2);
self.receiver_stream.add_ntp_source((socket_addr, rx));
self.io_front_end
.add_pool_source(pool_domain, socket_addr, tx);
}
pub async fn remove_pool_source(&mut self, pool_domain: &str, socket_addr: SocketAddr) {
self.clock_sync_algorithm.remove_ntp_source(&socket_addr);
self.receiver_stream.remove_ntp_source(&socket_addr);
self.io_front_end
.remove_pool_source(pool_domain, &socket_addr)
.await;
}
}
#[cfg(test)]
mod tests {
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::mpsc;
use crate::daemon::ClockSyncAlgorithm;
use crate::daemon::clock_sync_algorithm::Selector;
use crate::daemon::io::SourceIO;
use crate::daemon::io::ntp::DaemonInfo;
use crate::daemon::message::Dns as DnsMessage;
use crate::daemon::receiver_stream::ReceiverStream;
use crate::daemon::selected_clock::SelectedClockSource;
use crate::daemon::time::tsc::Skew;
use super::SourceMutator;
fn setup() -> (SourceIO, ClockSyncAlgorithm, ReceiverStream) {
let selected_clock = Arc::new(SelectedClockSource::default());
let daemon_info = DaemonInfo {
major_version: 2,
minor_version: 100,
startup_id: 0xDEAD_BEEF_CAFE_BABE,
};
let (dns_message_tx, _dns_message_rx) = mpsc::channel::<DnsMessage>(1);
let io = SourceIO::construct(selected_clock.clone(), daemon_info, dns_message_tx);
let csa = ClockSyncAlgorithm::builder()
.selected_clock(selected_clock)
.selector(Selector::new(Skew::from_ppm(15.0)))
.build();
let receiver_stream = ReceiverStream::default();
(io, csa, receiver_stream)
}
#[tokio::test]
async fn set_amazon_time_sync_populates_all_components() {
let (mut io, mut csa, mut rs) = setup();
assert!(csa.amazon_time_sync().is_none());
assert!(rs.amazon_time_sync().is_none());
let mut mutator = SourceMutator::new(&mut io, &mut csa, &mut rs);
mutator.set_amazon_time_sync(Skew::from_ppm(15.0));
assert!(csa.amazon_time_sync().is_some());
assert!(rs.amazon_time_sync().is_some());
}
#[tokio::test]
async fn add_ntp_source_populates_all_components() {
let (mut io, mut csa, mut rs) = setup();
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)), 123);
assert!(csa.ntp_sources().is_empty());
let mut mutator = SourceMutator::new(&mut io, &mut csa, &mut rs);
mutator.add_ntp_source(addr, Skew::from_ppm(15.0));
assert_eq!(csa.ntp_sources().len(), 1);
assert_eq!(csa.ntp_sources()[0].socket_address(), addr);
}
#[tokio::test]
async fn add_multiple_ntp_sources() {
let (mut io, mut csa, mut rs) = setup();
let addr1 = SocketAddr::from_str("192.0.1.1:123").unwrap();
let addr2 = SocketAddr::from_str("192.0.2.2:123").unwrap();
{
let mut mutator = SourceMutator::new(&mut io, &mut csa, &mut rs);
mutator.add_ntp_source(addr1, Skew::from_ppm(15.0));
mutator.add_ntp_source(addr2, Skew::from_ppm(10.0));
}
assert_eq!(csa.ntp_sources().len(), 2);
assert_eq!(csa.ntp_sources()[0].socket_address(), addr1);
assert_eq!(csa.ntp_sources()[1].socket_address(), addr2);
}
#[test]
fn add_pool_populates_io() {
let (mut io, mut csa, mut rs) = setup();
let pool_domain = "pool.ntp.org".to_string();
let mut mutator = SourceMutator::new(&mut io, &mut csa, &mut rs);
mutator.add_pool(pool_domain.clone());
assert!(io.pools().contains_key(&pool_domain));
}
}