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
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.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")
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:
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()
# 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
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
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 +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 -d @10.53.0.4 @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 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))