#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_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
} _packed_;
struct SNTPContext {
+ void (*report)(usec_t poll, double offset, double delay, double jitter, bool spike);
+
/* peer */
sd_event_source *event_receive;
char *server;
/* poll timer */
sd_event_source *event_timer;
- usec_t poll_interval;
+ usec_t poll_interval_usec;
bool poll_resync;
- /* statistics data */
+ /* history data */
struct {
double offset;
double delay;
* 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);
sntp->pending = true;
log_debug("Sent NTP request to: %s", sntp->server);
} else
- log_info("Sending NTP request to %s failed: %m", sntp->server);
+ log_debug("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) {
}
e = sd_event_source_get_event(sntp->event_receive);
- r = sd_event_add_monotonic(e, &sntp->event_timer, now(CLOCK_MONOTONIC) + next, 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;
}
/* resync */
- log_info("System time changed, resyncing.");
+ log_info("System time changed. Resyncing.");
sntp->poll_resync = true;
sntp_send_request(sntp);
* 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;
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;
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->poll_resync &&
- 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));
+
+ /* 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;
- return spike;
+ /* 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) {
- double delta;
-
if (sntp->poll_resync) {
- sntp->poll_interval = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC;
+ sntp->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC;
sntp->poll_resync = false;
return;
}
- if (spike) {
- if (sntp->poll_interval > NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC)
- sntp->poll_interval /= 2;
- return;
- }
-
- delta = fabs(offset);
-
/* set to minimal poll interval */
- if (delta > NTP_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_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_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;
}
}
.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;
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;
}
}
}
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;
}
/* 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;
}
* 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;
+ 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 : %+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),
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 %+10f %10f %10f %s",
- sntp->poll_interval / USEC_PER_SEC, offset, delay, sntp->samples_jitter, spike ? "spike" : "");
+ 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);
log_error("Failed to call clock_adjtime(): %m");
}
- r = sntp_arm_timer(sntp, sntp->poll_interval);
+ r = sntp_arm_timer(sntp, sntp->poll_interval_usec);
if (r < 0)
return r;
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);
}
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;