]>
Commit | Line | Data |
---|---|---|
dcacff17 RG |
1 | #!/usr/bin/env python |
2 | import struct | |
3 | import time | |
4 | import dns | |
06b0e003 RG |
5 | from dnsdisttests import DNSDistTest |
6 | ||
7 | try: | |
f0d9b6b7 | 8 | range = xrange |
06b0e003 | 9 | except NameError: |
f0d9b6b7 | 10 | pass |
dcacff17 RG |
11 | |
12 | class TestTCPKeepAlive(DNSDistTest): | |
13 | """ | |
14 | These tests make sure that dnsdist keeps the TCP connection alive | |
15 | in various cases, like cache hits, self-generated answer, and | |
16 | that it doesn't in error cases (Drop, invalid queries...) | |
17 | """ | |
18 | ||
19 | _tcpIdleTimeout = 20 | |
20 | _maxTCPQueriesPerConn = 99 | |
f0d9b6b7 | 21 | _maxTCPConnsPerClient = 100 |
dcacff17 RG |
22 | _maxTCPConnDuration = 99 |
23 | _config_template = """ | |
24 | newServer{address="127.0.0.1:%s"} | |
25 | setTCPRecvTimeout(%s) | |
26 | setMaxTCPQueriesPerConnection(%s) | |
27 | setMaxTCPConnectionsPerClient(%s) | |
28 | setMaxTCPConnectionDuration(%s) | |
7d294573 | 29 | pc = newPacketCache(100, {maxTTL=86400, minTTL=1}) |
dcacff17 | 30 | getPool(""):setCache(pc) |
198d8159 | 31 | addAction("largernumberofconnections.tcpka.tests.powerdns.com.", SetSkipCacheAction()) |
d3ec24f9 | 32 | addAction("refused.tcpka.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED)) |
dcacff17 RG |
33 | addAction("dropped.tcpka.tests.powerdns.com.", DropAction()) |
34 | addResponseAction("dropped-response.tcpka.tests.powerdns.com.", DropResponseAction()) | |
35 | -- create the pool named "nosuchpool" | |
36 | getPool("nosuchpool") | |
37 | addAction("nodownstream-servfail.tcpka.tests.powerdns.com.", PoolAction("nosuchpool")) | |
38 | setServFailWhenNoServer(true) | |
39 | """ | |
40 | _config_params = ['_testServerPort', '_tcpIdleTimeout', '_maxTCPQueriesPerConn', '_maxTCPConnsPerClient', '_maxTCPConnDuration'] | |
41 | ||
42 | def testTCPKaSelfGenerated(self): | |
43 | """ | |
44 | TCP KeepAlive: Self-generated answer | |
45 | """ | |
46 | name = 'refused.tcpka.tests.powerdns.com.' | |
47 | query = dns.message.make_query(name, 'A', 'IN') | |
7af22479 | 48 | query.flags &= ~dns.flags.RD |
dcacff17 RG |
49 | expectedResponse = dns.message.make_response(query) |
50 | expectedResponse.set_rcode(dns.rcode.REFUSED) | |
51 | ||
52 | conn = self.openTCPConnection() | |
53 | ||
54 | count = 0 | |
b4f23783 | 55 | for idx in range(5): |
dcacff17 RG |
56 | try: |
57 | self.sendTCPQueryOverConnection(conn, query) | |
58 | response = self.recvTCPResponseOverConnection(conn) | |
59 | if response is None: | |
60 | break | |
4bfebc93 | 61 | self.assertEqual(expectedResponse, response) |
dcacff17 RG |
62 | count = count + 1 |
63 | except: | |
64 | pass | |
65 | ||
66 | conn.close() | |
67 | self.assertEqual(count, 5) | |
68 | ||
69 | def testTCPKaCacheHit(self): | |
70 | """ | |
71 | TCP KeepAlive: Cache Hit | |
72 | """ | |
73 | name = 'cachehit.tcpka.tests.powerdns.com.' | |
74 | query = dns.message.make_query(name, 'A', 'IN') | |
75 | expectedResponse = dns.message.make_response(query) | |
76 | rrset = dns.rrset.from_text(name, | |
77 | 3600, | |
78 | dns.rdataclass.IN, | |
79 | dns.rdatatype.A, | |
80 | '192.0.2.1') | |
81 | expectedResponse.answer.append(rrset) | |
82 | ||
83 | # first query to fill the cache | |
84 | (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse) | |
85 | self.assertTrue(receivedQuery) | |
86 | self.assertTrue(receivedResponse) | |
87 | receivedQuery.id = query.id | |
4bfebc93 CH |
88 | self.assertEqual(query, receivedQuery) |
89 | self.assertEqual(receivedResponse, expectedResponse) | |
dcacff17 RG |
90 | |
91 | conn = self.openTCPConnection() | |
92 | ||
93 | count = 0 | |
b4f23783 | 94 | for idx in range(5): |
dcacff17 RG |
95 | try: |
96 | self.sendTCPQueryOverConnection(conn, query) | |
97 | response = self.recvTCPResponseOverConnection(conn) | |
98 | if response is None: | |
99 | break | |
4bfebc93 | 100 | self.assertEqual(expectedResponse, response) |
dcacff17 RG |
101 | count = count + 1 |
102 | except: | |
103 | pass | |
104 | ||
105 | conn.close() | |
106 | self.assertEqual(count, 5) | |
107 | ||
108 | def testTCPKaNoDownstreamServFail(self): | |
109 | """ | |
110 | TCP KeepAlive: No downstream ServFail | |
111 | ||
112 | The query is routed to a pool that has no server, | |
113 | and dnsdist is configured to send a ServFail when | |
114 | that happens. We should keep the TCP connection open. | |
115 | """ | |
116 | name = 'nodownstream-servfail.tcpka.tests.powerdns.com.' | |
117 | query = dns.message.make_query(name, 'A', 'IN') | |
118 | expectedResponse = dns.message.make_response(query) | |
119 | expectedResponse.set_rcode(dns.rcode.SERVFAIL) | |
120 | ||
121 | conn = self.openTCPConnection() | |
122 | ||
123 | count = 0 | |
b4f23783 | 124 | for idx in range(5): |
dcacff17 RG |
125 | try: |
126 | self.sendTCPQueryOverConnection(conn, query) | |
127 | response = self.recvTCPResponseOverConnection(conn) | |
128 | if response is None: | |
129 | break | |
4bfebc93 | 130 | self.assertEqual(expectedResponse, response) |
dcacff17 RG |
131 | count = count + 1 |
132 | except: | |
133 | pass | |
134 | ||
135 | conn.close() | |
136 | self.assertEqual(count, 5) | |
137 | ||
138 | def testTCPKaQRBitSet(self): | |
139 | """ | |
140 | TCP KeepAlive: QR bit set in question | |
141 | """ | |
142 | name = 'qrset.tcpka.tests.powerdns.com.' | |
143 | query = dns.message.make_query(name, 'A', 'IN') | |
144 | query.flags |= dns.flags.QR | |
145 | ||
146 | conn = self.openTCPConnection() | |
147 | ||
148 | count = 0 | |
b4f23783 | 149 | for idx in range(5): |
dcacff17 RG |
150 | try: |
151 | self.sendTCPQueryOverConnection(conn, query) | |
152 | response = self.recvTCPResponseOverConnection(conn) | |
153 | if response is None: | |
154 | break | |
155 | count = count + 1 | |
156 | except: | |
157 | pass | |
158 | ||
159 | conn.close() | |
160 | self.assertEqual(count, 0) | |
161 | ||
162 | def testTCPKaDrop(self): | |
163 | """ | |
164 | TCP KeepAlive: Drop | |
165 | """ | |
166 | name = 'dropped.tcpka.tests.powerdns.com.' | |
167 | query = dns.message.make_query(name, 'A', 'IN') | |
168 | query.flags |= dns.flags.QR | |
169 | ||
170 | conn = self.openTCPConnection() | |
171 | ||
172 | count = 0 | |
b4f23783 | 173 | for idx in range(5): |
dcacff17 RG |
174 | try: |
175 | self.sendTCPQueryOverConnection(conn, query) | |
176 | response = self.recvTCPResponseOverConnection(conn) | |
177 | if response is None: | |
178 | break | |
179 | count = count + 1 | |
180 | except: | |
181 | pass | |
182 | ||
183 | conn.close() | |
184 | self.assertEqual(count, 0) | |
185 | ||
186 | def testTCPKaDropResponse(self): | |
187 | """ | |
188 | TCP KeepAlive: Drop Response | |
189 | """ | |
190 | name = 'dropped-response.tcpka.tests.powerdns.com.' | |
191 | query = dns.message.make_query(name, 'A', 'IN') | |
192 | ||
193 | conn = self.openTCPConnection() | |
194 | ||
195 | count = 0 | |
b4f23783 | 196 | for idx in range(5): |
dcacff17 RG |
197 | try: |
198 | self.sendTCPQueryOverConnection(conn, query) | |
199 | response = self.recvTCPResponseOverConnection(conn) | |
200 | if response is None: | |
201 | break | |
202 | count = count + 1 | |
203 | except: | |
204 | pass | |
205 | ||
206 | conn.close() | |
207 | self.assertEqual(count, 0) | |
208 | ||
f0d9b6b7 RG |
209 | def testTCPKaLargeNumberOfConnections(self): |
210 | """ | |
211 | TCP KeepAlive: Large number of connections | |
212 | """ | |
213 | name = 'largernumberofconnections.tcpka.tests.powerdns.com.' | |
214 | query = dns.message.make_query(name, 'A', 'IN') | |
215 | expectedResponse = dns.message.make_response(query) | |
216 | #expectedResponse.set_rcode(dns.rcode.SERVFAIL) | |
217 | rrset = dns.rrset.from_text(name, | |
218 | 3600, | |
219 | dns.rdataclass.IN, | |
220 | dns.rdatatype.A, | |
221 | '192.0.2.1') | |
222 | expectedResponse.answer.append(rrset) | |
223 | ||
224 | # number of connections | |
225 | numConns = 50 | |
226 | # number of queries per connections | |
227 | numQueriesPerConn = 4 | |
228 | ||
229 | conns = [] | |
230 | start = time.time() | |
231 | for idx in range(numConns): | |
232 | conns.append(self.openTCPConnection()) | |
233 | ||
234 | count = 0 | |
235 | for idx in range(numConns * numQueriesPerConn): | |
236 | try: | |
237 | conn = conns[idx % numConns] | |
238 | self.sendTCPQueryOverConnection(conn, query, response=expectedResponse) | |
239 | response = self.recvTCPResponseOverConnection(conn) | |
240 | if response is None: | |
241 | break | |
4bfebc93 | 242 | self.assertEqual(expectedResponse, response) |
f0d9b6b7 RG |
243 | count = count + 1 |
244 | except: | |
245 | pass | |
246 | ||
247 | for con in conns: | |
248 | conn.close() | |
249 | ||
250 | self.assertEqual(count, numConns * numQueriesPerConn) | |
251 | ||
dcacff17 RG |
252 | class TestTCPKeepAliveNoDownstreamDrop(DNSDistTest): |
253 | """ | |
254 | This test makes sure that dnsdist drops the TCP connection | |
255 | if no downstream server is available and setServFailWhenNoServer() | |
256 | is not set. | |
257 | """ | |
258 | ||
259 | _tcpIdleTimeout = 20 | |
260 | _maxTCPQueriesPerConn = 99 | |
261 | _maxTCPConnsPerClient = 3 | |
262 | _maxTCPConnDuration = 99 | |
263 | _config_template = """ | |
264 | newServer{address="127.0.0.1:%s"} | |
265 | setTCPRecvTimeout(%s) | |
266 | setMaxTCPQueriesPerConnection(%s) | |
267 | setMaxTCPConnectionsPerClient(%s) | |
268 | setMaxTCPConnectionDuration(%s) | |
269 | -- create the pool named "nosuchpool" | |
270 | getPool("nosuchpool") | |
271 | addAction("nodownstream-drop.tcpka.tests.powerdns.com.", PoolAction("nosuchpool")) | |
272 | """ | |
273 | _config_params = ['_testServerPort', '_tcpIdleTimeout', '_maxTCPQueriesPerConn', '_maxTCPConnsPerClient', '_maxTCPConnDuration'] | |
274 | ||
275 | def testTCPKaNoDownstreamDrop(self): | |
276 | """ | |
277 | TCP KeepAlive: No downstream Drop | |
278 | ||
279 | The query is routed to a pool that has no server, | |
280 | and dnsdist is configured to drop the query when | |
281 | that happens. We should close the TCP connection right away. | |
282 | """ | |
283 | name = 'nodownstream-drop.tcpka.tests.powerdns.com.' | |
284 | query = dns.message.make_query(name, 'A', 'IN') | |
285 | ||
286 | conn = self.openTCPConnection() | |
287 | ||
288 | count = 0 | |
b4f23783 | 289 | for idx in range(5): |
dcacff17 RG |
290 | try: |
291 | self.sendTCPQueryOverConnection(conn, query) | |
292 | response = self.recvTCPResponseOverConnection(conn) | |
293 | if response is None: | |
294 | break | |
295 | count = count + 1 | |
296 | except: | |
297 | pass | |
298 | ||
299 | conn.close() | |
300 | self.assertEqual(count, 0) |