void BPFFilter::block(const ComboAddress& addr)
{
- std::lock_guard<std::mutex> lock(d_mutex);
-
uint64_t counter = 0;
int res = 0;
- if (addr.sin4.sin_family == AF_INET) {
+ if (addr.isIPv4()) {
uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
if (d_v4Count >= d_maxV4) {
throw std::runtime_error("Table full when trying to block " + addr.toString());
}
+ std::lock_guard<std::mutex> lock(d_mutex);
res = bpf_lookup_elem(d_v4map.fd, &key, &counter);
if (res != -1) {
throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
d_v4Count++;
}
}
- else if (addr.sin4.sin_family == AF_INET6) {
+ else if (addr.isIPv6()) {
uint8_t key[16];
static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
for (size_t idx = 0; idx < sizeof(key); idx++) {
throw std::runtime_error("Table full when trying to block " + addr.toString());
}
+ std::lock_guard<std::mutex> lock(d_mutex);
res = bpf_lookup_elem(d_v6map.fd, &key, &counter);
if (res != -1) {
throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
void BPFFilter::unblock(const ComboAddress& addr)
{
- std::lock_guard<std::mutex> lock(d_mutex);
-
int res = 0;
- if (addr.sin4.sin_family == AF_INET) {
+ if (addr.isIPv4()) {
uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
+ std::lock_guard<std::mutex> lock(d_mutex);
res = bpf_delete_elem(d_v4map.fd, &key);
if (res == 0) {
d_v4Count--;
}
}
- else if (addr.sin4.sin_family == AF_INET6) {
+ else if (addr.isIPv6()) {
uint8_t key[16];
static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
for (size_t idx = 0; idx < sizeof(key); idx++) {
key[idx] = addr.sin6.sin6_addr.s6_addr[idx];
}
+ std::lock_guard<std::mutex> lock(d_mutex);
res = bpf_delete_elem(d_v6map.fd, key);
if (res == 0) {
d_v6Count--;
std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
{
std::vector<std::pair<ComboAddress, uint64_t> > result;
- std::lock_guard<std::mutex> lock(d_mutex);
+ result.reserve(d_v4Count + d_v6Count);
- uint32_t v4Key = 0;
- uint32_t nextV4Key;
- uint64_t value;
- int res = bpf_get_next_key(d_v4map.fd, &v4Key, &nextV4Key);
sockaddr_in v4Addr;
memset(&v4Addr, 0, sizeof(v4Addr));
v4Addr.sin_family = AF_INET;
- while (res == 0) {
- v4Key = nextV4Key;
- if (bpf_lookup_elem(d_v4map.fd, &v4Key, &value) == 0) {
- v4Addr.sin_addr.s_addr = ntohl(v4Key);
- result.push_back(make_pair(ComboAddress(&v4Addr), value));
- }
-
- res = bpf_get_next_key(d_v4map.fd, &v4Key, &nextV4Key);
- }
+ uint32_t v4Key = 0;
+ uint32_t nextV4Key;
+ uint64_t value;
uint8_t v6Key[16];
uint8_t nextV6Key[16];
v6Key[idx] = 0;
}
+ std::lock_guard<std::mutex> lock(d_mutex);
+ int res = bpf_get_next_key(d_v4map.fd, &v4Key, &nextV4Key);
+
+ while (res == 0) {
+ v4Key = nextV4Key;
+ if (bpf_lookup_elem(d_v4map.fd, &v4Key, &value) == 0) {
+ v4Addr.sin_addr.s_addr = ntohl(v4Key);
+ result.push_back(make_pair(ComboAddress(&v4Addr), value));
+ }
+
+ res = bpf_get_next_key(d_v4map.fd, &v4Key, &nextV4Key);
+ }
+
res = bpf_get_next_key(d_v6map.fd, &v6Key, &nextV6Key);
while (res == 0) {
std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats()
{
std::vector<std::tuple<DNSName, uint16_t, uint64_t> > result;
- std::lock_guard<std::mutex> lock(d_mutex);
+ result.reserve(d_qNamesCount);
struct QNameKey key = { { 0 } };
struct QNameKey nextKey = { { 0 } };
struct QNameValue value;
+ std::lock_guard<std::mutex> lock(d_mutex);
+
int res = bpf_get_next_key(d_qnamemap.fd, &key, &nextKey);
while (res == 0) {
}
return result;
}
+
+uint64_t BPFFilter::getHits(const ComboAddress& requestor)
+{
+ uint64_t counter = 0;
+ if (requestor.isIPv4()) {
+ uint32_t key = htonl(requestor.sin4.sin_addr.s_addr);
+
+ std::lock_guard<std::mutex> lock(d_mutex);
+ int res = bpf_lookup_elem(d_v4map.fd, &key, &counter);
+ if (res == 0) {
+ return counter;
+ }
+ }
+ else if (requestor.isIPv6()) {
+ uint8_t key[16];
+ static_assert(sizeof(requestor.sin6.sin6_addr.s6_addr) == sizeof(key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
+ for (size_t idx = 0; idx < sizeof(key); idx++) {
+ key[idx] = requestor.sin6.sin6_addr.s6_addr[idx];
+ }
+
+ std::lock_guard<std::mutex> lock(d_mutex);
+ int res = bpf_lookup_elem(d_v6map.fd, &key, &counter);
+ if (res == 0) {
+ return counter;
+ }
+ }
+
+ return 0;
+}
+
+#else
+
+BPFFilter::BPFFilter(uint32_t maxV4Addresses, uint32_t maxV6Addresses, uint32_t maxQNames): d_maxV4(maxV4Addresses), d_maxV6(maxV6Addresses), d_maxQNames(maxQNames)
+{
+}
+
+void BPFFilter::addSocket(int sock)
+{
+ (void) sock;
+ throw std::runtime_error("eBPF support not enabled");
+}
+
+void BPFFilter::removeSocket(int sock)
+{
+ (void) sock;
+ throw std::runtime_error("eBPF support not enabled");
+}
+
+void BPFFilter::block(const ComboAddress& addr)
+{
+ (void) addr;
+ throw std::runtime_error("eBPF support not enabled");
+}
+
+void BPFFilter::unblock(const ComboAddress& addr)
+{
+ (void) addr;
+ throw std::runtime_error("eBPF support not enabled");
+}
+
+void BPFFilter::block(const DNSName& qname, uint16_t qtype)
+{
+ (void) qname;
+ (void) qtype;
+ throw std::runtime_error("eBPF support not enabled");
+}
+
+void BPFFilter::unblock(const DNSName& qname, uint16_t qtype)
+{
+ (void) qname;
+ (void) qtype;
+ throw std::runtime_error("eBPF support not enabled");
+}
+
+std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
+{
+ std::vector<std::pair<ComboAddress, uint64_t> > result;
+ return result;
+}
+
+std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats()
+{
+ std::vector<std::tuple<DNSName, uint16_t, uint64_t> > result;
+ return result;
+}
+
+uint64_t BPFFilter::getHits(const ComboAddress& requestor)
+{
+ (void) requestor;
+ return 0;
+}
#endif /* HAVE_EBPF */
#include "iputils.hh"
-#ifdef HAVE_EBPF
-
class BPFFilter
{
public:
void unblock(const DNSName& qname, uint16_t qtype=255);
std::vector<std::pair<ComboAddress, uint64_t> > getAddrStats();
std::vector<std::tuple<DNSName, uint16_t, uint64_t> > getQNameStats();
+ uint64_t getHits(const ComboAddress& requestor);
+
private:
struct FDWrapper
{
FDWrapper d_mainfilter;
FDWrapper d_qnamefilter;
};
-
-#endif /* HAVE_EBPF */
*/
#include "dnsdist-dynbpf.hh"
-#ifdef HAVE_EBPF
-
bool DynBPFFilter::block(const ComboAddress& addr, const struct timespec& until)
{
bool inserted = false;
return result;
}
-#endif /* HAVE_EBPF */
#include "bpf-filter.hh"
#include "iputils.hh"
-#ifdef HAVE_EBPF
-
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
NetmaskGroup d_excludedSubnets;
};
-#endif /* HAVE_EBPF */
setLuaNoSideEffect();
std::string res;
if (bpf) {
- std::vector<std::pair<ComboAddress, uint64_t> > stats = bpf->getAddrStats();
+ auto stats = bpf->getAddrStats();
for (const auto& value : stats) {
if (value.first.sin4.sin_family == AF_INET) {
res += value.first.toString() + ": " + std::to_string(value.second) + "\n";
res += "[" + value.first.toString() + "]: " + std::to_string(value.second) + "\n";
}
}
- std::vector<std::tuple<DNSName, uint16_t, uint64_t> > qstats = bpf->getQNameStats();
+ auto qstats = bpf->getQNameStats();
for (const auto& value : qstats) {
res += std::get<0>(value).toString() + " " + std::to_string(std::get<1>(value)) + ": " + std::to_string(std::get<2>(value)) + "\n";
}
g_outputBuffer = (fmt % "What" % "Seconds" % "Blocks" % "Warning" % "Action" % "Reason").str();
for(const auto& e: slow) {
if (now < e.second.until) {
- g_outputBuffer+= (fmt % e.first.toString() % (e.second.until.tv_sec - now.tv_sec) % e.second.blocks % (e.second.warning ? "true" : "false") % DNSAction::typeToString(e.second.action != DNSAction::Action::None ? e.second.action : g_dynBlockAction) % e.second.reason).str();
+ uint64_t counter = e.second.blocks;
+ if (g_defaultBPFFilter && e.second.bpf) {
+ counter += g_defaultBPFFilter->getHits(e.first.getNetwork());
+ }
+ g_outputBuffer+= (fmt % e.first.toString() % (e.second.until.tv_sec - now.tv_sec) % counter % (e.second.warning ? "true" : "false") % DNSAction::typeToString(e.second.action != DNSAction::Action::None ? e.second.action : g_dynBlockAction) % e.second.reason).str();
}
}
auto slow2 = g_dynblockSMT.getCopy();
std::vector<std::shared_ptr<TLSFrontend>> g_tlslocals;
std::vector<std::shared_ptr<DOHFrontend>> g_dohlocals;
std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
-#ifdef HAVE_EBPF
-shared_ptr<BPFFilter> g_defaultBPFFilter;
+
+shared_ptr<BPFFilter> g_defaultBPFFilter{nullptr};
std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
-#endif /* HAVE_EBPF */
+
std::vector<std::unique_ptr<ClientState>> g_frontends;
GlobalStateHolder<pools_t> g_pools;
size_t g_udpVectorSize{1};
{
}
- DynBlock(const DynBlock& rhs): reason(rhs.reason), domain(rhs.domain), until(rhs.until), action(rhs.action), warning(rhs.warning)
+ DynBlock(const DynBlock& rhs): reason(rhs.reason), domain(rhs.domain), until(rhs.until), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf)
{
blocks.store(rhs.blocks);
}
- DynBlock(DynBlock&& rhs): reason(std::move(rhs.reason)), domain(std::move(rhs.domain)), until(rhs.until), action(rhs.action), warning(rhs.warning)
+ DynBlock(DynBlock&& rhs): reason(std::move(rhs.reason)), domain(std::move(rhs.domain)), until(rhs.until), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf)
{
blocks.store(rhs.blocks);
}
action = rhs.action;
blocks.store(rhs.blocks);
warning = rhs.warning;
+ bpf = rhs.bpf;
return *this;
}
action = rhs.action;
blocks.store(rhs.blocks);
warning = rhs.warning;
+ bpf = rhs.bpf;
return *this;
}
mutable std::atomic<unsigned int> blocks;
DNSAction::Action action{DNSAction::Action::None};
bool warning{false};
+ bool bpf{false};
};
extern GlobalStateHolder<NetmaskTree<DynBlock>> g_dynblockNMG;
return result;
}
-#ifdef HAVE_EBPF
shared_ptr<BPFFilter> d_filter;
void detachFilter()
bpf->addSocket(getSocket());
d_filter = bpf;
}
-#endif /* HAVE_EBPF */
void updateTCPMetrics(size_t nbQueries, uint64_t durationMs)
{
extern bool g_preserveTrailingData;
extern bool g_allowEmptyResponse;
-#ifdef HAVE_EBPF
extern shared_ptr<BPFFilter> g_defaultBPFFilter;
extern std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
-#endif /* HAVE_EBPF */
struct LocalHolders
{
const auto& got = blocks->lookup(Netmask(requestor));
bool expired = false;
bool wasWarning = false;
+ bool bpf = false;
if (got) {
+ bpf = got->second.bpf;
+
if (warning && !got->second.warning) {
/* we have an existing entry which is not a warning,
don't override it */
DynBlock db{rule.d_blockReason, until, DNSName(), warning ? DNSAction::Action::NoOp : rule.d_action};
db.blocks = count;
db.warning = warning;
- if (!d_beQuiet && (!got || expired || wasWarning)) {
- warnlog("Inserting %sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
+ if (!got || expired || wasWarning) {
+ if (g_defaultBPFFilter) {
+ try {
+ g_defaultBPFFilter->block(requestor);
+ bpf = true;
+ }
+ catch (const std::exception& e) {
+ vinfolog("Unable to insert eBPF dynamic block for %s, falling back to regular dynamic block: %s", requestor.toString(), e.what());
+ }
+ }
+
+ if (!d_beQuiet) {
+ warnlog("Inserting %sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
+ }
}
- blocks->insert(Netmask(requestor)).second = db;
+
+ db.bpf = bpf;
+
+ blocks->insert(Netmask(requestor)).second = std::move(db);
+
updated = true;
}
for (const auto& entry : *blocks) {
if (!(now < entry.second.until)) {
toRemove.push_back(entry.first);
+ if (g_defaultBPFFilter && entry.second.bpf) {
+ try {
+ g_defaultBPFFilter->unblock(entry.first.getNetwork());
+ }
+ catch (const std::exception& e) {
+ vinfolog("Error while removing eBPF dynamic block for %s: %s", entry.first.toString(), e.what());
+ }
+ }
}
}
if (!toRemove.empty()) {
auto blocks = g_dynblockNMG.getLocal();
for (const auto& entry : *blocks) {
auto& topsForReason = results[entry.second.reason];
- if (topsForReason.size() < topN || topsForReason.front().second < entry.second.blocks) {
- auto newEntry = std::make_pair(entry.first, entry.second.blocks.load());
+ uint64_t value = entry.second.blocks.load();
+
+ if (g_defaultBPFFilter && entry.second.bpf) {
+ value += g_defaultBPFFilter->getHits(entry.first.getNetwork());
+ }
+
+ if (topsForReason.size() < topN || topsForReason.front().second < value) {
+ auto newEntry = std::make_pair(entry.first, value);
if (topsForReason.size() >= topN) {
topsForReason.pop_front();
They can be unregistered at a later point using the :func:`unregisterDynBPFFilter` function.
+Since 1.6.0, the default BPF filter set via :func:`setDefaultBPFFilter` will automatically get used when a dynamic block is inserted via a :ref:`DynBlockRulesGroup`.
+
This feature has been successfully tested on Arch Linux, Arch Linux ARM, Fedora Core 23 and Ubuntu Xenial
-- If the query rate raises above 300 qps for 10 seconds, we'll block the client for 60s.
dbr:setQueryRate(300, 10, "Exceeded query rate", 60, DNSAction.Drop, 100)
+Since 1.6.0, if a default eBPF filter has been set via :func:`setDefaultBPFFilter` dnsdist will automatically try to use it when a dynamic block is inserted via a :ref:`DynBlockRulesGroup`. eBPF blocks are applied in kernel space and are much more efficient than user space ones. Note that a regular block is also inserted so that any failure will result in a regular block being used instead of the eBPF one.
Rings g_rings;
GlobalStateHolder<NetmaskTree<DynBlock>> g_dynblockNMG;
GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
+shared_ptr<BPFFilter> g_defaultBPFFilter{nullptr};
BOOST_AUTO_TEST_SUITE(dnsdistdynblocks_hh)