12 from dnsdisttests
import DNSDistTest
, pickAvailablePort
13 from proxyprotocol
import ProxyProtocol
14 from proxyprotocolutils
import ProxyProtocolUDPResponder
, ProxyProtocolTCPResponder
15 from dnsdistdohtests
import DNSDistDOHTest
17 # Python2/3 compatibility hacks
19 from queue
import Queue
21 from Queue
import Queue
23 toProxyQueue
= Queue()
24 fromProxyQueue
= Queue()
25 proxyResponderPort
= pickAvailablePort()
27 udpResponder
= threading
.Thread(name
='UDP Proxy Protocol Responder', target
=ProxyProtocolUDPResponder
, args
=[proxyResponderPort
, toProxyQueue
, fromProxyQueue
])
28 udpResponder
.daemon
= True
30 tcpResponder
= threading
.Thread(name
='TCP Proxy Protocol Responder', target
=ProxyProtocolTCPResponder
, args
=[proxyResponderPort
, toProxyQueue
, fromProxyQueue
])
31 tcpResponder
.daemon
= True
34 backgroundThreads
= {}
36 def MockTCPReverseProxyAddingProxyProtocol(listeningPort
, forwardingPort
, serverCtx
=None, ca
=None, sni
=None):
37 # this responder accepts TCP connections on the listening port,
38 # and relay the raw content to a second TCP connection to the
39 # forwarding port, after adding a Proxy Protocol v2 payload
40 # containing the initial source IP and port, destination IP
42 backgroundThreads
[threading
.get_native_id()] = True
44 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
45 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEPORT
, 1)
46 sock
.setsockopt(socket
.IPPROTO_TCP
, socket
.TCP_NODELAY
, 1)
48 if serverCtx
is not None:
49 sock
= serverCtx
.wrap_socket(sock
, server_side
=True)
52 sock
.bind(("127.0.0.1", listeningPort
))
53 except socket
.error
as e
:
54 print("Error binding in the Mock TCP reverse proxy: %s" % str(e
))
61 (incoming
, _
) = sock
.accept()
62 except socket
.timeout
:
63 if backgroundThreads
.get(threading
.get_native_id(), False) == False:
64 del backgroundThreads
[threading
.get_native_id()]
69 incoming
.settimeout(5.0)
70 payload
= ProxyProtocol
.getPayload(False, True, False, '127.0.0.1', '127.0.0.1', incoming
.getpeername()[1], listeningPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
72 outgoing
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
73 outgoing
.setsockopt(socket
.IPPROTO_TCP
, socket
.TCP_NODELAY
, 1)
74 outgoing
.settimeout(2.0)
76 if hasattr(ssl
, 'create_default_context'):
77 sslctx
= ssl
.create_default_context(cafile
=ca
)
78 if hasattr(sslctx
, 'set_alpn_protocols'):
79 sslctx
.set_alpn_protocols(['h2'])
80 outgoing
= sslctx
.wrap_socket(outgoing
, server_hostname
=sni
)
82 outgoing
= ssl
.wrap_socket(outgoing
, ca_certs
=ca
, cert_reqs
=ssl
.CERT_REQUIRED
)
84 outgoing
.connect(('127.0.0.1', forwardingPort
))
86 outgoing
.send(payload
)
88 sel
= selectors
.DefaultSelector()
89 def readFromClient(conn
):
91 if not data
or len(data
) == 0:
96 def readFromBackend(conn
):
98 if not data
or len(data
) == 0:
103 sel
.register(incoming
, selectors
.EVENT_READ
, readFromClient
)
104 sel
.register(outgoing
, selectors
.EVENT_READ
, readFromBackend
)
108 events
= sel
.select()
109 for key
, mask
in events
:
110 if not (key
.data
)(key
.fileobj
):
113 except socket
.timeout
:
123 class ProxyProtocolTest(DNSDistTest
):
124 _proxyResponderPort
= proxyResponderPort
125 _config_params
= ['_proxyResponderPort']
127 class TestProxyProtocol(ProxyProtocolTest
):
129 dnsdist is configured to prepend a Proxy Protocol header to the query
132 _config_template
= """
133 newServer{address="127.0.0.1:%d", useProxyProtocol=true}
135 function addValues(dq)
136 local values = { [0]="foo", [42]="bar" }
137 dq:setProxyProtocolValues(values)
138 return DNSAction.None
141 addAction("values-lua.proxy.tests.powerdns.com.", LuaAction(addValues))
142 addAction("values-action.proxy.tests.powerdns.com.", SetProxyProtocolValuesAction({ ["1"]="dnsdist", ["255"]="proxy-protocol"}))
144 _config_params
= ['_proxyResponderPort']
147 def testProxyUDP(self
):
149 Proxy Protocol: no value (UDP)
151 name
= 'simple-udp.proxy.tests.powerdns.com.'
152 query
= dns
.message
.make_query(name
, 'A', 'IN')
153 response
= dns
.message
.make_response(query
)
155 toProxyQueue
.put(response
, True, 2.0)
157 data
= query
.to_wire()
158 self
._sock
.send(data
)
159 receivedResponse
= None
161 self
._sock
.settimeout(2.0)
162 data
= self
._sock
.recv(4096)
163 except socket
.timeout
:
167 receivedResponse
= dns
.message
.from_wire(data
)
169 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
170 self
.assertTrue(receivedProxyPayload
)
171 self
.assertTrue(receivedDNSData
)
172 self
.assertTrue(receivedResponse
)
174 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
175 receivedQuery
.id = query
.id
176 receivedResponse
.id = response
.id
177 self
.assertEqual(receivedQuery
, query
)
178 self
.assertEqual(receivedResponse
, response
)
179 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', False)
181 def testProxyTCP(self
):
183 Proxy Protocol: no value (TCP)
185 name
= 'simple-tcp.proxy.tests.powerdns.com.'
186 query
= dns
.message
.make_query(name
, 'A', 'IN')
187 response
= dns
.message
.make_response(query
)
189 toProxyQueue
.put(response
, True, 2.0)
191 conn
= self
.openTCPConnection(2.0)
192 data
= query
.to_wire()
193 self
.sendTCPQueryOverConnection(conn
, data
, rawQuery
=True)
194 receivedResponse
= None
196 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
197 except socket
.timeout
:
200 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
201 self
.assertTrue(receivedProxyPayload
)
202 self
.assertTrue(receivedDNSData
)
203 self
.assertTrue(receivedResponse
)
205 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
206 receivedQuery
.id = query
.id
207 receivedResponse
.id = response
.id
208 self
.assertEqual(receivedQuery
, query
)
209 self
.assertEqual(receivedResponse
, response
)
210 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True)
212 def testProxyUDPWithValuesFromLua(self
):
214 Proxy Protocol: values from Lua (UDP)
216 name
= 'values-lua.proxy.tests.powerdns.com.'
217 query
= dns
.message
.make_query(name
, 'A', 'IN')
218 response
= dns
.message
.make_response(query
)
220 toProxyQueue
.put(response
, True, 2.0)
222 data
= query
.to_wire()
223 self
._sock
.send(data
)
224 receivedResponse
= None
226 self
._sock
.settimeout(2.0)
227 data
= self
._sock
.recv(4096)
228 except socket
.timeout
:
232 receivedResponse
= dns
.message
.from_wire(data
)
234 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
235 self
.assertTrue(receivedProxyPayload
)
236 self
.assertTrue(receivedDNSData
)
237 self
.assertTrue(receivedResponse
)
239 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
240 receivedQuery
.id = query
.id
241 receivedResponse
.id = response
.id
242 self
.assertEqual(receivedQuery
, query
)
243 self
.assertEqual(receivedResponse
, response
)
244 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', False, [ [0, b
'foo'] , [ 42, b
'bar'] ])
246 def testProxyTCPWithValuesFromLua(self
):
248 Proxy Protocol: values from Lua (TCP)
250 name
= 'values-lua.proxy.tests.powerdns.com.'
251 query
= dns
.message
.make_query(name
, 'A', 'IN')
252 response
= dns
.message
.make_response(query
)
254 toProxyQueue
.put(response
, True, 2.0)
256 conn
= self
.openTCPConnection(2.0)
257 data
= query
.to_wire()
258 self
.sendTCPQueryOverConnection(conn
, data
, rawQuery
=True)
259 receivedResponse
= None
261 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
262 except socket
.timeout
:
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', True, [ [0, b
'foo'] , [ 42, b
'bar'] ])
277 def testProxyUDPWithValuesFromAction(self
):
279 Proxy Protocol: values from Action (UDP)
281 name
= 'values-action.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 data
= query
.to_wire()
288 self
._sock
.send(data
)
289 receivedResponse
= None
291 self
._sock
.settimeout(2.0)
292 data
= self
._sock
.recv(4096)
293 except socket
.timeout
:
297 receivedResponse
= dns
.message
.from_wire(data
)
299 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
300 self
.assertTrue(receivedProxyPayload
)
301 self
.assertTrue(receivedDNSData
)
302 self
.assertTrue(receivedResponse
)
304 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
305 receivedQuery
.id = query
.id
306 receivedResponse
.id = response
.id
307 self
.assertEqual(receivedQuery
, query
)
308 self
.assertEqual(receivedResponse
, response
)
309 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', False, [ [1, b
'dnsdist'] , [ 255, b
'proxy-protocol'] ])
311 def testProxyTCPWithValuesFromAction(self
):
313 Proxy Protocol: values from Action (TCP)
315 name
= 'values-action.proxy.tests.powerdns.com.'
316 query
= dns
.message
.make_query(name
, 'A', 'IN')
317 response
= dns
.message
.make_response(query
)
319 toProxyQueue
.put(response
, True, 2.0)
321 conn
= self
.openTCPConnection(2.0)
322 data
= query
.to_wire()
323 self
.sendTCPQueryOverConnection(conn
, data
, rawQuery
=True)
324 receivedResponse
= None
326 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
327 except socket
.timeout
:
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', True, [ [1, b
'dnsdist'] , [ 255, b
'proxy-protocol'] ])
342 def testProxyTCPSeveralQueriesOnSameConnection(self
):
344 Proxy Protocol: Several queries on the same TCP connection
346 name
= 'several-queries-same-conn.proxy.tests.powerdns.com.'
347 query
= dns
.message
.make_query(name
, 'A', 'IN')
348 response
= dns
.message
.make_response(query
)
350 conn
= self
.openTCPConnection(2.0)
351 data
= query
.to_wire()
353 for idx
in range(10):
354 toProxyQueue
.put(response
, True, 2.0)
355 self
.sendTCPQueryOverConnection(conn
, data
, rawQuery
=True)
356 receivedResponse
= None
358 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
359 except socket
.timeout
:
362 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
363 self
.assertTrue(receivedProxyPayload
)
364 self
.assertTrue(receivedDNSData
)
365 self
.assertTrue(receivedResponse
)
367 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
368 receivedQuery
.id = query
.id
369 receivedResponse
.id = response
.id
370 self
.assertEqual(receivedQuery
, query
)
371 self
.assertEqual(receivedResponse
, response
)
372 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [])
374 class TestProxyProtocolIncoming(ProxyProtocolTest
):
376 dnsdist is configured to prepend a Proxy Protocol header to the query and expect one on incoming queries
379 _config_template
= """
380 addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library='nghttp2', proxyProtocolOutsideTLS=true})
381 addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library='nghttp2', proxyProtocolOutsideTLS=false})
382 setProxyProtocolACL( { "127.0.0.1/32" } )
383 newServer{address="127.0.0.1:%d", useProxyProtocol=true, proxyProtocolAdvertiseTLS=true}
385 function addValues(dq)
386 dq:addProxyProtocolValue(0, 'foo')
387 dq:addProxyProtocolValue(42, 'bar')
388 return DNSAction.None
391 -- refuse queries with no TLV value type 2
392 addAction(NotRule(ProxyProtocolValueRule(2)), RCodeAction(DNSRCode.REFUSED))
393 -- or with a TLV value type 3 different from "proxy"
394 addAction(NotRule(ProxyProtocolValueRule(3, "proxy")), RCodeAction(DNSRCode.REFUSED))
396 function answerBasedOnForwardedDest(dq)
397 local port = dq.localaddr:getPort()
398 local dest = dq.localaddr:toString()
399 return DNSAction.Spoof, "address-was-"..dest.."-port-was-"..port..".proxy-protocol-incoming.tests.powerdns.com."
401 addAction("get-forwarded-dest.proxy-protocol-incoming.tests.powerdns.com.", LuaAction(answerBasedOnForwardedDest))
403 function answerBasedOnForwardedSrc(dq)
404 local port = dq.remoteaddr:getPort()
405 local src = dq.remoteaddr:toString()
406 return DNSAction.Spoof, "address-was-"..src.."-port-was-"..port..".proxy-protocol-incoming.tests.powerdns.com."
408 addAction("get-forwarded-src.proxy-protocol-incoming.tests.powerdns.com.", LuaAction(answerBasedOnForwardedSrc))
410 -- add these values for all queries
411 addAction("proxy-protocol-incoming.tests.powerdns.com.", LuaAction(addValues))
412 addAction("proxy-protocol-incoming.tests.powerdns.com.", SetAdditionalProxyProtocolValueAction(1, "dnsdist"))
413 addAction("proxy-protocol-incoming.tests.powerdns.com.", SetAdditionalProxyProtocolValueAction(255, "proxy-protocol"))
415 -- override all existing values
416 addAction("override.proxy-protocol-incoming.tests.powerdns.com.", SetProxyProtocolValuesAction({["50"]="overridden"}))
418 _serverKey
= 'server.key'
419 _serverCert
= 'server.chain'
420 _serverName
= 'tls.tests.dnsdist.org'
422 _dohServerPPOutsidePort
= pickAvailablePort()
423 _dohServerPPInsidePort
= pickAvailablePort()
424 _config_params
= ['_dohServerPPOutsidePort', '_serverCert', '_serverKey', '_dohServerPPInsidePort', '_serverCert', '_serverKey', '_proxyResponderPort']
426 def testNoHeader(self
):
428 Incoming Proxy Protocol: no header
430 # no proxy protocol header while one is expected, should be dropped
431 name
= 'no-header.incoming-proxy-protocol.tests.powerdns.com.'
432 query
= dns
.message
.make_query(name
, 'A', 'IN')
434 for method
in ("sendUDPQuery", "sendTCPQuery", "sendDOHQueryWrapper"):
435 sender
= getattr(self
, method
)
437 (_
, receivedResponse
) = sender(query
, response
=None)
439 receivedResponse
= None
440 self
.assertEqual(receivedResponse
, None)
442 def testIncomingProxyDest(self
):
444 Incoming Proxy Protocol: values from Lua
446 name
= 'get-forwarded-dest.proxy-protocol-incoming.tests.powerdns.com.'
447 query
= dns
.message
.make_query(name
, 'A', 'IN')
448 # dnsdist set RA = RD for spoofed responses
449 query
.flags
&= ~dns
.flags
.RD
451 destAddr
= "2001:db8::9"
453 srcAddr
= "2001:db8::8"
455 response
= dns
.message
.make_response(query
)
456 rrset
= dns
.rrset
.from_text(name
,
460 "address-was-{}-port-was-{}.proxy-protocol-incoming.tests.powerdns.com.".format(destAddr
, destPort
, self
._dnsDistPort
))
461 response
.answer
.append(rrset
)
463 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
464 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
465 self
.assertEqual(receivedResponse
, response
)
467 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
468 wire
= query
.to_wire()
470 receivedResponse
= None
472 conn
= self
.openTCPConnection(2.0)
473 conn
.send(tcpPayload
)
474 conn
.send(struct
.pack("!H", len(wire
)))
476 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
477 except socket
.timeout
:
479 self
.assertEqual(receivedResponse
, response
)
481 def testProxyUDPWithValuesFromLua(self
):
483 Incoming Proxy Protocol: values from Lua (UDP)
485 name
= 'values-lua.proxy-protocol-incoming.tests.powerdns.com.'
486 query
= dns
.message
.make_query(name
, 'A', 'IN')
487 response
= dns
.message
.make_response(query
)
489 destAddr
= "2001:db8::9"
491 srcAddr
= "2001:db8::8"
493 response
= dns
.message
.make_response(query
)
495 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
496 toProxyQueue
.put(response
, True, 2.0)
497 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
499 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
500 self
.assertTrue(receivedProxyPayload
)
501 self
.assertTrue(receivedDNSData
)
502 self
.assertTrue(receivedResponse
)
504 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
505 receivedQuery
.id = query
.id
506 receivedResponse
.id = response
.id
507 self
.assertEqual(receivedQuery
, query
)
508 self
.assertEqual(receivedResponse
, response
)
509 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
)
511 def testProxyTCPWithValuesFromLua(self
):
513 Incoming Proxy Protocol: values from Lua (TCP)
515 name
= 'values-lua.proxy-protocol-incoming.tests.powerdns.com.'
516 query
= dns
.message
.make_query(name
, 'A', 'IN')
517 response
= dns
.message
.make_response(query
)
519 destAddr
= "2001:db8::9"
521 srcAddr
= "2001:db8::8"
523 response
= dns
.message
.make_response(query
)
525 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
527 toProxyQueue
.put(response
, True, 2.0)
529 wire
= query
.to_wire()
531 receivedResponse
= None
533 conn
= self
.openTCPConnection(2.0)
534 conn
.send(tcpPayload
)
535 conn
.send(struct
.pack("!H", len(wire
)))
537 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
538 except socket
.timeout
:
540 self
.assertEqual(receivedResponse
, response
)
542 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
543 self
.assertTrue(receivedProxyPayload
)
544 self
.assertTrue(receivedDNSData
)
545 self
.assertTrue(receivedResponse
)
547 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
548 receivedQuery
.id = query
.id
549 self
.assertEqual(receivedQuery
, query
)
550 self
.assertEqual(receivedResponse
, response
)
551 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
)
553 def testProxyUDPWithValueOverride(self
):
555 Incoming Proxy Protocol: override existing value (UDP)
557 name
= 'override.proxy-protocol-incoming.tests.powerdns.com.'
558 query
= dns
.message
.make_query(name
, 'A', 'IN')
559 response
= dns
.message
.make_response(query
)
561 destAddr
= "2001:db8::9"
563 srcAddr
= "2001:db8::8"
565 response
= dns
.message
.make_response(query
)
567 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [2, b
'foo'], [3, b
'proxy'], [ 50, b
'initial-value']])
568 toProxyQueue
.put(response
, True, 2.0)
569 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
571 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
572 self
.assertTrue(receivedProxyPayload
)
573 self
.assertTrue(receivedDNSData
)
574 self
.assertTrue(receivedResponse
)
576 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
577 receivedQuery
.id = query
.id
578 receivedResponse
.id = response
.id
579 self
.assertEqual(receivedQuery
, query
)
580 self
.assertEqual(receivedResponse
, response
)
581 self
.checkMessageProxyProtocol(receivedProxyPayload
, srcAddr
, destAddr
, False, [ [50, b
'overridden'] ], True, srcPort
, destPort
)
583 def testProxyTCPSeveralQueriesOverConnection(self
):
585 Incoming Proxy Protocol: Several queries over the same connection (TCP)
587 name
= 'several-queries.proxy-protocol-incoming.tests.powerdns.com.'
588 query
= dns
.message
.make_query(name
, 'A', 'IN')
589 response
= dns
.message
.make_response(query
)
591 destAddr
= "2001:db8::9"
593 srcAddr
= "2001:db8::8"
596 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
598 toProxyQueue
.put(response
, True, 2.0)
600 wire
= query
.to_wire()
602 receivedResponse
= None
603 conn
= self
.openTCPConnection(2.0)
605 conn
.send(tcpPayload
)
606 conn
.send(struct
.pack("!H", len(wire
)))
608 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
609 except socket
.timeout
:
611 self
.assertEqual(receivedResponse
, response
)
613 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
614 self
.assertTrue(receivedProxyPayload
)
615 self
.assertTrue(receivedDNSData
)
616 self
.assertTrue(receivedResponse
)
618 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
619 receivedQuery
.id = query
.id
620 receivedResponse
.id = response
.id
621 self
.assertEqual(receivedQuery
, query
)
622 self
.assertEqual(receivedResponse
, response
)
623 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
)
626 receivedResponse
= None
627 toProxyQueue
.put(response
, True, 2.0)
629 conn
.send(struct
.pack("!H", len(wire
)))
631 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
632 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 testProxyDoHSeveralQueriesOverConnectionPPOutside(self
):
650 Incoming Proxy Protocol: Several queries over the same connection (DoH, PP outside TLS)
652 name
= 'several-queries.doh-outside.proxy-protocol-incoming.tests.powerdns.com.'
653 query
= dns
.message
.make_query(name
, 'A', 'IN')
654 response
= dns
.message
.make_response(query
)
656 toProxyQueue
.put(response
, True, 2.0)
658 wire
= query
.to_wire()
660 reverseProxyPort
= pickAvailablePort()
661 reverseProxy
= threading
.Thread(name
='Mock Proxy Protocol Reverse Proxy', target
=MockTCPReverseProxyAddingProxyProtocol
, args
=[reverseProxyPort
, self
._dohServerPPOutsidePort
])
665 receivedResponse
= None
666 conn
= self
.openDOHConnection(reverseProxyPort
, self
._caCert
, timeout
=2.0)
668 reverseProxyBaseURL
= ("https://%s:%d/" % (self
._serverName
, reverseProxyPort
))
669 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(reverseProxyPort
, self
._serverName
, reverseProxyBaseURL
, query
, response
=response
, caFile
=self
._caCert
, useQueue
=True, conn
=conn
)
670 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
671 self
.assertTrue(receivedProxyPayload
)
672 self
.assertTrue(receivedDNSData
)
673 self
.assertTrue(receivedResponse
)
675 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
676 receivedQuery
.id = query
.id
677 receivedResponse
.id = response
.id
678 self
.assertEqual(receivedQuery
, query
)
679 self
.assertEqual(receivedResponse
, response
)
680 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [ [0, b
'foo'], [1, b
'dnsdist'], [ 2, b
'foo'], [3, b
'proxy'], [32, ''], [42, b
'bar'], [255, b
'proxy-protocol'] ], v6
=False, sourcePort
=None, destinationPort
=reverseProxyPort
)
683 receivedResponse
= None
684 toProxyQueue
.put(response
, True, 2.0)
685 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(reverseProxyPort
, self
._serverName
, reverseProxyBaseURL
, query
, response
=response
, caFile
=self
._caCert
, useQueue
=True, conn
=conn
)
686 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
687 self
.assertTrue(receivedProxyPayload
)
688 self
.assertTrue(receivedDNSData
)
689 self
.assertTrue(receivedResponse
)
691 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
692 receivedQuery
.id = query
.id
693 receivedResponse
.id = response
.id
694 self
.assertEqual(receivedQuery
, query
)
695 self
.assertEqual(receivedResponse
, response
)
696 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [ [0, b
'foo'], [1, b
'dnsdist'], [ 2, b
'foo'], [3, b
'proxy'], [32, ''], [42, b
'bar'], [255, b
'proxy-protocol'] ], v6
=False, sourcePort
=None, destinationPort
=reverseProxyPort
)
698 def testProxyDoHSeveralQueriesOverConnectionPPInside(self
):
700 Incoming Proxy Protocol: Several queries over the same connection (DoH, PP inside TLS)
702 name
= 'several-queries.doh-inside.proxy-protocol-incoming.tests.powerdns.com.'
703 query
= dns
.message
.make_query(name
, 'A', 'IN')
704 response
= dns
.message
.make_response(query
)
706 toProxyQueue
.put(response
, True, 2.0)
708 wire
= query
.to_wire()
710 reverseProxyPort
= pickAvailablePort()
711 tlsContext
= ssl
.SSLContext(ssl
.PROTOCOL_TLS_SERVER
)
712 tlsContext
.load_cert_chain(self
._serverCert
, self
._serverKey
)
713 tlsContext
.set_alpn_protocols(['h2'])
714 reverseProxy
= threading
.Thread(name
='Mock Proxy Protocol Reverse Proxy', target
=MockTCPReverseProxyAddingProxyProtocol
, args
=[reverseProxyPort
, self
._dohServerPPInsidePort
, tlsContext
, self
._caCert
, self
._serverName
])
717 receivedResponse
= None
719 conn
= self
.openDOHConnection(reverseProxyPort
, self
._caCert
, timeout
=2.0)
721 reverseProxyBaseURL
= ("https://%s:%d/" % (self
._serverName
, reverseProxyPort
))
722 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(reverseProxyPort
, self
._serverName
, reverseProxyBaseURL
, query
, response
=response
, caFile
=self
._caCert
, useQueue
=True, conn
=conn
)
723 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
724 self
.assertTrue(receivedProxyPayload
)
725 self
.assertTrue(receivedDNSData
)
726 self
.assertTrue(receivedResponse
)
728 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
729 receivedQuery
.id = query
.id
730 receivedResponse
.id = response
.id
731 self
.assertEqual(receivedQuery
, query
)
732 self
.assertEqual(receivedResponse
, response
)
733 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [ [0, b
'foo'], [1, b
'dnsdist'], [ 2, b
'foo'], [3, b
'proxy'], [32, ''], [ 42, b
'bar'], [255, b
'proxy-protocol'] ], v6
=False, sourcePort
=None, destinationPort
=reverseProxyPort
)
736 receivedResponse
= None
737 toProxyQueue
.put(response
, True, 2.0)
738 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(reverseProxyPort
, self
._serverName
, reverseProxyBaseURL
, query
, response
=response
, caFile
=self
._caCert
, useQueue
=True, conn
=conn
)
739 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
740 self
.assertTrue(receivedProxyPayload
)
741 self
.assertTrue(receivedDNSData
)
742 self
.assertTrue(receivedResponse
)
744 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
745 receivedQuery
.id = query
.id
746 receivedResponse
.id = response
.id
747 self
.assertEqual(receivedQuery
, query
)
748 self
.assertEqual(receivedResponse
, response
)
749 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [ [0, b
'foo'], [1, b
'dnsdist'], [ 2, b
'foo'], [3, b
'proxy'], [32, ''], [ 42, b
'bar'], [255, b
'proxy-protocol'] ], v6
=False, sourcePort
=None, destinationPort
=reverseProxyPort
)
752 def tearDownClass(cls
):
754 for backgroundThread
in cls
._backgroundThreads
:
755 cls
._backgroundThreads
[backgroundThread
] = False
756 for backgroundThread
in backgroundThreads
:
757 backgroundThreads
[backgroundThread
] = False
758 cls
.killProcess(cls
._dnsdist
)
760 class TestProxyProtocolNotExpected(DNSDistTest
):
762 dnsdist is configured to expect a Proxy Protocol header on incoming queries but not from 127.0.0.1
765 _config_template
= """
766 setProxyProtocolACL( { "192.0.2.1/32" } )
767 newServer{address="127.0.0.1:%d"}
769 # NORMAL responder, does not expect a proxy protocol payload!
770 _config_params
= ['_testServerPort']
773 def testNoHeader(self
):
775 Unexpected Proxy Protocol: no header
777 # no proxy protocol header and none is expected from this source, should be passed on
778 name
= 'no-header.unexpected-proxy-protocol.tests.powerdns.com.'
779 query
= dns
.message
.make_query(name
, 'A', 'IN')
780 response
= dns
.message
.make_response(query
)
781 rrset
= dns
.rrset
.from_text(name
,
787 response
.answer
.append(rrset
)
789 for method
in ("sendUDPQuery", "sendTCPQuery"):
790 sender
= getattr(self
, method
)
791 (receivedQuery
, receivedResponse
) = sender(query
, response
)
792 receivedQuery
.id = query
.id
793 self
.assertEqual(query
, receivedQuery
)
794 self
.assertEqual(response
, receivedResponse
)
796 def testIncomingProxyDest(self
):
798 Unexpected Proxy Protocol: should be dropped
800 name
= 'with-proxy-payload.unexpected-protocol-incoming.tests.powerdns.com.'
801 query
= dns
.message
.make_query(name
, 'A', 'IN')
803 # Make sure that the proxy payload does NOT turn into a legal qname
804 destAddr
= "ff:db8::ffff"
806 srcAddr
= "ff:db8::ffff"
809 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
810 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
811 self
.assertEqual(receivedResponse
, None)
813 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
814 wire
= query
.to_wire()
816 receivedResponse
= None
818 conn
= self
.openTCPConnection(2.0)
819 conn
.send(tcpPayload
)
820 conn
.send(struct
.pack("!H", len(wire
)))
822 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
823 except socket
.timeout
:
825 self
.assertEqual(receivedResponse
, None)
827 class TestProxyProtocolNotAllowedOnBind(DNSDistTest
):
829 dnsdist is configured to expect a Proxy Protocol header on incoming queries but not on the 127.0.0.1 bind
831 _skipListeningOnCL
= True
832 _config_template
= """
833 -- proxy protocol payloads are not allowed on this bind address!
834 addLocal('127.0.0.1:%d', {enableProxyProtocol=false})
835 setProxyProtocolACL( { "127.0.0.1/8" } )
836 newServer{address="127.0.0.1:%d"}
838 # NORMAL responder, does not expect a proxy protocol payload!
839 _config_params
= ['_dnsDistPort', '_testServerPort']
841 def testNoHeader(self
):
843 Unexpected Proxy Protocol: no header
845 # no proxy protocol header and none is expected from this source, should be passed on
846 name
= 'no-header.unexpected-proxy-protocol.tests.powerdns.com.'
847 query
= dns
.message
.make_query(name
, 'A', 'IN')
848 response
= dns
.message
.make_response(query
)
849 rrset
= dns
.rrset
.from_text(name
,
855 response
.answer
.append(rrset
)
857 for method
in ("sendUDPQuery", "sendTCPQuery"):
858 sender
= getattr(self
, method
)
859 (receivedQuery
, receivedResponse
) = sender(query
, response
)
860 receivedQuery
.id = query
.id
861 self
.assertEqual(query
, receivedQuery
)
862 self
.assertEqual(response
, receivedResponse
)
864 def testIncomingProxyDest(self
):
866 Unexpected Proxy Protocol: should be dropped
868 name
= 'with-proxy-payload.unexpected-protocol-incoming.tests.powerdns.com.'
869 query
= dns
.message
.make_query(name
, 'A', 'IN')
871 # Make sure that the proxy payload does NOT turn into a legal qname
872 destAddr
= "ff:db8::ffff"
874 srcAddr
= "ff:db8::ffff"
877 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
878 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
879 self
.assertEqual(receivedResponse
, None)
881 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
882 wire
= query
.to_wire()
884 receivedResponse
= None
886 conn
= self
.openTCPConnection(2.0)
887 conn
.send(tcpPayload
)
888 conn
.send(struct
.pack("!H", len(wire
)))
890 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
891 except socket
.timeout
:
893 self
.assertEqual(receivedResponse
, None)
895 class TestDOHWithOutgoingProxyProtocol(DNSDistDOHTest
):
897 _serverKey
= 'server.key'
898 _serverCert
= 'server.chain'
899 _serverName
= 'tls.tests.dnsdist.org'
901 _dohWithNGHTTP2ServerPort
= pickAvailablePort()
902 _dohWithNGHTTP2BaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohWithNGHTTP2ServerPort
))
903 _dohWithH2OServerPort
= pickAvailablePort()
904 _dohWithH2OBaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohWithH2OServerPort
))
905 _proxyResponderPort
= proxyResponderPort
906 _config_template
= """
907 newServer{address="127.0.0.1:%s", useProxyProtocol=true}
908 addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true, library='nghttp2' })
909 addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true, library='h2o' })
910 setACL( { "::1/128", "127.0.0.0/8" } )
912 _config_params
= ['_proxyResponderPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey']
915 def testTruncation(self
):
917 DOH: Truncation over UDP
919 # the query is first forwarded over UDP, leading to a TC=1 answer from the
920 # backend, then over TCP
921 name
= 'truncated-udp.doh.proxy-protocol.tests.powerdns.com.'
922 query
= dns
.message
.make_query(name
, 'A', 'IN')
924 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
925 expectedQuery
.id = 42
926 response
= dns
.message
.make_response(query
)
927 rrset
= dns
.rrset
.from_text(name
,
932 response
.answer
.append(rrset
)
934 for (port
,url
) in [(self
._dohWithNGHTTP
2ServerPort
, self
._dohWithNGHTTP
2BaseURL
), (self
._dohWithH
2OServerPort
, self
._dohWithH
2OBaseURL
)]:
935 # first response is a TC=1
936 tcResponse
= dns
.message
.make_response(query
)
937 tcResponse
.flags |
= dns
.flags
.TC
938 toProxyQueue
.put(tcResponse
, True, 2.0)
940 ((receivedProxyPayload
, receivedDNSData
), receivedResponse
) = self
.sendDOHQuery(port
, self
._serverName
, url
, query
, caFile
=self
._caCert
, response
=response
, fromQueue
=fromProxyQueue
, toQueue
=toProxyQueue
)
941 # first query, received by the responder over UDP
942 self
.assertTrue(receivedProxyPayload
)
943 self
.assertTrue(receivedDNSData
)
944 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
945 self
.assertTrue(receivedQuery
)
946 receivedQuery
.id = expectedQuery
.id
947 self
.assertEqual(expectedQuery
, receivedQuery
)
948 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
949 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, destinationPort
=port
)
952 self
.assertTrue(receivedResponse
)
953 self
.assertEqual(response
, receivedResponse
)
955 # check the second query, received by the responder over TCP
956 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
957 self
.assertTrue(receivedDNSData
)
958 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
959 self
.assertTrue(receivedQuery
)
960 receivedQuery
.id = expectedQuery
.id
961 self
.assertEqual(expectedQuery
, receivedQuery
)
962 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
963 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, destinationPort
=port
)
965 # make sure we consumed everything
966 self
.assertTrue(toProxyQueue
.empty())
967 self
.assertTrue(fromProxyQueue
.empty())
969 def testAddressFamilyMismatch(self
):
971 DOH with IPv6 X-Forwarded-For to an IPv4 endpoint
973 name
= 'x-forwarded-for-af-mismatch.doh.outgoing-proxy-protocol.tests.powerdns.com.'
974 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
976 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
978 response
= dns
.message
.make_response(query
)
979 rrset
= dns
.rrset
.from_text(name
,
984 response
.answer
.append(rrset
)
986 for (port
,url
) in [(self
._dohWithNGHTTP
2ServerPort
, self
._dohWithNGHTTP
2BaseURL
), (self
._dohWithH
2OServerPort
, self
._dohWithH
2OBaseURL
)]:
987 # the query should be dropped
988 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(port
, self
._serverName
, url
, query
, caFile
=self
._caCert
, customHeaders
=['x-forwarded-for: [::1]:8080'], useQueue
=False)
989 self
.assertFalse(receivedQuery
)
990 self
.assertFalse(receivedResponse
)
992 # make sure the timeout is detected, if any
995 # this one should not
996 ((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
)
997 self
.assertTrue(receivedProxyPayload
)
998 self
.assertTrue(receivedDNSData
)
999 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
1000 self
.assertTrue(receivedQuery
)
1001 receivedQuery
.id = expectedQuery
.id
1002 self
.assertEqual(expectedQuery
, receivedQuery
)
1003 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
1004 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.42', '127.0.0.1', True, destinationPort
=port
)
1005 # check the response
1006 self
.assertTrue(receivedResponse
)
1007 receivedResponse
.id = response
.id
1008 self
.assertEqual(response
, receivedResponse
)