From: Lennart Poettering Date: Tue, 12 Aug 2014 17:48:24 +0000 (+0200) Subject: timesyncd: beef up NTP server selection logic, and acquire NTP servers from DHCP X-Git-Tag: v216~171 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=874ff7bf4d6fe693542209f127d23cd89adc499b timesyncd: beef up NTP server selection logic, and acquire NTP servers from DHCP --- diff --git a/Makefile-man.am b/Makefile-man.am index c7909465e..562ecba43 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -1199,7 +1199,8 @@ endif if ENABLE_TIMESYNCD MANPAGES += \ - man/systemd-timesyncd.service.8 + man/systemd-timesyncd.service.8 \ + man/timesyncd.conf.5 MANPAGES_ALIAS += \ man/systemd-timesyncd.8 man/systemd-timesyncd.8: man/systemd-timesyncd.service.8 @@ -1688,6 +1689,7 @@ EXTRA_DIST += \ man/sysusers.d.xml \ man/telinit.xml \ man/timedatectl.xml \ + man/timesyncd.conf.xml \ man/tmpfiles.d.xml \ man/udev.conf.xml \ man/udev.xml \ diff --git a/man/systemd-timesyncd.service.xml b/man/systemd-timesyncd.service.xml index 9647c54dd..47e243c27 100644 --- a/man/systemd-timesyncd.service.xml +++ b/man/systemd-timesyncd.service.xml @@ -65,12 +65,24 @@ subsequent reboots to ensure it monotonically advances even if the system lacks a battery-buffered RTC chip. + + The NTP servers contacted are determined from + the global settings in + timesyncd.conf5, + the per-link static settings in + .network files, and the per-link + dynamic settings received over DHCP. See + systemd.network5 + for more details. See Also systemd1, + timesyncd.conf5, + systemd.network5, + systemd-networkd.service8, timedatectl1, localtime5, hwclock8 diff --git a/man/timesyncd.conf.xml b/man/timesyncd.conf.xml new file mode 100644 index 000000000..aeb7182f9 --- /dev/null +++ b/man/timesyncd.conf.xml @@ -0,0 +1,115 @@ + + + + + + + + + timesyncd.conf + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + timesyncd.conf + 5 + + + + timesyncd.conf + Network Time Synchronization configuration file + + + + /etc/systemd/timesyncd.conf + + + + Description + + When starting, systemd-timesyncd will read the + configuration file + timesyncd.conf. This + configuration file controls NTP network time + synchronization. + + + + + Options + + + + + NTP= + A space separated list + of NTP servers host names or IP + addresses. During runtime this list is + combined with any per-interface NTP + servers acquired from + systemd-networkd.service8. systemd-timesyncd + will contact all configured system or + per-interface servers in turn until + one is found that responds. This + setting defaults to the empty + list. + + + + FallbackNTP= + A space separated list + of NTP server host names or IP + addresses to be used as the fallback + NTP servers. Any per-interface NTP + servers obtained from + systemd-networkd.service8 + take precedence over this setting, as + do any servers set via + NTP= above. This + setting is hence only used if no other + NTP server information is known. If + this option is not given, a + compiled-in list of NTP servers is + used instead. + + + + + + + See Also + + systemd1, + systemd-timesyncd.service8, + systemd-networkd.service8 + + + + diff --git a/src/timesync/timesyncd-conf.c b/src/timesync/timesyncd-conf.c index 5ec907bb4..4c2dcdb62 100644 --- a/src/timesync/timesyncd-conf.c +++ b/src/timesync/timesyncd-conf.c @@ -19,9 +19,49 @@ along with systemd; If not, see . ***/ +#include "in-addr-util.h" + #include "timesyncd-manager.h" +#include "timesyncd-server.h" #include "timesyncd-conf.h" +int manager_parse_server_string(Manager *m, ServerType type, const char *string) { + const char *word, *state; + size_t length; + ServerName *first; + int r; + + assert(m); + assert(string); + + first = type == SERVER_FALLBACK ? m->fallback_servers : m->system_servers; + + FOREACH_WORD_QUOTED(word, length, string, state) { + char buffer[length+1]; + bool found = false; + ServerName *n; + + memcpy(buffer, word, length); + buffer[length] = 0; + + /* Filter out duplicates */ + LIST_FOREACH(names, n, first) + if (streq_ptr(n->string, buffer)) { + found = true; + break; + } + + if (found) + continue; + + r = server_name_new(m, NULL, type, buffer); + if (r < 0) + return r; + } + + return 0; +} + int config_parse_servers( const char *unit, const char *filename, @@ -35,15 +75,30 @@ int config_parse_servers( void *userdata) { Manager *m = userdata; + int r; assert(filename); assert(lvalue); assert(rvalue); if (isempty(rvalue)) - manager_flush_names(m); - - manager_add_server_string(m, rvalue); + manager_flush_server_names(m, ltype); + else { + r = manager_parse_server_string(m, ltype, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, -r, "Failed to parse NTP server string '%s'. Ignoring.", rvalue); + return 0; + } + } return 0; } + +int manager_parse_config_file(Manager *m) { + assert(m); + + return config_parse(NULL, "/etc/systemd/timesyncd.conf", NULL, + "Time\0", + config_item_perf_lookup, timesyncd_gperf_lookup, + false, false, true, m); +} diff --git a/src/timesync/timesyncd-conf.h b/src/timesync/timesyncd-conf.h index 2102599e9..56466fe46 100644 --- a/src/timesync/timesyncd-conf.h +++ b/src/timesync/timesyncd-conf.h @@ -23,6 +23,12 @@ #include "conf-parser.h" +#include "timesyncd-manager.h" + const struct ConfigPerfItem* timesyncd_gperf_lookup(const char *key, unsigned length); +int manager_parse_server_string(Manager *m, ServerType type, const char *string); + int config_parse_servers(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); + +int manager_parse_config_file(Manager *m); diff --git a/src/timesync/timesyncd-gperf.gperf b/src/timesync/timesyncd-gperf.gperf index b97eec83e..29a2cfeef 100644 --- a/src/timesync/timesyncd-gperf.gperf +++ b/src/timesync/timesyncd-gperf.gperf @@ -14,4 +14,6 @@ struct ConfigPerfItem; %struct-type %includes %% -Time.Servers, config_parse_servers, 0, 0 +Time.NTP, config_parse_servers, SERVER_SYSTEM, 0 +Time.Servers, config_parse_servers, SERVER_SYSTEM, 0 +Time.FallbackNTP, config_parse_servers, SERVER_FALLBACK, 0 diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index c93937e7b..bb8fcca1d 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -734,33 +734,42 @@ static int manager_begin(Manager *m) { return manager_send_request(m); } -static void server_name_flush_addresses(ServerName *n) { - ServerAddress *a; +void manager_set_server_name(Manager *m, ServerName *n) { + assert(m); - assert(n); + if (m->current_server_name == n) + return; - while ((a = n->addresses)) { - LIST_REMOVE(addresses, n->addresses, a); - free(a); - } -} + m->current_server_name = n; + m->current_server_address = NULL; + + manager_disconnect(m); -void manager_flush_names(Manager *m) { - ServerName *n; + if (n) + log_debug("Selected server %s.", n->string); +} +void manager_set_server_address(Manager *m, ServerAddress *a) { assert(m); - while ((n = m->servers)) { - LIST_REMOVE(names, m->servers, n); - free(n->string); - server_name_flush_addresses(n); - free(n); + if (m->current_server_address == a) + return; + + m->current_server_name = a ? a->name : NULL; + m->current_server_address = a; + + manager_disconnect(m); + + if (a) { + _cleanup_free_ char *pretty = NULL; + server_address_pretty(a, &pretty); + log_debug("Selected address %s of server %s.", strna(pretty), a->name->string); } } static int manager_resolve_handler(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata) { Manager *m = userdata; - ServerAddress *a, *last = NULL; + int r; assert(q); assert(m); @@ -775,31 +784,25 @@ static int manager_resolve_handler(sd_resolve_query *q, int ret, const struct ad return manager_connect(m); } - server_name_flush_addresses(m->current_server_name); - for (; ai; ai = ai->ai_next) { _cleanup_free_ char *pretty = NULL; + ServerAddress *a; assert(ai->ai_addr); assert(ai->ai_addrlen >= offsetof(struct sockaddr, sa_data)); - assert(ai->ai_addrlen <= sizeof(union sockaddr_union)); if (!IN_SET(ai->ai_addr->sa_family, AF_INET, AF_INET6)) { log_warning("Unsuitable address protocol for %s", m->current_server_name->string); continue; } - a = new0(ServerAddress, 1); - if (!a) - return log_oom(); - - memcpy(&a->sockaddr, ai->ai_addr, ai->ai_addrlen); - a->socklen = ai->ai_addrlen; - - LIST_INSERT_AFTER(addresses, m->current_server_name->addresses, last, a); - last = a; + r = server_address_new(m->current_server_name, &a, (const union sockaddr_union*) ai->ai_addr, ai->ai_addrlen); + if (r < 0) { + log_error("Failed to add server address: %s", strerror(-r)); + return r; + } - sockaddr_pretty(&a->sockaddr.sa, a->socklen, true, &pretty); + server_address_pretty(a, &pretty); log_debug("Resolved address %s for %s.", pretty, m->current_server_name->string); } @@ -810,12 +813,12 @@ static int manager_resolve_handler(sd_resolve_query *q, int ret, const struct ad return manager_connect(m); } - m->current_server_address = m->current_server_name->addresses; + manager_set_server_address(m, m->current_server_name->addresses); return manager_begin(m); } -static int manager_retry(sd_event_source *source, usec_t usec, void *userdata) { +static int manager_retry_connect(sd_event_source *source, usec_t usec, void *userdata) { Manager *m = userdata; assert(m); @@ -824,11 +827,6 @@ static int manager_retry(sd_event_source *source, usec_t usec, void *userdata) { } int manager_connect(Manager *m) { - - struct addrinfo hints = { - .ai_flags = AI_NUMERICSERV|AI_ADDRCONFIG, - .ai_socktype = SOCK_DGRAM, - }; int r; assert(m); @@ -839,7 +837,7 @@ int manager_connect(Manager *m) { if (!ratelimit_test(&m->ratelimit)) { log_debug("Slowing down attempts to contact servers."); - r = sd_event_add_time(m->event, &m->event_retry, clock_boottime_or_monotonic(), now(clock_boottime_or_monotonic()) + RETRY_USEC, 0, manager_retry, m); + r = sd_event_add_time(m->event, &m->event_retry, clock_boottime_or_monotonic(), now(clock_boottime_or_monotonic()) + RETRY_USEC, 0, manager_retry_connect, m); if (r < 0) { log_error("Failed to create retry timer: %s", strerror(-r)); return r; @@ -851,27 +849,57 @@ int manager_connect(Manager *m) { /* If we already are operating on some address, switch to the * next one. */ if (m->current_server_address && m->current_server_address->addresses_next) - m->current_server_address = m->current_server_address->addresses_next; + manager_set_server_address(m, m->current_server_address->addresses_next); else { - /* Hmm, we are through all addresses, let's look for the next host instead */ - m->current_server_address = NULL; + struct addrinfo hints = { + .ai_flags = AI_NUMERICSERV|AI_ADDRCONFIG, + .ai_socktype = SOCK_DGRAM, + }; + /* Hmm, we are through all addresses, let's look for the next host instead */ if (m->current_server_name && m->current_server_name->names_next) - m->current_server_name = m->current_server_name->names_next; + manager_set_server_name(m, m->current_server_name->names_next); else { - if (!m->servers) { - m->current_server_name = NULL; + ServerName *f; + + /* Our current server name list is exhausted, + * let's find the next one to iterate. First + * we try the system list, then the link list. + * After having processed the link list we + * jump back to the system list. However, if + * both lists are empty, we change to the + * fallback list. */ + if (!m->current_server_name || m->current_server_name->type == SERVER_LINK) { + f = m->system_servers; + if (!f) + f = m->link_servers; + } else { + f = m->link_servers; + if (!f) + f = m->system_servers; + } + + if (!f) + f = m->fallback_servers; + + if (!f) { + manager_set_server_name(m, NULL); log_debug("No server found."); return 0; } - m->current_server_name = m->servers; + manager_set_server_name(m, f); } /* Tell the resolver to reread /etc/resolv.conf, in * case it changed. */ res_init(); + /* Flush out any previously resolved addresses */ + server_name_flush_addresses(m->current_server_name); + + log_debug("Resolving %s...", m->current_server_name->string); + r = sd_resolve_getaddrinfo(m->resolve, &m->resolve_query, m->current_server_name->string, "123", &hints, manager_resolve_handler, m); if (r < 0) { log_error("Failed to create resolver: %s", strerror(-r)); @@ -888,52 +916,6 @@ int manager_connect(Manager *m) { return 1; } -static int manager_add_server(Manager *m, const char *server) { - ServerName *n, *tail; - - assert(m); - assert(server); - - n = new0(ServerName, 1); - if (!n) - return -ENOMEM; - - n->string = strdup(server); - if (!n->string) { - free(n); - return -ENOMEM; - } - - LIST_FIND_TAIL(names, m->servers, tail); - LIST_INSERT_AFTER(names, m->servers, tail, n); - - return 0; -} - -int manager_add_server_string(Manager *m, const char *string) { - const char *word, *state; - size_t l; - int r; - - assert(m); - assert(string); - - FOREACH_WORD_QUOTED(word, l, string, state) { - char t[l+1]; - - memcpy(t, word, l); - t[l] = 0; - - r = manager_add_server(m, t); - if (r < 0) - log_error("Failed to add server %s to configuration, ignoring: %s", t, strerror(-r)); - } - if (!isempty(state)) - log_warning("Trailing garbage at the end of server list, ignoring."); - - return 0; -} - void manager_disconnect(Manager *m) { assert(m); @@ -952,13 +934,30 @@ void manager_disconnect(Manager *m) { sd_notifyf(false, "STATUS=Idle."); } +void manager_flush_server_names(Manager *m, ServerType t) { + assert(m); + + if (t == SERVER_SYSTEM) + while (m->system_servers) + server_name_free(m->system_servers); + + if (t == SERVER_LINK) + while (m->link_servers) + server_name_free(m->link_servers); + + if (t == SERVER_FALLBACK) + while (m->fallback_servers) + server_name_free(m->fallback_servers); +} void manager_free(Manager *m) { if (!m) return; manager_disconnect(m); - manager_flush_names(m); + manager_flush_server_names(m, SERVER_SYSTEM); + manager_flush_server_names(m, SERVER_LINK); + manager_flush_server_names(m, SERVER_FALLBACK); sd_event_source_unref(m->event_retry); @@ -971,13 +970,47 @@ void manager_free(Manager *m) { free(m); } -int manager_parse_config_file(Manager *m) { +static int manager_network_read_link_servers(Manager *m) { + _cleanup_strv_free_ char **ntp = NULL; + ServerName *n, *nx; + char **i; + int r; + assert(m); - return config_parse(NULL, "/etc/systemd/timesyncd.conf", NULL, - "Time\0", - config_item_perf_lookup, timesyncd_gperf_lookup, - false, false, true, m); + r = sd_network_get_ntp(&ntp); + if (r < 0) + goto clear; + + LIST_FOREACH(names, n, m->link_servers) + n->marked = true; + + STRV_FOREACH(i, ntp) { + bool found = false; + + LIST_FOREACH(names, n, m->link_servers) + if (streq(n->string, *i)) { + n->marked = false; + found = true; + break; + } + + if (!found) { + r = server_name_new(m, NULL, SERVER_LINK, *i); + if (r < 0) + goto clear; + } + } + + LIST_FOREACH_SAFE(names, n, nx, m->link_servers) + if (n->marked) + server_name_free(n); + + return 0; + +clear: + manager_flush_server_names(m, SERVER_LINK); + return r; } static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { @@ -989,11 +1022,13 @@ static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t re sd_network_monitor_flush(m->network_monitor); + manager_network_read_link_servers(m); + /* check if the machine is online */ online = network_is_online(); /* check if the client is currently connected */ - connected = m->server_socket >= 0; + connected = m->server_socket >= 0 || m->resolve_query; if (connected && !online) { log_info("No network connectivity, watching for changes."); @@ -1051,7 +1086,7 @@ int manager_new(Manager **ret) { RATELIMIT_INIT(m->ratelimit, RATELIMIT_INTERVAL_USEC, RATELIMIT_BURST); - r = manager_add_server_string(m, NTP_SERVERS); + r = manager_parse_server_string(m, SERVER_FALLBACK, NTP_SERVERS); if (r < 0) return r; @@ -1076,6 +1111,10 @@ int manager_new(Manager **ret) { if (r < 0) return r; + r = manager_network_read_link_servers(m); + if (r < 0) + return r; + *ret = m; m = NULL; diff --git a/src/timesync/timesyncd-manager.h b/src/timesync/timesyncd-manager.h index e6dd711f4..2345bf8f3 100644 --- a/src/timesync/timesyncd-manager.h +++ b/src/timesync/timesyncd-manager.h @@ -27,16 +27,17 @@ #include "list.h" #include "socket-util.h" #include "ratelimit.h" -#include "timesyncd-server.h" typedef struct Manager Manager; +#include "timesyncd-server.h" + struct Manager { sd_event *event; sd_resolve *resolve; + LIST_HEAD(ServerName, system_servers); LIST_HEAD(ServerName, link_servers); - LIST_HEAD(ServerName, servers); LIST_HEAD(ServerName, fallback_servers); RateLimit ratelimit; @@ -94,8 +95,9 @@ void manager_free(Manager *m); DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); -int manager_parse_config_file(Manager *m); -int manager_add_server_string(Manager *m, const char *string); -void manager_flush_names(Manager *m); +void manager_set_server_name(Manager *m, ServerName *n); +void manager_set_server_address(Manager *m, ServerAddress *a); +void manager_flush_server_names(Manager *m, ServerType t); + int manager_connect(Manager *m); void manager_disconnect(Manager *m); diff --git a/src/timesync/timesyncd-server.c b/src/timesync/timesyncd-server.c index e3cb1bf1f..ec3fe1fc4 100644 --- a/src/timesync/timesyncd-server.c +++ b/src/timesync/timesyncd-server.c @@ -20,3 +20,132 @@ ***/ #include "timesyncd-server.h" + +int server_address_new( + ServerName *n, + ServerAddress **ret, + const union sockaddr_union *sockaddr, + socklen_t socklen) { + + ServerAddress *a, *tail; + + assert(n); + assert(sockaddr); + assert(socklen >= offsetof(struct sockaddr, sa_data)); + assert(socklen <= sizeof(union sockaddr_union)); + + a = new0(ServerAddress, 1); + if (!a) + return -ENOMEM; + + memcpy(&a->sockaddr, sockaddr, socklen); + a->socklen = socklen; + + LIST_FIND_TAIL(addresses, n->addresses, tail); + LIST_INSERT_AFTER(addresses, n->addresses, tail, a); + a->name = n; + + if (ret) + *ret = a; + + return 0; +} + +ServerAddress* server_address_free(ServerAddress *a) { + if (!a) + return NULL; + + if (a->name) { + LIST_REMOVE(addresses, a->name->addresses, a); + + if (a->name->manager && a->name->manager->current_server_address == a) + manager_set_server_address(a->name->manager, NULL); + } + + free(a); + return NULL; +} + +int server_name_new( + Manager *m, + ServerName **ret, + ServerType type, + const char *string) { + + ServerName *n, *tail; + + assert(m); + assert(string); + + n = new0(ServerName, 1); + if (!n) + return -ENOMEM; + + n->type = type; + n->string = strdup(string); + if (!n->string) { + free(n); + return -ENOMEM; + } + + if (type == SERVER_SYSTEM) { + LIST_FIND_TAIL(names, m->system_servers, tail); + LIST_INSERT_AFTER(names, m->system_servers, tail, n); + } else if (type == SERVER_LINK) { + LIST_FIND_TAIL(names, m->link_servers, tail); + LIST_INSERT_AFTER(names, m->link_servers, tail, n); + } else if (type == SERVER_FALLBACK) { + LIST_FIND_TAIL(names, m->fallback_servers, tail); + LIST_INSERT_AFTER(names, m->fallback_servers, tail, n); + } else + assert_not_reached("Unknown server type"); + + n->manager = m; + + if (type != SERVER_FALLBACK && + m->current_server_name && + m->current_server_name->type == SERVER_FALLBACK) + manager_set_server_name(m, NULL); + + log_debug("Added new server %s.", string); + + if (ret) + *ret = n; + + return 0; +} + +ServerName *server_name_free(ServerName *n) { + if (!n) + return NULL; + + server_name_flush_addresses(n); + + if (n->manager) { + if (n->type == SERVER_SYSTEM) + LIST_REMOVE(names, n->manager->system_servers, n); + else if (n->type == SERVER_LINK) + LIST_REMOVE(names, n->manager->link_servers, n); + else if (n->type == SERVER_FALLBACK) + LIST_REMOVE(names, n->manager->fallback_servers, n); + else + assert_not_reached("Unknown server type"); + + if (n->manager->current_server_name == n) + manager_set_server_name(n->manager, NULL); + } + + log_debug("Removed server %s.", n->string); + + free(n->string); + free(n); + + return NULL; +} + +void server_name_flush_addresses(ServerName *n) { + assert(n); + + while (n->addresses) + server_address_free(n->addresses); +} diff --git a/src/timesync/timesyncd-server.h b/src/timesync/timesyncd-server.h index a1312bfe0..243b44a0e 100644 --- a/src/timesync/timesyncd-server.h +++ b/src/timesync/timesyncd-server.h @@ -21,24 +21,47 @@ along with systemd; If not, see . ***/ +#include "socket-util.h" +#include "list.h" + typedef struct ServerAddress ServerAddress; typedef struct ServerName ServerName; -#include "socket-util.h" -#include "list.h" +typedef enum ServerType { + SERVER_SYSTEM, + SERVER_FALLBACK, + SERVER_LINK, +} ServerType; + +#include "timesyncd-manager.h" struct ServerAddress { + ServerName *name; + union sockaddr_union sockaddr; socklen_t socklen; + LIST_FIELDS(ServerAddress, addresses); }; struct ServerName { + Manager *manager; + + ServerType type; char *string; + + bool marked:1; + LIST_HEAD(ServerAddress, addresses); LIST_FIELDS(ServerName, names); }; +int server_address_new(ServerName *n, ServerAddress **ret, const union sockaddr_union *sockaddr, socklen_t socklen); +ServerAddress* server_address_free(ServerAddress *a); static inline int server_address_pretty(ServerAddress *a, char **pretty) { return sockaddr_pretty(&a->sockaddr.sa, a->socklen, true, pretty); } + +int server_name_new(Manager *m, ServerName **ret, ServerType type,const char *string); +ServerName *server_name_free(ServerName *n); +void server_name_flush_addresses(ServerName *n); diff --git a/src/timesync/timesyncd.conf.in b/src/timesync/timesyncd.conf.in index a0caa1d40..674a51dbd 100644 --- a/src/timesync/timesyncd.conf.in +++ b/src/timesync/timesyncd.conf.in @@ -8,4 +8,5 @@ # See timesyncd.conf(5) for details [Time] -#Servers=@NTP_SERVERS@ +#NTP= +#FallbackNTP=@NTP_SERVERS@