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
12from __future__ import print_function
13import os
14import sys
15import signal
16import socket
17import select
18import struct
19
20import dns, dns.message
21from dns.rcode import *
22
23modes = [
24    b"silent",  # Do not respond
25    b"close",  # UDP: same as silent; TCP: also close the connection
26    b"servfail",  # Always respond with SERVFAIL
27    b"unstable",  # Constantly switch between "silent" and "servfail"
28]
29mode = modes[0]
30n = 0
31
32
33def ctrl_channel(msg):
34    global modes, mode, n
35
36    msg = msg.splitlines().pop(0)
37    print("Received control message: %s" % msg)
38
39    if msg in modes:
40        mode = msg
41        n = 0
42        print("New mode: %s" % str(mode))
43
44
45def create_servfail(msg):
46    m = dns.message.from_wire(msg)
47    qname = m.question[0].name.to_text()
48    rrtype = m.question[0].rdtype
49    typename = dns.rdatatype.to_text(rrtype)
50
51    with open("query.log", "a") as f:
52        f.write("%s %s\n" % (typename, qname))
53        print("%s %s" % (typename, qname), end=" ")
54
55    r = dns.message.make_response(m)
56    r.set_rcode(SERVFAIL)
57    return r
58
59
60def sigterm(signum, frame):
61    print("Shutting down now...")
62    os.remove("ans.pid")
63    running = False
64    sys.exit(0)
65
66
67ip4 = "10.53.0.8"
68
69try:
70    port = int(os.environ["PORT"])
71except:
72    port = 5300
73
74try:
75    ctrlport = int(os.environ["EXTRAPORT1"])
76except:
77    ctrlport = 5300
78
79query4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
80query4_udp.bind((ip4, port))
81
82query4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
83query4_tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
84query4_tcp.bind((ip4, port))
85query4_tcp.listen(100)
86
87ctrl4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
88ctrl4_tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
89ctrl4_tcp.bind((ip4, ctrlport))
90ctrl4_tcp.listen(100)
91
92signal.signal(signal.SIGTERM, sigterm)
93
94f = open("ans.pid", "w")
95pid = os.getpid()
96print(pid, file=f)
97f.close()
98
99running = True
100
101print("Listening on %s port %d" % (ip4, port))
102print("Listening on %s port %d" % (ip4, ctrlport))
103print("Ctrl-c to quit")
104
105input = [query4_udp, query4_tcp, ctrl4_tcp]
106
107hung_conns = []
108
109while running:
110    try:
111        inputready, outputready, exceptready = select.select(input, [], [])
112    except select.error as e:
113        break
114    except socket.error as e:
115        break
116    except KeyboardInterrupt:
117        break
118
119    for s in inputready:
120        if s == query4_udp:
121            n = n + 1
122            print("UDP query received on %s" % ip4, end=" ")
123            msg = s.recvfrom(65535)
124            if (
125                mode == b"silent"
126                or mode == b"close"
127                or (mode == b"unstable" and n % 2 == 1)
128            ):
129                # Do not respond.
130                print("NO RESPONSE (%s)" % str(mode))
131                continue
132            elif mode == b"servfail" or (mode == b"unstable" and n % 2 == 0):
133                rsp = create_servfail(msg[0])
134                if rsp:
135                    print(dns.rcode.to_text(rsp.rcode()))
136                    s.sendto(rsp.to_wire(), msg[1])
137                else:
138                    print("NO RESPONSE (can not create a response)")
139            else:
140                raise (Exception("unsupported mode: %s" % mode))
141        elif s == query4_tcp:
142            n = n + 1
143            print("TCP query received on %s" % ip4, end=" ")
144            conn = None
145            try:
146                if mode == b"silent" or (mode == b"unstable" and n % 2 == 1):
147                    conn, addr = s.accept()
148                    # Do not respond and hang the connection.
149                    print("NO RESPONSE (%s)" % str(mode))
150                    hung_conns.append(conn)
151                    continue
152                elif mode == b"close":
153                    conn, addr = s.accept()
154                    # Do not respond and close the connection.
155                    print("NO RESPONSE (%s)" % str(mode))
156                    conn.close()
157                    continue
158                elif mode == b"servfail" or (mode == b"unstable" and n % 2 == 0):
159                    conn, addr = s.accept()
160                    # get TCP message length
161                    msg = conn.recv(2)
162                    if len(msg) != 2:
163                        print("NO RESPONSE (can not read the message length)")
164                        conn.close()
165                        continue
166                    length = struct.unpack(">H", msg[:2])[0]
167                    msg = conn.recv(length)
168                    if len(msg) != length:
169                        print("NO RESPONSE (can not read the message)")
170                        conn.close()
171                        continue
172                    rsp = create_servfail(msg)
173                    if rsp:
174                        print(dns.rcode.to_text(rsp.rcode()))
175                        wire = rsp.to_wire()
176                        conn.send(struct.pack(">H", len(wire)))
177                        conn.send(wire)
178                    else:
179                        print("NO RESPONSE (can not create a response)")
180                else:
181                    raise (Exception("unsupported mode: %s" % mode))
182            except socket.error as e:
183                print("NO RESPONSE (error: %s)" % str(e))
184            if conn:
185                conn.close()
186        elif s == ctrl4_tcp:
187            print("Control channel connected")
188            conn = None
189            try:
190                # Handle control channel input
191                conn, addr = s.accept()
192                msg = conn.recv(1024)
193                if msg:
194                    ctrl_channel(msg)
195                conn.close()
196            except s.timeout:
197                pass
198            if conn:
199                conn.close()
200
201    if not running:
202        break
203