]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Retry lookups with unsigned DNAME over TCP
authorEvan Hunt <each@isc.org>
Tue, 30 Sep 2025 05:42:44 +0000 (22:42 -0700)
committerMichał Kępień <michal@isc.org>
Fri, 3 Oct 2025 15:50:07 +0000 (17:50 +0200)
To prevent spoofed unsigned DNAME responses being accepted retry
response with unsigned DNAMEs over TCP if the response is not TSIG
signed or there isn't a good DNS CLIENT COOKIE.

To prevent test failures, this required adding TCP support to the
ans3 and ans4 servers in the chain system test.

(cherry picked from commit 2e40705c06831988106335ed77db3cf924d431f6)

bin/tests/system/chain/ans3/ans.pl [deleted file]
bin/tests/system/chain/ans3/ans.py [new file with mode: 0644]
bin/tests/system/chain/ans4/ans.py
bin/tests/system/cookie/ans9/ans.py
lib/dns/include/dns/message.h
lib/dns/message.c
lib/dns/resolver.c
lib/dns/win32/libdns.def.in

diff --git a/bin/tests/system/chain/ans3/ans.pl b/bin/tests/system/chain/ans3/ans.pl
deleted file mode 100644 (file)
index 8afe710..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-#!/usr/bin/env perl
-#
-# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, you can obtain one at https://mozilla.org/MPL/2.0/.
-#
-# See the COPYRIGHT file distributed with this work for additional
-# information regarding copyright ownership.
-
-use strict;
-use warnings;
-
-use IO::File;
-use Getopt::Long;
-use Net::DNS::Nameserver;
-
-my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!";
-print $pidf "$$\n" or die "cannot write pid file: $!";
-$pidf->close or die "cannot close pid file: $!";
-sub rmpid { unlink "ans.pid"; exit 1; };
-sub term { };
-
-$SIG{INT} = \&rmpid;
-if ($Net::DNS::VERSION > 1.41) {
-    $SIG{TERM} = \&term;
-} else {
-    $SIG{TERM} = \&rmpid;
-}
-
-my $localaddr = "10.53.0.3";
-
-my $localport = int($ENV{'PORT'});
-if (!$localport) { $localport = 5300; }
-
-my $verbose = 0;
-my $ttl = 60;
-my $zone = "example.broken";
-my $nsname = "ns3.$zone";
-my $synth = "synth-then-dname.$zone";
-my $synth2 = "synth2-then-dname.$zone";
-
-sub reply_handler {
-    my ($qname, $qclass, $qtype, $peerhost, $query, $conn) = @_;
-    my ($rcode, @ans, @auth, @add);
-
-    print ("request: $qname/$qtype\n");
-    STDOUT->flush();
-
-    if ($qname eq "example.broken") {
-        if ($qtype eq "SOA") {
-           my $rr = new Net::DNS::RR("$qname $ttl $qclass SOA . . 0 0 0 0 0");
-           push @ans, $rr;
-        } elsif ($qtype eq "NS") {
-           my $rr = new Net::DNS::RR("$qname $ttl $qclass NS $nsname");
-           push @ans, $rr;
-           $rr = new Net::DNS::RR("$nsname $ttl $qclass A $localaddr");
-           push @add, $rr;
-        }
-        $rcode = "NOERROR";
-    } elsif ($qname eq "cname-to-$synth2") {
-        my $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME name.$synth2");
-       push @ans, $rr;
-        $rr = new Net::DNS::RR("name.$synth2 $ttl $qclass CNAME name");
-       push @ans, $rr;
-        $rr = new Net::DNS::RR("$synth2 $ttl $qclass DNAME .");
-       push @ans, $rr;
-       $rcode = "NOERROR";
-    } elsif ($qname eq "$synth" || $qname eq "$synth2") {
-       if ($qtype eq "DNAME") {
-           my $rr = new Net::DNS::RR("$qname $ttl $qclass DNAME .");
-           push @ans, $rr;
-       }
-       $rcode = "NOERROR";
-    } elsif ($qname eq "name.$synth") {
-       my $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME name.");
-       push @ans, $rr;
-       $rr = new Net::DNS::RR("$synth $ttl $qclass DNAME .");
-       push @ans, $rr;
-       $rcode = "NOERROR";
-    } elsif ($qname eq "name.$synth2") {
-       my $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME name.");
-       push @ans, $rr;
-       $rr = new Net::DNS::RR("$synth2 $ttl $qclass DNAME .");
-       push @ans, $rr;
-       $rcode = "NOERROR";
-    # The following three code branches referring to the "example.dname"
-    # zone are necessary for the resolver variant of the CVE-2021-25215
-    # regression test to work.  A named instance cannot be used for
-    # serving the DNAME records below as a version of BIND vulnerable to
-    # CVE-2021-25215 would crash while answering the queries asked by
-    # the tested resolver.
-    } elsif ($qname eq "ns3.example.dname") {
-       if ($qtype eq "A") {
-               my $rr = new Net::DNS::RR("$qname $ttl $qclass A 10.53.0.3");
-               push @ans, $rr;
-       }
-       if ($qtype eq "AAAA") {
-               my $rr = new Net::DNS::RR("example.dname. $ttl $qclass SOA . . 0 0 0 0 $ttl");
-               push @auth, $rr;
-       }
-       $rcode = "NOERROR";
-    } elsif ($qname eq "self.example.self.example.dname") {
-       my $rr = new Net::DNS::RR("self.example.dname. $ttl $qclass DNAME dname.");
-       push @ans, $rr;
-       $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME self.example.dname.");
-       push @ans, $rr;
-       $rcode = "NOERROR";
-    } elsif ($qname eq "self.example.dname") {
-       if ($qtype eq "DNAME") {
-               my $rr = new Net::DNS::RR("$qname $ttl $qclass DNAME dname.");
-               push @ans, $rr;
-       }
-       $rcode = "NOERROR";
-    } else {
-       $rcode = "REFUSED";
-    }
-    return ($rcode, \@ans, \@auth, \@add, { aa => 1 });
-}
-
-GetOptions(
-    'port=i' => \$localport,
-    'verbose!' => \$verbose,
-);
-
-my $ns = Net::DNS::Nameserver->new(
-    LocalAddr => $localaddr,
-    LocalPort => $localport,
-    ReplyHandler => \&reply_handler,
-    Verbose => $verbose,
-);
-
-if ($Net::DNS::VERSION >= 1.42) {
-    $ns->start_server();
-    select(undef, undef, undef, undef);
-    $ns->stop_server();
-    unlink "ans.pid";
-} else {
-    $ns->main_loop;
-}
diff --git a/bin/tests/system/chain/ans3/ans.py b/bin/tests/system/chain/ans3/ans.py
new file mode 100644 (file)
index 0000000..d6d4eea
--- /dev/null
@@ -0,0 +1,216 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+############################################################################
+# ans.py: See README.anspy for details.
+############################################################################
+
+from __future__ import print_function
+import os
+import sys
+import signal
+import socket
+import select
+from datetime import datetime, timedelta
+import functools
+
+import dns, dns.message, dns.query
+from dns.rdatatype import *
+from dns.rdataclass import *
+from dns.rcode import *
+from dns.name import *
+
+############################################################################
+# Respond to a DNS query.
+############################################################################
+def create_response(msg):
+    ttl = 60
+    zone = "example.broken."
+    nsname = "ns3." + zone
+    synth = "synth-then-dname." + zone
+    synth2 = "synth2-then-dname." + zone
+
+    m = dns.message.from_wire(msg)
+    qname = m.question[0].name.to_text()
+
+    # prepare the response and convert to wire format
+    r = dns.message.make_response(m)
+
+    # get qtype
+    rrtype = m.question[0].rdtype
+    qtype = dns.rdatatype.to_text(rrtype)
+    print("request: " + qname + "/" + qtype)
+
+    rcode = "NOERROR"
+    if qname == zone:
+        if qtype == "SOA":
+            r.answer.append(dns.rrset.from_text(qname, ttl, IN, SOA, ". . 0 0 0 0 0"))
+        elif qtype == "NS":
+            r.answer.append(dns.rrset.from_text(qname, ttl, IN, NS, nsname))
+            r.additional.append(dns.rrset.from_text(nsname, ttl, IN, A, ip4))
+    elif qname == "cname-to-" + synth2:
+        r.answer.append(dns.rrset.from_text(qname, ttl, IN, CNAME, "name." + synth2))
+        r.answer.append(dns.rrset.from_text("name." + synth2, ttl, IN, CNAME, "name."))
+        r.answer.append(dns.rrset.from_text(synth2, ttl, IN, DNAME, "."))
+    elif qname == synth or qname == synth2:
+        if qtype == "DNAME":
+            r.answer.append(dns.rrset.from_text(qname, ttl, IN, DNAME, "."))
+    elif qname == "name." + synth:
+        r.answer.append(dns.rrset.from_text(qname, ttl, IN, CNAME, "name."))
+        r.answer.append(dns.rrset.from_text(synth, ttl, IN, DNAME, "."))
+    elif qname == "name." + synth2:
+        r.answer.append(dns.rrset.from_text(qname, ttl, IN, CNAME, "name."))
+        r.answer.append(dns.rrset.from_text(synth2, ttl, IN, DNAME, "."))
+    elif qname == "ns3.example.dname.":
+        # This and the next two code branches referring to the "example.dname"
+        # zone are necessary for the resolver variant of the CVE-2021-25215
+        # regression test to work.  A named instance cannot be used for
+        # serving the DNAME records below as a version of BIND vulnerable to
+        # CVE-2021-25215 would crash while answering the queries asked by
+        # the tested resolver.
+        if qtype == "A":
+            r.answer.append(dns.rrset.from_text(qname, ttl, IN, A, ip4))
+        elif qtype == "AAAA":
+            r.authority.append(
+                dns.rrset.from_text("example.dname.", ttl, IN, SOA, ". . 0 0 0 0 0")
+            )
+    elif qname == "self.example.self..example.dname.":
+        r.answer.append(
+            dns.rrset.from_text("self.example.dname.", ttl, IN, DNAME, "dname.")
+        )
+        r.answer.append(
+            dns.rrset.from_text(qname, ttl, IN, CNAME, "self.example.dname.")
+        )
+    elif qname == "self.example.dname.":
+        if qtype == "DNAME":
+            r.answer.append(dns.rrset.from_text(qname, ttl, IN, DNAME, "dname."))
+    else:
+        rcode = "REFUSED"
+
+    r.flags |= dns.flags.AA
+    r.use_edns()
+    return r.to_wire()
+
+
+def sigterm(signum, frame):
+    print("Shutting down now...")
+    os.remove("ans.pid")
+    running = False
+    sys.exit(0)
+
+
+############################################################################
+# Main
+#
+# Set up responder and control channel, open the pid file, and start
+# the main loop, listening for queries on the query channel or commands
+# on the control channel and acting on them.
+############################################################################
+ip4 = "10.53.0.3"
+ip6 = "fd92:7065:b8e:ffff::3"
+
+try:
+    port = int(os.environ["PORT"])
+except:
+    port = 5300
+
+query4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+query4_udp.bind((ip4, port))
+
+query4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+query4_tcp.bind((ip4, port))
+query4_tcp.listen(1)
+query4_tcp.settimeout(1)
+
+havev6 = True
+try:
+    query6_udp = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+    try:
+        query6_udp.bind((ip6, port))
+    except:
+        query6_udp.close()
+        havev6 = False
+
+    query6_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    try:
+        query6_tcp.bind((ip4, port))
+        query6_tcp.listen(1)
+        query6_tcp.settimeout(1)
+    except:
+        query6_tcp.close()
+        havev6 = False
+except:
+    havev6 = False
+
+signal.signal(signal.SIGTERM, sigterm)
+
+f = open("ans.pid", "w")
+pid = os.getpid()
+print(pid, file=f)
+f.close()
+
+running = True
+
+print("Listening on %s port %d" % (ip4, port))
+if havev6:
+    print("Listening on %s port %d" % (ip6, port))
+print("Ctrl-c to quit")
+
+if havev6:
+    input = [query4_udp, query4_tcp, query6_udp, query6_tcp]
+else:
+    input = [query4_udp, query4_tcp]
+
+while running:
+    try:
+        inputready, outputready, exceptready = select.select(input, [], [])
+    except select.error as e:
+        break
+    except socket.error as e:
+        break
+    except KeyboardInterrupt:
+        break
+
+    for s in inputready:
+        if s == query4_udp or s == query6_udp:
+            print("Query received on %s" % (ip4 if s == query4_udp else ip6))
+            # Handle incoming queries
+            msg = s.recvfrom(65535)
+            rsp = create_response(msg[0])
+            if rsp:
+                s.sendto(rsp, msg[1])
+        elif s == query4_tcp or s == query6_tcp:
+            try:
+                conn, _ = s.accept()
+                if s == query4_tcp or s == query6_tcp:
+                    print(
+                        "TCP Query received on %s" % (ip4 if s == query4_tcp else ip6),
+                        end=" ",
+                    )
+                # get TCP message length
+                msg = conn.recv(2)
+                if len(msg) != 2:
+                    print("couldn't read TCP message length")
+                    continue
+                length = struct.unpack(">H", msg[:2])[0]
+                msg = conn.recv(length)
+                if len(msg) != length:
+                    print("couldn't read TCP message")
+                    continue
+                rsp = create_response(msg)
+                if rsp:
+                    conn.send(struct.pack(">H", len(rsp)))
+                    conn.send(rsp)
+                conn.close()
+            except socket.error as e:
+                print("error: %s" % str(e))
+    if not running:
+        break
index 45d650417f0c04a99488efb74be410bed01de010..e4fc15a280ec70cd1761cc95778d53f3a1afcd97 100755 (executable)
@@ -276,16 +276,30 @@ except: port=5300
 try: ctrlport=int(os.environ['EXTRAPORT1'])
 except: ctrlport=5300
 
