From e7dd394767330bb336e3aee68369aae3628e599f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 30 Apr 2018 23:02:09 +0900 Subject: [PATCH 1/5] timesync: expose manager properties on bus --- src/timesync/meson.build | 6 + src/timesync/org.freedesktop.timesync1.conf | 42 ++++++ .../org.freedesktop.timesync1.service | 14 ++ src/timesync/timesyncd-bus.c | 137 ++++++++++++++++++ src/timesync/timesyncd-bus.h | 6 + src/timesync/timesyncd-manager.c | 2 + src/timesync/timesyncd-manager.h | 2 + src/timesync/timesyncd.c | 7 + units/systemd-timesyncd.service.in | 1 + 9 files changed, 217 insertions(+) create mode 100644 src/timesync/org.freedesktop.timesync1.conf create mode 100644 src/timesync/org.freedesktop.timesync1.service create mode 100644 src/timesync/timesyncd-bus.c create mode 100644 src/timesync/timesyncd-bus.h diff --git a/src/timesync/meson.build b/src/timesync/meson.build index 72c7327cc8..5ecf98a029 100644 --- a/src/timesync/meson.build +++ b/src/timesync/meson.build @@ -4,6 +4,8 @@ systemd_timesyncd_sources = files(''' timesyncd.c + timesyncd-bus.c + timesyncd-bus.h timesyncd-conf.c timesyncd-conf.h timesyncd-manager.c @@ -27,6 +29,10 @@ if conf.get('ENABLE_TIMESYNCD') == 1 configuration : substs) install_data(timesyncd_conf, install_dir : pkgsysconfdir) + install_data('org.freedesktop.timesync1.conf', + install_dir : dbuspolicydir) + install_data('org.freedesktop.timesync1.service', + install_dir : dbussystemservicedir) endif ############################################################ diff --git a/src/timesync/org.freedesktop.timesync1.conf b/src/timesync/org.freedesktop.timesync1.conf new file mode 100644 index 0000000000..eccdbec718 --- /dev/null +++ b/src/timesync/org.freedesktop.timesync1.conf @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/timesync/org.freedesktop.timesync1.service b/src/timesync/org.freedesktop.timesync1.service new file mode 100644 index 0000000000..84b4b4f973 --- /dev/null +++ b/src/timesync/org.freedesktop.timesync1.service @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[D-BUS Service] +Name=org.freedesktop.timesync1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.timesync1.service diff --git a/src/timesync/timesyncd-bus.c b/src/timesync/timesyncd-bus.c new file mode 100644 index 0000000000..2068601e70 --- /dev/null +++ b/src/timesync/timesyncd-bus.c @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "sd-bus.h" + +#include "alloc-util.h" +#include "bus-util.h" +#include "in-addr-util.h" +#include "log.h" +#include "macro.h" +#include "time-util.h" +#include "timesyncd-bus.h" + +static int property_get_servers( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ServerName *p, **s = userdata; + int r; + + assert(s); + assert(bus); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + LIST_FOREACH(names, p, *s) { + r = sd_bus_message_append(reply, "s", p->string); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_current_server_name( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ServerName **s = userdata; + + assert(s); + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", *s ? (*s)->string : ""); +} + +static int property_get_current_server_address( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ServerAddress *a; + int r; + + assert(bus); + assert(reply); + assert(userdata); + + a = *(ServerAddress **) userdata; + + if (!a) + return sd_bus_message_append(reply, "(iay)", AF_UNSPEC, 0); + + r = sd_bus_message_open_container(reply, 'r', "iay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "i", a->sockaddr.sa.sa_family); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &a->sockaddr.in.sin_addr, FAMILY_ADDRESS_SIZE(a->sockaddr.sa.sa_family)); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +static const sd_bus_vtable manager_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("LinkNTPServers", "as", property_get_servers, offsetof(Manager, link_servers), 0), + SD_BUS_PROPERTY("SystemNTPServers", "as", property_get_servers, offsetof(Manager, system_servers), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("FallbackNTPServers", "as", property_get_servers, offsetof(Manager, fallback_servers), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ServerName", "s", property_get_current_server_name, offsetof(Manager, current_server_name), 0), + SD_BUS_PROPERTY("ServerAddress", "(iay)", property_get_current_server_address, offsetof(Manager, current_server_address), 0), + SD_BUS_PROPERTY("RootDistanceMaxUSec", "t", bus_property_get_usec, offsetof(Manager, max_root_distance_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PollIntervalMinUSec", "t", bus_property_get_usec, offsetof(Manager, poll_interval_min_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PollIntervalMaxUSec", "t", bus_property_get_usec, offsetof(Manager, poll_interval_max_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PollIntervalUSec", "t", bus_property_get_usec, offsetof(Manager, poll_interval_usec), 0), + + SD_BUS_VTABLE_END +}; + +int manager_connect_bus(Manager *m) { + int r; + + assert(m); + + if (m->bus) + return 0; + + r = bus_open_system_watch_bind_with_description(&m->bus, "bus-api-timesync"); + if (r < 0) + return log_error_errno(r, "Failed to connect to bus: %m"); + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/timesync1", "org.freedesktop.timesync1.Manager", manager_vtable, m); + if (r < 0) + return log_error_errno(r, "Failed to add manager object vtable: %m"); + + r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.timesync1", 0, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to request name: %m"); + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + return 0; +} diff --git a/src/timesync/timesyncd-bus.h b/src/timesync/timesyncd-bus.h new file mode 100644 index 0000000000..6670ffbc36 --- /dev/null +++ b/src/timesync/timesyncd-bus.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "timesyncd-manager.h" + +int manager_connect_bus(Manager *m); diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index c5721a5752..520e4e1f0f 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -986,6 +986,8 @@ void manager_free(Manager *m) { sd_resolve_unref(m->resolve); sd_event_unref(m->event); + sd_bus_unref(m->bus); + free(m); } diff --git a/src/timesync/timesyncd-manager.h b/src/timesync/timesyncd-manager.h index 62f129ef9b..500f94c5ca 100644 --- a/src/timesync/timesyncd-manager.h +++ b/src/timesync/timesyncd-manager.h @@ -7,6 +7,7 @@ Copyright 2014 Kay Sievers, Lennart Poettering ***/ +#include "sd-bus.h" #include "sd-event.h" #include "sd-network.h" #include "sd-resolve.h" @@ -27,6 +28,7 @@ typedef struct Manager Manager; #define NTP_POLL_INTERVAL_MAX_USEC (2048 * USEC_PER_SEC) struct Manager { + sd_bus *bus; sd_event *event; sd_resolve *resolve; diff --git a/src/timesync/timesyncd.c b/src/timesync/timesyncd.c index 8a4999c74a..dbecfb6ce8 100644 --- a/src/timesync/timesyncd.c +++ b/src/timesync/timesyncd.c @@ -16,6 +16,7 @@ #include "network-util.h" #include "process-util.h" #include "signal-util.h" +#include "timesyncd-bus.h" #include "timesyncd-conf.h" #include "timesyncd-manager.h" #include "user-util.h" @@ -133,6 +134,12 @@ int main(int argc, char *argv[]) { goto finish; } + r = manager_connect_bus(m); + if (r < 0) { + log_error_errno(r, "Could not connect to bus: %m"); + goto finish; + } + if (clock_is_localtime(NULL) > 0) { log_info("The system is configured to read the RTC time in the local time zone. " "This mode cannot be fully supported. All system time to RTC updates are disabled."); diff --git a/units/systemd-timesyncd.service.in b/units/systemd-timesyncd.service.in index 5b8b243f17..913dac773a 100644 --- a/units/systemd-timesyncd.service.in +++ b/units/systemd-timesyncd.service.in @@ -45,3 +45,4 @@ StateDirectory=systemd/timesync [Install] WantedBy=sysinit.target +Alias=dbus-org.freedesktop.timesync1.service From c583dd564c2fd89edb785aa94e1869650732dbcb Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 30 Apr 2018 23:51:04 +0900 Subject: [PATCH 2/5] timesync: try to reload DBus configuration when RequestName() fails If dbus.service starts earlier than the dynamic user systemd-timesync is realized, then the dbus policy file for timesyncd does not loaded and timesyncd fails to request name. To support such case, try to reload dbus configuration when requesting name fails. --- src/timesync/timesyncd-bus.c | 98 +++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/src/timesync/timesyncd-bus.c b/src/timesync/timesyncd-bus.c index 2068601e70..eb1c0b9c48 100644 --- a/src/timesync/timesyncd-bus.c +++ b/src/timesync/timesyncd-bus.c @@ -3,6 +3,8 @@ #include "sd-bus.h" #include "alloc-util.h" +#include "bus-internal.h" +#include "bus-protocol.h" #include "bus-util.h" #include "in-addr-util.h" #include "log.h" @@ -109,6 +111,100 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_VTABLE_END }; +static int reload_dbus_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + const sd_bus_error *e; + int r; + + assert(m); + + e = sd_bus_message_get_error(m); + if (e) { + log_error_errno(sd_bus_error_get_errno(e), "Failed to reload DBus configuration: %s", e->message); + return 1; + } + + /* Here, use the default request name handler to avoid an infinite loop of reloading and requesting. */ + r = sd_bus_request_name_async(sd_bus_message_get_bus(m), NULL, "org.freedesktop.timesync1", 0, NULL, NULL); + if (r < 0) + log_error_errno(r, "Failed to request name: %m"); + + return 1; +} + +static int request_name_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + uint32_t ret; + int r; + + assert(m); + + if (sd_bus_message_is_method_error(m, NULL)) { + const sd_bus_error *e = sd_bus_message_get_error(m); + + if (!sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED)) { + log_debug_errno(sd_bus_error_get_errno(e), + "Unable to request name, failing connection: %s", + e->message); + + bus_enter_closing(sd_bus_message_get_bus(m)); + return 1; + } + + log_debug_errno(sd_bus_error_get_errno(e), + "Unable to request name, retry after reloading DBus configuration: %s", + e->message); + + /* If systemd-timesyncd.service enables DynamicUser= and dbus.service + * started before the dynamic user is realized, then the DBus policy + * about timesyncd has not been enabled yet. So, let's try to reload + * DBus configuration, and after that request name again. Note that it + * seems that no privileges are necessary to call the following method. */ + + r = sd_bus_call_method_async( + sd_bus_message_get_bus(m), + NULL, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "ReloadConfig", + reload_dbus_handler, + NULL, NULL); + if (r < 0) { + log_error_errno(r, "Failed to reload DBus configuration: %m"); + bus_enter_closing(sd_bus_message_get_bus(m)); + } + + return 1; + } + + r = sd_bus_message_read(m, "u", &ret); + if (r < 0) + return r; + + switch (ret) { + + case BUS_NAME_ALREADY_OWNER: + log_debug("Already owner of requested service name, ignoring."); + return 1; + + case BUS_NAME_IN_QUEUE: + log_debug("In queue for requested service name."); + return 1; + + case BUS_NAME_PRIMARY_OWNER: + log_debug("Successfully acquired requested service name."); + return 1; + + case BUS_NAME_EXISTS: + log_debug("Requested service name already owned, failing connection."); + bus_enter_closing(sd_bus_message_get_bus(m)); + return 1; + } + + log_debug("Unexpected response from RequestName(), failing connection."); + bus_enter_closing(sd_bus_message_get_bus(m)); + return 1; +} + int manager_connect_bus(Manager *m) { int r; @@ -125,7 +221,7 @@ int manager_connect_bus(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to add manager object vtable: %m"); - r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.timesync1", 0, NULL, NULL); + r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.timesync1", 0, request_name_handler, NULL); if (r < 0) return log_error_errno(r, "Failed to request name: %m"); From 66086a4030e16e9a2c5dd48d5d6538a08cf7bfad Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 30 Apr 2018 22:37:02 +0900 Subject: [PATCH 3/5] timesync: save and expose NTP responce on bus --- src/timesync/meson.build | 1 + src/timesync/timesyncd-bus.c | 58 +++++++++++++++++++++++++++ src/timesync/timesyncd-manager.c | 60 ++++++---------------------- src/timesync/timesyncd-manager.h | 10 ++++- src/timesync/timesyncd-ntp-message.h | 45 +++++++++++++++++++++ 5 files changed, 126 insertions(+), 48 deletions(-) create mode 100644 src/timesync/timesyncd-ntp-message.h diff --git a/src/timesync/meson.build b/src/timesync/meson.build index 5ecf98a029..c8113ff30b 100644 --- a/src/timesync/meson.build +++ b/src/timesync/meson.build @@ -10,6 +10,7 @@ systemd_timesyncd_sources = files(''' timesyncd-conf.h timesyncd-manager.c timesyncd-manager.h + timesyncd-ntp-message.h timesyncd-server.c timesyncd-server.h '''.split()) diff --git a/src/timesync/timesyncd-bus.c b/src/timesync/timesyncd-bus.c index eb1c0b9c48..1932043fb9 100644 --- a/src/timesync/timesyncd-bus.c +++ b/src/timesync/timesyncd-bus.c @@ -95,6 +95,62 @@ static int property_get_current_server_address( return sd_bus_message_close_container(reply); } +static usec_t ntp_ts_short_to_usec(const struct ntp_ts_short *ts) { + return be16toh(ts->sec) * USEC_PER_SEC + (be16toh(ts->frac) * USEC_PER_SEC) / (usec_t) 0x10000ULL; +} + +static usec_t ntp_ts_to_usec(const struct ntp_ts *ts) { + return (be32toh(ts->sec) - OFFSET_1900_1970) * USEC_PER_SEC + (be32toh(ts->frac) * USEC_PER_SEC) / (usec_t) 0x100000000ULL; +} + +static int property_get_ntp_message( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + int r; + + assert(m); + assert(reply); + + r = sd_bus_message_open_container(reply, 'r', "uuuuittayttttbtt"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "uuuuitt", + NTP_FIELD_LEAP(m->ntpmsg.field), + NTP_FIELD_VERSION(m->ntpmsg.field), + NTP_FIELD_MODE(m->ntpmsg.field), + m->ntpmsg.stratum, + m->ntpmsg.precision, + ntp_ts_short_to_usec(&m->ntpmsg.root_delay), + ntp_ts_short_to_usec(&m->ntpmsg.root_dispersion)); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', m->ntpmsg.refid, 4); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "ttttbtt", + timespec_load(&m->origin_time), + ntp_ts_to_usec(&m->ntpmsg.recv_time), + ntp_ts_to_usec(&m->ntpmsg.trans_time), + timespec_load(&m->dest_time), + m->spike, + m->packet_count, + (usec_t) (m->samples_jitter * USEC_PER_SEC)); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + static const sd_bus_vtable manager_vtable[] = { SD_BUS_VTABLE_START(0), @@ -107,6 +163,8 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PROPERTY("PollIntervalMinUSec", "t", bus_property_get_usec, offsetof(Manager, poll_interval_min_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PollIntervalMaxUSec", "t", bus_property_get_usec, offsetof(Manager, poll_interval_max_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PollIntervalUSec", "t", bus_property_get_usec, offsetof(Manager, poll_interval_usec), 0), + SD_BUS_PROPERTY("NTPMessage", "(uuuuittayttttbtt)", property_get_ntp_message, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Frequency", "x", NULL, offsetof(Manager, drift_freq), 0), SD_BUS_VTABLE_END }; diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index 520e4e1f0f..4f639577ad 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -28,7 +28,6 @@ #include "network-util.h" #include "ratelimit.h" #include "socket-util.h" -#include "sparse-endian.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -50,59 +49,18 @@ */ #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 -#define NTP_MODE_CLIENT 3 -#define NTP_MODE_SERVER 4 -#define NTP_FIELD_LEAP(f) (((f) >> 6) & 3) -#define NTP_FIELD_VERSION(f) (((f) >> 3) & 7) -#define NTP_FIELD_MODE(f) ((f) & 7) -#define NTP_FIELD(l, v, m) (((l) << 6) | ((v) << 3) | (m)) - /* Default of maximum acceptable root distance in microseconds. */ #define NTP_MAX_ROOT_DISTANCE (5 * USEC_PER_SEC) /* Maximum number of missed replies before selecting another source. */ #define NTP_MAX_MISSED_REPLIES 2 -/* - * "NTP timestamps are represented as a 64-bit unsigned fixed-point number, - * in seconds relative to 0h on 1 January 1900." - */ -#define OFFSET_1900_1970 UINT64_C(2208988800) - #define RETRY_USEC (30*USEC_PER_SEC) #define RATELIMIT_INTERVAL_USEC (10*USEC_PER_SEC) #define RATELIMIT_BURST 10 #define TIMEOUT_USEC (10*USEC_PER_SEC) -struct ntp_ts { - be32_t sec; - be32_t frac; -} _packed_; - -struct ntp_ts_short { - be16_t sec; - be16_t frac; -} _packed_; - -struct ntp_msg { - uint8_t field; - uint8_t stratum; - int8_t poll; - int8_t precision; - struct ntp_ts_short root_delay; - struct ntp_ts_short root_dispersion; - char refid[4]; - struct ntp_ts reference_time; - struct ntp_ts origin_time; - struct ntp_ts recv_time; - struct ntp_ts trans_time; -} _packed_; - static int manager_arm_timer(Manager *m, usec_t next); static int manager_clock_watch_setup(Manager *m); static int manager_listen_setup(Manager *m); @@ -357,18 +315,18 @@ static int manager_adjust_clock(Manager *m, double offset, int leap_sec) { (void) touch("/var/lib/systemd/timesync/clock"); (void) touch("/run/systemd/timesync/synchronized"); - m->drift_ppm = tmx.freq / 65536; + m->drift_freq = tmx.freq; log_debug(" status : %04i %s\n" " time now : %"PRI_TIME".%03"PRI_USEC"\n" " constant : %"PRI_TIMEX"\n" " offset : %+.3f sec\n" - " freq offset : %+"PRI_TIMEX" (%i ppm)\n", + " freq offset : %+"PRI_TIMEX" (%+"PRI_TIMEX" ppm)\n", tmx.status, tmx.status & STA_UNSYNC ? "unsync" : "sync", tmx.time.tv_sec, tmx.time.tv_usec / NSEC_PER_MSEC, tmx.constant, (double)tmx.offset / NSEC_PER_SEC, - tmx.freq, m->drift_ppm); + tmx.freq, tmx.freq / 65536); return 0; } @@ -652,10 +610,18 @@ static int manager_receive_response(sd_event_source *source, int fd, uint32_t re log_error_errno(r, "Failed to call clock_adjtime(): %m"); } - log_debug("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, + /* Save NTP response */ + m->ntpmsg = ntpmsg; + m->origin_time = m->trans_time; + m->dest_time = *recv_time; + m->spike = spike; + + log_debug("interval/delta/delay/jitter/drift " USEC_FMT "s/%+.3fs/%.3fs/%.3fs/%+"PRI_TIMEX"ppm%s", + m->poll_interval_usec / USEC_PER_SEC, offset, delay, m->samples_jitter, m->drift_freq / 65536, spike ? " (ignored)" : ""); + (void) sd_bus_emit_properties_changed(m->bus, "/org/freedesktop/timesync1", "org.freedesktop.timesync1.Manager", "NTPMessage", NULL); + if (!m->good) { _cleanup_free_ char *pretty = NULL; diff --git a/src/timesync/timesyncd-manager.h b/src/timesync/timesyncd-manager.h index 500f94c5ca..1f04566254 100644 --- a/src/timesync/timesyncd-manager.h +++ b/src/timesync/timesyncd-manager.h @@ -7,6 +7,8 @@ Copyright 2014 Kay Sievers, Lennart Poettering ***/ +#include + #include "sd-bus.h" #include "sd-event.h" #include "sd-network.h" @@ -15,6 +17,7 @@ #include "list.h" #include "ratelimit.h" #include "time-util.h" +#include "timesyncd-ntp-message.h" typedef struct Manager Manager; @@ -81,7 +84,7 @@ struct Manager { /* last change */ bool jumped; bool sync; - int drift_ppm; + long drift_freq; /* watch for time changes */ sd_event_source *event_clock_watch; @@ -92,6 +95,11 @@ struct Manager { /* RTC runs in local time, leave it alone */ bool rtc_local_time; + + /* NTP response */ + struct ntp_msg ntpmsg; + struct timespec origin_time, dest_time; + bool spike; }; int manager_new(Manager **ret); diff --git a/src/timesync/timesyncd-ntp-message.h b/src/timesync/timesyncd-ntp-message.h new file mode 100644 index 0000000000..14fba6d2cc --- /dev/null +++ b/src/timesync/timesyncd-ntp-message.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "sparse-endian.h" + +/* NTP protocol, packet header */ +#define NTP_LEAP_PLUSSEC 1 +#define NTP_LEAP_MINUSSEC 2 +#define NTP_LEAP_NOTINSYNC 3 +#define NTP_MODE_CLIENT 3 +#define NTP_MODE_SERVER 4 +#define NTP_FIELD_LEAP(f) (((f) >> 6) & 3) +#define NTP_FIELD_VERSION(f) (((f) >> 3) & 7) +#define NTP_FIELD_MODE(f) ((f) & 7) +#define NTP_FIELD(l, v, m) (((l) << 6) | ((v) << 3) | (m)) + +/* + * "NTP timestamps are represented as a 64-bit unsigned fixed-point number, + * in seconds relative to 0h on 1 January 1900." + */ +#define OFFSET_1900_1970 UINT64_C(2208988800) + +struct ntp_ts { + be32_t sec; + be32_t frac; +} _packed_; + +struct ntp_ts_short { + be16_t sec; + be16_t frac; +} _packed_; + +struct ntp_msg { + uint8_t field; + uint8_t stratum; + int8_t poll; + int8_t precision; + struct ntp_ts_short root_delay; + struct ntp_ts_short root_dispersion; + char refid[4]; + struct ntp_ts reference_time; + struct ntp_ts origin_time; + struct ntp_ts recv_time; + struct ntp_ts trans_time; +} _packed_; From 6129ec852ee470a3682d55f87852ee7ccabb5520 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 3 May 2018 18:07:43 +0900 Subject: [PATCH 4/5] timedatectl: add timesync-status and show-timesync commands Closes #1589. --- meson.build | 10 + src/timedate/timedatectl.c | 474 ++++++++++++++++++++++++++++++++++++- 2 files changed, 471 insertions(+), 13 deletions(-) diff --git a/meson.build b/meson.build index 0086f77483..3fa4f7f327 100644 --- a/meson.build +++ b/meson.build @@ -1237,6 +1237,13 @@ foreach term : ['utmp', m4_defines += have ? ['-D' + name] : [] endforeach +if get_option('timedated') or get_option('timesyncd') + conf.set10('ENABLE_TIMEDATECTL', true) + m4_defines += ['-DENABLE_TIMEDATECTL'] +else + conf.set10('ENABLE_TIMEDATECTL', false) +endif + want_tests = get_option('tests') install_tests = get_option('install-tests') slow_tests = get_option('slow-tests') @@ -1863,12 +1870,15 @@ if conf.get('ENABLE_TIMEDATED') == 1 install_rpath : rootlibexecdir, install : true, install_dir : rootlibexecdir) +endif +if conf.get('ENABLE_TIMEDATECTL') == 1 exe = executable('timedatectl', 'src/timedate/timedatectl.c', include_directories : includes, install_rpath : rootlibexecdir, link_with : [libshared], + dependencies : [libm], install : true) public_programs += [exe] endif diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 9a803d1221..e52a64a9c5 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -15,9 +16,12 @@ #include "bus-error.h" #include "bus-util.h" +#include "in-addr-util.h" #include "pager.h" #include "parse-util.h" #include "spawn-polkit-agent.h" +#include "sparse-endian.h" +#include "string-table.h" #include "strv.h" #include "terminal-util.h" #include "util.h" @@ -28,6 +32,10 @@ static bool arg_ask_password = true; static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; static char *arg_host = NULL; static bool arg_adjust_system_clock = false; +static bool arg_monitor = false; +static char **arg_property = NULL; +static bool arg_value = false; +static bool arg_all = false; typedef struct StatusInfo { usec_t time; @@ -272,6 +280,405 @@ static int list_timezones(int argc, char **argv, void *userdata) { return 0; } +typedef struct NTPStatusInfo { + const char *server_name; + char *server_address; + usec_t poll_interval, poll_max, poll_min; + usec_t root_distance_max; + + uint32_t leap, version, mode, stratum; + int32_t precision; + usec_t root_delay, root_dispersion; + union { + char str[5]; + uint32_t val; + } reference; + usec_t origin, recv, trans, dest; + + bool spike; + uint64_t packet_count; + usec_t jitter; + + int64_t freq; +} NTPStatusInfo; + +static void ntp_status_info_clear(NTPStatusInfo *p) { + p->server_address = mfree(p->server_address); +} + +static const char * const ntp_leap_table[4] = { + [0] = "normal", + [1] = "last minute of the day has 61 seconds", + [2] = "last minute of the day has 59 seconds", + [3] = "not synchronized", +}; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(ntp_leap, uint32_t); +#pragma GCC diagnostic pop + +static void print_ntp_status_info(NTPStatusInfo *i) { + char ts[FORMAT_TIMESPAN_MAX], tmin[FORMAT_TIMESPAN_MAX], tmax[FORMAT_TIMESPAN_MAX]; + usec_t delay, t14, t23, offset, root_distance; + bool offset_sign; + + assert(i); + + /* + * "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" + */ + + printf(" Server: %s (%s)\n", + i->server_address, i->server_name); + printf("Poll interval: %s (min: %s; max %s)\n", + format_timespan(ts, sizeof(ts), i->poll_interval, 0), + format_timespan(tmin, sizeof(tmin), i->poll_min, 0), + format_timespan(tmax, sizeof(tmax), i->poll_max, 0)); + + if (i->packet_count == 0) { + printf(" Packet count: 0\n"); + return; + } + + if (i->dest < i->origin || i->trans < i->recv || i->dest - i->origin < i->trans - i->recv) { + log_error("Invalid NTP response"); + return; + } + + delay = (i->dest - i->origin) - (i->trans - i->recv); + + t14 = i->origin + i->dest; + t23 = i->recv + i->trans; + offset_sign = t14 < t23; + offset = (offset_sign ? t23 - t14 : t14 - t23) / 2; + + root_distance = i->root_delay / 2 + i->root_dispersion; + + printf(" Leap: %s\n" + " Version: %" PRIu32 "\n" + " Stratum: %" PRIu32 "\n", + ntp_leap_to_string(i->leap), + i->version, + i->stratum); + if (i->stratum <= 1) + printf(" Reference: %s\n", i->reference.str); + else + printf(" Reference: %" PRIX32 "\n", be32toh(i->reference.val)); + printf(" Precision: %s (%" PRIi32 ")\n", + format_timespan(ts, sizeof(ts), DIV_ROUND_UP((nsec_t) (exp2(i->precision) * NSEC_PER_SEC), NSEC_PER_USEC), 0), + i->precision); + printf("Root distance: %s (max: %s)\n", + format_timespan(ts, sizeof(ts), root_distance, 0), + format_timespan(tmax, sizeof(tmax), i->root_distance_max, 0)); + printf(" Offset: %s%s\n", + offset_sign ? "+" : "-", + format_timespan(ts, sizeof(ts), offset, 0)); + printf(" Delay: %s\n", + format_timespan(ts, sizeof(ts), delay, 0)); + printf(" Jitter: %s\n", + format_timespan(ts, sizeof(ts), i->jitter, 0)); + printf(" Packet count: %" PRIu64 "\n", i->packet_count); + + if (!i->spike) + printf(" Frequency: %+.3fppm\n", + (double) i->freq / 0x10000); +} + +static int map_server_address(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { + char **p = (char **) userdata; + const void *d; + int family, r; + size_t sz; + + assert(p); + + r = sd_bus_message_enter_container(m, 'r', "iay"); + if (r < 0) + return r; + + r = sd_bus_message_read(m, "i", &family); + if (r < 0) + return r; + + r = sd_bus_message_read_array(m, 'y', &d, &sz); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + if (sz == 0 && family == AF_UNSPEC) { + *p = mfree(*p); + return 0; + } + + if (!IN_SET(family, AF_INET, AF_INET6)) { + log_error("Unknown address family %i", family); + return -EINVAL; + } + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + log_error("Invalid address size"); + return -EINVAL; + } + + r = in_addr_to_string(family, d, p); + if (r < 0) + return r; + + return 0; +} + +static int map_ntp_message(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { + NTPStatusInfo *p = userdata; + const void *d; + size_t sz; + int32_t b; + int r; + + assert(p); + + r = sd_bus_message_enter_container(m, 'r', "uuuuittayttttbtt"); + if (r < 0) + return r; + + r = sd_bus_message_read(m, "uuuuitt", + &p->leap, &p->version, &p->mode, &p->stratum, &p->precision, + &p->root_delay, &p->root_dispersion); + if (r < 0) + return r; + + r = sd_bus_message_read_array(m, 'y', &d, &sz); + if (r < 0) + return r; + + r = sd_bus_message_read(m, "ttttbtt", + &p->origin, &p->recv, &p->trans, &p->dest, + &b, &p->packet_count, &p->jitter); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + if (sz != 4) + return -EINVAL; + + memcpy(p->reference.str, d, sz); + + p->spike = !!b; + + return 0; +} + +static int show_timesync_status_once(sd_bus *bus) { + static const struct bus_properties_map map_timesync[] = { + { "ServerName", "s", NULL, offsetof(NTPStatusInfo, server_name) }, + { "ServerAddress", "(iay)", map_server_address, offsetof(NTPStatusInfo, server_address) }, + { "PollIntervalUSec", "t", NULL, offsetof(NTPStatusInfo, poll_interval) }, + { "PollIntervalMinUSec", "t", NULL, offsetof(NTPStatusInfo, poll_min) }, + { "PollIntervalMaxUSec", "t", NULL, offsetof(NTPStatusInfo, poll_max) }, + { "RootDistanceMaxUSec", "t", NULL, offsetof(NTPStatusInfo, root_distance_max) }, + { "NTPMessage", "(uuuuittayttttbtt)", map_ntp_message, 0 }, + { "Frequency", "x", NULL, offsetof(NTPStatusInfo, freq) }, + {} + }; + _cleanup_(ntp_status_info_clear) NTPStatusInfo info = {}; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(bus); + + r = bus_map_all_properties(bus, + "org.freedesktop.timesync1", + "/org/freedesktop/timesync1", + map_timesync, + BUS_MAP_BOOLEAN_AS_BOOL, + &error, + &m, + &info); + if (r < 0) + return log_error_errno(r, "Failed to query server: %s", bus_error_message(&error, r)); + + if (arg_monitor && !terminal_is_dumb()) + fputs(ANSI_HOME_CLEAR, stdout); + + print_ntp_status_info(&info); + + return 0; +} + +static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char *name; + int r; + + assert(m); + + r = sd_bus_message_read(m, "s", &name); + if (r < 0) + return log_error_errno(r, "Failed to read interface name: %m"); + + if (!streq_ptr(name, "org.freedesktop.timesync1.Manager")) + return 0; + + return show_timesync_status_once(sd_bus_message_get_bus(m)); +} + +static int show_timesync_status(int argc, char **argv, void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + r = show_timesync_status_once(bus); + if (r < 0) + return r; + + if (!arg_monitor) + return 0; + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_match_signal(bus, + NULL, + "org.freedesktop.timesync1", + "/org/freedesktop/timesync1", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + on_properties_changed, NULL); + if (r < 0) + return log_error_errno(r, "Failed to request match for PropertiesChanged signal: %m"); + + r = sd_bus_attach_event(bus, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + return 0; +} + +#define property(name, fmt, ...) \ + do { \ + if (value) \ + printf(fmt "\n", __VA_ARGS__); \ + else \ + printf("%s=" fmt "\n", name, __VA_ARGS__); \ + } while (0) + +static int print_timesync_property(const char *name, sd_bus_message *m, bool value, bool all) { + char type; + const char *contents; + int r; + + assert(name); + assert(m); + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) + return r; + + switch (type) { + + case SD_BUS_TYPE_STRUCT: + if (streq(name, "NTPMessage")) { + _cleanup_(ntp_status_info_clear) NTPStatusInfo i = {}; + char ts[FORMAT_TIMESPAN_MAX], stamp[FORMAT_TIMESTAMP_MAX]; + + r = map_ntp_message(NULL, NULL, m, NULL, &i); + if (r < 0) + return r; + + if (i.packet_count == 0) + return 1; + + if (!value) { + fputs(name, stdout); + fputc('=', stdout); + } + + printf("{ Leap=%u, Version=%u, Mode=%u, Stratum=%u, Precision=%i,", + i.leap, i.version, i.mode, i.stratum, i.precision); + printf(" RootDelay=%s,", + format_timespan(ts, sizeof(ts), i.root_delay, 0)); + printf(" RootDispersion=%s,", + format_timespan(ts, sizeof(ts), i.root_dispersion, 0)); + + if (i.stratum == 1) + printf(" Reference=%s,", i.reference.str); + else + printf(" Reference=%" PRIX32 ",", be32toh(i.reference.val)); + + printf(" OriginateTimestamp=%s,", + format_timestamp(stamp, sizeof(stamp), i.origin)); + printf(" ReceiveTimestamp=%s,", + format_timestamp(stamp, sizeof(stamp), i.recv)); + printf(" TransmitTimestamp=%s,", + format_timestamp(stamp, sizeof(stamp), i.trans)); + printf(" DestinationTimestamp=%s,", + format_timestamp(stamp, sizeof(stamp), i.dest)); + printf(" Ignored=%s PacketCount=%" PRIu64 ",", + yes_no(i.spike), i.packet_count); + printf(" Jitter=%s }\n", + format_timespan(ts, sizeof(ts), i.jitter, 0)); + + return 1; + + } else if (streq(name, "ServerAddress")) { + _cleanup_free_ char *str = NULL; + + r = map_server_address(NULL, NULL, m, NULL, &str); + if (r < 0) + return r; + + if (arg_all || !isempty(str)) + property(name, "%s", str); + + return 1; + } + break; + } + + return 0; +} + +static int show_timesync(int argc, char **argv, void *userdata) { + sd_bus *bus = userdata; + int r; + + assert(bus); + + r = bus_print_all_properties(bus, + "org.freedesktop.timesync1", + "/org/freedesktop/timesync1", + print_timesync_property, + arg_property, + arg_value, + arg_all, + NULL); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + static int help(void) { printf("%s [OPTIONS...] COMMAND ...\n\n" "Query or change system time and date settings.\n\n" @@ -281,15 +688,24 @@ static int help(void) { " --no-ask-password Do not prompt for password\n" " -H --host=[USER@]HOST Operate on remote host\n" " -M --machine=CONTAINER Operate on local container\n" - " --adjust-system-clock Adjust system clock when changing local RTC mode\n\n" + " --adjust-system-clock Adjust system clock when changing local RTC mode\n" + " --monitor Monitor status of systemd-timesyncd\n" + " -p --property=NAME Show only properties by this name\n" + " -a --all Show all properties, including empty ones\n" + " --value When showing properties, only print the value\n" + "\n" "Commands:\n" " status Show current time settings\n" " set-time TIME Set system time\n" " set-timezone ZONE Set system time zone\n" " list-timezones Show known time zones\n" " set-local-rtc BOOL Control whether RTC is in local time\n" - " set-ntp BOOL Enable or disable network time synchronization\n", - program_invocation_short_name); + " set-ntp BOOL Enable or disable network time synchronization\n" + "\n" + "systemd-timesyncd Commands:\n" + " timesync-status Show status of systemd-timesyncd\n" + " show-timesync Show properties of systemd-timesyncd\n" + , program_invocation_short_name); return 0; } @@ -304,7 +720,9 @@ static int parse_argv(int argc, char *argv[]) { ARG_VERSION = 0x100, ARG_NO_PAGER, ARG_ADJUST_SYSTEM_CLOCK, - ARG_NO_ASK_PASSWORD + ARG_NO_ASK_PASSWORD, + ARG_MONITOR, + ARG_VALUE, }; static const struct option options[] = { @@ -315,15 +733,19 @@ static int parse_argv(int argc, char *argv[]) { { "machine", required_argument, NULL, 'M' }, { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK }, + { "monitor", no_argument, NULL, ARG_MONITOR }, + { "property", required_argument, NULL, 'p' }, + { "all", no_argument, NULL, 'a' }, + { "value", no_argument, NULL, ARG_VALUE }, {} }; - int c; + int c, r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hH:M:p:a", options, NULL)) >= 0) switch (c) { @@ -355,6 +777,30 @@ static int parse_argv(int argc, char *argv[]) { arg_no_pager = true; break; + case ARG_MONITOR: + arg_monitor = true; + break; + + case 'p': { + r = strv_extend(&arg_property, optarg); + if (r < 0) + return log_oom(); + + /* If the user asked for a particular + * property, show it to him, even if it is + * empty. */ + arg_all = true; + break; + } + + case 'a': + arg_all = true; + break; + + case ARG_VALUE: + arg_value = true; + break; + case '?': return -EINVAL; @@ -368,13 +814,15 @@ static int parse_argv(int argc, char *argv[]) { static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) { static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, show_status }, - { "set-time", 2, 2, 0, set_time }, - { "set-timezone", 2, 2, 0, set_timezone }, - { "list-timezones", VERB_ANY, 1, 0, list_timezones }, - { "set-local-rtc", 2, 2, 0, set_local_rtc }, - { "set-ntp", 2, 2, 0, set_ntp }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ + { "status", VERB_ANY, 1, VERB_DEFAULT, show_status }, + { "set-time", 2, 2, 0, set_time }, + { "set-timezone", 2, 2, 0, set_timezone }, + { "list-timezones", VERB_ANY, 1, 0, list_timezones }, + { "set-local-rtc", 2, 2, 0, set_local_rtc }, + { "set-ntp", 2, 2, 0, set_ntp }, + { "timesync-status", VERB_ANY, 1, 0, show_timesync_status }, + { "show-timesync", VERB_ANY, 1, 0, show_timesync }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ {} }; From 1e4acc77b0738b76b982f39eba854e7d81f44b80 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 30 Apr 2018 22:42:40 +0900 Subject: [PATCH 5/5] man: add explanations of show-timesync and timesync-status commands --- man/rules/meson.build | 2 +- man/systemd-timesyncd.service.xml | 4 ++ man/timedatectl.xml | 92 ++++++++++++++++++++++++++++++- 3 files changed, 96 insertions(+), 2 deletions(-) diff --git a/man/rules/meson.build b/man/rules/meson.build index b0919b9d44..6b07cfc4fe 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -706,7 +706,7 @@ manpages = [ ['systemd.unit', '5', [], ''], ['sysusers.d', '5', [], 'ENABLE_SYSUSERS'], ['telinit', '8', [], ''], - ['timedatectl', '1', [], 'ENABLE_TIMEDATED'], + ['timedatectl', '1', [], 'ENABLE_TIMEDATECTL'], ['timesyncd.conf', '5', ['timesyncd.conf.d'], 'ENABLE_TIMESYNCD'], ['tmpfiles.d', '5', [], ''], ['udev', '7', [], ''], diff --git a/man/systemd-timesyncd.service.xml b/man/systemd-timesyncd.service.xml index 49e00e2b91..b5eb8a4ac2 100644 --- a/man/systemd-timesyncd.service.xml +++ b/man/systemd-timesyncd.service.xml @@ -70,6 +70,10 @@ timedatectl1's set-ntp command may be used to enable and start, or disable and stop this service. + + timedatectl1's + timesync-status or show-timesync command can be used to show the + current status of this service. diff --git a/man/timedatectl.xml b/man/timedatectl.xml index 02b86b6641..b6caa5458d 100644 --- a/man/timedatectl.xml +++ b/man/timedatectl.xml @@ -10,7 +10,7 @@ Copyright 2012 Lennart Poettering --> - @@ -53,6 +53,11 @@ systemd-firstboot1 to initialize the system time zone for mounted (but not booted) system images. + + timedatectl may be used to show the current status of + systemd-timesyncd.service8. + + @@ -78,6 +83,43 @@ clock. + + + + If timesync-status is invoked and this option is passed, + then timedatectl monitors the status of + systemd-timesyncd.service8 + and updates the outputs. Use Ctrl-C to terminate the monitoring. + + + + + + + When showing properties of + systemd-timesyncd.service8, + show all properties regardless of whether they are set or not. + + + + + + + When showing properties of + systemd-timesyncd.service8, + limit display to certain properties as specified as argument. If not specified, all set properties are shown. + The argument should be a property name, such as ServerName. If specified more than once, + all properties with the specified names are shown. + + + + + + + When printing properties with show-timesync, only print the value, and skip the + property name and =. + + @@ -165,6 +207,35 @@ + systemd-timesyncd Commands + + The following commands are specific to + systemd-timesyncd.service8. + + + + + timesync-status + + Show current status of + systemd-timesyncd.service8. + If is specified, then this will monitor the status updates. + + + + show-timesync + + Show properties of the manager of + systemd-timesyncd.service8. + By default, empty properties are suppressed. Use to show those too. To select specific + properties to show, use . This command is intended to be used whenever + computer-parsable output is required. Use if you are looking for formatted + human-readable output. + + + + + @@ -208,6 +279,25 @@ Password: ******** └─595 /usr/lib/systemd/systemd-timesyncd … + + Show current status of + systemd-timesyncd.service8: + $ timedatectl timesync-status + Server: 216.239.38.15 (time4.google.com) +Poll interval: 1min 4s (min: 32s; max 34min 8s) + Leap: normal + Version: 4 + Stratum: 1 + Reference: GPS + Precision: 1us (-20) +Root distance: 335us (max: 5s) + Offset: +316us + Delay: 349us + Jitter: 0 + Packet count: 1 + Frequency: -8.802ppm + +