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
18from datetime import datetime, timedelta
19import time
20import functools
21
22import dns, dns.message, dns.query, dns.flags
23from dns.rdatatype import *
24from dns.rdataclass import *
25from dns.rcode import *
26from dns.name import *
27
28
29# Log query to file
30def logquery(type, qname):
31    with open("qlog", "a") as f:
32        f.write("%s %s\n", type, qname)
33
34
35############################################################################
36# Respond to a DNS query.
37# SOA gets a unsigned response.
38# NS gets a unsigned response.
39# DNSKEY get a unsigned NODATA response.
40# A gets a signed response.
41# All other types get a unsigned NODATA response.
42############################################################################
43def create_response(msg):
44    m = dns.message.from_wire(msg)
45    qname = m.question[0].name.to_text()
46    rrtype = m.question[0].rdtype
47    typename = dns.rdatatype.to_text(rrtype)
48
49    with open("query.log", "a") as f:
50        f.write("%s %s\n" % (typename, qname))
51        print("%s %s" % (typename, qname), end=" ")
52
53    r = dns.message.make_response(m)
54    r.set_rcode(NOERROR)
55    if rrtype == A:
56        now = datetime.today()
57        expire = now + timedelta(days=30)
58        inception = now - timedelta(days=1)
59        rrsig = (
60            "A 13 2 60 "
61            + expire.strftime("%Y%m%d%H%M%S")
62            + " "
63            + inception.strftime("%Y%m%d%H%M%S")
64            + " 12345 "
65            + qname
66            + " gB+eISXAhSPZU2i/II0W9ZUhC2SCIrb94mlNvP5092WAeXxqN/vG43/1nmDl"
67            + "y2Qs7y5VCjSMOGn85bnaMoAc7w=="
68        )
69        r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10"))
70        r.answer.append(dns.rrset.from_text(qname, 1, IN, RRSIG, rrsig))
71    elif rrtype == NS:
72        r.answer.append(dns.rrset.from_text(qname, 1, IN, NS, "."))
73    elif rrtype == SOA:
74        r.answer.append(dns.rrset.from_text(qname, 1, IN, SOA, ". . 0 0 0 0 0"))
75    else:
76        r.authority.append(dns.rrset.from_text(qname, 1, IN, SOA, ". . 0 0 0 0 0"))
77    r.flags |= dns.flags.AA
78    return r
79
80
81def sigterm(signum, frame):
82    print("Shutting down now...")
83    os.remove("ans.pid")
84    running = False
85    sys.exit(0)
86
87
88############################################################################
89# Main
90#
91# Set up responder and control channel, open the pid file, and start
92# the main loop, listening for queries on the query channel or commands
93# on the control channel and acting on them.
94############################################################################
95ip4 = "10.53.0.10"
96ip6 = "fd92:7065:b8e:ffff::10"
97
98try:
99    port = int(os.environ["PORT"])
100except:
101    port = 5300
102
103query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
104query4_socket.bind((ip4, port))
105havev6 = True
106try:
107    query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
108    try:
109        query6_socket.bind((ip6, port))
110    except:
111        query6_socket.close()
112        havev6 = False
113except:
114    havev6 = False
115signal.signal(signal.SIGTERM, sigterm)
116
117f = open("ans.pid", "w")
118pid = os.getpid()
119print(pid, file=f)
120f.close()
121
122running = True
123
124print("Listening on %s port %d" % (ip4, port))
125if havev6:
126    print("Listening on %s port %d" % (ip6, port))
127print("Ctrl-c to quit")
128
129if havev6:
130    input = [query4_socket, query6_socket]
131else:
132    input = [query4_socket]
133
134while running:
135    try:
136        inputready, outputready, exceptready = select.select(input, [], [])
137    except select.error as e:
138        break
139    except socket.error as e:
140        break
141    except KeyboardInterrupt:
142        break
143
144    for s in inputready:
145        if s == query4_socket or s == query6_socket:
146            print(
147                "Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
148            )
149            # Handle incoming queries
150            msg = s.recvfrom(65535)
151            rsp = create_response(msg[0])
152            if rsp:
153                print(dns.rcode.to_text(rsp.rcode()))
154                s.sendto(rsp.to_wire(), msg[1])
155            else:
156                print("NO RESPONSE")
157    if not running:
158        break
159