ans.py revision 1.1.1.2
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# icky.ptang.zoop.boing.good. NS a.bit.longer.ns.name. 38# icky.icky.icky.ptang.zoop.boing.good. A 192.0.2.1 39# more.icky.icky.icky.ptang.zoop.boing.good. A 192.0.2.2 40# it responds properly (with NODATA empty response) to non-empty terminals 41# 42# For slow. it works the same as for good., but each response is delayed by 400 milliseconds 43# 44# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals 45# 46# For ugly. it works the same as for good., but returns garbage to non-empty terminals 47############################################################################ 48def create_response(msg): 49 m = dns.message.from_wire(msg) 50 qname = m.question[0].name.to_text() 51 lqname = qname.lower() 52 labels = lqname.split('.') 53 54 # get qtype 55 rrtype = m.question[0].rdtype 56 typename = dns.rdatatype.to_text(rrtype) 57 if typename == "A" or typename == "AAAA": 58 typename = "ADDR" 59 bad = False 60 slow = False 61 ugly = False 62 63 # log this query 64 with open("query.log", "a") as f: 65 f.write("%s %s\n" % (typename, lqname)) 66 print("%s %s" % (typename, lqname), end=" ") 67 68 r = dns.message.make_response(m) 69 r.set_rcode(NOERROR) 70 71 if lqname.endswith("bad."): 72 bad = True 73 suffix = "bad." 74 lqname = lqname[:-4] 75 elif lqname.endswith("ugly."): 76 ugly = True 77 suffix = "ugly." 78 lqname = lqname[:-5] 79 elif lqname.endswith("good."): 80 suffix = "good." 81 lqname = lqname[:-5] 82 elif lqname.endswith("slow."): 83 slow = True 84 suffix = "slow." 85 lqname = lqname[:-5] 86 else: 87 r.set_rcode(REFUSED) 88 return r 89 90 # Good/bad differs only in how we treat non-empty terminals 91 if lqname == "icky.icky.icky.ptang.zoop.boing." and rrtype == A: 92 r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.1")) 93 r.flags |= dns.flags.AA 94 elif lqname == "more.icky.icky.icky.ptang.zoop.boing." and rrtype == A: 95 r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2")) 96 r.flags |= dns.flags.AA 97 elif lqname == "icky.ptang.zoop.boing." and rrtype == NS: 98 r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, NS, "a.bit.longer.ns.name."+suffix)) 99 r.flags |= dns.flags.AA 100 elif lqname.endswith("icky.ptang.zoop.boing."): 101 r.authority.append(dns.rrset.from_text("icky.ptang.zoop.boing." + suffix, 1, IN, SOA, "ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1")) 102 if bad or not "more.icky.icky.icky.ptang.zoop.boing.".endswith(lqname): 103 r.set_rcode(NXDOMAIN) 104 if ugly: 105 r.set_rcode(FORMERR) 106 else: 107 r.set_rcode(REFUSED) 108 109 if slow: 110 time.sleep(0.4) 111 return r 112 113 114def sigterm(signum, frame): 115 print ("Shutting down now...") 116 os.remove('ans.pid') 117 running = False 118 sys.exit(0) 119 120############################################################################ 121# Main 122# 123# Set up responder and control channel, open the pid file, and start 124# the main loop, listening for queries on the query channel or commands 125# on the control channel and acting on them. 126############################################################################ 127ip4 = "10.53.0.4" 128ip6 = "fd92:7065:b8e:ffff::4" 129 130try: port=int(os.environ['PORT']) 131except: port=5300 132 133query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 134query4_socket.bind((ip4, port)) 135 136havev6 = True 137try: 138 query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 139 try: 140 query6_socket.bind((ip6, port)) 141 except: 142 query6_socket.close() 143 havev6 = False 144except: 145 havev6 = False 146 147signal.signal(signal.SIGTERM, sigterm) 148 149f = open('ans.pid', 'w') 150pid = os.getpid() 151print (pid, file=f) 152f.close() 153 154running = True 155 156print ("Listening on %s port %d" % (ip4, port)) 157if havev6: 158 print ("Listening on %s port %d" % (ip6, port)) 159print ("Ctrl-c to quit") 160 161if havev6: 162 input = [query4_socket, query6_socket] 163else: 164 input = [query4_socket] 165 166while running: 167 try: 168 inputready, outputready, exceptready = select.select(input, [], []) 169 except select.error as e: 170 break 171 except socket.error as e: 172 break 173 except KeyboardInterrupt: 174 break 175 176 for s in inputready: 177 if s == query4_socket or s == query6_socket: 178 print ("Query received on %s" % 179 (ip4 if s == query4_socket else ip6), end=" ") 180 # Handle incoming queries 181 msg = s.recvfrom(65535) 182 rsp = create_response(msg[0]) 183 if rsp: 184 print(dns.rcode.to_text(rsp.rcode())) 185 s.sendto(rsp.to_wire(), msg[1]) 186 else: 187 print("NO RESPONSE") 188 if not running: 189 break 190