# 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()
# 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))
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))
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))
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