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/>.
26 import samba.ndr as ndr
27 from samba import credentials, param
28 from samba.tests import TestCase
29 from samba.dcerpc import dns, dnsp, dnsserver
30 from samba.netcmd.dns import TXTRecord, dns_record_match, data_to_dns_record
31 from samba.tests.subunitrun import SubunitOptions, TestProgram
32 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)
68 def make_txt_record(records):
69 rdata_txt = dns.txt_record()
70 s_list = dnsp.string_list()
71 s_list.count = len(records)
73 rdata_txt.txt = s_list
77 class DNSTest(TestCase):
79 errcodes = dict((v, k) for k, v in vars(dns).items() if k.startswith('DNS_RCODE_'))
81 def assert_dns_rcode_equals(self, packet, rcode):
82 "Helper function to check return code"
83 p_errcode = packet.operation & 0x000F
84 self.assertEquals(p_errcode, rcode, "Expected RCODE %s, got %s" %
85 (self.errcodes[rcode], self.errcodes[p_errcode]))
87 def assert_dns_opcode_equals(self, packet, opcode):
88 "Helper function to check opcode"
89 p_opcode = packet.operation & 0x7800
90 self.assertEquals(p_opcode, opcode, "Expected OPCODE %s, got %s" %
93 def make_name_packet(self, opcode, qid=None):
94 "Helper creating a dns.name_packet"
97 p.id = random.randint(0x0, 0xffff)
102 def finish_name_packet(self, packet, questions):
103 "Helper to finalize a dns.name_packet"
104 packet.qdcount = len(questions)
105 packet.questions = questions
107 def make_name_question(self, name, qtype, qclass):
108 "Helper creating a dns.name_question"
109 q = dns.name_question()
111 q.question_type = qtype
112 q.question_class = qclass
115 def get_dns_domain(self):
116 "Helper to get dns domain"
117 return self.creds.get_realm().lower()
119 def dns_transaction_udp(self, packet, host=server_ip,
120 dump=False, timeout=timeout):
121 "send a DNS query and read the reply"
124 send_packet = ndr.ndr_pack(packet)
126 print self.hexdump(send_packet)
127 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
128 s.settimeout(timeout)
129 s.connect((host, 53))
130 s.send(send_packet, 0)
131 recv_packet = s.recv(2048, 0)
133 print self.hexdump(recv_packet)
134 return ndr.ndr_unpack(dns.name_packet, recv_packet)
139 def make_cname_update(self, key, value):
140 p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
142 name = self.get_dns_domain()
143 u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
144 self.finish_name_packet(p, [u])
148 r.rr_type = dns.DNS_QTYPE_CNAME
149 r.rr_class = dns.DNS_QCLASS_IN
156 response = self.dns_transaction_udp(p)
157 self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
161 def contact_real_server(host, port):
162 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
163 s.connect((host, port))
167 class TestDnsForwarding(DNSTest):
168 def __init__(self, *args, **kwargs):
169 super(TestDnsForwarding, self).__init__(*args, **kwargs)
170 self.subprocesses = []
173 super(TestDnsForwarding, self).setUp()
174 self.server = server_name
175 self.server_ip = server_ip
179 def start_toy_server(self, host, port, id):
180 python = sys.executable
181 p = subprocess.Popen([python,
182 os.path.join(samba.source_tree_topdir(),
183 'python/samba/tests/'
184 'dns_forwarder_helpers/server.py'),
185 host, str(port), id])
186 self.subprocesses.append(p)
187 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
188 for i in xrange(300):
190 s.connect((host, port))
192 s.send('timeout 0', 0)
193 except socket.error as e:
194 if e.errno in (errno.ECONNREFUSED, errno.EHOSTUNREACH):
197 if p.returncode is not None:
198 self.fail("Toy server has managed to die already!")
203 super(TestDnsForwarding, self).tearDown()
204 for p in self.subprocesses:
207 def test_comatose_forwarder(self):
208 s = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
209 s.send("timeout 1000000", 0)
212 name = "an-address-that-will-not-resolve"
213 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
216 q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
219 self.finish_name_packet(p, questions)
220 send_packet = ndr.ndr_pack(p)
222 s.send(send_packet, 0)
225 s.recv(0xffff + 2, 0)
226 self.fail("DNS forwarder should have been inactive")
227 except socket.timeout:
228 # Expected forwarder to be dead
231 def test_no_active_forwarder(self):
232 ad = contact_real_server(server_ip, 53)
234 name = "dsfsfds.dsfsdfs"
235 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
238 q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
241 self.finish_name_packet(p, questions)
242 send_packet = ndr.ndr_pack(p)
244 self.finish_name_packet(p, questions)
245 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
246 send_packet = ndr.ndr_pack(p)
248 ad.send(send_packet, 0)
249 ad.settimeout(timeout)
251 data = ad.recv(0xffff + 2, 0)
252 data = ndr.ndr_unpack(dns.name_packet, data)
253 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_SERVFAIL)
254 self.assertEqual(data.ancount, 0)
255 except socket.timeout:
256 self.fail("DNS server is too slow (timeout %s)" % timeout)
258 def test_no_flag_recursive_forwarder(self):
259 ad = contact_real_server(server_ip, 53)
261 name = "dsfsfds.dsfsdfs"
262 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
265 q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
268 self.finish_name_packet(p, questions)
269 send_packet = ndr.ndr_pack(p)
271 self.finish_name_packet(p, questions)
272 # Leave off the recursive flag
273 send_packet = ndr.ndr_pack(p)
275 ad.send(send_packet, 0)
276 ad.settimeout(timeout)
278 data = ad.recv(0xffff + 2, 0)
279 data = ndr.ndr_unpack(dns.name_packet, data)
280 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_NXDOMAIN)
281 self.assertEqual(data.ancount, 0)
282 except socket.timeout:
283 self.fail("DNS server is too slow (timeout %s)" % timeout)
285 def test_single_forwarder(self):
286 s = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
287 ad = contact_real_server(server_ip, 53)
288 name = "dsfsfds.dsfsdfs"
289 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
292 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
296 self.finish_name_packet(p, questions)
297 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
298 send_packet = ndr.ndr_pack(p)
300 ad.send(send_packet, 0)
301 ad.settimeout(timeout)
303 data = ad.recv(0xffff + 2, 0)
304 data = ndr.ndr_unpack(dns.name_packet, data)
305 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
306 self.assertEqual('forwarder1', data.answers[0].rdata)
307 except socket.timeout:
308 self.fail("DNS server is too slow (timeout %s)" % timeout)
310 def test_single_forwarder_not_actually_there(self):
311 ad = contact_real_server(server_ip, 53)
312 name = "dsfsfds.dsfsdfs"
313 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
316 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
320 self.finish_name_packet(p, questions)
321 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
322 send_packet = ndr.ndr_pack(p)
324 ad.send(send_packet, 0)
325 ad.settimeout(timeout)
327 data = ad.recv(0xffff + 2, 0)
328 data = ndr.ndr_unpack(dns.name_packet, data)
329 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_SERVFAIL)
330 except socket.timeout:
331 self.fail("DNS server is too slow (timeout %s)" % timeout)
334 def test_single_forwarder_waiting_forever(self):
335 s = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
336 s.send('timeout 10000', 0)
337 ad = contact_real_server(server_ip, 53)
338 name = "dsfsfds.dsfsdfs"
339 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
342 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
346 self.finish_name_packet(p, questions)
347 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
348 send_packet = ndr.ndr_pack(p)
350 ad.send(send_packet, 0)
351 ad.settimeout(timeout)
353 data = ad.recv(0xffff + 2, 0)
354 data = ndr.ndr_unpack(dns.name_packet, data)
355 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_SERVFAIL)
356 except socket.timeout:
357 self.fail("DNS server is too slow (timeout %s)" % timeout)
359 def test_double_forwarder_first_frozen(self):
360 if len(dns_servers) < 2:
361 print "Ignoring test_double_forwarder_first_frozen"
363 s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
364 s2 = self.start_toy_server(dns_servers[1], 53, 'forwarder2')
365 s1.send('timeout 1000', 0)
366 ad = contact_real_server(server_ip, 53)
367 name = "dsfsfds.dsfsdfs"
368 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
371 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
375 self.finish_name_packet(p, questions)
376 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
377 send_packet = ndr.ndr_pack(p)
379 ad.send(send_packet, 0)
380 ad.settimeout(timeout)
382 data = ad.recv(0xffff + 2, 0)
383 data = ndr.ndr_unpack(dns.name_packet, data)
384 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
385 self.assertEqual('forwarder2', data.answers[0].rdata)
386 except socket.timeout:
387 self.fail("DNS server is too slow (timeout %s)" % timeout)
389 def test_double_forwarder_first_down(self):
390 if len(dns_servers) < 2:
391 print "Ignoring test_double_forwarder_first_down"
393 s2 = self.start_toy_server(dns_servers[1], 53, 'forwarder2')
394 ad = contact_real_server(server_ip, 53)
395 name = "dsfsfds.dsfsdfs"
396 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
399 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
403 self.finish_name_packet(p, questions)
404 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
405 send_packet = ndr.ndr_pack(p)
407 ad.send(send_packet, 0)
408 ad.settimeout(timeout)
410 data = ad.recv(0xffff + 2, 0)
411 data = ndr.ndr_unpack(dns.name_packet, data)
412 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
413 self.assertEqual('forwarder2', data.answers[0].rdata)
414 except socket.timeout:
415 self.fail("DNS server is too slow (timeout %s)" % timeout)
417 def test_double_forwarder_both_slow(self):
418 if len(dns_servers) < 2:
419 print "Ignoring test_double_forwarder_both_slow"
421 s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
422 s2 = self.start_toy_server(dns_servers[1], 53, 'forwarder2')
423 s1.send('timeout 1.5', 0)
424 s2.send('timeout 1.5', 0)
425 ad = contact_real_server(server_ip, 53)
426 name = "dsfsfds.dsfsdfs"
427 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
430 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
434 self.finish_name_packet(p, questions)
435 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
436 send_packet = ndr.ndr_pack(p)
438 ad.send(send_packet, 0)
439 ad.settimeout(timeout)
441 data = ad.recv(0xffff + 2, 0)
442 data = ndr.ndr_unpack(dns.name_packet, data)
443 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
444 self.assertEqual('forwarder1', data.answers[0].rdata)
445 except socket.timeout:
446 self.fail("DNS server is too slow (timeout %s)" % timeout)
448 def test_cname(self):
449 s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
451 ad = contact_real_server(server_ip, 53)
452 name = "resolve.cname"
453 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
456 q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
460 self.finish_name_packet(p, questions)
461 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
462 send_packet = ndr.ndr_pack(p)
464 ad.send(send_packet, 0)
465 ad.settimeout(timeout)
467 data = ad.recv(0xffff + 2, 0)
468 data = ndr.ndr_unpack(dns.name_packet, data)
469 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
470 self.assertEqual(len(data.answers), 1)
471 self.assertEqual('forwarder1', data.answers[0].rdata)
472 except socket.timeout:
473 self.fail("DNS server is too slow (timeout %s)" % timeout)
475 def test_double_cname(self):
476 s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
478 name = 'resolve.cname.%s' % self.get_dns_domain()
479 self.make_cname_update(name, "dsfsfds.dsfsdfs")
481 ad = contact_real_server(server_ip, 53)
483 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
485 q = self.make_name_question(name, dns.DNS_QTYPE_A,
489 self.finish_name_packet(p, questions)
490 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
491 send_packet = ndr.ndr_pack(p)
493 ad.send(send_packet, 0)
494 ad.settimeout(timeout)
496 data = ad.recv(0xffff + 2, 0)
497 data = ndr.ndr_unpack(dns.name_packet, data)
498 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
499 self.assertEqual('forwarder1', data.answers[1].rdata)
500 except socket.timeout:
501 self.fail("DNS server is too slow (timeout %s)" % timeout)
503 def test_cname_forwarding_with_slow_server(self):
504 if len(dns_servers) < 2:
505 print "Ignoring test_cname_forwarding_with_slow_server"
507 s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
508 s2 = self.start_toy_server(dns_servers[1], 53, 'forwarder2')
509 s1.send('timeout 10000', 0)
511 name = 'resolve.cname.%s' % self.get_dns_domain()
512 self.make_cname_update(name, "dsfsfds.dsfsdfs")
514 ad = contact_real_server(server_ip, 53)
516 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
518 q = self.make_name_question(name, dns.DNS_QTYPE_A,
522 self.finish_name_packet(p, questions)
523 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
524 send_packet = ndr.ndr_pack(p)
526 ad.send(send_packet, 0)
527 ad.settimeout(timeout)
529 data = ad.recv(0xffff + 2, 0)
530 data = ndr.ndr_unpack(dns.name_packet, data)
531 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
532 self.assertEqual('forwarder2', data.answers[-1].rdata)
533 except socket.timeout:
534 self.fail("DNS server is too slow (timeout %s)" % timeout)
536 def test_cname_forwarding_with_server_down(self):
537 if len(dns_servers) < 2:
538 print "Ignoring test_cname_forwarding_with_server_down"
540 s2 = self.start_toy_server(dns_servers[1], 53, 'forwarder2')
542 name1 = 'resolve1.cname.%s' % self.get_dns_domain()
543 name2 = 'resolve2.cname.%s' % self.get_dns_domain()
544 self.make_cname_update(name1, name2)
545 self.make_cname_update(name2, "dsfsfds.dsfsdfs")
547 ad = contact_real_server(server_ip, 53)
549 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
551 q = self.make_name_question(name1, dns.DNS_QTYPE_A,
555 self.finish_name_packet(p, questions)
556 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
557 send_packet = ndr.ndr_pack(p)
559 ad.send(send_packet, 0)
560 ad.settimeout(timeout)
562 data = ad.recv(0xffff + 2, 0)
563 data = ndr.ndr_unpack(dns.name_packet, data)
564 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
565 self.assertEqual('forwarder2', data.answers[-1].rdata)
566 except socket.timeout:
567 self.fail("DNS server is too slow (timeout %s)" % timeout)
569 def test_cname_forwarding_with_lots_of_cnames(self):
570 name3 = 'resolve3.cname.%s' % self.get_dns_domain()
571 s1 = self.start_toy_server(dns_servers[0], 53, name3)
573 name1 = 'resolve1.cname.%s' % self.get_dns_domain()
574 name2 = 'resolve2.cname.%s' % self.get_dns_domain()
575 self.make_cname_update(name1, name2)
576 self.make_cname_update(name3, name1)
577 self.make_cname_update(name2, "dsfsfds.dsfsdfs")
579 ad = contact_real_server(server_ip, 53)
581 p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
583 q = self.make_name_question(name1, dns.DNS_QTYPE_A,
587 self.finish_name_packet(p, questions)
588 p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
589 send_packet = ndr.ndr_pack(p)
591 ad.send(send_packet, 0)
592 ad.settimeout(timeout)
594 data = ad.recv(0xffff + 2, 0)
595 data = ndr.ndr_unpack(dns.name_packet, data)
596 # This should cause a loop in Windows
597 # (which is restricted by a 20 CNAME limit)
599 # The reason it doesn't here is because forwarded CNAME have no
600 # additional processing in the internal DNS server.
601 self.assert_dns_rcode_equals(data, dns.DNS_RCODE_OK)
602 self.assertEqual(name3, data.answers[-1].rdata)
603 except socket.timeout:
604 self.fail("DNS server is too slow (timeout %s)" % timeout)
606 TestProgram(module=__name__, opts=subunitopts)