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/>.
18 from __future__ import print_function
27 import samba.ndr as ndr
28 from samba import credentials, param
29 from samba.tests import TestCase
30 from samba.dcerpc import dns, dnsp, dnsserver
31 from samba.netcmd.dns import TXTRecord, dns_record_match, data_to_dns_record
32 from samba.tests.subunitrun import SubunitOptions, TestProgram
33 import samba.getopt as options
37 parser = optparse.OptionParser("dns_forwarder.py <server name> <server ip> (dns forwarder)+ [options]")
38 sambaopts = options.SambaOptions(parser)
39 parser.add_option_group(sambaopts)
41 # This timeout only has relevance when testing against Windows
42 # Format errors tend to return patchy responses, so a timeout is needed.
43 parser.add_option("--timeout", type="int", dest="timeout",
44 help="Specify timeout for DNS requests")
46 # use command line creds if available
47 credopts = options.CredentialsOptions(parser)
48 parser.add_option_group(credopts)
49 subunitopts = SubunitOptions(parser)
50 parser.add_option_group(subunitopts)
52 opts, args = parser.parse_args()
54 lp = sambaopts.get_loadparm()
55 creds = credopts.get_credentials(lp)
57 timeout = opts.timeout
65 dns_servers = args[2:]
67 creds.set_krb_forwardable(credentials.NO_KRB_FORWARDABLE)
69 def make_txt_record(records):
70 rdata_txt = dns.txt_record()
71 s_list = dnsp.string_list()
72 s_list.count = len(records)
74 rdata_txt.txt = s_list
78 class DNSTest(TestCase):
80 errcodes = dict((v, k) for k, v in vars(dns).items() if k.startswith('DNS_RCODE_'))
82 def assert_dns_rcode_equals(self, packet, rcode):
83 "Helper function to check return code"
84 p_errcode = packet.operation & 0x000F
85 self.assertEquals(p_errcode, rcode, "Expected RCODE %s, got %s" %
86 (self.errcodes[rcode], self.errcodes[p_errcode]))
88 def assert_dns_opcode_equals(self, packet, opcode):
89 "Helper function to check opcode"
90 p_opcode = packet.operation & 0x7800
91 self.assertEquals(p_opcode, opcode, "Expected OPCODE %s, got %s" %
94 def make_name_packet(self, opcode, qid=None):
95 "Helper creating a dns.name_packet"
98 p.id = random.randint(0x0, 0xffff)
103 def finish_name_packet(self, packet, questions):
104 "Helper to finalize a dns.name_packet"
105 packet.qdcount = len(questions)
106 packet.questions = questions
108 def make_name_question(self, name, qtype, qclass):
109 "Helper creating a dns.name_question"
110 q = dns.name_question()
112 q.question_type = qtype
113 q.question_class = qclass
116 def get_dns_domain(self):
117 "Helper to get dns domain"
118 return self.creds.get_realm().lower()
120 def dns_transaction_udp(self, packet, host=server_ip,
121 dump=False, timeout=timeout):
122 "send a DNS query and read the reply"
125 send_packet = ndr.ndr_pack(packet)
127 print(self.hexdump(send_packet))
128 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
129 s.settimeout(timeout)
130 s.connect((host, 53))
131 s.send(send_packet, 0)
132 recv_packet = s.recv(2048, 0)
134 print(self.hexdump(recv_packet))
135 return ndr.ndr_unpack(dns.name_packet, recv_packet)
140 def make_cname_update(self, key, value):
141 p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
143 name = self.get_dns_domain()
144 u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
145 self.finish_name_packet(p, [u])
149 r.rr_type = dns.DNS_QTYPE_CNAME
150 r.rr_class = dns.DNS_QCLASS_IN
157 response = self.dns_transaction_udp(p)
158 self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
162 def contact_real_server(host, port):
163 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
164 s.connect((host, port))
168 class TestDnsForwarding(DNSTest):
169 def __init__(self, *args, **kwargs):
170 super(TestDnsForwarding, self).__init__(*args, **kwargs)
171 self.subprocesses = []
174 super(TestDnsForwarding, self).setUp()
175 self.server = server_name
176 self.server_ip = server_ip
180 def start_toy_server(self, host, port, id):
181 python = sys.executable
182 p = subprocess.Popen([python,
183 os.path.join(samba.source_tree_topdir(),
184 'python/samba/tests/'
185 'dns_forwarder_helpers/server.py'),
186 host, str(port), id])
187 self.subprocesses.append(p)
188 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
191 s.connect((host, port))
193 s.send('timeout 0', 0)
194 except socket.error as e:
195 if e.errno in (errno.ECONNREFUSED, errno.EHOSTUNREACH):
198 if p.returncode is not None:
199 self.fail("Toy server has managed to die already!")
204 super(TestDnsForwarding, self).tearDown()
205 for p in self.subprocesses:
208 def test_comatose_forwarder(self):
209 s = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
210 s.send("timeout 1000000", 0)
213 name = "an-address-that-will-not-resolve"
214 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
217 q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
220 self.finish_name_packet(p, questions)
221 send_packet = ndr.ndr_pack(p)
223 s.send(send_packet, 0)
226 s.recv(0xffff + 2, 0)
227 self.fail("DNS forwarder should have been inactive")
228 except socket.timeout:
229 # Expected forwarder to be dead
232 def test_no_active_forwarder(self):
233 ad = contact_real_server(server_ip, 53)
235 name = "dsfsfds.dsfsdfs"
236 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
239 q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
242 self.finish_name_packet(p, questions)
243 send_packet = ndr.ndr_pack(p)
245 self.finish_name_packet(p, questions)
246 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
247 send_packet = ndr.ndr_pack(p)
249 ad.send(send_packet, 0)
250 ad.settimeout(timeout)
252 data = ad.recv(0xffff + 2, 0)
253 data = ndr.ndr_unpack(dns.name_packet, data)
254 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_SERVFAIL)
255 self.assertEqual(data.ancount, 0)
256 except socket.timeout:
257 self.fail("DNS server is too slow (timeout %s)" % timeout)
259 def test_no_flag_recursive_forwarder(self):
260 ad = contact_real_server(server_ip, 53)
262 name = "dsfsfds.dsfsdfs"
263 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
266 q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
269 self.finish_name_packet(p, questions)
270 send_packet = ndr.ndr_pack(p)
272 self.finish_name_packet(p, questions)
273 # Leave off the recursive flag
274 send_packet = ndr.ndr_pack(p)
276 ad.send(send_packet, 0)
277 ad.settimeout(timeout)
279 data = ad.recv(0xffff + 2, 0)
280 data = ndr.ndr_unpack(dns.name_packet, data)
281 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_NXDOMAIN)
282 self.assertEqual(data.ancount, 0)
283 except socket.timeout:
284 self.fail("DNS server is too slow (timeout %s)" % timeout)
286 def test_single_forwarder(self):
287 s = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
288 ad = contact_real_server(server_ip, 53)
289 name = "dsfsfds.dsfsdfs"
290 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
293 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
297 self.finish_name_packet(p, questions)
298 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
299 send_packet = ndr.ndr_pack(p)
301 ad.send(send_packet, 0)
302 ad.settimeout(timeout)
304 data = ad.recv(0xffff + 2, 0)
305 data = ndr.ndr_unpack(dns.name_packet, data)
306 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
307 self.assertEqual('forwarder1', data.answers[0].rdata)
308 except socket.timeout:
309 self.fail("DNS server is too slow (timeout %s)" % timeout)
311 def test_single_forwarder_not_actually_there(self):
312 ad = contact_real_server(server_ip, 53)
313 name = "dsfsfds.dsfsdfs"
314 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
317 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
321 self.finish_name_packet(p, questions)
322 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
323 send_packet = ndr.ndr_pack(p)
325 ad.send(send_packet, 0)
326 ad.settimeout(timeout)
328 data = ad.recv(0xffff + 2, 0)
329 data = ndr.ndr_unpack(dns.name_packet, data)
330 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_SERVFAIL)
331 except socket.timeout:
332 self.fail("DNS server is too slow (timeout %s)" % timeout)
335 def test_single_forwarder_waiting_forever(self):
336 s = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
337 s.send('timeout 10000', 0)
338 ad = contact_real_server(server_ip, 53)
339 name = "dsfsfds.dsfsdfs"
340 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
343 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
347 self.finish_name_packet(p, questions)
348 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
349 send_packet = ndr.ndr_pack(p)
351 ad.send(send_packet, 0)
352 ad.settimeout(timeout)
354 data = ad.recv(0xffff + 2, 0)
355 data = ndr.ndr_unpack(dns.name_packet, data)
356 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_SERVFAIL)
357 except socket.timeout:
358 self.fail("DNS server is too slow (timeout %s)" % timeout)
360 def test_double_forwarder_first_frozen(self):
361 if len(dns_servers) < 2:
362 print("Ignoring test_double_forwarder_first_frozen")
364 s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
365 s2 = self.start_toy_server(dns_servers[1], 53, 'forwarder2')
366 s1.send('timeout 1000', 0)
367 ad = contact_real_server(server_ip, 53)
368 name = "dsfsfds.dsfsdfs"
369 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
372 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
376 self.finish_name_packet(p, questions)
377 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
378 send_packet = ndr.ndr_pack(p)
380 ad.send(send_packet, 0)
381 ad.settimeout(timeout)
383 data = ad.recv(0xffff + 2, 0)
384 data = ndr.ndr_unpack(dns.name_packet, data)
385 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
386 self.assertEqual('forwarder2', data.answers[0].rdata)
387 except socket.timeout:
388 self.fail("DNS server is too slow (timeout %s)" % timeout)
390 def test_double_forwarder_first_down(self):
391 if len(dns_servers) < 2:
392 print("Ignoring test_double_forwarder_first_down")
394 s2 = self.start_toy_server(dns_servers[1], 53, 'forwarder2')
395 ad = contact_real_server(server_ip, 53)
396 name = "dsfsfds.dsfsdfs"
397 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
400 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
404 self.finish_name_packet(p, questions)
405 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
406 send_packet = ndr.ndr_pack(p)
408 ad.send(send_packet, 0)
409 ad.settimeout(timeout)
411 data = ad.recv(0xffff + 2, 0)
412 data = ndr.ndr_unpack(dns.name_packet, data)
413 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
414 self.assertEqual('forwarder2', data.answers[0].rdata)
415 except socket.timeout:
416 self.fail("DNS server is too slow (timeout %s)" % timeout)
418 def test_double_forwarder_both_slow(self):
419 if len(dns_servers) < 2:
420 print("Ignoring test_double_forwarder_both_slow")
422 s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
423 s2 = self.start_toy_server(dns_servers[1], 53, 'forwarder2')
424 s1.send('timeout 1.5', 0)
425 s2.send('timeout 1.5', 0)
426 ad = contact_real_server(server_ip, 53)
427 name = "dsfsfds.dsfsdfs"
428 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
431 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
435 self.finish_name_packet(p, questions)
436 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
437 send_packet = ndr.ndr_pack(p)
439 ad.send(send_packet, 0)
440 ad.settimeout(timeout)
442 data = ad.recv(0xffff + 2, 0)
443 data = ndr.ndr_unpack(dns.name_packet, data)
444 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
445 self.assertEqual('forwarder1', data.answers[0].rdata)
446 except socket.timeout:
447 self.fail("DNS server is too slow (timeout %s)" % timeout)
449 def test_cname(self):
450 s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
452 ad = contact_real_server(server_ip, 53)
453 name = "resolve.cname"
454 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
457 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
461 self.finish_name_packet(p, questions)
462 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
463 send_packet = ndr.ndr_pack(p)
465 ad.send(send_packet, 0)
466 ad.settimeout(timeout)
468 data = ad.recv(0xffff + 2, 0)
469 data = ndr.ndr_unpack(dns.name_packet, data)
470 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
471 self.assertEqual(len(data.answers), 1)
472 self.assertEqual('forwarder1', data.answers[0].rdata)
473 except socket.timeout:
474 self.fail("DNS server is too slow (timeout %s)" % timeout)
476 def test_double_cname(self):
477 s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
479 name = 'resolve.cname.%s' % self.get_dns_domain()
480 self.make_cname_update(name, "dsfsfds.dsfsdfs")
482 ad = contact_real_server(server_ip, 53)
484 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
486 q = self.make_name_question(name, dns.DNS_QTYPE_A,
490 self.finish_name_packet(p, questions)
491 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
492 send_packet = ndr.ndr_pack(p)
494 ad.send(send_packet, 0)
495 ad.settimeout(timeout)
497 data = ad.recv(0xffff + 2, 0)
498 data = ndr.ndr_unpack(dns.name_packet, data)
499 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
500 self.assertEqual('forwarder1', data.answers[1].rdata)
501 except socket.timeout:
502 self.fail("DNS server is too slow (timeout %s)" % timeout)
504 def test_cname_forwarding_with_slow_server(self):
505 if len(dns_servers) < 2:
506 print("Ignoring test_cname_forwarding_with_slow_server")
508 s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
509 s2 = self.start_toy_server(dns_servers[1], 53, 'forwarder2')
510 s1.send('timeout 10000', 0)
512 name = 'resolve.cname.%s' % self.get_dns_domain()
513 self.make_cname_update(name, "dsfsfds.dsfsdfs")
515 ad = contact_real_server(server_ip, 53)
517 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
519 q = self.make_name_question(name, dns.DNS_QTYPE_A,
523 self.finish_name_packet(p, questions)
524 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
525 send_packet = ndr.ndr_pack(p)
527 ad.send(send_packet, 0)
528 ad.settimeout(timeout)
530 data = ad.recv(0xffff + 2, 0)
531 data = ndr.ndr_unpack(dns.name_packet, data)
532 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
533 self.assertEqual('forwarder2', data.answers[-1].rdata)
534 except socket.timeout:
535 self.fail("DNS server is too slow (timeout %s)" % timeout)
537 def test_cname_forwarding_with_server_down(self):
538 if len(dns_servers) < 2:
539 print("Ignoring test_cname_forwarding_with_server_down")
541 s2 = self.start_toy_server(dns_servers[1], 53, 'forwarder2')
543 name1 = 'resolve1.cname.%s' % self.get_dns_domain()
544 name2 = 'resolve2.cname.%s' % self.get_dns_domain()
545 self.make_cname_update(name1, name2)
546 self.make_cname_update(name2, "dsfsfds.dsfsdfs")
548 ad = contact_real_server(server_ip, 53)
550 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
552 q = self.make_name_question(name1, dns.DNS_QTYPE_A,
556 self.finish_name_packet(p, questions)
557 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
558 send_packet = ndr.ndr_pack(p)
560 ad.send(send_packet, 0)
561 ad.settimeout(timeout)
563 data = ad.recv(0xffff + 2, 0)
564 data = ndr.ndr_unpack(dns.name_packet, data)
565 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
566 self.assertEqual('forwarder2', data.answers[-1].rdata)
567 except socket.timeout:
568 self.fail("DNS server is too slow (timeout %s)" % timeout)
570 def test_cname_forwarding_with_lots_of_cnames(self):
571 name3 = 'resolve3.cname.%s' % self.get_dns_domain()
572 s1 = self.start_toy_server(dns_servers[0], 53, name3)
574 name1 = 'resolve1.cname.%s' % self.get_dns_domain()
575 name2 = 'resolve2.cname.%s' % self.get_dns_domain()
576 self.make_cname_update(name1, name2)
577 self.make_cname_update(name3, name1)
578 self.make_cname_update(name2, "dsfsfds.dsfsdfs")
580 ad = contact_real_server(server_ip, 53)
582 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
584 q = self.make_name_question(name1, dns.DNS_QTYPE_A,
588 self.finish_name_packet(p, questions)
589 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
590 send_packet = ndr.ndr_pack(p)
592 ad.send(send_packet, 0)
593 ad.settimeout(timeout)
595 data = ad.recv(0xffff + 2, 0)
596 data = ndr.ndr_unpack(dns.name_packet, data)
597 # This should cause a loop in Windows
598 # (which is restricted by a 20 CNAME limit)
600 # The reason it doesn't here is because forwarded CNAME have no
601 # additional processing in the internal DNS server.
602 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
603 self.assertEqual(name3, data.answers[-1].rdata)
604 except socket.timeout:
605 self.fail("DNS server is too slow (timeout %s)" % timeout)
607 TestProgram(module=__name__, opts=subunitopts)