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 TestDOHWithOutgoingProxyProtocol(DNSDistDOHTest
):
829 _serverKey
= 'server.key'
830 _serverCert
= 'server.chain'
831 _serverName
= 'tls.tests.dnsdist.org'
833 _dohWithNGHTTP2ServerPort
= pickAvailablePort()
834 _dohWithNGHTTP2BaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohWithNGHTTP2ServerPort
))
835 _dohWithH2OServerPort
= pickAvailablePort()
836 _dohWithH2OBaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohWithH2OServerPort
))
837 _proxyResponderPort
= proxyResponderPort
838 _config_template
= """
839 newServer{address="127.0.0.1:%s", useProxyProtocol=true}
840 addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true, library='nghttp2' })
841 addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true, library='h2o' })
842 setACL( { "::1/128", "127.0.0.0/8" } )
844 _config_params
= ['_proxyResponderPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey']
847 def testTruncation(self
):
849 DOH: Truncation over UDP
851 # the query is first forwarded over UDP, leading to a TC=1 answer from the
852 # backend, then over TCP
853 name
= 'truncated-udp.doh.proxy-protocol.tests.powerdns.com.'
854 query
= dns
.message
.make_query(name
, 'A', 'IN')
856 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
857 expectedQuery
.id = 42
858 response
= dns
.message
.make_response(query
)
859 rrset
= dns
.rrset
.from_text(name
,
864 response
.answer
.append(rrset
)
866 for (port
,url
) in [(self
._dohWithNGHTTP
2ServerPort
, self
._dohWithNGHTTP
2BaseURL
), (self
._dohWithH
2OServerPort
, self
._dohWithH
2OBaseURL
)]:
867 # first response is a TC=1
868 tcResponse
= dns
.message
.make_response(query
)
869 tcResponse
.flags |
= dns
.flags
.TC
870 toProxyQueue
.put(tcResponse
, True, 2.0)
872 ((receivedProxyPayload
, receivedDNSData
), receivedResponse
) = self
.sendDOHQuery(port
, self
._serverName
, url
, query
, caFile
=self
._caCert
, response
=response
, fromQueue
=fromProxyQueue
, toQueue
=toProxyQueue
)
873 # first query, received by the responder over UDP
874 self
.assertTrue(receivedProxyPayload
)
875 self
.assertTrue(receivedDNSData
)
876 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
877 self
.assertTrue(receivedQuery
)
878 receivedQuery
.id = expectedQuery
.id
879 self
.assertEqual(expectedQuery
, receivedQuery
)
880 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
881 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, destinationPort
=port
)
884 self
.assertTrue(receivedResponse
)
885 self
.assertEqual(response
, receivedResponse
)
887 # check the second query, received by the responder over TCP
888 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
889 self
.assertTrue(receivedDNSData
)
890 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
891 self
.assertTrue(receivedQuery
)
892 receivedQuery
.id = expectedQuery
.id
893 self
.assertEqual(expectedQuery
, receivedQuery
)
894 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
895 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, destinationPort
=port
)
897 # make sure we consumed everything
898 self
.assertTrue(toProxyQueue
.empty())
899 self
.assertTrue(fromProxyQueue
.empty())
901 def testAddressFamilyMismatch(self
):
903 DOH with IPv6 X-Forwarded-For to an IPv4 endpoint
905 name
= 'x-forwarded-for-af-mismatch.doh.outgoing-proxy-protocol.tests.powerdns.com.'
906 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
908 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
910 response
= dns
.message
.make_response(query
)
911 rrset
= dns
.rrset
.from_text(name
,
916 response
.answer
.append(rrset
)
918 for (port
,url
) in [(self
._dohWithNGHTTP
2ServerPort
, self
._dohWithNGHTTP
2BaseURL
), (self
._dohWithH
2OServerPort
, self
._dohWithH
2OBaseURL
)]:
919 # the query should be dropped
920 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(port
, self
._serverName
, url
, query
, caFile
=self
._caCert
, customHeaders
=['x-forwarded-for: [::1]:8080'], useQueue
=False)
921 self
.assertFalse(receivedQuery
)
922 self
.assertFalse(receivedResponse
)
924 # make sure the timeout is detected, if any
927 # this one should not
928 ((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
)
929 self
.assertTrue(receivedProxyPayload
)
930 self
.assertTrue(receivedDNSData
)
931 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
932 self
.assertTrue(receivedQuery
)
933 receivedQuery
.id = expectedQuery
.id
934 self
.assertEqual(expectedQuery
, receivedQuery
)
935 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
936 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.42', '127.0.0.1', True, destinationPort
=port
)
938 self
.assertTrue(receivedResponse
)
939 receivedResponse
.id = response
.id
940 self
.assertEqual(response
, receivedResponse
)