selftest: improve debugging in dns_hub.py
[nivanova/samba-autobuild/.git] / selftest / target / dns_hub.py
1 #!/usr/bin/env python
2 #
3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Volker Lendecke 2017
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19 # Used by selftest to proxy DNS queries to the correct testenv DC.
20 # See selftest/target/README for more details.
21 # Based on the EchoServer example from python docs
22
23 import threading
24 import sys
25 import select
26 import socket
27 import time
28 from samba.dcerpc import dns
29 import samba.ndr as ndr
30
31 if sys.version_info[0] < 3:
32     import SocketServer
33     sserver = SocketServer
34 else:
35     import socketserver
36     sserver = socketserver
37
38 DNS_REQUEST_TIMEOUT = 10
39
40
41 class DnsHandler(sserver.BaseRequestHandler):
42     dns_qtype_strings = dict((v, k) for k, v in vars(dns).items() if k.startswith('DNS_QTYPE_'))
43     def dns_qtype_string(self, qtype):
44         "Return a readable qtype code"
45         return self.dns_qtype_strings[qtype]
46
47     dns_rcode_strings = dict((v, k) for k, v in vars(dns).items() if k.startswith('DNS_RCODE_'))
48     def dns_rcode_string(self, rcode):
49         "Return a readable error code"
50         return self.dns_rcode_strings[rcode]
51
52     def dns_transaction_udp(self, packet, host):
53         "send a DNS query and read the reply"
54         s = None
55         try:
56             send_packet = ndr.ndr_pack(packet)
57             s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
58             s.settimeout(DNS_REQUEST_TIMEOUT)
59             s.connect((host, 53))
60             s.sendall(send_packet, 0)
61             recv_packet = s.recv(2048, 0)
62             return ndr.ndr_unpack(dns.name_packet, recv_packet)
63         except socket.error as err:
64             print("Error sending to host %s for name %s: %s\n" %
65                   (host, packet.questions[0].name, err.errno))
66             raise
67         finally:
68             if s is not None:
69                 s.close()
70         return None
71
72     def forwarder(self, name):
73         lname = name.lower()
74
75         if lname.endswith('an-address-that-will-not-resolve'):
76             return 'ignore'
77         if lname.endswith('dsfsdfs'):
78             return 'fail'
79         if lname.endswith("torture1", 0, len(lname)-2):
80             # CATCH TORTURE100, TORTURE101, ...
81             return 'torture'
82         if lname.endswith('_none_.example.com'):
83             return 'torture'
84         if lname.endswith('torturedom.samba.example.com'):
85             return 'torture'
86         if lname.endswith('adnonssdom.samba.example.com'):
87             return '127.0.0.17'
88         if lname.endswith('adnontlmdom.samba.example.com'):
89             return '127.0.0.18'
90         if lname.endswith('samba2000.example.com'):
91             return '127.0.0.25'
92         if lname.endswith('samba2003.example.com'):
93             return '127.0.0.26'
94         if lname.endswith('samba2008r2.example.com'):
95             return '127.0.0.27'
96         if lname.endswith('addom.samba.example.com'):
97             return '127.0.0.30'
98         if lname.endswith('sub.samba.example.com'):
99             return '127.0.0.31'
100         if lname.endswith('chgdcpassword.samba.example.com'):
101             return '127.0.0.32'
102         if lname.endswith('backupdom.samba.example.com'):
103             return '127.0.0.40'
104         if lname.endswith('renamedom.samba.example.com'):
105             return '127.0.0.42'
106         if lname.endswith('labdom.samba.example.com'):
107             return '127.0.0.43'
108         if lname.endswith('samba.example.com'):
109             return '127.0.0.21'
110         return None
111
112     def handle(self):
113         start = time.monotonic()
114         data, sock = self.request
115         query = ndr.ndr_unpack(dns.name_packet, data)
116         name = query.questions[0].name
117         forwarder = self.forwarder(name)
118         response = None
119
120         if forwarder is 'ignore':
121             return
122         elif forwarder is 'fail':
123             pass
124         elif forwarder in ['torture', None]:
125             response = query
126             response.operation |= dns.DNS_FLAG_REPLY
127             response.operation |= dns.DNS_FLAG_RECURSION_AVAIL
128             response.operation |= dns.DNS_RCODE_NXDOMAIN
129         else:
130             response = self.dns_transaction_udp(query, forwarder)
131
132         if response is None:
133             response = query
134             response.operation |= dns.DNS_FLAG_REPLY
135             response.operation |= dns.DNS_FLAG_RECURSION_AVAIL
136             response.operation |= dns.DNS_RCODE_SERVFAIL
137
138         send_packet = ndr.ndr_pack(response)
139
140         end = time.monotonic()
141         tdiff = end - start
142         errcode = response.operation & dns.DNS_RCODE
143         if tdiff > (DNS_REQUEST_TIMEOUT/5):
144             debug = True
145         else:
146             debug = False
147         if debug:
148             print("dns_hub: forwarder[%s] client[%s] name[%s][%s] %s response.operation[0x%x] tdiff[%s]\n" %
149                 (forwarder, self.client_address, name,
150                  self.dns_qtype_string(query.questions[0].question_type),
151                  self.dns_rcode_string(errcode), response.operation, tdiff))
152
153         try:
154             sock.sendto(send_packet, self.client_address)
155         except socket.error as err:
156             print("dns_hub: Error sending response to client[%s] for name[%s] tdiff[%s]: %s\n" %
157                 (self.client_address, name, tdiff, err))
158
159
160 class server_thread(threading.Thread):
161     def __init__(self, server):
162         threading.Thread.__init__(self)
163         self.server = server
164
165     def run(self):
166         self.server.serve_forever()
167         print("dns_hub: after serve_forever()")
168
169
170 def main():
171     timeout = int(sys.argv[1]) * 1000
172     timeout = min(timeout, 2**31 - 1)  # poll with 32-bit int can't take more
173     host = sys.argv[2]
174     server = sserver.UDPServer((host, int(53)), DnsHandler)
175     t = server_thread(server)
176     t.start()
177     p = select.poll()
178     stdin = sys.stdin.fileno()
179     p.register(stdin, select.POLLIN)
180     p.poll(timeout)
181     print("dns_hub: after poll()")
182     server.shutdown()
183     t.join()
184     print("dns_hub: before exit()")
185     sys.exit(0)
186
187 main()