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