1# Copyright (C) Internet Systems Consortium, Inc. ("ISC") 2# 3# SPDX-License-Identifier: MPL-2.0 4# 5# This Source Code Form is subject to the terms of the Mozilla Public 6# License, v. 2.0. If a copy of the MPL was not distributed with this 7# file, you can obtain one at https://mozilla.org/MPL/2.0/. 8# 9# See the COPYRIGHT file distributed with this work for additional 10# information regarding copyright ownership. 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 34def endswith(domain, labels): 35 return domain.endswith("." + labels) or domain == labels 36 37############################################################################ 38# Respond to a DNS query. 39# For good. it serves: 40# ns2.good. IN A 10.53.0.2 41# zoop.boing.good. NS ns3.good. 42# ns3.good. IN A 10.53.0.3 43# too.many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.good. A 192.0.2.2 44# it responds properly (with NODATA empty response) to non-empty terminals 45# 46# For slow. it works the same as for good., but each response is delayed by 400 milliseconds 47# 48# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals 49# 50# For ugly. it works the same as for good., but returns garbage to non-empty terminals 51# 52# For 1.0.0.2.ip6.arpa it serves 53# 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa. IN PTR nee.com. 54# 8.2.6.0.1.0.0.2.ip6.arpa IN NS ns3.good 55# 1.0.0.2.ip6.arpa. IN NS ns2.good 56# ip6.arpa. IN NS ns2.good 57# 58# For stale. it serves: 59# a.b. NS ns.a.b.stale. 60# ns.a.b.stale. IN A 10.53.0.3 61# b. NS ns.b.stale. 62# ns.b.stale. IN A 10.53.0.4 63############################################################################ 64def create_response(msg): 65 m = dns.message.from_wire(msg) 66 qname = m.question[0].name.to_text() 67 lqname = qname.lower() 68 labels = lqname.split('.') 69 70 # get qtype 71 rrtype = m.question[0].rdtype 72 typename = dns.rdatatype.to_text(rrtype) 73 if typename == "A" or typename == "AAAA": 74 typename = "ADDR" 75 bad = False 76 ugly = False 77 slow = False 78 79 # log this query 80 with open("query.log", "a") as f: 81 f.write("%s %s\n" % (typename, lqname)) 82 print("%s %s" % (typename, lqname), end=" ") 83 84 r = dns.message.make_response(m) 85 r.set_rcode(NOERROR) 86 87 if endswith(lqname, "1.0.0.2.ip6.arpa."): 88 # Direct query - give direct answer 89 if endswith(lqname, "8.2.6.0.1.0.0.2.ip6.arpa."): 90 # Delegate to ns3 91 r.authority.append(dns.rrset.from_text("8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns3.good.")) 92 r.additional.append(dns.rrset.from_text("ns3.good.", 60, IN, A, "10.53.0.3")) 93 elif lqname == "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa." and rrtype == PTR: 94 # Direct query - give direct answer 95 r.answer.append(dns.rrset.from_text("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.", 1, IN, PTR, "nee.com.")) 96 r.flags |= dns.flags.AA 97 elif lqname == "1.0.0.2.ip6.arpa." and rrtype == NS: 98 # NS query at the apex 99 r.answer.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, NS, "ns2.good.")) 100 r.flags |= dns.flags.AA 101 elif endswith("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.", lqname): 102 # NODATA answer 103 r.authority.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1")) 104 else: 105 # NXDOMAIN 106 r.authority.append(dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1")) 107 r.set_rcode(NXDOMAIN) 108 return r 109 elif endswith(lqname, "ip6.arpa."): 110 if lqname == "ip6.arpa." and rrtype == NS: 111 # NS query at the apex 112 r.answer.append(dns.rrset.from_text("ip6.arpa.", 30, IN, NS, "ns2.good.")) 113 r.flags |= dns.flags.AA 114 elif endswith("1.0.0.2.ip6.arpa.", lqname): 115 # NODATA answer 116 r.authority.append(dns.rrset.from_text("ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1")) 117 else: 118 # NXDOMAIN 119 r.authority.append(dns.rrset.from_text("ip6.arpa.", 30, IN, SOA, "ns2.good. hostmaster.arpa. 2018050100 1 1 1 1")) 120 r.set_rcode(NXDOMAIN) 121 return r 122 elif endswith(lqname, "stale."): 123 if endswith(lqname, "a.b.stale."): 124 # Delegate to ns.a.b.stale. 125 r.authority.append(dns.rrset.from_text("a.b.stale.", 2, IN, NS, "ns.a.b.stale.")) 126 r.additional.append(dns.rrset.from_text("ns.a.b.stale.", 2, IN, A, "10.53.0.3")) 127 elif endswith(lqname, "b.stale."): 128 # Delegate to ns.b.stale. 129 r.authority.append(dns.rrset.from_text("b.stale.", 2, IN, NS, "ns.b.stale.")) 130 r.additional.append(dns.rrset.from_text("ns.b.stale.", 2, IN, A, "10.53.0.4")) 131 elif lqname == "stale." and rrtype == NS: 132 # NS query at the apex. 133 r.answer.append(dns.rrset.from_text("stale.", 2, IN, NS, "ns2.stale.")) 134 r.flags |= dns.flags.AA 135 elif lqname == "stale." and rrtype == SOA: 136 # SOA query at the apex. 137 r.answer.append(dns.rrset.from_text("stale.", 2, IN, SOA, "ns2.stale. hostmaster.stale. 1 2 3 4 5")) 138 r.flags |= dns.flags.AA 139 elif lqname == "stale.": 140 # NODATA answer 141 r.authority.append(dns.rrset.from_text("stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5")) 142 else: 143 # NXDOMAIN 144 r.authority.append(dns.rrset.from_text("stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5")) 145 r.set_rcode(NXDOMAIN) 146 return r 147 elif endswith(lqname, "bad."): 148 bad = True 149 suffix = "bad." 150 lqname = lqname[:-4] 151 elif endswith(lqname, "ugly."): 152 ugly = True 153 suffix = "ugly." 154 lqname = lqname[:-5] 155 elif endswith(lqname, "good."): 156 suffix = "good." 157 lqname = lqname[:-5] 158 elif endswith(lqname, "slow."): 159 slow = True 160 suffix = "slow." 161 lqname = lqname[:-5] 162 elif endswith(lqname, "fwd."): 163 suffix = "fwd." 164 lqname = lqname[:-4] 165 else: 166 r.set_rcode(REFUSED) 167 return r 168 169 # Good/bad/ugly differs only in how we treat non-empty terminals 170 if endswith(lqname, "zoop.boing."): 171 r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, NS, "ns3." + suffix)) 172 elif lqname == "many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z." and rrtype == A: 173 r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2")) 174 r.flags |= dns.flags.AA 175 elif lqname == "" and rrtype == NS: 176 r.answer.append(dns.rrset.from_text(suffix, 30, IN, NS, "ns2." + suffix)) 177 r.flags |= dns.flags.AA 178 elif lqname == "ns2." and rrtype == A: 179 r.answer.append(dns.rrset.from_text("ns2."+suffix, 30, IN, A, "10.53.0.2")) 180 r.flags |= dns.flags.AA 181 elif lqname == "ns2." and rrtype == AAAA: 182 r.answer.append(dns.rrset.from_text("ns2."+suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::2")) 183 r.flags |= dns.flags.AA 184 elif lqname == "ns3." and rrtype == A: 185 r.answer.append(dns.rrset.from_text("ns3."+suffix, 30, IN, A, "10.53.0.3")) 186 r.flags |= dns.flags.AA 187 elif lqname == "ns3." and rrtype == AAAA: 188 r.answer.append(dns.rrset.from_text("ns3."+suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::3")) 189 r.flags |= dns.flags.AA 190 elif lqname == "ns4." and rrtype == A: 191 r.answer.append(dns.rrset.from_text("ns4."+suffix, 30, IN, A, "10.53.0.4")) 192 r.flags |= dns.flags.AA 193 elif lqname == "ns4." and rrtype == AAAA: 194 r.answer.append(dns.rrset.from_text("ns4."+suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::4")) 195 r.flags |= dns.flags.AA 196 elif lqname == "a.bit.longer.ns.name." and rrtype == A: 197 r.answer.append(dns.rrset.from_text("a.bit.longer.ns.name."+suffix, 1, IN, A, "10.53.0.4")) 198 r.flags |= dns.flags.AA 199 elif lqname == "a.bit.longer.ns.name." and rrtype == AAAA: 200 r.answer.append(dns.rrset.from_text("a.bit.longer.ns.name."+suffix, 1, IN, AAAA, "fd92:7065:b8e:ffff::4")) 201 r.flags |= dns.flags.AA 202 else: 203 r.authority.append(dns.rrset.from_text(suffix, 1, IN, SOA, "ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1")) 204 if bad or not \ 205 (endswith("icky.icky.icky.ptang.zoop.boing.", lqname) or \ 206 endswith("many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.", lqname) or \ 207 endswith("a.bit.longer.ns.name.", lqname)): 208 r.set_rcode(NXDOMAIN) 209 if ugly: 210 r.set_rcode(FORMERR) 211 if slow: 212 time.sleep(0.2) 213 return r 214 215 216def sigterm(signum, frame): 217 print ("Shutting down now...") 218 os.remove('ans.pid') 219 running = False 220 sys.exit(0) 221 222############################################################################ 223# Main 224# 225# Set up responder and control channel, open the pid file, and start 226# the main loop, listening for queries on the query channel or commands 227# on the control channel and acting on them. 228############################################################################ 229ip4 = "10.53.0.2" 230ip6 = "fd92:7065:b8e:ffff::2" 231 232try: port=int(os.environ['PORT']) 233except: port=5300 234 235query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 236query4_socket.bind((ip4, port)) 237 238havev6 = True 239try: 240 query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 241 try: 242 query6_socket.bind((ip6, port)) 243 except: 244 query6_socket.close() 245 havev6 = False 246except: 247 havev6 = False 248 249signal.signal(signal.SIGTERM, sigterm) 250 251f = open('ans.pid', 'w') 252pid = os.getpid() 253print (pid, file=f) 254f.close() 255 256running = True 257 258print ("Listening on %s port %d" % (ip4, port)) 259if havev6: 260 print ("Listening on %s port %d" % (ip6, port)) 261print ("Ctrl-c to quit") 262 263if havev6: 264 input = [query4_socket, query6_socket] 265else: 266 input = [query4_socket] 267 268while running: 269 try: 270 inputready, outputready, exceptready = select.select(input, [], []) 271 except select.error as e: 272 break 273 except socket.error as e: 274 break 275 except KeyboardInterrupt: 276 break 277 278 for s in inputready: 279 if s == query4_socket or s == query6_socket: 280 print ("Query received on %s" % 281 (ip4 if s == query4_socket else ip6), end=" ") 282 # Handle incoming queries 283 msg = s.recvfrom(65535) 284 rsp = create_response(msg[0]) 285 if rsp: 286 print(dns.rcode.to_text(rsp.rcode())) 287 s.sendto(rsp.to_wire(), msg[1]) 288 else: 289 print("NO RESPONSE") 290 if not running: 291 break 292