-query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-query4_socket.bind((ip4, port))
+query4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+query4_udp.bind((ip4, port))
+
+query4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+query4_tcp.bind((ip4, port))
+query4_tcp.listen(1)
+query4_tcp.settimeout(1)
 
 havev6 = True
 try:
-    query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+    query6_udp = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+    try:
+        query6_udp.bind((ip6, port))
+    except:
+        query6_udp.close()
+        havev6 = False
+
+    query6_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     try:
-        query6_socket.bind((ip6, port))
+        query6_tcp.bind((ip4, port))
+        query6_tcp.listen(1)
+        query6_tcp.settimeout(1)
     except:
-        query6_socket.close()
+        query6_tcp.close()
         havev6 = False
 except:
     havev6 = False
@@ -310,9 +324,9 @@ print ("Control channel on %s port %d" % (ip4, ctrlport))
 print ("Ctrl-c to quit")
 
 if havev6:
-    input = [query4_socket, query6_socket, ctrl_socket]
+    input = [query4_udp, query4_tcp, query6_udp, query6_tcp, ctrl_socket]
 else:
-    input = [query4_socket, ctrl_socket]
+    input = [query4_udp, query4_tcp, ctrl_socket]
 
 while running:
     try:
@@ -335,13 +349,37 @@ while running:
                     break
                 ctl_channel(msg)
             conn.close()
