984f77
From b9844d3dd9d7fdf81d475b81d06a6e9ec821f91d Mon Sep 17 00:00:00 2001
984f77
From: Lennart Poettering <lennart@poettering.net>
984f77
Date: Mon, 9 Nov 2020 22:22:56 +0100
984f77
Subject: [PATCH] resolved: let's preferably route reverse lookups for local
984f77
 subnets to matching interfaces
984f77
984f77
Let's preferably route traffic for reverse lookups to LLMNR/mDNS/DNS on
984f77
the matching interface if the IP address is in the local subnet. Also,
984f77
if looking up an IP address of our own host, let's avoid doing
984f77
LLMNR/mDNS at all.
984f77
984f77
This is useful if "~." is a routing domain to DNS, as it means, local
984f77
reverse lookups still go to LLMNR/mDNS, too.
984f77
984f77
(cherry picked from commit 13eb76ef06f5d50bbeb58df1744057e41ef2647e)
984f77
984f77
Resolves #1739689
984f77
---
984f77
 src/resolve/resolved-dns-scope.c | 85 +++++++++++++++++++++++++++++++-
984f77
 src/resolve/resolved-link.c      | 12 +++--
984f77
 src/resolve/resolved-link.h      |  1 +
984f77
 3 files changed, 92 insertions(+), 6 deletions(-)
984f77
984f77
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
984f77
index 38ea7fea0a..8b65813428 100644
984f77
--- a/src/resolve/resolved-dns-scope.c
984f77
+++ b/src/resolve/resolved-dns-scope.c
984f77
@@ -417,6 +417,65 @@ int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *add
984f77
         return dns_scope_socket(s, SOCK_STREAM, family, address, server, port, ret_socket_address);
984f77
 }
984f77
 
984f77
+static DnsScopeMatch match_subnet_reverse_lookups(
984f77
+                DnsScope *s,
984f77
+                const char *domain,
984f77
+                bool exclude_own) {
984f77
+
984f77
+        union in_addr_union ia;
984f77
+        LinkAddress *a;
984f77
+        int f, r;
984f77
+
984f77
+        assert(s);
984f77
+        assert(domain);
984f77
+
984f77
+        /* Checks whether the specified domain is a reverse address domain (i.e. in the .in-addr.arpa or
984f77
+         * .ip6.arpa area), and if so, whether the address matches any of the local subnets of the link the
984f77
+         * scope is associated with. If so, our scope should consider itself relevant for any lookup in the
984f77
+         * domain, since it apparently refers to hosts on this link's subnet.
984f77
+         *
984f77
+         * If 'exclude_own' is true this will return DNS_SCOPE_NO for any IP addresses assigned locally. This
984f77
+         * is useful for LLMNR/mDNS as we never want to look up our own hostname on LLMNR/mDNS but always use
984f77
+         * the locally synthesized one. */
984f77
+
984f77
+        if (!s->link)
984f77
+                return _DNS_SCOPE_INVALID; /* No link, hence no local addresses to check */
984f77
+
984f77
+        r = dns_name_address(domain, &f, &ia);
984f77
+        if (r < 0)
984f77
+                log_debug_errno(r, "Failed to determine whether '%s' is an address domain: %m", domain);
984f77
+        if (r <= 0)
984f77
+                return _DNS_SCOPE_INVALID;
984f77
+
984f77
+        if (s->family != AF_UNSPEC && f != s->family)
984f77
+                return _DNS_SCOPE_INVALID; /* Don't look for IPv4 addresses on LLMNR/mDNS over IPv6 and vice versa */
984f77
+
984f77
+        LIST_FOREACH(addresses, a, s->link->addresses) {
984f77
+
984f77
+                if (a->family != f)
984f77
+                        continue;
984f77
+
984f77
+                /* Equals our own address? nah, let's not use this scope. The local synthesizer will pick it up for us. */
984f77
+                if (exclude_own &&
984f77
+                    in_addr_equal(f, &a->in_addr, &ia) > 0)
984f77
+                        return DNS_SCOPE_NO;
984f77
+
984f77
+                if (a->prefixlen == UCHAR_MAX) /* don't know subnet mask */
984f77
+                        continue;
984f77
+
984f77
+                /* Check if the address is in the local subnet */
984f77
+                r = in_addr_prefix_covers(f, &a->in_addr, a->prefixlen, &ia);
984f77
+                if (r < 0)
984f77
+                        log_debug_errno(r, "Failed to determine whether link address covers lookup address '%s': %m", domain);
984f77
+                if (r > 0)
984f77
+                        /* Note that we only claim zero labels match. This is so that this is at the same
984f77
+                         * priority a DNS scope with "." as routing domain is. */
984f77
+                        return DNS_SCOPE_YES + 0;
984f77
+        }
984f77
+
984f77
+        return _DNS_SCOPE_INVALID;
984f77
+}
984f77
+
984f77
 DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) {
984f77
         DnsSearchDomain *d;
984f77
 
984f77
@@ -455,6 +514,7 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
984f77
 
984f77
         case DNS_PROTOCOL_DNS: {
984f77
                 DnsServer *dns_server;
984f77
+                DnsScopeMatch m;
984f77
 
984f77
                 /* Never route things to scopes that lack DNS servers */
984f77
                 dns_server = dns_scope_get_dns_server(s);
984f77
@@ -485,10 +545,23 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
984f77
                     dns_name_endswith(domain, "local") == 0)
984f77
                         return DNS_SCOPE_MAYBE;
984f77
 
984f77
+                /* If the IP address to look up matches the local subnet, then implicity synthesizes
984f77
+                 * DNS_SCOPE_YES_BASE + 0 on this interface, i.e. preferably resolve IP addresses via the DNS
984f77
+                 * server belonging to this interface. */
984f77
+                m = match_subnet_reverse_lookups(s, domain, false);
984f77
+                if (m >= 0)
984f77
+                        return m;
984f77
+
984f77
                 return DNS_SCOPE_NO;
984f77
         }
