use std::io;
use std::sync::{Arc, RwLock};
use tracing::debug;
use crate::error::{ConfigError, NtpServerError};
use crate::server_common::{
ClientTable, ConfigHandle, HandleResult, ServerMetrics, ServerSystemState, handle_request,
};
crate::server_common::define_server_builder! {
extra_fields {}
extra_defaults {}
}
impl NtpServerBuilder {
pub async fn build(self) -> io::Result<NtpServer> {
let cfg = self.into_config();
#[cfg(feature = "socket-opts")]
let sock = {
let addr: std::net::SocketAddr = cfg.listen_addr.parse().map_err(|e| -> io::Error {
NtpServerError::Config(ConfigError::InvalidListenAddress {
address: cfg.listen_addr.clone(),
detail: format!("socket-opts requires IP:port: {e}"),
})
.into()
})?;
let std_sock = cfg.socket_opts.bind_udp(addr)?;
smol::Async::new(std_sock)?.into()
};
#[cfg(not(feature = "socket-opts"))]
let sock = {
let _ = cfg.socket_opts;
smol::net::UdpSocket::bind(&cfg.listen_addr).await?
};
debug!("NTP server listening on {}", cfg.listen_addr);
Ok(NtpServer {
sock,
system_state: Arc::new(RwLock::new(cfg.system_state)),
config: Arc::new(RwLock::new(cfg.server_config)),
client_table: ClientTable::new(cfg.max_clients),
metrics: cfg.metrics,
})
}
}
pub struct NtpServer {
sock: smol::net::UdpSocket,
system_state: Arc<RwLock<ServerSystemState>>,
config: Arc<RwLock<crate::server_common::ServerConfig>>,
client_table: ClientTable,
metrics: Option<Arc<ServerMetrics>>,
}
impl NtpServer {
pub fn builder() -> NtpServerBuilder {
NtpServerBuilder::new()
}
pub fn system_state(&self) -> &Arc<RwLock<ServerSystemState>> {
&self.system_state
}
pub fn config_handle(&self) -> ConfigHandle {
ConfigHandle::new(self.config.clone())
}
pub fn metrics(&self) -> Option<&Arc<ServerMetrics>> {
self.metrics.as_ref()
}
pub fn local_addr(&self) -> io::Result<std::net::SocketAddr> {
self.sock.local_addr()
}
pub async fn run(mut self) -> io::Result<()> {
let mut recv_buf = [0u8; 2048];
loop {
let (recv_len, src_addr) = self.sock.recv_from(&mut recv_buf).await?;
let server_state = self
.system_state
.read()
.map_err(|_| io::Error::other("system state lock poisoned"))?
.clone();
let result = {
let config = self
.config
.read()
.map_err(|_| io::Error::other("config lock poisoned"))?;
handle_request(
&recv_buf,
recv_len,
src_addr.ip(),
&server_state,
&config.access_control,
config.rate_limit.as_ref(),
&mut self.client_table,
config.enable_interleaved,
self.metrics.as_deref(),
)
};
if let Some(m) = &self.metrics {
m.set_active_clients(self.client_table.len() as u64);
}
match result {
HandleResult::Response(resp_buf) => {
let _ = self.sock.send_to(&resp_buf, src_addr).await;
}
#[cfg(feature = "ntpv5")]
HandleResult::V5Response(resp_buf) => {
let _ = self.sock.send_to(&resp_buf, src_addr).await;
}
HandleResult::Drop => {
debug!("dropped packet from {}", src_addr);
}
}
}
}
}