ntp_usg-client 3.3.3

NTP client library with sync, async (tokio/smol), and NTS support.
Documentation

ntp_usg

docs.rs docs.rs docs.rs Crates.io Crates.io Crates.io License GitHub Actions Workflow Status GitHub Issues or Pull Requests GitHub Issues or Pull Requests Codecov

A Network Time Protocol (NTP) library written in Rust, organized as a Cargo workspace with three crates:

Crate Lib name Description
ntp_usg-proto ntp_proto Protocol types, extension fields, and NTS cryptographic primitives
ntp_usg-client ntp_client NTP client (sync, async tokio/smol, NTS, clock adjustment)
ntp_usg-server ntp_server NTP server (tokio/smol, NTS-KE)

Features

🎯 Version 3.1.0 - 100% RFC Compliance

  • RFC 5905 Full Compliance: Selection, clustering, clock discipline (PLL/FLL), symmetric modes, and broadcast mode
  • RFC 4330 SNTP API: Simplified client API for one-off time queries
  • RFC 7822 Extension Registry: Generic dispatch system for extension field handlers
  • Security: Eliminated unmaintained rustls-pemfile dependency (RUSTSEC-2025-0134)

Core Features

  • 🔒 Safe & Secure: #![deny(unsafe_code)] crate-wide; only platform FFI in the optional clock module uses unsafe
  • 📚 Well Documented: Comprehensive API documentation with examples
  • Configurable Timeouts: Control request timeouts for different network conditions
  • 🔄 Async Ready: Optional async support via Tokio or smol
  • 🕐 Y2036 Safe: Era-aware timestamp handling for the NTP 32-bit rollover
  • 🌍 Multi-Server Support: Query multiple NTP servers for improved reliability
  • 🔐 Network Time Security: NTS (RFC 8915) with TLS 1.3 key establishment and AEAD authentication
  • 📡 Continuous Client: Adaptive poll interval, multi-peer selection, and interleaved mode (RFC 9769)
  • 🌐 IPv6 Dual-Stack: Automatic IPv4/IPv6 socket binding
  • 🧩 no_std Support: Core protocol parsing works without std or alloc
  • ⏱️ Clock Adjustment: Platform-native slew/step correction (Linux, macOS, Windows)
  • 📡 NTP Server: Full NTPv4 server with rate limiting, access control, and interleaved mode
  • 🦀 Modern Rust: Edition 2024 with MSRV 1.93
  • Well Tested: 290+ tests, CI/CD on Linux, macOS, and Windows

Installation

Add the crate(s) you need to your Cargo.toml:

[dependencies]
# Protocol types only (also supports no_std)
ntp_usg-proto = "3.1"

# NTP client
ntp_usg-client = { version = "3.1", features = ["tokio"] }

# NTP server
ntp_usg-server = { version = "3.1", features = ["tokio"] }

Minimum Supported Rust Version (MSRV): 1.93 Edition: 2024

Feature Flags

ntp_usg-proto

Feature Default Description
std Yes Full I/O and byteorder-based APIs
alloc No Vec-based extension field types without full std
nts No NTS cryptographic primitives (AEAD, cookie handling)

ntp_usg-client

Feature Default Description
tokio No Async NTP client using Tokio
smol-runtime No Async NTP client using smol
nts No NTS authentication (Tokio + rustls)
nts-smol No NTS authentication (smol + futures-rustls)
clock No System clock slew/step adjustment (Linux, macOS, Windows)

ntp_usg-server

Feature Default Description
tokio No NTP server using Tokio
smol-runtime No NTP server using smol
nts No NTS-KE server (Tokio + rustls)
nts-smol No NTS-KE server (smol + futures-rustls)

For no_std environments, use the proto crate with default features disabled:

[dependencies]
ntp_usg-proto = { version = "3.1", default-features = false }          # core parsing only
ntp_usg-proto = { version = "3.1", default-features = false, features = ["alloc"] }  # + Vec-based types

Usage

SNTP (Simple Network Time Protocol)

For simple, one-off time queries, use the SNTP API (RFC 4330 compliant):

use ntp_client::sntp;

