use clap::Parser;
use ntpsec_rs_core::control_client::*;
#[derive(Parser, Debug)]
#[command(
name = "ntptrace-rs",
about = "Trace NTP synchronization chain",
version
)]
struct Cli {
#[arg(default_value = "127.0.0.1")]
host: String,
#[arg(short = 'd', long, default_value = "10")]
max_depth: u32,
#[arg(short = 't', long, default_value = "5")]
timeout: u32,
}
fn main() {
let cli = Cli::parse();
println!(
"ntptrace-rs v{} — NTP trace tool (Rust)",
env!("CARGO_PKG_VERSION")
);
println!("Tracing from: {} (max depth: {})", cli.host, cli.max_depth);
println!();
println!(
"{:>15} {:>15} {:>3} {:>10}",
"host", "refid", "st", "offset"
);
println!("{}", "-".repeat(50));
trace_host(&cli.host, &cli.host, 0, cli.max_depth, cli.timeout);
}
fn trace_host(current_host: &str, original_host: &str, depth: u32, max_depth: u32, timeout: u32) {
if depth > max_depth {
return;
}
let mut client = ControlClient::new(timeout, 1);
let sys = match client.read_system_vars(current_host, 123) {
Ok(sys) => sys,
Err(_) => {
eprintln!("ERROR: could not query {current_host}");
return;
}
};
let stratum = sys.stratum();
let refid = sys.get("refid").unwrap_or("").to_string();
let offset = sys
.get("offset")
.and_then(|v| v.parse::<f64>().ok())
.unwrap_or(0.0);
println!(
"{:>15} {:>15} {:>3} {:>10.6}",
current_host, refid, stratum, offset
);
if stratum >= 15 {
return;
}
let syspeer = match sys.get("syspeer") {
Some(peer_id) => peer_id.parse::<u16>().ok(),
None => sys.get("associd").and_then(|v| v.parse::<u16>().ok()),
};
let peer_host = match syspeer {
Some(associd) => {
match client.read_peer_vars(current_host, 123, associd) {
Ok(pv) => pv.get("srcadr").map(|s| s.to_string()),
Err(_) => None,
}
}
None => None,
};
if let Some(next_host) = peer_host {
let next_host = next_host.trim_start_matches('[').trim_end_matches(']');
let next_host = next_host.split(':').next().unwrap_or(next_host);
let next_host = next_host.to_string();
if next_host != current_host && next_host != original_host {
trace_host(&next_host, original_host, depth + 1, max_depth, timeout);
}
}
}