Tick-Tock, Says the System Clock

Recently we have had a couple of in-depth conversations with resin.io users about how exactly is the system time managed on a device. It all boils down to using systemd for time management and interacting with the time service over dbus messaging.

Time Synchronization

When the resin.io device boots up, and before any container is run, the system will query the hardware clock to get the current time, while it will also read the timestamp value, stored in the last modification time of a special file, /var/lib/systemd/clock. If the hardware clock is behind the value stored with /var/lib/systemd/clock, the system will forcefully set the clock to the stored value. This is done to ensure that time from the point of view of the applications is monotonically increasing. After that, the device will start its Network Time Protocol (NTP) client which will be attempting to sync the clock with NTP servers periodically. If a successful synchronization occurs, the last modification time of /var/lib/systemd/clock is updated to that timestamp.

When you first provision a device, as a fallback, /var/lib/systemd/clock is set to the timestamp of the host OS build (or more precisely, the timestamp of the systemd build within the host OS). For more info, you can check the systemd-timesyncd documentation and the timesyncd source code.

Interacting with the Time Service

If you want to query the current time, you can do so by using the date utility or the datetime related functions of the standard library of your language.

If you want to learn if the system has completed at least one successful NTP synchronization since boot you can use dbus from your container to query that information. You have to install the dbus or dbus-send package (according to your base image), and call the host OS time service as:

DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host_run/dbus/system_bus_socket \  
  dbus-send \
  --system \
  --print-reply \
  --reply-timeout=2000 \
  --type=method_call \
  --dest=org.freedesktop.timedate1 \
  /org/freedesktop/timedate1  \
  org.freedesktop.DBus.Properties.GetAll \
  string:"org.freedesktop.timedate1"

The reply will be similar to:

method return time=1474008856.507103 sender=:1.12 -> destination=:1.11 serial=4 reply_serial=2  
   array [
      dict entry(
         string "Timezone"
         variant             string "UTC"
      )
      dict entry(
         string "LocalRTC"
         variant             boolean false
      )
      dict entry(
         string "CanNTP"
         variant             boolean true
      )
      dict entry(
         string "NTP"
         variant             boolean true
      )
      dict entry(
         string "NTPSynchronized"
         variant             boolean true
      )
      dict entry(
         string "TimeUSec"
         variant             uint64 1474008856505839
      )
      dict entry(
         string "RTCTimeUSec"
         variant             uint64 1474008857000000
      )
   ]

The NTPSynchronized property will tell you if you're running with a potentially stale clock or if the system is synced.

Networking Requirements

There are also certain networking requirements to ensure that the NTP service properly functions and the device time is synchronized.

First, the NTP service requires UDP port 123 to be open, as mentioned on our network requirements page.

Then the NTP service connects be able to connect to the following time servers, as they are set as default at the time of writing this: pool.ntp.org, time1.google.com, time2.google.com, time3.google.com, time4.google.com

Let us know on the forums if have any other time-related questions that we haven't answered this time!

resin_io