11 from dnsdisttests
import DNSDistTest
, pickAvailablePort
12 from proxyprotocol
import ProxyProtocol
13 from dnsdistdohtests
import DNSDistDOHTest
15 # Python2/3 compatibility hacks
17 from queue
import Queue
19 from Queue
import Queue
21 def ProxyProtocolUDPResponder(port
, fromQueue
, toQueue
):
22 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
)
23 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEPORT
, 1)
25 sock
.bind(("127.0.0.1", port
))
26 except socket
.error
as e
:
27 print("Error binding in the Proxy Protocol UDP responder: %s" % str(e
))
31 data
, addr
= sock
.recvfrom(4096)
33 proxy
= ProxyProtocol()
34 if len(data
) < proxy
.HEADER_SIZE
:
37 if not proxy
.parseHeader(data
):
41 # likely a healthcheck
42 data
= data
[proxy
.HEADER_SIZE
:]
43 request
= dns
.message
.from_wire(data
)
44 response
= dns
.message
.make_response(request
)
45 wire
= response
.to_wire()
47 sock
.sendto(wire
, addr
)
52 payload
= data
[:(proxy
.HEADER_SIZE
+ proxy
.contentLen
)]
53 dnsData
= data
[(proxy
.HEADER_SIZE
+ proxy
.contentLen
):]
54 toQueue
.put([payload
, dnsData
], True, 2.0)
55 # computing the correct ID for the response
56 request
= dns
.message
.from_wire(dnsData
)
57 response
= fromQueue
.get(True, 2.0)
58 response
.id = request
.id
61 sock
.sendto(response
.to_wire(), addr
)
66 def ProxyProtocolTCPResponder(port
, fromQueue
, toQueue
):
67 # be aware that this responder will not accept a new connection
68 # until the last one has been closed. This is done on purpose to
69 # to check for connection reuse, making sure that a lot of connections
70 # are not opened in parallel.
71 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
72 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEPORT
, 1)
73 sock
.setsockopt(socket
.IPPROTO_TCP
, socket
.TCP_NODELAY
, 1)
75 sock
.bind(("127.0.0.1", port
))
76 except socket
.error
as e
:
77 print("Error binding in the TCP responder: %s" % str(e
))
82 (conn
, _
) = sock
.accept()
84 # try to read the entire Proxy Protocol header
85 proxy
= ProxyProtocol()
86 header
= conn
.recv(proxy
.HEADER_SIZE
)
91 if not proxy
.parseHeader(header
):
95 proxyContent
= conn
.recv(proxy
.contentLen
)
100 payload
= header
+ proxyContent
104 except socket
.timeout
:
111 (datalen
,) = struct
.unpack("!H", data
)
112 data
= conn
.recv(datalen
)
114 toQueue
.put([payload
, data
], True, 2.0)
116 response
= copy
.deepcopy(fromQueue
.get(True, 2.0))
121 # computing the correct ID for the response
122 request
= dns
.message
.from_wire(data
)
123 response
.id = request
.id
125 wire
= response
.to_wire()
126 conn
.send(struct
.pack("!H", len(wire
)))
133 toProxyQueue
= Queue()
134 fromProxyQueue
= Queue()
135 proxyResponderPort
= pickAvailablePort()
137 udpResponder
= threading
.Thread(name
='UDP Proxy Protocol Responder', target
=ProxyProtocolUDPResponder
, args
=[proxyResponderPort
, toProxyQueue
, fromProxyQueue
])
138 udpResponder
.daemon
= True
140 tcpResponder
= threading
.Thread(name
='TCP Proxy Protocol Responder', target
=ProxyProtocolTCPResponder
, args
=[proxyResponderPort
, toProxyQueue
, fromProxyQueue
])
141 tcpResponder
.daemon
= True
144 class ProxyProtocolTest(DNSDistTest
):
145 _proxyResponderPort
= proxyResponderPort
146 _config_params
= ['_proxyResponderPort']
148 class TestProxyProtocol(ProxyProtocolTest
):
150 dnsdist is configured to prepend a Proxy Protocol header to the query
153 _config_template
= """
154 newServer{address="127.0.0.1:%d", useProxyProtocol=true}
156 function addValues(dq)
157 local values = { [0]="foo", [42]="bar" }
158 dq:setProxyProtocolValues(values)
159 return DNSAction.None
162 addAction("values-lua.proxy.tests.powerdns.com.", LuaAction(addValues))
163 addAction("values-action.proxy.tests.powerdns.com.", SetProxyProtocolValuesAction({ ["1"]="dnsdist", ["255"]="proxy-protocol"}))
165 _config_params
= ['_proxyResponderPort']
168 def testProxyUDP(self
):
170 Proxy Protocol: no value (UDP)
172 name
= 'simple-udp.proxy.tests.powerdns.com.'
173 query
= dns
.message
.make_query(name
, 'A', 'IN')
174 response
= dns
.message
.make_response(query
)
176 toProxyQueue
.put(response
, True, 2.0)
178 data
= query
.to_wire()
179 self
._sock
.send(data
)
180 receivedResponse
= None
182 self
._sock
.settimeout(2.0)
183 data
= self
._sock
.recv(4096)
184 except socket
.timeout
:
188 receivedResponse
= dns
.message
.from_wire(data
)
190 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
191 self
.assertTrue(receivedProxyPayload
)
192 self
.assertTrue(receivedDNSData
)
193 self
.assertTrue(receivedResponse
)
195 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
196 receivedQuery
.id = query
.id
197 receivedResponse
.id = response
.id
198 self
.assertEqual(receivedQuery
, query
)
199 self
.assertEqual(receivedResponse
, response
)
200 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', False)
202 def testProxyTCP(self
):
204 Proxy Protocol: no value (TCP)
206 name
= 'simple-tcp.proxy.tests.powerdns.com.'
207 query
= dns
.message
.make_query(name
, 'A', 'IN')
208 response
= dns
.message
.make_response(query
)
210 toProxyQueue
.put(response
, True, 2.0)
212 conn
= self
.openTCPConnection(2.0)
213 data
= query
.to_wire()
214 self
.sendTCPQueryOverConnection(conn
, data
, rawQuery
=True)
215 receivedResponse
= None
217 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
218 except socket
.timeout
:
221 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
222 self
.assertTrue(receivedProxyPayload
)
223 self
.assertTrue(receivedDNSData
)
224 self
.assertTrue(receivedResponse
)
226 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
227 receivedQuery
.id = query
.id
228 receivedResponse
.id = response
.id
229 self
.assertEqual(receivedQuery
, query
)
230 self
.assertEqual(receivedResponse
, response
)
231 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True)
233 def testProxyUDPWithValuesFromLua(self
):
235 Proxy Protocol: values from Lua (UDP)
237 name
= 'values-lua.proxy.tests.powerdns.com.'
238 query
= dns
.message
.make_query(name
, 'A', 'IN')
239 response
= dns
.message
.make_response(query
)
241 toProxyQueue
.put(response
, True, 2.0)
243 data
= query
.to_wire()
244 self
._sock
.send(data
)
245 receivedResponse
= None
247 self
._sock
.settimeout(2.0)
248 data
= self
._sock
.recv(4096)
249 except socket
.timeout
:
253 receivedResponse
= dns
.message
.from_wire(data
)
255 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
256 self
.assertTrue(receivedProxyPayload
)
257 self
.assertTrue(receivedDNSData
)
258 self
.assertTrue(receivedResponse
)
260 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
261 receivedQuery
.id = query
.id
262 receivedResponse
.id = response
.id
263 self
.assertEqual(receivedQuery
, query
)
264 self
.assertEqual(receivedResponse
, response
)
265 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', False, [ [0, b
'foo'] , [ 42, b
'bar'] ])
267 def testProxyTCPWithValuesFromLua(self
):
269 Proxy Protocol: values from Lua (TCP)
271 name
= 'values-lua.proxy.tests.powerdns.com.'
272 query
= dns
.message
.make_query(name
, 'A', 'IN')
273 response
= dns
.message
.make_response(query
)
275 toProxyQueue
.put(response
, True, 2.0)
277 conn
= self
.openTCPConnection(2.0)
278 data
= query
.to_wire()
279 self
.sendTCPQueryOverConnection(conn
, data
, rawQuery
=True)
280 receivedResponse
= None
282 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
283 except socket
.timeout
:
286 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
287 self
.assertTrue(receivedProxyPayload
)
288 self
.assertTrue(receivedDNSData
)
289 self
.assertTrue(receivedResponse
)
291 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
292 receivedQuery
.id = query
.id
293 receivedResponse
.id = response
.id
294 self
.assertEqual(receivedQuery
, query
)
295 self
.assertEqual(receivedResponse
, response
)
296 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [ [0, b
'foo'] , [ 42, b
'bar'] ])
298 def testProxyUDPWithValuesFromAction(self
):
300 Proxy Protocol: values from Action (UDP)
302 name
= 'values-action.proxy.tests.powerdns.com.'
303 query
= dns
.message
.make_query(name
, 'A', 'IN')
304 response
= dns
.message
.make_response(query
)
306 toProxyQueue
.put(response
, True, 2.0)
308 data
= query
.to_wire()
309 self
._sock
.send(data
)
310 receivedResponse
= None
312 self
._sock
.settimeout(2.0)
313 data
= self
._sock
.recv(4096)
314 except socket
.timeout
:
318 receivedResponse
= dns
.message
.from_wire(data
)
320 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
321 self
.assertTrue(receivedProxyPayload
)
322 self
.assertTrue(receivedDNSData
)
323 self
.assertTrue(receivedResponse
)
325 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
326 receivedQuery
.id = query
.id
327 receivedResponse
.id = response
.id
328 self
.assertEqual(receivedQuery
, query
)
329 self
.assertEqual(receivedResponse
, response
)
330 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', False, [ [1, b
'dnsdist'] , [ 255, b
'proxy-protocol'] ])
332 def testProxyTCPWithValuesFromAction(self
):
334 Proxy Protocol: values from Action (TCP)
336 name
= 'values-action.proxy.tests.powerdns.com.'
337 query
= dns
.message
.make_query(name
, 'A', 'IN')
338 response
= dns
.message
.make_response(query
)
340 toProxyQueue
.put(response
, True, 2.0)
342 conn
= self
.openTCPConnection(2.0)
343 data
= query
.to_wire()
344 self
.sendTCPQueryOverConnection(conn
, data
, rawQuery
=True)
345 receivedResponse
= None
347 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
348 except socket
.timeout
:
351 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
352 self
.assertTrue(receivedProxyPayload
)
353 self
.assertTrue(receivedDNSData
)
354 self
.assertTrue(receivedResponse
)
356 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
357 receivedQuery
.id = query
.id
358 receivedResponse
.id = response
.id
359 self
.assertEqual(receivedQuery
, query
)
360 self
.assertEqual(receivedResponse
, response
)
361 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [ [1, b
'dnsdist'] , [ 255, b
'proxy-protocol'] ])
363 def testProxyTCPSeveralQueriesOnSameConnection(self
):
365 Proxy Protocol: Several queries on the same TCP connection
367 name
= 'several-queries-same-conn.proxy.tests.powerdns.com.'
368 query
= dns
.message
.make_query(name
, 'A', 'IN')
369 response
= dns
.message
.make_response(query
)
371 conn
= self
.openTCPConnection(2.0)
372 data
= query
.to_wire()
374 for idx
in range(10):
375 toProxyQueue
.put(response
, True, 2.0)
376 self
.sendTCPQueryOverConnection(conn
, data
, rawQuery
=True)
377 receivedResponse
= None
379 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
380 except socket
.timeout
:
383 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
384 self
.assertTrue(receivedProxyPayload
)
385 self
.assertTrue(receivedDNSData
)
386 self
.assertTrue(receivedResponse
)
388 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
389 receivedQuery
.id = query
.id
390 receivedResponse
.id = response
.id
391 self
.assertEqual(receivedQuery
, query
)
392 self
.assertEqual(receivedResponse
, response
)
393 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, [])
395 class TestProxyProtocolIncoming(ProxyProtocolTest
):
397 dnsdist is configured to prepend a Proxy Protocol header to the query and expect one on incoming queries
400 _config_template
= """
401 setProxyProtocolACL( { "127.0.0.1/32" } )
402 newServer{address="127.0.0.1:%d", useProxyProtocol=true}
404 function addValues(dq)
405 dq:addProxyProtocolValue(0, 'foo')
406 dq:addProxyProtocolValue(42, 'bar')
407 return DNSAction.None
410 -- refuse queries with no TLV value type 2
411 addAction(NotRule(ProxyProtocolValueRule(2)), RCodeAction(DNSRCode.REFUSED))
412 -- or with a TLV value type 3 different from "proxy"
413 addAction(NotRule(ProxyProtocolValueRule(3, "proxy")), RCodeAction(DNSRCode.REFUSED))
415 function answerBasedOnForwardedDest(dq)
416 local port = dq.localaddr:getPort()
417 local dest = dq.localaddr:toString()
418 return DNSAction.Spoof, "address-was-"..dest.."-port-was-"..port..".proxy-protocol-incoming.tests.powerdns.com."
420 addAction("get-forwarded-dest.proxy-protocol-incoming.tests.powerdns.com.", LuaAction(answerBasedOnForwardedDest))
422 function answerBasedOnForwardedSrc(dq)
423 local port = dq.remoteaddr:getPort()
424 local src = dq.remoteaddr:toString()
425 return DNSAction.Spoof, "address-was-"..src.."-port-was-"..port..".proxy-protocol-incoming.tests.powerdns.com."
427 addAction("get-forwarded-src.proxy-protocol-incoming.tests.powerdns.com.", LuaAction(answerBasedOnForwardedSrc))
429 -- add these values for all queries
430 addAction("proxy-protocol-incoming.tests.powerdns.com.", LuaAction(addValues))
431 addAction("proxy-protocol-incoming.tests.powerdns.com.", SetAdditionalProxyProtocolValueAction(1, "dnsdist"))
432 addAction("proxy-protocol-incoming.tests.powerdns.com.", SetAdditionalProxyProtocolValueAction(255, "proxy-protocol"))
434 -- override all existing values
435 addAction("override.proxy-protocol-incoming.tests.powerdns.com.", SetProxyProtocolValuesAction({["50"]="overridden"}))
437 _config_params
= ['_proxyResponderPort']
440 def testNoHeader(self
):
442 Incoming Proxy Protocol: no header
444 # no proxy protocol header while one is expected, should be dropped
445 name
= 'no-header.incoming-proxy-protocol.tests.powerdns.com.'
446 query
= dns
.message
.make_query(name
, 'A', 'IN')
448 for method
in ("sendUDPQuery", "sendTCPQuery"):
449 sender
= getattr(self
, method
)
450 (_
, receivedResponse
) = sender(query
, response
=None)
451 self
.assertEqual(receivedResponse
, None)
453 def testIncomingProxyDest(self
):
455 Incoming Proxy Protocol: values from Lua
457 name
= 'get-forwarded-dest.proxy-protocol-incoming.tests.powerdns.com.'
458 query
= dns
.message
.make_query(name
, 'A', 'IN')
459 # dnsdist set RA = RD for spoofed responses
460 query
.flags
&= ~dns
.flags
.RD
462 destAddr
= "2001:db8::9"
464 srcAddr
= "2001:db8::8"
466 response
= dns
.message
.make_response(query
)
467 rrset
= dns
.rrset
.from_text(name
,
471 "address-was-{}-port-was-{}.proxy-protocol-incoming.tests.powerdns.com.".format(destAddr
, destPort
, self
._dnsDistPort
))
472 response
.answer
.append(rrset
)
474 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
475 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
476 self
.assertEqual(receivedResponse
, response
)
478 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
479 wire
= query
.to_wire()
481 receivedResponse
= None
483 conn
= self
.openTCPConnection(2.0)
484 conn
.send(tcpPayload
)
485 conn
.send(struct
.pack("!H", len(wire
)))
487 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
488 except socket
.timeout
:
490 self
.assertEqual(receivedResponse
, response
)
492 def testProxyUDPWithValuesFromLua(self
):
494 Incoming Proxy Protocol: values from Lua (UDP)
496 name
= 'values-lua.proxy-protocol-incoming.tests.powerdns.com.'
497 query
= dns
.message
.make_query(name
, 'A', 'IN')
498 response
= dns
.message
.make_response(query
)
500 destAddr
= "2001:db8::9"
502 srcAddr
= "2001:db8::8"
504 response
= dns
.message
.make_response(query
)
506 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
507 toProxyQueue
.put(response
, True, 2.0)
508 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
510 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
511 self
.assertTrue(receivedProxyPayload
)
512 self
.assertTrue(receivedDNSData
)
513 self
.assertTrue(receivedResponse
)
515 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
516 receivedQuery
.id = query
.id
517 receivedResponse
.id = response
.id
518 self
.assertEqual(receivedQuery
, query
)
519 self
.assertEqual(receivedResponse
, response
)
520 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
)
522 def testProxyTCPWithValuesFromLua(self
):
524 Incoming Proxy Protocol: values from Lua (TCP)
526 name
= 'values-lua.proxy-protocol-incoming.tests.powerdns.com.'
527 query
= dns
.message
.make_query(name
, 'A', 'IN')
528 response
= dns
.message
.make_response(query
)
530 destAddr
= "2001:db8::9"
532 srcAddr
= "2001:db8::8"
534 response
= dns
.message
.make_response(query
)
536 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
538 toProxyQueue
.put(response
, True, 2.0)
540 wire
= query
.to_wire()
542 receivedResponse
= None
544 conn
= self
.openTCPConnection(2.0)
545 conn
.send(tcpPayload
)
546 conn
.send(struct
.pack("!H", len(wire
)))
548 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
549 except socket
.timeout
:
551 self
.assertEqual(receivedResponse
, response
)
553 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
554 self
.assertTrue(receivedProxyPayload
)
555 self
.assertTrue(receivedDNSData
)
556 self
.assertTrue(receivedResponse
)
558 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
559 receivedQuery
.id = query
.id
560 self
.assertEqual(receivedQuery
, query
)
561 self
.assertEqual(receivedResponse
, response
)
562 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
)
564 def testProxyUDPWithValueOverride(self
):
566 Incoming Proxy Protocol: override existing value (UDP)
568 name
= 'override.proxy-protocol-incoming.tests.powerdns.com.'
569 query
= dns
.message
.make_query(name
, 'A', 'IN')
570 response
= dns
.message
.make_response(query
)
572 destAddr
= "2001:db8::9"
574 srcAddr
= "2001:db8::8"
576 response
= dns
.message
.make_response(query
)
578 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [2, b
'foo'], [3, b
'proxy'], [ 50, b
'initial-value']])
579 toProxyQueue
.put(response
, True, 2.0)
580 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
582 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
583 self
.assertTrue(receivedProxyPayload
)
584 self
.assertTrue(receivedDNSData
)
585 self
.assertTrue(receivedResponse
)
587 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
588 receivedQuery
.id = query
.id
589 receivedResponse
.id = response
.id
590 self
.assertEqual(receivedQuery
, query
)
591 self
.assertEqual(receivedResponse
, response
)
592 self
.checkMessageProxyProtocol(receivedProxyPayload
, srcAddr
, destAddr
, False, [ [50, b
'overridden'] ], True, srcPort
, destPort
)
594 def testProxyTCPSeveralQueriesOverConnection(self
):
596 Incoming Proxy Protocol: Several queries over the same connection (TCP)
598 name
= 'several-queries.proxy-protocol-incoming.tests.powerdns.com.'
599 query
= dns
.message
.make_query(name
, 'A', 'IN')
600 response
= dns
.message
.make_response(query
)
602 destAddr
= "2001:db8::9"
604 srcAddr
= "2001:db8::8"
607 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
609 toProxyQueue
.put(response
, True, 2.0)
611 wire
= query
.to_wire()
613 receivedResponse
= None
614 conn
= self
.openTCPConnection(2.0)
616 conn
.send(tcpPayload
)
617 conn
.send(struct
.pack("!H", len(wire
)))
619 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
620 except socket
.timeout
:
622 self
.assertEqual(receivedResponse
, response
)
624 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
625 self
.assertTrue(receivedProxyPayload
)
626 self
.assertTrue(receivedDNSData
)
627 self
.assertTrue(receivedResponse
)
629 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
630 receivedQuery
.id = query
.id
631 receivedResponse
.id = response
.id
632 self
.assertEqual(receivedQuery
, query
)
633 self
.assertEqual(receivedResponse
, response
)
634 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
)
637 receivedResponse
= None
638 toProxyQueue
.put(response
, True, 2.0)
640 conn
.send(struct
.pack("!H", len(wire
)))
642 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
643 except socket
.timeout
:
646 self
.assertEqual(receivedResponse
, response
)
648 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
649 self
.assertTrue(receivedProxyPayload
)
650 self
.assertTrue(receivedDNSData
)
651 self
.assertTrue(receivedResponse
)
653 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
654 receivedQuery
.id = query
.id
655 self
.assertEqual(receivedQuery
, query
)
656 self
.assertEqual(receivedResponse
, response
)
657 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
)
659 class TestProxyProtocolNotExpected(DNSDistTest
):
661 dnsdist is configured to expect a Proxy Protocol header on incoming queries but not from 127.0.0.1
664 _config_template
= """
665 setProxyProtocolACL( { "192.0.2.1/32" } )
666 newServer{address="127.0.0.1:%d"}
668 # NORMAL responder, does not expect a proxy protocol payload!
669 _config_params
= ['_testServerPort']
672 def testNoHeader(self
):
674 Unexpected Proxy Protocol: no header
676 # no proxy protocol header and none is expected from this source, should be passed on
677 name
= 'no-header.unexpected-proxy-protocol.tests.powerdns.com.'
678 query
= dns
.message
.make_query(name
, 'A', 'IN')
679 response
= dns
.message
.make_response(query
)
680 rrset
= dns
.rrset
.from_text(name
,
686 response
.answer
.append(rrset
)
688 for method
in ("sendUDPQuery", "sendTCPQuery"):
689 sender
= getattr(self
, method
)
690 (receivedQuery
, receivedResponse
) = sender(query
, response
)
691 receivedQuery
.id = query
.id
692 self
.assertEqual(query
, receivedQuery
)
693 self
.assertEqual(response
, receivedResponse
)
695 def testIncomingProxyDest(self
):
697 Unexpected Proxy Protocol: should be dropped
699 name
= 'with-proxy-payload.unexpected-protocol-incoming.tests.powerdns.com.'
700 query
= dns
.message
.make_query(name
, 'A', 'IN')
702 # Make sure that the proxy payload does NOT turn into a legal qname
703 destAddr
= "ff:db8::ffff"
705 srcAddr
= "ff:db8::ffff"
708 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
709 (_
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
=None, useQueue
=False, rawQuery
=True)
710 self
.assertEqual(receivedResponse
, None)
712 tcpPayload
= ProxyProtocol
.getPayload(False, True, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 3, b
'proxy'] ])
713 wire
= query
.to_wire()
715 receivedResponse
= None
717 conn
= self
.openTCPConnection(2.0)
718 conn
.send(tcpPayload
)
719 conn
.send(struct
.pack("!H", len(wire
)))
721 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
722 except socket
.timeout
:
724 self
.assertEqual(receivedResponse
, None)
726 class TestDOHWithOutgoingProxyProtocol(DNSDistDOHTest
):
728 _serverKey
= 'server.key'
729 _serverCert
= 'server.chain'
730 _serverName
= 'tls.tests.dnsdist.org'
732 _dohServerPort
= pickAvailablePort()
733 _dohBaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohServerPort
))
734 _proxyResponderPort
= proxyResponderPort
735 _config_template
= """
736 newServer{address="127.0.0.1:%s", useProxyProtocol=true}
737 addDOHLocal("127.0.0.1:%s", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true })
738 setACL( { "::1/128", "127.0.0.0/8" } )
740 _config_params
= ['_proxyResponderPort', '_dohServerPort', '_serverCert', '_serverKey']
742 def testTruncation(self
):
744 DOH: Truncation over UDP (with cache)
746 # the query is first forwarded over UDP, leading to a TC=1 answer from the
747 # backend, then over TCP
748 name
= 'truncated-udp.doh-with-cache.tests.powerdns.com.'
749 query
= dns
.message
.make_query(name
, 'A', 'IN')
751 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
752 expectedQuery
.id = 42
753 response
= dns
.message
.make_response(query
)
754 rrset
= dns
.rrset
.from_text(name
,
759 response
.answer
.append(rrset
)
761 # first response is a TC=1
762 tcResponse
= dns
.message
.make_response(query
)
763 tcResponse
.flags |
= dns
.flags
.TC
764 toProxyQueue
.put(tcResponse
, True, 2.0)
766 ((receivedProxyPayload
, receivedDNSData
), receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, response
=response
, fromQueue
=fromProxyQueue
, toQueue
=toProxyQueue
)
767 # first query, received by the responder over UDP
768 self
.assertTrue(receivedProxyPayload
)
769 self
.assertTrue(receivedDNSData
)
770 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
771 self
.assertTrue(receivedQuery
)
772 receivedQuery
.id = expectedQuery
.id
773 self
.assertEqual(expectedQuery
, receivedQuery
)
774 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
775 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, destinationPort
=self
._dohServerPort
)
778 self
.assertTrue(receivedResponse
)
779 self
.assertEqual(response
, receivedResponse
)
781 # check the second query, received by the responder over TCP
782 (receivedProxyPayload
, receivedDNSData
) = fromProxyQueue
.get(True, 2.0)
783 self
.assertTrue(receivedDNSData
)
784 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
785 self
.assertTrue(receivedQuery
)
786 receivedQuery
.id = expectedQuery
.id
787 self
.assertEqual(expectedQuery
, receivedQuery
)
788 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
789 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.1', '127.0.0.1', True, destinationPort
=self
._dohServerPort
)
791 # make sure we consumed everything
792 self
.assertTrue(toProxyQueue
.empty())
793 self
.assertTrue(fromProxyQueue
.empty())
795 def testAddressFamilyMismatch(self
):
797 DOH with IPv6 X-Forwarded-For to an IPv4 endpoint
799 name
= 'x-forwarded-for-af-mismatch.doh.outgoing-proxy-protocol.tests.powerdns.com.'
800 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
802 expectedQuery
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=True, payload
=4096)
804 response
= dns
.message
.make_response(query
)
805 rrset
= dns
.rrset
.from_text(name
,
810 response
.answer
.append(rrset
)
812 # the query should be dropped
813 (receivedQuery
, receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, customHeaders
=['x-forwarded-for: [::1]:8080'], useQueue
=False)
814 self
.assertFalse(receivedQuery
)
815 self
.assertFalse(receivedResponse
)
817 # make sure the timeout is detected, if any
820 # this one should not
821 ((receivedProxyPayload
, receivedDNSData
), receivedResponse
) = self
.sendDOHQuery(self
._dohServerPort
, self
._serverName
, self
._dohBaseURL
, query
, caFile
=self
._caCert
, customHeaders
=['x-forwarded-for: 127.0.0.42:8080'], response
=response
, fromQueue
=fromProxyQueue
, toQueue
=toProxyQueue
)
822 self
.assertTrue(receivedProxyPayload
)
823 self
.assertTrue(receivedDNSData
)
824 receivedQuery
= dns
.message
.from_wire(receivedDNSData
)
825 self
.assertTrue(receivedQuery
)
826 receivedQuery
.id = expectedQuery
.id
827 self
.assertEqual(expectedQuery
, receivedQuery
)
828 self
.checkQueryEDNSWithoutECS(expectedQuery
, receivedQuery
)
829 self
.checkMessageProxyProtocol(receivedProxyPayload
, '127.0.0.42', '127.0.0.1', True, destinationPort
=self
._dohServerPort
)
831 self
.assertTrue(receivedResponse
)
832 receivedResponse
.id = response
.id
833 self
.assertEqual(response
, receivedResponse
)