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