3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Volker Lendecke 2017
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.
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.
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/>.
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
28 from samba.dcerpc import dns
29 import samba.ndr as ndr
31 if sys.version_info[0] < 3:
33 sserver = SocketServer
36 sserver = socketserver
38 DNS_REQUEST_TIMEOUT = 10
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]
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]
52 def dns_transaction_udp(self, packet, host):
53 "send a DNS query and read the reply"
56 send_packet = ndr.ndr_pack(packet)
57 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
58 s.settimeout(DNS_REQUEST_TIMEOUT)
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))
72 def get_pdc_ipv4_addr(self, lookup_name):
73 """Maps a DNS realm to the IPv4 address of the PDC for that testenv"""
75 realm_to_ip_mappings = self.server.realm_to_ip_mappings
77 # sort the realms so we find the longest-match first
78 testenv_realms = sorted(realm_to_ip_mappings.keys(), key=len)
79 testenv_realms.reverse()
81 for realm in testenv_realms:
82 if lookup_name.endswith(realm):
83 # return the corresponding IP address for this realm's PDC
84 return realm_to_ip_mappings[realm]
88 def forwarder(self, name):
91 # check for special cases used by tests (e.g. dns_forwarder.py)
92 if lname.endswith('an-address-that-will-not-resolve'):
94 if lname.endswith('dsfsdfs'):
96 if lname.endswith("torture1", 0, len(lname)-2):
97 # CATCH TORTURE100, TORTURE101, ...
99 if lname.endswith('_none_.example.com'):
101 if lname.endswith('torturedom.samba.example.com'):
104 # return the testenv PDC matching the realm being requested
105 return self.get_pdc_ipv4_addr(lname)
108 start = time.monotonic()
109 data, sock = self.request
110 query = ndr.ndr_unpack(dns.name_packet, data)
111 name = query.questions[0].name
112 forwarder = self.forwarder(name)
115 if forwarder is 'ignore':
117 elif forwarder is 'fail':
119 elif forwarder in ['torture', None]:
121 response.operation |= dns.DNS_FLAG_REPLY
122 response.operation |= dns.DNS_FLAG_RECURSION_AVAIL
123 response.operation |= dns.DNS_RCODE_NXDOMAIN
125 response = self.dns_transaction_udp(query, forwarder)
129 response.operation |= dns.DNS_FLAG_REPLY
130 response.operation |= dns.DNS_FLAG_RECURSION_AVAIL
131 response.operation |= dns.DNS_RCODE_SERVFAIL
133 send_packet = ndr.ndr_pack(response)
135 end = time.monotonic()
137 errcode = response.operation & dns.DNS_RCODE
138 if tdiff > (DNS_REQUEST_TIMEOUT/5):
143 print("dns_hub: forwarder[%s] client[%s] name[%s][%s] %s response.operation[0x%x] tdiff[%s]\n" %
144 (forwarder, self.client_address, name,
145 self.dns_qtype_string(query.questions[0].question_type),
146 self.dns_rcode_string(errcode), response.operation, tdiff))
149 sock.sendto(send_packet, self.client_address)
150 except socket.error as err:
151 print("dns_hub: Error sending response to client[%s] for name[%s] tdiff[%s]: %s\n" %
152 (self.client_address, name, tdiff, err))
155 class server_thread(threading.Thread):
156 def __init__(self, server):
157 threading.Thread.__init__(self)
161 self.server.serve_forever()
162 print("dns_hub: after serve_forever()")
166 timeout = int(sys.argv[1]) * 1000
167 timeout = min(timeout, 2**31 - 1) # poll with 32-bit int can't take more
170 server = sserver.UDPServer((host, int(53)), DnsHandler)
172 # we pass in the realm-to-IP mappings as a comma-separated key=value
173 # string. Convert this back into a dictionary that the DnsHandler can use
174 realm_mapping = dict(kv.split('=') for kv in sys.argv[3].split(','))
175 server.realm_to_ip_mappings = realm_mapping
177 print("dns_hub will proxy DNS requests for the following realms:")
178 for realm, ip in server.realm_to_ip_mappings.items():
179 print(" {0} ==> {1}".format(realm, ip))
181 t = server_thread(server)
184 stdin = sys.stdin.fileno()
185 p.register(stdin, select.POLLIN)
187 print("dns_hub: after poll()")
190 print("dns_hub: before exit()")