diff --git a/bin/tests/system/digdelv/ans8/ans.py b/bin/tests/system/digdelv/ans8/ans.py index 3e18edc1cc..d959b597e2 100644 --- a/bin/tests/system/digdelv/ans8/ans.py +++ b/bin/tests/system/digdelv/ans8/ans.py @@ -9,194 +9,68 @@ # 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 -import struct +from typing import AsyncGenerator -import dns, dns.message -from dns.rcode import * +import dns +import dns.rcode -modes = [ - b"silent", # Do not respond - b"close", # UDP: same as silent; TCP: also close the connection - b"servfail", # Always respond with SERVFAIL - b"unstable", # Constantly switch between "silent" and "servfail" -] -mode = modes[0] -n = 0 +from isctest.asyncserver import ( + AsyncDnsServer, + CloseConnection, + DnsResponseSend, + DomainHandler, + IgnoreAllQueries, + QueryContext, + ResponseAction, + ResponseDrop, +) -def ctrl_channel(msg): - global modes, mode, n +class SilentHandler(DomainHandler, IgnoreAllQueries): + """Handler that doesn't respond.""" - msg = msg.splitlines().pop(0) - print("Received control message: %s" % msg) - - if msg in modes: - mode = msg - n = 0 - print("New mode: %s" % str(mode)) + domains = ["silent.example"] -def create_servfail(msg): - m = dns.message.from_wire(msg) - qname = m.question[0].name.to_text() - rrtype = m.question[0].rdtype - typename = dns.rdatatype.to_text(rrtype) +class CloseHandler(DomainHandler): + """Handler that doesn't respond and closes TCP connection.""" - with open("query.log", "a") as f: - f.write("%s %s\n" % (typename, qname)) - print("%s %s" % (typename, qname), end=" ") + domains = ["close.example"] - r = dns.message.make_response(m) - r.set_rcode(SERVFAIL) - return r + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[ResponseAction, None]: + yield CloseConnection() -def sigterm(signum, frame): - print("Shutting down now...") - os.remove("ans.pid") - running = False - sys.exit(0) +class SilentThenServfailHandler(DomainHandler): + """Handler that drops one query and response to the next one with SERVFAIL.""" + + domains = ["silent-then-servfail.example"] + counter = 0 + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[ResponseAction, None]: + if self.counter % 2 == 0: + yield ResponseDrop() + else: + qctx.response.set_rcode(dns.rcode.SERVFAIL) + yield DnsResponseSend(qctx.response, authoritative=False) + self.counter += 1 -ip4 = "10.53.0.8" +def main() -> None: + server = AsyncDnsServer() + server.install_response_handlers( + [ + CloseHandler(), + SilentHandler(), + SilentThenServfailHandler(), + ] + ) + server.run() -try: - port = int(os.environ["PORT"]) -except: - port = 5300 -try: - ctrlport = int(os.environ["EXTRAPORT1"]) -except: - ctrlport = 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.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -query4_tcp.bind((ip4, port)) -query4_tcp.listen(100) - -ctrl4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -ctrl4_tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -ctrl4_tcp.bind((ip4, ctrlport)) -ctrl4_tcp.listen(100) - -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)) -print("Listening on %s port %d" % (ip4, ctrlport)) -print("Ctrl-c to quit") - -input = [query4_udp, query4_tcp, ctrl4_tcp] - -hung_conns = [] - -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: - n = n + 1 - print("UDP query received on %s" % ip4, end=" ") - msg = s.recvfrom(65535) - if ( - mode == b"silent" - or mode == b"close" - or (mode == b"unstable" and n % 2 == 1) - ): - # Do not respond. - print("NO RESPONSE (%s)" % str(mode)) - continue - elif mode == b"servfail" or (mode == b"unstable" and n % 2 == 0): - rsp = create_servfail(msg[0]) - if rsp: - print(dns.rcode.to_text(rsp.rcode())) - s.sendto(rsp.to_wire(), msg[1]) - else: - print("NO RESPONSE (can not create a response)") - else: - raise (Exception("unsupported mode: %s" % mode)) - elif s == query4_tcp: - n = n + 1 - print("TCP query received on %s" % ip4, end=" ") - conn = None - try: - if mode == b"silent" or (mode == b"unstable" and n % 2 == 1): - conn, addr = s.accept() - # Do not respond and hang the connection. - print("NO RESPONSE (%s)" % str(mode)) - hung_conns.append(conn) - continue - elif mode == b"close": - conn, addr = s.accept() - # Do not respond and close the connection. - print("NO RESPONSE (%s)" % str(mode)) - conn.close() - continue - elif mode == b"servfail" or (mode == b"unstable" and n % 2 == 0): - conn, addr = s.accept() - # get TCP message length - msg = conn.recv(2) - if len(msg) != 2: - print("NO RESPONSE (can not read the message length)") - conn.close() - continue - length = struct.unpack(">H", msg[:2])[0] - msg = conn.recv(length) - if len(msg) != length: - print("NO RESPONSE (can not read the message)") - conn.close() - continue - rsp = create_servfail(msg) - if rsp: - print(dns.rcode.to_text(rsp.rcode())) - wire = rsp.to_wire() - conn.send(struct.pack(">H", len(wire))) - conn.send(wire) - else: - print("NO RESPONSE (can not create a response)") - else: - raise (Exception("unsupported mode: %s" % mode)) - except socket.error as e: - print("NO RESPONSE (error: %s)" % str(e)) - if conn: - conn.close() - elif s == ctrl4_tcp: - print("Control channel connected") - conn = None - try: - # Handle control channel input - conn, addr = s.accept() - msg = conn.recv(1024) - if msg: - ctrl_channel(msg) - conn.close() - except s.timeout: - pass - if conn: - conn.close() - - if not running: - break +if __name__ == "__main__": + main() diff --git a/bin/tests/system/digdelv/ns2/example.db.in b/bin/tests/system/digdelv/ns2/example.db.in index 7e73d3e97a..10b1045dee 100644 --- a/bin/tests/system/digdelv/ns2/example.db.in +++ b/bin/tests/system/digdelv/ns2/example.db.in @@ -33,6 +33,9 @@ c AAAA fd92:7065:b8e:ffff::3 d A 10.0.0.0 d AAAA fd92:7065:b8e:ffff:: +silent A 10.0.0.1 +close A 10.0.0.1 + xn--caf-dma A 10.1.2.3 foo TXT "testing" diff --git a/bin/tests/system/digdelv/tests.sh b/bin/tests/system/digdelv/tests.sh index eb753349ca..c07ed392c0 100644 --- a/bin/tests/system/digdelv/tests.sh +++ b/bin/tests/system/digdelv/tests.sh @@ -1275,20 +1275,16 @@ if [ -x "$DIG" ]; then # See [GL #3020] for more information n=$((n + 1)) echo_i "check that dig handles UDP timeout followed by a SERVFAIL correctly ($n)" - # Ask ans8 to be in "unstable" mode (switching between "silent" and "servfail" modes) - echo "unstable" | sendcmd 10.53.0.8 ret=0 - dig_with_opts +timeout=1 +nofail @10.53.0.8 a.example >dig.out.test$n 2>&1 || ret=1 + dig_with_opts +timeout=1 +nofail @10.53.0.8 silent-then-servfail.example >dig.out.test$n 2>&1 || ret=1 grep -F "status: SERVFAIL" dig.out.test$n >/dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) n=$((n + 1)) echo_i "check that dig handles TCP timeout followed by a SERVFAIL correctly ($n)" - # Ask ans8 to be in "unstable" mode (switching between "silent" and "servfail" modes) - echo "unstable" | sendcmd 10.53.0.8 ret=0 - dig_with_opts +timeout=1 +nofail +tcp @10.53.0.8 a.example >dig.out.test$n 2>&1 || ret=1 + dig_with_opts +timeout=1 +nofail +tcp @10.53.0.8 silent-then-servfail.example >dig.out.test$n 2>&1 || ret=1 grep -F "status: SERVFAIL" dig.out.test$n >/dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -1321,10 +1317,8 @@ if [ -x "$DIG" ]; then n=$((n + 1)) echo_i "check that dig tries the next server after a TCP socket read error ($n)" - # Ask ans8 to be in "close" mode, which closes the connection after accepting it - echo "close" | sendcmd 10.53.0.8 ret=0 - dig_with_opts +tcp @10.53.0.8 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1 + dig_with_opts +tcp @10.53.0.8 @10.53.0.3 close.example >dig.out.test$n 2>&1 || ret=1 grep -F "status: NOERROR" dig.out.test$n >/dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -1346,20 +1340,16 @@ if [ -x "$DIG" ]; then n=$((n + 1)) echo_i "check that dig tries the next server after UDP socket read timeouts ($n)" - # Ask ans8 to be in "silent" mode - echo "silent" | sendcmd 10.53.0.8 ret=0 - dig_with_opts +timeout=1 @10.53.0.8 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1 + dig_with_opts +timeout=1 @10.53.0.8 @10.53.0.3 silent.example >dig.out.test$n 2>&1 || ret=1 grep -F "status: NOERROR" dig.out.test$n >/dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) n=$((n + 1)) echo_i "check that dig tries the next server after TCP socket read timeouts ($n)" - # Ask ans8 to be in "silent" mode - echo "silent" | sendcmd 10.53.0.8 ret=0 - dig_with_opts +timeout=1 +tcp @10.53.0.8 @10.53.0.3 a.example >dig.out.test$n 2>&1 || ret=1 + dig_with_opts +timeout=1 +tcp @10.53.0.8 @10.53.0.3 silent.example >dig.out.test$n 2>&1 || ret=1 grep -F "status: NOERROR" dig.out.test$n >/dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -1368,7 +1358,7 @@ if [ -x "$DIG" ]; then n=$((n + 1)) echo_i "check that dig correctly refuses to use a server with a IPv4 mapped IPv6 address after failing with a regular IP address ($n)" ret=0 - dig_with_opts @10.53.0.8 @::ffff:10.53.0.8 a.example >dig.out.test$n 2>&1 || ret=1 + dig_with_opts @10.53.0.8 @::ffff:10.53.0.8 silent.example >dig.out.test$n 2>&1 || ret=1 grep -F ";; Skipping mapped address" dig.out.test$n >/dev/null || ret=1 grep -F ";; No acceptable nameservers" dig.out.test$n >/dev/null || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi