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()")