chiark / gitweb /
timedated: ignore initial delta in history data
[elogind.git] / src / timedate / timedate-sntp.c
index 4d992c49de5cf00b03cac98e21b98cfa3a707eb2..a85b37775290146f5afefab2cd5caa00b4ed9ef8 100644 (file)
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
+#include <sys/timerfd.h>
 #include <sys/timex.h>
 #include <sys/socket.h>
 
+#include "missing.h"
 #include "util.h"
 #include "sparse-endian.h"
 #include "log.h"
 #include "sd-event.h"
 #include "timedate-sntp.h"
 
+#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
+
 #ifndef ADJ_SETOFFSET
 #define ADJ_SETOFFSET                   0x0100  /* add 'time' to current time */
 #endif
 
-/* Maximum delta in seconds which the system clock is gradually adjusted
- * to approach the network time. Deltas larger that this are set by letting
- * the system time jump. The maximum for adjtime is 500ms.
- */
-#define NTP_MAX_ADJUST                  0.2
+/* expected accuracy of time synchronization; used to adjust the poll interval */
+#define NTP_ACCURACY_SEC                0.2
 
 /*
- * "Define the required accuracy of the system clock, then calculate the
- * maximum timeout. Use the longest maximum timeout possible given the system
- * constraints to minimize time server aggregate load."
- *
  * "A client MUST NOT under any conditions use a poll interval less
  * than 15 seconds."
  */
-#define NTP_POLL_INTERVAL_MIN_SEC       16
+#define NTP_POLL_INTERVAL_MIN_SEC       32
 #define NTP_POLL_INTERVAL_MAX_SEC       2048
-#define NTP_POLL_ACCURACY_SEC           0.1
 
+/*
+ * Maximum delta in seconds which the system clock is gradually adjusted
+ * (slew) to approach the network time. Deltas larger that this are set by
+ * letting the system time jump. The kernel's limit for adjtime is 0.5s.
+ */
+#define NTP_MAX_ADJUST                  0.4
+
+/* NTP protocol, packet header */
 #define NTP_LEAP_PLUSSEC                1
 #define NTP_LEAP_MINUSSEC               2
 #define NTP_LEAP_NOTINSYNC              3
@@ -114,29 +118,44 @@ struct ntp_msg {
 } _packed_;
 
 struct SNTPContext {
-        sd_event_source *event_receive;
-        sd_event_source *event_timer;
+        void (*report)(usec_t poll, double offset, double delay, double jitter, bool spike);
 
+        /* peer */
+        sd_event_source *event_receive;
         char *server;
         struct sockaddr_in server_addr;
         int server_socket;
         uint64_t packet_count;
 
+        /* last sent packet */
         struct timespec trans_time_mon;
         struct timespec trans_time;
+        usec_t retry_interval;
         bool pending;
 
-        usec_t poll_interval;
+        /* poll timer */
+        sd_event_source *event_timer;
+        usec_t poll_interval_usec;
+        bool poll_resync;
 
+        /* history data */
         struct {
                 double offset;
                 double delay;
         } samples[8];
         unsigned int samples_idx;
         double samples_jitter;
+
+        /* last change */
+        bool jumped;
+
+        /* watch for time changes */
+        sd_event_source *event_clock_watch;
+        int clock_watch_fd;
 };
 
