chiark / gitweb /
timedated: update spike handling
[elogind.git] / src / timedate / timedate-sntp.c
index 608177f662ad1f75d841d8ab7632fe2940c536d2..fff02c79e5b3f241c050b04d19e9251585756cf9 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
@@ -71,7 +75,7 @@
  */
 #define NTP_POLL_INTERVAL_MIN_SEC       16
 #define NTP_POLL_INTERVAL_MAX_SEC       2048
-#define NTP_POLL_ACCURACY_SEC           0.1
+#define NTP_ACCURACY_SEC                0.1
 
 #define NTP_LEAP_PLUSSEC                1
 #define NTP_LEAP_MINUSSEC               2
@@ -114,48 +118,42 @@ struct ntp_msg {
 } _packed_;
 
 struct SNTPContext {
+        /* peer */
         sd_event_source *event_receive;
-        sd_event_source *event_timer;
-
         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;
-};
-
-static int sntp_arm_timer(SNTPContext *sntp);
 
-static int log2i(int a) {
-        int exp = 0;
+        /* last change */
+        bool jumped;
 
-        assert(a > 0);
-
-        while (a > 0) {
-                a >>= 1;
-                exp++;
-        }
-
-        return exp;
-}
+        /* watch for time changes */
+        sd_event_source *event_clock_watch;
+        int clock_watch_fd;
+};
 
-static double log2d(int a) {
-        if (a < 0)
-                return 1.0 / (1UL << - a);
-        return 1UL << a;
-}
+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);
@@ -216,19 +214,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) {
-                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);
+        if (len == sizeof(ntpmsg)) {
+                sntp->pending = true;
+                log_debug("Sent NTP request to: %s", sntp->server);
+        } else
+                log_info("Sending NTP request to %s failed: %m", sntp->server);
+
+        /* 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;
 }
 
@@ -241,20 +242,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;
 
@@ -262,13 +263,76 @@ 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_monotonic(e, &sntp->event_timer, 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;
@@ -283,17 +347,18 @@ static int sntp_adjust_clock(SNTPContext *sntp, double offset, int leap_sec) {
         if (offset < NTP_MAX_ADJUST && offset > -NTP_MAX_ADJUST) {
                 int constant;
 
-                constant = log2i(sntp->poll_interval / USEC_PER_SEC) - 5;
+                constant = log2i(sntp->poll_interval_usec / USEC_PER_SEC) - 6;
 
                 tmx.modes |= ADJ_STATUS | ADJ_OFFSET | ADJ_TIMECONST;
                 tmx.status = STA_PLL;
                 tmx.offset = offset * 1000 * 1000;
                 tmx.constant = constant;
-
                 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));
         }
 
@@ -327,7 +392,7 @@ 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;
 
         /* store the current data in our samples array */
         idx_cur = sntp->samples_idx;
@@ -337,53 +402,62 @@ static bool sntp_sample_spike_detection(SNTPContext *sntp, double offset, double
         sntp->samples[idx_new].delay = delay;
 
         sntp->packet_count++;
+        jitter = sntp->samples_jitter;
+
+        /* ignore samples when resyncing */
+        if (sntp->poll_resync)
+                return false;
 
-       /*
-        * 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;
+        /* we need a few samples before we calculate anything */
+        if (sntp->packet_count < 3)
+                return false;
+
+        /* always accept sample if we are farther off than the round trip delay */
+        if (fabs(offset) > delay)
+                return false;
 
         /* calculate new jitter value from the RMS differences relative to the lowest delay sample */
         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;
-}
+        /* do not accept anything worse than the maximum possible error of the best sample */
+        if (abs(offset) > sntp->samples[idx_min].delay)
+                return true;
 
-static void snmp_adjust_poll(SNTPContext *sntp, double offset, bool spike) {
-        double delta;
+        /* compare the difference between the current offset to the previous offset and jitter */
+        return fabs(offset - sntp->samples[idx_cur].offset) > 3 * jitter;
+}
 
-        if (spike) {
-                if (sntp->poll_interval > NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC)
-                        sntp->poll_interval /= 2;
+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 (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;
         }
 }
@@ -409,11 +483,11 @@ 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;
@@ -462,7 +536,6 @@ static int sntp_receive_response(sd_event_source *source, int fd, uint32_t reven
                 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||
@@ -486,6 +559,10 @@ static int sntp_receive_response(sd_event_source *source, int fd, uint32_t reven
                 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;
@@ -505,18 +582,18 @@ static int sntp_receive_response(sd_event_source *source, int fd, uint32_t reven
          *  The roundtrip 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"
@@ -526,30 +603,32 @@ 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 : %llu\n"
-                  "  jitter/spike : %f (%s)\n"
+                  "  packet count : %"PRIu64"\n"
+                  "  jitter       : %f%s\n"
                   "  poll interval: %llu\n",
                   NTP_FIELD_LEAP(ntpmsg->field),
                   NTP_FIELD_VERSION(ntpmsg->field),
                   NTP_FIELD_MODE(ntpmsg->field),
                   ntpmsg->stratum,
-                  log2d(ntpmsg->precision), ntpmsg->precision,
+                  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,
-                  (unsigned long long)sntp->packet_count,
-                  sntp->samples_jitter, spike ? "yes" : "no",
-                  sntp->poll_interval / USEC_PER_SEC);
+                  sntp->packet_count,
+                  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);
+        log_info("%4llu %+10f %10f %10f%s",
+                 sntp->poll_interval_usec / USEC_PER_SEC, offset, delay,
+                 sntp->samples_jitter, spike ? " spike" : "");
 
         if (!spike) {
                 r = sntp_adjust_clock(sntp, offset, leap_sec);
@@ -557,7 +636,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;
 
@@ -583,7 +662,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 = 2 * NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC;
 
         return sntp_send_request(sntp);
 }
@@ -593,27 +672,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;
@@ -632,13 +713,32 @@ 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;
+}
+
+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;