xref: /netbsd-src/external/mpl/bind/dist/bin/tests/system/qmin/ans3/ans.py (revision 33881f779a77dce6440bdc44610d94de75bebefe)
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 http://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 miliseconds
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    if lqname.endswith("bad."):
71        bad = True
72        suffix = "bad."
73        lqname = lqname[:-4]
74    elif lqname.endswith("ugly."):
75        ugly = True
76        suffix = "ugly."
77        lqname = lqname[:-5]
78    elif lqname.endswith("good."):
79        suffix = "good."
80        lqname = lqname[:-5]
81    elif lqname.endswith("slow."):
82        slow = True
83        suffix = "slow."
84        lqname = lqname[:-5]
85    else:
86        r.set_rcode(REFUSED)
87        return r
88
89    # Good/bad differs only in how we treat non-empty terminals
90    if lqname == "zoop.boing." and rrtype == NS:
91        r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, NS, "ns3."+suffix))
92        r.flags |= dns.flags.AA
93    elif lqname.endswith("icky.ptang.zoop.boing."):
94        r.authority.append(dns.rrset.from_text("icky.ptang.zoop.boing." + suffix, 1, IN, NS, "a.bit.longer.ns.name." + suffix))
95    elif "icky.ptang.zoop.boing.".endswith(lqname):
96        r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
97        if bad:
98            r.set_rcode(NXDOMAIN)
99        if ugly:
100            r.set_rcode(FORMERR)
101    elif lqname.endswith("zoop.boing."):
102        r.authority.append(dns.rrset.from_text("zoop.boing." + suffix, 1, IN, SOA, "ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1"))
103        r.set_rcode(NXDOMAIN)
104    else:
105        r.set_rcode(REFUSED)
106
107    if slow:
108        time.sleep(0.4)
109    return r
110
111
112def sigterm(signum, frame):
113    print ("Shutting down now...")
114    os.remove('ans.pid')
115    running = False
116    sys.exit(0)
117
118############################################################################
119# Main
120#
121# Set up responder and control channel, open the pid file, and start
122# the main loop, listening for queries on the query channel or commands
123# on the control channel and acting on them.
124############################################################################
125ip4 = "10.53.0.3"
126ip6 = "fd92:7065:b8e:ffff::3"
127
128try: port=int(os.environ['PORT'])
129except: port=5300
130
131query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
132query4_socket.bind((ip4, port))
133
134havev6 = True
135try:
136    query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
137    try:
138        query6_socket.bind((ip6, port))
139    except:
140        query6_socket.close()
141        havev6 = False
142except:
143    havev6 = False
144
145signal.signal(signal.SIGTERM, sigterm)
146
147f = open('ans.pid', 'w')
148pid = os.getpid()
149print (pid, file=f)
150f.close()
151
152running = True
153
154print ("Listening on %s port %d" % (ip4, port))
155if havev6:
156    print ("Listening on %s port %d" % (ip6, port))
157print ("Ctrl-c to quit")
158
159if havev6:
160    input = [query4_socket, query6_socket]
161else:
162    input = [query4_socket]
163
164while running:
165    try:
166        inputready, outputready, exceptready = select.select(input, [], [])
167    except select.error as e:
168        break
169    except socket.error as e:
170        break
171    except KeyboardInterrupt:
172        break
173
174    for s in inputready:
175        if s == query4_socket or s == query6_socket:
176            print ("Query received on %s" %
177                    (ip4 if s == query4_socket else ip6), end=" ")
178            # Handle incoming queries
179            msg = s.recvfrom(65535)
180            rsp = create_response(msg[0])
181            if rsp:
182                print(dns.rcode.to_text(rsp.rcode()))
183                s.sendto(rsp.to_wire(), msg[1])
184            else:
185                print("NO RESPONSE")
186    if not running:
187        break
188