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