fn main() -> std::io::Result<()> {
    let result = sntp::request("time.nist.gov:123")?;
    println!("Clock offset: {:.6} seconds", result.offset_seconds);
    println!("Round-trip delay: {:.6} seconds", result.delay_seconds);
    Ok(())
}

With async:

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let result = sntp::async_request("time.cloudflare.com:123").await?;
    println!("Offset: {:.6}s", result.offset_seconds);
    Ok(())
}

Basic Example (Full NTP)

use chrono::TimeZone;

fn main() {
    let address = "time.nist.gov:123";
    let response = ntp_client::request(address).unwrap();
    let unix_time = ntp_client::unix_time::Instant::from(response.transmit_timestamp);
    let local_time = chrono::Local
        .timestamp_opt(unix_time.secs(), unix_time.subsec_nanos() as _)
        .unwrap();
    println!("Current time: {}", local_time);
}

Custom Timeout

use std::time::Duration;

let response = ntp_client::request_with_timeout("time.nist.gov:123", Duration::from_secs(10))?;

Async with Tokio

Enable the tokio feature:

[dependencies]
ntp_usg-client = { version = "3.1", features = ["tokio"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let result = ntp_client::async_ntp::request("time.nist.gov:123").await?;
    println!("Offset: {:.6} seconds", result.offset_seconds);
    Ok(())
}

Continuous Client

The continuous client polls servers with adaptive intervals and supports interleaved mode (RFC 9769):

use ntp_client::client::NtpClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (client, mut state_rx) = NtpClient::builder()
        .server("time.nist.gov:123")
        .min_poll(4)
        .max_poll(10)
        .build()
        .await?;

    tokio::spawn(client.run());

    // Wait for sync state updates.
    while state_rx.changed().await.is_ok() {
        let state = state_rx.borrow();
        println!("Offset: {:.6}s, Delay: {:.6}s", state.offset, state.delay);
    }
    Ok(())
}

NTS (Network Time Security)

Enable the nts feature for authenticated NTP:

[dependencies]
ntp_usg-client = { version = "3.1", features = ["nts"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
use ntp_client::nts::NtsSession;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut session = NtsSession::from_ke("time.cloudflare.com").await?;
    let result = session.request().await?;
    println!("NTS offset: {:.6}s", result.offset_seconds);
    Ok(())
}

NTS Continuous Client

Combine NTS authentication with the continuous polling client:

use ntp_client::client::NtpClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (client, mut state_rx) = NtpClient::builder()
        .nts_server("time.cloudflare.com")
        .min_poll(4)
        .max_poll(10)
        .build()
        .await?;

    tokio::spawn(client.run());

    while state_rx.changed().await.is_ok() {
        let state = state_rx.borrow();
        println!("Offset: {:.6}s, NTS: {}", state.offset, state.nts_authenticated);
    }
    Ok(())
}

Async with smol

Enable the smol-runtime feature:

[dependencies]
ntp_usg-client = { version = "3.1", features = ["smol-runtime"] }
smol = "2"
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    smol::block_on(async {
        let result = ntp_client::smol_ntp::request_with_timeout(
            "time.nist.gov:123",
            Duration::from_secs(5),
        ).await?;
        println!("Offset: {:.6} seconds", result.offset_seconds);
        Ok(())
    })
}

The smol continuous client uses Arc<RwLock<NtpSyncState>> for state sharing:

use ntp_client::smol_client::NtpClient;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    smol::block_on(async {
        let (client, state) = NtpClient::builder()
            .server("time.nist.gov:123")
            .build()
            .await?;

        smol::spawn(client.run()).detach();

        loop {
            smol::Timer::after(std::time::Duration::from_secs(5)).await;
            let s = state.read().unwrap();
            println!("Offset: {:.6}s, Delay: {:.6}s", s.offset, s.delay);
        }
    })
}

Clock Adjustment

Enable the clock feature to correct the system clock based on NTP measurements:

[dependencies]
ntp_usg-client = { version = "3.1", features = ["clock", "tokio"] }
use ntp_client::clock;

// Gradual correction (slew) for small offsets
clock::slew_clock(0.05)?;