-        if s == query4_socket or s == query6_socket:
-            print ("Query received on %s" %
-                    (ip4 if s == query4_socket else ip6))
+        elif s == query4_udp or s == query6_udp:
+            print("Query received on %s" % (ip4 if s == query4_udp else ip6))
             # Handle incoming queries
             msg = s.recvfrom(65535)
             rsp = create_response(msg[0])
             if rsp:
                 s.sendto(rsp, msg[1])
+        elif s == query4_tcp or s == query6_tcp:
+            try:
+                conn, _ = s.accept()
+                if s == query4_tcp or s == query6_tcp:
+                    print(
+                        "TCP Query received on %s" % (ip4 if s == query4_tcp else ip6),
+                        end=" ",
+                    )
+                # get TCP message length
+                msg = conn.recv(2)
+                if len(msg) != 2:
+                    print("couldn't read TCP message length")
+                    continue
+                length = struct.unpack(">H", msg[:2])[0]
+                msg = conn.recv(length)
+                if len(msg) != length:
+                    print("couldn't read TCP message")
+                    continue
+                rsp = create_response(msg)
+                if rsp:
+                    conn.send(struct.pack(">H", len(rsp)))
+                    conn.send(rsp)
+                conn.close()
+            except socket.error as e:
+                print("error: %s" % str(e))
     if not running:
         break
