From b9335a329db0ea7e2aa4aa9ca1853abb770cdb93 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Tue, 15 Oct 2024 16:30:52 -0400 Subject: [PATCH] resolved: add a helper to check if DNS server is accessible We check this by opening a UDP socket and attempting to connect. We do not send any traffic on it, but this will tell us if there are routes to the DNS server. This will be used in a later commit. --- src/resolve/resolved-dns-server.c | 52 +++++++++++++++++++++++++++++++ src/resolve/resolved-dns-server.h | 9 ++++++ src/resolve/resolved-link.c | 3 ++ 3 files changed, 64 insertions(+) diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index b48d8e1417..1702866a14 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -1,14 +1,19 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-messages.h" #include "alloc-util.h" +#include "errno-util.h" +#include "fd-util.h" #include "resolved-bus.h" #include "resolved-dns-server.h" #include "resolved-dns-stub.h" #include "resolved-manager.h" #include "resolved-resolv-conf.h" #include "siphash24.h" +#include "socket-util.h" #include "string-table.h" #include "string-util.h" @@ -69,6 +74,7 @@ int dns_server_new( .ifindex = ifindex, .server_name = TAKE_PTR(name), .config_source = config_source, + .accessible = -1, }; dns_server_reset_features(s); @@ -1128,3 +1134,49 @@ int dns_server_dump_state_to_json(DnsServer *server, sd_json_variant **ret) { SD_JSON_BUILD_PAIR_BOOLEAN("PacketInvalid", server->packet_invalid), SD_JSON_BUILD_PAIR_BOOLEAN("PacketDoOff", server->packet_do_off)); } + +int dns_server_is_accessible(DnsServer *s) { + _cleanup_close_ int fd = -EBADF; + union sockaddr_union sa; + int r; + + assert(s); + + if (s->accessible >= 0) + return s->accessible; + + r = sockaddr_set_in_addr(&sa, s->family, &s->address, dns_server_port(s)); + if (r < 0) + return r; + + fd = socket(s->family, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (fd < 0) + return -errno; + + if (s->family == AF_INET6 && in_addr_is_link_local(AF_INET6, &s->address)) { + /* Connecting to ipv6 link-local requires binding to an interface. */ + r = socket_bind_to_ifindex(fd, dns_server_ifindex(s)); + if (r < 0) + return r; + } + + r = RET_NERRNO(connect(fd, &sa.sa, SOCKADDR_LEN(sa))); + if (!IN_SET(r, + 0, + -ENETUNREACH, + -EHOSTDOWN, + -EHOSTUNREACH, + -ENETDOWN, + -ENETRESET, + -ENONET)) + /* If we did not receive one of the expected return values, + * then leave the accessible flag untouched. */ + return r; + + return (s->accessible = r >= 0); +} + +void dns_server_reset_accessible_all(DnsServer *first) { + LIST_FOREACH(servers, s, first) + dns_server_reset_accessible(s); +} diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index 7d7d294c82..8915b87e95 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -106,6 +106,9 @@ struct DnsServer { /* Servers registered via D-Bus are not removed on reload */ ResolveConfigSource config_source; + + /* Tri-state to indicate if the DNS server is accessible. */ + int accessible; }; int dns_server_new( @@ -187,3 +190,9 @@ static inline bool dns_server_is_fallback(DnsServer *s) { } int dns_server_dump_state_to_json(DnsServer *server, sd_json_variant **ret); + +int dns_server_is_accessible(DnsServer *s); +static inline void dns_server_reset_accessible(DnsServer *s) { + s->accessible = -1; +} +void dns_server_reset_accessible_all(DnsServer *first); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 928137b967..1c5ad0878c 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -299,6 +299,9 @@ static int link_update_dns_servers(Link *l) { } dns_server_unlink_marked(l->dns_servers); + + dns_server_reset_accessible_all(l->dns_servers); + return 0; clear: