7 from dnsdisttests
import DNSDistTest
, pickAvailablePort
9 class OOORTCPResponder(object):
11 def handleConnection(self
, conn
):
17 except socket
.timeout
:
24 (datalen
,) = struct
.unpack("!H", data
)
25 data
= conn
.recv(datalen
)
27 # computing the correct ID for the response
28 request
= dns
.message
.from_wire(data
)
29 #print("got a query for %s" % (request.question[0].name))
30 if request
.question
[0].name
== "0.simple.ooor.tests.powerdns.com":
33 response
= dns
.message
.make_response(request
)
35 wire
= response
.to_wire()
36 conn
.send(struct
.pack("!H", len(wire
)))
39 except ConnectionError
as err
:
40 print("Error in the thread handling reverse OOOR connections: %s" % (err
))
44 def __init__(self
, port
):
45 OOORTCPResponder
.numberOfConnections
= 0
47 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
48 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEPORT
, 1)
49 sock
.setsockopt(socket
.IPPROTO_TCP
, socket
.TCP_NODELAY
, 1)
51 sock
.bind(("127.0.0.1", port
))
52 except socket
.error
as e
:
53 print("Error binding in the TCP responder: %s" % str(e
))
58 (conn
, _
) = sock
.accept()
61 OOORTCPResponder
.numberOfConnections
= OOORTCPResponder
.numberOfConnections
+ 1
62 thread
= threading
.Thread(name
='Connection Handler',
63 target
=self
.handleConnection
,
70 class ReverseOOORTCPResponder(OOORTCPResponder
):
72 def handleConnection(self
, conn
):
74 # short timeout since we want to answer only after receiving 5 requests
83 except socket
.timeout
:
87 if timedout
or len(queuedResponses
) >= 5:
88 queuedResponses
.reverse()
89 for response
in queuedResponses
:
90 wire
= response
.to_wire()
91 conn
.send(struct
.pack("!H", len(wire
)))
100 (datalen
,) = struct
.unpack("!H", data
)
101 data
= conn
.recv(datalen
)
103 # computing the correct ID for the response
104 request
= dns
.message
.from_wire(data
)
105 #print("got a query for %s" % (request.question[0].name))
107 response
= dns
.message
.make_response(request
)
108 queuedResponses
.append(response
)
110 except ConnectionError
as err
:
111 print("Error in the thread handling reverse OOOR connections: %s" % (err
))
115 def __init__(self
, port
):
116 ReverseOOORTCPResponder
.numberOfConnections
= 0
118 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
119 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEPORT
, 1)
120 sock
.setsockopt(socket
.IPPROTO_TCP
, socket
.TCP_NODELAY
, 1)
122 sock
.bind(("127.0.0.1", port
))
123 except socket
.error
as e
:
124 print("Error binding in the TCP responder: %s" % str(e
))
129 (conn
, _
) = sock
.accept()
131 ReverseOOORTCPResponder
.numberOfConnections
= ReverseOOORTCPResponder
.numberOfConnections
+ 1
132 thread
= threading
.Thread(name
='Connection Handler',
133 target
=self
.handleConnection
,
141 OOORResponderPort
= pickAvailablePort()
142 ooorTCPResponder
= threading
.Thread(name
='TCP Responder', target
=OOORTCPResponder
, args
=[OOORResponderPort
])
143 ooorTCPResponder
.daemon
= True
144 ooorTCPResponder
.start()
146 ReverseOOORResponderPort
= pickAvailablePort()
147 ReverseOoorTCPResponder
= threading
.Thread(name
='TCP Responder', target
=ReverseOOORTCPResponder
, args
=[ReverseOOORResponderPort
])
148 ReverseOoorTCPResponder
.daemon
= True
149 ReverseOoorTCPResponder
.start()
151 class TestOOORWithClientNotBackend(DNSDistTest
):
152 # this test suite uses a different responder port
153 _testServerPort
= OOORResponderPort
155 _concurrentQueriesFromClient
= 10
156 _config_template
= """
157 newServer{address="127.0.0.1:%d", maxInFlight=0, pool={""}}:setUp()
158 newServer{address="127.0.0.1:%d", maxInFlight=0, pool={"more-queries"}}:setUp()
159 -- route these queries to a different backend so we don't reuse the connection from a previous test
160 addAction("more-queries.ooor.tests.powerdns.com.", PoolAction("more-queries"))
161 setLocal("%s:%d", {maxInFlight=%d})
163 _config_params
= ['_testServerPort', '_testServerPort', '_dnsDistListeningAddr', '_dnsDistPort', '_concurrentQueriesFromClient']
165 _skipListeningOnCL
= True
168 def startResponders(cls
):
171 def testSimple(self
):
176 OOORTCPResponder
.numberOfConnections
= 0
179 names
.append('%d.simple.ooor.tests.powerdns.com.' % (idx
))
181 conn
= self
.openTCPConnection()
185 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
187 counter
= counter
+ 1
189 self
.sendTCPQueryOverConnection(conn
, query
)
191 receivedResponses
= {}
194 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
195 self
.assertTrue(receivedResponse
)
196 receivedResponses
[str(receivedResponse
.question
[0].name
)] = (receivedResponse
)
198 self
.assertEqual(len(receivedResponses
), 5)
200 self
.assertIn('%d.simple.ooor.tests.powerdns.com.' % (idx
), receivedResponses
)
202 # we can get a response to one of the first query before they all have
203 # been read, reusing a backend connection
204 self
.assertLessEqual(OOORTCPResponder
.numberOfConnections
, 5)
206 def testMoreQueriesThanAllowedInFlight(self
):
208 OOOR: 100 queries, 10 in flight
211 OOORTCPResponder
.numberOfConnections
= 0
213 for idx
in range(100):
214 names
.append('%d.more-queries.ooor.tests.powerdns.com.' % (idx
))
216 conn
= self
.openTCPConnection()
220 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
222 counter
= counter
+ 1
224 self
.sendTCPQueryOverConnection(conn
, query
)
226 receivedResponses
= {}
229 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
230 self
.assertTrue(receivedResponse
)
231 receivedResponses
[str(receivedResponse
.question
[0].name
)] = (receivedResponse
)
233 self
.assertEqual(len(receivedResponses
), 100)
235 self
.assertIn('%d.more-queries.ooor.tests.powerdns.com.' % (idx
), receivedResponses
)
237 self
.assertLessEqual(OOORTCPResponder
.numberOfConnections
, self
._concurrentQueriesFromClient
)
239 class TestOOORWithClientAndBackend(DNSDistTest
):
240 # this test suite uses a different responder port
241 _testServerPort
= ReverseOOORResponderPort
243 _concurrentQueriesFromClient
= 10
244 _concurrentQueriesToServer
= 5
245 _config_template
= """
246 newServer{address="127.0.0.1:%d", maxInFlight=%d, pool={""}}:setUp()
247 newServer{address="127.0.0.1:%d", maxInFlight=%d, pool={"more-queries"}}:setUp()
248 -- route these queries to a different backend so we don't reuse the connection from a previous test
249 addAction("more-queries.reverse-ooor.tests.powerdns.com.", PoolAction("more-queries"))
250 setLocal("%s:%d", {maxInFlight=%d})
252 _config_params
= ['_testServerPort', '_concurrentQueriesToServer', '_testServerPort', '_concurrentQueriesToServer', '_dnsDistListeningAddr', '_dnsDistPort', '_concurrentQueriesFromClient']
254 _skipListeningOnCL
= True
257 def startResponders(cls
):
260 def testSimple(self
):
262 OOOR Reverse: 5 queries
265 ReverseOOORTCPResponder
.numberOfConnections
= 0
268 names
.append('%d.simple.reverse-ooor.tests.powerdns.com.' % (idx
))
270 conn
= self
.openTCPConnection()
274 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
276 counter
= counter
+ 1
278 self
.sendTCPQueryOverConnection(conn
, query
)
280 receivedResponses
= {}
283 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
284 self
.assertTrue(receivedResponse
)
285 receivedResponses
[str(receivedResponse
.question
[0].name
)] = (receivedResponse
)
287 self
.assertEqual(len(receivedResponses
), 5)
289 self
.assertIn('%d.simple.reverse-ooor.tests.powerdns.com.' % (idx
), receivedResponses
)
291 self
.assertEqual(ReverseOOORTCPResponder
.numberOfConnections
, 1)
293 def testMoreQueriesThanAllowedInFlight(self
):
295 OOOR Reverse: 100 queries, 10 in flight, 5 per backend
298 ReverseOOORTCPResponder
.numberOfConnections
= 0
300 for idx
in range(100):
301 names
.append('%d.more-queries.reverse-ooor.tests.powerdns.com.' % (idx
))
303 conn
= self
.openTCPConnection()
307 query
= dns
.message
.make_query(name
, 'A', 'IN', use_edns
=False)
309 counter
= counter
+ 1
311 self
.sendTCPQueryOverConnection(conn
, query
)
313 receivedResponses
= {}
316 receivedResponse
= self
.recvTCPResponseOverConnection(conn
)
317 self
.assertTrue(receivedResponse
)
318 receivedResponses
[str(receivedResponse
.question
[0].name
)] = (receivedResponse
)
319 #print("Received a response for %s" % (receivedResponse.question[0].name))
321 self
.assertEqual(len(receivedResponses
), 100)
323 self
.assertIn('%d.more-queries.reverse-ooor.tests.powerdns.com.' % (idx
), receivedResponses
)
325 # in theory they could all be handled by the same backend if we get the responses
326 # fast enough, but over 100 queries that's very, very unlikely
327 self
.assertEqual(ReverseOOORTCPResponder
.numberOfConnections
, 2)