From: bert hubert Date: Tue, 3 Mar 2015 13:46:29 +0000 (+0100) Subject: add QPS limiting per domain or subnet X-Git-Tag: dnsdist-1.0.0-alpha1~248^2~88^2~102 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7c0860e1a4a8d91e9f28f87833e5a88cf06ad935;p=thirdparty%2Fpdns.git add QPS limiting per domain or subnet --- diff --git a/pdns/README-dnsdist.md b/pdns/README-dnsdist.md index 470aeb800b..c866fcc390 100644 --- a/pdns/README-dnsdist.md +++ b/pdns/README-dnsdist.md @@ -154,6 +154,33 @@ This is still much in flux, but for now, try: * `topQueries(20,2)`: shows the top-20 two-level domain queries (so `topQueries(20,1)` only shows TLDs) * `topResponses(20, 2)`: top-20 servfail responses (use ,3 for NXDOMAIN) + +Per domain or subnet QPS limiting +--------------------------------- +If certain domains or source addresses are generating onerous amounts of +traffic, you can put ceilings on the amount of traffic you are willing to +forward: + +``` +> addQPSLimit("h4xorbooter.xyz.", 10) +> addQPSLimit({"130.161.0.0/16", "145.14.0.0/16"} , 20) +> addQPSLimit({"nl.", "be."}, 1) +> showQPSLimits() +# Object Lim Passed Blocked +0 h4xorbooter.xyz. 10 0 0 +1 130.161.0.0/16, 145.14.0.0/16 20 0 0 +2 nl., be. 1 2 8 +``` + +To delete a limit: +``` +> deleteQPSLimit(1) +> showQPSLimits() +# Object Lim Passed Blocked +0 h4xorbooter.xyz. 10 0 0 +1 nl., be. 1 16 251 +``` + Dynamic load balancing ---------------------- diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index be88f3965f..117001f516 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -74,6 +74,7 @@ bool g_console; NetmaskGroup g_ACL; string g_outputBuffer; + /* UDP: the grand design. Per socket we listen on for incoming queries there is one thread. Then we have a bunch of connected sockets for talking to downstream servers. We send directly to those sockets. @@ -153,11 +154,22 @@ public: return d_passthrough? 0 : d_rate; } + int getPassed() const + { + return d_passed; + } + int getBlocked() const + { + return d_blocked; + } + bool check() { if(d_passthrough) return true; - d_tokens += 1.0*d_rate * (d_prev.udiffAndSet()/1000000.0); + auto delta = d_prev.udiffAndSet(); + + d_tokens += 1.0*d_rate * (delta/1000000.0); if(d_tokens > d_burst) d_tokens = d_burst; @@ -166,7 +178,11 @@ public: if(d_tokens >= 1.0) { // we need this because burst=1 is weird otherwise ret=true; --d_tokens; + d_passed++; } + else + d_blocked++; + return ret; } private: @@ -175,8 +191,12 @@ private: unsigned int d_burst; double d_tokens; StopWatch d_prev; + unsigned int d_passed{0}; + unsigned int d_blocked{0}; }; +vector, QPSLimiter> > g_limiters; + struct IDState { IDState() : origFD(-1) {} @@ -469,6 +489,24 @@ try g_rings.queryRing.push_back(qname); + bool blocked=false; + for(auto& lim : g_limiters) { + if(auto nmg=boost::get(&lim.first)) { + if(nmg->match(remote) && !lim.second.check()) { + blocked=true; + break; + } + } + else if(auto smn=boost::get(&lim.first)) { + if(smn->check(qname) && !lim.second.check()) { + blocked=true; + break; + } + } + } + if(blocked) + continue; + if(blockFilter) { std::lock_guard lock(g_luamutex); @@ -931,15 +969,18 @@ void setupLua(bool client) return a->order < b->order; }); return ret; - - } ); + + g_lua.writeFunction("deleteServer", - [](std::shared_ptr rem) + [](boost::variant, int> var) { - g_dstates.erase(remove(g_dstates.begin(), g_dstates.end(), rem), g_dstates.end()); + if(auto* rem = boost::get>(&var)) + g_dstates.erase(remove(g_dstates.begin(), g_dstates.end(), *rem), g_dstates.end()); + else + g_dstates.erase(g_dstates.begin() + boost::get(var)); } ); @@ -1018,6 +1059,52 @@ void setupLua(bool client) }); + g_lua.writeFunction("addQPSLimit", [](boost::variant> > var, int lim) { + SuffixMatchNode smn; + NetmaskGroup nmg; + + auto add=[&](string src) { + try { + smn.add(DNSName(src)); + } catch(...) { + nmg.addMask(src); + } + }; + if(auto src = boost::get(&var)) + add(*src); + else { + for(auto& a : boost::get>>(var)) { + add(a.second); + } + } + if(nmg.empty()) + g_limiters.push_back({smn, QPSLimiter(lim, lim)}); + else + g_limiters.push_back({nmg, QPSLimiter(lim, lim)}); + }); + + g_lua.writeFunction("deleteQPSLimit", [](int i) { + g_limiters.erase(g_limiters.begin() + i); + }); + + g_lua.writeFunction("showQPSLimits", []() { + boost::format fmt("%-3d %-50s %7d %8d %8d\n"); + g_outputBuffer += (fmt % "#" % "Object" % "Lim" % "Passed" % "Blocked").str(); + int num=0; + for(const auto& lim : g_limiters) { + string name; + if(auto nmg=boost::get(&lim.first)) { + name=nmg->toString(); + } + else if(auto smn=boost::get(&lim.first)) { + name=smn->toString(); + } + g_outputBuffer += (fmt % num % name % lim.second.getRate() % lim.second.getPassed() % lim.second.getBlocked()).str(); + ++num; + } + }); + + g_lua.writeFunction("getServers", []() { vector > > ret; int count=1; diff --git a/pdns/dnsname.hh b/pdns/dnsname.hh index 8e03bb0352..7f1e9d9936 100644 --- a/pdns/dnsname.hh +++ b/pdns/dnsname.hh @@ -71,6 +71,7 @@ struct SuffixMatchNode SuffixMatchNode(const std::string& name_="", bool endNode_=false) : name(name_), endNode(endNode_) {} std::string name; + std::string d_human; mutable bool endNode; mutable std::set children; bool operator<(const SuffixMatchNode& rhs) const @@ -80,6 +81,9 @@ struct SuffixMatchNode void add(const DNSName& name) { + if(!d_human.empty()) + d_human.append(", "); + d_human += name.toString(); add(name.getRawLabels()); } @@ -103,7 +107,6 @@ struct SuffixMatchNode return check(name.getRawLabels()); } - bool check(std::deque labels) const { if(labels.empty()) // optimization @@ -116,6 +119,10 @@ struct SuffixMatchNode labels.pop_back(); return child->check(labels); } - + + std::string toString() const + { + return d_human; + } };