]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsdist-dynblocks.hh
spelling: syscall
[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 "dolog.hh"
25 #include "dnsdist-rings.hh"
26
27 class DynBlockRulesGroup
28 {
29 private:
30
31 struct Counts
32 {
33 std::map<uint8_t, uint64_t> d_rcodeCounts;
34 std::map<uint16_t, uint64_t> d_qtypeCounts;
35 uint64_t queries{0};
36 uint64_t respBytes{0};
37 };
38
39 struct DynBlockRule
40 {
41 DynBlockRule(): d_enabled(false)
42 {
43 }
44
45 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)
46 {
47 }
48
49 bool matches(const struct timespec& when)
50 {
51 if (!d_enabled) {
52 return false;
53 }
54
55 if (d_seconds && when < d_cutOff) {
56 return false;
57 }
58
59 if (when < d_minTime) {
60 d_minTime = when;
61 }
62
63 return true;
64 }
65
66 bool rateExceeded(unsigned int count, const struct timespec& now) const
67 {
68 if (!d_enabled) {
69 return false;
70 }
71
72 double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime);
73 double limit = delta * d_rate;
74 return (count > limit);
75 }
76
77 bool warningRateExceeded(unsigned int count, const struct timespec& now) const
78 {
79 if (d_warningRate == 0) {
80 return false;
81 }
82
83 double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime);
84 double limit = delta * d_warningRate;
85 return (count > limit);
86 }
87
88 bool isEnabled() const
89 {
90 return d_enabled;
91 }
92
93 std::string toString() const
94 {
95 if (!isEnabled()) {
96 return "";
97 }
98
99 std::stringstream result;
100 if (d_action != DNSAction::Action::None) {
101 result << DNSAction::typeToString(d_action) << " ";
102 }
103 else {
104 result << "Apply the global DynBlock action ";
105 }
106 result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_rate) << " during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
107
108 return result.str();
109 }
110
111 std::string d_blockReason;
112 struct timespec d_cutOff;
113 struct timespec d_minTime;
114 unsigned int d_blockDuration{0};
115 unsigned int d_rate{0};
116 unsigned int d_warningRate{0};
117 unsigned int d_seconds{0};
118 DNSAction::Action d_action{DNSAction::Action::None};
119 bool d_enabled{false};
120 };
121
122 typedef std::unordered_map<ComboAddress, Counts, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
123
124 public:
125 DynBlockRulesGroup()
126 {
127 }
128
129 void setQueryRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
130 {
131 d_queryRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
132 }
133
134 /* rate is in bytes per second */
135 void setResponseByteRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
136 {
137 d_respRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
138 }
139
140 void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
141 {
142 auto& entry = d_rcodeRules[rcode];
143 entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
144 }
145
146 void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
147 {
148 auto& entry = d_qtypeRules[qtype];
149 entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
150 }
151
152 void apply()
153 {
154 struct timespec now;
155 gettime(&now);
156
157 apply(now);
158 }
159
160 void apply(const struct timespec& now)
161 {
162 counts_t counts;
163
164 size_t entriesCount = 0;
165 if (hasQueryRules()) {
166 entriesCount += g_rings.getNumberOfQueryEntries();
167 }
168 if (hasResponseRules()) {
169 entriesCount += g_rings.getNumberOfResponseEntries();
170 }
171 counts.reserve(entriesCount);
172
173 processQueryRules(counts, now);
174 processResponseRules(counts, now);
175
176 if (counts.empty()) {
177 return;
178 }
179
180 boost::optional<NetmaskTree<DynBlock> > blocks;
181 bool updated = false;
182
183 for (const auto& entry : counts) {
184 const auto& requestor = entry.first;
185 const auto& counters = entry.second;
186
187 if (d_queryRateRule.warningRateExceeded(counters.queries, now)) {
188 handleWarning(blocks, now, requestor, d_queryRateRule, updated);
189 }
190
191 if (d_queryRateRule.rateExceeded(counters.queries, now)) {
192 addBlock(blocks, now, requestor, d_queryRateRule, updated);
193 continue;
194 }
195
196 if (d_respRateRule.warningRateExceeded(counters.respBytes, now)) {
197 handleWarning(blocks, now, requestor, d_respRateRule, updated);
198 }
199
200 if (d_respRateRule.rateExceeded(counters.respBytes, now)) {
201 addBlock(blocks, now, requestor, d_respRateRule, updated);
202 continue;
203 }
204
205 for (const auto& pair : d_qtypeRules) {
206 const auto qtype = pair.first;
207
208 const auto& typeIt = counters.d_qtypeCounts.find(qtype);
209 if (typeIt != counters.d_qtypeCounts.cend()) {
210
211 if (pair.second.warningRateExceeded(typeIt->second, now)) {
212 handleWarning(blocks, now, requestor, pair.second, updated);
213 }
214
215 if (pair.second.rateExceeded(typeIt->second, now)) {
216 addBlock(blocks, now, requestor, pair.second, updated);
217 break;
218 }
219 }
220 }
221
222 for (const auto& pair : d_rcodeRules) {
223 const auto rcode = pair.first;
224
225 const auto& rcodeIt = counters.d_rcodeCounts.find(rcode);
226 if (rcodeIt != counters.d_rcodeCounts.cend()) {
227 if (pair.second.warningRateExceeded(rcodeIt->second, now)) {
228 handleWarning(blocks, now, requestor, pair.second, updated);
229 }
230
231 if (pair.second.rateExceeded(rcodeIt->second, now)) {
232 addBlock(blocks, now, requestor, pair.second, updated);
233 break;
234 }
235 }
236 }
237 }
238
239 if (updated && blocks) {
240 g_dynblockNMG.setState(*blocks);
241 }
242 }
243
244 void excludeRange(const Netmask& range)
245 {
246 d_excludedSubnets.addMask(range);
247 }
248
249 void includeRange(const Netmask& range)
250 {
251 d_excludedSubnets.addMask(range, false);
252 }
253
254 std::string toString() const
255 {
256 std::stringstream result;
257
258 result << "Query rate rule: " << d_queryRateRule.toString() << std::endl;
259 result << "Response rate rule: " << d_respRateRule.toString() << std::endl;
260 result << "RCode rules: " << std::endl;
261 for (const auto& rule : d_rcodeRules) {
262 result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
263 }
264 result << "QType rules: " << std::endl;
265 for (const auto& rule : d_qtypeRules) {
266 result << "- " << QType(rule.first).getName() << ": " << rule.second.toString() << std::endl;
267 }
268 result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl;
269
270 return result.str();
271 }
272
273 void setQuiet(bool quiet)
274 {
275 d_beQuiet = quiet;
276 }
277
278 private:
279 bool checkIfQueryTypeMatches(const Rings::Query& query)
280 {
281 auto rule = d_qtypeRules.find(query.qtype);
282 if (rule == d_qtypeRules.end()) {
283 return false;
284 }
285
286 return rule->second.matches(query.when);
287 }
288
289 bool checkIfResponseCodeMatches(const Rings::Response& response)
290 {
291 auto rule = d_rcodeRules.find(response.dh.rcode);
292 if (rule == d_rcodeRules.end()) {
293 return false;
294 }
295
296 return rule->second.matches(response.when);
297 }
298
299 void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning)
300 {
301 if (d_excludedSubnets.match(requestor)) {
302 /* do not add a block for excluded subnets */
303 return;
304 }
305
306 if (!blocks) {
307 blocks = g_dynblockNMG.getCopy();
308 }
309 struct timespec until = now;
310 until.tv_sec += rule.d_blockDuration;
311 unsigned int count = 0;
312 const auto& got = blocks->lookup(Netmask(requestor));
313 bool expired = false;
314 bool wasWarning = false;
315
316 if (got) {
317 if (warning && !got->second.warning) {
318 /* we have an existing entry which is not a warning,
319 don't override it */
320 return;
321 }
322 else if (!warning && got->second.warning) {
323 wasWarning = true;
324 }
325 else {
326 if (until < got->second.until) {
327 // had a longer policy
328 return;
329 }
330 }
331
332 if (now < got->second.until) {
333 // only inherit count on fresh query we are extending
334 count = got->second.blocks;
335 }
336 else {
337 expired = true;
338 }
339 }
340
341 DynBlock db{rule.d_blockReason, until, DNSName(), warning ? DNSAction::Action::NoOp : rule.d_action};
342 db.blocks = count;
343 db.warning = warning;
344 if (!d_beQuiet && (!got || expired || wasWarning)) {
345 warnlog("Inserting %sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
346 }
347 blocks->insert(Netmask(requestor)).second = db;
348 updated = true;
349 }
350
351 void addBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
352 {
353 addOrRefreshBlock(blocks, now, requestor, rule, updated, false);
354 }
355
356 void handleWarning(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
357 {
358 addOrRefreshBlock(blocks, now, requestor, rule, updated, true);
359 }
360
361 bool hasQueryRules() const
362 {
363 return d_queryRateRule.isEnabled() || !d_qtypeRules.empty();
364 }
365
366 bool hasResponseRules() const
367 {
368 return d_respRateRule.isEnabled() || !d_rcodeRules.empty();
369 }
370
371 bool hasRules() const
372 {
373 return hasQueryRules() || hasResponseRules();
374 }
375
376 void processQueryRules(counts_t& counts, const struct timespec& now)
377 {
378 if (!hasQueryRules()) {
379 return;
380 }
381
382 d_queryRateRule.d_cutOff = d_queryRateRule.d_minTime = now;
383 d_queryRateRule.d_cutOff.tv_sec -= d_queryRateRule.d_seconds;
384
385 for (auto& rule : d_qtypeRules) {
386 rule.second.d_cutOff = rule.second.d_minTime = now;
387 rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
388 }
389
390 for (const auto& shard : g_rings.d_shards) {
391 std::lock_guard<std::mutex> rl(shard->queryLock);
392 for(const auto& c : shard->queryRing) {
393 if (now < c.when) {
394 continue;
395 }
396
397 bool qRateMatches = d_queryRateRule.matches(c.when);
398 bool typeRuleMatches = checkIfQueryTypeMatches(c);
399
400 if (qRateMatches || typeRuleMatches) {
401 auto& entry = counts[c.requestor];
402 if (qRateMatches) {
403 entry.queries++;
404 }
405 if (typeRuleMatches) {
406 entry.d_qtypeCounts[c.qtype]++;
407 }
408 }
409 }
410 }
411 }
412
413 void processResponseRules(counts_t& counts, const struct timespec& now)
414 {
415 if (!hasResponseRules()) {
416 return;
417 }
418
419 d_respRateRule.d_cutOff = d_respRateRule.d_minTime = now;
420 d_respRateRule.d_cutOff.tv_sec -= d_respRateRule.d_seconds;
421
422 for (auto& rule : d_rcodeRules) {
423 rule.second.d_cutOff = rule.second.d_minTime = now;
424 rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
425 }
426
427 for (const auto& shard : g_rings.d_shards) {
428 std::lock_guard<std::mutex> rl(shard->respLock);
429 for(const auto& c : shard->respRing) {
430 if (now < c.when) {
431 continue;
432 }
433
434 bool respRateMatches = d_respRateRule.matches(c.when);
435 bool rcodeRuleMatches = checkIfResponseCodeMatches(c);
436
437 if (respRateMatches || rcodeRuleMatches) {
438 auto& entry = counts[c.requestor];
439 if (respRateMatches) {
440 entry.respBytes += c.size;
441 }
442 if (rcodeRuleMatches) {
443 entry.d_rcodeCounts[c.dh.rcode]++;
444 }
445 }
446 }
447 }
448 }
449
450 std::map<uint8_t, DynBlockRule> d_rcodeRules;
451 std::map<uint16_t, DynBlockRule> d_qtypeRules;
452 DynBlockRule d_queryRateRule;
453 DynBlockRule d_respRateRule;
454 NetmaskGroup d_excludedSubnets;
455 bool d_beQuiet{false};
456 };