]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/test_DynBlocks.py
dnsdist: Clarify the Lua FFI DNS header set/get regression tests
[thirdparty/pdns.git] / regression-tests.dnsdist / test_DynBlocks.py
1 #!/usr/bin/env python
2 import base64
3 import socket
4 import time
5 import dns
6 from dnsdisttests import DNSDistTest
7 from dnsdistDynBlockTests import DynBlocksTest, waitForMaintenanceToRun, _maintenanceWaitTime
8
9 class TestDynBlockQPS(DynBlocksTest):
10
11 _config_template = """
12 function maintenance()
13 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
14 end
15 newServer{address="127.0.0.1:%s"}
16 webserver("127.0.0.1:%s")
17 setWebserverConfig({password="%s", apiKey="%s"})
18 """
19 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
20
21 def testDynBlocksQRate(self):
22 """
23 Dyn Blocks: QRate
24 """
25 name = 'qrate.dynblocks.tests.powerdns.com.'
26 self.doTestQRate(name)
27
28 class TestDynBlockQPSRefused(DynBlocksTest):
29
30 _config_template = """
31 function maintenance()
32 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
33 end
34 setDynBlocksAction(DNSAction.Refused)
35 newServer{address="127.0.0.1:%s"}
36 """
37
38 def testDynBlocksQRate(self):
39 """
40 Dyn Blocks: QRate refused
41 """
42 name = 'qraterefused.dynblocks.tests.powerdns.com.'
43 self.doTestQRateRCode(name, dns.rcode.REFUSED)
44
45 class TestDynBlockQPSActionRefused(DynBlocksTest):
46
47 _config_template = """
48 function maintenance()
49 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Refused)
50 end
51 setDynBlocksAction(DNSAction.Drop)
52 newServer{address="127.0.0.1:%s"}
53 """
54
55 def testDynBlocksQRate(self):
56 """
57 Dyn Blocks: QRate refused (action)
58 """
59 name = 'qrateactionrefused.dynblocks.tests.powerdns.com.'
60 self.doTestQRateRCode(name, dns.rcode.REFUSED)
61
62 class TestDynBlockQPSActionNXD(DynBlocksTest):
63
64 _config_template = """
65 function maintenance()
66 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Nxdomain)
67 end
68 setDynBlocksAction(DNSAction.Drop)
69 newServer{address="127.0.0.1:%s"}
70 """
71
72 def testDynBlocksQRate(self):
73 """
74 Dyn Blocks: QRate NXD (action)
75 """
76 name = 'qrateactionnxd.dynblocks.tests.powerdns.com.'
77 self.doTestQRateRCode(name, dns.rcode.NXDOMAIN)
78
79 class TestDynBlockQPSActionTruncated(DNSDistTest):
80
81 _dynBlockQPS = 10
82 _dynBlockPeriod = 2
83 # this needs to be greater than maintenanceWaitTime
84 _dynBlockDuration = _maintenanceWaitTime + 1
85 _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
86 _config_template = """
87 function maintenance()
88 addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Truncate)
89 end
90 setDynBlocksAction(DNSAction.Drop)
91 newServer{address="127.0.0.1:%s"}
92 """
93
94 def testDynBlocksQRate(self):
95 """
96 Dyn Blocks: QRate truncated (action)
97 """
98 name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
99 query = dns.message.make_query(name, 'A', 'IN')
100 # dnsdist sets RA = RD for TC responses
101 query.flags &= ~dns.flags.RD
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')
108 response.answer.append(rrset)
109 truncatedResponse = dns.message.make_response(query)
110 truncatedResponse.flags |= dns.flags.TC
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
119 self.assertEqual(query, receivedQuery)
120 self.assertEqual(receivedResponse, response)
121 allowed = allowed + 1
122 else:
123 self.assertEqual(receivedResponse, truncatedResponse)
124 # the query has not reached the responder,
125 # let's clear the response queue
126 self.clearToResponderQueue()
127
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)
131
132 if allowed == sent:
133 waitForMaintenanceToRun()
134
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)
138
139 # check over TCP, which should not be truncated
140 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
141
142 receivedQuery.id = query.id
143 self.assertEqual(query, receivedQuery)
144 self.assertEqual(receivedResponse, response)
145
146 # wait until we are not blocked anymore
147 time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
148
149 # this one should succeed
150 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
151 receivedQuery.id = query.id
152 self.assertEqual(query, receivedQuery)
153 self.assertEqual(response, receivedResponse)
154
155 allowed = 0
156 sent = 0
157 # again, over TCP this time, we should never get truncated!
158 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
159 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
160 sent = sent + 1
161 receivedQuery.id = query.id
162 self.assertEqual(query, receivedQuery)
163 self.assertEqual(receivedResponse, response)
164 receivedQuery.id = query.id
165 allowed = allowed + 1
166
167 self.assertEqual(allowed, sent)
168
169 class TestDynBlockAllowlist(DynBlocksTest):
170
171 _config_template = """
172 allowlisted = false
173 function maintenance()
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
191 end
192 addAction("allowlisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule))
193
194 newServer{address="127.0.0.1:%s"}
195 """
196
197 def testAllowlisted(self):
198 """
199 Dyn Blocks: Allowlisted from the dynamic blocks
200 """
201 name = 'allowlisted.dynblocks.tests.powerdns.com.'
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
213 for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
214 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
215 sent = sent + 1
216 if receivedQuery:
217 receivedQuery.id = query.id
218 self.assertEqual(query, receivedQuery)
219 self.assertEqual(response, receivedResponse)
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
226 # we should not have been blocked
227 self.assertEqual(allowed, sent)
228
229 waitForMaintenanceToRun()
230
231 # we should still not be blocked
232 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
233 receivedQuery.id = query.id
234 self.assertEqual(query, receivedQuery)
235 self.assertEqual(receivedResponse, receivedResponse)
236
237 # check that we would have been blocked without the allowlisting
238 name = 'allowlisted-test.dynblocks.tests.powerdns.com.'
239 query = dns.message.make_query(name, 'A', 'IN')
240 # dnsdist set RA = RD for spoofed responses
241 query.flags &= ~dns.flags.RD
242 expectedResponse = dns.message.make_response(query)
243 rrset = dns.rrset.from_text(name,
244 60,
245 dns.rdataclass.IN,
246 dns.rdatatype.A,
247 '192.0.2.42')
248 expectedResponse.answer.append(rrset)
249 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
250 self.assertEqual(receivedResponse, expectedResponse)