2aacef
From 677b20b6738ee287d1b882815b3bcca67754e003 Mon Sep 17 00:00:00 2001
2aacef
From: Lennart Poettering <lennart@poettering.net>
2aacef
Date: Fri, 25 Nov 2022 12:15:56 +0100
2aacef
Subject: [PATCH] resolved: introduce the _localdnsstub and _localdnsproxy
2aacef
 special hostnames for 127.0.0.54 + 127.0.0.53
2aacef
2aacef
Let's give these special IP addresses names. After all name resolution
2aacef
is our job here.
2aacef
2aacef
Fixes: #23623
2aacef
(cherry picked from commit 17f244e8f9de008ea1c6e0880bdc924b95a66e2b)
2aacef
2aacef
Related: #2138081
2aacef
---
2aacef
 man/resolvectl.xml                    |  11 +--
2aacef
 man/systemd-resolved.service.xml      |   6 ++
2aacef
 src/basic/hostname-util.h             |   8 ++
2aacef
 src/resolve/resolvectl.c              |   6 +-
2aacef
 src/resolve/resolved-dns-scope.c      |   7 +-
2aacef
 src/resolve/resolved-dns-synthesize.c | 110 +++++++++++++++++++++++++-
2aacef
 test/units/testsuite-75.sh            |  11 +++
2aacef
 7 files changed, 147 insertions(+), 12 deletions(-)
2aacef
2aacef
diff --git a/man/resolvectl.xml b/man/resolvectl.xml
2aacef
index 2cb855c360..c966ca67bd 100644
2aacef
--- a/man/resolvectl.xml
2aacef
+++ b/man/resolvectl.xml
2aacef
@@ -323,11 +323,12 @@
2aacef
 
2aacef
         <listitem><para>Takes a boolean parameter; used in conjunction with <command>query</command>. If true
2aacef
         (the default), select domains are resolved on the local system, among them
2aacef
-        <literal>localhost</literal>, <literal>_gateway</literal> and <literal>_outbound</literal>, or
2aacef
-        entries from <filename>/etc/hosts</filename>. If false these domains are not resolved locally, and
2aacef
-        either fail (in case of <literal>localhost</literal>, <literal>_gateway</literal> or
2aacef
-        <literal>_outbound</literal> and suchlike) or go to the network via regular DNS/mDNS/LLMNR lookups
2aacef
-        (in case of <filename>/etc/hosts</filename> entries).</para></listitem>
2aacef
+        <literal>localhost</literal>, <literal>_gateway</literal>, <literal>_outbound</literal>,
2aacef
+        <literal>_localdnsstub</literal> and <literal>_localdnsproxy</literal> or entries from
2aacef
+        <filename>/etc/hosts</filename>. If false these domains are not resolved locally, and either fail (in
2aacef
+        case of <literal>localhost</literal>, <literal>_gateway</literal> or <literal>_outbound</literal> and
2aacef
+        suchlike) or go to the network via regular DNS/mDNS/LLMNR lookups (in case of
2aacef
+        <filename>/etc/hosts</filename> entries).</para></listitem>
2aacef
       </varlistentry>
2aacef
 
2aacef
       <varlistentry>
2aacef
diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml
2aacef
index 7f30fa6536..c006c03b53 100644
2aacef
--- a/man/systemd-resolved.service.xml
2aacef
+++ b/man/systemd-resolved.service.xml
2aacef
@@ -118,6 +118,12 @@
2aacef
       local default gateway configured. This assigns a stable hostname to the local outbound IP addresses,
2aacef
       useful for referencing them independently of the current network configuration state.</para></listitem>
2aacef
 
2aacef
+      <listitem><para>The hostname <literal>_localdnsstub</literal> is resolved to the IP address 127.0.0.53,
2aacef
+      i.e. the address the local DNS stub (see above) is listening on.</para></listitem>
2aacef
+
2aacef
+      <listitem><para>The hostname <literal>_localdnsproxy</literal> is resolved to the IP address 127.0.0.54,
2aacef
+      i.e. the address the local DNS proxy (see above) is listening on.</para></listitem>
2aacef
+
2aacef
       <listitem><para>The mappings defined in <filename>/etc/hosts</filename> are resolved to their
