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