]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsdist-dynblocks.hh
Merge pull request #6694 from zeha/cleanup20180530
[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 seconds, DNSAction::Action action): d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), 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 isEnabled() const
78 {
79 return d_enabled;
80 }
81
82 std::string toString() const
83 {
84 if (!isEnabled()) {
85 return "";
86 }
87
88 std::stringstream result;
89 if (d_action != DNSAction::Action::None) {
90 result << DNSAction::typeToString(d_action) << " ";
91 }
92 else {
93 result << "Apply the global DynBlock action ";
94 }
95 result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_rate) << " during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
96
97 return result.str();
98 }
99
100 std::string d_blockReason;
101 struct timespec d_cutOff;
102 struct timespec d_minTime;
103 unsigned int d_blockDuration{0};
104 unsigned int d_rate{0};
105 unsigned int d_seconds{0};
106 DNSAction::Action d_action{DNSAction::Action::None};
107 bool d_enabled{false};
108 };
109
110 typedef std::unordered_map<ComboAddress, Counts, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
111
112 public:
113 DynBlockRulesGroup()
114 {
115 }
116
117 void setQueryRate(unsigned int rate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
118 {
119 d_queryRateRule = DynBlockRule(reason, blockDuration, rate, seconds, action);
120 }
121
122 void setResponseByteRate(unsigned int rate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
123 {
124 d_respRateRule = DynBlockRule(reason, blockDuration, rate, seconds, action);
125 }
126
127 void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
128 {
129 auto& entry = d_rcodeRules[rcode];
130 entry = DynBlockRule(reason, blockDuration, rate, seconds, action);
131 }
132
133 void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
134 {
135 auto& entry = d_qtypeRules[qtype];
136 entry = DynBlockRule(reason, blockDuration, rate, seconds, action);
137 }
138
139 void apply()
140 {
141 counts_t counts;
142
143 size_t entriesCount = 0;
144 if (hasQueryRules()) {
145 entriesCount += g_rings.getNumberOfQueryEntries();
146 }
147 if (hasResponseRules()) {
148 entriesCount += g_rings.getNumberOfResponseEntries();
149 }
150 counts.reserve(entriesCount);
151
152 processQueryRules(counts);
153 processResponseRules(counts);
154
155 if (counts.empty()) {
156 return;
157 }
158
159 boost::optional<NetmaskTree<DynBlock> > blocks;
160 bool updated = false;
161 struct timespec now;
162 gettime(&now);
163
164 for (const auto& entry : counts) {
165 if (d_queryRateRule.rateExceeded(entry.second.queries, now)) {
166 addBlock(blocks, now, entry.first, d_queryRateRule, updated);
167 continue;
168 }
169
170 if (d_respRateRule.rateExceeded(entry.second.respBytes, now)) {
171 addBlock(blocks, now, entry.first, d_respRateRule, updated);
172 continue;
173 }
174
175 for (const auto& rule : d_qtypeRules) {
176 const auto& typeIt = entry.second.d_qtypeCounts.find(rule.first);
177 if (typeIt != entry.second.d_qtypeCounts.cend() && rule.second.rateExceeded(typeIt->second, now)) {
178 addBlock(blocks, now, entry.first, rule.second, updated);
179 break;
180 }
181 }
182
183 for (const auto& rule : d_rcodeRules) {
184 const auto& rcodeIt = entry.second.d_rcodeCounts.find(rule.first);
185 if (rcodeIt != entry.second.d_rcodeCounts.cend() && rule.second.rateExceeded(rcodeIt->second, now)) {
186 addBlock(blocks, now, entry.first, rule.second, updated);
187 break;
188 }
189 }
190 }
191
192 if (updated && blocks) {
193 g_dynblockNMG.setState(*blocks);
194 }
195 }
196
197 void excludeRange(const Netmask& range)
198 {
199 d_excludedSubnets.addMask(range);
200 }
201
202 void includeRange(const Netmask& range)
203 {
204 d_excludedSubnets.addMask(range, false);
205 }
206
207 std::string toString() const
208 {
209 std::stringstream result;
210
211 result << "Query rate rule: " << d_queryRateRule.toString() << std::endl;
212 result << "Response rate rule: " << d_respRateRule.toString() << std::endl;
213 result << "RCode rules: " << std::endl;
214 for (const auto& rule : d_rcodeRules) {
215 result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
216 }
217 result << "QType rules: " << std::endl;
218 for (const auto& rule : d_qtypeRules) {
219 result << "- " << QType(rule.first).getName() << ": " << rule.second.toString() << std::endl;
220 }
221 result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl;
222
223 return result.str();
224 }
225
226 private:
227 bool checkIfQueryTypeMatches(const Rings::Query& query)
228 {
229 auto rule = d_qtypeRules.find(query.qtype);
230 if (rule == d_qtypeRules.end()) {
231 return false;
232 }
233
234 return rule->second.matches(query.when);
235 }
236
237 bool checkIfResponseCodeMatches(const Rings::Response& response)
238 {
239 auto rule = d_rcodeRules.find(response.dh.rcode);
240 if (rule == d_rcodeRules.end()) {
241 return false;
242 }
243
244 return rule->second.matches(response.when);
245 }
246
247 void addBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
248 {
249 if (d_excludedSubnets.match(requestor)) {
250 /* do not add a block for excluded subnets */
251 return;
252 }
253
254 if (!blocks) {
255 blocks = g_dynblockNMG.getCopy();
256 }
257 struct timespec until = now;
258 until.tv_sec += rule.d_blockDuration;
259 unsigned int count = 0;
260 const auto& got = blocks->lookup(Netmask(requestor));
261 bool expired = false;
262 if (got) {
263 if (until < got->second.until) {
264 // had a longer policy
265 return;
266 }
267
268 if (now < got->second.until) {
269 // only inherit count on fresh query we are extending
270 count = got->second.blocks;
271 }
272 else {
273 expired = true;
274 }
275 }
276
277 DynBlock db{rule.d_blockReason, until, DNSName(), rule.d_action};
278 db.blocks = count;
279 if (!got || expired) {
280 warnlog("Inserting dynamic block for %s for %d seconds: %s", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
281 }
282 blocks->insert(Netmask(requestor)).second = db;
283 updated = true;
284 }
285
286 bool hasQueryRules() const
287 {
288 return d_queryRateRule.isEnabled() || !d_qtypeRules.empty();
289 }
290
291 bool hasResponseRules() const
292 {
293 return d_respRateRule.isEnabled() || !d_rcodeRules.empty();
294 }
295
296 bool hasRules() const
297 {
298 return hasQueryRules() || hasResponseRules();
299 }
300
301 void processQueryRules(counts_t& counts)
302 {
303 if (!hasQueryRules()) {
304 return;
305 }
306
307 struct timespec now;
308 gettime(&now);
309 d_queryRateRule.d_cutOff = d_queryRateRule.d_minTime = now;
310 d_queryRateRule.d_cutOff.tv_sec -= d_queryRateRule.d_seconds;
311
312 for (auto& rule : d_qtypeRules) {
313 rule.second.d_cutOff = rule.second.d_minTime = now;
314 rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
315 }
316
317 for (const auto& shard : g_rings.d_shards) {
318 std::lock_guard<std::mutex> rl(shard->queryLock);
319 for(const auto& c : shard->queryRing) {
320 if (now < c.when) {
321 continue;
322 }
323
324 bool qRateMatches = d_queryRateRule.matches(c.when);
325 bool typeRuleMatches = checkIfQueryTypeMatches(c);
326
327 if (qRateMatches || typeRuleMatches) {
328 auto& entry = counts[c.requestor];
329 if (qRateMatches) {
330 entry.queries++;
331 }
332 if (typeRuleMatches) {
333 entry.d_qtypeCounts[c.qtype]++;
334 }
335 }
336 }
337 }
338 }
339
340 void processResponseRules(counts_t& counts)
341 {
342 if (!hasResponseRules()) {
343 return;
344 }
345
346 struct timespec now;
347 gettime(&now);
348 d_respRateRule.d_cutOff = d_respRateRule.d_minTime = now;
349 d_respRateRule.d_cutOff.tv_sec -= d_respRateRule.d_seconds;
350
351 for (auto& rule : d_rcodeRules) {
352 rule.second.d_cutOff = rule.second.d_minTime = now;
353 rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
354 }
355
356 for (const auto& shard : g_rings.d_shards) {
357 std::lock_guard<std::mutex> rl(shard->respLock);
358 for(const auto& c : shard->respRing) {
359 if (now < c.when) {
360 continue;
361 }
362
363 bool respRateMatches = d_respRateRule.matches(c.when);
364 bool rcodeRuleMatches = checkIfResponseCodeMatches(c);
365
366 if (respRateMatches || rcodeRuleMatches) {
367 auto& entry = counts[c.requestor];
368 if (respRateMatches) {
369 entry.respBytes += c.size;
370 }
371 if (rcodeRuleMatches) {
372 entry.d_rcodeCounts[c.dh.rcode]++;
373 }
374 }
375 }
376 }
377 }
378
379 std::map<uint8_t, DynBlockRule> d_rcodeRules;
380 std::map<uint16_t, DynBlockRule> d_qtypeRules;
381 DynBlockRule d_queryRateRule;
382 DynBlockRule d_respRateRule;
383 NetmaskGroup d_excludedSubnets;
384 };