Hi,
I run chronyd -x as a containerised NTP server (one instance, no clock
control, no CAP_SYS_TIME) that a cluster orchestrator can move between
hosts. Every host runs a normal chronyd that disciplines its own clock and
uses that server as its only source, so sooner or later the server runs
on a host whose clock it is indirectly disciplining.
With plain -x that is a feedback loop: the null driver measures the host
CLOCK_REALTIME, which the host chronyd slews towards the server, which
in turn corrects for the host's slewing. Two loops end up driving one
oscillator. On two x86 servers (chrony 4.8) the served time oscillated
with growing amplitude, about +-160 ms against independent references
within 25 minutes, and the server's frequency estimate swung between -275
and +592 ppm. A disposable VM reproduced it (p90 of the served error from
12.7 to 55 ms). With the server on a different host from its clients the
setup is stable.
As an experiment I gave -x a private raw clock (-X, Linux only) that the
host's clock discipline cannot reach:
LCL_ReadRawTime() = R0 + (MONOTONIC_RAW_now - M0) + (S_now - S0),
S = CLOCK_BOOTTIME - CLOCK_MONOTONIC (time spent in suspend)
R0/M0/S0 are sampled once before LCL_Initialise(). sys_null.c is
unchanged, so its frequency/offset model now disciplines the raw
oscillator, and R0 is only an anchor whose error is corrected like the
host clock's error with plain -x. The patch also has to:
- use daemon timestamps only (kernel/HW timestamps are in the
CLOCK_REALTIME domain), so -X refuses hwtimestamp, refclock, RTC and
makestep/initstepslew;
- time leap seconds in served time rather than raw time, and require
"leapsecmode slew" (the null driver cannot step);
- keep the null driver's offset correction when the scheduler detects a
jump (after a resume it is the whole error estimate).
Results so far: the upstream unit and simulation tests pass with the
patch. In a VM, host steps of +-1 s and +-30 s, +-500 ppm and a +1 %
tick left the served error median at <= 0.45 ms, where plain -x followed
them (up to +29 s). On real hardware a native chronyd has followed a -X
server on the same host since 2026-10-01. Deliberate host steps of
+-1 s and +-500 ppm were not visible in the served time (within about
0.7 ms of Netnod).
The prototype is about +250 lines in local.c, main.c, ntp_io.c,
ntp_io_linux.c, reference.c and refclock.c. I know it is not
mergeable as it stands: it has no kernel timestamp mapping, it is Linux
only, it supports only the slew leap mode, and it is an option rather
than a directive. So before going further I would like to ask:
1. Is this a use case you would want to support in chronyd, or would you
rather recommend a different setup? (For example, the host chronyd
serving time itself. That loses the single movable authority we want,
but I may be missing a better option.)
2. If it is welcome, what interface would you prefer: a directive
(something like "localclock raw") or an option? And would daemon-only
timestamps be acceptable for a first version, or should kernel RX/TX
timestamps be mapped into the private domain from the start?
3. I also have unit and clknetsim simulation tests (host steps and
slews, +-500 ppm, leap seconds, suspend). I left them out to keep
the patch small and can add them in the form you prefer. The
simulation tests need clock_gettime(CLOCK_BOOTTIME) in clknetsim; I
will send that separately as a two-line clknetsim patch.
The patch (one commit against current master) is here:
Thanks,
Robin Nilsson