// Immediate correction (step) for large offsets
clock::step_clock(-1.5)?;

// Automatic: slew if |offset| <= 128ms, step otherwise
let method = clock::apply_correction(offset)?;

NTP Server

Enable the tokio feature on the server crate:

[dependencies]
ntp_usg-server = { version = "3.1", features = ["tokio"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
use ntp_server::protocol::Stratum;
use ntp_server::server::NtpServer;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let server = NtpServer::builder()
        .listen("0.0.0.0:123")
        .stratum(Stratum(2))
        .build()
        .await?;

    server.run().await
}

Multiple Servers

See crates/ntp_usg-client/examples/multiple_servers.rs for a complete example of querying multiple NTP servers.

Examples

Production Examples (v3.1.0+)

The following examples demonstrate production-ready deployments with comprehensive monitoring and error handling:

Multi-Peer Deployment - examples/multi_peer_deployment.rs

cargo run -p ntp_usg-client --example multi_peer_deployment --features ntp_usg-client/tokio

Demonstrates RFC 5905 selection, clustering, and combine algorithms with 5 diverse NTP servers. Includes real-time health assessment and offset trend analysis.

NTS Multi-Peer - examples/nts_multi_peer.rs

cargo run -p ntp_usg-client --example nts_multi_peer --features ntp_usg-client/nts

Mixed NTS-authenticated and standard NTP deployment for maximum security and resilience. Tracks security posture with NTS failure monitoring.

System Daemon - examples/daemon.rs

cargo run -p ntp_usg-client --example daemon --features ntp_usg-client/tokio

Production-ready long-running service with structured logging, health-based alerts, and systemd integration documentation.

Basic Examples

Run the included examples to see the library in action:

# Basic request example
cargo run -p ntp_usg-client --example request

# Custom timeout demonstration
cargo run -p ntp_usg-client --example timeout

# Query multiple servers
cargo run -p ntp_usg-client --example multiple_servers

# Detailed packet information
cargo run -p ntp_usg-client --example packet_details

# Async concurrent queries (requires tokio feature)
cargo run -p ntp_usg-client --example async_request --features ntp_usg-client/tokio

# Continuous client with poll management (requires tokio feature)
cargo run -p ntp_usg-client --example continuous --features ntp_usg-client/tokio

# NTS-authenticated request (requires nts feature)
cargo run -p ntp_usg-client --example nts_request --features ntp_usg-client/nts

# NTS continuous client (requires nts feature)
cargo run -p ntp_usg-client --example nts_continuous --features ntp_usg-client/nts

# Smol one-shot request
cargo run -p ntp_usg-client --example smol_request --features ntp_usg-client/smol-runtime

# Smol continuous client
cargo run -p ntp_usg-client --example smol_continuous --features ntp_usg-client/smol-runtime

# Clock adjustment (requires root/sudo on Unix, Administrator on Windows)
cargo run -p ntp_usg-client --example clock_adjust --features "ntp_usg-client/clock ntp_usg-client/tokio"

# NTP server (requires tokio feature)
cargo run -p ntp_usg-server --example server --features ntp_usg-server/tokio

# NTS server (requires nts feature + TLS certs)
cargo run -p ntp_usg-server --example nts_server --features ntp_usg-server/nts -- --cert server.crt --key server.key

Roadmap

  • async support (tokio)
  • NTP era handling (Y2036)
  • IPv6 dual-stack support
  • Continuous client with adaptive polling
  • Interleaved mode (RFC 9769)
  • Network Time Security (RFC 8915)
  • IO-independent parsing (FromBytes/ToBytes traits)
  • no_std support (with optional alloc)
  • smol support (one-shot, continuous, and NTS)
  • System clock adjustment (slew/step on Linux, macOS, Windows)
  • NTP server with NTS-KE
  • Workspace restructure (proto, client, server crates)
  • Reference clock interface (GPS, PPS)

Contributing

Pull requests and issues are welcome! Please see our GitHub repository for more information.

License

ntp_usg is distributed under the terms of both the MIT license and the Apache License (Version 2.0).

See LICENSE-APACHE and LICENSE-MIT for details.