2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-30 14:07:59 +00:00

Add various dig/host tests for TCP/UDP socket error handling cases

Rework the "ans8" server in the "digdelv" system test to support various
modes of operations using a control channel.

The supported modes are:

1. `silent` (do not respond)
2. `close` (UDP: same as `silent`; TCP: also close the connection)
3. `servfail` (always respond with `SERVFAIL`)
4. `unstable` (constantly switch between `silent` and `servfail`)

Add multiple tests to check the handling of both TCP and UDP socket
error scenarios in dig/host.
This commit is contained in:
Aram Sargsyan
2022-03-13 13:47:44 +00:00
parent 0fb4fc1897
commit 03697f1bcc
2 changed files with 123 additions and 32 deletions

View File

@@ -20,7 +20,27 @@ import struct
import dns, dns.message
from dns.rcode import *
def create_response(msg):
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
@@ -45,6 +65,9 @@ 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))
@@ -53,6 +76,11 @@ 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")
@@ -63,12 +91,11 @@ 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]
input = [query4_udp, query4_tcp, ctrl4_tcp]
n_udp = 0
n_tcp = 0
hung_conns = []
while running:
@@ -83,54 +110,79 @@ while running:
for s in inputready:
if s == query4_udp:
n = n + 1
print("UDP query received on %s" % ip4, end=" ")
n_udp = n_udp + 1
msg = s.recvfrom(65535)
# Do not response to every other query.
if n_udp % 2 == 1:
print("NO RESPONSE")
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
rsp = create_response(msg[0])
if rsp:
print(dns.rcode.to_text(rsp.rcode()))
s.sendto(rsp.to_wire(), msg[1])
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:
print("NO RESPONSE")
raise(Exception("unsupported mode: %s" % mode))
elif s == query4_tcp:
n = n + 1
print("TCP query received on %s" % ip4, end=" ")
n_tcp = n_tcp + 1
conn = None
try:
conn, addr = s.accept()
# Do not response to every other query, hang the connection.
if n_tcp % 2 == 1:
print("NO RESPONSE")
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)
conn = None
continue
else:
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")
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")
print("NO RESPONSE (can not read the message)")
conn.close()
continue
rsp = create_response(msg)
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")
except:
print("NO RESPONSE")
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()

View File

@@ -1001,6 +1001,8 @@ 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
grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
@@ -1009,6 +1011,8 @@ if [ -x "$DIG" ] ; then
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
grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
@@ -1016,19 +1020,54 @@ if [ -x "$DIG" ] ; then
status=$((status+ret))
n=$((n+1))
echo_i "check that dig tries the next server after a connection error ($n)"
echo_i "check that dig tries the next server after a UDP socket read error ($n)"
ret=0
dig_with_opts -d @10.53.0.99 @10.53.0.3 a.example > dig.out.test$n 2>&1 || ret=1
dig_with_opts @10.53.0.99 @10.53.0.3 a.example > dig.out.test$n 2>&1 || ret=1
grep "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 timeouts ($n)"
# Ask ans4 to not respond to queries
echo "//" | sendcmd 10.53.0.4
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 -d @10.53.0.4 @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 a.example > dig.out.test$n 2>&1 || ret=1
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# Note that we combine TCP socket "connection error" and "timeout" cases in
# one, because it is not trivial to simulate the timeout case in a system test
# in Linux without a firewall, but the code which handles error cases during
# the connection establishment time does not differentiate between timeout and
# other types of errors (unlike during reading), so this one check should be
# sufficient for both cases.
n=$((n+1))
echo_i "check that dig tries the next server after a TCP socket connection error/timeout ($n)"
ret=0
dig_with_opts +tcp @10.53.0.99 @10.53.0.3 a.example > dig.out.test$n 2>&1 || ret=1
test $(grep "connection refused\|timed out" dig.out.test$n | wc -l) -eq 3 || ret=1
grep "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
grep "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
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status+ret))