c9da223739e7db154a8fcf60c60af52542635015
[nivanova/samba-autobuild/.git] / python / samba / tests / dns_forwarder.py
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Kai Blin  <kai@samba.org> 2011
3 #
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.
8 #
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.
13 #
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/>.
16 #
17
18 from __future__ import print_function
19 import os
20 import sys
21 import struct
22 import random
23 import socket
24 import samba
25 import time
26 import errno
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
34 import optparse
35 import subprocess
36
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)
40
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")
45
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)
51
52 opts, args = parser.parse_args()
53
54 lp = sambaopts.get_loadparm()
55 creds = credopts.get_credentials(lp)
56
57 timeout = opts.timeout
58
59 if len(args) < 3:
60     parser.print_usage()
61     sys.exit(1)
62
63 server_name = args[0]
64 server_ip = args[1]
65 dns_servers = args[2:]
66
67 creds.set_krb_forwardable(credentials.NO_KRB_FORWARDABLE)
68
69 def make_txt_record(records):
70     rdata_txt = dns.txt_record()
71     s_list = dnsp.string_list()
72     s_list.count = len(records)
73     s_list.str = records
74     rdata_txt.txt = s_list
75     return rdata_txt
76
77
78 class DNSTest(TestCase):
79
80     errcodes = dict((v, k) for k, v in vars(dns).items() if k.startswith('DNS_RCODE_'))
81
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]))
87
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" %
92                             (opcode, p_opcode))
93
94     def make_name_packet(self, opcode, qid=None):
95         "Helper creating a dns.name_packet"
96         p = dns.name_packet()
97         if qid is None:
98             p.id = random.randint(0x0, 0xffff)
99         p.operation = opcode
100         p.questions = []
101         return p
102
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
107
108     def make_name_question(self, name, qtype, qclass):
109         "Helper creating a dns.name_question"
110         q = dns.name_question()
111         q.name = name
112         q.question_type = qtype
113         q.question_class = qclass
114         return q
115
116     def get_dns_domain(self):
117         "Helper to get dns domain"
118         return self.creds.get_realm().lower()
119
120     def dns_transaction_udp(self, packet, host=server_ip,
121                             dump=False, timeout=timeout):
122         "send a DNS query and read the reply"
123         s = None
124         try:
125             send_packet = ndr.ndr_pack(packet)
126             if dump:
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)
133             if dump:
134                 print(self.hexdump(recv_packet))
135             return ndr.ndr_unpack(dns.name_packet, recv_packet)
136         finally:
137             if s is not None:
138                 s.close()
139
140     def make_cname_update(self, key, value):
141         p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
142
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])
146
147         r = dns.res_rec()
148         r.name = key
149         r.rr_type = dns.DNS_QTYPE_CNAME
150         r.rr_class = dns.DNS_QCLASS_IN
151         r.ttl = 900
152         r.length = 0xffff
153         rdata = value
154         r.rdata = rdata
155         p.nscount = 1
156         p.nsrecs = [r]
157         response = self.dns_transaction_udp(p)
158         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
159
160
161
162 def contact_real_server(host, port):
163     s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
164     s.connect((host, port))
165     return s
166
167
168 class TestDnsForwarding(DNSTest):
169     def __init__(self, *args, **kwargs):
170         super(TestDnsForwarding, self).__init__(*args, **kwargs)
171         self.subprocesses = []
172
173     def setUp(self):
174         super(TestDnsForwarding, self).setUp()
175         self.server = server_name
176         self.server_ip = server_ip
177         self.lp = lp
178         self.creds = creds
179
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)
189         for i in range(300):
190             time.sleep(0.05)
191             s.connect((host, port))
192             try:
193                 s.send('timeout 0', 0)
194             except socket.error as e:
195                 if e.errno in (errno.ECONNREFUSED, errno.EHOSTUNREACH):
196                     continue
197
198             if p.returncode is not None:
199                 self.fail("Toy server has managed to die already!")
200
201             return s
202
203     def tearDown(self):
204         super(TestDnsForwarding, self).tearDown()
205         for p in self.subprocesses:
206             p.kill()
207
208     def test_comatose_forwarder(self):
209         s = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
210         s.send("timeout 1000000", 0)
211
212         # make DNS query
213         name = "an-address-that-will-not-resolve"
214         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
215         questions = []
216
217         q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
218         questions.append(q)
219
220         self.finish_name_packet(p, questions)
221         send_packet = ndr.ndr_pack(p)
222
223         s.send(send_packet, 0)
224         s.settimeout(1)
225         try:
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
230             pass
231
232     def test_no_active_forwarder(self):
233         ad = contact_real_server(server_ip, 53)
234
235         name = "dsfsfds.dsfsdfs"
236         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
237         questions = []
238
239         q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
240         questions.append(q)
241
242         self.finish_name_packet(p, questions)
243         send_packet = ndr.ndr_pack(p)
244
245         self.finish_name_packet(p, questions)
246         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
247         send_packet = ndr.ndr_pack(p)
248
249         ad.send(send_packet, 0)
250         ad.settimeout(timeout)
251         try:
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)
258
259     def test_no_flag_recursive_forwarder(self):
260         ad = contact_real_server(server_ip, 53)
261
262         name = "dsfsfds.dsfsdfs"
263         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
264         questions = []
265
266         q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
267         questions.append(q)
268
269         self.finish_name_packet(p, questions)
270         send_packet = ndr.ndr_pack(p)
271
272         self.finish_name_packet(p, questions)
273         # Leave off the recursive flag
274         send_packet = ndr.ndr_pack(p)
275
276         ad.send(send_packet, 0)
277         ad.settimeout(timeout)
278         try:
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)
285
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)
291         questions = []
292
293         q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
294                                     dns.DNS_QCLASS_IN)
295         questions.append(q)
296
297         self.finish_name_packet(p, questions)
298         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
299         send_packet = ndr.ndr_pack(p)
300
301         ad.send(send_packet, 0)
302         ad.settimeout(timeout)
303         try:
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)
310
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)
315         questions = []
316
317         q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
318                                     dns.DNS_QCLASS_IN)
319         questions.append(q)
320
321         self.finish_name_packet(p, questions)
322         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
323         send_packet = ndr.ndr_pack(p)
324
325         ad.send(send_packet, 0)
326         ad.settimeout(timeout)
327         try:
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)
333
334
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)
341         questions = []
342
343         q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
344                                     dns.DNS_QCLASS_IN)
345         questions.append(q)
346
347         self.finish_name_packet(p, questions)
348         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
349         send_packet = ndr.ndr_pack(p)
350
351         ad.send(send_packet, 0)
352         ad.settimeout(timeout)
353         try:
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)
359
360     def test_double_forwarder_first_frozen(self):
361         if len(dns_servers) < 2:
362             print("Ignoring test_double_forwarder_first_frozen")
363             return
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)
370         questions = []
371
372         q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
373                                     dns.DNS_QCLASS_IN)
374         questions.append(q)
375
376         self.finish_name_packet(p, questions)
377         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
378         send_packet = ndr.ndr_pack(p)
379
380         ad.send(send_packet, 0)
381         ad.settimeout(timeout)
382         try:
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)
389
390     def test_double_forwarder_first_down(self):
391         if len(dns_servers) < 2:
392             print("Ignoring test_double_forwarder_first_down")
393             return
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)
398         questions = []
399
400         q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
401                                     dns.DNS_QCLASS_IN)
402         questions.append(q)
403
404         self.finish_name_packet(p, questions)
405         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
406         send_packet = ndr.ndr_pack(p)
407
408         ad.send(send_packet, 0)
409         ad.settimeout(timeout)
410         try:
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)
417
418     def test_double_forwarder_both_slow(self):
419         if len(dns_servers) < 2:
420             print("Ignoring test_double_forwarder_both_slow")
421             return
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)
429         questions = []
430
431         q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
432                                     dns.DNS_QCLASS_IN)
433         questions.append(q)
434
435         self.finish_name_packet(p, questions)
436         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
437         send_packet = ndr.ndr_pack(p)
438
439         ad.send(send_packet, 0)
440         ad.settimeout(timeout)
441         try:
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)
448
449     def test_cname(self):
450         s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
451
452         ad = contact_real_server(server_ip, 53)
453         name = "resolve.cname"
454         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
455         questions = []
456
457         q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
458                                     dns.DNS_QCLASS_IN)
459         questions.append(q)
460
461         self.finish_name_packet(p, questions)
462         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
463         send_packet = ndr.ndr_pack(p)
464
465         ad.send(send_packet, 0)
466         ad.settimeout(timeout)
467         try:
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)
475
476     def test_double_cname(self):
477         s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
478
479         name = 'resolve.cname.%s' % self.get_dns_domain()
480         self.make_cname_update(name, "dsfsfds.dsfsdfs")
481
482         ad = contact_real_server(server_ip, 53)
483
484         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
485         questions = []
486         q = self.make_name_question(name, dns.DNS_QTYPE_A,
487                                     dns.DNS_QCLASS_IN)
488         questions.append(q)
489
490         self.finish_name_packet(p, questions)
491         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
492         send_packet = ndr.ndr_pack(p)
493
494         ad.send(send_packet, 0)
495         ad.settimeout(timeout)
496         try:
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)
503
504     def test_cname_forwarding_with_slow_server(self):
505         if len(dns_servers) < 2:
506             print("Ignoring test_cname_forwarding_with_slow_server")
507             return
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)
511
512         name = 'resolve.cname.%s' % self.get_dns_domain()
513         self.make_cname_update(name, "dsfsfds.dsfsdfs")
514
515         ad = contact_real_server(server_ip, 53)
516
517         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
518         questions = []
519         q = self.make_name_question(name, dns.DNS_QTYPE_A,
520                                     dns.DNS_QCLASS_IN)
521         questions.append(q)
522
523         self.finish_name_packet(p, questions)
524         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
525         send_packet = ndr.ndr_pack(p)
526
527         ad.send(send_packet, 0)
528         ad.settimeout(timeout)
529         try:
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)
536
537     def test_cname_forwarding_with_server_down(self):
538         if len(dns_servers) < 2:
539             print("Ignoring test_cname_forwarding_with_server_down")
540             return
541         s2 = self.start_toy_server(dns_servers[1], 53, 'forwarder2')
542
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")
547
548         ad = contact_real_server(server_ip, 53)
549
550         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
551         questions = []
552         q = self.make_name_question(name1, dns.DNS_QTYPE_A,
553                                     dns.DNS_QCLASS_IN)
554         questions.append(q)
555
556         self.finish_name_packet(p, questions)
557         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
558         send_packet = ndr.ndr_pack(p)
559
560         ad.send(send_packet, 0)
561         ad.settimeout(timeout)
562         try:
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)
569
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)
573
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")
579
580         ad = contact_real_server(server_ip, 53)
581
582         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
583         questions = []
584         q = self.make_name_question(name1, dns.DNS_QTYPE_A,
585                                     dns.DNS_QCLASS_IN)
586         questions.append(q)
587
588         self.finish_name_packet(p, questions)
589         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
590         send_packet = ndr.ndr_pack(p)
591
592         ad.send(send_packet, 0)
593         ad.settimeout(timeout)
594         try:
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)
599             #
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)
606
607 TestProgram(module=__name__, opts=subunitopts)