mirror of
https://github.com/isc-projects/bind9.git
synced 2026-06-10 22:39:58 -04:00
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.
This commit is contained in:
parent
575f0e3916
commit
0b7a089c7f
3 changed files with 59 additions and 192 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue