ans.py revision 1.1.1.3 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": {"hmac-sha256", "aaaaaaaaaaaa"},
50 "fake": {"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 if len(o.data) == 8:
125 cookie.data = o.data + o.data
126 else:
127 cookie.data = o.data
128 r.use_edns(options=[cookie])
129 r.flags |= dns.flags.AA
130 return r
131
132
133 def sigterm(signum, frame):
134 print("Shutting down now...")
135 os.remove("ans.pid")
136 running = False
137 sys.exit(0)
138
139
140 ############################################################################
141 # Main
142 #
143 # Set up responder and control channel, open the pid file, and start
144 # the main loop, listening for queries on the query channel or commands
145 # on the control channel and acting on them.
146 ############################################################################
147 ip4_addr1 = "10.53.0.9"
148 ip4_addr2 = "10.53.0.10"
149 ip6_addr1 = "fd92:7065:b8e:ffff::9"
150 ip6_addr2 = "fd92:7065:b8e:ffff::10"
151
152 try:
153 port = int(os.environ["PORT"])
154 except:
155 port = 5300
156
157 query4_udp1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
158 query4_udp1.bind((ip4_addr1, port))
159 query4_tcp1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
160 query4_tcp1.bind((ip4_addr1, port))
161 query4_tcp1.listen(1)
162 query4_tcp1.settimeout(1)
163
164 query4_udp2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
165 query4_udp2.bind((ip4_addr2, port))
166 query4_tcp2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
167 query4_tcp2.bind((ip4_addr2, port))
168 query4_tcp2.listen(1)
169 query4_tcp2.settimeout(1)
170
171 havev6 = True
172 query6_udp1 = None
173 query6_udp2 = None
174 query6_tcp1 = None
175 query6_tcp2 = None
176 try:
177 query6_udp1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
178 query6_udp1.bind((ip6_addr1, port))
179 query6_tcp1 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
180 query6_tcp1.bind((ip6_addr1, port))
181 query6_tcp1.listen(1)
182 query6_tcp1.settimeout(1)
183
184 query6_udp2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
185 query6_udp2.bind((ip6_addr2, port))
186 query6_tcp2 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
187 query6_tcp2.bind((ip6_addr2, port))
188 query6_tcp2.listen(1)
189 query6_tcp2.settimeout(1)
190 except:
191 if query6_udp1 != None:
192 query6_udp1.close()
193 if query6_tcp1 != None:
194 query6_tcp1.close()
195 if query6_udp2 != None:
196 query6_udp2.close()
197 if query6_tcp2 != None:
198 query6_tcp2.close()
199 havev6 = False
200
201 signal.signal(signal.SIGTERM, sigterm)
202
203 f = open("ans.pid", "w")
204 pid = os.getpid()
205 print(pid, file=f)
206 f.close()
207
208 running = True
209
210 print("Using DNS version %s" % dns.version.version)
211 print("Listening on %s port %d" % (ip4_addr1, port))
212 print("Listening on %s port %d" % (ip4_addr2, port))
213 if havev6:
214 print("Listening on %s port %d" % (ip6_addr1, port))
215 print("Listening on %s port %d" % (ip6_addr2, port))
216 print("Ctrl-c to quit")
217
218 if havev6:
219 input = [
220 query4_udp1,
221 query6_udp1,
222 query4_tcp1,
223 query6_tcp1,
224 query4_udp2,
225 query6_udp2,
226 query4_tcp2,
227 query6_tcp2,
228 ]
229 else:
230 input = [query4_udp1, query4_tcp1, query4_udp2, query4_tcp2]
231
232 while running:
233 try:
234 inputready, outputready, exceptready = select.select(input, [], [])
235 except select.error as e:
236 break
237 except socket.error as e:
238 break
239 except KeyboardInterrupt:
240 break
241
242 for s in inputready:
243 ns10 = False
244 if s == query4_udp1 or s == query6_udp1 or s == query4_udp2 or s == query6_udp2:
245 if s == query4_udp1 or s == query6_udp1:
246 print(
247 "UDP Query received on %s"
248 % (ip4_addr1 if s == query4_udp1 else ip6_addr1),
249 end=" ",
250 )
251 if s == query4_udp2 or s == query6_udp2:
252 print(
253 "UDP Query received on %s"
254 % (ip4_addr2 if s == query4_udp2 else ip6_addr2),
255 end=" ",
256 )
257 ns10 = True
258 # Handle incoming queries
259 msg = s.recvfrom(65535)
260 dopass2 = False
261 rsp = create_response(msg[0], False, True, ns10)
262 print(dns.rcode.to_text(rsp.rcode()))
263 s.sendto(rsp.to_wire(), msg[1])
264 if dopass2:
265 print("Sending second UDP response without TSIG", end=" ")
266 rsp = create_response(msg[0], False, False, ns10)
267 s.sendto(rsp.to_wire(), msg[1])
268 print(dns.rcode.to_text(rsp.rcode()))
269
270 if s == query4_tcp1 or s == query6_tcp1 or s == query4_tcp2 or s == query6_tcp2:
271 try:
272 (cs, _) = s.accept()
273 if s == query4_tcp1 or s == query6_tcp1:
274 print(
275 "TCP Query received on %s"
276 % (ip4_addr1 if s == query4_tcp1 else ip6_addr1),
277 end=" ",
278 )
279 if s == query4_tcp2 or s == query6_tcp2:
280 print(
281 "TCP Query received on %s"
282 % (ip4_addr2 if s == query4_tcp2 else ip6_addr2),
283 end=" ",
284 )
285 ns10 = True
286 # get TCP message length
287 buf = cs.recv(2)
288 length = struct.unpack(">H", buf[:2])[0]
289 # grep DNS message
290 msg = cs.recv(length)
291 rsp = create_response(msg, True, True, ns10)
292 print(dns.rcode.to_text(rsp.rcode()))
293 wire = rsp.to_wire()
294 cs.send(struct.pack(">H", len(wire)))
295 cs.send(wire)
296 cs.close()
297 except s.timeout:
298 pass
299 if not running:
300 break
301