diff options
author | Lennart Poettering <lennart@poettering.net> | 2018-12-21 18:03:58 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-21 18:03:58 +0100 |
commit | 44f52cce9ee9976b465841a811a7a9963947bc7d (patch) | |
tree | 94a3a8ad8318968490265fd7f1fc9e02f705d240 /src | |
parent | Merge pull request #11210 from thom311/dhcp-set-client-id-no-inval (diff) | |
parent | man: document new systemd-resolved.service(8) routing features in more detail (diff) | |
download | systemd-44f52cce9ee9976b465841a811a7a9963947bc7d.tar.gz systemd-44f52cce9ee9976b465841a811a7a9963947bc7d.tar.bz2 systemd-44f52cce9ee9976b465841a811a7a9963947bc7d.zip |
Merge pull request #11050 from poettering/resolved-domain-route
resolved: beef up domain routing
Diffstat (limited to 'src')
-rw-r--r-- | src/libsystemd/sd-network/sd-network.c | 19 | ||||
-rw-r--r-- | src/network/networkd-link.c | 2 | ||||
-rw-r--r-- | src/network/networkd-network-gperf.gperf | 1 | ||||
-rw-r--r-- | src/network/networkd-network.c | 14 | ||||
-rw-r--r-- | src/network/networkd-network.h | 9 | ||||
-rw-r--r-- | src/resolve/resolvectl.c | 64 | ||||
-rw-r--r-- | src/resolve/resolved-bus.c | 5 | ||||
-rw-r--r-- | src/resolve/resolved-dns-query.c | 27 | ||||
-rw-r--r-- | src/resolve/resolved-dns-scope.c | 167 | ||||
-rw-r--r-- | src/resolve/resolved-dns-scope.h | 10 | ||||
-rw-r--r-- | src/resolve/resolved-dns-server.c | 30 | ||||
-rw-r--r-- | src/resolve/resolved-dns-server.h | 4 | ||||
-rw-r--r-- | src/resolve/resolved-link-bus.c | 52 | ||||
-rw-r--r-- | src/resolve/resolved-link-bus.h | 1 | ||||
-rw-r--r-- | src/resolve/resolved-link.c | 62 | ||||
-rw-r--r-- | src/resolve/resolved-link.h | 2 | ||||
-rw-r--r-- | src/resolve/resolved-resolv-conf.c | 15 | ||||
-rw-r--r-- | src/systemd/sd-network.h | 3 |
18 files changed, 392 insertions, 95 deletions
diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c index 4b66a9220..d4b5e248c 100644 --- a/src/libsystemd/sd-network/sd-network.c +++ b/src/libsystemd/sd-network/sd-network.c @@ -204,6 +204,25 @@ _public_ int sd_network_link_get_route_domains(int ifindex, char ***ret) { return network_link_get_strv(ifindex, "ROUTE_DOMAINS", ret); } +_public_ int sd_network_link_get_dns_default_route(int ifindex) { + char path[STRLEN("/run/systemd/netif/links/") + DECIMAL_STR_MAX(ifindex) + 1]; + _cleanup_free_ char *s = NULL; + int r; + + assert_return(ifindex > 0, -EINVAL); + + xsprintf(path, "/run/systemd/netif/links/%i", ifindex); + + r = parse_env_file(NULL, path, "DNS_DEFAULT_ROUTE", &s); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + return parse_boolean(s); +} + static int network_link_get_ifindexes(int ifindex, const char *key, int **ret) { char path[STRLEN("/run/systemd/netif/links/") + DECIMAL_STR_MAX(ifindex) + 1]; _cleanup_free_ int *ifis = NULL; diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index d73e85cf2..e2851df31 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -3929,6 +3929,8 @@ int link_save(Link *link) { resolve_support_to_string(link->network->llmnr)); fprintf(f, "MDNS=%s\n", resolve_support_to_string(link->network->mdns)); + if (link->network->dns_default_route >= 0) + fprintf(f, "DNS_DEFAULT_ROUTE=%s\n", yes_no(link->network->dns_default_route)); if (link->network->dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID) fprintf(f, "DNS_OVER_TLS=%s\n", diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 48d8ae52f..5d8aede59 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -58,6 +58,7 @@ Network.Address, config_parse_address, Network.Gateway, config_parse_gateway, 0, 0 Network.Domains, config_parse_domains, 0, 0 Network.DNS, config_parse_dns, 0, 0 +Network.DNSDefaultRoute, config_parse_tristate, 0, offsetof(Network, dns_default_route) Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr) Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns) Network.DNSOverTLS, config_parse_dns_over_tls_mode, 0, offsetof(Network, dns_over_tls_mode) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 62dc6a0bf..ccc1c3ce8 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -168,6 +168,7 @@ int network_load_one(Manager *manager, const char *filename) { .lldp_mode = LLDP_MODE_ROUTERS_ONLY, + .dns_default_route = -1, .llmnr = RESOLVE_SUPPORT_YES, .mdns = RESOLVE_SUPPORT_NO, .dnssec_mode = _DNSSEC_MODE_INVALID, @@ -657,7 +658,6 @@ int config_parse_domains( * routing domain, unconditionally. */ is_route = true; domain = "."; /* make sure we don't allow empty strings, thus write the root domain as "." */ - } else { r = dns_name_normalize(domain, 0, &normalized); if (r < 0) { @@ -673,16 +673,12 @@ int config_parse_domains( } } - if (is_route) { + if (is_route) r = strv_extend(&n->route_domains, domain); - if (r < 0) - return log_oom(); - - } else { + else r = strv_extend(&n->search_domains, domain); - if (r < 0) - return log_oom(); - } + if (r < 0) + return log_oom(); } strv_uniq(n->route_domains); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 3a72c5bd9..f6e62cdd7 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -260,17 +260,20 @@ struct Network { Hashmap *prefixes_by_section; Hashmap *rules_by_section; + /* All kinds of DNS configuration */ struct in_addr_data *dns; unsigned n_dns; - - char **search_domains, **route_domains, **ntp, **bind_carrier; - + char **search_domains, **route_domains; + int dns_default_route; ResolveSupport llmnr; ResolveSupport mdns; DnssecMode dnssec_mode; DnsOverTlsMode dns_over_tls_mode; Set *dnssec_negative_trust_anchors; + char **ntp; + char **bind_carrier; + LIST_FIELDS(Network, networks); }; diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 8a175ebef..4d533f851 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -67,6 +67,7 @@ typedef enum StatusMode { STATUS_ALL, STATUS_DNS, STATUS_DOMAIN, + STATUS_DEFAULT_ROUTE, STATUS_LLMNR, STATUS_MDNS, STATUS_PRIVATE, @@ -1369,6 +1370,7 @@ struct link_info { char **domains; char **ntas; bool dnssec_supported; + bool default_route; }; static void link_info_clear(struct link_info *p) { @@ -1384,6 +1386,7 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode { "DNS", "a(iay)", map_link_dns_servers, offsetof(struct link_info, dns) }, { "CurrentDNSServer", "(iay)", map_link_current_dns_server, offsetof(struct link_info, current_dns) }, { "Domains", "a(sb)", map_link_domains, offsetof(struct link_info, domains) }, + { "DefaultRoute", "b", NULL, offsetof(struct link_info, default_route) }, { "LLMNR", "s", NULL, offsetof(struct link_info, llmnr) }, { "MulticastDNS", "s", NULL, offsetof(struct link_info, mdns) }, { "DNSOverTLS", "s", NULL, offsetof(struct link_info, dns_over_tls) }, @@ -1439,6 +1442,14 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode if (mode == STATUS_NTA) return status_print_strv_ifindex(ifindex, name, link_info.ntas); + if (mode == STATUS_DEFAULT_ROUTE) { + printf("%sLink %i (%s)%s: %s\n", + ansi_highlight(), ifindex, name, ansi_normal(), + yes_no(link_info.default_route)); + + return 0; + } + if (mode == STATUS_LLMNR) { printf("%sLink %i (%s)%s: %s\n", ansi_highlight(), ifindex, name, ansi_normal(), @@ -1487,11 +1498,13 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode link_info.scopes_mask & SD_RESOLVED_MDNS_IPV4 ? " mDNS/IPv4" : "", link_info.scopes_mask & SD_RESOLVED_MDNS_IPV6 ? " mDNS/IPv6" : ""); - printf(" LLMNR setting: %s\n" + printf("DefaultRoute setting: %s\n" + " LLMNR setting: %s\n" "MulticastDNS setting: %s\n" " DNSOverTLS setting: %s\n" " DNSSEC setting: %s\n" " DNSSEC supported: %s\n", + yes_no(link_info.default_route), strna(link_info.llmnr), strna(link_info.mdns), strna(link_info.dns_over_tls), @@ -2020,6 +2033,51 @@ static int verb_domain(int argc, char **argv, void *userdata) { return 0; } +static int verb_default_route(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, b; + + assert(bus); + + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } + + if (arg_ifindex <= 0) + return status_all(bus, STATUS_DEFAULT_ROUTE); + + if (argc < 3) + return status_ifindex(bus, arg_ifindex, NULL, STATUS_DEFAULT_ROUTE, NULL); + + b = parse_boolean(argv[2]); + if (b < 0) + return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); + + r = sd_bus_call_method(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "SetLinkDefaultRoute", + &error, + NULL, + "ib", arg_ifindex, b); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) + return log_interface_is_managed(r, arg_ifindex); + + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set default route configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + static int verb_llmnr(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; @@ -2407,6 +2465,7 @@ static int native_help(void) { " reset-server-features Forget learnt DNS server feature levels\n" " dns [LINK [SERVER...]] Get/set per-interface DNS server address\n" " domain [LINK [DOMAIN...]] Get/set per-interface search domain\n" + " default-route [LINK [BOOL]] Get/set per-interface default route flag\n" " llmnr [LINK [MODE]] Get/set per-interface LLMNR mode\n" " mdns [LINK [MODE]] Get/set per-interface MulticastDNS mode\n" " dnsovertls [LINK [MODE]] Get/set per-interface DNS-over-TLS mode\n" @@ -2950,9 +3009,10 @@ static int native_main(int argc, char *argv[], sd_bus *bus) { { "reset-server-features", VERB_ANY, 1, 0, reset_server_features }, { "dns", VERB_ANY, VERB_ANY, 0, verb_dns }, { "domain", VERB_ANY, VERB_ANY, 0, verb_domain }, + { "default-route", VERB_ANY, 3, 0, verb_default_route }, { "llmnr", VERB_ANY, 3, 0, verb_llmnr }, { "mdns", VERB_ANY, 3, 0, verb_mdns }, - { "dnsovertls", VERB_ANY, 3, 0, verb_dns_over_tls }, + { "dnsovertls", VERB_ANY, 3, 0, verb_dns_over_tls }, { "dnssec", VERB_ANY, 3, 0, verb_dnssec }, { "nta", VERB_ANY, VERB_ANY, 0, verb_nta }, { "revert", VERB_ANY, 2, 0, verb_revert_link }, diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index fbe823da6..5b547ba7e 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -1530,6 +1530,10 @@ static int bus_method_set_link_domains(sd_bus_message *message, void *userdata, return call_link_method(userdata, message, bus_link_method_set_domains, error); } +static int bus_method_set_link_default_route(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_default_route, error); +} + static int bus_method_set_link_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { return call_link_method(userdata, message, bus_link_method_set_llmnr, error); } @@ -1855,6 +1859,7 @@ static const sd_bus_vtable resolve_vtable[] = { SD_BUS_METHOD("GetLink", "i", "o", bus_method_get_link, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetLinkDNS", "ia(iay)", NULL, bus_method_set_link_dns_servers, 0), SD_BUS_METHOD("SetLinkDomains", "ia(sb)", NULL, bus_method_set_link_domains, 0), + SD_BUS_METHOD("SetLinkDefaultRoute", "ib", NULL, bus_method_set_link_default_route, 0), SD_BUS_METHOD("SetLinkLLMNR", "is", NULL, bus_method_set_link_llmnr, 0), SD_BUS_METHOD("SetLinkMulticastDNS", "is", NULL, bus_method_set_link_mdns, 0), SD_BUS_METHOD("SetLinkDNSOverTLS", "is", NULL, bus_method_set_link_dns_over_tls, 0), diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 746ff1b8b..7a4f97754 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -682,22 +682,15 @@ int dns_query_go(DnsQuery *q) { continue; match = dns_scope_good_domain(s, q->ifindex, q->flags, name); - if (match < 0) - return match; - - if (match == DNS_SCOPE_NO) + if (match < 0) { + log_debug("Couldn't check if '%s' matches against scope, ignoring.", name); continue; + } - found = match; - - if (match == DNS_SCOPE_YES) { + if (match > found) { /* Does this match better? If so, remember how well it matched, and the first one + * that matches this well */ + found = match; first = s; - break; - } else { - assert(match == DNS_SCOPE_MAYBE); - - if (!first) - first = s; } } @@ -725,10 +718,12 @@ int dns_query_go(DnsQuery *q) { continue; match = dns_scope_good_domain(s, q->ifindex, q->flags, name); - if (match < 0) - goto fail; + if (match < 0) { + log_debug("Couldn't check if '%s' matches agains scope, ignoring.", name); + continue; + } - if (match != found) + if (match < found) continue; r = dns_query_add_candidate(q, s); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 35c3804db..972e661d7 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -30,15 +30,17 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int assert(m); assert(ret); - s = new0(DnsScope, 1); + s = new(DnsScope, 1); if (!s) return -ENOMEM; - s->manager = m; - s->link = l; - s->protocol = protocol; - s->family = family; - s->resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC; + *s = (DnsScope) { + .manager = m, + .link = l, + .protocol = protocol, + .family = family, + .resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC, + }; if (protocol == DNS_PROTOCOL_DNS) { /* Copy DNSSEC mode from the link if it is set there, @@ -457,9 +459,40 @@ int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *add return dns_scope_socket(s, SOCK_STREAM, family, address, server, port, ret_socket_address); } -DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { +static DnsScopeMatch accept_link_local_reverse_lookups(const char *domain) { + assert(domain); + + if (dns_name_endswith(domain, "254.169.in-addr.arpa") > 0) + return DNS_SCOPE_YES_BASE + 4; /* 4 labels match */ + + if (dns_name_endswith(domain, "8.e.f.ip6.arpa") > 0 || + dns_name_endswith(domain, "9.e.f.ip6.arpa") > 0 || + dns_name_endswith(domain, "a.e.f.ip6.arpa") > 0 || + dns_name_endswith(domain, "b.e.f.ip6.arpa") > 0) + return DNS_SCOPE_YES_BASE + 5; /* 5 labels match */ + + return _DNS_SCOPE_MATCH_INVALID; +} + +DnsScopeMatch dns_scope_good_domain( + DnsScope *s, + int ifindex, + uint64_t flags, + const char *domain) { + DnsSearchDomain *d; + /* This returns the following return values: + * + * DNS_SCOPE_NO → This scope is not suitable for lookups of this domain, at all + * DNS_SCOPE_MAYBE → This scope is suitable, but only if nothing else wants it + * DNS_SCOPE_YES_BASE+n → This scope is suitable, and 'n' suffix labels match + * + * (The idea is that the caller will only use the scopes with the longest 'n' returned. If no scopes return + * DNS_SCOPE_YES_BASE+n, then it should use those which returned DNS_SCOPE_MAYBE. It should never use those + * which returned DNS_SCOPE_NO.) + */ + assert(s); assert(domain); @@ -494,23 +527,35 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co switch (s->protocol) { case DNS_PROTOCOL_DNS: { - DnsServer *dns_server; + int n_best = -1; /* Never route things to scopes that lack DNS servers */ - dns_server = dns_scope_get_dns_server(s); - if (!dns_server) + if (!dns_scope_get_dns_server(s)) return DNS_SCOPE_NO; /* Always honour search domains for routing queries, except if this scope lacks DNS servers. Note that * we return DNS_SCOPE_YES here, rather than just DNS_SCOPE_MAYBE, which means other wildcard scopes * won't be considered anymore. */ LIST_FOREACH(domains, d, dns_scope_get_search_domains(s)) - if (dns_name_endswith(domain, d->name) > 0) - return DNS_SCOPE_YES; + if (dns_name_endswith(domain, d->name) > 0) { + int c; + + c = dns_name_count_labels(d->name); + if (c < 0) + continue; + + if (c > n_best) + n_best = c; + } - /* If the DNS server has route-only domains, don't send other requests to it. This would be a privacy - * violation, will most probably fail anyway, and adds unnecessary load. */ - if (dns_server_limited_domains(dns_server)) + /* Let's return the number of labels in the best matching result */ + if (n_best >= 0) { + assert(n_best <= DNS_SCOPE_YES_END - DNS_SCOPE_YES_BASE); + return DNS_SCOPE_YES_BASE + n_best; + } + + /* See if this scope is suitable as default route. */ + if (!dns_scope_is_default_route(s)) return DNS_SCOPE_NO; /* Exclude link-local IP ranges */ @@ -528,25 +573,48 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co return DNS_SCOPE_NO; } - case DNS_PROTOCOL_MDNS: + case DNS_PROTOCOL_MDNS: { + DnsScopeMatch m; + + m = accept_link_local_reverse_lookups(domain); + if (m >= 0) + return m; + if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || - (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || - (dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */ + (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0)) + return DNS_SCOPE_MAYBE; + + if ((dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */ dns_name_equal(domain, "local") == 0 && /* but not the single-label "local" name itself */ manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via mDNS */ - return DNS_SCOPE_MAYBE; + return DNS_SCOPE_YES_BASE + 1; /* Return +1, as the top-level .local domain matches, i.e. one label */ return DNS_SCOPE_NO; + } + + case DNS_PROTOCOL_LLMNR: { + DnsScopeMatch m; + + m = accept_link_local_reverse_lookups(domain); + if (m >= 0) + return m; - case DNS_PROTOCOL_LLMNR: if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || - (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || - (dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */ + (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0)) + return DNS_SCOPE_MAYBE; + + if ((dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */ !is_gateway_hostname(domain) && /* don't resolve "gateway" with LLMNR, let nss-myhostname handle this */ manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */ - return DNS_SCOPE_MAYBE; + return DNS_SCOPE_YES_BASE + 1; /* Return +1, as we consider ourselves authoritative for + * single-label names, i.e. one label. This is particular + * relevant as it means a "." route on some other scope won't + * pull all traffic away from us. (If people actually want to + * pull traffic away from us they should turn off LLMNR on the + * link) */ return DNS_SCOPE_NO; + } default: assert_not_reached("Unknown scope protocol"); @@ -1322,3 +1390,56 @@ int dns_scope_remove_dnssd_services(DnsScope *scope) { return 0; } + +static bool dns_scope_has_route_only_domains(DnsScope *scope) { + DnsSearchDomain *domain, *first; + bool route_only = false; + + assert(scope); + assert(scope->protocol == DNS_PROTOCOL_DNS); + + /* Returns 'true' if this scope is suitable for queries to specific domains only. For that we check + * if there are any route-only domains on this interface, as a heuristic to discern VPN-style links + * from non-VPN-style links. Returns 'false' for all other cases, i.e. if the scope is intended to + * take queries to arbitrary domains, i.e. has no routing domains set. */ + + if (scope->link) + first = scope->link->search_domains; + else + first = scope->manager->search_domains; + + LIST_FOREACH(domains, domain, first) { + /* "." means "any domain", thus the interface takes any kind of traffic. Thus, we exit early + * here, as it doesn't really matter whether this link has any route-only domains or not, + * "~." really trumps everything and clearly indicates that this interface shall receive all + * traffic it can get. */ + if (dns_name_is_root(DNS_SEARCH_DOMAIN_NAME(domain))) + return false; + + if (domain->route_only) + route_only = true; + } + + return route_only; +} + +bool dns_scope_is_default_route(DnsScope *scope) { + assert(scope); + + /* Only use DNS scopes as default routes */ + if (scope->protocol != DNS_PROTOCOL_DNS) + return false; + + /* The global DNS scope is always suitable as default route */ + if (!scope->link) + return true; + + /* Honour whatever is explicitly configured. This is really the best approach, and trumps any + * automatic logic. */ + if (scope->link->default_route >= 0) + return scope->link->default_route; + + /* Otherwise check if we have any route-only domains, as a sensible heuristic: if so, let's not + * volunteer as default route. */ + return !dns_scope_has_route_only_domains(scope); +} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 04e93f8f7..f4b45c4f2 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -18,9 +18,10 @@ typedef struct DnsScope DnsScope; typedef enum DnsScopeMatch { DNS_SCOPE_NO, DNS_SCOPE_MAYBE, - DNS_SCOPE_YES, + DNS_SCOPE_YES_BASE, /* Add the number of matching labels to this */ + DNS_SCOPE_YES_END = DNS_SCOPE_YES_BASE + DNS_N_LABELS_MAX, _DNS_SCOPE_MATCH_MAX, - _DNS_SCOPE_INVALID = -1 + _DNS_SCOPE_MATCH_INVALID = -1 } DnsScopeMatch; struct DnsScope { @@ -28,6 +29,8 @@ struct DnsScope { DnsProtocol protocol; int family; + + /* Copied at scope creation time from the link/manager */ DnssecMode dnssec_mode; DnsOverTlsMode dns_over_tls_mode; @@ -104,5 +107,6 @@ int dns_scope_ifindex(DnsScope *s); int dns_scope_announce(DnsScope *scope, bool goodbye); int dns_scope_add_dnssd_services(DnsScope *scope); - int dns_scope_remove_dnssd_services(DnsScope *scope); + +bool dns_scope_is_default_route(DnsScope *scope); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 3e69741b8..b85eb7527 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -580,26 +580,6 @@ void dns_server_warn_downgrade(DnsServer *server) { server->warned_downgrade = true; } -bool dns_server_limited_domains(DnsServer *server) { - DnsSearchDomain *domain; - bool domain_restricted = false; - - /* Check if the server has route-only domains without ~., i. e. whether - * it should only be used for particular domains */ - if (!server->link) - return false; - - LIST_FOREACH(domains, domain, server->link->search_domains) - if (domain->route_only) { - domain_restricted = true; - /* ~. means "any domain", thus it is a global server */ - if (dns_name_is_root(DNS_SEARCH_DOMAIN_NAME(domain))) - return false; - } - - return domain_restricted; -} - static void dns_server_hash_func(const DnsServer *s, struct siphash *state) { assert(s); @@ -906,6 +886,16 @@ void dns_server_unref_stream(DnsServer *s) { dns_stream_unref(ref); } +DnsScope *dns_server_scope(DnsServer *s) { + assert(s); + assert((s->type == DNS_SERVER_LINK) == !!s->link); + + if (s->link) + return s->link->unicast_scope; + + return s->manager->unicast_scope; +} + static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = { [DNS_SERVER_SYSTEM] = "system", [DNS_SERVER_FALLBACK] = "fallback", diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index 6e73f32df..3c4627bca 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -122,8 +122,6 @@ bool dns_server_dnssec_supported(DnsServer *server); void dns_server_warn_downgrade(DnsServer *server); -bool dns_server_limited_domains(DnsServer *server); - DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, int ifindex); void dns_server_unlink_all(DnsServer *first); @@ -153,3 +151,5 @@ void dns_server_reset_features_all(DnsServer *s); void dns_server_dump(DnsServer *s, FILE *f); void dns_server_unref_stream(DnsServer *s); + +DnsScope *dns_server_scope(DnsServer *s); diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c index b1581740d..96093fff5 100644 --- a/src/resolve/resolved-link-bus.c +++ b/src/resolve/resolved-link-bus.c @@ -107,6 +107,31 @@ static int property_get_domains( return sd_bus_message_close_container(reply); } +static int property_get_default_route( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *l = userdata; + + assert(reply); + assert(l); + + /* Return what is configured, if there's something configured */ + if (l->default_route >= 0) + return sd_bus_message_append(reply, "b", l->default_route); + + /* Otherwise report what is in effect */ + if (l->unicast_scope) + return sd_bus_message_append(reply, "b", dns_scope_is_default_route(l->unicast_scope)); + + return sd_bus_message_append(reply, "b", false); +} + static int property_get_scopes_mask( sd_bus *bus, const char *path, @@ -346,6 +371,31 @@ clear: return r; } +int bus_link_method_set_default_route(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + int r, b; + + assert(message); + assert(l); + + r = verify_unmanaged_link(l, error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (l->default_route != b) { + l->default_route = b; + + (void) link_save_user(l); + (void) manager_write_resolv_conf(l->manager); + } + + return sd_bus_reply_method_return(message, NULL); +} + int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { Link *l = userdata; ResolveSupport mode; @@ -550,6 +600,7 @@ const sd_bus_vtable link_vtable[] = { SD_BUS_PROPERTY("DNS", "a(iay)", property_get_dns, 0, 0), SD_BUS_PROPERTY("CurrentDNSServer", "(iay)", property_get_current_dns_server, offsetof(Link, current_dns_server), 0), SD_BUS_PROPERTY("Domains", "a(sb)", property_get_domains, 0, 0), + SD_BUS_PROPERTY("DefaultRoute", "b", property_get_default_route, 0, 0), SD_BUS_PROPERTY("LLMNR", "s", bus_property_get_resolve_support, offsetof(Link, llmnr_support), 0), SD_BUS_PROPERTY("MulticastDNS", "s", bus_property_get_resolve_support, offsetof(Link, mdns_support), 0), SD_BUS_PROPERTY("DNSOverTLS", "s", property_get_dns_over_tls_mode, 0, 0), @@ -559,6 +610,7 @@ const sd_bus_vtable link_vtable[] = { SD_BUS_METHOD("SetDNS", "a(iay)", NULL, bus_link_method_set_dns_servers, 0), SD_BUS_METHOD("SetDomains", "a(sb)", NULL, bus_link_method_set_domains, 0), + SD_BUS_METHOD("SetDefaultRoute", "b", NULL, bus_link_method_set_default_route, 0), SD_BUS_METHOD("SetLLMNR", "s", NULL, bus_link_method_set_llmnr, 0), SD_BUS_METHOD("SetMulticastDNS", "s", NULL, bus_link_method_set_mdns, 0), SD_BUS_METHOD("SetDNSOverTLS", "s", NULL, bus_link_method_set_dns_over_tls, 0), diff --git a/src/resolve/resolved-link-bus.h b/src/resolve/resolved-link-bus.h index 6b8092fdb..671725101 100644 --- a/src/resolve/resolved-link-bus.h +++ b/src/resolve/resolved-link-bus.h @@ -13,6 +13,7 @@ int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char *** int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_default_route(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_link_method_set_dns_over_tls(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 2cfb848a8..44f70acea 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -30,16 +30,19 @@ int link_new(Manager *m, Link **ret, int ifindex) { if (r < 0) return r; - l = new0(Link, 1); + l = new(Link, 1); if (!l) return -ENOMEM; - l->ifindex = ifindex; - l->llmnr_support = RESOLVE_SUPPORT_YES; - l->mdns_support = RESOLVE_SUPPORT_NO; - l->dnssec_mode = _DNSSEC_MODE_INVALID; - l->dns_over_tls_mode = _DNS_OVER_TLS_MODE_INVALID; - l->operstate = IF_OPER_UNKNOWN; + *l = (Link) { + .ifindex = ifindex, + .default_route = -1, + .llmnr_support = RESOLVE_SUPPORT_YES, + .mdns_support = RESOLVE_SUPPORT_NO, + .dnssec_mode = _DNSSEC_MODE_INVALID, + .dns_over_tls_mode = _DNS_OVER_TLS_MODE_INVALID, + .operstate = IF_OPER_UNKNOWN, + }; if (asprintf(&l->state_file, "/run/systemd/resolve/netif/%i", ifindex) < 0) return -ENOMEM; @@ -60,6 +63,7 @@ int link_new(Manager *m, Link **ret, int ifindex) { void link_flush_settings(Link *l) { assert(l); + l->default_route = -1; l->llmnr_support = RESOLVE_SUPPORT_YES; l->mdns_support = RESOLVE_SUPPORT_NO; l->dnssec_mode = _DNSSEC_MODE_INVALID; @@ -297,6 +301,27 @@ clear: return r; } +static int link_update_default_route(Link *l) { + int r; + + assert(l); + + r = sd_network_link_get_dns_default_route(l->ifindex); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + l->default_route = r > 0; + return 0; + +clear: + l->default_route = -1; + return r; +} + static int link_update_llmnr_support(Link *l) { _cleanup_free_ char *b = NULL; int r; @@ -613,6 +638,10 @@ static void link_read_settings(Link *l) { r = link_update_search_domains(l); if (r < 0) log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name); + + r = link_update_default_route(l); + if (r < 0) + log_warning_errno(r, "Failed to read default route setting for interface %s, proceeding anyway: %m", l->name); } int link_update(Link *l) { @@ -1109,7 +1138,8 @@ static bool link_needs_save(Link *l) { if (l->llmnr_support != RESOLVE_SUPPORT_YES || l->mdns_support != RESOLVE_SUPPORT_NO || - l->dnssec_mode != _DNSSEC_MODE_INVALID) + l->dnssec_mode != _DNSSEC_MODE_INVALID || + l->dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID) return true; if (l->dns_servers || @@ -1119,6 +1149,9 @@ static bool link_needs_save(Link *l) { if (!set_isempty(l->dnssec_negative_trust_anchors)) return true; + if (l->default_route >= 0) + return true; + return false; } @@ -1161,6 +1194,9 @@ int link_save_user(Link *l) { if (v) fprintf(f, "DNSSEC=%s\n", v); + if (l->default_route >= 0) + fprintf(f, "DEFAULT_ROUTE=%s\n", yes_no(l->default_route)); + if (l->dns_servers) { DnsServer *server; @@ -1242,7 +1278,8 @@ int link_load_user(Link *l) { *dnssec = NULL, *servers = NULL, *domains = NULL, - *ntas = NULL; + *ntas = NULL, + *default_route = NULL; ResolveSupport s; const char *p; @@ -1265,7 +1302,8 @@ int link_load_user(Link *l) { "DNSSEC", &dnssec, "SERVERS", &servers, "DOMAINS", &domains, - "NTAS", &ntas); + "NTAS", &ntas, + "DEFAULT_ROUTE", &default_route); if (r == -ENOENT) return 0; if (r < 0) @@ -1282,6 +1320,10 @@ int link_load_user(Link *l) { if (s >= 0) l->mdns_support = s; + r = parse_boolean(default_route); + if (r >= 0) + l->default_route = r; + /* If we can't recognize the DNSSEC setting, then set it to invalid, so that the daemon default is used. */ l->dnssec_mode = dnssec_mode_from_string(dnssec); diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index 81ab2056a..f95ea37a4 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -51,6 +51,8 @@ struct Link { LIST_HEAD(DnsSearchDomain, search_domains); unsigned n_search_domains; + int default_route; + ResolveSupport llmnr_support; ResolveSupport mdns_support; DnsOverTlsMode dns_over_tls_mode; diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c index 5fcd59d87..5205071d3 100644 --- a/src/resolve/resolved-resolv-conf.c +++ b/src/resolve/resolved-resolv-conf.c @@ -217,6 +217,8 @@ clear: } static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { + DnsScope *scope; + assert(s); assert(f); assert(count); @@ -226,13 +228,12 @@ static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { return; } - /* Check if the DNS server is limited to particular domains; - * resolv.conf does not have a syntax to express that, so it must not - * appear as a global name server to avoid routing unrelated domains to - * it (which is a privacy violation, will most probably fail anyway, - * and adds unnecessary load) */ - if (dns_server_limited_domains(s)) { - log_debug("DNS server %s has route-only domains, not using as global name server", dns_server_string(s)); + /* Check if the scope this DNS server belongs to is suitable as 'default' route for lookups; resolv.conf does + * not have a syntax to express that, so it must not appear as a global name server to avoid routing unrelated + * domains to it (which is a privacy violation, will most probably fail anyway, and adds unnecessary load) */ + scope = dns_server_scope(s); + if (scope && !dns_scope_is_default_route(scope)) { + log_debug("Scope of DNS server %s has only route-only domains, not using as global name server", dns_server_string(s)); return; } diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h index c8b7226bc..cc6bca9f5 100644 --- a/src/systemd/sd-network.h +++ b/src/systemd/sd-network.h @@ -151,6 +151,9 @@ int sd_network_link_get_search_domains(int ifindex, char ***domains); /* Get the route DNS domain names for a given link. */ int sd_network_link_get_route_domains(int ifindex, char ***domains); +/* Get whether this link shall be used as 'default route' for DNS queries */ +int sd_network_link_get_dns_default_route(int ifindex); + /* Get the carrier interface indexes to which current link is bound to. */ int sd_network_link_get_carrier_bound_to(int ifindex, int **ifindexes); |