1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Kai Blin <kai@samba.org> 2011
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import samba
.ndr
as ndr
26 from samba
import credentials
27 from samba
.tests
import TestCase
28 from samba
.dcerpc
import dns
, dnsp
29 from samba
.tests
.subunitrun
import SubunitOptions
, TestProgram
30 import samba
.getopt
as options
36 parser
= optparse
.OptionParser("dns_forwarder.py <server name> <server ip> (dns forwarder)+ [options]")
37 sambaopts
= options
.SambaOptions(parser
)
38 parser
.add_option_group(sambaopts
)
40 # This timeout only has relevance when testing against Windows
41 # Format errors tend to return patchy responses, so a timeout is needed.
42 parser
.add_option("--timeout", type="int", dest
="timeout",
43 help="Specify timeout for DNS requests")
45 # use command line creds if available
46 credopts
= options
.CredentialsOptions(parser
)
47 parser
.add_option_group(credopts
)
48 subunitopts
= SubunitOptions(parser
)
49 parser
.add_option_group(subunitopts
)
51 opts
, args
= parser
.parse_args()
53 lp
= sambaopts
.get_loadparm()
54 creds
= credopts
.get_credentials(lp
)
56 timeout
= opts
.timeout
64 dns_servers
= args
[2:]
66 creds
.set_krb_forwardable(credentials
.NO_KRB_FORWARDABLE
)
69 class DNSTest(TestCase
):
71 errcodes
= dict((v
, k
) for k
, v
in vars(dns
).items() if k
.startswith('DNS_RCODE_'))
73 def assert_dns_rcode_equals(self
, packet
, rcode
):
74 "Helper function to check return code"
75 p_errcode
= packet
.operation
& dns
.DNS_RCODE
76 self
.assertEqual(p_errcode
, rcode
, "Expected RCODE %s, got %s" %
77 (self
.errcodes
[rcode
], self
.errcodes
[p_errcode
]))
79 def assert_dns_opcode_equals(self
, packet
, opcode
):
80 "Helper function to check opcode"
81 p_opcode
= packet
.operation
& dns
.DNS_OPCODE
82 self
.assertEqual(p_opcode
, opcode
, "Expected OPCODE %s, got %s" %
85 def make_name_packet(self
, opcode
, qid
=None):
86 "Helper creating a dns.name_packet"
89 p
.id = random
.randint(0x0, 0xffff)
94 def finish_name_packet(self
, packet
, questions
):
95 "Helper to finalize a dns.name_packet"
96 packet
.qdcount
= len(questions
)
97 packet
.questions
= questions
99 def make_name_question(self
, name
, qtype
, qclass
):
100 "Helper creating a dns.name_question"
101 q
= dns
.name_question()
103 q
.question_type
= qtype
104 q
.question_class
= qclass
107 def get_dns_domain(self
):
108 "Helper to get dns domain"
109 return self
.creds
.get_realm().lower()
111 def dns_transaction_udp(self
, packet
, host
=server_ip
,
112 dump
=False, timeout
=timeout
):
113 "send a DNS query and read the reply"
116 send_packet
= ndr
.ndr_pack(packet
)
118 print(self
.hexdump(send_packet
))
119 s
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
, 0)
120 s
.settimeout(timeout
)
121 s
.connect((host
, 53))
122 s
.send(send_packet
, 0)
123 recv_packet
= s
.recv(2048, 0)
125 print(self
.hexdump(recv_packet
))
126 return ndr
.ndr_unpack(dns
.name_packet
, recv_packet
)
131 def make_cname_update(self
, key
, value
):
132 p
= self
.make_name_packet(dns
.DNS_OPCODE_UPDATE
)
134 name
= self
.get_dns_domain()
135 u
= self
.make_name_question(name
, dns
.DNS_QTYPE_SOA
, dns
.DNS_QCLASS_IN
)
136 self
.finish_name_packet(p
, [u
])
140 r
.rr_type
= dns
.DNS_QTYPE_CNAME
141 r
.rr_class
= dns
.DNS_QCLASS_IN
148 response
= self
.dns_transaction_udp(p
)
149 self
.assert_dns_rcode_equals(response
, dns
.DNS_RCODE_OK
)
152 def contact_real_server(host
, port
):
153 s
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
, 0)
154 s
.connect((host
, port
))
158 class TestDnsForwarding(DNSTest
):
159 def __init__(self
, *args
, **kwargs
):
160 super(TestDnsForwarding
, self
).__init
__(*args
, **kwargs
)
161 self
.subprocesses
= []
164 super(TestDnsForwarding
, self
).setUp()
165 self
.server
= server_name
166 self
.server_ip
= server_ip
170 def start_toy_server(self
, host
, port
, id):
171 python
= sys
.executable
172 p
= subprocess
.Popen([python
,
173 os
.path
.join(samba
.source_tree_topdir(),
174 'python/samba/tests/'
175 'dns_forwarder_helpers/server.py'),
176 host
, str(port
), id])
177 self
.subprocesses
.append(p
)
178 if (host
.find(':') != -1):
179 s
= socket
.socket(socket
.AF_INET6
, socket
.SOCK_DGRAM
, 0)
181 s
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
, 0)
184 s
.connect((host
, port
))
186 s
.send(b
'timeout 0', 0)
187 except socket
.error
as e
:
188 if e
.errno
in (errno
.ECONNREFUSED
, errno
.EHOSTUNREACH
):
191 if p
.returncode
is not None:
192 self
.fail("Toy server has managed to die already!")
197 super(TestDnsForwarding
, self
).tearDown()
198 for p
in self
.subprocesses
:
201 def test_comatose_forwarder(self
):
202 s
= self
.start_toy_server(dns_servers
[0], 53, 'forwarder1')
203 s
.send(b
"timeout 1000000", 0)
206 name
= "an-address-that-will-not-resolve"
207 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
210 q
= self
.make_name_question(name
, dns
.DNS_QTYPE_TXT
, dns
.DNS_QCLASS_IN
)
213 self
.finish_name_packet(p
, questions
)
214 send_packet
= ndr
.ndr_pack(p
)
216 s
.send(send_packet
, 0)
219 s
.recv(0xffff + 2, 0)
220 self
.fail("DNS forwarder should have been inactive")
221 except socket
.timeout
:
222 # Expected forwarder to be dead
225 def test_no_active_forwarder(self
):
226 ad
= contact_real_server(server_ip
, 53)
228 name
= "dsfsfds.dsfsdfs"
229 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
232 q
= self
.make_name_question(name
, dns
.DNS_QTYPE_TXT
, dns
.DNS_QCLASS_IN
)
235 self
.finish_name_packet(p
, questions
)
236 send_packet
= ndr
.ndr_pack(p
)
238 self
.finish_name_packet(p
, questions
)
239 p
.operation |
= dns
.DNS_FLAG_RECURSION_DESIRED
240 send_packet
= ndr
.ndr_pack(p
)
242 ad
.send(send_packet
, 0)
243 ad
.settimeout(timeout
)
245 data
= ad
.recv(0xffff + 2, 0)
246 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
247 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_SERVFAIL
)
248 self
.assertEqual(data
.ancount
, 0)
249 except socket
.timeout
:
250 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
252 def test_no_flag_recursive_forwarder(self
):
253 ad
= contact_real_server(server_ip
, 53)
255 name
= "dsfsfds.dsfsdfs"
256 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
259 q
= self
.make_name_question(name
, dns
.DNS_QTYPE_TXT
, dns
.DNS_QCLASS_IN
)
262 self
.finish_name_packet(p
, questions
)
263 send_packet
= ndr
.ndr_pack(p
)
265 self
.finish_name_packet(p
, questions
)
266 # Leave off the recursive flag
267 send_packet
= ndr
.ndr_pack(p
)
269 ad
.send(send_packet
, 0)
270 ad
.settimeout(timeout
)
272 data
= ad
.recv(0xffff + 2, 0)
273 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
274 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_NXDOMAIN
)
275 self
.assertEqual(data
.ancount
, 0)
276 except socket
.timeout
:
277 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
279 def test_single_forwarder(self
):
280 s
= self
.start_toy_server(dns_servers
[0], 53, 'forwarder1')
281 ad
= contact_real_server(server_ip
, 53)
282 name
= "dsfsfds.dsfsdfs"
283 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
286 q
= self
.make_name_question(name
, dns
.DNS_QTYPE_CNAME
,
290 self
.finish_name_packet(p
, questions
)
291 p
.operation |
= dns
.DNS_FLAG_RECURSION_DESIRED
292 send_packet
= ndr
.ndr_pack(p
)
294 ad
.send(send_packet
, 0)
295 ad
.settimeout(timeout
)
297 data
= ad
.recv(0xffff + 2, 0)
298 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
299 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_OK
)
300 self
.assertEqual('forwarder1', data
.answers
[0].rdata
)
301 except socket
.timeout
:
302 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
304 def test_single_forwarder_not_actually_there(self
):
305 ad
= contact_real_server(server_ip
, 53)
306 name
= "dsfsfds.dsfsdfs"
307 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
310 q
= self
.make_name_question(name
, dns
.DNS_QTYPE_CNAME
,
314 self
.finish_name_packet(p
, questions
)
315 p
.operation |
= dns
.DNS_FLAG_RECURSION_DESIRED
316 send_packet
= ndr
.ndr_pack(p
)
318 ad
.send(send_packet
, 0)
319 ad
.settimeout(timeout
)
321 data
= ad
.recv(0xffff + 2, 0)
322 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
323 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_SERVFAIL
)
324 except socket
.timeout
:
325 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
327 def test_single_forwarder_waiting_forever(self
):
328 s
= self
.start_toy_server(dns_servers
[0], 53, 'forwarder1')
329 s
.send(b
'timeout 10000', 0)
330 ad
= contact_real_server(server_ip
, 53)
331 name
= "dsfsfds.dsfsdfs"
332 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
335 q
= self
.make_name_question(name
, dns
.DNS_QTYPE_CNAME
,
339 self
.finish_name_packet(p
, questions
)
340 p
.operation |
= dns
.DNS_FLAG_RECURSION_DESIRED
341 send_packet
= ndr
.ndr_pack(p
)
343 ad
.send(send_packet
, 0)
344 ad
.settimeout(timeout
)
346 data
= ad
.recv(0xffff + 2, 0)
347 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
348 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_SERVFAIL
)
349 except socket
.timeout
:
350 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
352 def test_double_forwarder_first_frozen(self
):
353 if len(dns_servers
) < 2:
354 print("Ignoring test_double_forwarder_first_frozen")
356 s1
= self
.start_toy_server(dns_servers
[0], 53, 'forwarder1')
357 s2
= self
.start_toy_server(dns_servers
[1], DNS_PORT2
, 'forwarder2')
358 s1
.send(b
'timeout 1000', 0)
359 ad
= contact_real_server(server_ip
, 53)
360 name
= "dsfsfds.dsfsdfs"
361 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
364 q
= self
.make_name_question(name
, dns
.DNS_QTYPE_CNAME
,
368 self
.finish_name_packet(p
, questions
)
369 p
.operation |
= dns
.DNS_FLAG_RECURSION_DESIRED
370 send_packet
= ndr
.ndr_pack(p
)
372 ad
.send(send_packet
, 0)
373 ad
.settimeout(timeout
)
375 data
= ad
.recv(0xffff + 2, 0)
376 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
377 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_OK
)
378 self
.assertEqual('forwarder2', data
.answers
[0].rdata
)
379 except socket
.timeout
:
380 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
382 def test_double_forwarder_first_down(self
):
383 if len(dns_servers
) < 2:
384 print("Ignoring test_double_forwarder_first_down")
386 s2
= self
.start_toy_server(dns_servers
[1], DNS_PORT2
, 'forwarder2')
387 ad
= contact_real_server(server_ip
, 53)
388 name
= "dsfsfds.dsfsdfs"
389 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
392 q
= self
.make_name_question(name
, dns
.DNS_QTYPE_CNAME
,
396 self
.finish_name_packet(p
, questions
)
397 p
.operation |
= dns
.DNS_FLAG_RECURSION_DESIRED
398 send_packet
= ndr
.ndr_pack(p
)
400 ad
.send(send_packet
, 0)
401 ad
.settimeout(timeout
)
403 data
= ad
.recv(0xffff + 2, 0)
404 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
405 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_OK
)
406 self
.assertEqual('forwarder2', data
.answers
[0].rdata
)
407 except socket
.timeout
:
408 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
410 def test_double_forwarder_both_slow(self
):
411 if len(dns_servers
) < 2:
412 print("Ignoring test_double_forwarder_both_slow")
414 s1
= self
.start_toy_server(dns_servers
[0], 53, 'forwarder1')
415 s2
= self
.start_toy_server(dns_servers
[1], DNS_PORT2
, 'forwarder2')
416 s1
.send(b
'timeout 1.5', 0)
417 s2
.send(b
'timeout 1.5', 0)
418 ad
= contact_real_server(server_ip
, 53)
419 name
= "dsfsfds.dsfsdfs"
420 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
423 q
= self
.make_name_question(name
, dns
.DNS_QTYPE_CNAME
,
427 self
.finish_name_packet(p
, questions
)
428 p
.operation |
= dns
.DNS_FLAG_RECURSION_DESIRED
429 send_packet
= ndr
.ndr_pack(p
)
431 ad
.send(send_packet
, 0)
432 ad
.settimeout(timeout
)
434 data
= ad
.recv(0xffff + 2, 0)
435 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
436 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_OK
)
437 self
.assertEqual('forwarder1', data
.answers
[0].rdata
)
438 except socket
.timeout
:
439 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
441 def test_cname(self
):
442 s1
= self
.start_toy_server(dns_servers
[0], 53, 'forwarder1')
444 ad
= contact_real_server(server_ip
, 53)
445 name
= "resolve.cname"
446 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
449 q
= self
.make_name_question(name
, dns
.DNS_QTYPE_CNAME
,
453 self
.finish_name_packet(p
, questions
)
454 p
.operation |
= dns
.DNS_FLAG_RECURSION_DESIRED
455 send_packet
= ndr
.ndr_pack(p
)
457 ad
.send(send_packet
, 0)
458 ad
.settimeout(timeout
)
460 data
= ad
.recv(0xffff + 2, 0)
461 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
462 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_OK
)
463 self
.assertEqual(len(data
.answers
), 1)
464 self
.assertEqual('forwarder1', data
.answers
[0].rdata
)
465 except socket
.timeout
:
466 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
468 def test_double_cname(self
):
469 s1
= self
.start_toy_server(dns_servers
[0], 53, 'forwarder1')
471 name
= 'resolve.cname.%s' % self
.get_dns_domain()
472 self
.make_cname_update(name
, "dsfsfds.dsfsdfs")
474 ad
= contact_real_server(server_ip
, 53)
476 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
478 q
= self
.make_name_question(name
, dns
.DNS_QTYPE_A
,
482 self
.finish_name_packet(p
, questions
)
483 p
.operation |
= dns
.DNS_FLAG_RECURSION_DESIRED
484 send_packet
= ndr
.ndr_pack(p
)
486 ad
.send(send_packet
, 0)
487 ad
.settimeout(timeout
)
489 data
= ad
.recv(0xffff + 2, 0)
490 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
491 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_OK
)
492 self
.assertEqual('forwarder1', data
.answers
[1].rdata
)
493 except socket
.timeout
:
494 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
496 def test_cname_forwarding_with_slow_server(self
):
497 if len(dns_servers
) < 2:
498 print("Ignoring test_cname_forwarding_with_slow_server")
500 s1
= self
.start_toy_server(dns_servers
[0], 53, 'forwarder1')
501 s2
= self
.start_toy_server(dns_servers
[1], DNS_PORT2
, 'forwarder2')
502 s1
.send(b
'timeout 10000', 0)
504 name
= 'resolve.cname.%s' % self
.get_dns_domain()
505 self
.make_cname_update(name
, "dsfsfds.dsfsdfs")
507 ad
= contact_real_server(server_ip
, 53)
509 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
511 q
= self
.make_name_question(name
, dns
.DNS_QTYPE_A
,
515 self
.finish_name_packet(p
, questions
)
516 p
.operation |
= dns
.DNS_FLAG_RECURSION_DESIRED
517 send_packet
= ndr
.ndr_pack(p
)
519 ad
.send(send_packet
, 0)
520 ad
.settimeout(timeout
)
522 data
= ad
.recv(0xffff + 2, 0)
523 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
524 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_OK
)
525 self
.assertEqual('forwarder2', data
.answers
[-1].rdata
)
526 except socket
.timeout
:
527 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
529 def test_cname_forwarding_with_server_down(self
):
530 if len(dns_servers
) < 2:
531 print("Ignoring test_cname_forwarding_with_server_down")
533 s2
= self
.start_toy_server(dns_servers
[1], DNS_PORT2
, 'forwarder2')
535 name1
= 'resolve1.cname.%s' % self
.get_dns_domain()
536 name2
= 'resolve2.cname.%s' % self
.get_dns_domain()
537 self
.make_cname_update(name1
, name2
)
538 self
.make_cname_update(name2
, "dsfsfds.dsfsdfs")
540 ad
= contact_real_server(server_ip
, 53)
542 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
544 q
= self
.make_name_question(name1
, dns
.DNS_QTYPE_A
,
548 self
.finish_name_packet(p
, questions
)
549 p
.operation |
= dns
.DNS_FLAG_RECURSION_DESIRED
550 send_packet
= ndr
.ndr_pack(p
)
552 ad
.send(send_packet
, 0)
553 ad
.settimeout(timeout
)
555 data
= ad
.recv(0xffff + 2, 0)
556 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
557 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_OK
)
558 self
.assertEqual('forwarder2', data
.answers
[-1].rdata
)
559 except socket
.timeout
:
560 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
562 def test_cname_forwarding_with_lots_of_cnames(self
):
563 name3
= 'resolve3.cname.%s' % self
.get_dns_domain()
564 s1
= self
.start_toy_server(dns_servers
[0], 53, name3
)
566 name1
= 'resolve1.cname.%s' % self
.get_dns_domain()
567 name2
= 'resolve2.cname.%s' % self
.get_dns_domain()
568 self
.make_cname_update(name1
, name2
)
569 self
.make_cname_update(name3
, name1
)
570 self
.make_cname_update(name2
, "dsfsfds.dsfsdfs")
572 ad
= contact_real_server(server_ip
, 53)
574 p
= self
.make_name_packet(dns
.DNS_OPCODE_QUERY
)
576 q
= self
.make_name_question(name1
, dns
.DNS_QTYPE_A
,
580 self
.finish_name_packet(p
, questions
)
581 p
.operation |
= dns
.DNS_FLAG_RECURSION_DESIRED
582 send_packet
= ndr
.ndr_pack(p
)
584 ad
.send(send_packet
, 0)
585 ad
.settimeout(timeout
)
587 data
= ad
.recv(0xffff + 2, 0)
588 data
= ndr
.ndr_unpack(dns
.name_packet
, data
)
589 # This should cause a loop in Windows
590 # (which is restricted by a 20 CNAME limit)
592 # The reason it doesn't here is because forwarded CNAME have no
593 # additional processing in the internal DNS server.
594 self
.assert_dns_rcode_equals(data
, dns
.DNS_RCODE_OK
)
595 self
.assertEqual(name3
, data
.answers
[-1].rdata
)
596 except socket
.timeout
:
597 self
.fail("DNS server is too slow (timeout %s)" % timeout
)
600 TestProgram(module
=__name__
, opts
=subunitopts
)