X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Ftimedate%2Ftimedate-sntp.c;h=e4aad36cc5499f42e410bbe0a59a227bf972f8c8;hp=608177f662ad1f75d841d8ab7632fe2940c536d2;hb=12c0d47c60f4fb6f5a11c02ddb91a6c7ded5a049;hpb=bcdbbd7ee1b7dc6ec19261c957ed11e5e1ed1aaf;ds=sidebyside diff --git a/src/timedate/timedate-sntp.c b/src/timedate/timedate-sntp.c index 608177f66..e4aad36cc 100644 --- a/src/timedate/timedate-sntp.c +++ b/src/timedate/timedate-sntp.c @@ -42,15 +42,19 @@ #include #include #include +#include #include #include +#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,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; -}; - -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 +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; } @@ -241,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; @@ -262,13 +265,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; @@ -281,19 +347,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)); } @@ -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++; - - /* - * 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; + jitter = sntp->samples_jitter; /* 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; -} + /* ignore samples when resyncing */ + if (sntp->poll_resync) + return false; -static void snmp_adjust_poll(SNTPContext *sntp, double offset, bool spike) { - double delta; + /* always accept offset if we are farther off than the round-trip delay */ + if (fabs(offset) > delay) + return false; - if (spike) { - if (sntp->poll_interval > NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC) - sntp->poll_interval /= 2; + /* we need a few samples before looking at them */ + if (sntp->packet_count < 4) + return false; + + /* do not accept anything worse than the maximum possible error of the best sample */ + if (fabs(offset) > sntp->samples[idx_min].delay) + return true; + + /* 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 (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,31 @@ 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); + 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); @@ -557,7 +635,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 +661,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 +671,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 +712,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;