12 from dnsdisttests
import DNSDistTest
, pickAvailablePort
13 from proxyprotocol
import ProxyProtocol
14 from dnsdistdohtests
import DNSDistDOHTest
16 # Python2/3 compatibility hacks
18 from queue
import Queue
20 from Queue
import Queue
22 def ProxyProtocolUDPResponder(port
, fromQueue
, toQueue
):
23 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
)
24 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEPORT
, 1)
26 sock
.bind(("127.0.0.1", port
))
27 except socket
.error
as e
:
28 print("Error binding in the Proxy Protocol UDP responder: %s" % str(e
))
32 data
, addr
= sock
.recvfrom(4096)
34 proxy
= ProxyProtocol()
35 if len(data
) < proxy
.HEADER_SIZE
:
38 if not proxy
.parseHeader(data
):
42 # likely a healthcheck
43 data
= data
[proxy
.HEADER_SIZE
:]
44 request
= dns
.message
.from_wire(data
)
45 response
= dns
.message
.make_response(request
)
46 wire
= response
.to_wire()
48 sock
.sendto(wire
, addr
)
53 payload
= data
[:(proxy
.HEADER_SIZE
+ proxy
.contentLen
)]
54 dnsData
= data
[(proxy
.HEADER_SIZE
+ proxy
.contentLen
):]
55 toQueue
.put([payload
, dnsData
], True, 2.0)
56 # computing the correct ID for the response
57 request
= dns
.message
.from_wire(dnsData
)
58 response
= fromQueue
.get(True, 2.0)
59 response
.id = request
.id
62 sock
.sendto(response
.to_wire(), addr
)
67 def ProxyProtocolTCPResponder(port
, fromQueue
, toQueue
):
68 # be aware that this responder will not accept a new connection
69 # until the last one has been closed. This is done on purpose to
70 # to check for connection reuse, making sure that a lot of connections
71 # are not opened in parallel.
72 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
73 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEPORT
, 1)
74 sock
.setsockopt(socket
.IPPROTO_TCP
, socket
.TCP_NODELAY
, 1)
76 sock
.bind(("127.0.0.1", port
))
77 except socket
.error
as e
:
78 print("Error binding in the TCP responder: %s" % str(e
))
83 (conn
, _
) = sock
.accept()
85 # try to read the entire Proxy Protocol header
86 proxy
= ProxyProtocol()
87 header
= conn
.recv(proxy
.HEADER_SIZE
)
92 if not proxy
.parseHeader(header
):
96 proxyContent
= conn
.recv(proxy
.contentLen
)
101 payload
= header
+ proxyContent
105 except socket
.timeout
:
112 (datalen
,) = struct
.unpack("!H", data
)
113 data
= conn
.recv(datalen
)
115 toQueue
.put([payload
, data
], True, 2.0)
117 response
= copy
.deepcopy(fromQueue
.get(True, 2.0))
122 # computing the correct ID for the response
123 request
= dns
.message
.from_wire(data
)
124 response
.id = request
.id
126 wire
= response
.to_wire()
127 conn
.send(struct
.pack("!H", len(wire
)))
134 toProxyQueue
= Queue()
135 fromProxyQueue
= Queue()
136 proxyResponderPort
= pickAvailablePort()
138 udpResponder
= threading
.Thread(name
='UDP Proxy Protocol Responder', target
=ProxyProtocolUDPResponder
, args
=[proxyResponderPort
, toProxyQueue
, fromProxyQueue
])
139 udpResponder
.daemon
= True
141 tcpResponder
= threading
.Thread(name
='TCP Proxy Protocol Responder', target
=ProxyProtocolTCPResponder
, args
=[proxyResponderPort
, toProxyQueue
, fromProxyQueue
])
142 tcpResponder
.daemon
= True
145 backgroundThreads
= {}
147 def MockTCPReverseProxyAddingProxyProtocol(listeningPort
, forwardingPort
):
148 # this responder accepts TCP connections on the listening port,
149 # and relay the raw content to a second TCP connection to the
150 # forwarding port, after adding a Proxy Protocol v2 payload
151 # containing the initial source IP and port, destination IP
153 backgroundThreads
[threading
.get_native_id()] = True
154 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
155 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEPORT
, 1)
156 sock
.setsockopt(socket
.IPPROTO_TCP
, socket
.TCP_NODELAY
, 1)
158 sock
.bind(("127.0.0.1", listeningPort
))
159 except socket
.error
as e
:
160 print("Error binding in the Mock TCP reverse proxy: %s" % str(e
))
166 (incoming
, _
) = sock
.accept()
167 except socket
.timeout
:
168 if backgroundThreads
.get(threading
.get_native_id(), False) == False:
169 del backgroundThreads
[threading
.get_native_id()]
174 incoming
.settimeout(5.0)
175 payload
= ProxyProtocol
.getPayload(False, True, False, '127.0.0.1', '127.0.0.1', incoming
.getpeername()[1], listeningPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
177 outgoing
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
178 outgoing
.setsockopt(socket
.IPPROTO_TCP
, socket
.TCP_NODELAY
, 1)
179 outgoing
.settimeout(2.0)
180 outgoing
.connect(('127.0.0.1', forwardingPort
))
182 outgoing
.send(payload
)
184 sel
= selectors
.DefaultSelector()
185 def readFromClient(conn
):
186 data
= conn
.recv(512)
187 if not data
or len(data
) == 0:
192 def readFromBackend(conn
):
193 data
= conn
.recv(512)
194 if not data
or len(data
) == 0:
199 sel
.register(incoming
, selectors
.EVENT_READ
, readFromClient
)
200 sel
.register(outgoing
, selectors
.EVENT_READ
, readFromBackend
)
204 events
= sel
.select()
205 for key
, mask
in events
:
206 if not (key
.data
)(key
.fileobj
):
209 except socket
.timeout
:
219 class ProxyProtocolTest(DNSDistTest
):
220 _proxyResponderPort
= proxyResponderPort
221 _config_params
= ['_proxyResponderPort']
223 class TestProxyProtocol(ProxyProtocolTest
):
225 dnsdist is configured to prepend a Proxy Protocol header to the query
228 _config_template
= """
229 newServer{address="127.0.0.1:%d", useProxyProtocol=true}
231 function addValues(dq)
232 local values = { [0]="foo", [42]="bar" }
233 dq:setProxyProtocolValues(values)
234 return DNSAction.None
237 addAction("values-lua.proxy.tests.powerdns.com.", LuaAction(addValues))
238 addAction("values-action.proxy.tests.powerdns.com.", SetProxyProtocolValuesAction({ ["1"]="dnsdist", ["255"]="proxy-protocol"}))
240 _config_params
= ['_proxyResponderPort']
243 def testProxyUDP(self
):
245 Proxy Protocol: no value (UDP)
247 name
= 'simple-udp.proxy.tests.powerdns.com.'
248 query
= dns
.message
.make_query(name
, 'A', 'IN')
249 response
= dns
.message
.make_response(query
)
251 toProxyQueue
.put(response
, True, 2.0)
253 data
= query
.to_wire()
254 self
._sock
.send(data
)
255 receivedResponse
= None
257 self
._sock
.settimeout(2.0)
258 data
= self
._sock
.recv(4096)
259 except socket
.timeout
:
263 receivedResponse
= dns
.message
.from_wire(data
)
265 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
266 self
.assertTrue(receivedProxyPayload
)
267 self
.assertTrue(receivedDNSData
)
268 self
.assertTrue(receivedResponse
)
270 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
271 receivedQuery
.id = query
.id
272 receivedResponse
.id = response
.id
273 self
.assertEqual(receivedQuery
, query
)
274 self
.assertEqual(receivedResponse
, response
)
275 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', False)
277 def testProxyTCP(self
):
279 Proxy Protocol: no value (TCP)
281 name
= 'simple-tcp.proxy.tests.powerdns.com.'
282 query
= dns
.message
.make_query(name
, 'A', 'IN')
283 response
= dns
.message
.make_response(query
)
285 toProxyQueue
.put(response
, True, 2.0)
287 conn
= self
.openTCPConnection(2.0)
288 data
= query
.to_wire()
289 self
.sendTCPQueryOverConnection(conn
, data
, rawQuery
=True)
290 receivedResponse
= None
292 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
293 except socket
.timeout
:
296 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
297 self
.assertTrue(receivedProxyPayload
)
298 self
.assertTrue(receivedDNSData
)
299 self
.assertTrue(receivedResponse
)
301 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
302 receivedQuery
.id = query
.id
303 receivedResponse
.id = response
.id
304 self
.assertEqual(receivedQuery
, query
)
305 self
.assertEqual(receivedResponse
, response
)
306 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True)
308 def testProxyUDPWithValuesFromLua(self
):
310 Proxy Protocol: values from Lua (UDP)
312 name
= 'values-lua.proxy.tests.powerdns.com.'
313 query
= dns
.message
.make_query(name
, 'A', 'IN')
314 response
= dns
.message
.make_response(query
)
316 toProxyQueue
.put(response
, True, 2.0)
318 data
= query
.to_wire()
319 self
._sock
.send(data
)
320 receivedResponse
= None
322 self
._sock
.settimeout(2.0)
323 data
= self
._sock
.recv(4096)
324 except socket
.timeout
:
328 receivedResponse
= dns
.message
.from_wire(data
)
330 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
331 self
.assertTrue(receivedProxyPayload
)
332 self
.assertTrue(receivedDNSData
)
333 self
.assertTrue(receivedResponse
)
335 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
336 receivedQuery
.id = query
.id
337 receivedResponse
.id = response
.id
338 self
.assertEqual(receivedQuery
, query
)
339 self
.assertEqual(receivedResponse
, response
)
340 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', False, [ [0, b
'foo'] , [ 42, b
'bar'] ])
342 def testProxyTCPWithValuesFromLua(self
):
344 Proxy Protocol: values from Lua (TCP)
346 name
= 'values-lua.proxy.tests.powerdns.com.'
347 query
= dns
.message
.make_query(name
, 'A', 'IN')
348 response
= dns
.message
.make_response(query
)
350 toProxyQueue
.put(response
, True, 2.0)
352 conn
= self
.openTCPConnection(2.0)
353 data
= query
.to_wire()
354 self
.sendTCPQueryOverConnection(conn
, data
, rawQuery
=True)
355 receivedResponse
= None
357 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
358 except socket
.timeout
:
361 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
362 self
.assertTrue(receivedProxyPayload
)
363 self
.assertTrue(receivedDNSData
)
364 self
.assertTrue(receivedResponse
)
366 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
367 receivedQuery
.id = query
.id
368 receivedResponse
.id = response
.id
369 self
.assertEqual(receivedQuery
, query
)
370 self
.assertEqual(receivedResponse
, response
)
371 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [ [0, b
'foo'] , [ 42, b
'bar'] ])
373 def testProxyUDPWithValuesFromAction(self
):
375 Proxy Protocol: values from Action (UDP)
377 name
= 'values-action.proxy.tests.powerdns.com.'
378 query
= dns
.message
.make_query(name
, 'A', 'IN')
379 response
= dns
.message
.make_response(query
)
381 toProxyQueue
.put(response
, True, 2.0)
383 data
= query
.to_wire()
384 self
._sock
.send(data
)
385 receivedResponse
= None
387 self
._sock
.settimeout(2.0)
388 data
= self
._sock
.recv(4096)
389 except socket
.timeout
:
393 receivedResponse
= dns
.message
.from_wire(data
)
395 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
396 self
.assertTrue(receivedProxyPayload
)
397 self
.assertTrue(receivedDNSData
)
398 self
.assertTrue(receivedResponse
)
400 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
401 receivedQuery
.id = query
.id
402 receivedResponse
.id = response
.id
403 self
.assertEqual(receivedQuery
, query
)
404 self
.assertEqual(receivedResponse
, response
)
405 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', False, [ [1, b
'dnsdist'] , [ 255, b
'proxy-protocol'] ])
407 def testProxyTCPWithValuesFromAction(self
):
409 Proxy Protocol: values from Action (TCP)
411 name
= 'values-action.proxy.tests.powerdns.com.'
412 query
= dns
.message
.make_query(name
, 'A', 'IN')
413 response
= dns
.message
.make_response(query
)
415 toProxyQueue
.put(response
, True, 2.0)
417 conn
= self
.openTCPConnection(2.0)
418 data
= query
.to_wire()
419 self
.sendTCPQueryOverConnection(conn
, data
, rawQuery
=True)
420 receivedResponse
= None
422 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
423 except socket
.timeout
:
426 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
427 self
.assertTrue(receivedProxyPayload
)
428 self
.assertTrue(receivedDNSData
)
429 self
.assertTrue(receivedResponse
)
431 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
432 receivedQuery
.id = query
.id
433 receivedResponse
.id = response
.id
434 self
.assertEqual(receivedQuery
, query
)
435 self
.assertEqual(receivedResponse
, response
)
436 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [ [1, b
'dnsdist'] , [ 255, b
'proxy-protocol'] ])
438 def testProxyTCPSeveralQueriesOnSameConnection(self
):
440 Proxy Protocol: Several queries on the same TCP connection
442 name
= 'several-queries-same-conn.proxy.tests.powerdns.com.'
443 query
= dns
.message
.make_query(name
, 'A', 'IN')
444 response
= dns
.message
.make_response(query
)
446 conn
= self
.openTCPConnection(2.0)
447 data
= query
.to_wire()
449 for idx
in range(10):
450 toProxyQueue
.put(response
, True, 2.0)
451 self
.sendTCPQueryOverConnection(conn
, data
, rawQuery
=True)
452 receivedResponse
= None
454 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
455 except socket
.timeout
:
458 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
459 self
.assertTrue(receivedProxyPayload
)
460 self
.assertTrue(receivedDNSData
)
461 self
.assertTrue(receivedResponse
)
463 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
464 receivedQuery
.id = query
.id
465 receivedResponse
.id = response
.id
466 self
.assertEqual(receivedQuery
, query
)
467 self
.assertEqual(receivedResponse
, response
)
468 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [])
470 class TestProxyProtocolIncoming(ProxyProtocolTest
):
472 dnsdist is configured to prepend a Proxy Protocol header to the query and expect one on incoming queries
475 _config_template
= """
476 addDOHLocal("127.0.0.1:%s", "%s", "%s", {"/"}, {library='nghttp2', proxyProtocolOutsideTLS=true})
477 setProxyProtocolACL( { "127.0.0.1/32" } )
478 newServer{address="127.0.0.1:%d", useProxyProtocol=true}
480 function addValues(dq)
481 dq:addProxyProtocolValue(0, 'foo')
482 dq:addProxyProtocolValue(42, 'bar')
483 return DNSAction.None
486 -- refuse queries with no TLV value type 2
487 addAction(NotRule(ProxyProtocolValueRule(2)), RCodeAction(DNSRCode.REFUSED))
488 -- or with a TLV value type 3 different from "proxy"
489 addAction(NotRule(ProxyProtocolValueRule(3, "proxy")), RCodeAction(DNSRCode.REFUSED))
491 function answerBasedOnForwardedDest(dq)
492 local port = dq.localaddr:getPort()
493 local dest = dq.localaddr:toString()
494 return DNSAction.Spoof, "address-was-"..dest.."-port-was-"..port..".proxy-protocol-incoming.tests.powerdns.com."
496 addAction("get-forwarded-dest.proxy-protocol-incoming.tests.powerdns.com.", LuaAction(answerBasedOnForwardedDest))
498 function answerBasedOnForwardedSrc(dq)
499 local port = dq.remoteaddr:getPort()
500 local src = dq.remoteaddr:toString()
501 return DNSAction.Spoof, "address-was-"..src.."-port-was-"..port..".proxy-protocol-incoming.tests.powerdns.com."
503 addAction("get-forwarded-src.proxy-protocol-incoming.tests.powerdns.com.", LuaAction(answerBasedOnForwardedSrc))
505 -- add these values for all queries
506 addAction("proxy-protocol-incoming.tests.powerdns.com.", LuaAction(addValues))
507 addAction("proxy-protocol-incoming.tests.powerdns.com.", SetAdditionalProxyProtocolValueAction(1, "dnsdist"))
508 addAction("proxy-protocol-incoming.tests.powerdns.com.", SetAdditionalProxyProtocolValueAction(255, "proxy-protocol"))
510 -- override all existing values
511 addAction("override.proxy-protocol-incoming.tests.powerdns.com.", SetProxyProtocolValuesAction({["50"]="overridden"}))
513 _serverKey
= 'server.key'
514 _serverCert
= 'server.chain'
515 _serverName
= 'tls.tests.dnsdist.org'
517 _dohServerPort
= 8443
518 _dohBaseURL
= ("https://%s:%d/" % (_serverName
, _dohServerPort
))
519 _config_params
= ['_dohServerPort', '_serverCert', '_serverKey', '_proxyResponderPort']
521 def testNoHeader(self
):
523 Incoming Proxy Protocol: no header
525 # no proxy protocol header while one is expected, should be dropped
526 name
= 'no-header.incoming-proxy-protocol.tests.powerdns.com.'
527 query
= dns
.message
.make_query(name
, 'A', 'IN')
529 for method
in ("sendUDPQuery", "sendTCPQuery", "sendDOHQueryWrapper"):
530 sender
= getattr(self
, method
)
532 (_
, receivedResponse
) = sender(query
, response
=None)
534 receivedResponse
= None
535 self
.assertEqual(receivedResponse
, None)
537 def testIncomingProxyDest(self
):
539 Incoming Proxy Protocol: values from Lua
541 name
= 'get-forwarded-dest.proxy-protocol-incoming.tests.powerdns.com.'
542 query
= dns
.message
.make_query(name
, 'A', 'IN')
543 # dnsdist set RA = RD for spoofed responses
544 query
.flags
&= ~dns
.flags
.RD
546 destAddr
= "2001:db8::9"
548 srcAddr
= "2001:db8::8"
550 response
= dns
.message
.make_response(query
)
551 rrset
= dns
.rrset
.from_text(name
,
555 "address-was-{}-port-was-{}.proxy-protocol-incoming.tests.powerdns.com.".format(destAddr
, destPort
, self
._dnsDistPort
))
556 response
.answer
.append(rrset
)
558 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
559 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
560 self
.assertEqual(receivedResponse
, response
)
562 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
563 wire
= query
.to_wire()
565 receivedResponse
= None
567 conn
= self
.openTCPConnection(2.0)
568 conn
.send(tcpPayload
)
569 conn
.send(struct
.pack("!H", len(wire
)))
571 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
572 except socket
.timeout
:
574 self
.assertEqual(receivedResponse
, response
)
576 def testProxyUDPWithValuesFromLua(self
):
578 Incoming Proxy Protocol: values from Lua (UDP)
580 name
= 'values-lua.proxy-protocol-incoming.tests.powerdns.com.'
581 query
= dns
.message
.make_query(name
, 'A', 'IN')
582 response
= dns
.message
.make_response(query
)
584 destAddr
= "2001:db8::9"
586 srcAddr
= "2001:db8::8"
588 response
= dns
.message
.make_response(query
)
590 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
591 toProxyQueue
.put(response
, True, 2.0)
592 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
594 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
595 self
.assertTrue(receivedProxyPayload
)
596 self
.assertTrue(receivedDNSData
)
597 self
.assertTrue(receivedResponse
)
599 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
600 receivedQuery
.id = query
.id
601 receivedResponse
.id = response
.id
602 self
.assertEqual(receivedQuery
, query
)
603 self
.assertEqual(receivedResponse
, response
)
604 self
.checkMessageProxyProtocol(receivedProxyPayload
, srcAddr
, destAddr
, False, [ [0, b
'foo'], [1, b
'dnsdist'], [ 2, b
'foo'], [3, b
'proxy'], [ 42, b
'bar'], [255, b
'proxy-protocol'] ], True, srcPort
, destPort
)
606 def testProxyTCPWithValuesFromLua(self
):
608 Incoming Proxy Protocol: values from Lua (TCP)
610 name
= 'values-lua.proxy-protocol-incoming.tests.powerdns.com.'
611 query
= dns
.message
.make_query(name
, 'A', 'IN')
612 response
= dns
.message
.make_response(query
)
614 destAddr
= "2001:db8::9"
616 srcAddr
= "2001:db8::8"
618 response
= dns
.message
.make_response(query
)
620 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
622 toProxyQueue
.put(response
, True, 2.0)
624 wire
= query
.to_wire()
626 receivedResponse
= None
628 conn
= self
.openTCPConnection(2.0)
629 conn
.send(tcpPayload
)
630 conn
.send(struct
.pack("!H", len(wire
)))
632 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
633 except socket
.timeout
:
635 self
.assertEqual(receivedResponse
, response
)
637 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
638 self
.assertTrue(receivedProxyPayload
)
639 self
.assertTrue(receivedDNSData
)
640 self
.assertTrue(receivedResponse
)
642 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
643 receivedQuery
.id = query
.id
644 self
.assertEqual(receivedQuery
, query
)
645 self
.assertEqual(receivedResponse
, response
)
646 self
.checkMessageProxyProtocol(receivedProxyPayload
, srcAddr
, destAddr
, True, [ [0, b
'foo'], [1, b
'dnsdist'], [ 2, b
'foo'], [3, b
'proxy'], [ 42, b
'bar'], [255, b
'proxy-protocol'] ], True, srcPort
, destPort
)
648 def testProxyUDPWithValueOverride(self
):
650 Incoming Proxy Protocol: override existing value (UDP)
652 name
= 'override.proxy-protocol-incoming.tests.powerdns.com.'
653 query
= dns
.message
.make_query(name
, 'A', 'IN')
654 response
= dns
.message
.make_response(query
)
656 destAddr
= "2001:db8::9"
658 srcAddr
= "2001:db8::8"
660 response
= dns
.message
.make_response(query
)
662 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [2, b
'foo'], [3, b
'proxy'], [ 50, b
'initial-value']])
663 toProxyQueue
.put(response
, True, 2.0)
664 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
666 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
667 self
.assertTrue(receivedProxyPayload
)
668 self
.assertTrue(receivedDNSData
)
669 self
.assertTrue(receivedResponse
)
671 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
672 receivedQuery
.id = query
.id
673 receivedResponse
.id = response
.id
674 self
.assertEqual(receivedQuery
, query
)
675 self
.assertEqual(receivedResponse
, response
)
676 self
.checkMessageProxyProtocol(receivedProxyPayload
, srcAddr
, destAddr
, False, [ [50, b
'overridden'] ], True, srcPort
, destPort
)
678 def testProxyTCPSeveralQueriesOverConnection(self
):
680 Incoming Proxy Protocol: Several queries over the same connection (TCP)
682 name
= 'several-queries.proxy-protocol-incoming.tests.powerdns.com.'
683 query
= dns
.message
.make_query(name
, 'A', 'IN')
684 response
= dns
.message
.make_response(query
)
686 destAddr
= "2001:db8::9"
688 srcAddr
= "2001:db8::8"
691 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
693 toProxyQueue
.put(response
, True, 2.0)
695 wire
= query
.to_wire()
697 receivedResponse
= None
698 conn
= self
.openTCPConnection(2.0)
700 conn
.send(tcpPayload
)
701 conn
.send(struct
.pack("!H", len(wire
)))
703 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
704 except socket
.timeout
:
706 self
.assertEqual(receivedResponse
, response
)
708 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
709 self
.assertTrue(receivedProxyPayload
)
710 self
.assertTrue(receivedDNSData
)
711 self
.assertTrue(receivedResponse
)
713 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
714 receivedQuery
.id = query
.id
715 receivedResponse
.id = response
.id
716 self
.assertEqual(receivedQuery
, query
)
717 self
.assertEqual(receivedResponse
, response
)
718 self
.checkMessageProxyProtocol(receivedProxyPayload
, srcAddr
, destAddr
, True, [ [0, b
'foo'], [1, b
'dnsdist'], [ 2, b
'foo'], [3, b
'proxy'], [ 42, b
'bar'], [255, b
'proxy-protocol'] ], True, srcPort
, destPort
)
721 receivedResponse
= None
722 toProxyQueue
.put(response
, True, 2.0)
724 conn
.send(struct
.pack("!H", len(wire
)))
726 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
727 except socket
.timeout
:
730 self
.assertEqual(receivedResponse
, response
)
732 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
733 self
.assertTrue(receivedProxyPayload
)
734 self
.assertTrue(receivedDNSData
)
735 self
.assertTrue(receivedResponse
)
737 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
738 receivedQuery
.id = query
.id
739 self
.assertEqual(receivedQuery
, query
)
740 self
.assertEqual(receivedResponse
, response
)
741 self
.checkMessageProxyProtocol(receivedProxyPayload
, srcAddr
, destAddr
, True, [ [0, b
'foo'], [1, b
'dnsdist'], [ 2, b
'foo'], [3, b
'proxy'], [ 42, b
'bar'], [255, b
'proxy-protocol'] ], True, srcPort
, destPort
)
743 def testProxyDoHSeveralQueriesOverConnection(self
):
745 Incoming Proxy Protocol: Several queries over the same connection (DoH)
747 name
= 'several-queries.proxy-protocol-incoming.tests.powerdns.com.'
748 query
= dns
.message
.make_query(name
, 'A', 'IN')
749 response
= dns
.message
.make_response(query
)
751 toProxyQueue
.put(response
, True, 2.0)
753 wire
= query
.to_wire()
755 reverseProxyPort
= 13053
756 reverseProxy
= threading
.Thread(name
='Mock Proxy Protocol Reverse Proxy', target
=MockTCPReverseProxyAddingProxyProtocol
, args
=[reverseProxyPort
, self
._dohServerPort
])
759 receivedResponse
= None
760 conn
= self
.openDOHConnection(reverseProxyPort
, self
._caCert
, timeout
=2.0)
762 reverseProxyBaseURL
= ("https://%s:%d/" % (self
._serverName
, reverseProxyPort
))
763 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(reverseProxyPort
, self
._serverName
, reverseProxyBaseURL
, query
, response
=response
, caFile
=self
._caCert
, useQueue
=True, conn
=conn
)
764 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
765 self
.assertTrue(receivedProxyPayload
)
766 self
.assertTrue(receivedDNSData
)
767 self
.assertTrue(receivedResponse
)
769 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
770 receivedQuery
.id = query
.id
771 receivedResponse
.id = response
.id
772 self
.assertEqual(receivedQuery
, query
)
773 self
.assertEqual(receivedResponse
, response
)
774 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [ [0, b
'foo'], [1, b
'dnsdist'], [ 2, b
'foo'], [3, b
'proxy'], [ 42, b
'bar'], [255, b
'proxy-protocol'] ], v6
=False, sourcePort
=None, destinationPort
=reverseProxyPort
)
777 receivedResponse
= None
778 toProxyQueue
.put(response
, True, 2.0)
779 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(reverseProxyPort
, self
._serverName
, reverseProxyBaseURL
, query
, response
=response
, caFile
=self
._caCert
, useQueue
=True, conn
=conn
)
780 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
781 self
.assertTrue(receivedProxyPayload
)
782 self
.assertTrue(receivedDNSData
)
783 self
.assertTrue(receivedResponse
)
785 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
786 receivedQuery
.id = query
.id
787 receivedResponse
.id = response
.id
788 self
.assertEqual(receivedQuery
, query
)
789 print(receivedResponse
)
791 self
.assertEqual(receivedResponse
, response
)
792 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [ [0, b
'foo'], [1, b
'dnsdist'], [ 2, b
'foo'], [3, b
'proxy'], [ 42, b
'bar'], [255, b
'proxy-protocol'] ], v6
=False, sourcePort
=None, destinationPort
=reverseProxyPort
)
795 def tearDownClass(cls
):
797 for backgroundThread
in cls
._backgroundThreads
:
798 cls
._backgroundThreads
[backgroundThread
] = False
799 for backgroundThread
in backgroundThreads
:
800 backgroundThreads
[backgroundThread
] = False
801 cls
.killProcess(cls
._dnsdist
)
803 class TestProxyProtocolNotExpected(DNSDistTest
):
805 dnsdist is configured to expect a Proxy Protocol header on incoming queries but not from 127.0.0.1
808 _config_template
= """
809 setProxyProtocolACL( { "192.0.2.1/32" } )
810 newServer{address="127.0.0.1:%d"}
812 # NORMAL responder, does not expect a proxy protocol payload!
813 _config_params
= ['_testServerPort']
816 def testNoHeader(self
):
818 Unexpected Proxy Protocol: no header
820 # no proxy protocol header and none is expected from this source, should be passed on
821 name
= 'no-header.unexpected-proxy-protocol.tests.powerdns.com.'
822 query
= dns
.message
.make_query(name
, 'A', 'IN')
823 response
= dns
.message
.make_response(query
)
824 rrset
= dns
.rrset
.from_text(name
,
830 response
.answer
.append(rrset
)
832 for method
in ("sendUDPQuery", "sendTCPQuery"):
833 sender
= getattr(self
, method
)
834 (receivedQuery
, receivedResponse
) = sender(query
, response
)
835 receivedQuery
.id = query
.id
836 self
.assertEqual(query
, receivedQuery
)
837 self
.assertEqual(response
, receivedResponse
)
839 def testIncomingProxyDest(self
):
841 Unexpected Proxy Protocol: should be dropped
843 name
= 'with-proxy-payload.unexpected-protocol-incoming.tests.powerdns.com.'
844 query
= dns
.message
.make_query(name
, 'A', 'IN')
846 # Make sure that the proxy payload does NOT turn into a legal qname
847 destAddr
= "ff:db8::ffff"
849 srcAddr
= "ff:db8::ffff"
852 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
853 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
854 self
.assertEqual(receivedResponse
, None)
856 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
857 wire
= query
.to_wire()
859 receivedResponse
= None
861 conn
= self
.openTCPConnection(2.0)
862 conn
.send(tcpPayload
)
863 conn
.send(struct
.pack("!H", len(wire
)))
865 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
866 except socket
.timeout
:
868 self
.assertEqual(receivedResponse
, None)
870 class TestDOHWithOutgoingProxyProtocol(DNSDistDOHTest
):
872 _serverKey
= 'server.key'
873 _serverCert
= 'server.chain'
874 _serverName
= 'tls.tests.dnsdist.org'
876 _dohWithNGHTTP2ServerPort
= pickAvailablePort()
877 _dohWithNGHTTP2BaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohWithNGHTTP2ServerPort
))
878 _dohWithH2OServerPort
= pickAvailablePort()
879 _dohWithH2OBaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohWithH2OServerPort
))
880 _proxyResponderPort
= proxyResponderPort
881 _config_template
= """
882 newServer{address="127.0.0.1:%s", useProxyProtocol=true}
883 addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true, library='nghttp2' })
884 addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true, library='h2o' })
885 setACL( { "::1/128", "127.0.0.0/8" } )
887 _config_params
= ['_proxyResponderPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey']
890 def testTruncation(self
):
892 DOH: Truncation over UDP
894 # the query is first forwarded over UDP, leading to a TC=1 answer from the
895 # backend, then over TCP
896 name
= 'truncated-udp.doh.proxy-protocol.tests.powerdns.com.'
897 query
= dns
.message
.make_query(name
, 'A', 'IN')
899 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
900 expectedQuery
.id = 42
901 response
= dns
.message
.make_response(query
)
902 rrset
= dns
.rrset
.from_text(name
,
907 response
.answer
.append(rrset
)
909 for (port
,url
) in [(self
._dohWithNGHTTP
2ServerPort
, self
._dohWithNGHTTP
2BaseURL
), (self
._dohWithH
2OServerPort
, self
._dohWithH
2OBaseURL
)]:
910 # first response is a TC=1
911 tcResponse
= dns
.message
.make_response(query
)
912 tcResponse
.flags |
= dns
.flags
.TC
913 toProxyQueue
.put(tcResponse
, True, 2.0)
915 ((receivedProxyPayload
, receivedDNSData
), receivedResponse
) = self
.sendDOHQuery(port
, self
._serverName
, url
, query
, caFile
=self
._caCert
, response
=response
, fromQueue
=fromProxyQueue
, toQueue
=toProxyQueue
)
916 # first query, received by the responder over UDP
917 self
.assertTrue(receivedProxyPayload
)
918 self
.assertTrue(receivedDNSData
)
919 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
920 self
.assertTrue(receivedQuery
)
921 receivedQuery
.id = expectedQuery
.id
922 self
.assertEqual(expectedQuery
, receivedQuery
)
923 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
924 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, destinationPort
=port
)
927 self
.assertTrue(receivedResponse
)
928 self
.assertEqual(response
, receivedResponse
)
930 # check the second query, received by the responder over TCP
931 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
932 self
.assertTrue(receivedDNSData
)
933 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
934 self
.assertTrue(receivedQuery
)
935 receivedQuery
.id = expectedQuery
.id
936 self
.assertEqual(expectedQuery
, receivedQuery
)
937 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
938 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, destinationPort
=port
)
940 # make sure we consumed everything
941 self
.assertTrue(toProxyQueue
.empty())
942 self
.assertTrue(fromProxyQueue
.empty())
944 def testAddressFamilyMismatch(self
):
946 DOH with IPv6 X-Forwarded-For to an IPv4 endpoint
948 name
= 'x-forwarded-for-af-mismatch.doh.outgoing-proxy-protocol.tests.powerdns.com.'
949 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
951 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
953 response
= dns
.message
.make_response(query
)
954 rrset
= dns
.rrset
.from_text(name
,
959 response
.answer
.append(rrset
)
961 for (port
,url
) in [(self
._dohWithNGHTTP
2ServerPort
, self
._dohWithNGHTTP
2BaseURL
), (self
._dohWithH
2OServerPort
, self
._dohWithH
2OBaseURL
)]:
962 # the query should be dropped
963 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(port
, self
._serverName
, url
, query
, caFile
=self
._caCert
, customHeaders
=['x-forwarded-for: [::1]:8080'], useQueue
=False)
964 self
.assertFalse(receivedQuery
)
965 self
.assertFalse(receivedResponse
)
967 # make sure the timeout is detected, if any
970 # this one should not
971 ((receivedProxyPayload
, receivedDNSData
), receivedResponse
) = self
.sendDOHQuery(port
, self
._serverName
, url
, query
, caFile
=self
._caCert
, customHeaders
=['x-forwarded-for: 127.0.0.42:8080'], response
=response
, fromQueue
=fromProxyQueue
, toQueue
=toProxyQueue
)
972 self
.assertTrue(receivedProxyPayload
)
973 self
.assertTrue(receivedDNSData
)
974 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
975 self
.assertTrue(receivedQuery
)
976 receivedQuery
.id = expectedQuery
.id
977 self
.assertEqual(expectedQuery
, receivedQuery
)
978 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
979 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.42', '127.0.0.1', True, destinationPort
=port
)
981 self
.assertTrue(receivedResponse
)
982 receivedResponse
.id = response
.id
983 self
.assertEqual(response
, receivedResponse
)