-static int sntp_arm_timer(SNTPContext *sntp);
+static int sntp_arm_timer(SNTPContext *sntp, usec_t next);
+static int sntp_clock_watch_setup(SNTPContext *sntp);
 
 static double ntp_ts_to_d(const struct ntp_ts *ts) {
         return be32toh(ts->sec) + ((double)be32toh(ts->frac) / UINT_MAX);
@@ -186,7 +205,7 @@ static int sntp_send_request(SNTPContext *sntp) {
          * matching answer to our request.
          *
          * The actual value does not matter, We do not care about the correct
-         * NTP UINT_MAX fraction, we just pass the plain nanosecond value.
+         * NTP UINT_MAX fraction; we just pass the plain nanosecond value.
          */
         clock_gettime(CLOCK_MONOTONIC, &sntp->trans_time_mon);
         clock_gettime(CLOCK_REALTIME, &sntp->trans_time);
@@ -197,19 +216,22 @@ static int sntp_send_request(SNTPContext *sntp) {
         addr.sin_port = htobe16(123);
         addr.sin_addr.s_addr = inet_addr(sntp->server);
         len = sendto(sntp->server_socket, &ntpmsg, sizeof(ntpmsg), MSG_DONTWAIT, &addr, sizeof(addr));
-        if (len < 0) {
+        if (len == sizeof(ntpmsg)) {
+                sntp->pending = true;
+                log_debug("Sent NTP request to: %s", sntp->server);
+        } else
                 log_debug("Sending NTP request to %s failed: %m", sntp->server);
-                return -errno;
-        }
 
-        sntp->pending = true;
-
-        /* re-arm timer for next poll interval, in case the packet never arrives back */
-        r = sntp_arm_timer(sntp);
+        /* re-arm timer with incresing timeout, in case the packets never arrive back */
+        if (sntp->retry_interval > 0) {
+                if (sntp->retry_interval < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC)
+                        sntp->retry_interval *= 2;
+        } else
+                sntp->retry_interval = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC;
+        r = sntp_arm_timer(sntp, sntp->retry_interval);
         if (r < 0)
                 return r;
 
-        log_debug("Sent NTP request to: %s", sntp->server);
         return 0;
 }
 
@@ -222,20 +244,20 @@ static int sntp_timer(sd_event_source *source, usec_t usec, void *userdata) {
         return 0;
 }
 
-static int sntp_arm_timer(SNTPContext *sntp) {
+static int sntp_arm_timer(SNTPContext *sntp, usec_t next) {
         sd_event *e;
         int r;
 
         assert(sntp);
         assert(sntp->event_receive);
 
-        if (sntp->poll_interval <= 0) {
+        if (next == 0) {
                 sntp->event_timer = sd_event_source_unref(sntp->event_timer);
                 return 0;
         }
 
         if (sntp->event_timer) {
-                r = sd_event_source_set_time(sntp->event_timer, now(CLOCK_MONOTONIC) + sntp->poll_interval);
+                r = sd_event_source_set_time(sntp->event_timer, now(CLOCK_MONOTONIC) + next);
                 if (r < 0)
                         return r;
 
@@ -243,13 +265,81 @@ static int sntp_arm_timer(SNTPContext *sntp) {
         }
 
         e = sd_event_source_get_event(sntp->event_receive);
-        r = sd_event_add_monotonic(e, &sntp->event_timer, now(CLOCK_MONOTONIC) + sntp->poll_interval, 0, sntp_timer, sntp);
+        r = sd_event_add_time(
+                        e,
+                        &sntp->event_timer,
+                        CLOCK_MONOTONIC,
+                        now(CLOCK_MONOTONIC) + next, 0,
+                        sntp_timer, sntp);
         if (r < 0)
                 return r;
 
         return 0;
 }
 
+static int sntp_clock_watch(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
+        SNTPContext *sntp = userdata;
+
+        assert(sntp);
+        assert(sntp->event_receive);
+
+        /* rearm timer */
+        sntp_clock_watch_setup(sntp);
+
+        /* skip our own jumps */
+        if (sntp->jumped) {
+                sntp->jumped = false;
+                return 0;
+        }
+
+        /* resync */
+        log_info("System time changed. Resyncing.");
+        sntp->poll_resync = true;
+        sntp_send_request(sntp);
+
+        return 0;
+}
+
+/* wake up when the system time changes underneath us */
+static int sntp_clock_watch_setup(SNTPContext *sntp) {
+        struct itimerspec its = { .it_value.tv_sec = TIME_T_MAX };
+        _cleanup_close_ int fd = -1;
+        sd_event *e;
+        sd_event_source *source;
+        int r;
+
+        assert(sntp);
+        assert(sntp->event_receive);
+
+        fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC);
+        if (fd < 0) {
+                log_error("Failed to create timerfd: %m");
+                return -errno;
+        }
+
+        if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) {
+                log_error("Failed to set up timerfd: %m");
+                return -errno;
+        }
+
+        e = sd_event_source_get_event(sntp->event_receive);
+        r = sd_event_add_io(e, &source, fd, EPOLLIN, sntp_clock_watch, sntp);
+        if (r < 0) {
+                log_error("Failed to create clock watch event source: %s", strerror(-r));
+                return r;
+        }
+
+        sd_event_source_unref(sntp->event_clock_watch);
+        sntp->event_clock_watch = source;
+
+        if (sntp->clock_watch_fd >= 0)
+                close(sntp->clock_watch_fd);
+        sntp->clock_watch_fd = fd;
+        fd = -1;
+
+        return 0;
+}
+
 static int sntp_adjust_clock(SNTPContext *sntp, double offset, int leap_sec) {
         struct timex tmx = {};
         int r;
@@ -262,19 +352,18 @@ static int sntp_adjust_clock(SNTPContext *sntp, double offset, int leap_sec) {
          * syncs the system time periodically to the hardware clock.
          */
         if (offset < NTP_MAX_ADJUST && offset > -NTP_MAX_ADJUST) {
-                int constant;
-
-                constant = log2i(sntp->poll_interval / USEC_PER_SEC) - 5;
-
-                tmx.modes |= ADJ_STATUS | ADJ_OFFSET | ADJ_TIMECONST;
+                tmx.modes |= ADJ_STATUS | ADJ_OFFSET | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR;
                 tmx.status = STA_PLL;
                 tmx.offset = offset * 1000 * 1000;
-                tmx.constant = constant;
-
+                tmx.constant = log2i(sntp->poll_interval_usec / USEC_PER_SEC) - 6;
+                tmx.maxerror = 0;
+                tmx.esterror = 0;
                 log_debug("  adjust (slew): %+f sec\n", (double)tmx.offset / USEC_PER_SEC);
         } else {
                 tmx.modes = ADJ_SETOFFSET;
                 d_to_tv(offset, &tmx.time);
+
+                sntp->jumped = true;
                 log_debug("  adjust (jump): %+f sec\n", tv_to_d(&tmx.time));
         }
 
@@ -308,7 +397,13 @@ static int sntp_adjust_clock(SNTPContext *sntp, double offset, int leap_sec) {
 static bool sntp_sample_spike_detection(SNTPContext *sntp, double offset, double delay) {
         unsigned int i, idx_cur, idx_new, idx_min;
         double jitter;
-        bool spike;
+        double j;
+
+        sntp->packet_count++;
+
+        /* ignore initial sample */
+        if (sntp->packet_count == 1)
+                return false;
 
         /* store the current data in our samples array */
         idx_cur = sntp->samples_idx;
@@ -317,54 +412,61 @@ static bool sntp_sample_spike_detection(SNTPContext *sntp, double offset, double
         sntp->samples[idx_new].offset = offset;
         sntp->samples[idx_new].delay = delay;
 
-        sntp->packet_count++;
-
-       /*
-        * Spike detection; compare the difference between the
-        * current offset to the previous offset and jitter.
-        */
-        spike = sntp->packet_count > 2 && fabs(offset - sntp->samples[idx_cur].offset) > sntp->samples_jitter * 3;
-
         /* calculate new jitter value from the RMS differences relative to the lowest delay sample */
+        jitter = sntp->samples_jitter;
         for (idx_min = idx_cur, i = 0; i < ELEMENTSOF(sntp->samples); i++)
                 if (sntp->samples[i].delay > 0 && sntp->samples[i].delay < sntp->samples[idx_min].delay)
                         idx_min = i;
 
-        for (jitter = 0, i = 0; i < ELEMENTSOF(sntp->samples); i++)
-                jitter += square(sntp->samples[i].offset - sntp->samples[idx_min].offset);
-        sntp->samples_jitter = sqrt(jitter / (ELEMENTSOF(sntp->samples) - 1));
+        j = 0;
+        for (i = 0; i < ELEMENTSOF(sntp->samples); i++)
+                j += square(sntp->samples[i].offset - sntp->samples[idx_min].offset);
+        sntp->samples_jitter = sqrt(j / (ELEMENTSOF(sntp->samples) - 1));
 
-        return spike;
-}
+        /* ignore samples when resyncing */
+        if (sntp->poll_resync)
+                return false;
+
+        /* always accept offset if we are farther off than the round-trip delay */
+        if (fabs(offset) > delay)
+                return false;
+
+        /* we need a few samples before looking at them */
+        if (sntp->packet_count < 4)
+                return false;
 
-static void snmp_adjust_poll(SNTPContext *sntp, double offset, bool spike) {
-        double delta;
+        /* do not accept anything worse than the maximum possible error of the best sample */
+        if (fabs(offset) > sntp->samples[idx_min].delay)
+                return true;
 
-        if (spike) {
-                if (sntp->poll_interval > NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC)
-                        sntp->poll_interval /= 2;
+        /* compare the difference between the current offset to the previous offset and jitter */
+        return fabs(offset - sntp->samples[idx_cur].offset) > 3 * jitter;
+}
+
+static void sntp_adjust_poll(SNTPContext *sntp, double offset, bool spike) {
+        if (sntp->poll_resync) {
+                sntp->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC;
+                sntp->poll_resync = false;
                 return;
         }
 
-        delta = fabs(offset);
-
         /* set to minimal poll interval */
-        if (delta > NTP_POLL_ACCURACY_SEC) {
-                sntp->poll_interval = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC;
+        if (!spike && fabs(offset) > NTP_ACCURACY_SEC) {
+                sntp->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC;
                 return;
         }
 
         /* increase polling interval */
-        if (delta < NTP_POLL_ACCURACY_SEC * 0.25) {
-                if (sntp->poll_interval < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC)
-                        sntp->poll_interval *= 2;
+        if (fabs(offset) < NTP_ACCURACY_SEC * 0.25) {
+                if (sntp->poll_interval_usec < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC)
+                        sntp->poll_interval_usec *= 2;
                 return;
         }
 
         /* decrease polling interval */
-        if (delta > NTP_POLL_ACCURACY_SEC * 0.75) {
-                if (sntp->poll_interval > NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC)
-                        sntp->poll_interval /= 2;
+        if (spike || fabs(offset) > NTP_ACCURACY_SEC * 0.75) {
+                if (sntp->poll_interval_usec > NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC)
+                        sntp->poll_interval_usec /= 2;
                 return;
         }
 }
@@ -390,35 +492,35 @@ static int sntp_receive_response(sd_event_source *source, int fd, uint32_t reven
                 .msg_namelen = sizeof(server_addr),
         };
         struct cmsghdr *cmsg;
-        struct timespec now;
+        struct timespec now_ts;
         struct timeval *recv_time;
         ssize_t len;
         struct ntp_msg *ntpmsg;
-        double origin, recv, trans, dest;
+        double origin, receive, trans, dest;
         double delay, offset;
         bool spike;
         int leap_sec;
         int r;
 
         if (revents & (EPOLLHUP|EPOLLERR)) {
-                log_debug("Server connection returned error, closing.");
+                log_debug("Server connection returned error. Closing.");
                 sntp_server_disconnect(sntp);
                 return -ENOTCONN;
         }
 
         len = recvmsg(fd, &msghdr, MSG_DONTWAIT);
         if (len < 0) {
-                log_debug("Error receiving message, disconnecting");
+                log_debug("Error receiving message. Disconnecting.");
                 return -EINVAL;
         }
 
         if (iov.iov_len < sizeof(struct ntp_msg)) {
-                log_debug("Invalid response from server, disconnecting");
+                log_debug("Invalid response from server. Disconnecting.");
                 return -EINVAL;
         }
 
         if (sntp->server_addr.sin_addr.s_addr != server_addr.sin_addr.s_addr) {
-                log_debug("Response from unknown server, disconnecting");
+                log_debug("Response from unknown server. Disconnecting.");
                 return -EINVAL;
         }
 
@@ -434,39 +536,42 @@ static int sntp_receive_response(sd_event_source *source, int fd, uint32_t reven
                 }
         }
         if (!recv_time) {
-                log_debug("Invalid packet timestamp, disconnecting");
+                log_debug("Invalid packet timestamp. Disconnecting.");
                 return -EINVAL;
         }
 
         ntpmsg = iov.iov_base;
         if (!sntp->pending) {
-                log_debug("Unexpected reply, ignoring");
+                log_debug("Unexpected reply. Ignoring.");
                 return 0;
         }
-        sntp->pending = false;
 
         /* check our "time cookie" (we just stored nanoseconds in the fraction field) */
-        if (be32toh(ntpmsg->origin_time.sec) != sntp->trans_time.tv_sec + OFFSET_1900_1970||
+        if (be32toh(ntpmsg->origin_time.sec) != sntp->trans_time.tv_sec + OFFSET_1900_1970 ||
             be32toh(ntpmsg->origin_time.frac) != sntp->trans_time.tv_nsec) {
-                log_debug("Invalid reply, not our transmit time, ignoring");
+                log_debug("Invalid reply; not our transmit time. Ignoring.");
                 return 0;
         }
 
         if (NTP_FIELD_LEAP(ntpmsg->field) == NTP_LEAP_NOTINSYNC) {
-                log_debug("Server is not synchronized, disconnecting");
+                log_debug("Server is not synchronized. Disconnecting.");
                 return -EINVAL;
         }
 
         if (NTP_FIELD_VERSION(ntpmsg->field) != 4) {
-                log_debug("Response NTPv%d, disconnecting", NTP_FIELD_VERSION(ntpmsg->field));
+                log_debug("Response NTPv%d. Disconnecting.", NTP_FIELD_VERSION(ntpmsg->field));
                 return -EINVAL;
         }
 
         if (NTP_FIELD_MODE(ntpmsg->field) != NTP_MODE_SERVER) {
-                log_debug("Unsupported mode %d, disconnecting", NTP_FIELD_MODE(ntpmsg->field));
+                log_debug("Unsupported mode %d. Disconnecting.", NTP_FIELD_MODE(ntpmsg->field));
                 return -EINVAL;
         }
 
+        /* valid packet */
+        sntp->pending = false;
+        sntp->retry_interval = 0;
+
         /* announce leap seconds */
         if (NTP_FIELD_LEAP(ntpmsg->field) & NTP_LEAP_PLUSSEC)
                 leap_sec = 1;
@@ -483,21 +588,21 @@ static int sntp_receive_response(sd_event_source *source, int fd, uint32_t reven
          *  Transmit Timestamp      T3   time reply sent by server
          *  Destination Timestamp   T4   time reply received by client
          *
-         *  The roundtrip delay d and system clock offset t are defined as:
+         *  The round-trip delay, d, and system clock offset, t, are defined as:
          *  d = (T4 - T1) - (T3 - T2)     t = ((T2 - T1) + (T3 - T4)) / 2"
          */
-        clock_gettime(CLOCK_MONOTONIC, &now);
-        origin = tv_to_d(recv_time) - (ts_to_d(&now) - ts_to_d(&sntp->trans_time_mon)) + OFFSET_1900_1970;
-        recv = ntp_ts_to_d(&ntpmsg->recv_time);
+        clock_gettime(CLOCK_MONOTONIC, &now_ts);
+        origin = tv_to_d(recv_time) - (ts_to_d(&now_ts) - ts_to_d(&sntp->trans_time_mon)) + OFFSET_1900_1970;
+        receive = ntp_ts_to_d(&ntpmsg->recv_time);
         trans = ntp_ts_to_d(&ntpmsg->trans_time);
         dest = tv_to_d(recv_time) + OFFSET_1900_1970;
 
-        offset = ((recv - origin) + (trans - dest)) / 2;
-        delay = (dest - origin) - (trans - recv);
+        offset = ((receive - origin) + (trans - dest)) / 2;
+        delay = (dest - origin) - (trans - receive);
 
         spike = sntp_sample_spike_detection(sntp, offset, delay);
 
-        snmp_adjust_poll(sntp, offset, spike);
+        sntp_adjust_poll(sntp, offset, spike);
 
         log_debug("NTP response:\n"
                   "  leap         : %u\n"
@@ -507,13 +612,13 @@ static int sntp_receive_response(sd_event_source *source, int fd, uint32_t reven
                   "  precision    : %f sec (%d)\n"
                   "  reference    : %.4s\n"
                   "  origin       : %f\n"
-                  "  recv         : %f\n"
+                  "  receive      : %f\n"
                   "  transmit     : %f\n"
                   "  dest         : %f\n"
                   "  offset       : %+f sec\n"
                   "  delay        : %+f sec\n"
                   "  packet count : %"PRIu64"\n"
-                  "  jitter/spike : %f (%s)\n"
+                  "  jitter       : %f%s\n"
                   "  poll interval: %llu\n",
                   NTP_FIELD_LEAP(ntpmsg->field),
                   NTP_FIELD_VERSION(ntpmsg->field),
@@ -522,15 +627,16 @@ static int sntp_receive_response(sd_event_source *source, int fd, uint32_t reven
                   exp2(ntpmsg->precision), ntpmsg->precision,
                   ntpmsg->stratum == 1 ? ntpmsg->refid : "n/a",
                   origin - OFFSET_1900_1970,
-                  recv - OFFSET_1900_1970,
+                  receive - OFFSET_1900_1970,
                   trans - OFFSET_1900_1970,
                   dest - OFFSET_1900_1970,
                   offset, delay,
                   sntp->packet_count,
-                  sntp->samples_jitter, spike ? "yes" : "no",
-                  sntp->poll_interval / USEC_PER_SEC);
+                  sntp->samples_jitter, spike ? " spike" : "",
+                  sntp->poll_interval_usec / USEC_PER_SEC);
 
-        log_info("%4llu %s %+12f", sntp->poll_interval / USEC_PER_SEC, spike ? "y" : "n", offset);
+        if (sntp->report)
+                sntp->report(sntp->poll_interval_usec, offset, delay, sntp->samples_jitter, spike);
 
         if (!spike) {
                 r = sntp_adjust_clock(sntp, offset, leap_sec);
@@ -538,7 +644,7 @@ static int sntp_receive_response(sd_event_source *source, int fd, uint32_t reven
                         log_error("Failed to call clock_adjtime(): %m");
         }
 
-        r = sntp_arm_timer(sntp);
+        r = sntp_arm_timer(sntp, sntp->poll_interval_usec);
         if (r < 0)
                 return r;
 
@@ -564,7 +670,7 @@ int sntp_server_connect(SNTPContext *sntp, const char *server) {
         sntp->server_addr.sin_family = AF_INET;
         sntp->server_addr.sin_addr.s_addr = inet_addr(server);
 
-        sntp->poll_interval = 2 * NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC;
+        sntp->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC;
 
         return sntp_send_request(sntp);
 }
@@ -574,27 +680,29 @@ void sntp_server_disconnect(SNTPContext *sntp) {
                 return;
 
         sntp->event_timer = sd_event_source_unref(sntp->event_timer);
+
+        sntp->event_clock_watch = sd_event_source_unref(sntp->event_clock_watch);
+        if (sntp->clock_watch_fd > 0)
+                close(sntp->clock_watch_fd);
+        sntp->clock_watch_fd = -1;
+
         sntp->event_receive = sd_event_source_unref(sntp->event_receive);
         if (sntp->server_socket > 0)
                 close(sntp->server_socket);
         sntp->server_socket = -1;
+
         zero(sntp->server_addr);
         free(sntp->server);
         sntp->server = NULL;
 }
 
-int sntp_new(SNTPContext **sntp, sd_event *e) {
-        _cleanup_free_ SNTPContext *c;
+static int sntp_listen_setup(SNTPContext *sntp, sd_event *e) {
         _cleanup_close_ int fd = -1;
         struct sockaddr_in addr;
         const int on = 1;
         const int tos = IPTOS_LOWDELAY;
         int r;
 
-        c = new0(SNTPContext, 1);
-        if (!c)
-                return -ENOMEM;
-
         fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
         if (fd < 0)
                 return -errno;
@@ -613,13 +721,36 @@ int sntp_new(SNTPContext **sntp, sd_event *e) {
         if (r < 0)
                 return -errno;
 
-        r = sd_event_add_io(e, &c->event_receive, fd, EPOLLIN, sntp_receive_response, c);
+        r = sd_event_add_io(e, &sntp->event_receive, fd, EPOLLIN, sntp_receive_response, sntp);
         if (r < 0)
                 return r;
 
-        c->server_socket = fd;
+        sntp->server_socket = fd;
         fd = -1;
 
+        return 0;
+}
+
+void sntp_report_register(SNTPContext *sntp, void (*report)(usec_t poll_usec, double offset, double delay, double jitter, bool spike)) {
+        sntp->report = report;
+}
+
+int sntp_new(SNTPContext **sntp, sd_event *e) {
+        _cleanup_free_ SNTPContext *c;
+        int r;
+
+        c = new0(SNTPContext, 1);
+        if (!c)
+                return -ENOMEM;
+
+        r = sntp_listen_setup(c, e);
+        if (r < 0)
+                return r;
+
+        r = sntp_clock_watch_setup(c);
+        if (r < 0)
+                return r;
+
         *sntp = c;
         c = NULL;