]>
Commit | Line | Data |
---|---|---|
d354e773 | 1 | #!/usr/bin/env python |
87b0577d | 2 | import base64 |
aa306c70 | 3 | import socket |
d354e773 RG |
4 | import time |
5 | import dns | |
8c87daac RG |
6 | from dnsdisttests import DNSDistTest |
7 | from dnsdistDynBlockTests import DynBlocksTest, waitForMaintenanceToRun, _maintenanceWaitTime | |
d354e773 | 8 | |
8c87daac | 9 | class TestDynBlockQPS(DynBlocksTest): |
dc2fd93a | 10 | |
dc2fd93a | 11 | _config_template = """ |
dc2fd93a | 12 | function maintenance() |
8c87daac | 13 | addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d) |
dc2fd93a | 14 | end |
dc2fd93a | 15 | newServer{address="127.0.0.1:%s"} |
8c87daac RG |
16 | webserver("127.0.0.1:%s") |
17 | setWebserverConfig({password="%s", apiKey="%s"}) | |
dc2fd93a | 18 | """ |
8c87daac | 19 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] |
dc2fd93a | 20 | |
8c87daac | 21 | def testDynBlocksQRate(self): |
dc2fd93a | 22 | """ |
8c87daac | 23 | Dyn Blocks: QRate |
dc2fd93a | 24 | """ |
8c87daac RG |
25 | name = 'qrate.dynblocks.tests.powerdns.com.' |
26 | self.doTestQRate(name) | |
dc2fd93a | 27 | |
8c87daac | 28 | class TestDynBlockQPSRefused(DynBlocksTest): |
94c38f24 | 29 | |
94c38f24 | 30 | _config_template = """ |
94c38f24 | 31 | function maintenance() |
8c87daac | 32 | addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d) |
94c38f24 | 33 | end |
8c87daac | 34 | setDynBlocksAction(DNSAction.Refused) |
94c38f24 RG |
35 | newServer{address="127.0.0.1:%s"} |
36 | """ | |
37 | ||
8c87daac | 38 | def testDynBlocksQRate(self): |
94c38f24 | 39 | """ |
8c87daac | 40 | Dyn Blocks: QRate refused |
94c38f24 | 41 | """ |
8c87daac RG |
42 | name = 'qraterefused.dynblocks.tests.powerdns.com.' |
43 | self.doTestQRateRCode(name, dns.rcode.REFUSED) | |
94c38f24 | 44 | |
8c87daac | 45 | class TestDynBlockQPSActionRefused(DynBlocksTest): |
dc2fd93a | 46 | |
dc2fd93a | 47 | _config_template = """ |
dc2fd93a | 48 | function maintenance() |
8c87daac | 49 | addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Refused) |
dc2fd93a | 50 | end |
8c87daac | 51 | setDynBlocksAction(DNSAction.Drop) |
dc2fd93a RG |
52 | newServer{address="127.0.0.1:%s"} |
53 | """ | |
54 | ||
8c87daac | 55 | def testDynBlocksQRate(self): |
dc2fd93a | 56 | """ |
8c87daac | 57 | Dyn Blocks: QRate refused (action) |
dc2fd93a | 58 | """ |
8c87daac RG |
59 | name = 'qrateactionrefused.dynblocks.tests.powerdns.com.' |
60 | self.doTestQRateRCode(name, dns.rcode.REFUSED) | |
dc2fd93a | 61 | |
8c87daac | 62 | class TestDynBlockQPSActionNXD(DynBlocksTest): |
dc2fd93a | 63 | |
dc2fd93a | 64 | _config_template = """ |
dc2fd93a | 65 | function maintenance() |
8c87daac | 66 | addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Nxdomain) |
dc2fd93a | 67 | end |
8c87daac | 68 | setDynBlocksAction(DNSAction.Drop) |
dc2fd93a RG |
69 | newServer{address="127.0.0.1:%s"} |
70 | """ | |
71 | ||
8c87daac | 72 | def testDynBlocksQRate(self): |
dc2fd93a | 73 | """ |
8c87daac | 74 | Dyn Blocks: QRate NXD (action) |
dc2fd93a | 75 | """ |
8c87daac RG |
76 | name = 'qrateactionnxd.dynblocks.tests.powerdns.com.' |
77 | self.doTestQRateRCode(name, dns.rcode.NXDOMAIN) | |
b718792f | 78 | |
8c87daac | 79 | class TestDynBlockQPSActionTruncated(DNSDistTest): |
b718792f RG |
80 | |
81 | _dynBlockQPS = 10 | |
82 | _dynBlockPeriod = 2 | |
8c87daac RG |
83 | # this needs to be greater than maintenanceWaitTime |
84 | _dynBlockDuration = _maintenanceWaitTime + 1 | |
477c86a0 | 85 | _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] |
b718792f | 86 | _config_template = """ |
b718792f | 87 | function maintenance() |
8c87daac | 88 | addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Truncate) |
b718792f | 89 | end |
8c87daac | 90 | setDynBlocksAction(DNSAction.Drop) |
b718792f RG |
91 | newServer{address="127.0.0.1:%s"} |
92 | """ | |
93 | ||
8c87daac | 94 | def testDynBlocksQRate(self): |
b718792f | 95 | """ |
8c87daac | 96 | Dyn Blocks: QRate truncated (action) |
b718792f | 97 | """ |
8c87daac | 98 | name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.' |
b718792f | 99 | query = dns.message.make_query(name, 'A', 'IN') |
8c87daac RG |
100 | # dnsdist sets RA = RD for TC responses |
101 | query.flags &= ~dns.flags.RD | |
b718792f RG |
102 | response = dns.message.make_response(query) |
103 | rrset = dns.rrset.from_text(name, | |
104 | 60, | |
105 | dns.rdataclass.IN, | |
106 | dns.rdatatype.A, | |
107 | '192.0.2.1') | |
5a2f3287 | 108 | response.answer.append(rrset) |
8c87daac RG |
109 | truncatedResponse = dns.message.make_response(query) |
110 | truncatedResponse.flags |= dns.flags.TC | |
5a2f3287 RG |
111 | |
112 | allowed = 0 | |
113 | sent = 0 | |
114 | for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1): | |
115 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) | |
116 | sent = sent + 1 | |
117 | if receivedQuery: | |
118 | receivedQuery.id = query.id | |
4bfebc93 | 119 | self.assertEqual(query, receivedQuery) |
8c87daac | 120 | self.assertEqual(receivedResponse, response) |
5a2f3287 RG |
121 | allowed = allowed + 1 |
122 | else: | |
8c87daac | 123 | self.assertEqual(receivedResponse, truncatedResponse) |
5a2f3287 RG |
124 | # the query has not reached the responder, |
125 | # let's clear the response queue | |
126 | self.clearToResponderQueue() | |
127 | ||
8c87daac RG |
128 | # we might be already truncated, but we should have been able to send |
129 | # at least self._dynBlockQPS queries | |
130 | self.assertGreaterEqual(allowed, self._dynBlockQPS) | |
5a2f3287 | 131 | |
8c87daac RG |
132 | if allowed == sent: |
133 | waitForMaintenanceToRun() | |
5a2f3287 | 134 | |
8c87daac RG |
135 | # we should now be 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod |
136 | (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) | |
137 | self.assertEqual(receivedResponse, truncatedResponse) | |
b718792f | 138 | |
8c87daac RG |
139 | # check over TCP, which should not be truncated |
140 | (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) | |
b718792f | 141 | |
8c87daac RG |
142 | receivedQuery.id = query.id |
143 | self.assertEqual(query, receivedQuery) | |
144 | self.assertEqual(receivedResponse, response) | |
b718792f | 145 | |
8c87daac RG |
146 | # wait until we are not blocked anymore |
147 | time.sleep(self._dynBlockDuration + self._dynBlockPeriod) | |
b718792f | 148 | |
8c87daac | 149 | # this one should succeed |
b718792f RG |
150 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) |
151 | receivedQuery.id = query.id | |
4bfebc93 | 152 | self.assertEqual(query, receivedQuery) |
8c87daac | 153 | self.assertEqual(response, receivedResponse) |
477c86a0 RG |
154 | |
155 | allowed = 0 | |
156 | sent = 0 | |
8c87daac | 157 | # again, over TCP this time, we should never get truncated! |
477c86a0 | 158 | for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1): |
8c87daac | 159 | (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) |
477c86a0 | 160 | sent = sent + 1 |
8c87daac RG |
161 | receivedQuery.id = query.id |
162 | self.assertEqual(query, receivedQuery) | |
163 | self.assertEqual(receivedResponse, response) | |
164 | receivedQuery.id = query.id | |
165 | allowed = allowed + 1 | |
477c86a0 | 166 | |
477c86a0 RG |
167 | self.assertEqual(allowed, sent) |
168 | ||
8c87daac | 169 | class TestDynBlockAllowlist(DynBlocksTest): |
1d3ba133 | 170 | |
1d3ba133 | 171 | _config_template = """ |
8c87daac | 172 | allowlisted = false |
1d3ba133 | 173 | function maintenance() |
8c87daac RG |
174 | toBlock = exceedQRate(%d, %d) |
175 | for addr, count in pairs(toBlock) do | |
176 | if tostring(addr) == "127.0.0.1" then | |
177 | allowlisted = true | |
178 | toBlock[addr] = nil | |
179 | end | |
180 | end | |
181 | addDynBlocks(toBlock, "Exceeded query rate", %d) | |
182 | end | |
183 | ||
184 | function spoofrule(dq) | |
185 | if (allowlisted) | |
186 | then | |
187 | return DNSAction.Spoof, "192.0.2.42" | |
188 | else | |
189 | return DNSAction.None, "" | |
190 | end | |
1d3ba133 | 191 | end |
8c87daac | 192 | addAction("allowlisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule)) |
1d3ba133 RG |
193 | |
194 | newServer{address="127.0.0.1:%s"} | |
1d3ba133 RG |
195 | """ |
196 | ||
8c87daac | 197 | def testAllowlisted(self): |
1d3ba133 | 198 | """ |
8c87daac | 199 | Dyn Blocks: Allowlisted from the dynamic blocks |
1d3ba133 | 200 | """ |
8c87daac | 201 | name = 'allowlisted.dynblocks.tests.powerdns.com.' |
1d3ba133 RG |
202 | query = dns.message.make_query(name, 'A', 'IN') |
203 | response = dns.message.make_response(query) | |
204 | rrset = dns.rrset.from_text(name, | |
205 | 60, | |
206 | dns.rdataclass.IN, | |
207 | dns.rdatatype.A, | |
208 | '192.0.2.1') | |
209 | response.answer.append(rrset) | |
210 | ||
211 | allowed = 0 | |
212 | sent = 0 | |
8c87daac | 213 | for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1): |
1d3ba133 RG |
214 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) |
215 | sent = sent + 1 | |
216 | if receivedQuery: | |
217 | receivedQuery.id = query.id | |
4bfebc93 CH |
218 | self.assertEqual(query, receivedQuery) |
219 | self.assertEqual(response, receivedResponse) | |
1d3ba133 RG |
220 | allowed = allowed + 1 |
221 | else: | |
222 | # the query has not reached the responder, | |
223 | # let's clear the response queue | |
224 | self.clearToResponderQueue() | |
225 | ||
8c87daac | 226 | # we should not have been blocked |
1d3ba133 RG |
227 | self.assertEqual(allowed, sent) |
228 | ||
8c87daac | 229 | waitForMaintenanceToRun() |
1d3ba133 | 230 | |
8c87daac | 231 | # we should still not be blocked |
1d3ba133 RG |
232 | (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) |
233 | receivedQuery.id = query.id | |
4bfebc93 CH |
234 | self.assertEqual(query, receivedQuery) |
235 | self.assertEqual(receivedResponse, receivedResponse) | |
1d3ba133 | 236 | |
8c87daac RG |
237 | # check that we would have been blocked without the allowlisting |
238 | name = 'allowlisted-test.dynblocks.tests.powerdns.com.' | |
aa306c70 | 239 | query = dns.message.make_query(name, 'A', 'IN') |
8c87daac RG |
240 | # dnsdist set RA = RD for spoofed responses |
241 | query.flags &= ~dns.flags.RD | |
242 | expectedResponse = dns.message.make_response(query) | |
aa306c70 RG |
243 | rrset = dns.rrset.from_text(name, |
244 | 60, | |
245 | dns.rdataclass.IN, | |
246 | dns.rdatatype.A, | |
8c87daac RG |
247 | '192.0.2.42') |
248 | expectedResponse.answer.append(rrset) | |
aa306c70 | 249 | (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) |
8c87daac | 250 | self.assertEqual(receivedResponse, expectedResponse) |