1############################################################################ 2# Copyright (C) Internet Systems Consortium, Inc. ("ISC") 3# 4# This Source Code Form is subject to the terms of the Mozilla Public 5# License, v. 2.0. If a copy of the MPL was not distributed with this 6# file, You can obtain one at http://mozilla.org/MPL/2.0/. 7# 8# See the COPYRIGHT file distributed with this work for additional 9# information regarding copyright ownership. 10############################################################################ 11 12from __future__ import print_function 13import os 14import sys 15import signal 16import socket 17import select 18from datetime import datetime, timedelta 19import time 20import functools 21 22import dns, dns.message, dns.query, dns.flags 23from dns.rdatatype import * 24from dns.rdataclass import * 25from dns.rcode import * 26from dns.name import * 27 28 29# Log query to file 30def logquery(type, qname): 31 with open("qlog", "a") as f: 32 f.write("%s %s\n", type, qname) 33 34############################################################################ 35# Respond to a DNS query. 36# For good. it serves: 37# zoop.boing.good. NS ns3.good. 38# icky.ptang.zoop.boing.good. NS a.bit.longer.ns.name.good. 39# it responds properly (with NODATA empty response) to non-empty terminals 40# 41# For slow. it works the same as for good., but each response is delayed by 400 miliseconds 42# 43# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals 44# 45# For ugly. it works the same as for good., but returns garbage to non-empty terminals 46############################################################################ 47def create_response(msg): 48 m = dns.message.from_wire(msg) 49 qname = m.question[0].name.to_text() 50 lqname = qname.lower() 51 labels = lqname.split('.') 52 53 # get qtype 54 rrtype = m.question[0].rdtype 55 typename = dns.rdatatype.to_text(rrtype) 56 if typename == "A" or typename == "AAAA": 57 typename = "ADDR" 58 bad = False 59 ugly = False 60 slow = False 61 62 # log this query 63 with open("query.log", "a") as f: 64 f.write("%s %s\n" % (typename, lqname)) 65 print("%s %s" % (typename, lqname), end=" ") 66 67 r = dns.message.make_response(m) 68 r.set_rcode(NOERROR) 69 70 if lqname.endswith("bad."): 71 bad = True 72 suffix = "bad." 73 lqname = lqname[:-4] 74 elif lqname.endswith("ugly."): 75 ugly = True 76 suffix = "ugly." 77 lqname = lqname[:-5] 78 elif lqname.endswith("good."): 79 suffix = "good." 80 lqname = lqname[:-5] 81 elif lqname.endswith("slow."): 82 slow = True 83 suffix = "slow." 84 lqname = lqname[:-5] 85 else: 86 r.set_rcode(REFUSED) 87 return r 88 89 # Good/bad differs only in how we treat non-empty terminals 90 if lqname == "zoop.boing." and rrtype == NS: 91 r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, NS, "ns3."+suffix)) 92 r.flags |= dns.flags.AA 93 elif lqname.endswith("icky.ptang.zoop.boing."): 94 r.authority.append(dns.rrset.from_text("icky.ptang.zoop.boing." + suffix, 1, IN, NS, "a.bit.longer.ns.name." + suffix)) 95 elif "icky.ptang.zoop.boing.".endswith(lqname): 96 r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1")) 97 if bad: 98 r.set_rcode(NXDOMAIN) 99 if ugly: 100 r.set_rcode(FORMERR) 101 elif lqname.endswith("zoop.boing."): 102 r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1")) 103 r.set_rcode(NXDOMAIN) 104 else: 105 r.set_rcode(REFUSED) 106 107 if slow: 108 time.sleep(0.4) 109 return r 110 111 112def sigterm(signum, frame): 113 print ("Shutting down now...") 114 os.remove('ans.pid') 115 running = False 116 sys.exit(0) 117 118############################################################################ 119# Main 120# 121# Set up responder and control channel, open the pid file, and start 122# the main loop, listening for queries on the query channel or commands 123# on the control channel and acting on them. 124############################################################################ 125ip4 = "10.53.0.3" 126ip6 = "fd92:7065:b8e:ffff::3" 127 128try: port=int(os.environ['PORT']) 129except: port=5300 130 131query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 132query4_socket.bind((ip4, port)) 133 134havev6 = True 135try: 136 query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 137 try: 138 query6_socket.bind((ip6, port)) 139 except: 140 query6_socket.close() 141 havev6 = False 142except: 143 havev6 = False 144 145signal.signal(signal.SIGTERM, sigterm) 146 147f = open('ans.pid', 'w') 148pid = os.getpid() 149print (pid, file=f) 150f.close() 151 152running = True 153 154print ("Listening on %s port %d" % (ip4, port)) 155if havev6: 156 print ("Listening on %s port %d" % (ip6, port)) 157print ("Ctrl-c to quit") 158 159if havev6: 160 input = [query4_socket, query6_socket] 161else: 162 input = [query4_socket] 163 164while running: 165 try: 166 inputready, outputready, exceptready = select.select(input, [], []) 167 except select.error as e: 168 break 169 except socket.error as e: 170 break 171 except KeyboardInterrupt: 172 break 173 174 for s in inputready: 175 if s == query4_socket or s == query6_socket: 176 print ("Query received on %s" % 177 (ip4 if s == query4_socket else ip6), end=" ") 178 # Handle incoming queries 179 msg = s.recvfrom(65535) 180 rsp = create_response(msg[0]) 181 if rsp: 182 print(dns.rcode.to_text(rsp.rcode())) 183 s.sendto(rsp.to_wire(), msg[1]) 184 else: 185 print("NO RESPONSE") 186 if not running: 187 break 188