xref: /netbsd-src/external/mpl/bind/dist/bin/tests/system/qmin/ans3/ans.py (revision 2718af68c3efc72c9769069b5c7f9ed36f6b9def)
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, 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# Respond to a DNS query.
36# For good. it serves:
37# zoop.boing.good. NS ns3.good.
38# icky.ptang.zoop.boing.good. NS a.bit.longer.ns.name.good.
39# it responds properly (with NODATA empty response) to non-empty terminals
40#
41# For slow. it works the same as for good., but each response is delayed by 400 milliseconds
42#
43# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals
44#
45# For ugly. it works the same as for good., but returns garbage to non-empty terminals
46############################################################################
47def create_response(msg):
48    m = dns.message.from_wire(msg)
49    qname = m.question[0].name.to_text()
50    lqname = qname.lower()
51    labels = lqname.split('.')
52
53    # get qtype
54    rrtype = m.question[0].rdtype
55    typename = dns.rdatatype.to_text(rrtype)
56    if typename == "A" or typename == "AAAA":
57        typename = "ADDR"
58    bad = False
59    ugly = False
60    slow = False
61
62    # log this query
63    with open("query.log", "a") as f:
64        f.write("%s %s\n" % (typename, lqname))
65        print("%s %s" % (typename, lqname), end=" ")
66
67    r = dns.message.make_response(m)
68    r.set_rcode(NOERROR)
69
70    ip6req = False
71
72    if lqname.endswith("bad."):
73        bad = True
74        suffix = "bad."
75        lqname = lqname[:-4]
76    elif lqname.endswith("ugly."):
77        ugly = True
78        suffix = "ugly."
79        lqname = lqname[:-5]
80    elif lqname.endswith("good."):
81        suffix = "good."
82        lqname = lqname[:-5]
83    elif lqname.endswith("slow."):
84        slow = True
85        suffix = "slow."
86        lqname = lqname[:-5]
87    elif lqname.endswith("8.2.6.0.1.0.0.2.ip6.arpa."):
88        ip6req = True
89    else:
90        r.set_rcode(REFUSED)
91        return r
92
93    # Good/bad differs only in how we treat non-empty terminals
94    if lqname == "zoop.boing." and rrtype == NS:
95        r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, NS, "ns3."+suffix))
96        r.flags |= dns.flags.AA
97    elif lqname.endswith("icky.ptang.zoop.boing."):
98        r.authority.append(dns.rrset.from_text("icky.ptang.zoop.boing." + suffix, 1, IN, NS, "a.bit.longer.ns.name." + suffix))
99    elif "icky.ptang.zoop.boing.".endswith(lqname):
100        r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
101        if bad:
102            r.set_rcode(NXDOMAIN)
103        if ugly:
104            r.set_rcode(FORMERR)
105    elif lqname.endswith("zoop.boing."):
106        r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
107        r.set_rcode(NXDOMAIN)
108    elif ip6req:
109        r.authority.append(dns.rrset.from_text("1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns4.good."))
110        r.additional.append(dns.rrset.from_text("ns4.good.", 60, IN, A, "10.53.0.4"))
111    else:
112        r.set_rcode(REFUSED)
113
114    if slow:
115        time.sleep(0.4)
116    return r
117
118
119def sigterm(signum, frame):
120    print ("Shutting down now...")
121    os.remove('ans.pid')
122    running = False
123    sys.exit(0)
124
125############################################################################
126# Main
127#
128# Set up responder and control channel, open the pid file, and start
129# the main loop, listening for queries on the query channel or commands
130# on the control channel and acting on them.
131############################################################################
132ip4 = "10.53.0.3"
133ip6 = "fd92:7065:b8e:ffff::3"
134
135try: port=int(os.environ['PORT'])
136except: port=5300
137
138query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
139query4_socket.bind((ip4, port))
140
141havev6 = True
142try:
143    query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
144    try:
145        query6_socket.bind((ip6, port))
146    except:
147        query6_socket.close()
148        havev6 = False
149except:
150    havev6 = False
151
152signal.signal(signal.SIGTERM, sigterm)
153
154f = open('ans.pid', 'w')
155pid = os.getpid()
156print (pid, file=f)
157f.close()
158
159running = True
160
161print ("Listening on %s port %d" % (ip4, port))
162if havev6:
163    print ("Listening on %s port %d" % (ip6, port))
164print ("Ctrl-c to quit")
165
166if havev6:
167    input = [query4_socket, query6_socket]
168else:
169    input = [query4_socket]
170
171while running:
172    try:
173        inputready, outputready, exceptready = select.select(input, [], [])
174    except select.error as e:
175        break
176    except socket.error as e:
177        break
178    except KeyboardInterrupt:
179        break
180
181    for s in inputready:
182        if s == query4_socket or s == query6_socket:
183            print ("Query received on %s" %
184                    (ip4 if s == query4_socket else ip6), end=" ")
185            # Handle incoming queries
186            msg = s.recvfrom(65535)
187            rsp = create_response(msg[0])
188            if rsp:
189                print(dns.rcode.to_text(rsp.rcode()))
190                s.sendto(rsp.to_wire(), msg[1])
191            else:
192                print("NO RESPONSE")
193    if not running:
194        break
195