mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-28 04:34:54 -04:00
Merge branch '2275-tighten-dns-cookie-response-handling' into 'main'
Resolve "Tighten DNS COOKIE response handling" Closes #2275 See merge request isc-projects/bind9!4391
This commit is contained in:
commit
3d7a0e7af7
13 changed files with 565 additions and 9 deletions
3
CHANGES
3
CHANGES
|
|
@ -1,3 +1,6 @@
|
|||
5539. [bug] Tighten handling of missing DNS COOKIE responses over
|
||||
UDP by falling back to TCP. [GL #2275]
|
||||
|
||||
5538. [func] Add NSEC3 support for zones that manage DNSSEC with
|
||||
the 'dnssec-policy' configuration. A new option
|
||||
'nsec3param' can be used to set the NSEC3 parameters.
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ TESTS += \
|
|||
checkconf \
|
||||
checknames \
|
||||
checkzone \
|
||||
cookie \
|
||||
database \
|
||||
dlz \
|
||||
dlzexternal \
|
||||
|
|
@ -202,7 +201,7 @@ if HAVE_PYTHON
|
|||
TESTS += kasp tcp pipelined
|
||||
|
||||
if HAVE_PYMOD_DNS
|
||||
TESTS += qmin
|
||||
TESTS += qmin cookie
|
||||
|
||||
if HAVE_PERLMOD_NET_DNS
|
||||
TESTS += dnssec
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ cds
|
|||
checkconf
|
||||
checknames
|
||||
checkzone
|
||||
cookie
|
||||
database
|
||||
digdelv
|
||||
dlz
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ SEQUENTIALDIRS="$SEQUENTIAL_COMMON $SEQUENTIAL_UNIX"
|
|||
|
||||
PARALLEL_UNIX="@DNSTAP@
|
||||
chain
|
||||
cookie
|
||||
dlzexternal
|
||||
dnssec
|
||||
dyndb
|
||||
|
|
|
|||
277
bin/tests/system/cookie/ans9/ans.py
Normal file
277
bin/tests/system/cookie/ans9/ans.py
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
############################################################################
|
||||
# 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.
|
||||
############################################################################
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import socket
|
||||
import select
|
||||
from datetime import datetime, timedelta
|
||||
import time
|
||||
import functools
|
||||
|
||||
import dns
|
||||
import dns.edns
|
||||
import dns.flags
|
||||
import dns.message
|
||||
import dns.query
|
||||
import dns.tsig
|
||||
import dns.tsigkeyring
|
||||
import dns.version
|
||||
|
||||
from dns.edns import *
|
||||
from dns.name import *
|
||||
from dns.rcode import *
|
||||
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"
|
||||
}
|
||||
})
|
||||
except:
|
||||
keyring = dns.tsigkeyring.from_text({ "foo" : "aaaaaaaaaaaa",
|
||||
"fake" : "aaaaaaaaaaaa" })
|
||||
|
||||
dopass2 = False
|
||||
|
||||
############################################################################
|
||||
#
|
||||
# This server will serve valid and spoofed answers. A spoofed answer will
|
||||
# have the address 10.53.0.10 included.
|
||||
#
|
||||
# When receiving a query over UDP:
|
||||
#
|
||||
# A query to "nocookie"/A will result in a spoofed answer with no cookie set.
|
||||
# A query to "tcponly"/A will result in a spoofed answer with no cookie set.
|
||||
# A query to "withtsig"/A will result in two responses, the first is a spoofed
|
||||
# answer that is TSIG signed, the second is a valid answer with a cookie set.
|
||||
# A query to anything else will result in a valid answer with a cookie set.
|
||||
#
|
||||
# When receiving a query over TCP:
|
||||
#
|
||||
# A query to "nocookie"/A will result in a valid answer with no cookie set.
|
||||
# A query to anything else will result in a valid answer with a cookie set.
|
||||
#
|
||||
############################################################################
|
||||
def create_response(msg, tcp, first, ns10):
|
||||
global dopass2
|
||||
m = dns.message.from_wire(msg, keyring=keyring)
|
||||
qname = m.question[0].name.to_text()
|
||||
lqname = qname.lower()
|
||||
labels = lqname.split('.')
|
||||
rrtype = m.question[0].rdtype
|
||||
typename = dns.rdatatype.to_text(rrtype)
|
||||
|
||||
with open("query.log", "a") as f:
|
||||
f.write("%s %s\n" % (typename, qname))
|
||||
print("%s %s" % (typename, qname), end=" ")
|
||||
|
||||
r = dns.message.make_response(m)
|
||||
r.set_rcode(NOERROR)
|
||||
if rrtype == A:
|
||||
# exempt potential nameserver A records.
|
||||
if labels[0] == "ns" and ns10:
|
||||
r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10"))
|
||||
else:
|
||||
r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.9"))
|
||||
if not tcp and labels[0] == "nocookie":
|
||||
r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10"))
|
||||
if not tcp and labels[0] == "tcponly":
|
||||
r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10"))
|
||||
if first and not tcp and labels[0] == "withtsig":
|
||||
r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10"))
|
||||
dopass2 = True
|
||||
elif rrtype == NS:
|
||||
r.answer.append(dns.rrset.from_text(qname, 1, IN, NS, "."))
|
||||
elif rrtype == SOA:
|
||||
r.answer.append(dns.rrset.from_text(qname, 1, IN, SOA, ". . 0 0 0 0 0"))
|
||||
else:
|
||||
r.authority.append(dns.rrset.from_text(qname, 1, IN, SOA, ". . 0 0 0 0 0"))
|
||||
# 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])
|
||||
r.flags |= dns.flags.AA
|
||||
return r
|
||||
|
||||
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_addr1 = "10.53.0.9"
|
||||
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
|
||||
|
||||
query4_udp1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
query4_udp1.bind((ip4_addr1, port))
|
||||
query4_tcp1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
query4_tcp1.bind((ip4_addr1, port))
|
||||
query4_tcp1.listen(1)
|
||||
query4_tcp1.settimeout(1)
|
||||
|
||||
query4_udp2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
query4_udp2.bind((ip4_addr2, port))
|
||||
query4_tcp2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
query4_tcp2.bind((ip4_addr2, port))
|
||||
query4_tcp2.listen(1)
|
||||
query4_tcp2.settimeout(1)
|
||||
|
||||
havev6 = True
|
||||
query6_udp1 = None
|
||||
query6_udp2 = None
|
||||
query6_tcp1 = None
|
||||
query6_tcp2 = None
|
||||
try:
|
||||
query6_udp1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
query6_udp1.bind((ip6_addr1, port))
|
||||
query6_tcp1 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
query6_tcp1.bind((ip6_addr1, port))
|
||||
query6_tcp1.listen(1)
|
||||
query6_tcp1.settimeout(1)
|
||||
|
||||
query6_udp2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
query6_udp2.bind((ip6_addr2, port))
|
||||
query6_tcp2 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
query6_tcp2.bind((ip6_addr2, port))
|
||||
query6_tcp2.listen(1)
|
||||
query6_tcp2.settimeout(1)
|
||||
except:
|
||||
if query6_udp1 != None:
|
||||
query6_udp1.close()
|
||||
if query6_tcp1 != None:
|
||||
query6_tcp1.close()
|
||||
if query6_udp2 != None:
|
||||
query6_udp2.close()
|
||||
if query6_tcp2 != None:
|
||||
query6_tcp2.close()
|
||||
havev6 = False
|
||||
|
||||
signal.signal(signal.SIGTERM, sigterm)
|
||||
|
||||
f = open('ans.pid', 'w')
|
||||
pid = os.getpid()
|
||||
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))
|
||||
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")
|
||||
|
||||
if havev6:
|
||||
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]
|
||||
|
||||
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:
|
||||
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:
|
||||
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=" ")
|
||||
ns10 = True
|
||||
# Handle incoming queries
|
||||
msg = s.recvfrom(65535)
|
||||
dopass2 = False
|
||||
rsp = create_response(msg[0], False, True, ns10)
|
||||
print(dns.rcode.to_text(rsp.rcode()))
|
||||
s.sendto(rsp.to_wire(), msg[1])
|
||||
if dopass2:
|
||||
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:
|
||||
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=" ")
|
||||
if s == query4_tcp2 or s == query6_tcp2:
|
||||
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]
|
||||
# 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(wire)
|
||||
cs.close()
|
||||
except s.timeout:
|
||||
pass
|
||||
if not running:
|
||||
break
|
||||
|
|
@ -9,9 +9,12 @@
|
|||
|
||||
rm -f ns*/named.conf
|
||||
rm -f dig.out.*
|
||||
rm -f named.run.*
|
||||
rm -f rndc.out.*
|
||||
rm -f ns1/named_dump.db*
|
||||
rm -f ns*/named.memstats
|
||||
rm -f ns*/named.run
|
||||
rm -f ns*/named.lock
|
||||
rm -f ns*/managed-keys.bind*
|
||||
rm -f ns*/named.run.prev
|
||||
rm -f ans*/ans.run ans*/ans.log
|
||||
|
|
|
|||
|
|
@ -14,6 +14,15 @@ key rndc_key {
|
|||
algorithm hmac-sha256;
|
||||
};
|
||||
|
||||
key foo {
|
||||
secret "aaaaaaaaaaaa";
|
||||
algorithm hmac-sha256;
|
||||
};
|
||||
|
||||
server 10.53.0.10 {
|
||||
keys foo;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,3 +20,7 @@ large.xxx TXT ( large large large large large large large large
|
|||
large large large large large large large large
|
||||
large large large large large large large large
|
||||
large large large large large large large large )
|
||||
tld. NS ns.tld.
|
||||
ns.tld A 10.53.0.9
|
||||
tsig. NS ns.tsig.
|
||||
ns.tsig A 10.53.0.10
|
||||
|
|
|
|||
|
|
@ -302,5 +302,211 @@ grep "status: NOERROR," dig.out.test$n > /dev/null || ret=1
|
|||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=`expr $status + $ret`
|
||||
|
||||
n=`expr $n + 1`
|
||||
echo_i "check that test server is correctly configured ($n)"
|
||||
ret=0
|
||||
pat="; COOKIE: ................................ (good)"
|
||||
#UDP
|
||||
$DIG $DIGOPTS @10.53.0.9 +notcp tld > dig.out.test$n.1
|
||||
grep "status: NOERROR" dig.out.test$n.1 > /dev/null || ret=1
|
||||
grep "$pat" dig.out.test$n.1 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.1 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.1 > /dev/null && ret=1
|
||||
grep ";; TSIG PSEUDOSECTION:" dig.out.test$n.1 > /dev/null && ret=1
|
||||
|
||||
$DIG $DIGOPTS @10.53.0.9 +notcp tcponly.tld > dig.out.test$n.2
|
||||
grep "status: NOERROR" dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep "; COOKIE:" dig.out.test$n.2 > /dev/null && ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep ";; TSIG PSEUDOSECTION:" dig.out.test$n.1 > /dev/null && ret=1
|
||||
|
||||
$DIG $DIGOPTS @10.53.0.9 +notcp nocookie.tld > dig.out.test$n.3
|
||||
grep "status: NOERROR" dig.out.test$n.3 > /dev/null || ret=1
|
||||
grep "; COOKIE:" dig.out.test$n.3 > /dev/null && ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.3 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.3 > /dev/null || ret=1
|
||||
grep ";; TSIG PSEUDOSECTION:" dig.out.test$n.1 > /dev/null && ret=1
|
||||
|
||||
$DIG $DIGOPTS @10.53.0.9 +notcp withtsig.tld > dig.out.test$n.4
|
||||
grep "status: NOERROR" dig.out.test$n.4 > /dev/null || ret=1
|
||||
grep "; COOKIE:" dig.out.test$n.4 > /dev/null && ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.4 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.4 > /dev/null || ret=1
|
||||
grep ";; TSIG PSEUDOSECTION:" dig.out.test$n.4 > /dev/null || ret=1
|
||||
|
||||
#TCP
|
||||
$DIG $DIGOPTS @10.53.0.9 +tcp tld > dig.out.test$n.5
|
||||
grep "status: NOERROR" dig.out.test$n.5 > /dev/null || ret=1
|
||||
grep "$pat" dig.out.test$n.5 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.5 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.5 > /dev/null && ret=1
|
||||
grep ";; TSIG PSEUDOSECTION:" dig.out.test$n.1 > /dev/null && ret=1
|
||||
|
||||
$DIG $DIGOPTS @10.53.0.9 +tcp tcponly.tld > dig.out.test$n.6
|
||||
grep "status: NOERROR" dig.out.test$n.6 > /dev/null || ret=1
|
||||
grep "$pat" dig.out.test$n.6 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.6 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.6 > /dev/null && ret=1
|
||||
grep ";; TSIG PSEUDOSECTION:" dig.out.test$n.1 > /dev/null && ret=1
|
||||
|
||||
$DIG $DIGOPTS @10.53.0.9 +tcp nocookie.tld > dig.out.test$n.7
|
||||
grep "status: NOERROR" dig.out.test$n.7 > /dev/null || ret=1
|
||||
grep "; COOKIE:" dig.out.test$n.7 > /dev/null && ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.7 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.7 > /dev/null && ret=1
|
||||
grep ";; TSIG PSEUDOSECTION:" dig.out.test$n.1 > /dev/null && ret=1
|
||||
|
||||
$DIG $DIGOPTS @10.53.0.9 +tcp withtsig.tld > dig.out.test$n.8
|
||||
grep "status: NOERROR" dig.out.test$n.8 > /dev/null || ret=1
|
||||
grep "$pat" dig.out.test$n.8 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.8 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.8 > /dev/null && ret=1
|
||||
grep ";; TSIG PSEUDOSECTION:" dig.out.test$n.8 > /dev/null && ret=1
|
||||
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=`expr $status + $ret`
|
||||
|
||||
n=`expr $n + 1`
|
||||
echo_i "check that spoofed response is dropped when we have a server cookie ($n)"
|
||||
ret=0
|
||||
msg="missing expected cookie from"
|
||||
pat='10\.53\.0\.9 .*\[cookie=................................\] \[ttl'
|
||||
# prime EDNS COOKIE state
|
||||
$DIG $DIGOPTS @10.53.0.1 tld > dig.out.test$n.1
|
||||
grep "status: NOERROR" dig.out.test$n.1 > /dev/null || ret=1
|
||||
rndc_dumpdb ns1
|
||||
grep "$pat" ns1/named_dump.db.test$n > /dev/null || ret=1
|
||||
# spoofed response contains 10.53.0.10
|
||||
nextpart ns1/named.run >/dev/null
|
||||
$DIG $DIGOPTS @10.53.0.1 tcponly.tld > dig.out.test$n.2
|
||||
wait_for_log 5 "$msg" ns1/named.run || ret=1
|
||||
grep "status: NOERROR" dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.2 > /dev/null && ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=`expr $status + $ret`
|
||||
|
||||
n=`expr $n + 1`
|
||||
echo_i "check that gracefully handle server disabling DNS COOKIE we have a server cookie ($n)"
|
||||
ret=0
|
||||
msg="missing expected cookie from"
|
||||
pat='10\.53\.0\.9 .*\[cookie=................................\] \[ttl'
|
||||
# prime EDNS COOKIE state
|
||||
$DIG $DIGOPTS @10.53.0.1 tld > dig.out.test$n.1
|
||||
grep "status: NOERROR" dig.out.test$n.1 > /dev/null || ret=1
|
||||
rndc_dumpdb ns1
|
||||
grep "$pat" ns1/named_dump.db.test$n > /dev/null || ret=1
|
||||
# check the disabled server response
|
||||
nextpart ns1/named.run >/dev/null
|
||||
$DIG $DIGOPTS @10.53.0.1 nocookie.tld > dig.out.test$n.2
|
||||
wait_for_log 5 "$msg" ns1/named.run || ret=1
|
||||
grep "status: NOERROR" dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.2 > /dev/null && ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=`expr $status + $ret`
|
||||
|
||||
n=`expr $n + 1`
|
||||
echo_i "check that spoofed response with a TSIG is dropped when we have a server cookie ($n)"
|
||||
ret=0
|
||||
pat='10\.53\.0\.9 .*\[cookie=................................\] \[ttl'
|
||||
# prime EDNS COOKIE state
|
||||
$DIG $DIGOPTS @10.53.0.1 tld > dig.out.test$n.1
|
||||
grep "status: NOERROR" dig.out.test$n.1 > /dev/null || ret=1
|
||||
rndc_dumpdb ns1
|
||||
grep "$pat" ns1/named_dump.db.test$n > /dev/null || ret=1
|
||||
# spoofed response contains 10.53.0.10
|
||||
nextpart ns1/named.run >/dev/null
|
||||
$DIG $DIGOPTS @10.53.0.1 withtsig.tld > dig.out.test$n.2
|
||||
grep "status: NOERROR" dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.2 > /dev/null && ret=1
|
||||
nextpart ns1/named.run > named.run.test$n
|
||||
count=$(grep -c ') [0-9][0-9]* NOERROR 0' named.run.test$n)
|
||||
test $count -eq 1 || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=`expr $status + $ret`
|
||||
|
||||
if $PYTHON -c '
|
||||
import dns.version, sys;
|
||||
if dns.version.MAJOR > 1: sys.exit(0);
|
||||
if dns.version.MAJOR == 1 and dns.version.MINOR >= 16: sys.exit(0);
|
||||
sys.exit(1)'
|
||||
then
|
||||
n=`expr $n + 1`
|
||||
echo_i "check that TSIG test server is correctly configured ($n)"
|
||||
ret=0
|
||||
pat="; COOKIE: ................................ (good)"
|
||||
key=hmac-sha256:foo:aaaaaaaaaaaa
|
||||
#UDP
|
||||
$DIG $DIGOPTS @10.53.0.10 -y $key +notcp tsig. > dig.out.test$n.1
|
||||
grep "status: NOERROR" dig.out.test$n.1 > /dev/null || ret=1
|
||||
grep "$pat" dig.out.test$n.1 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.1 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.1 > /dev/null && ret=1
|
||||
grep 'TSIG.*NOERROR' dig.out.test$n.1 > /dev/null || ret=1
|
||||
|
||||
$DIG $DIGOPTS @10.53.0.10 -y $key +notcp tcponly.tsig > dig.out.test$n.2
|
||||
grep "status: NOERROR" dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep "; COOKIE:" dig.out.test$n.2 > /dev/null && ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep 'TSIG.*NOERROR' dig.out.test$n.1 > /dev/null || ret=1
|
||||
|
||||
$DIG $DIGOPTS @10.53.0.10 -y $key +notcp nocookie.tsig > dig.out.test$n.3
|
||||
grep "status: NOERROR" dig.out.test$n.3 > /dev/null || ret=1
|
||||
grep "; COOKIE:" dig.out.test$n.3 > /dev/null && ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.3 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.3 > /dev/null || ret=1
|
||||
grep 'TSIG.*NOERROR' dig.out.test$n.1 > /dev/null || ret=1
|
||||
|
||||
#TCP
|
||||
$DIG $DIGOPTS @10.53.0.10 -y $key +tcp tsig. > dig.out.test$n.5
|
||||
grep "status: NOERROR" dig.out.test$n.5 > /dev/null || ret=1
|
||||
grep "$pat" dig.out.test$n.5 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.5 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.5 > /dev/null && ret=1
|
||||
grep 'TSIG.*NOERROR' dig.out.test$n.1 > /dev/null || ret=1
|
||||
|
||||
$DIG $DIGOPTS @10.53.0.10 -y $key +tcp tcponly.tsig > dig.out.test$n.6
|
||||
grep "status: NOERROR" dig.out.test$n.6 > /dev/null || ret=1
|
||||
grep "$pat" dig.out.test$n.6 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.6 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.6 > /dev/null && ret=1
|
||||
grep 'TSIG.*NOERROR' dig.out.test$n.1 > /dev/null || ret=1
|
||||
|
||||
$DIG $DIGOPTS @10.53.0.10 -y $key +tcp nocookie.tsig > dig.out.test$n.7
|
||||
grep "status: NOERROR" dig.out.test$n.7 > /dev/null || ret=1
|
||||
grep "; COOKIE:" dig.out.test$n.7 > /dev/null && ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.7 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.7 > /dev/null && ret=1
|
||||
grep 'TSIG.*NOERROR' dig.out.test$n.1 > /dev/null || ret=1
|
||||
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=`expr $status + $ret`
|
||||
|
||||
n=`expr $n + 1`
|
||||
echo_i "check that missing COOKIE with a valid TSIG signed response does not trigger TCP fallback ($n)"
|
||||
ret=0
|
||||
pat='10\.53\.0\.10 .*\[cookie=................................\] \[ttl'
|
||||
# prime EDNS COOKIE state
|
||||
$DIG $DIGOPTS @10.53.0.1 tsig. > dig.out.test$n.1
|
||||
grep "status: NOERROR" dig.out.test$n.1 > /dev/null || ret=1
|
||||
rndc_dumpdb ns1
|
||||
grep "$pat" ns1/named_dump.db.test$n > /dev/null || ret=1
|
||||
# check the disabled server response
|
||||
nextpart ns1/named.run >/dev/null
|
||||
$DIG $DIGOPTS @10.53.0.1 nocookie.tsig > dig.out.test$n.2
|
||||
grep "status: NOERROR" dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.2 > /dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.2 > /dev/null || ret=1
|
||||
nextpart ns1/named.run > named.run.test$n
|
||||
count=$(grep -c ') [0-9][0-9]* NOERROR 0' named.run.test$n)
|
||||
test $count -eq 2 || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=`expr $status + $ret`
|
||||
fi
|
||||
|
||||
echo_i "exit status: $status"
|
||||
[ $status -eq 0 ] || exit 1
|
||||
|
|
|
|||
|
|
@ -46,3 +46,6 @@ Bug Fixes
|
|||
|
||||
- The synthesised CNAME from a DNAME was incorrectly followed when the QTYPE
|
||||
was CNAME or ANY. [GL #2280]
|
||||
|
||||
- Tighten handling of missing DNS COOKIE responses over UDP by
|
||||
falling back to TCP. [GL #2275]
|
||||
|
|
|
|||
|
|
@ -197,6 +197,10 @@ fromwire_opt(ARGS_FROMWIRE) {
|
|||
isc_region_consume(&sregion, length);
|
||||
break;
|
||||
case DNS_OPT_COOKIE:
|
||||
/*
|
||||
* Client cookie alone has length 8.
|
||||
* Client + server cookie is 8 + [8..32].
|
||||
*/
|
||||
if (length != 8 && (length < 16 || length > 40)) {
|
||||
return (DNS_R_OPTERR);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2358,7 +2358,12 @@ add_serveraddr(uint8_t *buf, const size_t bufsize, const resquery_t *query) {
|
|||
return (addr2buf(buf, bufsize, &query->addrinfo->sockaddr));
|
||||
}
|
||||
|
||||
/*
|
||||
* Client cookie is 8 octets.
|
||||
* Server cookie is [8..32] octets.
|
||||
*/
|
||||
#define CLIENT_COOKIE_SIZE 8U
|
||||
#define COOKIE_BUFFER_SIZE (8U + 32U)
|
||||
|
||||
static void
|
||||
compute_cc(const resquery_t *query, uint8_t *cookie, const size_t len) {
|
||||
|
|
@ -2603,7 +2608,7 @@ resquery_send(resquery_t *query) {
|
|||
bool reqnsid = res->view->requestnsid;
|
||||
bool sendcookie = res->view->sendcookie;
|
||||
bool tcpkeepalive = false;
|
||||
unsigned char cookie[64];
|
||||
unsigned char cookie[COOKIE_BUFFER_SIZE];
|
||||
uint16_t padding = 0;
|
||||
|
||||
/*
|
||||
|
|
@ -2675,9 +2680,11 @@ resquery_send(resquery_t *query) {
|
|||
fctx->res,
|
||||
dns_resstatscounter_cookieout);
|
||||
} else {
|
||||
compute_cc(query, cookie, 8);
|
||||
compute_cc(query, cookie,
|
||||
CLIENT_COOKIE_SIZE);
|
||||
ednsopts[ednsopt].value = cookie;
|
||||
ednsopts[ednsopt].length = 8;
|
||||
ednsopts[ednsopt].length =
|
||||
CLIENT_COOKIE_SIZE;
|
||||
inc_stats(
|
||||
fctx->res,
|
||||
dns_resstatscounter_cookienew);
|
||||
|
|
@ -7627,6 +7634,10 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
|
|||
result = dns_message_checksig(query->rmessage, fctx->res->view);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
FCTXTRACE3("signature check failed", result);
|
||||
if (result == DNS_R_UNEXPECTEDTSIG ||
|
||||
result == DNS_R_EXPECTEDTSIG) {
|
||||
rctx.nextitem = true;
|
||||
}
|
||||
rctx_done(&rctx, result);
|
||||
return;
|
||||
}
|
||||
|
|
@ -7643,6 +7654,40 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
|
|||
* ensured by the dispatch code).
|
||||
*/
|
||||
|
||||
/*
|
||||
* If we have had a server cookie and don't get one retry over 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)
|
||||
{
|
||||
unsigned char cookie[COOKIE_BUFFER_SIZE];
|
||||
if (dns_adb_getcookie(fctx->adb, query->addrinfo, cookie,
|
||||
sizeof(cookie)) > CLIENT_COOKIE_SIZE)
|
||||
{
|
||||
if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) {
|
||||
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
||||
isc_sockaddr_format(&query->addrinfo->sockaddr,
|
||||
addrbuf, sizeof(addrbuf));
|
||||
isc_log_write(
|
||||
dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
||||
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
|
||||
"missing expected cookie from %s",
|
||||
addrbuf);
|
||||
}
|
||||
rctx.retryopts |= DNS_FETCHOPT_TCP;
|
||||
rctx.resend = true;
|
||||
rctx_done(&rctx, result);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* XXXMPA When support for DNS COOKIE becomes ubiquitous, fall
|
||||
* back to TCP for all non-COOKIE responses.
|
||||
*/
|
||||
}
|
||||
|
||||
rctx_edns(&rctx);
|
||||
|
||||
/*
|
||||
|
|
@ -8058,7 +8103,7 @@ rctx_opt(respctx_t *rctx) {
|
|||
uint16_t optlen;
|
||||
unsigned char *optvalue;
|
||||
dns_adbaddrinfo_t *addrinfo;
|
||||
unsigned char cookie[8];
|
||||
unsigned char cookie[CLIENT_COOKIE_SIZE];
|
||||
bool seen_cookie = false;
|
||||
bool seen_nsid = false;
|
||||
|
||||
|
|
@ -8095,8 +8140,10 @@ rctx_opt(respctx_t *rctx) {
|
|||
compute_cc(query, cookie, sizeof(cookie));
|
||||
INSIST(query->rmessage->cc_bad == 0 &&
|
||||
query->rmessage->cc_ok == 0);
|
||||
if (optlen >= 8U &&
|
||||
memcmp(cookie, optvalue, 8) == 0) {
|
||||
if (optlen >= CLIENT_COOKIE_SIZE &&
|
||||
memcmp(cookie, optvalue,
|
||||
CLIENT_COOKIE_SIZE) == 0)
|
||||
{
|
||||
query->rmessage->cc_ok = 1;
|
||||
inc_stats(fctx->res,
|
||||
dns_resstatscounter_cookieok);
|
||||
|
|
|
|||
|
|
@ -287,6 +287,7 @@
|
|||
./bin/tests/system/conf.sh.common SH 2018,2019,2020
|
||||
./bin/tests/system/conf.sh.in SH 2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020
|
||||
./bin/tests/system/conf.sh.win32 SH 2016,2017,2018,2019,2020
|
||||
./bin/tests/system/cookie/ans9/ans.py PYTHON 2020
|
||||
./bin/tests/system/cookie/clean.sh SH 2014,2015,2016,2018,2019,2020
|
||||
./bin/tests/system/cookie/setup.sh SH 2018,2019,2020
|
||||
./bin/tests/system/cookie/tests.sh SH 2014,2015,2016,2017,2018,2019,2020
|
||||
|
|
|
|||
Loading…
Reference in a new issue