A multi-threaded NTP server in Rust implementing RFC 10030 — Network Time Protocol (NTP) over the Precision Time Protocol (PTP) — layered on RFC 5905 (NTPv4) and RFC 7822 (extension fields).
RFC 10030 encapsulates unmodified NTP messages inside PTP event messages so that NTP inherits what many NICs and switches provide only for PTP: hardware timestamping and on-path (transparent-clock) delay correction — without adopting PTP's weaker security model or clock hierarchy.
- Serves both transports: plain NTP over UDP (port 123) and NTP over PTP (PTP event port 319), from the same process.
- Share-nothing hot path: one socket per worker (
SO_REUSEPORTon Linux), lock-free state publication viaarc-swap, zero allocation per packet. Measured per-packet service demand: ~5 ns plain NTP, ~28 ns over PTP. - Safety by construction:
#![forbid(unsafe_code)]everywhere, panic-free lints denied on all network-facing code, and the RFC's MUST-level rules (anti-amplification, domain verification, negative-correction rejection) encoded in the type system so they cannot be skipped. - Adversarially tested: property tests over arbitrary byte strings, exhaustive bit-flip and length-field sweeps, worst observed amplification factor exactly 1.0.
See docs/SPECIFICATION.md for the full implementation specification: architecture, wire formats, the RFC conformance matrix, the safety argument, and the performance model.
- Rust 1.85 or newer (stable;
rust-toolchain.tomlpins the channel and pullsclippy/rustfmt). - A local reference daemon (
chronydor anotherntpd) for the server to mirror. This server deliberately does not discipline the clock itself — it republishes the state of a daemon that does. - Linux is the supported deployment target (
SO_REUSEPORTload balancing, and — where the NIC supports it — hardware timestamping). The code also compiles on macOS for development, but runs there with a single effective receive queue per address. - Optional, for the verification/benchmark harness:
cargo-audit, a nightly toolchain withmiri, and Python 3 (used bybench.shfor a stub reference daemon).
cargo build --releaseBinaries land in target/release/: ntpd (the server) and ntpload (the
measurement client). Prebuilt Linux binaries (x86-64 and aarch64) are
published on the releases page.
Start the server, mirroring a local reference daemon and serving both transports:
ntpd --upstream 127.0.0.1:123 --stats 5Common options (see ntpd --help for the full list):
-a, --udp-address ADDR:PORT plain NTP listen address (default 0.0.0.0:123)
-b, --ptp-address ADDR:PORT NTP-over-PTP listen address (default 0.0.0.0:319)
--no-udp / --no-ptp disable one of the transports
-4, --udp-threads N worker threads for plain NTP (default 1)
-6, --ptp-threads N worker threads for NTP over PTP (default 1)
--domain N PTP domainNumber to accept (default 123)
--sdo-id N PTP sdoId to accept (default 0)
--link-speed BPS link speed for the receive-duration correction
-s, --upstream ADDR:PORT reference daemon to mirror (default 127.0.0.1:11123)
--rate N / --burst N per-worker response rate limit
--rcvbuf BYTES SO_RCVBUF per worker (default 4 MiB)
--stats SECONDS print per-interval counters
Ports 123 and 319 are privileged on most systems; either run with the needed
capability (setcap cap_net_bind_service=+ep on Linux) or bind high ports for
testing.
Drive it with the bundled load generator:
ntpload --server 127.0.0.1:319 --transport ptp --threads 8 --seconds 10./verify.sh # build, lints, tests, cargo-audit, Miri — every safety claim
./bench.sh # criterion benchmarks and load sweepsBoth scripts print PASS/FAIL/SKIP per check and are the reproduction path for every claim in docs/SPECIFICATION.md.
cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspaceThe workspace denies the panicking clippy lints (unwrap_used,
indexing_slicing, arithmetic_side_effects, …) on all library and server
code; tests and benches opt out locally with written reasons.
Licensed under the Apache License, Version 2.0.