index b454fc8c91a540d7a486dd40632908bb1f6bc9d1..dd48bdb0950bf31e1a267c60cc3d55534d02cbfc 100644 (file)
@@ -1,13 +1,13 @@
-############################################################################
 # Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 #
+# SPDX-License-Identifier: MPL-2.0
+#
 # This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
 # file, you can obtain one at https://mozilla.org/MPL/2.0/.
 #
 # See the COPYRIGHT file distributed with this work for additional
 # information regarding copyright ownership.
-############################################################################
 
 from __future__ import print_function
 import os
@@ -35,28 +35,27 @@ from dns.rdataclass import *
 from dns.rdatatype import *
 from dns.tsig import *
 
+
 # Log query to file
 def logquery(type, qname):
     with open("qlog", "a") as f:
         f.write("%s %s\n", type, qname)
 
+
 # DNS 2.0 keyring specifies the algorithm
 try:
-    keyring = dns.tsigkeyring.from_text({ "foo" : {
-                                                   "hmac-sha256",
-                                                   "aaaaaaaaaaaa"
-                                                  } ,
-                                          "fake" : {
-                                                   "hmac-sha256",
-                                                   "aaaaaaaaaaaa"
-                                                  }
-                                         })
+    keyring = dns.tsigkeyring.from_text(
+        {
+            "foo": {"hmac-sha256", "aaaaaaaaaaaa"},
+            "fake": {"hmac-sha256", "aaaaaaaaaaaa"},
+        }
+    )
 except:
