From: Nicki Křížek Date: Fri, 28 Nov 2025 15:41:44 +0000 (+0100) Subject: Replace digdelv/ans8 with AsyncDnsServer X-Git-Tag: v9.21.18~32^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0b7a089c7f575e6005fbcf84c8faf4dd439d521a;p=thirdparty%2Fbind9.git 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. --- diff --git a/bin/tests/system/digdelv/ans8/ans.py b/bin/tests/system/digdelv/ans8/ans.py index 3e18edc1cc6..d959b597e23 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 - - -def ctrl_channel(msg): - global modes, mode, n - - 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)) - - -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) - - 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(SERVFAIL) - return r - - -def sigterm(signum, frame): - print("Shutting down now...") - os.remove("ans.pid") - running = False - sys.exit(0) - - -ip4 = "10.53.0.8" - -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 +from isctest.asyncserver import ( + AsyncDnsServer, + CloseConnection, + DnsResponseSend, + DomainHandler, + IgnoreAllQueries, + QueryContext, + ResponseAction, + ResponseDrop, +) + + +class SilentHandler(DomainHandler, IgnoreAllQueries): + """Handler that doesn't respond.""" + + domains = ["silent.example"] + + +class CloseHandler(DomainHandler): + """Handler that doesn't respond and closes TCP connection.""" + + domains = ["close.example"] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[ResponseAction, None]: + yield CloseConnection() + + +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 + + +def main() -> None: + server = AsyncDnsServer() + server.install_response_handlers( + [ + CloseHandler(), + SilentHandler(), + SilentThenServfailHandler(), + ] + ) + server.run() + + +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 7e73d3e97a0..10b1045deee 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 eb753349ca8..c07ed392c09 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