2aacef
       configured addresses and back, but they will not affect lookups for non-address types (like MX).
2aacef
       Support for <filename>/etc/hosts</filename> may be disabled with <varname>ReadEtcHosts=no</varname>,
2aacef
diff --git a/src/basic/hostname-util.h b/src/basic/hostname-util.h
2aacef
index a00b852395..bcac3d9fb0 100644
2aacef
--- a/src/basic/hostname-util.h
2aacef
+++ b/src/basic/hostname-util.h
2aacef
@@ -60,4 +60,12 @@ static inline bool is_outbound_hostname(const char *hostname) {
2aacef
         return STRCASE_IN_SET(hostname, "_outbound", "_outbound.");
2aacef
 }
2aacef
 
2aacef
+static inline bool is_dns_stub_hostname(const char *hostname) {
2aacef
+        return STRCASE_IN_SET(hostname, "_localdnsstub", "_localdnsstub.");
2aacef
+}
2aacef
+
2aacef
+static inline bool is_dns_proxy_stub_hostname(const char *hostname) {
2aacef
+        return STRCASE_IN_SET(hostname, "_localdnsproxy", "_localdnsproxy.");
2aacef
+}
2aacef
+
2aacef
 int get_pretty_hostname(char **ret);
2aacef
diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c
2aacef
index b07761a495..2a7347ca27 100644
2aacef
--- a/src/resolve/resolvectl.c
2aacef
+++ b/src/resolve/resolvectl.c
2aacef
@@ -478,7 +478,11 @@ static bool single_label_nonsynthetic(const char *name) {
2aacef
         if (!dns_name_is_single_label(name))
2aacef
                 return false;
2aacef
 
2aacef
-        if (is_localhost(name) || is_gateway_hostname(name))
2aacef
+        if (is_localhost(name) ||
2aacef
+            is_gateway_hostname(name) ||
2aacef
+            is_outbound_hostname(name) ||
2aacef
+            is_dns_stub_hostname(name) ||
2aacef
+            is_dns_proxy_stub_hostname(name))
2aacef
                 return false;
2aacef
 
2aacef
         r = resolve_system_hostname(NULL, &first_label);
2aacef
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
2aacef
index 4f744499aa..607109ee0f 100644
2aacef
--- a/src/resolve/resolved-dns-scope.c
2aacef
+++ b/src/resolve/resolved-dns-scope.c
2aacef
@@ -635,8 +635,11 @@ DnsScopeMatch dns_scope_good_domain(
2aacef
         if (dns_name_dont_resolve(domain))
2aacef
                 return DNS_SCOPE_NO;
2aacef
 
2aacef
-        /* Never go to network for the _gateway or _outbound domain — they're something special, synthesized locally. */
2aacef
-        if (is_gateway_hostname(domain) || is_outbound_hostname(domain))
2aacef
+        /* Never go to network for the _gateway, _outbound, _localdnsstub, _localdnsproxy domain — they're something special, synthesized locally. */
2aacef
+        if (is_gateway_hostname(domain) ||
2aacef
+            is_outbound_hostname(domain) ||
2aacef
+            is_dns_stub_hostname(domain) ||
2aacef
+            is_dns_proxy_stub_hostname(domain))
2aacef
                 return DNS_SCOPE_NO;
2aacef
 
2aacef
         switch (s->protocol) {
2aacef
diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c
2aacef
index b3442ad906..fa8b4a5760 100644
2aacef
--- a/src/resolve/resolved-dns-synthesize.c
2aacef
+++ b/src/resolve/resolved-dns-synthesize.c
2aacef
@@ -356,7 +356,90 @@ static int synthesize_gateway_rr(
2aacef
         return 1; /* > 0 means: we have some gateway */
2aacef
 }
2aacef
 
2aacef
-static int synthesize_gateway_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) {
2aacef
+static int synthesize_dns_stub_rr(
2aacef
+                Manager *m,
2aacef
+                const DnsResourceKey *key,
2aacef
+                in_addr_t addr,
2aacef
+                DnsAnswer **answer) {
2aacef
+
2aacef
+        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
2aacef
+        int r;
2aacef
+
2aacef
+        assert(m);
2aacef
+        assert(key);
2aacef
+        assert(answer);
2aacef
+
2aacef
+        if (!IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY))
2aacef
+                return 1; /* we still consider ourselves the owner of this name */
2aacef
+
2aacef
+        r = dns_answer_reserve(answer, 1);
2aacef
+        if (r < 0)
2aacef
+                return r;
2aacef
+
2aacef
+        rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, dns_resource_key_name(key));
2aacef
+        if (!rr)
2aacef
+                return -ENOMEM;
2aacef
+
2aacef
+        rr->a.in_addr.s_addr = htobe32(addr);
2aacef
+
2aacef
+        r = dns_answer_add(*answer, rr, LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED, NULL);
2aacef
+        if (r < 0)
2aacef
+                return r;
2aacef
+
2aacef
+        return 1;
2aacef
+}
2aacef
+
2aacef
+static int synthesize_dns_stub_ptr(
2aacef
+                Manager *m,
2aacef
+                int af,
2aacef
+                const union in_addr_union *address,
2aacef
+                DnsAnswer **answer) {
2aacef
+
2aacef
+        int r;
2aacef
+
2aacef
+        assert(m);
2aacef
+        assert(address);
2aacef
+        assert(answer);
2aacef
+
2aacef
+        if (af != AF_INET)
2aacef
+                return 0;
2aacef
+
2aacef
+        if (address->in.s_addr == htobe32(INADDR_DNS_STUB)) {
2aacef
+
2aacef
+                r = dns_answer_reserve(answer, 1);
2aacef
+                if (r < 0)
2aacef
+                        return r;
2aacef
+
2aacef
+                r = answer_add_ptr(answer, "53.0.0.127.in-addr.arpa", "_localdnsstub", LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED);
2aacef
+                if (r < 0)
2aacef
+                        return r;
2aacef
+
2aacef
+                return 1;
2aacef
+        }
2aacef
+
2aacef
+        if (address->in.s_addr == htobe32(INADDR_DNS_PROXY_STUB)) {
2aacef
+
2aacef
+                r = dns_answer_reserve(answer, 1);
2aacef
+                if (r < 0)
2aacef
+                        return r;
2aacef
+
2aacef
+                r = answer_add_ptr(answer, "54.0.0.127.in-addr.arpa", "_localdnsproxy", LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED);
2aacef
+                if (r < 0)
2aacef
+                        return r;
2aacef
+
2aacef
+                return 1;
2aacef
+        }
2aacef
+
2aacef
+        return 0;
2aacef
+}
2aacef
+
2aacef
+static int synthesize_gateway_ptr(
2aacef
+                Manager *m,
2aacef
+                int af,
2aacef
+                const union in_addr_union *address,
2aacef
+                int ifindex,
2aacef
+                DnsAnswer **answer) {
2aacef
+
2aacef
         _cleanup_free_ struct local_address *addresses = NULL;
2aacef
         int n;
2aacef
 
2aacef
@@ -437,7 +520,22 @@ int dns_synthesize_answer(
2aacef
                                 continue;
2aacef
                         }
2aacef
 
2aacef
-                } else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) ||
2aacef
+                } else if (is_dns_stub_hostname(name)) {
2aacef
+
2aacef
+                        r = synthesize_dns_stub_rr(m, key, INADDR_DNS_STUB, &answer);
2aacef
+                        if (r < 0)
2aacef
+                                return log_error_errno(r, "Failed to synthesize local DNS stub RRs: %m");
2aacef
+
2aacef
+                } else if (is_dns_proxy_stub_hostname(name)) {
2aacef
+
2aacef
+                        r = synthesize_dns_stub_rr(m, key, INADDR_DNS_PROXY_STUB, &answer);
2aacef
+                        if (r < 0)
2aacef
+                                return log_error_errno(r, "Failed to synthesize local DNS stub RRs: %m");
2aacef
+
2aacef
+                } else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 &&
2aacef
+                            dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0 &&
2aacef
+                            dns_name_equal(name, "53.0.0.127.in-addr.arpa") == 0 &&
2aacef
+                            dns_name_equal(name, "54.0.0.127.in-addr.arpa") == 0) ||
2aacef
                            dns_name_equal(name, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) {
2aacef
 
2aacef
                         r = synthesize_localhost_ptr(m, key, ifindex, &answer);
2aacef
@@ -445,7 +543,7 @@ int dns_synthesize_answer(
2aacef
                                 return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m");
2aacef
 
2aacef
                 } else if (dns_name_address(name, &af, &address) > 0) {
2aacef
-                        int v, w;
2aacef
+                        int v, w, u;
2aacef
 
2aacef
                         if (getenv_bool("SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME") == 0)
2aacef
                                 continue;
2aacef
@@ -458,7 +556,11 @@ int dns_synthesize_answer(
2aacef
                         if (w < 0)
2aacef
                                 return log_error_errno(w, "Failed to synthesize gateway hostname PTR RR: %m");
2aacef
 
2aacef
-                        if (v == 0 && w == 0) /* This IP address is neither a local one nor a gateway */
2aacef
+                        u = synthesize_dns_stub_ptr(m, af, &address, &answer);
2aacef
+                        if (u < 0)
2aacef
+                                return log_error_errno(u, "Failed to synthesize local stub hostname PTR PR: %m");
2aacef
+
2aacef
+                        if (v == 0 && w == 0 && u == 0) /* This IP address is neither a local one, nor a gateway, nor a stub address */
2aacef
                                 continue;
2aacef
 
2aacef
                         /* Note that we never synthesize reverse PTR for _outbound, since those are local
2aacef
diff --git a/test/units/testsuite-75.sh b/test/units/testsuite-75.sh
2aacef
index 1a656fcdc1..0c68e0636f 100755
2aacef
--- a/test/units/testsuite-75.sh
2aacef
+++ b/test/units/testsuite-75.sh
2aacef
@@ -56,6 +56,17 @@ echo nameserver 10.0.3.3 10.0.3.4 | "$RESOLVCONF" -a hoge.foo.dhcp
2aacef
 assert_in '10.0.3.1 10.0.3.2' "$(resolvectl dns hoge)"
2aacef
 assert_in '10.0.3.3 10.0.3.4' "$(resolvectl dns hoge.foo)"
2aacef
 
2aacef
+# Tests for _localdnsstub and _localdnsproxy
2aacef
+assert_in '127.0.0.53' "$(resolvectl query _localdnsstub)"
2aacef
+assert_in '_localdnsstub' "$(resolvectl query 127.0.0.53)"
2aacef
+assert_in '127.0.0.54' "$(resolvectl query _localdnsproxy)"
2aacef
+assert_in '_localdnsproxy' "$(resolvectl query 127.0.0.54)"
2aacef
+
2aacef
+assert_in '127.0.0.53' "$(dig @127.0.0.53 _localdnsstub)"
2aacef
+assert_in '_localdnsstub' "$(dig @127.0.0.53 -x 127.0.0.53)"
2aacef
+assert_in '127.0.0.54' "$(dig @127.0.0.53 _localdnsproxy)"
2aacef
+assert_in '_localdnsproxy' "$(dig @127.0.0.53 -x 127.0.0.54)"
2aacef
+
2aacef
 # Tests for mDNS and LLMNR settings
2aacef
 mkdir -p /run/systemd/resolved.conf.d
2aacef
 {