-    keyring = dns.tsigkeyring.from_text({ "foo" : "aaaaaaaaaaaa",
-                                           "fake" : "aaaaaaaaaaaa" })
+    keyring = dns.tsigkeyring.from_text({"foo": "aaaaaaaaaaaa", "fake": "aaaaaaaaaaaa"})
 
 dopass2 = False
 
+
 ############################################################################
 #
 # This server will serve valid and spoofed answers. A spoofed answer will
@@ -81,7 +80,7 @@ def create_response(msg, tcp, first, ns10):
     m = dns.message.from_wire(msg, keyring=keyring)
     qname = m.question[0].name.to_text()
     lqname = qname.lower()
-    labels = lqname.split('.')
+    labels = lqname.split(".")
     rrtype = m.question[0].rdtype
     typename = dns.rdatatype.to_text(rrtype)
 
@@ -113,27 +112,35 @@ def create_response(msg, tcp, first, ns10):
     # Add a server cookie to the response
     if labels[0] != "nocookie":
         for o in m.options:
-            if o.otype == 10: # Use 10 instead of COOKIE
-                 if first and labels[0] == "withtsig" and not tcp:
-                     r.use_tsig(keyring = keyring,
-                                keyname = dns.name.from_text("fake"),
-                                algorithm = HMAC_SHA256)
-                 elif labels[0] != "tcponly" or tcp:
-                     cookie = o
-                     if len(o.data) == 8:
-                         cookie.data = o.data + o.data
-                     else:
-                         cookie.data = o.data
-                     r.use_edns(options=[cookie])
+            if o.otype == 10:  # Use 10 instead of COOKIE
+                if first and labels[0] == "withtsig" and not tcp:
+                    r.use_tsig(
+                        keyring=keyring,
+                        keyname=dns.name.from_text("fake"),
+                        algorithm=HMAC_SHA256,
+                    )
+                elif labels[0] != "tcponly" or tcp:
+                    cookie = o
+                    try:
+                        if len(o.server) == 0:
+                            cookie.server = o.client
+                    except AttributeError:  # dnspython<2.7.0 compat
+                        if len(o.data) == 8:
+                            cookie.data = o.data + o.data
+                        else:
+                            cookie.data = o.data
+                    r.use_edns(options=[cookie])
     r.flags |= dns.flags.AA
     return r
 
+
 def sigterm(signum, frame):
-    print ("Shutting down now...")
-    os.remove('ans.pid')
+    print("Shutting down now...")
+    os.remove("ans.pid")
     running = False
     sys.exit(0)
 
+
 ############################################################################
 # Main
 #
