Hacker News new | ask | show | jobs
by nathas 1349 days ago
I'm really looking forward to the client implementation. I worked on a Rust NTP server where I used to work. It was truly faster than ntpd or chrony, which is a meaningful benefit when you're talking about something that sends out data about clocks and time.

Unfortunately the server is relatively easy to build. The client, however, is where a LOT of the intelligence and difficulty lies.

4 comments

Assuming a pure network client, there's not that much to build if you make simplifying assumptions, and build off the work of others.

You need something to get a list of servers, and maybe update the list overtime (if you're using a dns name).

For each server, you need to poll to accumulate a list of delays and offset. If you want to make the delay precise, you can try to get the NIC to timestamp the NTP packets. Once you've got sufficient samples from a server, you can use a regression to approximate the time offset and frequency offset for that server.

If you've got more than one server, you can choose one to follow (ala the ntp reference implementation) or merge the data from some selection. This is kind of tricky, but there are many examples to choose from. Some implementations use metrics from multiple servers to try to estimate how much of the delay is asymmetric and use that to enhance the clock precision; but the reference implementation didn't do that and most people didn't mind.

Once you've figured out which time and frequency offsets to do, send it to the OS via settimeofday and ntp_adjtime. This is a bit trickier if you don't want to step the clock, but you can calculate a frequency offset to slew the clock in the direction you want over the time you find acceptable. Reference ntp drops old measurements after an adjustment, but you can also scale them by your adjustments and keep using them.

You might want to have some feedback loop to adjust your polling rates, but either way.

None of this code needs to be particularly fast either. Ideally, very little in between timestamp and send, and receive and timestamp (NIC timestamping helps tremendously here, if that's possible); and you also want to have a minimum of delay during offset operations too. But the protocol parsing, and offset calculations can take as long as you like.

I've got a one-shot Erlang ntp client in 108 lines of Erlang, and 50 lines of nif (which is mostly unpacking the arguments) for an unpublished hobby os. It's not perfect, but it does pretty ok. If it ran continuously, it would probably meet or beat reference ntpd. Reference ntpd does a whole lot more cool stuff, of course.

The tricky part isn't the protocol, it's all of the interfaces with weirdo hardware clocks. Even just parsing GPGGA messages from a serial port can be tricky when you're trying to keep the timing tight.
There is immense value in replacing simple NTP deployments, which don't interface with weirdo hardware clocks, with a memory-safe alternative; those simple deployments dwarf the weird ones. It is fine (good, even) for there to be multiple viable implementations of NTP, fit for different purposes.
This work is the NTP client, with server promised later. https://github.com/memorysafety/ntpd-rs
Did your work get published anywhere on what you did to outperform chrony? By any chance was it clockwork.io?