Zbigniew Jędrzejewski-Szmek 4b2af1
From e0ae456a554d0fce250f9a009c561b97f20c41f8 Mon Sep 17 00:00:00 2001
Adam Williamson e5f70d
From: Lennart Poettering <lennart@poettering.net>
Adam Williamson e5f70d
Date: Fri, 5 Mar 2021 17:47:45 +0100
Adam Williamson e5f70d
Subject: [PATCH 1/6] dns-query: export CNAME_MAX, so that we can use it in
Adam Williamson e5f70d
 other files, too
Adam Williamson e5f70d
MIME-Version: 1.0
Adam Williamson e5f70d
Content-Type: text/plain; charset=UTF-8
Adam Williamson e5f70d
Content-Transfer-Encoding: 8bit
Adam Williamson e5f70d
Adam Williamson e5f70d
Let's rename it a bit, to be more explanatory while exporting it.
Adam Williamson e5f70d
Adam Williamson e5f70d
(And let's bump the CNAME limit to 16 — 8 just sounded so little)
Adam Williamson e5f70d
---
Adam Williamson e5f70d
 src/resolve/resolved-dns-query.c | 3 +--
Adam Williamson e5f70d
 src/resolve/resolved-dns-query.h | 2 ++
Adam Williamson e5f70d
 2 files changed, 3 insertions(+), 2 deletions(-)
Adam Williamson e5f70d
Adam Williamson e5f70d
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
Adam Williamson e5f70d
index 7554d1e82f4..aa9d65d4a82 100644
Adam Williamson e5f70d
--- a/src/resolve/resolved-dns-query.c
Adam Williamson e5f70d
+++ b/src/resolve/resolved-dns-query.c
Adam Williamson e5f70d
@@ -10,7 +10,6 @@
Adam Williamson e5f70d
 #include "resolved-etc-hosts.h"
Adam Williamson e5f70d
 #include "string-util.h"
Adam Williamson e5f70d
 
Adam Williamson e5f70d
-#define CNAME_MAX 8
Adam Williamson e5f70d
 #define QUERIES_MAX 2048
Adam Williamson e5f70d
 #define AUXILIARY_QUERIES_MAX 64
Adam Williamson e5f70d
 
Adam Williamson e5f70d
@@ -977,7 +976,7 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
Adam Williamson e5f70d
         assert(q);
Adam Williamson e5f70d
 
Adam Williamson e5f70d
         q->n_cname_redirects++;
Adam Williamson e5f70d
-        if (q->n_cname_redirects > CNAME_MAX)
Adam Williamson e5f70d
+        if (q->n_cname_redirects > CNAME_REDIRECT_MAX)
Adam Williamson e5f70d
                 return -ELOOP;
Adam Williamson e5f70d
 
Adam Williamson e5f70d
         r = dns_question_cname_redirect(q->question_idna, cname, &nq_idna);
Adam Williamson e5f70d
diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h
Adam Williamson e5f70d
index ea296167b61..5d12171b0a1 100644
Adam Williamson e5f70d
--- a/src/resolve/resolved-dns-query.h
Adam Williamson e5f70d
+++ b/src/resolve/resolved-dns-query.h
Adam Williamson e5f70d
@@ -145,3 +145,5 @@ static inline uint64_t dns_query_reply_flags_make(DnsQuery *q) {
Adam Williamson e5f70d
                                       dns_query_fully_confidential(q)) |
Adam Williamson e5f70d
                 (q->answer_query_flags & (SD_RESOLVED_FROM_MASK|SD_RESOLVED_SYNTHETIC));
Adam Williamson e5f70d
 }
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+#define CNAME_REDIRECT_MAX 16
Adam Williamson e5f70d
Zbigniew Jędrzejewski-Szmek 4b2af1
From d29958261a3df80f5cf0e98b1cd307790a92b13b Mon Sep 17 00:00:00 2001
Adam Williamson e5f70d
From: Lennart Poettering <lennart@poettering.net>
Adam Williamson e5f70d
Date: Fri, 5 Mar 2021 17:48:43 +0100
Adam Williamson e5f70d
Subject: [PATCH 2/6] resolved: tighten checks in
Adam Williamson e5f70d
 dns_resource_record_get_cname_target()
Adam Williamson e5f70d
Adam Williamson e5f70d
Let's refuse to consider CNAME/DNAME replies matching for RR types where
Adam Williamson e5f70d
that is not really conceptually allow (i.e. on CNAME/DNAME lookups
Adam Williamson e5f70d
themselves).
Adam Williamson e5f70d
Adam Williamson e5f70d
(And add a similar check to dns_resource_key_match_cname_or_dname() too,
Adam Williamson e5f70d
which implements a smilar match)
Adam Williamson e5f70d
---
Adam Williamson e5f70d
 src/resolve/resolved-dns-rr.c | 10 ++++++++++