@@ -146,8 +153,10 @@ ip4_addr2 = "10.53.0.10"
 ip6_addr1 = "fd92:7065:b8e:ffff::9"
 ip6_addr2 = "fd92:7065:b8e:ffff::10"
 
-try: port=int(os.environ['PORT'])
-except: port=5300
+try:
+    port = int(os.environ["PORT"])
+except:
+    port = 5300
 
 query4_udp1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 query4_udp1.bind((ip4_addr1, port))
@@ -195,24 +204,32 @@ except:
 
 signal.signal(signal.SIGTERM, sigterm)
 
-f = open('ans.pid', 'w')
+f = open("ans.pid", "w")
 pid = os.getpid()
-print (pid, file=f)
+print(pid, file=f)
 f.close()
 
 running = True
 
-print ("Using DNS version %s" % dns.version.version)
-print ("Listening on %s port %d" % (ip4_addr1, port))
-print ("Listening on %s port %d" % (ip4_addr2, port))
+print("Using DNS version %s" % dns.version.version)
+print("Listening on %s port %d" % (ip4_addr1, port))
+print("Listening on %s port %d" % (ip4_addr2, port))
 if havev6:
-    print ("Listening on %s port %d" % (ip6_addr1, port))
-    print ("Listening on %s port %d" % (ip6_addr2, port))
-print ("Ctrl-c to quit")
+    print("Listening on %s port %d" % (ip6_addr1, port))
+    print("Listening on %s port %d" % (ip6_addr2, port))
+print("Ctrl-c to quit")
 
 if havev6:
-    input = [query4_udp1, query6_udp1, query4_tcp1, query6_tcp1,
-             query4_udp2, query6_udp2, query4_tcp2, query6_tcp2]
+    input = [
+        query4_udp1,
+        query6_udp1,
+        query4_tcp1,
+        query6_tcp1,
+        query4_udp2,
+        query6_udp2,
+        query4_tcp2,
+        query6_tcp2,
+    ]
 else:
     input = [query4_udp1, query4_tcp1, query4_udp2, query4_tcp2]
 
@@ -228,14 +245,19 @@ while running:
 
     for s in inputready:
         ns10 = False
-        if s == query4_udp1 or s == query6_udp1 or \
-           s == query4_udp2 or s == query6_udp2:
+        if s == query4_udp1 or s == query6_udp1 or s == query4_udp2 or s == query6_udp2:
             if s == query4_udp1 or s == query6_udp1:
-                print ("UDP Query received on %s" %
-                       (ip4_addr1 if s == query4_udp1 else ip6_addr1), end=" ")
+                print(
+                    "UDP Query received on %s"
+                    % (ip4_addr1 if s == query4_udp1 else ip6_addr1),
+                    end=" ",
+                )
             if s == query4_udp2 or s == query6_udp2:
-                print ("UDP Query received on %s" %
-                       (ip4_addr2 if s == query4_udp2 else ip6_addr2), end=" ")
+                print(
+                    "UDP Query received on %s"
+                    % (ip4_addr2 if s == query4_udp2 else ip6_addr2),
+                    end=" ",
+                )
                 ns10 = True
             # Handle incoming queries
             msg = s.recvfrom(65535)
@@ -244,31 +266,36 @@ while running:
             print(dns.rcode.to_text(rsp.rcode()))
             s.sendto(rsp.to_wire(), msg[1])
             if dopass2:
-                print ("Sending second UDP response without TSIG", end=" ")
+                print("Sending second UDP response without TSIG", end=" ")
                 rsp = create_response(msg[0], False, False, ns10)
                 s.sendto(rsp.to_wire(), msg[1])
                 print(dns.rcode.to_text(rsp.rcode()))
 
-        if s == query4_tcp1 or s == query6_tcp1 or \
-           s == query4_tcp2 or s == query6_tcp2:
+        if s == query4_tcp1 or s == query6_tcp1 or s == query4_tcp2 or s == query6_tcp2:
             try:
                 (cs, _) = s.accept()
                 if s == query4_tcp1 or s == query6_tcp1:
-                    print ("TCP Query received on %s" %
-                           (ip4_addr1 if s == query4_tcp1 else ip6_addr1), end=" ")
+                    print(
+                        "TCP Query received on %s"
+                        % (ip4_addr1 if s == query4_tcp1 else ip6_addr1),
+                        end=" ",
+                    )
                 if s == query4_tcp2 or s == query6_tcp2:
