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