From: Remi Gacogne Date: Wed, 29 Jun 2016 13:05:50 +0000 (+0200) Subject: dnsdist: Display the dyn eBPF filters stats in the web interface X-Git-Tag: rec-4.0.2~20^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F4068%2Fhead;p=thirdparty%2Fpdns.git dnsdist: Display the dyn eBPF filters stats in the web interface Dynamic BPF filters need to be registered to appear in the interface, and unregistered when not needed anymore. Automatic registration would mean that dangling dynamic BPF filters could not be garbage collected without being unregistered first. --- diff --git a/pdns/README-dnsdist.md b/pdns/README-dnsdist.md index bd91fa3900..edbadb981e 100644 --- a/pdns/README-dnsdist.md +++ b/pdns/README-dnsdist.md @@ -1160,6 +1160,7 @@ Here are all functions: * function `showBinds()`: list every local bind * function `getBind(n)`: return the corresponding `ClientState` object * member `attachFilter(BPFFilter)`: attach a BPF Filter to this bind + * member `detachFilter()`: detach the BPF Filter attached to this bind, if any * member `toString()`: print the address this bind listens to * Network related: * `addLocal(netmask, [true], [false], [TCP Fast Open queue size])`: add to addresses we listen on. Second optional parameter sets TCP or not. Third optional parameter sets SO_REUSEPORT when available. Last parameter sets the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0. @@ -1413,6 +1414,8 @@ instantiate a server with additional parameters * function `newDynBPFFilter(BPFFilter)`: return a new DynBPFFilter object using this BPF Filter * member `block(ComboAddress[, seconds]): add this address to the underlying BPF Filter for `seconds` seconds (default to 10 seconds) * member `purgeExpired()`: remove expired entries + * function `registerDynBPFFilter(DynBPFFilter)`: register this dynamic BPF filter into the web interface so that its counters are displayed + * function `unregisterDynBPFFilter(DynBPFFilter)`: unregister this dynamic BPF filter * RemoteLogger related: * `newRemoteLogger(address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1])`: create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()` diff --git a/pdns/bpf-filter.cc b/pdns/bpf-filter.cc index 90a2031271..020d97c3e2 100644 --- a/pdns/bpf-filter.cc +++ b/pdns/bpf-filter.cc @@ -174,6 +174,15 @@ void BPFFilter::addSocket(int sock) } } +void BPFFilter::removeSocket(int sock) +{ + int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &d_mainfilter.fd, sizeof(d_mainfilter.fd)); + + if (res != 0) { + throw std::runtime_error("Error detaching BPF filter from this socket: " + std::string(strerror(errno))); + } +} + void BPFFilter::block(const ComboAddress& addr) { std::unique_lock lock(d_mutex); diff --git a/pdns/bpf-filter.hh b/pdns/bpf-filter.hh index 38c65eb640..b23bd04314 100644 --- a/pdns/bpf-filter.hh +++ b/pdns/bpf-filter.hh @@ -12,6 +12,7 @@ class BPFFilter public: BPFFilter(uint32_t maxV4Addresses, uint32_t maxV6Addresses, uint32_t maxQNames); void addSocket(int sock); + void removeSocket(int sock); void block(const ComboAddress& addr); void block(const DNSName& qname, uint16_t qtype=255); void unblock(const ComboAddress& addr); diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index 4a7f05412a..4f3dab28f1 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -282,6 +282,7 @@ const std::vector g_consoleKeywords{ { "PoolAction", true, "poolname", "set the packet into the specified pool" }, { "printDNSCryptProviderFingerprint", true, "\"/path/to/providerPublic.key\"", "display the fingerprint of the provided resolver public key" }, { "RegexRule", true, "regex", "matches the query name against the supplied regex" }, + { "registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed" }, { "RemoteLogAction", true, "RemoteLogger", "send the content of this query to a remote logger via Protocol Buffer" }, { "RemoteLogResponseAction", true, "RemoteLogger", "send the content of this response to a remote logger via Protocol Buffer" }, { "rmResponseRule", true, "n", "remove response rule n" }, @@ -329,6 +330,7 @@ const std::vector g_consoleKeywords{ { "topRule", true, "", "move the last rule to the first position" }, { "topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds, grouped by last `labels` labels" }, { "truncateTC", true, "bool", "if set (default) truncate TC=1 answers so they are actually empty. Fixes an issue for PowerDNS Authoritative Server 2.9.22" }, + { "unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter" }, { "webserver", true, "address:port, password [, apiKey [, customHeaders ]])", "launch a webserver with stats on that address with that password" }, { "whashed", false, "", "Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter" }, { "wrandom", false, "", "Weighted random over available servers, based on the server 'weight' parameter" }, diff --git a/pdns/dnsdist-dynbpf.cc b/pdns/dnsdist-dynbpf.cc index a8bd68a2dc..c3c0f68fc0 100644 --- a/pdns/dnsdist-dynbpf.cc +++ b/pdns/dnsdist-dynbpf.cc @@ -37,4 +37,21 @@ void DynBPFFilter::purgeExpired(const struct timespec& now) } } +std::vector > DynBPFFilter::getAddrStats() +{ + std::vector > result; + if (!d_bpf) { + return result; + } + + const auto& stats = d_bpf->getAddrStats(); + for (const auto& stat : stats) { + const container_t::iterator it = d_entries.find(stat.first); + if (it != d_entries.end()) { + result.push_back({stat.first, stat.second, it->d_until}); + } + } + return result; +} + #endif /* HAVE_EBPF */ diff --git a/pdns/dnsdist-dynbpf.hh b/pdns/dnsdist-dynbpf.hh index ebd9876112..a05fe55dac 100644 --- a/pdns/dnsdist-dynbpf.hh +++ b/pdns/dnsdist-dynbpf.hh @@ -22,6 +22,7 @@ public: } void block(const ComboAddress& addr, const struct timespec& until); void purgeExpired(const struct timespec& now); + std::vector > getAddrStats(); private: struct BlockEntry { diff --git a/pdns/dnsdist-lua2.cc b/pdns/dnsdist-lua2.cc index 12066df03a..ce2ae0b5b3 100644 --- a/pdns/dnsdist-lua2.cc +++ b/pdns/dnsdist-lua2.cc @@ -871,15 +871,19 @@ void moreLua(bool client) g_lua.registerFunction::*)()>("attachToAllBinds", [](std::shared_ptr bpf) { std::string res; if (bpf) { - for (const auto& front : g_frontends) { - bpf->addSocket(front->udpFD != -1 ? front->udpFD : front->tcpFD); + for (const auto& frontend : g_frontends) { + frontend->attachFilter(bpf); } } }); + g_lua.registerFunction("detachFilter", [](ClientState& frontend) { + frontend.detachFilter(); + }); + g_lua.registerFunction)>("attachFilter", [](ClientState& frontend, std::shared_ptr bpf) { if (bpf) { - bpf->addSocket(frontend.udpFD != -1 ? frontend.udpFD : frontend.tcpFD); + frontend.attachFilter(bpf); } }); @@ -895,6 +899,23 @@ void moreLua(bool client) return std::make_shared(bpf); }); + g_lua.writeFunction("registerDynBPFFilter", [](std::shared_ptr dbpf) { + if (dbpf) { + g_dynBPFFilters.push_back(dbpf); + } + }); + + g_lua.writeFunction("unregisterDynBPFFilter", [](std::shared_ptr dbpf) { + if (dbpf) { + for (auto it = g_dynBPFFilters.cbegin(); it != g_dynBPFFilters.cend(); it++) { + if (*it == dbpf) { + g_dynBPFFilters.erase(it); + break; + } + } + } + }); + g_lua.registerFunction::*)(const ComboAddress& addr, boost::optional seconds)>("block", [](std::shared_ptr dbpf, const ComboAddress& addr, boost::optional seconds) { if (dbpf) { struct timespec until; diff --git a/pdns/dnsdist-web.cc b/pdns/dnsdist-web.cc index e5a6e9939c..28d0460587 100644 --- a/pdns/dnsdist-web.cc +++ b/pdns/dnsdist-web.cc @@ -180,8 +180,11 @@ static void connectionThread(int sock, ComboAddress remote, string password, str gettime(&now); for(const auto& e: slow) { if(now < e->second.until ) { - Json::object thing{{"reason", e->second.reason}, {"seconds", (double)(e->second.until.tv_sec - now.tv_sec)}, - {"blocks", (double)e->second.blocks} }; + Json::object thing{ + {"reason", e->second.reason}, + {"seconds", (double)(e->second.until.tv_sec - now.tv_sec)}, + {"blocks", (double)e->second.blocks} + }; obj.insert({e->first.toString(), thing}); } } @@ -201,6 +204,27 @@ static void connectionThread(int sock, ComboAddress remote, string password, str + Json my_json = obj; + resp.body=my_json.dump(); + resp.headers["Content-Type"] = "application/json"; + } + else if(command=="ebpfblocklist") { + Json::object obj; +#ifdef HAVE_EBPF + struct timespec now; + gettime(&now); + for (const auto& dynbpf : g_dynBPFFilters) { + std::vector > addrStats = dynbpf->getAddrStats(); + for (const auto& entry : addrStats) { + Json::object thing + { + {"seconds", (double)(std::get<2>(entry).tv_sec - now.tv_sec)}, + {"blocks", (double)(std::get<1>(entry))} + }; + obj.insert({std::get<0>(entry).toString(), thing }); + } + } +#endif /* HAVE_EBPF */ Json my_json = obj; resp.body=my_json.dump(); resp.headers["Content-Type"] = "application/json"; diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 605ba2b7f9..71fa52d3b8 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -85,6 +85,7 @@ std::vector> g_dnsCryptLocals #endif #ifdef HAVE_EBPF shared_ptr g_defaultBPFFilter; +std::vector > g_dynBPFFilters; #endif /* HAVE_EBPF */ vector g_frontends; GlobalStateHolder g_pools; @@ -1688,7 +1689,7 @@ try #ifdef HAVE_EBPF if (g_defaultBPFFilter) { - g_defaultBPFFilter->addSocket(cs->udpFD); + cs->attachFilter(g_defaultBPFFilter); vinfolog("Attaching default BPF Filter to UDP frontend %s", cs->local.toStringWithPort()); } #endif /* HAVE_EBPF */ @@ -1731,7 +1732,7 @@ try #endif #ifdef HAVE_EBPF if (g_defaultBPFFilter) { - g_defaultBPFFilter->addSocket(cs->tcpFD); + cs->attachFilter(g_defaultBPFFilter); vinfolog("Attaching default BPF Filter to TCP frontend %s", cs->local.toStringWithPort()); } #endif /* HAVE_EBPF */ @@ -1773,7 +1774,7 @@ try } #ifdef HAVE_EBPF if (g_defaultBPFFilter) { - g_defaultBPFFilter->addSocket(cs->udpFD); + cs->attachFilter(g_defaultBPFFilter); vinfolog("Attaching default BPF Filter to UDP DNSCrypt frontend %s", cs->local.toStringWithPort()); } #endif /* HAVE_EBPF */ @@ -1808,7 +1809,7 @@ try } #ifdef HAVE_EBPF if (g_defaultBPFFilter) { - g_defaultBPFFilter->addSocket(cs->tcpFD); + cs->attachFilter(g_defaultBPFFilter); vinfolog("Attaching default BPF Filter to TCP DNSCrypt frontend %s", cs->local.toStringWithPort()); } #endif /* HAVE_EBPF */ diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index a91fd16e64..78c0c7da84 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -283,6 +283,31 @@ struct ClientState std::atomic queries{0}; int udpFD{-1}; int tcpFD{-1}; + + int getSocket() const + { + return udpFD != -1 ? udpFD : tcpFD; + } + +#ifdef HAVE_EBPF + shared_ptr d_filter; + + void detachFilter() + { + if (d_filter) { + d_filter->removeSocket(getSocket()); + d_filter = nullptr; + } + } + + void attachFilter(shared_ptr bpf) + { + detachFilter(); + + bpf->addSocket(getSocket()); + d_filter = bpf; + } +#endif /* HAVE_EBPF */ }; class TCPClientCollection { @@ -629,6 +654,7 @@ extern const std::vector g_consoleKeywords; #ifdef HAVE_EBPF extern shared_ptr g_defaultBPFFilter; +extern std::vector > g_dynBPFFilters; #endif /* HAVE_EBPF */ struct dnsheader; diff --git a/pdns/dnsdistdist/html/index.html b/pdns/dnsdistdist/html/index.html index 1417c212a7..9641111bda 100644 --- a/pdns/dnsdistdist/html/index.html +++ b/pdns/dnsdistdist/html/index.html @@ -86,6 +86,9 @@
+ +
+ diff --git a/pdns/dnsdistdist/html/local.js b/pdns/dnsdistdist/html/local.js index 4b20cd074f..c4f66441fc 100644 --- a/pdns/dnsdistdist/html/local.js +++ b/pdns/dnsdistdist/html/local.js @@ -236,6 +236,22 @@ $(document).ready(function() { }}); + $.ajax({ url: 'jsonstat?command=ebpfblocklist', type: 'GET', dataType: 'json', jsonp: false, + success: function(data) { + var bouw=''; + var gotsome=false; + $.each(data, function(a,b) { + bouw=bouw+(""); + gotsome=true; + }); + + if(!gotsome) + bouw = bouw + ''; + + bouw=bouw+"
Kernel-based dyn blocked netmaskSecondsBlocks
"+a+""+b.seconds+""+b.blocks+"
No eBPF blocks active
"; + $("#ebpfblock").html(bouw); + + }}); };