mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-27 03:46:03 -04:00
Retry lookups with unsigned DNAME over TCP
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 2e40705c06)
This commit is contained in:
parent
cd17dfe696
commit
4c6d03b0bb
6 changed files with 366 additions and 168 deletions
|
|
@ -1,143 +0,0 @@
|
|||
#!/usr/bin/env perl
|
||||
|
||||
# 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.
|
||||
|
||||
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;
|
||||
}
|
||||
217
bin/tests/system/chain/ans3/ans.py
Normal file
217
bin/tests/system/chain/ans3/ans.py
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
# 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 = f"ns3.{zone}"
|
||||
synth = f"synth-then-dname.{zone}"
|
||||
synth2 = f"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(f"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 == f"cname-to-{synth2}":
|
||||
r.answer.append(dns.rrset.from_text(qname, ttl, IN, CNAME, f"name.{synth2}"))
|
||||
r.answer.append(dns.rrset.from_text(f"name.{synth2}", ttl, IN, CNAME, "name."))
|
||||
r.answer.append(dns.rrset.from_text(synth2, ttl, IN, DNAME, "."))
|
||||
elif qname == f"{synth}" or qname == f"{synth2}":
|
||||
if qtype == "DNAME":
|
||||
r.answer.append(dns.rrset.from_text(qname, ttl, IN, DNAME, "."))
|
||||
elif qname == f"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 == f"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
|
||||
|
|
@ -316,16 +316,30 @@ try:
|
|||
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_socket.bind((ip6, port))
|
||||
query6_udp.bind((ip6, port))
|
||||
except:
|
||||
query6_socket.close()
|
||||
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
|
||||
|
|
@ -350,9 +364,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:
|
||||
|
|
@ -375,12 +389,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
|
||||
|
|
|
|||
|
|
@ -284,6 +284,7 @@ struct dns_message {
|
|||
unsigned int tkey : 1;
|
||||
unsigned int rdclass_set : 1;
|
||||
unsigned int fuzzing : 1;
|
||||
unsigned int has_dname : 1;
|
||||
|
||||
unsigned int opt_reserved;
|
||||
unsigned int sig_reserved;
|
||||
|
|
@ -1527,4 +1528,11 @@ dns_message_response_minttl(dns_message_t *msg, dns_ttl_t *pttl);
|
|||
* \li 'pttl != NULL'.
|
||||
*/
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -428,6 +428,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 = "\t";
|
||||
m->indent.count = 0;
|
||||
|
|
@ -1654,6 +1655,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;
|
||||
|
||||
|
|
@ -4798,3 +4804,9 @@ dns_message_response_minttl(dns_message_t *msg, dns_ttl_t *pttl) {
|
|||
|
||||
return ISC_R_SUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
dns_message_hasdname(dns_message_t *msg) {
|
||||
REQUIRE(DNS_MESSAGE_VALID(msg));
|
||||
return msg->has_dname;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -808,6 +808,7 @@ typedef struct respctx {
|
|||
bool get_nameservers; /* get a new NS rrset at
|
||||
* zone cut? */
|
||||
bool resend; /* resend this query? */
|
||||
bool secured; /* message was signed or had a valid cookie */
|
||||
bool nextitem; /* invalid response; keep
|
||||
* listening for the correct one */
|
||||
bool truncated; /* response was truncated */
|
||||
|
|
@ -7894,6 +7895,47 @@ betterreferral(respctx_t *rctx) {
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
rctx_need_tcpretry(respctx_t *rctx) {
|
||||
resquery_t *query = rctx->query;
|
||||
if ((rctx->retryopts & DNS_FETCHOPT_TCP) != 0) {
|
||||
/* TCP is already in the retry flags */
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the message was secured, no need to continue.
|
||||
*/
|
||||
if (rctx->secured) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Currently the only extra reason why we might need to
|
||||
* retry a UDP response over TCP is a DNAME in the message.
|
||||
*/
|
||||
if (dns_message_hasdname(query->rmessage)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static isc_result_t
|
||||
rctx_tcpretry(respctx_t *rctx) {
|
||||
/*
|
||||
* Do we need to retry a UDP response over TCP?
|
||||
*/
|
||||
if (rctx_need_tcpretry(rctx)) {
|
||||
rctx->retryopts |= DNS_FETCHOPT_TCP;
|
||||
rctx->resend = true;
|
||||
rctx_done(rctx, ISC_R_SUCCESS);
|
||||
return ISC_R_COMPLETE;
|
||||
}
|
||||
|
||||
return ISC_R_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* resquery_response():
|
||||
* Handles responses received in response to iterative queries sent by
|
||||
|
|
@ -8083,6 +8125,17 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) {
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remember whether this message was signed or had a
|
||||
* valid client cookie; if not, we may need to retry over
|
||||
* TCP later.
|
||||
*/
|
||||
if (query->rmessage->cc_ok || query->rmessage->tsig != NULL ||
|
||||
query->rmessage->sig0 != NULL)
|
||||
{
|
||||
rctx.secured = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* The dispatcher should ensure we only get responses with QR
|
||||
* set.
|
||||
|
|
@ -8094,10 +8147,7 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) {
|
|||
* TCP. 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(query->rmessage, NULL) == NULL &&
|
||||
!query->rmessage->cc_ok && !query->rmessage->cc_bad &&
|
||||
(rctx.retryopts & DNS_FETCHOPT_TCP) == 0)
|
||||
{
|
||||
if (!rctx.secured && (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) {
|
||||
unsigned char cookie[COOKIE_BUFFER_SIZE];
|
||||
if (dns_adb_getcookie(fctx->adb, query->addrinfo, cookie,
|
||||
sizeof(cookie)) > CLIENT_COOKIE_SIZE)
|
||||
|
|
@ -8109,8 +8159,7 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) {
|
|||
isc_log_write(
|
||||
dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
||||
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
||||
"missing expected cookie "
|
||||
"from %s",
|
||||
"missing expected cookie from %s",
|
||||
addrbuf);
|
||||
}
|
||||
rctx.retryopts |= DNS_FETCHOPT_TCP;
|
||||
|
|
@ -8120,6 +8169,17 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether we need to retry over TCP for some other reason.
|
||||
*/
|
||||
result = rctx_tcpretry(&rctx);
|
||||
if (result == ISC_R_COMPLETE) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for EDNS issues.
|
||||
*/
|
||||
rctx_edns(&rctx);
|
||||
|
||||
/*
|
||||
|
|
@ -8851,8 +8911,8 @@ rctx_answer_positive(respctx_t *rctx) {
|
|||
}
|
||||
|
||||
/*
|
||||
* Cache records in the authority section, if
|
||||
* there are any suitable for caching.
|
||||
* Cache records in the authority section, if there are
|
||||
* any suitable for caching.
|
||||
*/
|
||||
rctx_authority_positive(rctx);
|
||||
|
||||
|
|
@ -9225,14 +9285,14 @@ rctx_answer_dname(respctx_t *rctx) {
|
|||
|
||||
/*
|
||||
* rctx_authority_positive():
|
||||
* Examine the records in the authority section (if there are any) for a
|
||||
* positive answer. We expect the names for all rdatasets in this
|
||||
* section to be subdomains of the domain being queried; any that are
|
||||
* not are skipped. We expect to find only *one* owner name; any names
|
||||
* after the first one processed are ignored. We expect to find only
|
||||
* rdatasets of type NS, RRSIG, or SIG; all others are ignored. Whatever
|
||||
* remains can be cached at trust level authauthority or additional
|
||||
* (depending on whether the AA bit was set on the answer).
|
||||
* If a positive answer was received over TCP or secured with a cookie
|
||||
* or TSIG, examine the authority section. We expect names for all
|
||||
* rdatasets in this section to be subdomains of the domain being queried;
|
||||
* any that are not are skipped. We expect to find only *one* owner name;
|
||||
* any names after the first one processed are ignored. We expect to find
|
||||
* only rdatasets of type NS; all others are ignored. Whatever remains can
|
||||
* be cached at trust level authauthority or additional (depending on
|
||||
* whether the AA bit was set on the answer).
|
||||
*/
|
||||
static void
|
||||
rctx_authority_positive(respctx_t *rctx) {
|
||||
|
|
@ -9240,6 +9300,11 @@ rctx_authority_positive(respctx_t *rctx) {
|
|||
bool done = false;
|
||||
isc_result_t result;
|
||||
|
||||
/* If it's spoofable, don't cache it. */
|
||||
if (!rctx->secured && (rctx->query->options & DNS_FETCHOPT_TCP) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
result = dns_message_firstname(rctx->query->rmessage,
|
||||
DNS_SECTION_AUTHORITY);
|
||||
while (!done && result == ISC_R_SUCCESS) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue