]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsdist-dynblocks.hh
Merge pull request #9134 from omoerbeek/secpoll-cleanup
[thirdparty/pdns.git] / pdns / dnsdist-dynblocks.hh
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22 #pragma once
23
24 #include <unordered_set>
25
26 #include "dolog.hh"
27 #include "dnsdist-rings.hh"
28 #include "statnode.hh"
29
30 #include "dnsdist-lua-inspection-ffi.hh"
31
32 // dnsdist_ffi_stat_node_t is a lightuserdata
33 template<>
34 struct LuaContext::Pusher<dnsdist_ffi_stat_node_t*> {
35 static const int minSize = 1;
36 static const int maxSize = 1;
37
38 static PushedObject push(lua_State* state, dnsdist_ffi_stat_node_t* ptr) noexcept {
39 lua_pushlightuserdata(state, ptr);
40 return PushedObject{state, 1};
41 }
42 };
43
44 typedef std::function<bool(dnsdist_ffi_stat_node_t*)> dnsdist_ffi_stat_node_visitor_t;
45
46 struct dnsdist_ffi_stat_node_t
47 {
48 dnsdist_ffi_stat_node_t(const StatNode& node_, const StatNode::Stat& self_, const StatNode::Stat& children_): node(node_), self(self_), children(children_)
49 {
50 }
51
52 const StatNode& node;
53 const StatNode::Stat& self;
54 const StatNode::Stat& children;
55 };
56
57 class DynBlockRulesGroup
58 {
59 private:
60
61 struct Counts
62 {
63 std::map<uint8_t, uint64_t> d_rcodeCounts;
64 std::map<uint16_t, uint64_t> d_qtypeCounts;
65 uint64_t queries{0};
66 uint64_t respBytes{0};
67 };
68
69 struct DynBlockRule
70 {
71 DynBlockRule(): d_enabled(false)
72 {
73 }
74
75 DynBlockRule(const std::string& blockReason, unsigned int blockDuration, unsigned int rate, unsigned int warningRate, unsigned int seconds, DNSAction::Action action): d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), d_warningRate(warningRate), d_seconds(seconds), d_action(action), d_enabled(true)
76 {
77 }
78
79 bool matches(const struct timespec& when)
80 {
81 if (!d_enabled) {
82 return false;
83 }
84
85 if (d_seconds && when < d_cutOff) {
86 return false;
87 }
88
89 if (when < d_minTime) {
90 d_minTime = when;
91 }
92
93 return true;
94 }
95
96 bool rateExceeded(unsigned int count, const struct timespec& now) const
97 {
98 if (!d_enabled) {
99 return false;
100 }
101
102 double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime);
103 double limit = delta * d_rate;
104 return (count > limit);
105 }
106
107 bool warningRateExceeded(unsigned int count, const struct timespec& now) const
108 {
109 if (d_warningRate == 0) {
110 return false;
111 }
112
113 double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime);
114 double limit = delta * d_warningRate;
115 return (count > limit);
116 }
117
118 bool isEnabled() const
119 {
120 return d_enabled;
121 }
122
123 std::string toString() const
124 {
125 if (!isEnabled()) {
126 return "";
127 }
128
129 std::stringstream result;
130 if (d_action != DNSAction::Action::None) {
131 result << DNSAction::typeToString(d_action) << " ";
132 }
133 else {
134 result << "Apply the global DynBlock action ";
135 }
136 result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_rate) << " during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
137
138 return result.str();
139 }
140
141 std::string d_blockReason;
142 struct timespec d_cutOff;
143 struct timespec d_minTime;
144 unsigned int d_blockDuration{0};
145 unsigned int d_rate{0};
146 unsigned int d_warningRate{0};
147 unsigned int d_seconds{0};
148 DNSAction::Action d_action{DNSAction::Action::None};
149 bool d_enabled{false};
150 };
151
152 struct DynBlockRatioRule: DynBlockRule
153 {
154 DynBlockRatioRule(): DynBlockRule()
155 {
156 }
157
158 DynBlockRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses): DynBlockRule(blockReason, blockDuration, 0, 0, seconds, action), d_minimumNumberOfResponses(minimumNumberOfResponses), d_ratio(ratio), d_warningRatio(warningRatio)
159 {
160 }
161
162 bool ratioExceeded(unsigned int total, unsigned int count) const
163 {
164 if (!d_enabled) {
165 return false;
166 }
167
168 if (total < d_minimumNumberOfResponses) {
169 return false;
170 }
171
172 double allowed = d_ratio * static_cast<double>(total);
173 return (count > allowed);
174 }
175
176 bool warningRatioExceeded(unsigned int total, unsigned int count) const
177 {
178 if (d_warningRate == 0) {
179 return false;
180 }
181
182 if (total < d_minimumNumberOfResponses) {
183 return false;
184 }
185
186 double allowed = d_warningRatio * static_cast<double>(total);
187 return (count > allowed);
188 }
189
190 std::string toString() const
191 {
192 if (!isEnabled()) {
193 return "";
194 }
195
196 std::stringstream result;
197 if (d_action != DNSAction::Action::None) {
198 result << DNSAction::typeToString(d_action) << " ";
199 }
200 else {
201 result << "Apply the global DynBlock action ";
202 }
203 result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_ratio) << " ratio during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
204
205 return result.str();
206 }
207
208 size_t d_minimumNumberOfResponses{0};
209 double d_ratio{0.0};
210 double d_warningRatio{0.0};
211 };
212
213 typedef std::unordered_map<ComboAddress, Counts, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
214
215 public:
216 DynBlockRulesGroup()
217 {
218 }
219
220 void setQueryRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
221 {
222 d_queryRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
223 }
224
225 /* rate is in bytes per second */
226 void setResponseByteRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
227 {
228 d_respRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
229 }
230
231 void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
232 {
233 auto& entry = d_rcodeRules[rcode];
234 entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
235 }
236
237 void setRCodeRatio(uint8_t rcode, double ratio, double warningRatio, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses)
238 {
239 auto& entry = d_rcodeRatioRules[rcode];
240 entry = DynBlockRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses);
241 }
242
243 void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
244 {
245 auto& entry = d_qtypeRules[qtype];
246 entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
247 }
248
249 typedef std::function<bool(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)> smtVisitor_t;
250
251 void setSuffixMatchRule(unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, smtVisitor_t visitor)
252 {
253 d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
254 d_smtVisitor = visitor;
255 }
256
257 void setSuffixMatchRuleFFI(unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, dnsdist_ffi_stat_node_visitor_t visitor)
258 {
259 d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
260 d_smtVisitorFFI = visitor;
261 }
262
263 void apply()
264 {
265 struct timespec now;
266 gettime(&now);
267
268 apply(now);
269 }
270
271 void apply(const struct timespec& now);
272
273 void excludeRange(const Netmask& range)
274 {
275 d_excludedSubnets.addMask(range);
276 }
277
278 void includeRange(const Netmask& range)
279 {
280 d_excludedSubnets.addMask(range, false);
281 }
282
283 void excludeDomain(const DNSName& domain)
284 {
285 d_excludedDomains.add(domain);
286 }
287
288 std::string toString() const
289 {
290 std::stringstream result;
291
292 result << "Query rate rule: " << d_queryRateRule.toString() << std::endl;
293 result << "Response rate rule: " << d_respRateRule.toString() << std::endl;
294 result << "SuffixMatch rule: " << d_suffixMatchRule.toString() << std::endl;
295 result << "RCode rules: " << std::endl;
296 for (const auto& rule : d_rcodeRules) {
297 result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
298 }
299 for (const auto& rule : d_rcodeRatioRules) {
300 result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
301 }
302 result << "QType rules: " << std::endl;
303 for (const auto& rule : d_qtypeRules) {
304 result << "- " << QType(rule.first).getName() << ": " << rule.second.toString() << std::endl;
305 }
306 result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl;
307 result << "Excluded Domains: " << d_excludedDomains.toString() << std::endl;
308
309 return result.str();
310 }
311
312 void setQuiet(bool quiet)
313 {
314 d_beQuiet = quiet;
315 }
316
317 private:
318
319 bool checkIfQueryTypeMatches(const Rings::Query& query);
320 bool checkIfResponseCodeMatches(const Rings::Response& response);
321 void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning);
322 void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated);
323
324 void addBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
325 {
326 addOrRefreshBlock(blocks, now, requestor, rule, updated, false);
327 }
328
329 void handleWarning(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
330 {
331 addOrRefreshBlock(blocks, now, requestor, rule, updated, true);
332 }
333
334 bool hasQueryRules() const
335 {
336 return d_queryRateRule.isEnabled() || !d_qtypeRules.empty();
337 }
338
339 bool hasResponseRules() const
340 {
341 return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty();
342 }
343
344 bool hasSuffixMatchRules() const
345 {
346 return d_suffixMatchRule.isEnabled();
347 }
348
349 bool hasRules() const
350 {
351 return hasQueryRules() || hasResponseRules();
352 }
353
354 void processQueryRules(counts_t& counts, const struct timespec& now);
355 void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now);
356
357 std::map<uint8_t, DynBlockRule> d_rcodeRules;
358 std::map<uint8_t, DynBlockRatioRule> d_rcodeRatioRules;
359 std::map<uint16_t, DynBlockRule> d_qtypeRules;
360 DynBlockRule d_queryRateRule;
361 DynBlockRule d_respRateRule;
362 DynBlockRule d_suffixMatchRule;
363 NetmaskGroup d_excludedSubnets;
364 SuffixMatchNode d_excludedDomains;
365 smtVisitor_t d_smtVisitor;
366 dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI;
367 bool d_beQuiet{false};
368 };