static int sntp_receive_response(sd_event_source *source, int fd, uint32_t revents, void *userdata) { SNTPContext *sntp = userdata; unsigned char buf[sizeof(struct ntp_msg)]; struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf), }; union { struct cmsghdr cmsghdr; uint8_t buf[CMSG_SPACE(sizeof(struct timeval))]; } control; struct sockaddr_in server_addr; struct msghdr msghdr = { .msg_iov = &iov, .msg_iovlen = 1, .msg_control = &control, .msg_controllen = sizeof(control), .msg_name = &server_addr, .msg_namelen = sizeof(server_addr), }; struct cmsghdr *cmsg; struct timespec now_ts; struct timeval *recv_time; ssize_t len; struct ntp_msg *ntpmsg; 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."); sntp_server_disconnect(sntp); return -ENOTCONN; } len = recvmsg(fd, &msghdr, MSG_DONTWAIT); if (len < 0) { log_debug("Error receiving message. Disconnecting."); return -EINVAL; } if (iov.iov_len < sizeof(struct ntp_msg)) { 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."); return -EINVAL; } recv_time = NULL; for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg; cmsg = CMSG_NXTHDR(&msghdr, cmsg)) { if (cmsg->cmsg_level != SOL_SOCKET) continue; switch (cmsg->cmsg_type) { case SCM_TIMESTAMP: recv_time = (struct timeval *) CMSG_DATA(cmsg); break; } } if (!recv_time) { log_debug("Invalid packet timestamp. Disconnecting."); return -EINVAL; } ntpmsg = iov.iov_base; if (!sntp->pending) { 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 || be32toh(ntpmsg->origin_time.frac) != sntp->trans_time.tv_nsec) { 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."); return -EINVAL; } if (NTP_FIELD_VERSION(ntpmsg->field) != 4) { 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)); 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; else if (NTP_FIELD_LEAP(ntpmsg->field) & NTP_LEAP_MINUSSEC) leap_sec = -1; else leap_sec = 0; /* * "Timestamp Name ID When Generated * ------------------------------------------------------------ * Originate Timestamp T1 time request sent by client * Receive Timestamp T2 time request received by server * Transmit Timestamp T3 time reply sent by server * Destination Timestamp T4 time reply received by client * * 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_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 = ((receive - origin) + (trans - dest)) / 2; delay = (dest - origin) - (trans - receive); spike = sntp_sample_spike_detection(sntp, offset, delay); sntp_adjust_poll(sntp, offset, spike); log_debug("NTP response:\n" " leap : %u\n" " version : %u\n" " mode : %u\n" " stratum : %u\n" " precision : %f sec (%d)\n" " reference : %.4s\n" " origin : %f\n" " receive : %f\n" " transmit : %f\n" " dest : %f\n" " offset : %+f sec\n" " delay : %+f sec\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, exp2(ntpmsg->precision), ntpmsg->precision, ntpmsg->stratum == 1 ? ntpmsg->refid : "n/a", origin - OFFSET_1900_1970, receive - OFFSET_1900_1970, trans - OFFSET_1900_1970, dest - OFFSET_1900_1970, offset, delay, sntp->packet_count, sntp->samples_jitter, spike ? " spike" : "", sntp->poll_interval_usec / USEC_PER_SEC); 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); if (r < 0) log_error("Failed to call clock_adjtime(): %m"); } r = sntp_arm_timer(sntp, sntp->poll_interval_usec); if (r < 0) return r; return 0; } int sntp_server_connect(SNTPContext *sntp, const char *server) { _cleanup_free_ char *s = NULL; assert(sntp); assert(server); assert(sntp->server_socket >= 0); s = strdup(server); if (!s) return -ENOMEM; free(sntp->server); sntp->server = s; s = NULL; zero(sntp->server_addr); sntp->server_addr.sin_family = AF_INET; sntp->server_addr.sin_addr.s_addr = inet_addr(server); sntp->poll_interval_usec = 2 * NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; return sntp_send_request(sntp); } void sntp_server_disconnect(SNTPContext *sntp) { if (!sntp->server) 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; } 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; fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (fd < 0) return -errno; zero(addr); addr.sin_family = AF_INET; r = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); if (r < 0) return -errno; r = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &on, sizeof(on)); if (r < 0) return -errno; r = setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); if (r < 0) return -errno; r = sd_event_add_io(e, &sntp->event_receive, fd, EPOLLIN, sntp_receive_response, sntp); if (r < 0) return r; sntp->server_socket = fd; fd = -1; return 0; }
static int manager_receive_response(sd_event_source *source, int fd, uint32_t revents, void *userdata) { Manager *m = userdata; struct ntp_msg ntpmsg; struct iovec iov = { .iov_base = &ntpmsg, .iov_len = sizeof(ntpmsg), }; union { struct cmsghdr cmsghdr; uint8_t buf[CMSG_SPACE(sizeof(struct timeval))]; } control; union sockaddr_union server_addr; struct msghdr msghdr = { .msg_iov = &iov, .msg_iovlen = 1, .msg_control = &control, .msg_controllen = sizeof(control), .msg_name = &server_addr, .msg_namelen = sizeof(server_addr), }; struct cmsghdr *cmsg; struct timespec now_ts; struct timeval *recv_time; ssize_t len; double origin, receive, trans, dest; double delay, offset; bool spike; int leap_sec; int r; assert(source); assert(m); if (revents & (EPOLLHUP|EPOLLERR)) { log_warning("Server connection returned error."); return manager_connect(m); } len = recvmsg(fd, &msghdr, MSG_DONTWAIT); if (len < 0) { if (errno == EAGAIN) return 0; log_warning("Error receiving message. Disconnecting."); return manager_connect(m); } if (iov.iov_len < sizeof(struct ntp_msg)) { log_warning("Invalid response from server. Disconnecting."); return manager_connect(m); } if (!m->current_server_name || !m->current_server_address || !sockaddr_equal(&server_addr, &m->current_server_address->sockaddr)) { log_debug("Response from unknown server."); return 0; } recv_time = NULL; for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg; cmsg = CMSG_NXTHDR(&msghdr, cmsg)) { if (cmsg->cmsg_level != SOL_SOCKET) continue; switch (cmsg->cmsg_type) { case SCM_TIMESTAMP: recv_time = (struct timeval *) CMSG_DATA(cmsg); break; } } if (!recv_time) { log_error("Invalid packet timestamp."); return -EINVAL; } if (!m->pending) { 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) != m->trans_time.tv_sec + OFFSET_1900_1970 || be32toh(ntpmsg.origin_time.frac) != m->trans_time.tv_nsec) { log_debug("Invalid reply; not our transmit time. Ignoring."); return 0; } m->event_timeout = sd_event_source_unref(m->event_timeout); if (be32toh(ntpmsg.recv_time.sec) < TIME_EPOCH + OFFSET_1900_1970 || be32toh(ntpmsg.trans_time.sec) < TIME_EPOCH + OFFSET_1900_1970) { log_debug("Invalid reply, returned times before epoch. Ignoring."); return manager_connect(m); } if (NTP_FIELD_LEAP(ntpmsg.field) == NTP_LEAP_NOTINSYNC) { log_debug("Server is not synchronized. Disconnecting."); return manager_connect(m); } if (!IN_SET(NTP_FIELD_VERSION(ntpmsg.field), 3, 4)) { log_debug("Response NTPv%d. Disconnecting.", NTP_FIELD_VERSION(ntpmsg.field)); return manager_connect(m); } if (NTP_FIELD_MODE(ntpmsg.field) != NTP_MODE_SERVER) { log_debug("Unsupported mode %d. Disconnecting.", NTP_FIELD_MODE(ntpmsg.field)); return manager_connect(m); } /* valid packet */ m->pending = false; m->retry_interval = 0; /* announce leap seconds */ if (NTP_FIELD_LEAP(ntpmsg.field) & NTP_LEAP_PLUSSEC) leap_sec = 1; else if (NTP_FIELD_LEAP(ntpmsg.field) & NTP_LEAP_MINUSSEC) leap_sec = -1; else leap_sec = 0; /* * "Timestamp Name ID When Generated * ------------------------------------------------------------ * Originate Timestamp T1 time request sent by client * Receive Timestamp T2 time request received by server * Transmit Timestamp T3 time reply sent by server * Destination Timestamp T4 time reply received by client * * The round-trip delay, d, and system clock offset, t, are defined as: * d = (T4 - T1) - (T3 - T2) t = ((T2 - T1) + (T3 - T4)) / 2" */ assert_se(clock_gettime(clock_boottime_or_monotonic(), &now_ts) >= 0); origin = tv_to_d(recv_time) - (ts_to_d(&now_ts) - ts_to_d(&m->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 = ((receive - origin) + (trans - dest)) / 2; delay = (dest - origin) - (trans - receive); spike = manager_sample_spike_detection(m, offset, delay); manager_adjust_poll(m, offset, spike); log_debug("NTP response:\n" " leap : %u\n" " version : %u\n" " mode : %u\n" " stratum : %u\n" " precision : %.6f sec (%d)\n" " reference : %.4s\n" " origin : %.3f\n" " receive : %.3f\n" " transmit : %.3f\n" " dest : %.3f\n" " offset : %+.3f sec\n" " delay : %+.3f sec\n" " packet count : %"PRIu64"\n" " jitter : %.3f%s\n" " poll interval: " USEC_FMT "\n", NTP_FIELD_LEAP(ntpmsg.field), NTP_FIELD_VERSION(ntpmsg.field), NTP_FIELD_MODE(ntpmsg.field), ntpmsg.stratum, exp2(ntpmsg.precision), ntpmsg.precision, ntpmsg.stratum == 1 ? ntpmsg.refid : "n/a", origin - OFFSET_1900_1970, receive - OFFSET_1900_1970, trans - OFFSET_1900_1970, dest - OFFSET_1900_1970, offset, delay, m->packet_count, m->samples_jitter, spike ? " spike" : "", m->poll_interval_usec / USEC_PER_SEC); if (!spike) { m->sync = true; r = manager_adjust_clock(m, offset, leap_sec); if (r < 0) log_error("Failed to call clock_adjtime(): %m"); } log_info("interval/delta/delay/jitter/drift " USEC_FMT "s/%+.3fs/%.3fs/%.3fs/%+ippm%s", m->poll_interval_usec / USEC_PER_SEC, offset, delay, m->samples_jitter, m->drift_ppm, spike ? " (ignored)" : ""); r = manager_arm_timer(m, m->poll_interval_usec); if (r < 0) { log_error("Failed to rearm timer: %s", strerror(-r)); return r; } return 0; } static int manager_listen_setup(Manager *m) { union sockaddr_union addr = {}; static const int tos = IPTOS_LOWDELAY; static const int on = 1; int r; assert(m); assert(m->server_socket < 0); assert(!m->event_receive); assert(m->current_server_address); addr.sa.sa_family = m->current_server_address->sockaddr.sa.sa_family; m->server_socket = socket(addr.sa.sa_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (m->server_socket < 0) return -errno; r = bind(m->server_socket, &addr.sa, m->current_server_address->socklen); if (r < 0) return -errno; r = setsockopt(m->server_socket, SOL_SOCKET, SO_TIMESTAMP, &on, sizeof(on)); if (r < 0) return -errno; setsockopt(m->server_socket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); return sd_event_add_io(m->event, &m->event_receive, m->server_socket, EPOLLIN, manager_receive_response, m); } static int manager_begin(Manager *m) { _cleanup_free_ char *pretty = NULL; int r; assert(m); assert_return(m->current_server_name, -EHOSTUNREACH); assert_return(m->current_server_address, -EHOSTUNREACH); m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; server_address_pretty(m->current_server_address, &pretty); log_info("Using NTP server %s (%s).", strna(pretty), m->current_server_name->string); sd_notifyf(false, "STATUS=Using Time Server %s (%s).", strna(pretty), m->current_server_name->string); r = manager_listen_setup(m); if (r < 0) { log_warning("Failed to setup connection socket: %s", strerror(-r)); return r; } r = manager_clock_watch_setup(m); if (r < 0) return r; return manager_send_request(m); } void manager_set_server_name(Manager *m, ServerName *n) { assert(m); if (m->current_server_name == n) return; m->current_server_name = n; m->current_server_address = NULL; manager_disconnect(m); if (n) log_debug("Selected server %s.", n->string); }