ans.py revision 1.1 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
12 from __future__ import print_function
13 import os
14 import sys
15 import signal
16 import socket
17 import select
18 from datetime import datetime, timedelta
19 import time
20 import functools
21
22 import dns
23 import dns.edns
24 import dns.flags
25 import dns.message
26 import dns.query
27 import dns.tsig
28 import dns.tsigkeyring
29 import dns.version
30
31 from dns.edns import *
32 from dns.name import *
33 from dns.rcode import *
34 from dns.rdataclass import *
35 from dns.rdatatype import *
36 from dns.tsig import *
37
38 # Log query to file
39 def 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
44 try:
45 keyring = dns.tsigkeyring.from_text({ "foo" : {
46 "hmac-sha256",
47 "aaaaaaaaaaaa"
48 } ,
49 "fake" : {
50 "hmac-sha256",
51 "aaaaaaaaaaaa"
52 }
53 })
54 except:
55 keyring = dns.tsigkeyring.from_text({ "foo" : "aaaaaaaaaaaa",
56 "fake" : "aaaaaaaaaaaa" })
57
58 dopass2 = 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 ############################################################################
79 def 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
131 def 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 ############################################################################
144 ip4_addr1 = "10.53.0.9"
145 ip4_addr2 = "10.53.0.10"
146 ip6_addr1 = "fd92:7065:b8e:ffff::9"
147 ip6_addr2 = "fd92:7065:b8e:ffff::10"
148
149 try: port=int(os.environ['PORT'])
150 except: port=5300
151
152 query4_udp1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
153 query4_udp1.bind((ip4_addr1, port))
154 query4_tcp1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
155 query4_tcp1.bind((ip4_addr1, port))
156 query4_tcp1.listen(1)
157 query4_tcp1.settimeout(1)
158
159 query4_udp2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
160 query4_udp2.bind((ip4_addr2, port))
161 query4_tcp2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
162 query4_tcp2.bind((ip4_addr2, port))
163 query4_tcp2.listen(1)
164 query4_tcp2.settimeout(1)
165
166 havev6 = True
167 query6_udp1 = None
168 query6_udp2 = None
169 query6_tcp1 = None
170 query6_tcp2 = None
171 try:
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)
185 except:
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
196 signal.signal(signal.SIGTERM, sigterm)
197
198 f = open('ans.pid', 'w')
199 pid = os.getpid()
200 print (pid, file=f)
201 f.close()
202
203 running = True
204
205 print ("Using DNS version %s" % dns.version.version)
206 print ("Listening on %s port %d" % (ip4_addr1, port))
207 print ("Listening on %s port %d" % (ip4_addr2, port))
208 if havev6:
209 print ("Listening on %s port %d" % (ip6_addr1, port))
210 print ("Listening on %s port %d" % (ip6_addr2, port))
211 print ("Ctrl-c to quit")
212
213 if havev6:
214 input = [query4_udp1, query6_udp1, query4_tcp1, query6_tcp1,
215 query4_udp2, query6_udp2, query4_tcp2, query6_tcp2]
216 else:
217 input = [query4_udp1, query4_tcp1, query4_udp2, query4_tcp2]
218
219 while 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