]>
Commit | Line | Data |
---|---|---|
dc2fd93a RG |
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 | ||
3dec2251 | 24 | #ifndef DISABLE_DYNBLOCKS |
23adffab RG |
25 | #include <unordered_set> |
26 | ||
dc2fd93a | 27 | #include "dolog.hh" |
03b00917 | 28 | #include "dnsdist-rings.hh" |
23adffab | 29 | #include "statnode.hh" |
dc2fd93a | 30 | |
b4fbe20d RG |
31 | extern "C" { |
32 | #include "dnsdist-lua-inspection-ffi.h" | |
33 | } | |
861ce85b RG |
34 | |
35 | // dnsdist_ffi_stat_node_t is a lightuserdata | |
36 | template<> | |
37 | struct LuaContext::Pusher<dnsdist_ffi_stat_node_t*> { | |
38 | static const int minSize = 1; | |
39 | static const int maxSize = 1; | |
40 | ||
41 | static PushedObject push(lua_State* state, dnsdist_ffi_stat_node_t* ptr) noexcept { | |
42 | lua_pushlightuserdata(state, ptr); | |
43 | return PushedObject{state, 1}; | |
44 | } | |
45 | }; | |
46 | ||
47 | typedef std::function<bool(dnsdist_ffi_stat_node_t*)> dnsdist_ffi_stat_node_visitor_t; | |
48 | ||
49 | struct dnsdist_ffi_stat_node_t | |
50 | { | |
7179dab0 | 51 | dnsdist_ffi_stat_node_t(const StatNode& node_, const StatNode::Stat& self_, const StatNode::Stat& children_, std::optional<std::string>& reason_): node(node_), self(self_), children(children_), reason(reason_) |
861ce85b RG |
52 | { |
53 | } | |
54 | ||
55 | const StatNode& node; | |
56 | const StatNode::Stat& self; | |
57 | const StatNode::Stat& children; | |
7179dab0 | 58 | std::optional<std::string>& reason; |
861ce85b RG |
59 | }; |
60 | ||
dc2fd93a RG |
61 | class DynBlockRulesGroup |
62 | { | |
63 | private: | |
64 | ||
65 | struct Counts | |
66 | { | |
67 | std::map<uint8_t, uint64_t> d_rcodeCounts; | |
68 | std::map<uint16_t, uint64_t> d_qtypeCounts; | |
69 | uint64_t queries{0}; | |
3814053e | 70 | uint64_t responses{0}; |
dc2fd93a RG |
71 | uint64_t respBytes{0}; |
72 | }; | |
73 | ||
74 | struct DynBlockRule | |
75 | { | |
76 | DynBlockRule(): d_enabled(false) | |
77 | { | |
78 | } | |
79 | ||
1d3ba133 | 80 | 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) |
dc2fd93a RG |
81 | { |
82 | } | |
83 | ||
84 | bool matches(const struct timespec& when) | |
85 | { | |
86 | if (!d_enabled) { | |
87 | return false; | |
88 | } | |
89 | ||
90 | if (d_seconds && when < d_cutOff) { | |
91 | return false; | |
92 | } | |
93 | ||
94 | if (when < d_minTime) { | |
95 | d_minTime = when; | |
96 | } | |
97 | ||
98 | return true; | |
99 | } | |
100 | ||
101 | bool rateExceeded(unsigned int count, const struct timespec& now) const | |
102 | { | |
103 | if (!d_enabled) { | |
104 | return false; | |
105 | } | |
106 | ||
107 | double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime); | |
108 | double limit = delta * d_rate; | |
109 | return (count > limit); | |
110 | } | |
111 | ||
1d3ba133 RG |
112 | bool warningRateExceeded(unsigned int count, const struct timespec& now) const |
113 | { | |
b1fd5841 DF |
114 | if (!d_enabled) { |
115 | return false; | |
116 | } | |
117 | ||
15e60f9f | 118 | if (d_warningRate == 0) { |
1d3ba133 RG |
119 | return false; |
120 | } | |
121 | ||
122 | double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime); | |
123 | double limit = delta * d_warningRate; | |
124 | return (count > limit); | |
125 | } | |
126 | ||
dc2fd93a RG |
127 | bool isEnabled() const |
128 | { | |
15e60f9f | 129 | return d_enabled; |
dc2fd93a RG |
130 | } |
131 | ||
b718792f RG |
132 | std::string toString() const |
133 | { | |
134 | if (!isEnabled()) { | |
135 | return ""; | |
136 | } | |
137 | ||
138 | std::stringstream result; | |
139 | if (d_action != DNSAction::Action::None) { | |
140 | result << DNSAction::typeToString(d_action) << " "; | |
141 | } | |
142 | else { | |
143 | result << "Apply the global DynBlock action "; | |
144 | } | |
145 | result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_rate) << " during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'"; | |
146 | ||
147 | return result.str(); | |
148 | } | |
149 | ||
dc2fd93a RG |
150 | std::string d_blockReason; |
151 | struct timespec d_cutOff; | |
152 | struct timespec d_minTime; | |
153 | unsigned int d_blockDuration{0}; | |
154 | unsigned int d_rate{0}; | |
1d3ba133 | 155 | unsigned int d_warningRate{0}; |
dc2fd93a RG |
156 | unsigned int d_seconds{0}; |
157 | DNSAction::Action d_action{DNSAction::Action::None}; | |
158 | bool d_enabled{false}; | |
159 | }; | |
160 | ||
838c2f00 RG |
161 | struct DynBlockRatioRule: DynBlockRule |
162 | { | |
163 | DynBlockRatioRule(): DynBlockRule() | |
164 | { | |
165 | } | |
166 | ||
167 | 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) | |
168 | { | |
169 | } | |
170 | ||
171 | bool ratioExceeded(unsigned int total, unsigned int count) const | |
172 | { | |
173 | if (!d_enabled) { | |
174 | return false; | |
175 | } | |
176 | ||
177 | if (total < d_minimumNumberOfResponses) { | |
178 | return false; | |
179 | } | |
180 | ||
181 | double allowed = d_ratio * static_cast<double>(total); | |
182 | return (count > allowed); | |
183 | } | |
184 | ||
185 | bool warningRatioExceeded(unsigned int total, unsigned int count) const | |
186 | { | |
b1fd5841 DF |
187 | if (!d_enabled) { |
188 | return false; | |
189 | } | |
190 | ||
191 | if (d_warningRatio == 0.0) { | |
838c2f00 RG |
192 | return false; |
193 | } | |
194 | ||
195 | if (total < d_minimumNumberOfResponses) { | |
196 | return false; | |
197 | } | |
198 | ||
199 | double allowed = d_warningRatio * static_cast<double>(total); | |
200 | return (count > allowed); | |
201 | } | |
202 | ||
203 | std::string toString() const | |
204 | { | |
205 | if (!isEnabled()) { | |
206 | return ""; | |
207 | } | |
208 | ||
209 | std::stringstream result; | |
210 | if (d_action != DNSAction::Action::None) { | |
211 | result << DNSAction::typeToString(d_action) << " "; | |
212 | } | |
213 | else { | |
214 | result << "Apply the global DynBlock action "; | |
215 | } | |
216 | 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 << "'"; | |
217 | ||
218 | return result.str(); | |
219 | } | |
220 | ||
221 | size_t d_minimumNumberOfResponses{0}; | |
222 | double d_ratio{0.0}; | |
223 | double d_warningRatio{0.0}; | |
224 | }; | |
225 | ||
c173228a | 226 | typedef std::unordered_map<AddressAndPortRange, Counts, AddressAndPortRange::hash> counts_t; |
dc2fd93a RG |
227 | |
228 | public: | |
229 | DynBlockRulesGroup() | |
230 | { | |
231 | } | |
232 | ||
26512612 | 233 | void setQueryRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action) |
dc2fd93a | 234 | { |
1d3ba133 | 235 | d_queryRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action); |
dc2fd93a RG |
236 | } |
237 | ||
1d3ba133 | 238 | /* rate is in bytes per second */ |
26512612 | 239 | void setResponseByteRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action) |
dc2fd93a | 240 | { |
1d3ba133 | 241 | d_respRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action); |
dc2fd93a RG |
242 | } |
243 | ||
26512612 | 244 | void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action) |
dc2fd93a RG |
245 | { |
246 | auto& entry = d_rcodeRules[rcode]; | |
1d3ba133 | 247 | entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action); |
dc2fd93a RG |
248 | } |
249 | ||
26512612 | 250 | void setRCodeRatio(uint8_t rcode, double ratio, double warningRatio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses) |
838c2f00 RG |
251 | { |
252 | auto& entry = d_rcodeRatioRules[rcode]; | |
253 | entry = DynBlockRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses); | |
254 | } | |
255 | ||
26512612 | 256 | void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action) |
dc2fd93a RG |
257 | { |
258 | auto& entry = d_qtypeRules[qtype]; | |
1d3ba133 | 259 | entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action); |
dc2fd93a RG |
260 | } |
261 | ||
7179dab0 | 262 | typedef std::function<std::tuple<bool, boost::optional<std::string>>(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)> smtVisitor_t; |
23adffab | 263 | |
26512612 | 264 | void setSuffixMatchRule(unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, smtVisitor_t visitor) |
23adffab RG |
265 | { |
266 | d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action); | |
c92e6020 | 267 | d_smtVisitor = std::move(visitor); |
23adffab RG |
268 | } |
269 | ||
26512612 | 270 | void setSuffixMatchRuleFFI(unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, dnsdist_ffi_stat_node_visitor_t visitor) |
861ce85b RG |
271 | { |
272 | d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action); | |
c92e6020 | 273 | d_smtVisitorFFI = std::move(visitor); |
861ce85b RG |
274 | } |
275 | ||
c173228a | 276 | void setMasks(uint8_t v4, uint8_t v6, uint8_t port) |
6d4de128 RG |
277 | { |
278 | d_v4Mask = v4; | |
279 | d_v6Mask = v6; | |
c173228a | 280 | d_portMask = port; |
6d4de128 RG |
281 | } |
282 | ||
dc2fd93a | 283 | void apply() |
1d3ba133 RG |
284 | { |
285 | struct timespec now; | |
286 | gettime(&now); | |
287 | ||
288 | apply(now); | |
289 | } | |
290 | ||
838c2f00 | 291 | void apply(const struct timespec& now); |
dc2fd93a | 292 | |
b718792f RG |
293 | void excludeRange(const Netmask& range) |
294 | { | |
295 | d_excludedSubnets.addMask(range); | |
296 | } | |
297 | ||
5a2f3287 RG |
298 | void excludeRange(const NetmaskGroup& group) |
299 | { | |
300 | d_excludedSubnets.addMasks(group, true); | |
301 | } | |
302 | ||
b718792f RG |
303 | void includeRange(const Netmask& range) |
304 | { | |
305 | d_excludedSubnets.addMask(range, false); | |
306 | } | |
307 | ||
5a2f3287 RG |
308 | void includeRange(const NetmaskGroup& group) |
309 | { | |
310 | d_excludedSubnets.addMasks(group, false); | |
311 | } | |
312 | ||
23adffab RG |
313 | void excludeDomain(const DNSName& domain) |
314 | { | |
315 | d_excludedDomains.add(domain); | |
316 | } | |
317 | ||
b718792f RG |
318 | std::string toString() const |
319 | { | |
320 | std::stringstream result; | |
321 | ||
322 | result << "Query rate rule: " << d_queryRateRule.toString() << std::endl; | |
323 | result << "Response rate rule: " << d_respRateRule.toString() << std::endl; | |
23adffab | 324 | result << "SuffixMatch rule: " << d_suffixMatchRule.toString() << std::endl; |
b718792f RG |
325 | result << "RCode rules: " << std::endl; |
326 | for (const auto& rule : d_rcodeRules) { | |
327 | result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl; | |
328 | } | |
838c2f00 RG |
329 | for (const auto& rule : d_rcodeRatioRules) { |
330 | result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl; | |
331 | } | |
b718792f RG |
332 | result << "QType rules: " << std::endl; |
333 | for (const auto& rule : d_qtypeRules) { | |
d5fcd583 | 334 | result << "- " << QType(rule.first).toString() << ": " << rule.second.toString() << std::endl; |
b718792f RG |
335 | } |
336 | result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl; | |
23adffab | 337 | result << "Excluded Domains: " << d_excludedDomains.toString() << std::endl; |
b718792f RG |
338 | |
339 | return result.str(); | |
340 | } | |
341 | ||
1d3ba133 RG |
342 | void setQuiet(bool quiet) |
343 | { | |
344 | d_beQuiet = quiet; | |
345 | } | |
346 | ||
dc2fd93a | 347 | private: |
dc2fd93a | 348 | |
838c2f00 RG |
349 | bool checkIfQueryTypeMatches(const Rings::Query& query); |
350 | bool checkIfResponseCodeMatches(const Rings::Response& response); | |
c173228a | 351 | void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning); |
838c2f00 | 352 | void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated); |
23adffab | 353 | |
c173228a | 354 | void addBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated) |
1d3ba133 RG |
355 | { |
356 | addOrRefreshBlock(blocks, now, requestor, rule, updated, false); | |
357 | } | |
358 | ||
c173228a | 359 | void handleWarning(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated) |
1d3ba133 RG |
360 | { |
361 | addOrRefreshBlock(blocks, now, requestor, rule, updated, true); | |
362 | } | |
363 | ||
dc2fd93a RG |
364 | bool hasQueryRules() const |
365 | { | |
366 | return d_queryRateRule.isEnabled() || !d_qtypeRules.empty(); | |
367 | } | |
368 | ||
369 | bool hasResponseRules() const | |
370 | { | |
838c2f00 | 371 | return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty(); |
dc2fd93a RG |
372 | } |
373 | ||
23adffab RG |
374 | bool hasSuffixMatchRules() const |
375 | { | |
376 | return d_suffixMatchRule.isEnabled(); | |
377 | } | |
378 | ||
dc2fd93a RG |
379 | bool hasRules() const |
380 | { | |
381 | return hasQueryRules() || hasResponseRules(); | |
382 | } | |
383 | ||
838c2f00 RG |
384 | void processQueryRules(counts_t& counts, const struct timespec& now); |
385 | void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now); | |
dc2fd93a RG |
386 | |
387 | std::map<uint8_t, DynBlockRule> d_rcodeRules; | |
838c2f00 | 388 | std::map<uint8_t, DynBlockRatioRule> d_rcodeRatioRules; |
dc2fd93a RG |
389 | std::map<uint16_t, DynBlockRule> d_qtypeRules; |
390 | DynBlockRule d_queryRateRule; | |
391 | DynBlockRule d_respRateRule; | |
23adffab | 392 | DynBlockRule d_suffixMatchRule; |
b718792f | 393 | NetmaskGroup d_excludedSubnets; |
23adffab RG |
394 | SuffixMatchNode d_excludedDomains; |
395 | smtVisitor_t d_smtVisitor; | |
861ce85b | 396 | dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI; |
6d4de128 RG |
397 | uint8_t d_v6Mask{128}; |
398 | uint8_t d_v4Mask{32}; | |
c173228a | 399 | uint8_t d_portMask{0}; |
1d3ba133 | 400 | bool d_beQuiet{false}; |
dc2fd93a | 401 | }; |
a4244240 | 402 | |
59b37d25 | 403 | class DynBlockMaintenance |
a4244240 RG |
404 | { |
405 | public: | |
59b37d25 | 406 | static void run(); |
a4244240 | 407 | |
59b37d25 | 408 | /* return the (cached) number of hits per second for the top offenders, averaged over 60s */ |
c173228a | 409 | static std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> getHitsForTopNetmasks(); |
59b37d25 RG |
410 | static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getHitsForTopSuffixes(); |
411 | ||
412 | /* get the the top offenders based on the current value of the counters */ | |
c173228a | 413 | static std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> getTopNetmasks(size_t topN); |
3814053e | 414 | static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getTopSuffixes(size_t topN); |
59b37d25 | 415 | static void purgeExpired(const struct timespec& now); |
a4244240 | 416 | |
b03d051c RG |
417 | static time_t s_expiredDynBlocksPurgeInterval; |
418 | ||
a4244240 | 419 | private: |
59b37d25 RG |
420 | static void collectMetrics(); |
421 | static void generateMetrics(); | |
a4244240 | 422 | |
59b37d25 RG |
423 | struct MetricsSnapshot |
424 | { | |
c173228a | 425 | std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> nmgData; |
59b37d25 RG |
426 | std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> smtData; |
427 | }; | |
428 | ||
01830997 RG |
429 | struct Tops |
430 | { | |
c173228a | 431 | std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> topNMGsByReason; |
01830997 RG |
432 | std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> topSMTsByReason; |
433 | }; | |
434 | ||
435 | static LockGuarded<Tops> s_tops; | |
436 | /* s_metricsData should only be accessed by the dynamic blocks maintenance thread so it does not need a lock */ | |
59b37d25 RG |
437 | // need N+1 datapoints to be able to do the diff after a collection point has been reached |
438 | static std::list<MetricsSnapshot> s_metricsData; | |
59b37d25 RG |
439 | static size_t s_topN; |
440 | }; | |
3dec2251 RG |
441 | |
442 | #endif /* DISABLE_DYNBLOCKS */ |