]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.recursor-dnssec/test_ProxyProtocol.py
Introduce structured YAML settings for Recursor.
[thirdparty/pdns.git] / regression-tests.recursor-dnssec / test_ProxyProtocol.py
1 import dns
2 import os
3 import socket
4 import struct
5 import sys
6 import time
7
8 try:
9 range = xrange
10 except NameError:
11 pass
12
13 from recursortests import RecursorTest
14 from proxyprotocol import ProxyProtocol
15
16 class ProxyProtocolRecursorTest(RecursorTest):
17
18 @classmethod
19 def setUpClass(cls):
20
21 # we don't need all the auth stuff
22 cls.setUpSockets()
23 cls.startResponders()
24
25 confdir = os.path.join('configs', cls._confdir)
26 cls.createConfigDir(confdir)
27
28 cls.generateRecursorConfig(confdir)
29 cls.startRecursor(confdir, cls._recursorPort)
30
31 @classmethod
32 def tearDownClass(cls):
33 cls.tearDownRecursor()
34
35 class ProxyProtocolAllowedRecursorTest(ProxyProtocolRecursorTest):
36 _confdir = 'ProxyProtocol'
37 _lua_dns_script_file = """
38
39 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp, proxyProtocolValues)
40 local remoteaddr = remote:toStringWithPort()
41 local localaddr = localip:toStringWithPort()
42 local foundFoo = false
43 local foundBar = false
44
45 if remoteaddr ~= '127.0.0.42:0' and remoteaddr ~= '[::42]:0' then
46 pdnslog('gettag: invalid source '..remoteaddr)
47 return 1
48 end
49 if localaddr ~= '255.255.255.255:65535' and localaddr ~= '[2001:db8::ff]:65535' then
50 pdnslog('gettag: invalid dest '..localaddr)
51 return 2
52 end
53
54 for k,v in pairs(proxyProtocolValues) do
55 local type = v:getType()
56 local content = v:getContent()
57 if type == 0 and content == 'foo' then
58 foundFoo = true
59 end
60 if type == 255 and content == 'bar' then
61 foundBar = true
62 end
63 end
64
65 if not foundFoo or not foundBar then
66 pdnslog('gettag: TLV not found')
67 return 3
68 end
69
70 return 42
71 end
72
73 function preresolve(dq)
74 local foundFoo = false
75 local foundBar = false
76 local values = dq:getProxyProtocolValues()
77 for k,v in pairs(values) do
78 local type = v:getType()
79 local content = v:getContent()
80 if type == 0 and content == 'foo' then
81 foundFoo = true
82 end
83 if type == 255 and content == 'bar' then
84 foundBar = true
85 end
86 end
87
88 if not foundFoo or not foundBar then
89 pdnslog('TLV not found')
90 dq:addAnswer(pdns.A, '192.0.2.255', 60)
91 return true
92 end
93
94 local remoteaddr = dq.remoteaddr:toStringWithPort()
95 local localaddr = dq.localaddr:toStringWithPort()
96
97 if remoteaddr ~= '127.0.0.42:0' and remoteaddr ~= '[::42]:0' then
98 pdnslog('invalid source '..remoteaddr)
99 dq:addAnswer(pdns.A, '192.0.2.128', 60)
100 return true
101 end
102 if localaddr ~= '255.255.255.255:65535' and localaddr ~= '[2001:db8::ff]:65535' then
103 pdnslog('invalid dest '..localaddr)
104 dq:addAnswer(pdns.A, '192.0.2.129', 60)
105 return true
106 end
107
108 if dq.tag ~= 42 then
109 pdnslog('invalid tag '..dq.tag)
110 dq:addAnswer(pdns.A, '192.0.2.130', 60)
111 return true
112 end
113
114 dq:addAnswer(pdns.A, '192.0.2.1', 60)
115 return true
116 end
117 """
118
119 _config_template = """
120 proxy-protocol-from=127.0.0.1
121 proxy-protocol-maximum-size=512
122 allow-from=127.0.0.0/24, ::1/128, ::42/128
123 """ % ()
124
125 def testLocalProxyProtocol(self):
126 qname = 'local.proxy-protocol.recursor-tests.powerdns.com.'
127 expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.255')
128
129 query = dns.message.make_query(qname, 'A', want_dnssec=True)
130 queryPayload = query.to_wire()
131 ppPayload = ProxyProtocol.getPayload(True, False, False, None, None, None, None, [])
132 payload = ppPayload + queryPayload
133
134 # UDP
135 self._sock.settimeout(2.0)
136
137 try:
138 self._sock.send(payload)
139 data = self._sock.recv(4096)
140 except socket.timeout:
141 data = None
142 finally:
143 self._sock.settimeout(None)
144
145 res = None
146 if data:
147 res = dns.message.from_wire(data)
148 self.assertRcodeEqual(res, dns.rcode.NOERROR)
149 self.assertRRsetInAnswer(res, expected)
150
151 # TCP
152 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
153 sock.settimeout(2.0)
154 sock.connect(("127.0.0.1", self._recursorPort))
155
156 try:
157 sock.send(ppPayload)
158 sock.send(struct.pack("!H", len(queryPayload)))
159 sock.send(queryPayload)
160 data = sock.recv(2)
161 if data:
162 (datalen,) = struct.unpack("!H", data)
163 data = sock.recv(datalen)
164 except socket.timeout as e:
165 print("Timeout: %s" % (str(e)))
166 data = None
167 except socket.error as e:
168 print("Network error: %s" % (str(e)))
169 data = None
170 finally:
171 sock.close()
172
173 res = None
174 if data:
175 res = dns.message.from_wire(data)
176 self.assertRcodeEqual(res, dns.rcode.NOERROR)
177 self.assertRRsetInAnswer(res, expected)
178
179 def testInvalidMagicProxyProtocol(self):
180 qname = 'invalid-magic.proxy-protocol.recursor-tests.powerdns.com.'
181
182 query = dns.message.make_query(qname, 'A', want_dnssec=True)
183 queryPayload = query.to_wire()
184 ppPayload = ProxyProtocol.getPayload(True, False, False, None, None, None, None, [])
185 ppPayload = b'\x00' + ppPayload[1:]
186 payload = ppPayload + queryPayload
187
188 # UDP
189 self._sock.settimeout(2.0)
190
191 try:
192 self._sock.send(payload)
193 data = self._sock.recv(4096)
194 except socket.timeout:
195 data = None
196 finally:
197 self._sock.settimeout(None)
198
199 res = None
200 if data:
201 res = dns.message.from_wire(data)
202 self.assertEqual(res, None)
203
204 # TCP
205 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
206 sock.settimeout(2.0)
207 sock.connect(("127.0.0.1", self._recursorPort))
208
209 try:
210 sock.send(ppPayload)
211 sock.send(struct.pack("!H", len(queryPayload)))
212 sock.send(queryPayload)
213 data = sock.recv(2)
214 if data:
215 (datalen,) = struct.unpack("!H", data)
216 data = sock.recv(datalen)
217 except socket.timeout as e:
218 print("Timeout: %s" % (str(e)))
219 data = None
220 except socket.error as e:
221 print("Network error: %s" % (str(e)))
222 data = None
223 finally:
224 sock.close()
225
226 res = None
227 if data:
228 res = dns.message.from_wire(data)
229 self.assertEqual(res, None)
230
231 def testTCPOneByteAtATimeProxyProtocol(self):
232 qname = 'tcp-one-byte-at-a-time.proxy-protocol.recursor-tests.powerdns.com.'
233 expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
234
235 query = dns.message.make_query(qname, 'A', want_dnssec=True)
236 queryPayload = query.to_wire()
237 ppPayload = ProxyProtocol.getPayload(False, True, False, '127.0.0.42', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
238
239 # TCP
240 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
241 sock.settimeout(2.0)
242 sock.connect(("127.0.0.1", self._recursorPort))
243
244 try:
245 for i in range(len(ppPayload)):
246 sock.send(ppPayload[i:i+1])
247 time.sleep(0.01)
248 value = struct.pack("!H", len(queryPayload))
249 for i in range(len(value)):
250 sock.send(value[i:i+1])
251 time.sleep(0.01)
252 for i in range(len(queryPayload)):
253 sock.send(queryPayload[i:i+1])
254 time.sleep(0.01)
255
256 data = sock.recv(2)
257 if data:
258 (datalen,) = struct.unpack("!H", data)
259 data = sock.recv(datalen)
260 except socket.timeout as e:
261 print("Timeout: %s" % (str(e)))
262 data = None
263 except socket.error as e:
264 print("Network error: %s" % (str(e)))
265 data = None
266 finally:
267 sock.close()
268
269 res = None
270 if data:
271 res = dns.message.from_wire(data)
272 self.assertRcodeEqual(res, dns.rcode.NOERROR)
273 self.assertRRsetInAnswer(res, expected)
274
275 def testTooLargeProxyProtocol(self):
276 # the total payload (proxy protocol + DNS) is larger than proxy-protocol-maximum-size
277 # so it should be dropped
278 qname = 'too-large.proxy-protocol.recursor-tests.powerdns.com.'
279 expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
280
281 query = dns.message.make_query(qname, 'A', want_dnssec=True)
282 queryPayload = query.to_wire()
283 ppPayload = ProxyProtocol.getPayload(False, True, False, '127.0.0.42', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [1, b'A'*512], [ 255, b'bar'] ])
284 payload = ppPayload + queryPayload
285
286 # UDP
287 self._sock.settimeout(2.0)
288
289 try:
290 self._sock.send(payload)
291 data = self._sock.recv(4096)
292 except socket.timeout:
293 data = None
294 finally:
295 self._sock.settimeout(None)
296
297 res = None
298 if data:
299 res = dns.message.from_wire(data)
300 self.assertEqual(res, None)
301
302 # TCP
303 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
304 sock.settimeout(2.0)
305 sock.connect(("127.0.0.1", self._recursorPort))
306
307 try:
308 sock.send(ppPayload)
309 sock.send(struct.pack("!H", len(queryPayload)))
310 sock.send(queryPayload)
311
312 data = sock.recv(2)
313 if data:
314 (datalen,) = struct.unpack("!H", data)
315 data = sock.recv(datalen)
316 except socket.timeout as e:
317 print("Timeout: %s" % (str(e)))
318 data = None
319 except socket.error as e:
320 print("Network error: %s" % (str(e)))
321 data = None
322 finally:
323 sock.close()
324
325 res = None
326 if data:
327 res = dns.message.from_wire(data)
328 self.assertEqual(res, None)
329
330 def testNoHeaderProxyProtocol(self):
331 qname = 'no-header.proxy-protocol.recursor-tests.powerdns.com.'
332
333 query = dns.message.make_query(qname, 'A', want_dnssec=True)
334 for method in ("sendUDPQuery", "sendTCPQuery"):
335 sender = getattr(self, method)
336 res = sender(query)
337 self.assertEqual(res, None)
338
339 def testIPv4ProxyProtocol(self):
340 qname = 'ipv4.proxy-protocol.recursor-tests.powerdns.com.'
341 expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
342
343 query = dns.message.make_query(qname, 'A', want_dnssec=True)
344 for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
345 sender = getattr(self, method)
346 res = sender(query, False, '127.0.0.42', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
347 self.assertRcodeEqual(res, dns.rcode.NOERROR)
348 self.assertRRsetInAnswer(res, expected)
349
350 def testIPv4NoValuesProxyProtocol(self):
351 qname = 'ipv4-no-values.proxy-protocol.recursor-tests.powerdns.com.'
352 expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.255')
353
354 query = dns.message.make_query(qname, 'A', want_dnssec=True)
355 for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
356 sender = getattr(self, method)
357 res = sender(query, False, '127.0.0.42', '255.255.255.255', 0, 65535)
358 self.assertRcodeEqual(res, dns.rcode.NOERROR)
359 self.assertRRsetInAnswer(res, expected)
360
361 def testIPv4ProxyProtocolNotAuthorized(self):
362 qname = 'ipv4-not-authorized.proxy-protocol.recursor-tests.powerdns.com.'
363
364 query = dns.message.make_query(qname, 'A', want_dnssec=True)
365 for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
366 sender = getattr(self, method)
367 res = sender(query, False, '192.0.2.255', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
368 self.assertEqual(res, None)
369
370 def testIPv6ProxyProtocol(self):
371 qname = 'ipv6.proxy-protocol.recursor-tests.powerdns.com.'
372 expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
373
374 query = dns.message.make_query(qname, 'A', want_dnssec=True)
375 for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
376 sender = getattr(self, method)
377 res = sender(query, True, '::42', '2001:db8::ff', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
378 self.assertRcodeEqual(res, dns.rcode.NOERROR)
379 self.assertRRsetInAnswer(res, expected)
380
381 def testIPv6NoValuesProxyProtocol(self):
382 qname = 'ipv6-no-values.proxy-protocol.recursor-tests.powerdns.com.'
383 expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.255')
384
385 query = dns.message.make_query(qname, 'A', want_dnssec=True)
386 for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
387 sender = getattr(self, method)
388 res = sender(query, True, '::42', '2001:db8::ff', 0, 65535)
389 self.assertRcodeEqual(res, dns.rcode.NOERROR)
390 self.assertRRsetInAnswer(res, expected)
391
392 def testIPv6ProxyProtocolNotAuthorized(self):
393 qname = 'ipv6-not-authorized.proxy-protocol.recursor-tests.powerdns.com.'
394
395 query = dns.message.make_query(qname, 'A', want_dnssec=True)
396 for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
397 sender = getattr(self, method)
398 res = sender(query, True, '2001:db8::1', '2001:db8::ff', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
399 self.assertEqual(res, None)
400
401 def testIPv6ProxyProtocolSeveralQueriesOverTCP(self):
402 qname = 'several-queries-tcp.proxy-protocol.recursor-tests.powerdns.com.'
403 expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
404
405 query = dns.message.make_query(qname, 'A', want_dnssec=True)
406 queryPayload = query.to_wire()
407 ppPayload = ProxyProtocol.getPayload(False, True, True, '::42', '2001:db8::ff', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
408 payload = ppPayload + queryPayload
409
410 # TCP
411 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
412 sock.settimeout(2.0)
413 sock.connect(("127.0.0.1", self._recursorPort))
414
415 sock.send(ppPayload)
416
417 count = 0
418 for idx in range(5):
419 try:
420 sock.send(struct.pack("!H", len(queryPayload)))
421 sock.send(queryPayload)
422
423 data = sock.recv(2)
424 if data:
425 (datalen,) = struct.unpack("!H", data)
426 data = sock.recv(datalen)
427 except socket.timeout as e:
428 print("Timeout: %s" % (str(e)))
429 data = None
430 break
431 except socket.error as e:
432 print("Network error: %s" % (str(e)))
433 data = None
434 break
435
436 res = None
437 if data:
438 res = dns.message.from_wire(data)
439 self.assertRcodeEqual(res, dns.rcode.NOERROR)
440 self.assertRRsetInAnswer(res, expected)
441 count = count + 1
442
443 self.assertEqual(count, 5)
444 sock.close()
445
446 class ProxyProtocolAllowedFFIRecursorTest(ProxyProtocolAllowedRecursorTest):
447 # same tests than ProxyProtocolAllowedRecursorTest but with the Lua FFI interface instead of the regular one
448 _confdir = 'ProxyProtocolFFI'
449 _lua_dns_script_file = """
450 local ffi = require("ffi")
451
452 ffi.cdef[[
453 typedef struct pdns_ffi_param pdns_ffi_param_t;
454
455 typedef struct pdns_proxyprotocol_value {
456 uint8_t type;
457 uint16_t len;
458 const void* data;
459 } pdns_proxyprotocol_value_t;
460
461 size_t pdns_ffi_param_get_proxy_protocol_values(pdns_ffi_param_t* ref, const pdns_proxyprotocol_value_t** out);
462 const char* pdns_ffi_param_get_remote(pdns_ffi_param_t* ref);
463 const char* pdns_ffi_param_get_local(pdns_ffi_param_t* ref);
464 uint16_t pdns_ffi_param_get_remote_port(const pdns_ffi_param_t* ref);
465 uint16_t pdns_ffi_param_get_local_port(const pdns_ffi_param_t* ref);
466
467 void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag);
468 ]]
469
470 function gettag_ffi(obj)
471 local remoteaddr = ffi.string(ffi.C.pdns_ffi_param_get_remote(obj))
472 local localaddr = ffi.string(ffi.C.pdns_ffi_param_get_local(obj))
473 local foundFoo = false
474 local foundBar = false
475
476 if remoteaddr ~= '127.0.0.42' and remoteaddr ~= '::42' then
477 pdnslog('gettag-ffi: invalid source '..remoteaddr)
478 ffi.C.pdns_ffi_param_set_tag(obj, 1)
479 return
480 end
481 if localaddr ~= '255.255.255.255' and localaddr ~= '2001:db8::ff' then
482 pdnslog('gettag-ffi: invalid dest '..localaddr)
483 ffi.C.pdns_ffi_param_set_tag(obj, 2)
484 return
485 end
486
487 if ffi.C.pdns_ffi_param_get_remote_port(obj) ~= 0 then
488 pdnslog('gettag-ffi: invalid source port '..ffi.C.pdns_ffi_param_get_remote_port(obj))
489 ffi.C.pdns_ffi_param_set_tag(obj, 1)
490 return
491 end
492
493 if ffi.C.pdns_ffi_param_get_local_port(obj) ~= 65535 then
494 pdnslog('gettag-ffi: invalid source port '..ffi.C.pdns_ffi_param_get_local_port(obj))
495 ffi.C.pdns_ffi_param_set_tag(obj, 2)
496 return
497 end
498
499 local ret_ptr = ffi.new("const pdns_proxyprotocol_value_t *[1]")
500 local ret_ptr_param = ffi.cast("const pdns_proxyprotocol_value_t **", ret_ptr)
501 local values_count = ffi.C.pdns_ffi_param_get_proxy_protocol_values(obj, ret_ptr_param)
502
503 if values_count > 0 then
504 for i = 0,tonumber(values_count)-1 do
505 local type = ret_ptr[0][i].type
506 local content = ffi.string(ret_ptr[0][i].data, ret_ptr[0][i].len)
507 if type == 0 and content == 'foo' then
508 foundFoo = true
509 end
510 if type == 255 and content == 'bar' then
511 foundBar = true
512 end
513 end
514 end
515
516 if not foundFoo or not foundBar then
517 pdnslog('gettag-ffi: TLV not found')
518 ffi.C.pdns_ffi_param_set_tag(obj, 3)
519 return
520 end
521
522 ffi.C.pdns_ffi_param_set_tag(obj, 42)
523 end
524
525 function preresolve(dq)
526 local foundFoo = false
527 local foundBar = false
528 local values = dq:getProxyProtocolValues()
529 for k,v in pairs(values) do
530 local type = v:getType()
531 local content = v:getContent()
532 if type == 0 and content == 'foo' then
533 foundFoo = true
534 end
535 if type == 255 and content == 'bar' then
536 foundBar = true
537 end
538 end
539
540 if not foundFoo or not foundBar then
541 pdnslog('TLV not found')
542 dq:addAnswer(pdns.A, '192.0.2.255', 60)
543 return true
544 end
545
546 local remoteaddr = dq.remoteaddr:toStringWithPort()
547 local localaddr = dq.localaddr:toStringWithPort()
548
549 if remoteaddr ~= '127.0.0.42:0' and remoteaddr ~= '[::42]:0' then
550 pdnslog('invalid source '..remoteaddr)
551 dq:addAnswer(pdns.A, '192.0.2.128', 60)
552 return true
553 end
554 if localaddr ~= '255.255.255.255:65535' and localaddr ~= '[2001:db8::ff]:65535' then
555 pdnslog('invalid dest '..localaddr)
556 dq:addAnswer(pdns.A, '192.0.2.129', 60)
557 return true
558 end
559
560 if dq.tag ~= 42 then
561 pdnslog('invalid tag '..dq.tag)
562 dq:addAnswer(pdns.A, '192.0.2.130', 60)
563 return true
564 end
565
566 dq:addAnswer(pdns.A, '192.0.2.1', 60)
567 return true
568 end
569 """
570
571 class ProxyProtocolNotAllowedRecursorTest(ProxyProtocolRecursorTest):
572 _confdir = 'ProxyProtocolNotAllowed'
573 _lua_dns_script_file = """
574
575 function preresolve(dq)
576 dq:addAnswer(pdns.A, '192.0.2.1', 60)
577 return true
578 end
579 """
580
581 _config_template = """
582 proxy-protocol-from=192.0.2.1/32
583 allow-from=127.0.0.0/24, ::1/128
584 """ % ()
585
586 def testNoHeaderProxyProtocol(self):
587 qname = 'no-header.proxy-protocol-not-allowed.recursor-tests.powerdns.com.'
588 expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
589
590 query = dns.message.make_query(qname, 'A', want_dnssec=True)
591 for method in ("sendUDPQuery", "sendTCPQuery"):
592 sender = getattr(self, method)
593 res = sender(query)
594 self.assertRcodeEqual(res, dns.rcode.NOERROR)
595 self.assertRRsetInAnswer(res, expected)
596
597 def testIPv4ProxyProtocol(self):
598 qname = 'ipv4.proxy-protocol-not-allowed.recursor-tests.powerdns.com.'
599 expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
600
601 query = dns.message.make_query(qname, 'A', want_dnssec=True)
602 for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
603 sender = getattr(self, method)
604 res = sender(query, False, '127.0.0.42', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
605 self.assertEqual(res, None)