13 from recursortests
import RecursorTest
14 from proxyprotocol
import ProxyProtocol
16 class ProxyProtocolRecursorTest(RecursorTest
):
21 # we don't need all the auth stuff
25 confdir
= os
.path
.join('configs', cls
._confdir
)
26 cls
.createConfigDir(confdir
)
28 cls
.generateRecursorConfig(confdir
)
29 cls
.startRecursor(confdir
, cls
._recursorPort
)
32 def tearDownClass(cls
):
33 cls
.tearDownRecursor()
35 class ProxyProtocolAllowedRecursorTest(ProxyProtocolRecursorTest
):
36 _confdir
= 'ProxyProtocol'
37 _lua_dns_script_file
= """
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
45 if remoteaddr ~= '127.0.0.42:0' and remoteaddr ~= '[::42]:0' then
46 pdnslog('gettag: invalid source '..remoteaddr)
49 if localaddr ~= '255.255.255.255:65535' and localaddr ~= '[2001:db8::ff]:65535' then
50 pdnslog('gettag: invalid dest '..localaddr)
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
60 if type == 255 and content == 'bar' then
65 if not foundFoo or not foundBar then
66 pdnslog('gettag: TLV not found')
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
83 if type == 255 and content == 'bar' then
88 if not foundFoo or not foundBar then
89 pdnslog('TLV not found')
90 dq:addAnswer(pdns.A, '192.0.2.255', 60)
94 local remoteaddr = dq.remoteaddr:toStringWithPort()
95 local localaddr = dq.localaddr:toStringWithPort()
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)
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)
109 pdnslog('invalid tag '..dq.tag)
110 dq:addAnswer(pdns.A, '192.0.2.130', 60)
114 dq:addAnswer(pdns.A, '192.0.2.1', 60)
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
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')
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
135 self
._sock
.settimeout(2.0)
138 self
._sock
.send(payload
)
139 data
= self
._sock
.recv(4096)
140 except socket
.timeout
:
143 self
._sock
.settimeout(None)
147 res
= dns
.message
.from_wire(data
)
148 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
149 self
.assertRRsetInAnswer(res
, expected
)
152 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
154 sock
.connect(("127.0.0.1", self
._recursorPort
))
158 sock
.send(struct
.pack("!H", len(queryPayload
)))
159 sock
.send(queryPayload
)
162 (datalen
,) = struct
.unpack("!H", data
)
163 data
= sock
.recv(datalen
)
164 except socket
.timeout
as e
:
165 print("Timeout: %s" % (str(e
)))
167 except socket
.error
as e
:
168 print("Network error: %s" % (str(e
)))
175 res
= dns
.message
.from_wire(data
)
176 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
177 self
.assertRRsetInAnswer(res
, expected
)
179 def testInvalidMagicProxyProtocol(self
):
180 qname
= 'invalid-magic.proxy-protocol.recursor-tests.powerdns.com.'
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
189 self
._sock
.settimeout(2.0)
192 self
._sock
.send(payload
)
193 data
= self
._sock
.recv(4096)
194 except socket
.timeout
:
197 self
._sock
.settimeout(None)
201 res
= dns
.message
.from_wire(data
)
202 self
.assertEqual(res
, None)
205 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
207 sock
.connect(("127.0.0.1", self
._recursorPort
))
211 sock
.send(struct
.pack("!H", len(queryPayload
)))
212 sock
.send(queryPayload
)
215 (datalen
,) = struct
.unpack("!H", data
)
216 data
= sock
.recv(datalen
)
217 except socket
.timeout
as e
:
218 print("Timeout: %s" % (str(e
)))
220 except socket
.error
as e
:
221 print("Network error: %s" % (str(e
)))
228 res
= dns
.message
.from_wire(data
)
229 self
.assertEqual(res
, None)
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')
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'] ])
240 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
242 sock
.connect(("127.0.0.1", self
._recursorPort
))
245 for i
in range(len(ppPayload
)):
246 sock
.send(ppPayload
[i
:i
+1])
248 value
= struct
.pack("!H", len(queryPayload
))
249 for i
in range(len(value
)):
250 sock
.send(value
[i
:i
+1])
252 for i
in range(len(queryPayload
)):
253 sock
.send(queryPayload
[i
:i
+1])
258 (datalen
,) = struct
.unpack("!H", data
)
259 data
= sock
.recv(datalen
)
260 except socket
.timeout
as e
:
261 print("Timeout: %s" % (str(e
)))
263 except socket
.error
as e
:
264 print("Network error: %s" % (str(e
)))
271 res
= dns
.message
.from_wire(data
)
272 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
273 self
.assertRRsetInAnswer(res
, expected
)
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')
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
287 self
._sock
.settimeout(2.0)
290 self
._sock
.send(payload
)
291 data
= self
._sock
.recv(4096)
292 except socket
.timeout
:
295 self
._sock
.settimeout(None)
299 res
= dns
.message
.from_wire(data
)
300 self
.assertEqual(res
, None)
303 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
305 sock
.connect(("127.0.0.1", self
._recursorPort
))
309 sock
.send(struct
.pack("!H", len(queryPayload
)))
310 sock
.send(queryPayload
)
314 (datalen
,) = struct
.unpack("!H", data
)
315 data
= sock
.recv(datalen
)
316 except socket
.timeout
as e
:
317 print("Timeout: %s" % (str(e
)))
319 except socket
.error
as e
:
320 print("Network error: %s" % (str(e
)))
327 res
= dns
.message
.from_wire(data
)
328 self
.assertEqual(res
, None)
330 def testNoHeaderProxyProtocol(self
):
331 qname
= 'no-header.proxy-protocol.recursor-tests.powerdns.com.'
333 query
= dns
.message
.make_query(qname
, 'A', want_dnssec
=True)
334 for method
in ("sendUDPQuery", "sendTCPQuery"):
335 sender
= getattr(self
, method
)
337 self
.assertEqual(res
, None)
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')
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
)
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')
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
)
361 def testIPv4ProxyProtocolNotAuthorized(self
):
362 qname
= 'ipv4-not-authorized.proxy-protocol.recursor-tests.powerdns.com.'
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)
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')
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
)
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')
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
)
392 def testIPv6ProxyProtocolNotAuthorized(self
):
393 qname
= 'ipv6-not-authorized.proxy-protocol.recursor-tests.powerdns.com.'
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)
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')
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
411 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
413 sock
.connect(("127.0.0.1", self
._recursorPort
))
420 sock
.send(struct
.pack("!H", len(queryPayload
)))
421 sock
.send(queryPayload
)
425 (datalen
,) = struct
.unpack("!H", data
)
426 data
= sock
.recv(datalen
)
427 except socket
.timeout
as e
:
428 print("Timeout: %s" % (str(e
)))
431 except socket
.error
as e
:
432 print("Network error: %s" % (str(e
)))
438 res
= dns
.message
.from_wire(data
)
439 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
440 self
.assertRRsetInAnswer(res
, expected
)
443 self
.assertEqual(count
, 5)
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")
453 typedef struct pdns_ffi_param pdns_ffi_param_t;
455 typedef struct pdns_proxyprotocol_value {
459 } pdns_proxyprotocol_value_t;
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);
467 void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag);
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
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)
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)
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)
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)
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)
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
510 if type == 255 and content == 'bar' then
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)
522 ffi.C.pdns_ffi_param_set_tag(obj, 42)
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
535 if type == 255 and content == 'bar' then
540 if not foundFoo or not foundBar then
541 pdnslog('TLV not found')
542 dq:addAnswer(pdns.A, '192.0.2.255', 60)
546 local remoteaddr = dq.remoteaddr:toStringWithPort()
547 local localaddr = dq.localaddr:toStringWithPort()
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)
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)
561 pdnslog('invalid tag '..dq.tag)
562 dq:addAnswer(pdns.A, '192.0.2.130', 60)
566 dq:addAnswer(pdns.A, '192.0.2.1', 60)
571 class ProxyProtocolNotAllowedRecursorTest(ProxyProtocolRecursorTest
):
572 _confdir
= 'ProxyProtocolNotAllowed'
573 _lua_dns_script_file
= """
575 function preresolve(dq)
576 dq:addAnswer(pdns.A, '192.0.2.1', 60)
581 _config_template
= """
582 proxy-protocol-from=192.0.2.1/32
583 allow-from=127.0.0.0/24, ::1/128
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')
590 query
= dns
.message
.make_query(qname
, 'A', want_dnssec
=True)
591 for method
in ("sendUDPQuery", "sendTCPQuery"):
592 sender
= getattr(self
, method
)
594 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
595 self
.assertRRsetInAnswer(res
, expected
)
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')
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)