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 
pushLuaContext::Pusher38     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 {
dnsdist_ffi_stat_node_tdnsdist_ffi_stat_node_t48   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 responses{0};
67     uint64_t respBytes{0};
68   };
69 
70   struct DynBlockRule
71   {
DynBlockRuleDynBlockRulesGroup::DynBlockRule72     DynBlockRule(): d_enabled(false)
73     {
74     }
75 
DynBlockRuleDynBlockRulesGroup::DynBlockRule76     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)
77     {
78     }
79 
matchesDynBlockRulesGroup::DynBlockRule80     bool matches(const struct timespec& when)
81     {
82       if (!d_enabled) {
83         return false;
84       }
85 
86       if (d_seconds && when < d_cutOff) {
87         return false;
88       }
89 
90       if (when < d_minTime) {
91         d_minTime = when;
92       }
93 
94       return true;
95     }
96 
rateExceededDynBlockRulesGroup::DynBlockRule97     bool rateExceeded(unsigned int count, const struct timespec& now) const
98     {
99       if (!d_enabled) {
100         return false;
101       }
102 
103       double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime);
104       double limit = delta * d_rate;
105       return (count > limit);
106     }
107 
warningRateExceededDynBlockRulesGroup::DynBlockRule108     bool warningRateExceeded(unsigned int count, const struct timespec& now) const
109     {
110       if (d_warningRate == 0) {
111         return false;
112       }
113 
114       double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime);
115       double limit = delta * d_warningRate;
116       return (count > limit);
117     }
118 
isEnabledDynBlockRulesGroup::DynBlockRule119     bool isEnabled() const
120     {
121       return d_enabled;
122     }
123 
toStringDynBlockRulesGroup::DynBlockRule124     std::string toString() const
125     {
126       if (!isEnabled()) {
127         return "";
128       }
129 
130       std::stringstream result;
131       if (d_action != DNSAction::Action::None) {
132         result << DNSAction::typeToString(d_action) << " ";
133       }
134       else {
135         result << "Apply the global DynBlock action ";
136       }
137       result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_rate) << " during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
138 
139       return result.str();
140     }
141 
142     std::string d_blockReason;
143     struct timespec d_cutOff;
144     struct timespec d_minTime;
145     unsigned int d_blockDuration{0};
146     unsigned int d_rate{0};
147     unsigned int d_warningRate{0};
148     unsigned int d_seconds{0};
149     DNSAction::Action d_action{DNSAction::Action::None};
150     bool d_enabled{false};
151   };
152 
153   struct DynBlockRatioRule: DynBlockRule
154   {
DynBlockRatioRuleDynBlockRulesGroup::DynBlockRatioRule155     DynBlockRatioRule(): DynBlockRule()
156     {
157     }
158 
DynBlockRatioRuleDynBlockRulesGroup::DynBlockRatioRule159     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)
160     {
161     }
162 
ratioExceededDynBlockRulesGroup::DynBlockRatioRule163     bool ratioExceeded(unsigned int total, unsigned int count) const
164     {
165       if (!d_enabled) {
166         return false;
167       }
168 
169       if (total < d_minimumNumberOfResponses) {
170         return false;
171       }
172 
173       double allowed = d_ratio * static_cast<double>(total);
174       return (count > allowed);
175     }
176 
warningRatioExceededDynBlockRulesGroup::DynBlockRatioRule177     bool warningRatioExceeded(unsigned int total, unsigned int count) const
178     {
179       if (d_warningRate == 0) {
180         return false;
181       }
182 
183       if (total < d_minimumNumberOfResponses) {
184         return false;
185       }
186 
187       double allowed = d_warningRatio * static_cast<double>(total);
188       return (count > allowed);
189     }
190 
toStringDynBlockRulesGroup::DynBlockRatioRule191     std::string toString() const
192     {
193       if (!isEnabled()) {
194         return "";
195       }
196 
197       std::stringstream result;
198       if (d_action != DNSAction::Action::None) {
199         result << DNSAction::typeToString(d_action) << " ";
200       }
201       else {
202         result << "Apply the global DynBlock action ";
203       }
204       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 << "'";
205 
206       return result.str();
207     }
208 
209     size_t d_minimumNumberOfResponses{0};
210     double d_ratio{0.0};
211     double d_warningRatio{0.0};
212   };
213 
214   typedef std::unordered_map<ComboAddress, Counts, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
215 
216 public:
DynBlockRulesGroup()217   DynBlockRulesGroup()
218   {
219   }
220 
setQueryRate(unsigned int rate,unsigned int warningRate,unsigned int seconds,std::string reason,unsigned int blockDuration,DNSAction::Action action)221   void setQueryRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
222   {
223     d_queryRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
224   }
225 
226   /* rate is in bytes per second */
setResponseByteRate(unsigned int rate,unsigned int warningRate,unsigned int seconds,std::string reason,unsigned int blockDuration,DNSAction::Action action)227   void setResponseByteRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
228   {
229     d_respRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
230   }
231 
setRCodeRate(uint8_t rcode,unsigned int rate,unsigned int warningRate,unsigned int seconds,std::string reason,unsigned int blockDuration,DNSAction::Action action)232   void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
233   {
234     auto& entry = d_rcodeRules[rcode];
235     entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
236   }
237 
setRCodeRatio(uint8_t rcode,double ratio,double warningRatio,unsigned int seconds,std::string reason,unsigned int blockDuration,DNSAction::Action action,size_t minimumNumberOfResponses)238   void setRCodeRatio(uint8_t rcode, double ratio, double warningRatio, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses)
239   {
240     auto& entry = d_rcodeRatioRules[rcode];
241     entry = DynBlockRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses);
242   }
243 
setQTypeRate(uint16_t qtype,unsigned int rate,unsigned int warningRate,unsigned int seconds,std::string reason,unsigned int blockDuration,DNSAction::Action action)244   void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
245   {
246     auto& entry = d_qtypeRules[qtype];
247     entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
248   }
249 
250   typedef std::function<bool(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)> smtVisitor_t;
251 
setSuffixMatchRule(unsigned int seconds,std::string reason,unsigned int blockDuration,DNSAction::Action action,smtVisitor_t visitor)252   void setSuffixMatchRule(unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, smtVisitor_t visitor)
253   {
254     d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
255     d_smtVisitor = visitor;
256   }
257 
setSuffixMatchRuleFFI(unsigned int seconds,std::string reason,unsigned int blockDuration,DNSAction::Action action,dnsdist_ffi_stat_node_visitor_t visitor)258   void setSuffixMatchRuleFFI(unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, dnsdist_ffi_stat_node_visitor_t visitor)
259   {
260     d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
261     d_smtVisitorFFI = visitor;
262   }
263 
apply()264   void apply()
265   {
266     struct timespec now;
267     gettime(&now);
268 
269     apply(now);
270   }
271 
272   void apply(const struct timespec& now);
273 
excludeRange(const Netmask & range)274   void excludeRange(const Netmask& range)
275   {
276     d_excludedSubnets.addMask(range);
277   }
278 
excludeRange(const NetmaskGroup & group)279   void excludeRange(const NetmaskGroup& group)
280   {
281     d_excludedSubnets.addMasks(group, true);
282   }
283 
includeRange(const Netmask & range)284   void includeRange(const Netmask& range)
285   {
286     d_excludedSubnets.addMask(range, false);
287   }
288 
includeRange(const NetmaskGroup & group)289   void includeRange(const NetmaskGroup& group)
290   {
291     d_excludedSubnets.addMasks(group, false);
292   }
293 
excludeDomain(const DNSName & domain)294   void excludeDomain(const DNSName& domain)
295   {
296     d_excludedDomains.add(domain);
297   }
298 
toString() const299   std::string toString() const
300   {
301     std::stringstream result;
302 
303     result << "Query rate rule: " << d_queryRateRule.toString() << std::endl;
304     result << "Response rate rule: " << d_respRateRule.toString() << std::endl;
305     result << "SuffixMatch rule: " << d_suffixMatchRule.toString() << std::endl;
306     result << "RCode rules: " << std::endl;
307     for (const auto& rule : d_rcodeRules) {
308       result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
309     }
310     for (const auto& rule : d_rcodeRatioRules) {
311       result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
312     }
313     result << "QType rules: " << std::endl;
314     for (const auto& rule : d_qtypeRules) {
315       result << "- " << QType(rule.first).getName() << ": " << rule.second.toString() << std::endl;
316     }
317     result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl;
318     result << "Excluded Domains: " << d_excludedDomains.toString() << std::endl;
319 
320     return result.str();
321   }
322 
setQuiet(bool quiet)323   void setQuiet(bool quiet)
324   {
325     d_beQuiet = quiet;
326   }
327 
328 private:
329 
330   bool checkIfQueryTypeMatches(const Rings::Query& query);
331   bool checkIfResponseCodeMatches(const Rings::Response& response);
332   void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning);
333   void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated);
334 
addBlock(boost::optional<NetmaskTree<DynBlock>> & blocks,const struct timespec & now,const ComboAddress & requestor,const DynBlockRule & rule,bool & updated)335   void addBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
336   {
337     addOrRefreshBlock(blocks, now, requestor, rule, updated, false);
338   }
339 
handleWarning(boost::optional<NetmaskTree<DynBlock>> & blocks,const struct timespec & now,const ComboAddress & requestor,const DynBlockRule & rule,bool & updated)340   void handleWarning(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
341   {
342     addOrRefreshBlock(blocks, now, requestor, rule, updated, true);
343   }
344 
hasQueryRules() const345   bool hasQueryRules() const
346   {
347     return d_queryRateRule.isEnabled() || !d_qtypeRules.empty();
348   }
349 
hasResponseRules() const350   bool hasResponseRules() const
351   {
352     return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty();
353   }
354 
hasSuffixMatchRules() const355   bool hasSuffixMatchRules() const
356   {
357     return d_suffixMatchRule.isEnabled();
358   }
359 
hasRules() const360   bool hasRules() const
361   {
362     return hasQueryRules() || hasResponseRules();
363   }
364 
365   void processQueryRules(counts_t& counts, const struct timespec& now);
366   void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now);
367 
368   std::map<uint8_t, DynBlockRule> d_rcodeRules;
369   std::map<uint8_t, DynBlockRatioRule> d_rcodeRatioRules;
370   std::map<uint16_t, DynBlockRule> d_qtypeRules;
371   DynBlockRule d_queryRateRule;
372   DynBlockRule d_respRateRule;
373   DynBlockRule d_suffixMatchRule;
374   NetmaskGroup d_excludedSubnets;
375   SuffixMatchNode d_excludedDomains;
376   smtVisitor_t d_smtVisitor;
377   dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI;
378   bool d_beQuiet{false};
379 };
380 
381 class DynBlockMaintenance
382 {
383 public:
384   static void run();
385 
386   /* return the (cached) number of hits per second for the top offenders, averaged over 60s */
387   static std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> getHitsForTopNetmasks();
388   static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getHitsForTopSuffixes();
389 
390   /* get the the top offenders based on the current value of the counters */
391   static std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> getTopNetmasks(size_t topN);
392   static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getTopSuffixes(size_t topN);
393   static void purgeExpired(const struct timespec& now);
394 
395   static time_t s_expiredDynBlocksPurgeInterval;
396 
397 private:
398   static void collectMetrics();
399   static void generateMetrics();
400 
401   struct MetricsSnapshot
402   {
403     std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> nmgData;
404     std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> smtData;
405   };
406 
407   /* Protects s_topNMGsByReason and s_topSMTsByReason. s_metricsData should only be accessed
408      by the dynamic blocks maintenance thread so it does not need a lock. */
409   static std::mutex s_topsMutex;
410   // need N+1 datapoints to be able to do the diff after a collection point has been reached
411   static std::list<MetricsSnapshot> s_metricsData;
412   static std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> s_topNMGsByReason;
413   static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> s_topSMTsByReason;
414   static size_t s_topN;
415 };
416