PEP8: fix E303: too many blank lines (2)
[samba.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
70 def make_txt_record(records):
71     rdata_txt = dns.txt_record()
72     s_list = dnsp.string_list()
73     s_list.count = len(records)
74     s_list.str = records
75     rdata_txt.txt = s_list
76     return rdata_txt
77
78
79 class DNSTest(TestCase):
80
81     errcodes = dict((v, k) for k, v in vars(dns).items() if k.startswith('DNS_RCODE_'))
82
83     def assert_dns_rcode_equals(self, packet, rcode):
84         "Helper function to check return code"
85         p_errcode = packet.operation & 0x000F
86         self.assertEquals(p_errcode, rcode, "Expected RCODE %s, got %s" %
87                           (self.errcodes[rcode], self.errcodes[p_errcode]))
88
89     def assert_dns_opcode_equals(self, packet, opcode):
90         "Helper function to check opcode"
91         p_opcode = packet.operation & 0x7800
92         self.assertEquals(p_opcode, opcode, "Expected OPCODE %s, got %s" %
93                           (opcode, p_opcode))
94
95     def make_name_packet(self, opcode, qid=None):
96         "Helper creating a dns.name_packet"
97         p = dns.name_packet()
98         if qid is None:
99             p.id = random.randint(0x0, 0xffff)
100         p.operation = opcode
101         p.questions = []
102         return p
103
104     def finish_name_packet(self, packet, questions):
105         "Helper to finalize a dns.name_packet"
106         packet.qdcount = len(questions)
107         packet.questions = questions
108
109     def make_name_question(self, name, qtype, qclass):
110         "Helper creating a dns.name_question"
111         q = dns.name_question()
112         q.name = name
113         q.question_type = qtype
114         q.question_class = qclass
115         return q
116
117     def get_dns_domain(self):
118         "Helper to get dns domain"
119         return self.creds.get_realm().lower()
120
121     def dns_transaction_udp(self, packet, host=server_ip,
122                             dump=False, timeout=timeout):
123         "send a DNS query and read the reply"
124         s = None
125         try:
126             send_packet = ndr.ndr_pack(packet)
127             if dump:
128                 print(self.hexdump(send_packet))
129             s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
130             s.settimeout(timeout)
131             s.connect((host, 53))
132             s.send(send_packet, 0)
133             recv_packet = s.recv(2048, 0)
134             if dump:
135                 print(self.hexdump(recv_packet))
136             return ndr.ndr_unpack(dns.name_packet, recv_packet)
137         finally:
138             if s is not None:
139                 s.close()
140
141     def make_cname_update(self, key, value):
142         p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
143
144         name = self.get_dns_domain()
145         u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
146         self.finish_name_packet(p, [u])
147
148         r = dns.res_rec()
149         r.name = key
150         r.rr_type = dns.DNS_QTYPE_CNAME
151         r.rr_class = dns.DNS_QCLASS_IN
152         r.ttl = 900
153         r.length = 0xffff
154         rdata = value
155         r.rdata = rdata
156         p.nscount = 1
157         p.nsrecs = [r]
158         response = self.dns_transaction_udp(p)
159         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
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     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)
340         questions = []
341
342         q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
343                                     dns.DNS_QCLASS_IN)
344         questions.append(q)
345
346         self.finish_name_packet(p, questions)
347         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
348         send_packet = ndr.ndr_pack(p)
349
350         ad.send(send_packet, 0)
351         ad.settimeout(timeout)
352         try:
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)
358
359     def test_double_forwarder_first_frozen(self):
360         if len(dns_servers) < 2:
361             print("Ignoring test_double_forwarder_first_frozen")
362             return
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)
369         questions = []
370
371         q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
372                                     dns.DNS_QCLASS_IN)
373         questions.append(q)
374
375         self.finish_name_packet(p, questions)
376         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
377         send_packet = ndr.ndr_pack(p)
378
379         ad.send(send_packet, 0)
380         ad.settimeout(timeout)
381         try:
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)
388
389     def test_double_forwarder_first_down(self):
390         if len(dns_servers) < 2:
391             print("Ignoring test_double_forwarder_first_down")
392             return
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)
397         questions = []
398
399         q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
400                                     dns.DNS_QCLASS_IN)
401         questions.append(q)
402
403         self.finish_name_packet(p, questions)
404         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
405         send_packet = ndr.ndr_pack(p)
406
407         ad.send(send_packet, 0)
408         ad.settimeout(timeout)
409         try:
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)
416
417     def test_double_forwarder_both_slow(self):
418         if len(dns_servers) < 2:
419             print("Ignoring test_double_forwarder_both_slow")
420             return
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)
428         questions = []
429
430         q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
431                                     dns.DNS_QCLASS_IN)
432         questions.append(q)
433
434         self.finish_name_packet(p, questions)
435         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
436         send_packet = ndr.ndr_pack(p)
437
438         ad.send(send_packet, 0)
439         ad.settimeout(timeout)
440         try:
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)
447
448     def test_cname(self):
449         s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
450
451         ad = contact_real_server(server_ip, 53)
452         name = "resolve.cname"
453         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
454         questions = []
455
456         q = self.make_name_question(name, dns.DNS_QTYPE_CNAME,
457                                     dns.DNS_QCLASS_IN)
458         questions.append(q)
459
460         self.finish_name_packet(p, questions)
461         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
462         send_packet = ndr.ndr_pack(p)
463
464         ad.send(send_packet, 0)
465         ad.settimeout(timeout)
466         try:
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)
474
475     def test_double_cname(self):
476         s1 = self.start_toy_server(dns_servers[0], 53, 'forwarder1')
477
478         name = 'resolve.cname.%s' % self.get_dns_domain()
479         self.make_cname_update(name, "dsfsfds.dsfsdfs")
480
481         ad = contact_real_server(server_ip, 53)
482
483         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
484         questions = []
485         q = self.make_name_question(name, dns.DNS_QTYPE_A,
486                                     dns.DNS_QCLASS_IN)
487         questions.append(q)
488
489         self.finish_name_packet(p, questions)
490         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
491         send_packet = ndr.ndr_pack(p)
492
493         ad.send(send_packet, 0)
494         ad.settimeout(timeout)
495         try:
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)
502
503     def test_cname_forwarding_with_slow_server(self):
504         if len(dns_servers) < 2:
505             print("Ignoring test_cname_forwarding_with_slow_server")
506             return
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)
510
511         name = 'resolve.cname.%s' % self.get_dns_domain()
512         self.make_cname_update(name, "dsfsfds.dsfsdfs")
513
514         ad = contact_real_server(server_ip, 53)
515
516         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
517         questions = []
518         q = self.make_name_question(name, dns.DNS_QTYPE_A,
519                                     dns.DNS_QCLASS_IN)
520         questions.append(q)
521
522         self.finish_name_packet(p, questions)
523         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
524         send_packet = ndr.ndr_pack(p)
525
526         ad.send(send_packet, 0)
527         ad.settimeout(timeout)
528         try:
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)
535
536     def test_cname_forwarding_with_server_down(self):
537         if len(dns_servers) < 2:
538             print("Ignoring test_cname_forwarding_with_server_down")
539             return
540         s2 = self.start_toy_server(dns_servers[1], 53, 'forwarder2')
541
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")
546
547         ad = contact_real_server(server_ip, 53)
548
549         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
550         questions = []
551         q = self.make_name_question(name1, dns.DNS_QTYPE_A,
552                                     dns.DNS_QCLASS_IN)
553         questions.append(q)
554
555         self.finish_name_packet(p, questions)
556         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
557         send_packet = ndr.ndr_pack(p)
558
559         ad.send(send_packet, 0)
560         ad.settimeout(timeout)
561         try:
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)
568
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)
572
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")
578
579         ad = contact_real_server(server_ip, 53)
580
581         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
582         questions = []
583         q = self.make_name_question(name1, dns.DNS_QTYPE_A,
584                                     dns.DNS_QCLASS_IN)
585         questions.append(q)
586
587         self.finish_name_packet(p, questions)
588         p.operation |= dns.DNS_FLAG_RECURSION_DESIRED
589         send_packet = ndr.ndr_pack(p)
590
591         ad.send(send_packet, 0)
592         ad.settimeout(timeout)
593         try:
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)
598             #
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)
605
606 TestProgram(module=__name__, opts=subunitopts)