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 https://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 23import dns.edns 24import dns.flags 25import dns.message 26import dns.query 27import dns.tsig 28import dns.tsigkeyring 29import dns.version 30 31from dns.edns import * 32from dns.name import * 33from dns.rcode import * 34from dns.rdataclass import * 35from dns.rdatatype import * 36from dns.tsig import * 37 38# Log query to file 39def logquery(type, qname): 40 with open("qlog", "a") as f: 41 f.write("%s %s\n", type, qname) 42 43# DNS 2.0 keyring specifies the algorithm 44try: 45 keyring = dns.tsigkeyring.from_text({ "foo" : { 46 "hmac-sha256", 47 "aaaaaaaaaaaa" 48 } , 49 "fake" : { 50 "hmac-sha256", 51 "aaaaaaaaaaaa" 52 } 53 }) 54except: 55 keyring = dns.tsigkeyring.from_text({ "foo" : "aaaaaaaaaaaa", 56 "fake" : "aaaaaaaaaaaa" }) 57 58dopass2 = False 59 60############################################################################ 61# 62# This server will serve valid and spoofed answers. A spoofed answer will 63# have the address 10.53.0.10 included. 64# 65# When receiving a query over UDP: 66# 67# A query to "nocookie"/A will result in a spoofed answer with no cookie set. 68# A query to "tcponly"/A will result in a spoofed answer with no cookie set. 69# A query to "withtsig"/A will result in two responses, the first is a spoofed 70# answer that is TSIG signed, the second is a valid answer with a cookie set. 71# A query to anything else will result in a valid answer with a cookie set. 72# 73# When receiving a query over TCP: 74# 75# A query to "nocookie"/A will result in a valid answer with no cookie set. 76# A query to anything else will result in a valid answer with a cookie set. 77# 78############################################################################ 79def create_response(msg, tcp, first, ns10): 80 global dopass2 81 m = dns.message.from_wire(msg, keyring=keyring) 82 qname = m.question[0].name.to_text() 83 lqname = qname.lower() 84 labels = lqname.split('.') 85 rrtype = m.question[0].rdtype 86 typename = dns.rdatatype.to_text(rrtype) 87 88 with open("query.log", "a") as f: 89 f.write("%s %s\n" % (typename, qname)) 90 print("%s %s" % (typename, qname), end=" ") 91 92 r = dns.message.make_response(m) 93 r.set_rcode(NOERROR) 94 if rrtype == A: 95 # exempt potential nameserver A records. 96 if labels[0] == "ns" and ns10: 97 r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10")) 98 else: 99 r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.9")) 100 if not tcp and labels[0] == "nocookie": 101 r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10")) 102 if not tcp and labels[0] == "tcponly": 103 r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10")) 104 if first and not tcp and labels[0] == "withtsig": 105 r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10")) 106 dopass2 = True 107 elif rrtype == NS: 108 r.answer.append(dns.rrset.from_text(qname, 1, IN, NS, ".")) 109 elif rrtype == SOA: 110 r.answer.append(dns.rrset.from_text(qname, 1, IN, SOA, ". . 0 0 0 0 0")) 111 else: 112 r.authority.append(dns.rrset.from_text(qname, 1, IN, SOA, ". . 0 0 0 0 0")) 113 # Add a server cookie to the response 114 if labels[0] != "nocookie": 115 for o in m.options: 116 if o.otype == 10: # Use 10 instead of COOKIE 117 if first and labels[0] == "withtsig" and not tcp: 118 r.use_tsig(keyring = keyring, 119 keyname = dns.name.from_text("fake"), 120 algorithm = HMAC_SHA256) 121 elif labels[0] != "tcponly" or tcp: 122 cookie = o 123 if len(o.data) == 8: 124 cookie.data = o.data + o.data 125 else: 126 cookie.data = o.data 127 r.use_edns(options=[cookie]) 128 r.flags |= dns.flags.AA 129 return r 130 131def sigterm(signum, frame): 132 print ("Shutting down now...") 133 os.remove('ans.pid') 134 running = False 135 sys.exit(0) 136 137############################################################################ 138# Main 139# 140# Set up responder and control channel, open the pid file, and start 141# the main loop, listening for queries on the query channel or commands 142# on the control channel and acting on them. 143############################################################################ 144ip4_addr1 = "10.53.0.9" 145ip4_addr2 = "10.53.0.10" 146ip6_addr1 = "fd92:7065:b8e:ffff::9" 147ip6_addr2 = "fd92:7065:b8e:ffff::10" 148 149try: port=int(os.environ['PORT']) 150except: port=5300 151 152query4_udp1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 153query4_udp1.bind((ip4_addr1, port)) 154query4_tcp1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 155query4_tcp1.bind((ip4_addr1, port)) 156query4_tcp1.listen(1) 157query4_tcp1.settimeout(1) 158 159query4_udp2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 160query4_udp2.bind((ip4_addr2, port)) 161query4_tcp2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 162query4_tcp2.bind((ip4_addr2, port)) 163query4_tcp2.listen(1) 164query4_tcp2.settimeout(1) 165 166havev6 = True 167query6_udp1 = None 168query6_udp2 = None 169query6_tcp1 = None 170query6_tcp2 = None 171try: 172 query6_udp1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 173 query6_udp1.bind((ip6_addr1, port)) 174 query6_tcp1 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 175 query6_tcp1.bind((ip6_addr1, port)) 176 query6_tcp1.listen(1) 177 query6_tcp1.settimeout(1) 178 179 query6_udp2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 180 query6_udp2.bind((ip6_addr2, port)) 181 query6_tcp2 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 182 query6_tcp2.bind((ip6_addr2, port)) 183 query6_tcp2.listen(1) 184 query6_tcp2.settimeout(1) 185except: 186 if query6_udp1 != None: 187 query6_udp1.close() 188 if query6_tcp1 != None: 189 query6_tcp1.close() 190 if query6_udp2 != None: 191 query6_udp2.close() 192 if query6_tcp2 != None: 193 query6_tcp2.close() 194 havev6 = False 195 196signal.signal(signal.SIGTERM, sigterm) 197 198f = open('ans.pid', 'w') 199pid = os.getpid() 200print (pid, file=f) 201f.close() 202 203running = True 204 205print ("Using DNS version %s" % dns.version.version) 206print ("Listening on %s port %d" % (ip4_addr1, port)) 207print ("Listening on %s port %d" % (ip4_addr2, port)) 208if havev6: 209 print ("Listening on %s port %d" % (ip6_addr1, port)) 210 print ("Listening on %s port %d" % (ip6_addr2, port)) 211print ("Ctrl-c to quit") 212 213if havev6: 214 input = [query4_udp1, query6_udp1, query4_tcp1, query6_tcp1, 215 query4_udp2, query6_udp2, query4_tcp2, query6_tcp2] 216else: 217 input = [query4_udp1, query4_tcp1, query4_udp2, query4_tcp2] 218 219while running: 220 try: 221 inputready, outputready, exceptready = select.select(input, [], []) 222 except select.error as e: 223 break 224 except socket.error as e: 225 break 226 except KeyboardInterrupt: 227 break 228 229 for s in inputready: 230 ns10 = False 231 if s == query4_udp1 or s == query6_udp1 or \ 232 s == query4_udp2 or s == query6_udp2: 233 if s == query4_udp1 or s == query6_udp1: 234 print ("UDP Query received on %s" % 235 (ip4_addr1 if s == query4_udp1 else ip6_addr1), end=" ") 236 if s == query4_udp2 or s == query6_udp2: 237 print ("UDP Query received on %s" % 238 (ip4_addr2 if s == query4_udp2 else ip6_addr2), end=" ") 239 ns10 = True 240 # Handle incoming queries 241 msg = s.recvfrom(65535) 242 dopass2 = False 243 rsp = create_response(msg[0], False, True, ns10) 244 print(dns.rcode.to_text(rsp.rcode())) 245 s.sendto(rsp.to_wire(), msg[1]) 246 if dopass2: 247 print ("Sending second UDP response without TSIG", end=" ") 248 rsp = create_response(msg[0], False, False, ns10) 249 s.sendto(rsp.to_wire(), msg[1]) 250 print(dns.rcode.to_text(rsp.rcode())) 251 252 if s == query4_tcp1 or s == query6_tcp1 or \ 253 s == query4_tcp2 or s == query6_tcp2: 254 try: 255 (cs, _) = s.accept() 256 if s == query4_tcp1 or s == query6_tcp1: 257 print ("TCP Query received on %s" % 258 (ip4_addr1 if s == query4_tcp1 else ip6_addr1), end=" ") 259 if s == query4_tcp2 or s == query6_tcp2: 260 print ("TCP Query received on %s" % 261 (ip4_addr2 if s == query4_tcp2 else ip6_addr2), end=" ") 262 ns10 = True 263 # get TCP message length 264 buf = cs.recv(2) 265 length = struct.unpack('>H', buf[:2])[0] 266 # grep DNS message 267 msg = cs.recv(length) 268 rsp = create_response(msg, True, True, ns10) 269 print(dns.rcode.to_text(rsp.rcode())) 270 wire = rsp.to_wire() 271 cs.send(struct.pack('>H', len(wire))) 272 cs.send(wire) 273 cs.close() 274 except s.timeout: 275 pass 276 if not running: 277 break 278