-                    print ("TCP Query received on %s" %
-                           (ip4_addr2 if s == query4_tcp2 else ip6_addr2), end=" ")
+                    print(
+                        "TCP Query received on %s"
+                        % (ip4_addr2 if s == query4_tcp2 else ip6_addr2),
+                        end=" ",
+                    )
                     ns10 = True
                 # get TCP message length
                 buf = cs.recv(2)
-                length = struct.unpack('>H', buf[:2])[0]
+                length = struct.unpack(">H", buf[:2])[0]
                 # grep DNS message
                 msg = cs.recv(length)
                 rsp = create_response(msg, True, True, ns10)
                 print(dns.rcode.to_text(rsp.rcode()))
                 wire = rsp.to_wire()
-                cs.send(struct.pack('>H', len(wire)))
+                cs.send(struct.pack(">H", len(wire)))
                 cs.send(wire)
                 cs.close()
             except s.timeout:
index 96c5ef1f79cf3ce6c2d82a9114d8d5179800d10a..54208a49e61f8557a4affd1778b69f03bf4a73db 100644 (file)
@@ -226,6 +226,7 @@ struct dns_message {
        unsigned int                    cc_bad : 1;
        unsigned int                    tkey : 1;
        unsigned int                    rdclass_set : 1;
+       unsigned int                    has_dname : 1;
 
        unsigned int                    opt_reserved;
        unsigned int                    sig_reserved;
@@ -1408,6 +1409,13 @@ dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass);
  * \li   msg be a valid message with parsing intent.
  */
 
+bool
+dns_message_hasdname(dns_message_t *msg);
+/*%<
+ * Return whether a DNAME was detected in the ANSWER section of a QUERY
+ * message when it was parsed.
+ */
+
 ISC_LANG_ENDDECLS
 
 #endif /* DNS_MESSAGE_H */
index 03ed2a178dc60bf3ebfd3919a2900d8965221ae7..8a010a8e9d79ac5558545d4eb4270dac4c2cf5f4 100644 (file)
@@ -459,6 +459,7 @@ msginit(dns_message_t *m) {
        m->cc_bad = 0;
        m->tkey = 0;
        m->rdclass_set = 0;
+       m->has_dname = 0;
        m->querytsig = NULL;
        m->indent.string = dns_master_indentstr;
        m->indent.count = dns_master_indent;
@@ -1829,6 +1830,11 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
                         */
                        msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS;
                        free_name = false;
+               } else if (rdtype == dns_rdatatype_dname &&
+                          sectionid == DNS_SECTION_ANSWER &&
+                          msg->opcode == dns_opcode_query)
+               {
+                       msg->has_dname = 1;
                }
                rdataset = NULL;
 
@@ -4701,3 +4707,9 @@ dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass) {
        msg->rdclass = rdclass;
        msg->rdclass_set = 1;
 }
+
+bool
+dns_message_hasdname(dns_message_t *msg) {
+       REQUIRE(DNS_MESSAGE_VALID(msg));
+       return msg->has_dname;
+}
index e48899b3263f0b424383e5f69008fbf66217b96a..2b57b8ebb4b04a0d47197d80d8f3db1f853d0ad7 100644 (file)
@@ -7209,8 +7209,9 @@ validinanswer(dns_rdataset_t *rdataset, fetchctx_t *fctx) {
 }
 
 static isc_result_t
