---
# System prepended metadata

title: 'Target practice: ntpd-rs on FreeBSD and other platforms'

---

# Target practice: ntpd-rs on FreeBSD and other platforms

![](https://hackmd.io/_uploads/SkxptHr53.png)


**The latest release of ntpd-rs compiles on several new targets: the [FreeBSD](https://github.com/pendulum-project/ntpd-rs/pull/724) and [macOS](https://github.com/pendulum-project/ntpd-rs/pull/718) operating systems now work, and ntpd-rs now supports [musl libc](https://github.com/pendulum-project/ntpd-rs/pull/696) on Linux. The PRs adding support for these platforms are all community contributions, which is very exciting.**

Most of our code is written in a target-agnostic way. For most code, any unix-like target will do. But because our lowest layer interfaces directly with libc, some subtle changes may be needed to support a new target. For instance, some types have slightly different fields, or more modern Linux APIs don't have an equivalent.

## Sockets

We use libc and usafe code to configure UDP sockets, so that they provide timestamps when sending or receiving messages. Linux provides several APIs for timestamping and we use the most modern and accurate method. The other unix-like systems don't have a direct equivalent.

### Memory Layout

Musl makes some minor changes to memory layout of some types. It adds private padding fields, so it is impossible to initialize a value of those types using only safe code. We have to use a bit of unsafe code instead:

```rust
fn empty_msghdr() -> libc::msghdr {
    unsafe { MaybeUninit::<libc::msghdr>::zeroed().assume_init() }
}
```

Initializing with zeros should be fine for all libc types, but it certainly is for the particular ones that we use. In fact we must zero the memory here; just using `MaybeUninit::uninit` would trigger undefined behavior (UB), because using uninitialized memory as an integer/pointer is undefined behavior. This might be slightly unintuitive because the bits form a valid integer value. Nonetheless, the decision was made to declare this UB.

### Timestamping Method

MacOS does not provide a method for nanosecond timestamps, it only supports the older posix method of microsecond timestamps, which are sufficiently accurate for NTP clients. MacOS systems are not designed to be (NTP) servers.

FreeBSD has its own nanosecond precision API, which is sligtly different from the one on Linux. An extra flag can be set, and from then on the timestamps will contain nanoseconds instead of microseconds.

## Clock steering

FreeBSD and macOS use an older API to update the clock. Fortunately, Linux still supports this API, so the implementation also works on Linux, and it was easy to develop on Linux.

The major downside of these APIs is that many operations require more syscalls. For instance stepping the clock on Linux can take a delta, but on macOS and FreeBSD requires the full timestamp. That means that before setting the clock, you need to first read the clock to get the current time, then add the delta, then set the clock again. 

Time elapses between reading and subsequently setting the clock, which reduces precision. The modern Linux APIs use just one atomic update to modify the current time and are therefore more precise.

## Pool deployment

The code changes to support these 3 new platforms were relatively straightforward. But (sadly) the fact that ntpd-rs compiles for these targets does not guarantee that it actually works. We need to validate the behavior by actually running the code on the target system.

We now run our tests for all targets, to make sure all primitives work as expected. Then we also perform manual tests where we set the time forward or backward by some amount and observe that the clock is re-synchronized to the correct time.

Finally, to get some production experience and verify that our implementation performs well for real traffic, we added a FreeBSD server to the NTP pool.

### Building for FreeBSD

I develop on Linux, and up to this point had not actually used FreeBSD. All of our CI machines are also Linux machines. But to deploy to the pool, we'd need a FreeBSD build of ntpd-rs. We also want this build to be reproducable.

Rust supports many targets, but the cross-compilation story is still rough. A major pain point is that there is no convenient way to acquire a linker and system libraries for the target platform. 

[Zig](https://ziglang.org/) does much better: it bundles many linkers and system library headers into its binary, and it is my prefered way to do cross-compilation. [cargo-zigbuild](https://github.com/rust-cross/cargo-zigbuild) is a wrapper around `cargo` that uses zig as the linker. We use cargo-zigbuild in our CI to run clippy for some targets like 32-bit Linux for the raspberry pi.

However, FreeBSD is not currently supported by cargo-zigbuild, and I ran into missing libraries trying to make it work. I'm sure it's possible, but I have no patience for this.

So instead we use an inelegant Vagrant setup to spin up a FreeBSD VM, install Rust, and build our source code. I'm not proud of it, it still took a day to set up, but it works and the build is now reproducable.

### Configuration

Next I set up a FreeBSD machine in Google Cloud. Then it is just a matter of putting the right files in the right places. We use an Ansible Playbook to move all of the files over.

The final missing ingredient is how to run ntpd-rs as a daemon. On Linux we use `systemd` to take care of the details, but FreeBSD does not have a direct equivalent. We eventually got a working [`/etc/rc.d/ntp-deamon`](https://github.com/pendulum-project/ntpd-rs/pull/766#issuecomment-1594611904) configuration that has all of the behavior we want (like running in the background and rotating log files).

With our daemon set up, it was time to add our new server to the NTP pool and slowly watch its accuracy score climb. 

![ntpd-rs on freebsd slowly reaching a score of 20 in the pool](https://hackmd.io/_uploads/HyPpYFav3.png)
*ntpd-rs on FreeBSD slowly reaching a score of 20 in the pool*

It looks like the output is a bit noisier than for our Linux servers, but it is more than adequate for the NTP pool.

<!--

I think we decided to just not do this part.

## Client validation

At our office, we have a setup to test the accuracy of our time synchronization. We hook up a GPS receiver to the clock of machine A, and let machine A be an NTP server. Machine A blindly follows the GPS time, its only purpose is to provide time to machine B.

Machine B in this case runs FreeBSD, and is an NTP client whose only source of time is machine A. We can then measure the difference in the time signals between machines A and B, to determine how much inaccuracy the NTP client adds. 

This is an idealized scenario, because in practice the network is much more complicated and there can be assymetry in the path that a packet travels. Nonetheless, this setup provides a benchmark where we can compare our client with other clients, or compare between systems.

-->

## Conclusion

It is very exciting that we support so many targets now. The FreeBSD compilation process is rough, but overall the process went smoothly. We are very happy to see such substantial community contributions! 

Try [`ntpd-rs`](https://github.com/pendulum-project/ntpd-rs/releases) today. For Linux we provide installers. For FreeBSD [this PR](https://github.com/pendulum-project/ntpd-rs/pull/766) contains useful information.

We would greatly appreciate feedback from users, particlarly those who are running ntpd-rs on a sizable network of computers. Opening an issue to share feedback or suggestions is an option, but you may also contact [Erik](https://tweedegolf.nl/en/about/6/erik) directly with your issues or proposed contributions.