import signal
import socket
import select
+import struct
from datetime import datetime, timedelta
import time
import functools
with open("qlog", "a") as f:
f.write("%s %s\n", type, qname)
+# Create a UDP listener
+def udp_listen(ip, port, is_ipv6 = False):
+ try:
+ udp = socket.socket(socket.AF_INET6 if is_ipv6 else socket.AF_INET,
+ socket.SOCK_DGRAM)
+ try:
+ udp.bind((ip, port))
+ except:
+ udp.close()
+ udp = None
+ except:
+ udp = None
+
+ if udp is None and not is_ipv6:
+ raise socket.error("Can not create an IPv4 UDP listener")
+
+ return udp
+
+# Create a TCP listener
+def tcp_listen(ip, port, is_ipv6 = False):
+ try:
+ tcp = socket.socket(socket.AF_INET6 if is_ipv6 else socket.AF_INET,
+ socket.SOCK_STREAM)
+ try:
+ tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ tcp.bind((ip, port))
+ tcp.listen(100)
+ except:
+ tcp.close()
+ tcp = None
+ except:
+ tcp = None
+
+ if tcp is None and not is_ipv6:
+ raise socket.error("Can not create an IPv4 TCP listener")
+
+ return tcp
+
+############################################################################
+# Control channel - send "1" or "0" to enable or disable the "silent" mode.
+############################################################################
+silent = False
+def ctrl_channel(msg):
+ global silent
+
+ msg = msg.splitlines().pop(0)
+ print("Received control message: %s" % msg)
+
+ if len(msg) != 1:
+ return
+
+ if silent:
+ if msg == b'0':
+ silent = False
+ print("Silent mode was disabled")
+ else:
+ if msg == b'1':
+ silent = True
+ print("Silent mode was enabled")
+
############################################################################
# Respond to a DNS query.
############################################################################
try: port=int(os.environ['PORT'])
except: port=5300
-query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-query4_socket.bind((ip4, port))
-havev6 = True
-try:
- query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
- try:
- query6_socket.bind((ip6, port))
- except:
- query6_socket.close()
- havev6 = False
-except:
- havev6 = False
+try: ctrlport=int(os.environ['EXTRAPORT1'])
+except: ctrlport=5300
+
+ctrl4_tcp = tcp_listen(ip4, ctrlport)
+query4_udp = udp_listen(ip4, port)
+query6_udp = udp_listen(ip6, port, is_ipv6=True)
+query4_tcp = tcp_listen(ip4, port)
+query6_tcp = tcp_listen(ip6, port, is_ipv6=True)
+
+havev6 = query6_udp is not None and query6_tcp is not None
+
signal.signal(signal.SIGTERM, sigterm)
f = open('ans.pid', 'w')
running = True
+print ("Listening on %s port %d" % (ip4, ctrlport))
print ("Listening on %s port %d" % (ip4, port))
if havev6:
print ("Listening on %s port %d" % (ip6, port))
+
print ("Ctrl-c to quit")
if havev6:
- input = [query4_socket, query6_socket]
+ input = [ctrl4_tcp, query4_udp, query6_udp, query4_tcp, query6_tcp]
else:
- input = [query4_socket]
+ input = [ctrl4_tcp, query4_udp, query4_tcp]
+
+hung_conns = []
while running:
try:
break
for s in inputready:
- if s == query4_socket or s == query6_socket:
- print ("Query received on %s" %
- (ip4 if s == query4_socket else ip6), end=" ")
+ if s == ctrl4_tcp:
+ print("Control channel connected")
+ conn = None
+ try:
+ # Handle control channel input
+ conn, addr = s.accept()
+ msg = conn.recv(1)
+ if msg:
+ ctrl_channel(msg)
+ conn.close()
+ except s.timeout:
+ pass
+ if conn:
+ conn.close()
+ elif s == query4_tcp or s == query6_tcp:
+ print("TCP query received on %s" %
+ (ip4 if s == query4_tcp else ip6), end=" ")
+ conn = None
+ try:
+ # Handle incoming queries
+ conn, addr = s.accept()
+ if not silent:
+ # 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_response(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:
+ # Do not respond and hang the connection.
+ print("NO RESPONSE (silent mode)")
+ hung_conns.append(conn)
+ continue
+ except socket.error as e:
+ print("NO RESPONSE (error: %s)" % str(e))
+ if conn:
+ conn.close()
+ elif s == query4_udp or s == query6_udp:
+ print("UDP query received on %s" %
+ (ip4 if s == query4_udp else ip6), end=" ")
# Handle incoming queries
msg = s.recvfrom(65535)
- rsp = create_response(msg[0])
- if rsp:
- print(dns.rcode.to_text(rsp.rcode()))
- s.sendto(rsp.to_wire(), msg[1])
+ if not silent:
+ rsp = create_response(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")
+ # Do not respond.
+ print("NO RESPONSE (silent mode)")
if not running:
break
)
sendcmd() (
- send 10.53.0.6 "$EXTRAPORT1"
+ send "$1" "$EXTRAPORT1"
)
rndccmd() {
echo_i "checking that a forwarder timeout prevents it from being reused in the same fetch context ($n)"
ret=0
# Make ans6 receive queries without responding to them.
-echo "//" | sendcmd
+echo "//" | sendcmd 10.53.0.6
# Query for a record in a zone which is forwarded to a non-responding forwarder
# and is delegated from the root to check whether the forwarder will be retried
# when a delegation is encountered after falling back to full recursive
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
-n=$((n+1))
-echo_i "checking switch from forwarding to normal resolution while chasing DS ($n)"
-ret=0
-copy_setports ns3/named2.conf.in ns3/named.conf
-rndccmd 10.53.0.3 reconfig 2>&1 | sed 's/^/ns3 /' | cat_i
-sleep 1
-sendcmd << EOF
+# Prepare ans6 for the chasing DS tests.
+sendcmd 10.53.0.6 << EOF
/ns1.sld.tld/A/
300 A 10.53.0.2
/sld.tld/NS/
300 NS ns1.sld.tld.
/sld.tld/
EOF
+
+n=$((n+1))
+echo_i "checking switch from forwarding to normal resolution while chasing DS ($n)"
+ret=0
+copy_setports ns3/named2.conf.in ns3/named.conf
+rndccmd 10.53.0.3 reconfig 2>&1 | sed 's/^/ns3 /' | cat_i
+sleep 1
+nextpart ns3/named.run >/dev/null
+dig_with_opts @$f1 xxx.yyy.sld.tld ds > dig.out.$n.f1 || ret=1
+grep "status: SERVFAIL" dig.out.$n.f1 > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# See [GL #3129].
+# Enable silent mode for ans11.
+echo "1" | sendcmd 10.53.0.11
+n=$((n+1))
+echo_i "checking the handling of hung DS fetch while chasing DS ($n)"
+ret=0
+copy_setports ns3/named2.conf.in ns3/tmp
+sed 's/root.db/root2.db/' ns3/tmp > ns3/named.conf
+rm -f ns3/tmp
+rndccmd 10.53.0.3 reconfig 2>&1 | sed 's/^/ns3 /' | cat_i
+rndccmd 10.53.0.3 flush 2>&1 | sed 's/^/ns3 /' | cat_i
+sleep 1
nextpart ns3/named.run >/dev/null
dig_with_opts @$f1 xxx.yyy.sld.tld ds > dig.out.$n.f1 || ret=1
grep "status: SERVFAIL" dig.out.$n.f1 > /dev/null || ret=1
+# Disable silent mode for ans11.
+echo "0" | sendcmd 10.53.0.11
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))