diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index b0f22516df..7b2ed4f831 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -10,8 +10,10 @@ #include "sd-bus.h" #include "sd-daemon.h" +#include "sd-json.h" #include "sd-messages.h" #include "sd-network.h" +#include "sd-varlink.h" #include "alloc-util.h" #include "clock-util.h" @@ -23,6 +25,7 @@ #include "format-util.h" #include "fs-util.h" #include "hashmap.h" +#include "in-addr-util.h" #include "list.h" #include "log.h" #include "logarithm.h" @@ -30,6 +33,8 @@ #include "random-util.h" #include "ratelimit.h" #include "resolve-private.h" +#include "resolve-varlink-util.h" +#include "resolved-def.h" #include "socket-util.h" #include "string-util.h" #include "strv.h" @@ -780,6 +785,119 @@ static int manager_resolve_handler(sd_resolve_query *q, int ret, const struct ad return manager_begin(m); } +static int manager_resolve_varlink_reply( + sd_varlink *link, + sd_json_variant *parameters, + const char *error_id, + sd_varlink_reply_flags_t flags, + void *userdata) { + + Manager *m = ASSERT_PTR(userdata); + int r; + + assert(m->current_server_name); + + _unused_ _cleanup_(sd_varlink_unrefp) sd_varlink *link_free = TAKE_PTR(m->resolve_varlink); + + if (!isempty(error_id)) { + log_debug("Failed to resolve %s: %s", m->current_server_name->string, error_id); + + /* Try next one */ + return manager_connect(m); + } + + _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply reply = {}; + r = dispatch_resolve_hostname_reply(/* name = */ NULL, parameters, SD_JSON_LOG, &reply); + if (r < 0) { + log_warning_errno(r, "Failed to parse reply for %s, ignoring: %m", m->current_server_name->string); + + return manager_connect(m); + } + + FOREACH_ARRAY(entry, reply.addresses, reply.n_addresses) { + _cleanup_free_ char *pretty = NULL; + union sockaddr_union sa = {}; + ServerAddress *a; + + if (!IN_SET(entry->family, AF_INET, AF_INET6)) { + log_debug("Ignoring unsuitable address protocol for %s.", m->current_server_name->string); + continue; + } + + r = sockaddr_set_in_addr(&sa, entry->family, &entry->in_addr.address, 123); + if (r < 0) { + log_warning_errno(r, "Failed to convert address for %s, trying next host: %m", m->current_server_name->string); + return manager_connect(m); + } + + /* Only link-local addresses need a scope, setting it on a global one pins us to one interface. */ + if (entry->family == AF_INET6 && in6_addr_is_link_local(&entry->in_addr.address.in6)) + sa.in6.sin6_scope_id = entry->ifindex; + + r = server_address_new(m->current_server_name, &a, &sa, sockaddr_len(&sa)); + if (r < 0) { + log_warning_errno(r, "Failed to add server address, trying next host: %m"); + return manager_connect(m); + } + + (void) server_address_pretty(a, &pretty); + log_debug("Resolved address %s for %s.", strna(pretty), m->current_server_name->string); + } + + if (!m->current_server_name->addresses) { + log_error("Failed to find suitable address for host %s.", m->current_server_name->string); + + /* Try next host */ + return manager_connect(m); + } + + manager_set_server_address(m, m->current_server_name->addresses); + + return manager_begin(m); +} + +/* Resolve the current server name through resolved's Varlink interface. This returns 0 if resolved is + * unavailable */ +static int manager_resolve_varlink(Manager *m) { + _cleanup_(sd_varlink_close_unrefp) sd_varlink *link = NULL; + int r; + + assert(m); + assert(m->current_server_name); + + r = sd_varlink_connect_address(&link, "/run/systemd/resolve/io.systemd.Resolve"); + if (r < 0) { + log_debug_errno(r, "Failed to connect to systemd-resolved, using getaddrinfo(): %m"); + return 0; + } + + (void) sd_varlink_set_description(link, "timesyncd-resolve"); + (void) sd_varlink_set_relative_timeout(link, SD_RESOLVED_QUERY_TIMEOUT_USEC); + + r = sd_varlink_attach_event(link, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach Varlink connection to event loop: %m"); + + r = sd_varlink_bind_reply(link, manager_resolve_varlink_reply); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink reply callback: %m"); + + sd_varlink_set_userdata(link, m); + + /* Ask resolved to skip DNSSEC validation as long as our clock is not synchronized yet. */ + r = sd_varlink_invokebo( + link, + "io.systemd.Resolve.ResolveHostname", + SD_JSON_BUILD_PAIR_STRING("name", m->current_server_name->string), + SD_JSON_BUILD_PAIR_CONDITION(!socket_ipv6_is_enabled(), "family", SD_JSON_BUILD_INTEGER(AF_INET)), + SD_JSON_BUILD_PAIR_UNSIGNED("flags", m->synchronized ? 0 : SD_RESOLVED_NO_VALIDATE)); + if (r < 0) + return log_error_errno(r, "Failed to call ResolveHostname(): %m"); + + m->resolve_varlink = TAKE_PTR(link); + return 1; +} + static int manager_retry_connect(sd_event_source *source, usec_t usec, void *userdata) { Manager *m = ASSERT_PTR(userdata); @@ -868,15 +986,22 @@ int manager_connect(Manager *m) { 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); + /* Prefer resolved's varlink interface. If that's unavailable, then fall through to getaddrinfo below. */ + r = manager_resolve_varlink(m); + if (r < 0) + return r; + if (r > 0) + return 1; + + /* Tell the resolver to reread /etc/resolv.conf, in + * case it changed. */ + res_init(); + struct addrinfo hints = { .ai_flags = AI_NUMERICSERV|AI_ADDRCONFIG, .ai_socktype = SOCK_DGRAM, @@ -901,6 +1026,7 @@ void manager_disconnect(Manager *m) { assert(m); m->resolve_query = sd_resolve_query_unref(m->resolve_query); + m->resolve_varlink = sd_varlink_close_unref(m->resolve_varlink); m->event_timer = sd_event_source_unref(m->event_timer); @@ -1035,7 +1161,7 @@ static bool manager_is_connected(Manager *m) { /* Return true when the manager is sending a request, resolving a server name, or * in a poll interval. */ - return m->server_socket >= 0 || m->resolve_query || m->event_timer; + return m->server_socket >= 0 || m->resolve_query || m->resolve_varlink || m->event_timer; } static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { diff --git a/src/timesync/timesyncd-manager.h b/src/timesync/timesyncd-manager.h index 9966b64ac6..8c1e30f48f 100644 --- a/src/timesync/timesyncd-manager.h +++ b/src/timesync/timesyncd-manager.h @@ -41,6 +41,7 @@ typedef struct Manager { /* peer */ sd_resolve_query *resolve_query; + sd_varlink *resolve_varlink; sd_event_source *event_receive; ServerName *current_server_name; ServerAddress *current_server_address; diff --git a/units/systemd-timesyncd.service.in b/units/systemd-timesyncd.service.in index 560bcee92f..bc64e24140 100644 --- a/units/systemd-timesyncd.service.in +++ b/units/systemd-timesyncd.service.in @@ -22,10 +22,6 @@ Wants=time-set.target AmbientCapabilities=CAP_SYS_TIME BusName=org.freedesktop.timesync1 CapabilityBoundingSet=CAP_SYS_TIME -# Turn off DNSSEC validation for hostname look-ups, since those need the -# correct time to work, but we likely won't acquire that without NTP. Let's -# break this chicken-and-egg cycle here. -Environment=SYSTEMD_NSS_RESOLVE_VALIDATE=0 ExecStart={{LIBEXECDIR}}/systemd-timesyncd LockPersonality=yes MemoryDenyWriteExecute=yes