From 0b7a089c7f575e6005fbcf84c8faf4dd439d521a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Fri, 28 Nov 2025 16:41:44 +0100 Subject: [PATCH] Replace digdelv/ans8 with AsyncDnsServer Previously, the ans8 server had different response modes that applied to all queries. Replace it with AsyncDnsServer that has serves the different response modes under different domains without the need to change the server behaviour at runtime. Add the new queries that require an ns3 fallback to the ns3/example.db zone. --- bin/tests/system/digdelv/ans8/ans.py | 226 +++++---------------- bin/tests/system/digdelv/ns2/example.db.in | 3 + bin/tests/system/digdelv/tests.sh | 22 +- 3 files changed, 59 insertions(+), 192 deletions(-) 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