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