resolved: add an explicit way to configure whether a link is useful as default route

Previously, we'd use a link as "default" route depending on whether
there are route-only domains defined on it or not. (If there are, it
would not be used as default route, if there aren't it would.)

Let's make this explicit and add a link variable controlling this. The
variable is not changeable from the outside yet, but subsequent commits
are supposed to add that.

Note that making this configurable adds a certain amount of redundancy,
as there are now two ways to ensure a link does not receive "default"
lookup (i.e. DNS queries matching no configured route):

1. By ensuring that at least one other link configures a route on it
   (for example by add "." to its search list)

2. By setting this new boolean to false.

But this is exactly what is intended with this patch: that there is an
explicit way to configure on the link itself whether it receives
'default' traffic, rather than require this to be configured on other
links.

The variable added is a tri-state: if true, the link is suitable for
recieving "default" traffic. If false, the link is not suitable for it.
If unset (i.e. negative) the original logic of "has this route-only
routes" is used, to ensure compatibility with the status quo ante.
This commit is contained in:
Lennart Poettering 2018-12-04 12:40:07 +01:00
parent f76fa08899
commit ca5394d260
5 changed files with 66 additions and 23 deletions

View File

@ -554,9 +554,8 @@ DnsScopeMatch dns_scope_good_domain(
return DNS_SCOPE_YES_BASE + n_best;
}
/* 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_scope_has_route_only_domains(s))
/* 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 */
@ -1392,15 +1391,17 @@ int dns_scope_remove_dnssd_services(DnsScope *scope) {
return 0;
}
bool dns_scope_has_route_only_domains(DnsScope *scope) {
static bool dns_scope_has_route_only_domains(DnsScope *scope) {
DnsSearchDomain *domain, *first;
bool route_only = false;
/* Check if the scope has any route-only domains except for "~.", i. e. whether it should only be
* used for particular domains */
assert(scope);
assert(scope->protocol == DNS_PROTOCOL_DNS);
if (scope->protocol != DNS_PROTOCOL_DNS)
return false;
/* 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;
@ -1408,7 +1409,10 @@ bool dns_scope_has_route_only_domains(DnsScope *scope) {
first = scope->manager->search_domains;
LIST_FOREACH(domains, domain, first) {
/* "." means "any domain", thus the interface takes any kind of traffic. */
/* "." 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;
@ -1418,3 +1422,24 @@ bool dns_scope_has_route_only_domains(DnsScope *scope) {
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);
}

View File

@ -109,4 +109,4 @@ 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_has_route_only_domains(DnsScope *scope);
bool dns_scope_is_default_route(DnsScope *scope);

View File

@ -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;
@ -1120,6 +1124,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;
}
@ -1162,6 +1169,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;
@ -1243,7 +1253,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;
@ -1266,7 +1277,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)
@ -1283,6 +1295,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);

View File

@ -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;

View File

@ -228,11 +228,11 @@ static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
return;
}
/* Check if the scope this DNS server belongs to 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) */
/* 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_has_route_only_domains(scope)) {
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;
}