From 230d79c1911c2520be24e08eb1b40366a2f6e05a Mon Sep 17 00:00:00 2001 From: Diego Fronza Date: Wed, 26 Aug 2020 14:36:14 -0300 Subject: [PATCH 1/3] Fix resolution of unusual ip6.arpa names Before this commit, BIND was unable to resolve ip6.arpa names like the one reported in issue #1847 when using query minimization. As reported in the issue, an attempt to resolve a name like 'rec-test-dom-158937817846788.test123.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.3.4.3.5.4.0.8.2.6.0.1.0.0.2.ip6.arpa' using default settings would fail. The reason was that query minimization algorithm in 'fctx_minimize_qname' would divide any ip6.arpa names in increasing number of labels, 7,11, ... up to 35, thus limiting the destination name (minimized) to a number of 35 labels. In case the last query minimization attempt (with 35 labels) would fail with NXDOMAIN, BIND would attempt the query mininimization again with the exact same QNAME, limited on the 35 labels, and that in turn would fail again. This fix avoids this fail loop by considering the extra labels that may appear in the leftmost part of an ip6.arpa name, those after the IPv6 part. --- lib/dns/resolver.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index df43b0a09a..612dab1501 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -10646,8 +10646,10 @@ fctx_minimize_qname(fetchctx_t *fctx) { fctx->qmin_labels = 17; } else if (fctx->qmin_labels < 19) { fctx->qmin_labels = 19; - } else if (fctx->qmin_labels > 19) { + } else if (fctx->qmin_labels < 35) { fctx->qmin_labels = 35; + } else { + fctx->qmin_labels = nlabels; } } else if (fctx->qmin_labels > DNS_QMIN_MAXLABELS) { fctx->qmin_labels = DNS_MAX_LABELS + 1; From 11add6919855012fb8b41bb0ae6c15d6a6855f6d Mon Sep 17 00:00:00 2001 From: Diego Fronza Date: Fri, 28 Aug 2020 18:49:26 -0300 Subject: [PATCH 2/3] Added test for the proposed fix The test works as follows: 1. Client wants to resolve unusual ip6.arpa. name: test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa. IN TXT 2. Query is sent to ns7, a qmin enabled resolver. 3. ns7 do the first stage in query minimization for the name and send a new query to root (ns1): _.1.0.0.2.ip6.arpa. IN A 4. ns1 delegates ip6.arpa. to ns2.good.: ;; AUTHORITY SECTION: ;ip6.arpa. 20 IN NS ns2.good. ;; ADDITIONAL SECTION: ;ns2.good. 20 IN A 10.53.0.2 5. ns7 do a second round in minimizing the name and send a new query to ns2.good. (10.53.0.2): _.8.2.6.0.1.0.0.2.ip6.arpa. IN A 6. ans2 delegates 8.2.6.0.1.0.0.2.ip6.arpa. to ns3.good.: ;; AUTHORITY SECTION: ;8.2.6.0.1.0.0.2.ip6.arpa. 60 IN NS ns3.good. ;; ADDITIONAL SECTION: ;ns3.good. 60 IN A 10.53.0.3 7. ns7 do a third round in minimizing the name and send a new query to ns3.good.: _.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa. IN A 8. ans3 delegates 1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa. to ns4.good.: ;; AUTHORITY SECTION: ;1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa. 60 IN NS ns4.good. ;; ADDITIONAL SECTION: ;ns4.good. 60 IN A 10.53.0.4 9. ns7 do fourth round in minimizing the name and send a new query to ns4.good.: _.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa. IN A 10. ns4.good. doesn't know such name, but answers stating it is authoritative for the domai: ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 53815 ... ;; AUTHORITY SECTION: 1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa. 60 IN SOA ns4.good. ... 11. ns7 do another minimization on name: _.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa sends to ns4.good. and gets the same SOA response stated in item #10 12. ns7 do another minimization on name: _.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa sends to ns4.good. and gets the same SOA response stated in item #10. 13. ns7 do the last query minimization name for the ip6.arpa. QNAME. After all IPv6 labels are exausted the algorithm falls back to the original QNAME: test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa ns7 sends a new query with the original QNAME to ans4. 14. Finally ans4 answers with the expected response: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40969 ;; flags: qr aa; QUESTION: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 8192 ;; QUESTION SECTION: ;test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa. IN TXT ;; ANSWER SECTION: ;test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa. 1 IN TXT "long_ip6_name" --- bin/tests/system/qmin/ans2/ans.py | 35 ++++++++++++++++++++----------- bin/tests/system/qmin/ans3/ans.py | 7 +++++++ bin/tests/system/qmin/ans4/ans.py | 15 +++++++++++++ bin/tests/system/qmin/tests.sh | 11 ++++++++++ 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/bin/tests/system/qmin/ans2/ans.py b/bin/tests/system/qmin/ans2/ans.py index c4e9874537..96e723986a 100755 --- a/bin/tests/system/qmin/ans2/ans.py +++ b/bin/tests/system/qmin/ans2/ans.py @@ -48,6 +48,7 @@ def logquery(type, qname): # # For 1.0.0.2.ip6.arpa it serves # 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa. IN PTR nee.com. +# 8.2.6.0.1.0.0.2.ip6.arpa IN NS ns3.good # 1.0.0.2.ip6.arpa. IN NS ns2.good # ip6.arpa. IN NS ns2.good ############################################################################ @@ -76,33 +77,37 @@ def create_response(msg): if lqname.endswith("1.0.0.2.ip6.arpa."): # Direct query - give direct answer - if lqname == "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa." and rrtype == PTR: + if lqname.endswith("8.2.6.0.1.0.0.2.ip6.arpa."): + # Delegate to ns3 + r.authority.append(dns.rrset.from_text("8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns3.good.")) + r.additional.append(dns.rrset.from_text("ns3.good.", 60, IN, A, "10.53.0.3")) + elif lqname == "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa." and rrtype == PTR: # Direct query - give direct answer r.answer.append(dns.rrset.from_text("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.", 1, IN, PTR, "nee.com.")) r.flags |= dns.flags.AA elif lqname == "1.0.0.2.ip6.arpa." and rrtype == NS: # NS query at the apex - r.answer.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 1, IN, NS, "ns2.good.")) + r.answer.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, NS, "ns2.good.")) r.flags |= dns.flags.AA elif "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.".endswith(lqname): # NODATA answer - r.authority.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 1, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1")) + r.authority.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1")) else: # NXDOMAIN - r.authority.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 1, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1")) + r.authority.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1")) r.set_rcode(NXDOMAIN) return r elif lqname.endswith("ip6.arpa."): if lqname == "ip6.arpa." and rrtype == NS: # NS query at the apex - r.answer.append(dns.rrset.from_text("ip6.arpa.", 1, IN, NS, "ns2.good.")) + r.answer.append(dns.rrset.from_text("ip6.arpa.", 30, IN, NS, "ns2.good.")) r.flags |= dns.flags.AA elif "1.0.0.2.ip6.arpa.".endswith(lqname): # NODATA answer - r.authority.append(dns.rrset.from_text("ip6.arpa.", 1, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1")) + r.authority.append(dns.rrset.from_text("ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1")) else: # NXDOMAIN - r.authority.append(dns.rrset.from_text("ip6.arpa.", 1, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1")) + r.authority.append(dns.rrset.from_text("ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1")) r.set_rcode(NXDOMAIN) return r elif lqname.endswith("bad."): @@ -134,19 +139,25 @@ def create_response(msg): r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2")) r.flags |= dns.flags.AA elif lqname == "" and rrtype == NS: - r.answer.append(dns.rrset.from_text(suffix, 1, IN, NS, "ns2." + suffix)) + r.answer.append(dns.rrset.from_text(suffix, 30, IN, NS, "ns2." + suffix)) r.flags |= dns.flags.AA elif lqname == "ns2." and rrtype == A: - r.answer.append(dns.rrset.from_text("ns2."+suffix, 1, IN, A, "10.53.0.2")) + r.answer.append(dns.rrset.from_text("ns2."+suffix, 30, IN, A, "10.53.0.2")) r.flags |= dns.flags.AA elif lqname == "ns2." and rrtype == AAAA: - r.answer.append(dns.rrset.from_text("ns2."+suffix, 1, IN, AAAA, "fd92:7065:b8e:ffff::2")) + r.answer.append(dns.rrset.from_text("ns2."+suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::2")) r.flags |= dns.flags.AA elif lqname == "ns3." and rrtype == A: - r.answer.append(dns.rrset.from_text("ns3."+suffix, 1, IN, A, "10.53.0.3")) + r.answer.append(dns.rrset.from_text("ns3."+suffix, 30, IN, A, "10.53.0.3")) r.flags |= dns.flags.AA elif lqname == "ns3." and rrtype == AAAA: - r.answer.append(dns.rrset.from_text("ns3."+suffix, 1, IN, AAAA, "fd92:7065:b8e:ffff::3")) + r.answer.append(dns.rrset.from_text("ns3."+suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::3")) + r.flags |= dns.flags.AA + elif lqname == "ns4." and rrtype == A: + r.answer.append(dns.rrset.from_text("ns4."+suffix, 30, IN, A, "10.53.0.4")) + r.flags |= dns.flags.AA + elif lqname == "ns4." and rrtype == AAAA: + r.answer.append(dns.rrset.from_text("ns4."+suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::4")) r.flags |= dns.flags.AA elif lqname == "a.bit.longer.ns.name." and rrtype == A: r.answer.append(dns.rrset.from_text("a.bit.longer.ns.name."+suffix, 1, IN, A, "10.53.0.4")) diff --git a/bin/tests/system/qmin/ans3/ans.py b/bin/tests/system/qmin/ans3/ans.py index e96d3971fa..9a459d7835 100755 --- a/bin/tests/system/qmin/ans3/ans.py +++ b/bin/tests/system/qmin/ans3/ans.py @@ -67,6 +67,8 @@ def create_response(msg): r = dns.message.make_response(m) r.set_rcode(NOERROR) + ip6req = False + if lqname.endswith("bad."): bad = True suffix = "bad." @@ -82,6 +84,8 @@ def create_response(msg): slow = True suffix = "slow." lqname = lqname[:-5] + elif lqname.endswith("8.2.6.0.1.0.0.2.ip6.arpa."): + ip6req = True else: r.set_rcode(REFUSED) return r @@ -101,6 +105,9 @@ def create_response(msg): elif lqname.endswith("zoop.boing."): r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1")) r.set_rcode(NXDOMAIN) + elif ip6req: + r.authority.append(dns.rrset.from_text("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns4.good.")) + r.additional.append(dns.rrset.from_text("ns4.good.", 60, IN, A, "10.53.0.4")) else: r.set_rcode(REFUSED) diff --git a/bin/tests/system/qmin/ans4/ans.py b/bin/tests/system/qmin/ans4/ans.py index 15c256ba32..a836af43ff 100755 --- a/bin/tests/system/qmin/ans4/ans.py +++ b/bin/tests/system/qmin/ans4/ans.py @@ -68,6 +68,8 @@ def create_response(msg): r = dns.message.make_response(m) r.set_rcode(NOERROR) + ip6req = False + if lqname.endswith("bad."): bad = True suffix = "bad." @@ -83,6 +85,8 @@ def create_response(msg): slow = True suffix = "slow." lqname = lqname[:-5] + elif lqname.endswith("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa."): + ip6req = True else: r.set_rcode(REFUSED) return r @@ -103,6 +107,17 @@ def create_response(msg): r.set_rcode(NXDOMAIN) if ugly: r.set_rcode(FORMERR) + elif ip6req: + r.flags |= dns.flags.AA + if lqname == "test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa." and rrtype == TXT: + r.answer.append(dns.rrset.from_text("test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 1, IN, TXT, "long_ip6_name")) + elif "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.".endswith(lqname): + #NODATA answer + r.authority.append(dns.rrset.from_text("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, SOA, "ns4.good. hostmaster.arpa. 2018050100 120 30 320 16")) + else: + # NXDOMAIN + r.authority.append(dns.rrset.from_text("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, SOA, "ns4.good. hostmaster.arpa. 2018050100 120 30 320 16")) + r.set_rcode(NXDOMAIN) else: r.set_rcode(REFUSED) diff --git a/bin/tests/system/qmin/tests.sh b/bin/tests/system/qmin/tests.sh index 59c06e9bbf..ff77d21fd6 100755 --- a/bin/tests/system/qmin/tests.sh +++ b/bin/tests/system/qmin/tests.sh @@ -383,5 +383,16 @@ for ans in ans2; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` +n=`expr $n + 1` +echo_i "qname minimization resolves unusual ip6.arpa. names ($n)" +ret=0 +$CLEANQL +$DIG $DIGOPTS test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa. txt @10.53.0.7 > dig.out.test$n 2>&1 +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +# Expected output in dig.out.test$n: +# ;; ANSWER SECTION: +# test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa. 1 IN TXT "long_ip6_name" +grep 'ip6\.arpa.*TXT.*long_ip6_name' dig.out.test$n > /dev/null || ret=1 + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 From 044a72cca9912f0b0937588b83f2c9d00fe5a355 Mon Sep 17 00:00:00 2001 From: Diego Fronza Date: Mon, 31 Aug 2020 13:26:56 -0300 Subject: [PATCH 3/3] Add CHANGES and release note for GL #1847 --- CHANGES | 4 ++++ doc/notes/notes-current.rst | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGES b/CHANGES index b8cb5de607..6de484a2e3 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +5495. [bug] With query minimization enabled, named failed to + resolve ip6.arpa. names that had more labels after the + IPv6 part. [GL #1847] + 5494. [bug] Silence the EPROTO syslog message on older systems. [GL #1928] diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index c374f0f99c..422a02b3e0 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -45,3 +45,13 @@ Bug Fixes seen on older operating systems where unhandled ICMPv6 errors result in a generic protocol error being returned instead of the more specific error code. [GL #1928] + +- With query minimization enabled, named failed to resolve ip6.arpa. names + that had more labels before the IPv6 part. For example, when named + implemented query minimization on a name like + ``A.B.1.2.3.4.(...).ip6.arpa.``, it stopped at the left-most IPv6 label, i.e. + ``1.2.3.4.(...).ip6.arpa.`` without considering the extra labels ``A.B``. + That caused a query loop when resolving the name: if named received + NXDOMAIN answers, then the same query was repeatedly sent until the number + of queries sent reached the value in the ``max-recursion-queries`` + configuration option. [GL #1847]