-answer_response(fetchctx_t *fctx, dns_message_t *message) {
+answer_response(resquery_t *query,  dns_message_t *message) {
        isc_result_t result;
+       fetchctx_t *fctx = NULL;
        dns_name_t *name = NULL, *qname = NULL, *ns_name = NULL;
        dns_name_t *aname = NULL, *cname = NULL, *dname = NULL;
        dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
@@ -7223,6 +7224,8 @@ answer_response(fetchctx_t *fctx, dns_message_t *message) {
        dns_view_t *view = NULL;
        dns_trust_t trust;
 
+       REQUIRE(VALID_QUERY(query));
+       fctx = query->fctx;
        REQUIRE(VALID_FCTX(fctx));
 
        FCTXTRACE("answer_response");
@@ -7527,8 +7530,17 @@ answer_response(fetchctx_t *fctx, dns_message_t *message) {
         *
         * We expect there to be only one owner name for all the rdatasets
         * in this section, and we expect that it is not external.
+        *
+        * If the message was not sent over TCP or otherwise secured,
+        * skip this.
         */
-       result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+       if (message->cc_ok || message->tsig != NULL || message->sig0 != NULL ||
+           (query->options & DNS_FETCHOPT_TCP) != 0)
+       {
+               result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
+       } else {
+               done = true;
+       }
        while (!done && result == ISC_R_SUCCESS) {
                name = NULL;
                dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
@@ -8011,6 +8023,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
        unsigned int bucketnum;
        dns_resolver_t *res;
        bool bucket_empty;
+       bool secured = false;
 #ifdef HAVE_DNSTAP
        isc_socket_t *sock = NULL;
        isc_sockaddr_t localaddr, *la = NULL;
@@ -8321,6 +8334,17 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
                goto done;
        }
 
+       /*
+        * Remember whether this message was signed or had a
+        * valid client cookie; if not, we may need to retry over
+        * TCP later.
+        */
+       if (rmessage->cc_ok || rmessage->tsig != NULL ||
+           rmessage->sig0 != NULL)
+       {
+               secured = true;
+       }
+
        /*
         * The dispatcher should ensure we only get responses with QR set.
         */
@@ -8338,10 +8362,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
         * This may be a misconfigured anycast server or an attempt to send
         * a spoofed response.  Skip if we have a valid tsig.
         */
-       if (dns_message_gettsig(rmessage, NULL) == NULL &&
-           !rmessage->cc_ok && !rmessage->cc_bad &&
-           (options & DNS_FETCHOPT_TCP) == 0)
-       {
+       if (!secured && (options & DNS_FETCHOPT_TCP) == 0) {
                unsigned char cookie[COOKIE_BUFFER_SIZE];
                if (dns_adb_getcookie(fctx->adb, query->addrinfo, cookie,
                                      sizeof(cookie)) > CLIENT_COOKIE_SIZE)
@@ -8364,6 +8385,16 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
                 * XXXMPA When support for DNS COOKIE becomes ubiquitous, fall
                 * back to TCP for all non-COOKIE responses.
                 */
+
+               /*
+                * Check whether we need to retry over TCP for some other
+                * reason.
+                */
+               if (dns_message_hasdname(rmessage)) {
+                       options |= DNS_FETCHOPT_TCP;
+                       resend = true;
+                       goto done;
+               }
        }
 
        /*
@@ -8720,7 +8751,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
                if ((rmessage->flags & DNS_MESSAGEFLAG_AA) != 0 ||
                    ISFORWARDER(query->addrinfo))
                {
-                       result = answer_response(fctx, rmessage);
+                       result = answer_response(query, rmessage);
                        if (result != ISC_R_SUCCESS)
                                FCTXTRACE3("answer_response (AA/fwd)", result);
                } else if (iscname(fctx, rmessage) &&
@@ -8732,7 +8763,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
                         * answer when a CNAME is followed.  We should treat
                         * it as a valid answer.
                         */
-                       result = answer_response(fctx, rmessage);
+                       result = answer_response(query, rmessage);
                        if (result != ISC_R_SUCCESS)
                                FCTXTRACE3("answer_response (!ANY/!CNAME)",
                                           result);
@@ -8741,7 +8772,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
                        /*
                         * Lame response !!!.
                         */
-                       result = answer_response(fctx, rmessage);
+                       result = answer_response(query, rmessage);
                        if (result != ISC_R_SUCCESS)
                                FCTXTRACE3("answer_response (!NS)", result);
                } else {
index a0a8f625563b6c1b60de88a7651217327027b340..3cac2ec7da1cd59c5fed3dc6ded007bbb21660eb 100644 (file)
@@ -537,6 +537,7 @@ dns_message_gettemprdataset
 dns_message_gettimeadjust
 dns_message_gettsig
 dns_message_gettsigkey
+dns_message_hasdname
 dns_message_logfmtpacket
 dns_message_logfmtpacket2
 dns_message_logpacket