984f77
 
984f77
-        case DNS_PROTOCOL_MDNS:
984f77
+        case DNS_PROTOCOL_MDNS: {
984f77
+                DnsScopeMatch m;
984f77
+
984f77
+                m = match_subnet_reverse_lookups(s, domain, true);
984f77
+                if (m >= 0)
984f77
+                        return m;
984f77
+
984f77
                 if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) ||
984f77
                     (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) ||
984f77
                     (dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */
984f77
@@ -497,8 +570,15 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
984f77
                         return DNS_SCOPE_MAYBE;
984f77
 
984f77
                 return DNS_SCOPE_NO;
984f77
+        }
984f77
+
984f77
+        case DNS_PROTOCOL_LLMNR: {
984f77
+                DnsScopeMatch m;
984f77
+
984f77
+                m = match_subnet_reverse_lookups(s, domain, true);
984f77
+                if (m >= 0)
984f77
+                        return m;
984f77
 
984f77
-        case DNS_PROTOCOL_LLMNR:
984f77
                 if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) ||
984f77
                     (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) ||
984f77
                     (dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */
984f77
@@ -507,6 +587,7 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
984f77
                         return DNS_SCOPE_MAYBE;
984f77
 
984f77
                 return DNS_SCOPE_NO;
984f77
+        }
984f77
 
984f77
         default:
984f77
                 assert_not_reached("Unknown scope protocol");
984f77
diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c
984f77
index ff2be12415..c42fe5b5f4 100644
984f77
--- a/src/resolve/resolved-link.c
984f77
+++ b/src/resolve/resolved-link.c
984f77
@@ -776,10 +776,13 @@ int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr
984f77
         if (!a)
984f77
                 return -ENOMEM;
984f77
 
984f77
-        a->family = family;
984f77
-        a->in_addr = *in_addr;
984f77
+        *a = (LinkAddress) {
984f77
+                .family = family,
984f77
+                .in_addr = *in_addr,
984f77
+                .link = l,
984f77
+                .prefixlen = UCHAR_MAX,
984f77
+        };
984f77
 
984f77
-        a->link = l;
984f77
         LIST_PREPEND(addresses, l->addresses, a);
984f77
         l->n_addresses++;
984f77
 
984f77
@@ -1077,7 +1080,8 @@ int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m) {
984f77
         if (r < 0)
984f77
                 return r;
984f77
 
984f77
-        sd_rtnl_message_addr_get_scope(m, &a->scope);
984f77
+        (void) sd_rtnl_message_addr_get_prefixlen(m, &a->prefixlen);
984f77
+        (void) sd_rtnl_message_addr_get_scope(m, &a->scope);
984f77
 
984f77
         link_allocate_scopes(a->link);
984f77
         link_add_rrs(a->link, false);
984f77
diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h
984f77
index 063d3f35c3..8d52b10950 100644
984f77
--- a/src/resolve/resolved-link.h
984f77
+++ b/src/resolve/resolved-link.h
984f77
@@ -24,6 +24,7 @@ struct LinkAddress {
984f77
 
984f77
         int family;
984f77
         union in_addr_union in_addr;
984f77
+        unsigned char prefixlen;
984f77
 
984f77
         unsigned char flags, scope;
984f77