* 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.
* 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()`
}
}
+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<std::mutex> lock(d_mutex);
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);
{ "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" },
{ "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" },
}
}
+std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > DynBPFFilter::getAddrStats()
+{
+ std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > 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 */
}
void block(const ComboAddress& addr, const struct timespec& until);
void purgeExpired(const struct timespec& now);
+ std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > getAddrStats();
private:
struct BlockEntry
{
g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)()>("attachToAllBinds", [](std::shared_ptr<BPFFilter> 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<void(ClientState::*)()>("detachFilter", [](ClientState& frontend) {
+ frontend.detachFilter();
+ });
+
g_lua.registerFunction<void(ClientState::*)(std::shared_ptr<BPFFilter>)>("attachFilter", [](ClientState& frontend, std::shared_ptr<BPFFilter> bpf) {
if (bpf) {
- bpf->addSocket(frontend.udpFD != -1 ? frontend.udpFD : frontend.tcpFD);
+ frontend.attachFilter(bpf);
}
});
return std::make_shared<DynBPFFilter>(bpf);
});
+ g_lua.writeFunction("registerDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
+ if (dbpf) {
+ g_dynBPFFilters.push_back(dbpf);
+ }
+ });
+
+ g_lua.writeFunction("unregisterDynBPFFilter", [](std::shared_ptr<DynBPFFilter> 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<void(std::shared_ptr<DynBPFFilter>::*)(const ComboAddress& addr, boost::optional<int> seconds)>("block", [](std::shared_ptr<DynBPFFilter> dbpf, const ComboAddress& addr, boost::optional<int> seconds) {
if (dbpf) {
struct timespec until;
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});
}
}
+ 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<std::tuple<ComboAddress, uint64_t, struct timespec> > 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";
#endif
#ifdef HAVE_EBPF
shared_ptr<BPFFilter> g_defaultBPFFilter;
+std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
#endif /* HAVE_EBPF */
vector<ClientState *> g_frontends;
GlobalStateHolder<pools_t> g_pools;
#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 */
#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 */
}
#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 */
}
#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 */
std::atomic<uint64_t> queries{0};
int udpFD{-1};
int tcpFD{-1};
+
+ int getSocket() const
+ {
+ return udpFD != -1 ? udpFD : tcpFD;
+ }
+
+#ifdef HAVE_EBPF
+ shared_ptr<BPFFilter> d_filter;
+
+ void detachFilter()
+ {
+ if (d_filter) {
+ d_filter->removeSocket(getSocket());
+ d_filter = nullptr;
+ }
+ }
+
+ void attachFilter(shared_ptr<BPFFilter> bpf)
+ {
+ detachFilter();
+
+ bpf->addSocket(getSocket());
+ d_filter = bpf;
+ }
+#endif /* HAVE_EBPF */
};
class TCPClientCollection {
#ifdef HAVE_EBPF
extern shared_ptr<BPFFilter> g_defaultBPFFilter;
+extern std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
#endif /* HAVE_EBPF */
struct dnsheader;
<tr>
<td><div id="dynblock"></div></td>
</tr>
+ <tr>
+ <td><div id="ebpfblock"></div></td>
+ </tr>
</table>
</td>
}});
+ $.ajax({ url: 'jsonstat?command=ebpfblocklist', type: 'GET', dataType: 'json', jsonp: false,
+ success: function(data) {
+ var bouw='<table width="100%"><tr align=left><th>Kernel-based dyn blocked netmask</th><th>Seconds</th></th><th>Blocks</th></tr>';
+ var gotsome=false;
+ $.each(data, function(a,b) {
+ bouw=bouw+("<tr><td>"+a+"</td><td>"+b.seconds+"</td><td>"+b.blocks+"</td></tr>");
+ gotsome=true;
+ });
+
+ if(!gotsome)
+ bouw = bouw + '<tr><td align="center" colspan="4"><font color="#aaaaaa">No eBPF blocks active</font></td></tr>';
+
+ bouw=bouw+"</table>";
+ $("#ebpfblock").html(bouw);
+
+ }});
};