Adam Williamson e5f70d
 1 file changed, 10 insertions(+)
Adam Williamson e5f70d
Adam Williamson e5f70d
diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c
Adam Williamson e5f70d
index 823117e5c92..7e76e0c6cc0 100644
Adam Williamson e5f70d
--- a/src/resolve/resolved-dns-rr.c
Adam Williamson e5f70d
+++ b/src/resolve/resolved-dns-rr.c
Adam Williamson e5f70d
@@ -244,6 +244,9 @@ int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsRe
Adam Williamson e5f70d
         if (cname->class != key->class && key->class != DNS_CLASS_ANY)
Adam Williamson e5f70d
                 return 0;
Adam Williamson e5f70d
 
Adam Williamson e5f70d
+        if (!dns_type_may_redirect(key->type))
Adam Williamson e5f70d
+                return 0;
Adam Williamson e5f70d
+
Adam Williamson e5f70d
         if (cname->type == DNS_TYPE_CNAME)
Adam Williamson e5f70d
                 r = dns_name_equal(dns_resource_key_name(key), dns_resource_key_name(cname));
Adam Williamson e5f70d
         else if (cname->type == DNS_TYPE_DNAME)
Adam Williamson e5f70d
@@ -1743,9 +1746,16 @@ int dns_resource_record_get_cname_target(DnsResourceKey *key, DnsResourceRecord
Adam Williamson e5f70d
         assert(key);
Adam Williamson e5f70d
         assert(cname);
Adam Williamson e5f70d
 
Adam Williamson e5f70d
+        /* Checks if the RR `cname` is a CNAME/DNAME RR that matches the specified `key`. If so, returns the
Adam Williamson e5f70d
+         * target domain. If not, returns -EUNATCH */
Adam Williamson e5f70d
+
Adam Williamson e5f70d
         if (key->class != cname->key->class && key->class != DNS_CLASS_ANY)
Adam Williamson e5f70d
                 return -EUNATCH;
Adam Williamson e5f70d
 
Adam Williamson e5f70d
+        if (!dns_type_may_redirect(key->type)) /* This key type is not subject to CNAME/DNAME redirection?
Adam Williamson e5f70d
+                                                * Then let's refuse right-away */
Adam Williamson e5f70d
+                return -EUNATCH;
Adam Williamson e5f70d
+
Adam Williamson e5f70d
         if (cname->key->type == DNS_TYPE_CNAME) {
Adam Williamson e5f70d
                 r = dns_name_equal(dns_resource_key_name(key),
Adam Williamson e5f70d
                                    dns_resource_key_name(cname->key));
Adam Williamson e5f70d
Zbigniew Jędrzejewski-Szmek 4b2af1
From 4838dc4f2be1d29da9ce9a930c48717a4491d70e Mon Sep 17 00:00:00 2001
Adam Williamson e5f70d
From: Lennart Poettering <lennart@poettering.net>
Adam Williamson e5f70d
Date: Fri, 5 Mar 2021 17:53:31 +0100
Adam Williamson e5f70d
Subject: [PATCH 3/6] resolved: handle multiple CNAME redirects in a single
Adam Williamson e5f70d
 reply from upstream
Adam Williamson e5f70d
Adam Williamson e5f70d
www.netflix.com responds with a chain of CNAMEs in the same packet.
Zbigniew Jędrzejewski-Szmek 4b2af1
Let's handle that properly (so far we only followed CNAMEs a single step
Zbigniew Jędrzejewski-Szmek 4b2af1
when in the same packet)
Adam Williamson e5f70d
Adam Williamson e5f70d
Fixes: #18819
Adam Williamson e5f70d
---
Adam Williamson e5f70d
 src/resolve/resolved-dns-stub.c | 105 +++++++++++++++++---------------
Adam Williamson e5f70d
 1 file changed, 57 insertions(+), 48 deletions(-)
Adam Williamson e5f70d
Adam Williamson e5f70d
diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c
Zbigniew Jędrzejewski-Szmek 4b2af1
index c2734e57b9b..c3a28d390a4 100644
Adam Williamson e5f70d
--- a/src/resolve/resolved-dns-stub.c
Adam Williamson e5f70d
+++ b/src/resolve/resolved-dns-stub.c
Adam Williamson e5f70d
@@ -162,79 +162,88 @@ static int dns_stub_collect_answer_by_question(
Adam Williamson e5f70d
                 bool with_rrsig) { /* Add RRSIG RR matching each RR */
Adam Williamson e5f70d
 
Adam Williamson e5f70d
         _cleanup_(dns_resource_key_unrefp) DnsResourceKey *redirected_key = NULL;
Adam Williamson e5f70d
+        unsigned n_cname_redirects = 0;
Adam Williamson e5f70d
         DnsAnswerItem *item;
Adam Williamson e5f70d
         int r;
Adam Williamson e5f70d
 
Adam Williamson e5f70d
         assert(reply);
Adam Williamson e5f70d
 
Adam Williamson e5f70d
-        /* Copies all RRs from 'answer' into 'reply', if they match 'question'. */
Adam Williamson e5f70d
+        /* Copies all RRs from 'answer' into 'reply', if they match 'question'. There might be direct and
Zbigniew Jędrzejewski-Szmek 4b2af1
+         * indirect matches (i.e. via CNAME/DNAME). If they have an indirect one, remember where we need to
Adam Williamson e5f70d
+         * go, and restart the loop */
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+        for (;;) {
Adam Williamson e5f70d
+                _cleanup_(dns_resource_key_unrefp) DnsResourceKey *next_redirected_key = NULL;
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+                DNS_ANSWER_FOREACH_ITEM(item, answer) {
Adam Williamson e5f70d
+                        DnsResourceKey *k = NULL;
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+                        if (redirected_key) {
Adam Williamson e5f70d
+                                /* There was a redirect in this packet, let's collect all matching RRs for the redirect */
Adam Williamson e5f70d
+                                r = dns_resource_key_match_rr(redirected_key, item->rr, NULL);
Adam Williamson e5f70d
+                                if (r < 0)
Adam Williamson e5f70d
+                                        return r;
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+                                k = redirected_key;
Adam Williamson e5f70d
+                        } else if (question) {
Adam Williamson e5f70d
+                                /* We have a question, let's see if this RR matches it */
Adam Williamson e5f70d
+                                r = dns_question_matches_rr(question, item->rr, NULL);
Adam Williamson e5f70d
+                                if (r < 0)
Adam Williamson e5f70d
+                                        return r;
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+                                k = question->keys[0];
Adam Williamson e5f70d
+                        } else
Adam Williamson e5f70d
+                                r = 1; /* No question, everything matches */
Adam Williamson e5f70d
 
Adam Williamson e5f70d
-        DNS_ANSWER_FOREACH_ITEM(item, answer) {
Adam Williamson e5f70d
-                if (question) {
Adam Williamson e5f70d
-                        r = dns_question_matches_rr(question, item->rr, NULL);
Adam Williamson e5f70d
-                        if (r < 0)
Adam Williamson e5f70d
-                                return r;
Adam Williamson e5f70d
                         if (r == 0) {
Adam Williamson e5f70d
                                 _cleanup_free_ char *target = NULL;
Adam Williamson e5f70d
 
Adam Williamson e5f70d
                                 /* OK, so the RR doesn't directly match. Let's see if the RR is a matching
Adam Williamson e5f70d
                                  * CNAME or DNAME */
Adam Williamson e5f70d
 
Adam Williamson e5f70d
-                                r = dns_resource_record_get_cname_target(
Adam Williamson e5f70d
-                                                question->keys[0],
Adam Williamson e5f70d
-                                                item->rr,
Adam Williamson e5f70d
-                                                &target);
Adam Williamson e5f70d
+                                assert(k);
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+                                r = dns_resource_record_get_cname_target(k, item->rr, &target);
Adam Williamson e5f70d
                                 if (r == -EUNATCH)
Adam Williamson e5f70d
                                         continue; /* Not a CNAME/DNAME or doesn't match */
Adam Williamson e5f70d
                                 if (r < 0)
Adam Williamson e5f70d
                                         return r;
Adam Williamson e5f70d
 
Adam Williamson e5f70d
-                                dns_resource_key_unref(redirected_key);
Adam Williamson e5f70d
+                                /* Oh, wow, this is a redirect. Let's remember where this points, and store
Adam Williamson e5f70d
+                                 * it in 'next_redirected_key'. Once we finished iterating through the rest
Zbigniew Jędrzejewski-Szmek 4b2af1
+                                 * of the RR's we'll start again, with the redirected RR key. */
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+                                n_cname_redirects++;
Adam Williamson e5f70d
+                                if (n_cname_redirects > CNAME_REDIRECT_MAX) /* don't loop forever */
Adam Williamson e5f70d
+                                        return -ELOOP;
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+                                dns_resource_key_unref(next_redirected_key);
Adam Williamson e5f70d
 
Adam Williamson e5f70d
                                 /* There can only be one CNAME per name, hence no point in storing more than one here */
Adam Williamson e5f70d
-                                redirected_key = dns_resource_key_new(question->keys[0]->class, question->keys[0]->type, target);
Adam Williamson e5f70d
-                                if (!redirected_key)
Adam Williamson e5f70d
+                                next_redirected_key = dns_resource_key_new(k->class, k->type, target);
Adam Williamson e5f70d
+                                if (!next_redirected_key)
Adam Williamson e5f70d
                                         return -ENOMEM;
Adam Williamson e5f70d
                         }
Adam Williamson e5f70d
-                }
Adam Williamson e5f70d
 
Adam Williamson e5f70d
-                /* Mask the section info, we want the primary answers to always go without section info, so
Adam Williamson e5f70d
-                 * that it is added to the answer section when we synthesize a reply. */
Adam Williamson e5f70d
+                        /* Mask the section info, we want the primary answers to always go without section info, so
Adam Williamson e5f70d
+                         * that it is added to the answer section when we synthesize a reply. */
Adam Williamson e5f70d
 
Adam Williamson e5f70d
-                r = reply_add_with_rrsig(
Adam Williamson e5f70d
-                                reply,
Adam Williamson e5f70d
-                                item->rr,
Adam Williamson e5f70d
-                                item->ifindex,
Adam Williamson e5f70d
-                                item->flags & ~DNS_ANSWER_MASK_SECTIONS,
Adam Williamson e5f70d
-                                item->rrsig,
Adam Williamson e5f70d
-                                with_rrsig);
Adam Williamson e5f70d
-                if (r < 0)
Adam Williamson e5f70d
-                        return r;
Adam Williamson e5f70d
-        }
Adam Williamson e5f70d
-
Adam Williamson e5f70d
-        if (!redirected_key)
Adam Williamson e5f70d
-                return 0;
Adam Williamson e5f70d
-
Adam Williamson e5f70d
-        /* This is a CNAME/DNAME answer. In this case also append where the redirections point to to the main
Adam Williamson e5f70d
-         * answer section */
Adam Williamson e5f70d
-
Adam Williamson e5f70d
-        DNS_ANSWER_FOREACH_ITEM(item, answer) {
Adam Williamson e5f70d
+                        r = reply_add_with_rrsig(
Adam Williamson e5f70d
+                                        reply,
Adam Williamson e5f70d
+                                        item->rr,
Adam Williamson e5f70d
+                                        item->ifindex,
Adam Williamson e5f70d
+                                        item->flags & ~DNS_ANSWER_MASK_SECTIONS,
Adam Williamson e5f70d
+                                        item->rrsig,
Adam Williamson e5f70d
+                                        with_rrsig);
Adam Williamson e5f70d
+                        if (r < 0)
Adam Williamson e5f70d
+                                return r;
Adam Williamson e5f70d
+                }
Adam Williamson e5f70d
 
Adam Williamson e5f70d
-                r = dns_resource_key_match_rr(redirected_key, item->rr, NULL);
Adam Williamson e5f70d
-                if (r < 0)
Adam Williamson e5f70d
-                        return r;
Adam Williamson e5f70d
-                if (r == 0)
Adam Williamson e5f70d
-                        continue;
Adam Williamson e5f70d
+                if (!next_redirected_key)
Adam Williamson e5f70d
+                        break;
Adam Williamson e5f70d
 
Adam Williamson e5f70d
-                r = reply_add_with_rrsig(
Adam Williamson e5f70d
-                                reply,
Adam Williamson e5f70d
-                                item->rr,
Adam Williamson e5f70d
-                                item->ifindex,
Adam Williamson e5f70d
-                                item->flags & ~DNS_ANSWER_MASK_SECTIONS,
Adam Williamson e5f70d
-                                item->rrsig,
Adam Williamson e5f70d
-                                with_rrsig);
Adam Williamson e5f70d
-                if (r < 0)
Adam Williamson e5f70d
-                        return r;
Adam Williamson e5f70d
+                dns_resource_key_unref(redirected_key);
Adam Williamson e5f70d
+                redirected_key = TAKE_PTR(next_redirected_key);
Adam Williamson e5f70d
         }
Adam Williamson e5f70d
 
Adam Williamson e5f70d
         return 0;
Adam Williamson e5f70d
Zbigniew Jędrzejewski-Szmek 4b2af1
From 39005e187095062718621880e5d8ad707ac8fe8f Mon Sep 17 00:00:00 2001
Adam Williamson e5f70d
From: Lennart Poettering <lennart@poettering.net>
Adam Williamson e5f70d
Date: Fri, 5 Mar 2021 18:01:27 +0100
Adam Williamson e5f70d
Subject: [PATCH 4/6] resolved: split out helper that checks whether we shall
Adam Williamson e5f70d
 reply with EDNS0 DO
Adam Williamson e5f70d
Adam Williamson e5f70d
Just some refactoring, no actual code changes.
Adam Williamson e5f70d
---
Adam Williamson e5f70d
 src/resolve/resolved-dns-stub.c | 22 ++++++++++++++--------
Adam Williamson e5f70d
 1 file changed, 14 insertions(+), 8 deletions(-)
Adam Williamson e5f70d
Adam Williamson e5f70d
diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c
Zbigniew Jędrzejewski-Szmek 4b2af1
index c3a28d390a4..b4df5837aad 100644
Adam Williamson e5f70d
--- a/src/resolve/resolved-dns-stub.c
Adam Williamson e5f70d
+++ b/src/resolve/resolved-dns-stub.c
Adam Williamson e5f70d
@@ -561,6 +561,19 @@ static int dns_stub_send(
Adam Williamson e5f70d
         return 0;
Adam Williamson e5f70d
 }
Adam Williamson e5f70d
 
Adam Williamson e5f70d
+static int dns_stub_reply_with_edns0_do(DnsQuery *q) {
Adam Williamson e5f70d
+         assert(q);
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+        /* Reply with DNSSEC DO set? Only if client supports it; and we did any DNSSEC verification
Adam Williamson e5f70d
+         * ourselves, or consider the data fully authenticated because we generated it locally, or the client
Adam Williamson e5f70d
+         * set cd */
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+         return DNS_PACKET_DO(q->request_packet) &&
Adam Williamson e5f70d
+                 (q->answer_dnssec_result >= 0 ||        /* we did proper DNSSEC validation … */
Adam Williamson e5f70d
+                  dns_query_fully_authenticated(q) ||    /* … or we considered it authentic otherwise … */
Adam Williamson e5f70d
+                  DNS_PACKET_CD(q->request_packet));     /* … or client set CD */
Adam Williamson e5f70d
+}
Adam Williamson e5f70d
+
Adam Williamson e5f70d
 static int dns_stub_send_reply(
Adam Williamson e5f70d
                 DnsQuery *q,
Adam Williamson e5f70d
                 int rcode) {
Adam Williamson e5f70d
@@ -571,14 +584,7 @@ static int dns_stub_send_reply(
Adam Williamson e5f70d
 
Adam Williamson e5f70d
         assert(q);
Adam Williamson e5f70d
 
Adam Williamson e5f70d
-        /* Reply with DNSSEC DO set? Only if client supports it; and we did any DNSSEC verification
Adam Williamson e5f70d
-         * ourselves, or consider the data fully authenticated because we generated it locally, or
Adam Williamson e5f70d
-         * the client set cd */
Adam Williamson e5f70d
-        edns0_do =
Adam Williamson e5f70d
-                DNS_PACKET_DO(q->request_packet) &&
Adam Williamson e5f70d
-                (q->answer_dnssec_result >= 0 ||        /* we did proper DNSSEC validation … */
Adam Williamson e5f70d
-                 dns_query_fully_authenticated(q) ||    /* … or we considered it authentic otherwise … */
Adam Williamson e5f70d
-                 DNS_PACKET_CD(q->request_packet));     /* … or client set CD */
Zbigniew Jędrzejewski-Szmek 4b2af1
+        edns0_do = dns_stub_reply_with_edns0_do(q); /* let's check if we shall reply with EDNS0 DO? */
Adam Williamson e5f70d
 
Adam Williamson e5f70d
         r = dns_stub_assign_sections(
Adam Williamson e5f70d
                         q,
Adam Williamson e5f70d
Zbigniew Jędrzejewski-Szmek 4b2af1
From b97fc57178932689bdcb9030e1e2bf299d49ce0b Mon Sep 17 00:00:00 2001
Adam Williamson e5f70d
From: Lennart Poettering <lennart@poettering.net>
Adam Williamson e5f70d
Date: Fri, 5 Mar 2021 16:50:04 +0100
Adam Williamson e5f70d
Subject: [PATCH 5/6] resolved: fully follow CNAMEs in the DNS stub after all
Adam Williamson e5f70d
Adam Williamson e5f70d
In 2f4d8e577ca7bc51fb054b8c2c8dd57c2e188a41 I argued that following
Adam Williamson e5f70d
CNAMEs in the stub is not necessary anymore. However, I think it' better
Adam Williamson e5f70d
to revert to the status quo ante and follow it after all, given it is
Adam Williamson e5f70d
easy for us and makes sure our D-Bus/varlink replies are more similar to
Adam Williamson e5f70d
our DNS stub replies that way, and we save clients potential roundtrips.
Adam Williamson e5f70d
Adam Williamson e5f70d
Hence, whenever we hit a CNAME/DNAME redirect, let's restart the query
Adam Williamson e5f70d
like we do for the D-Bus/Varlink case, and collect replies as we go.
Adam Williamson e5f70d
---
Adam Williamson e5f70d
 src/resolve/resolved-dns-stub.c | 38 +++++++++++++++++++++++----------
Adam Williamson e5f70d
 1 file changed, 27 insertions(+), 11 deletions(-)
Adam Williamson e5f70d
Adam Williamson e5f70d
diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c
Zbigniew Jędrzejewski-Szmek 4b2af1
index b4df5837aad..85c4eda469c 100644
Adam Williamson e5f70d
--- a/src/resolve/resolved-dns-stub.c
Adam Williamson e5f70d
+++ b/src/resolve/resolved-dns-stub.c
Adam Williamson e5f70d
@@ -586,13 +586,6 @@ static int dns_stub_send_reply(
Adam Williamson e5f70d
 
Zbigniew Jędrzejewski-Szmek 4b2af1
         edns0_do = dns_stub_reply_with_edns0_do(q); /* let's check if we shall reply with EDNS0 DO? */
Adam Williamson e5f70d
 
Adam Williamson e5f70d
-        r = dns_stub_assign_sections(
Adam Williamson e5f70d
-                        q,
Adam Williamson e5f70d
-                        q->request_packet->question,
Adam Williamson e5f70d
-                        edns0_do);
Adam Williamson e5f70d
-        if (r < 0)
Adam Williamson e5f70d
-                return log_debug_errno(r, "Failed to assign sections: %m");
Adam Williamson e5f70d
-
Adam Williamson e5f70d
         r = dns_stub_make_reply_packet(
Adam Williamson e5f70d
                         &reply,
Adam Williamson e5f70d
                         DNS_PACKET_PAYLOAD_SIZE_MAX(q->request_packet),
Adam Williamson e5f70d
@@ -743,13 +736,37 @@ static void dns_stub_query_complete(DnsQuery *q) {
Adam Williamson e5f70d
                 }
Adam Williamson e5f70d
         }
Adam Williamson e5f70d
 
Adam Williamson e5f70d
-        /* Note that we don't bother with following CNAMEs here. We propagate the authoritative/additional
Adam Williamson e5f70d
-         * sections from the upstream answer however, hence if the upstream server collected that information
Adam Williamson e5f70d
-         * already we don't have to collect it ourselves anymore. */
Adam Williamson e5f70d
+        /* Take all data from the current reply, and merge it into the three reply sections we are building
Adam Williamson e5f70d
+         * up. We do this before processing CNAME redirects, so that we gradually build up our sections, and
Adam Williamson e5f70d
+         * and keep adding all RRs in the CNAME chain. */
Adam Williamson e5f70d
+        r = dns_stub_assign_sections(
Adam Williamson e5f70d
+                        q,
Adam Williamson e5f70d
+                        q->request_packet->question,
Adam Williamson e5f70d
+                        dns_stub_reply_with_edns0_do(q));
Adam Williamson e5f70d
+        if (r < 0) {
Adam Williamson e5f70d
+                log_debug_errno(r, "Failed to assign sections: %m");
Adam Williamson e5f70d
+                dns_query_free(q);
Adam Williamson e5f70d
+                return;
Adam Williamson e5f70d
+        }
Adam Williamson e5f70d
 
Adam Williamson e5f70d
         switch (q->state) {
Adam Williamson e5f70d
 
Adam Williamson e5f70d
         case DNS_TRANSACTION_SUCCESS:
Adam Williamson e5f70d
+                r = dns_query_process_cname(q);
Adam Williamson e5f70d
+                if (r == -ELOOP) { /* CNAME loop, let's send what we already have */
Adam Williamson e5f70d
+                        log_debug_errno(r, "Detected CNAME loop, returning what we already have.");
Adam Williamson e5f70d
+                        (void) dns_stub_send_reply(q, q->answer_rcode);
Adam Williamson e5f70d
+                        break;
Adam Williamson e5f70d
+                }
Adam Williamson e5f70d
+                if (r < 0) {
Adam Williamson e5f70d
+                        log_debug_errno(r, "Failed to process CNAME: %m");
Adam Williamson e5f70d
+                        break;
Adam Williamson e5f70d
+                }
Adam Williamson e5f70d
+                if (r == DNS_QUERY_RESTARTED)
Adam Williamson e5f70d
+                        return;
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+                _fallthrough_;
Adam Williamson e5f70d
+
Adam Williamson e5f70d
         case DNS_TRANSACTION_RCODE_FAILURE:
Adam Williamson e5f70d
                 (void) dns_stub_send_reply(q, q->answer_rcode);
Adam Williamson e5f70d
                 break;
Adam Williamson e5f70d
@@ -888,7 +905,6 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea
Adam Williamson e5f70d
                 r = dns_query_new(m, &q, p->question, p->question, NULL, 0,
Adam Williamson e5f70d
                                   SD_RESOLVED_PROTOCOLS_ALL|
Adam Williamson e5f70d
                                   SD_RESOLVED_NO_SEARCH|
Adam Williamson e5f70d
-                                  SD_RESOLVED_NO_CNAME|
Adam Williamson e5f70d
                                   (DNS_PACKET_DO(p) ? SD_RESOLVED_REQUIRE_PRIMARY : 0)|
Adam Williamson e5f70d
                                   SD_RESOLVED_CLAMP_TTL);
Adam Williamson e5f70d
         if (r < 0) {
Adam Williamson e5f70d
Zbigniew Jędrzejewski-Szmek 4b2af1
From 5d7da51ee1d27e86a0487a4b2abc3cfb0ed44c23 Mon Sep 17 00:00:00 2001
Adam Williamson e5f70d
From: Lennart Poettering <lennart@poettering.net>
Adam Williamson e5f70d
Date: Fri, 5 Mar 2021 18:20:59 +0100
Adam Williamson e5f70d
Subject: [PATCH 6/6] resolved: when synthesizing stub replies from multiple
Adam Williamson e5f70d
 upstream packet, let's avoid RR duplicates
Adam Williamson e5f70d
Adam Williamson e5f70d
If we synthesize a stub reply from multiple upstream packet (i.e. a
Adam Williamson e5f70d
series of CNAME/DNAME redirects), it might happen that we add the same
Adam Williamson e5f70d
RR to a different reply section at a different CNAME/DNAME redirect
Adam Williamson e5f70d
chain element. Let's clean this up once we are about to send the reply
Adam Williamson e5f70d
message to the client: let's remove sections from "lower-priority"
Adam Williamson e5f70d
sections when they are already listed in a "higher-priority" section.
Adam Williamson e5f70d
---
Adam Williamson e5f70d
 src/resolve/resolved-dns-answer.c | 25 +++++++++++++++++++++++++
Adam Williamson e5f70d
 src/resolve/resolved-dns-answer.h |  1 +
Zbigniew Jędrzejewski-Szmek 4b2af1
 src/resolve/resolved-dns-stub.c   | 20 ++++++++++++++++++++
Zbigniew Jędrzejewski-Szmek 4b2af1
 3 files changed, 46 insertions(+)
Adam Williamson e5f70d
Adam Williamson e5f70d
diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c
Zbigniew Jędrzejewski-Szmek 4b2af1
index ce3cbce308d..a667ab5ede4 100644
Adam Williamson e5f70d
--- a/src/resolve/resolved-dns-answer.c
Adam Williamson e5f70d
+++ b/src/resolve/resolved-dns-answer.c
Adam Williamson e5f70d
@@ -640,6 +640,31 @@ int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) {
Adam Williamson e5f70d
         return 1;
Adam Williamson e5f70d
 }
Adam Williamson e5f70d
 
Adam Williamson e5f70d
+int dns_answer_remove_by_answer_keys(DnsAnswer **a, DnsAnswer *b) {
Adam Williamson e5f70d
+        _cleanup_(dns_resource_key_unrefp) DnsResourceKey *prev = NULL;
Adam Williamson e5f70d
+        DnsAnswerItem *item;
Adam Williamson e5f70d
+        int r;
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+        /* Removes all items from '*a' that have a matching key in 'b' */
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+        DNS_ANSWER_FOREACH_ITEM(item, b) {
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+                if (prev && dns_resource_key_equal(item->rr->key, prev)) /* Skip this one, we already looked at it */
Adam Williamson e5f70d
+                        continue;
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+                r = dns_answer_remove_by_key(a, item->rr->key);
Adam Williamson e5f70d
+                if (r < 0)
Adam Williamson e5f70d
+                        return r;
Adam Williamson e5f70d
+
Zbigniew Jędrzejewski-Szmek 4b2af1
+                /* Let's remember this entry's RR key, to optimize the loop a bit: if we have an RRset with
Zbigniew Jędrzejewski-Szmek 4b2af1
+                 * more than one item then we don't need to remove the key multiple times */
Adam Williamson e5f70d
+                dns_resource_key_unref(prev);
Adam Williamson e5f70d
+                prev = dns_resource_key_ref(item->rr->key);
Adam Williamson e5f70d
+        }
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+        return 0;
Adam Williamson e5f70d
+}
Adam Williamson e5f70d
+
Adam Williamson e5f70d
 int dns_answer_copy_by_key(
Adam Williamson e5f70d
                 DnsAnswer **a,
Adam Williamson e5f70d
                 DnsAnswer *source,
Adam Williamson e5f70d
diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h
Adam Williamson e5f70d
index c2fd0c078f4..7d19eee4e2b 100644
Adam Williamson e5f70d
--- a/src/resolve/resolved-dns-answer.h
Adam Williamson e5f70d
+++ b/src/resolve/resolved-dns-answer.h
Adam Williamson e5f70d
@@ -68,6 +68,7 @@ int dns_answer_reserve_or_clone(DnsAnswer **a, size_t n_free);
Adam Williamson e5f70d
 
Adam Williamson e5f70d
 int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key);
Adam Williamson e5f70d
 int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr);
Adam Williamson e5f70d
+int dns_answer_remove_by_answer_keys(DnsAnswer **a, DnsAnswer *b);
Adam Williamson e5f70d
 
Adam Williamson e5f70d
 int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags, DnsResourceRecord *rrsig);
Adam Williamson e5f70d
 int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags, DnsResourceRecord *rrsig);
Adam Williamson e5f70d
diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c
Zbigniew Jędrzejewski-Szmek 4b2af1
index 85c4eda469c..8e781dd7389 100644
Adam Williamson e5f70d
--- a/src/resolve/resolved-dns-stub.c
Adam Williamson e5f70d
+++ b/src/resolve/resolved-dns-stub.c
Zbigniew Jędrzejewski-Szmek 4b2af1
@@ -574,6 +574,24 @@ static int dns_stub_reply_with_edns0_do(DnsQuery *q) {
Adam Williamson e5f70d
                   DNS_PACKET_CD(q->request_packet));     /* … or client set CD */
Adam Williamson e5f70d
 }
Adam Williamson e5f70d
 
Adam Williamson e5f70d
+static void dns_stub_suppress_duplicate_section_rrs(DnsQuery *q) {
Adam Williamson e5f70d
+        /* If we follow a CNAME/DNAME chain we might end up populating our sections with redundant RRs
Adam Williamson e5f70d
+         * because we built up the sections from multiple reply packets (one from each CNAME/DNAME chain
Zbigniew Jędrzejewski-Szmek 4b2af1
+         * element). E.g. it could be that an RR that was included in the first reply's additional section
Adam Williamson e5f70d
+         * ends up being relevant as main answer in a subsequent reply in the chain. Let's clean this up, and
Zbigniew Jędrzejewski-Szmek 4b2af1
+         * remove everything in the "higher priority" sections from the "lower priority" sections.
Adam Williamson e5f70d
+         *
Adam Williamson e5f70d
+         * Note that this removal matches by RR keys instead of the full RRs. This is because RRsets should
Zbigniew Jędrzejewski-Szmek 4b2af1
+         * always end up in one section fully or not at all, but never be split among sections.
Zbigniew Jędrzejewski-Szmek 4b2af1
+         *
Zbigniew Jędrzejewski-Szmek 4b2af1
+         * Specifically: we remove ANSWER section RRs from the AUTHORITATIVE and ADDITIONAL sections, as well
Zbigniew Jędrzejewski-Szmek 4b2af1
+         * as AUTHORITATIVE section RRs from the ADDITIONAL section. */
Adam Williamson e5f70d
+
Adam Williamson e5f70d
+        dns_answer_remove_by_answer_keys(&q->reply_authoritative, q->reply_answer);
Adam Williamson e5f70d
+        dns_answer_remove_by_answer_keys(&q->reply_additional, q->reply_answer);
Adam Williamson e5f70d
+        dns_answer_remove_by_answer_keys(&q->reply_additional, q->reply_authoritative);
Adam Williamson e5f70d
+}
Adam Williamson e5f70d
+
Adam Williamson e5f70d
 static int dns_stub_send_reply(
Adam Williamson e5f70d
                 DnsQuery *q,
Adam Williamson e5f70d
                 int rcode) {
Zbigniew Jędrzejewski-Szmek 4b2af1
@@ -594,6 +612,8 @@ static int dns_stub_send_reply(
Adam Williamson e5f70d
         if (r < 0)
Adam Williamson e5f70d
                 return log_debug_errno(r, "Failed to build reply packet: %m");
Adam Williamson e5f70d
 
Adam Williamson e5f70d
+        dns_stub_suppress_duplicate_section_rrs(q);
Adam Williamson e5f70d
+
Adam Williamson e5f70d
         r = dns_stub_add_reply_packet_body(
Adam Williamson e5f70d
                         reply,
Adam Williamson e5f70d
                         q->reply_answer,