From: Remi Gacogne Date: Fri, 27 Nov 2020 10:18:27 +0000 (+0100) Subject: dnsdist: Use an eBPF filter for Dynamic blocks when available X-Git-Tag: rec-4.5.0-alpha1~39^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0da9ec8d9815ceed8cf4a6d23c7b47ef79c5a235;p=thirdparty%2Fpdns.git dnsdist: Use an eBPF filter for Dynamic blocks when available --- diff --git a/pdns/bpf-filter.cc b/pdns/bpf-filter.cc index 96b7acdc5e..7e833f42fa 100644 --- a/pdns/bpf-filter.cc +++ b/pdns/bpf-filter.cc @@ -214,16 +214,15 @@ void BPFFilter::removeSocket(int sock) void BPFFilter::block(const ComboAddress& addr) { - std::lock_guard 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 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()); @@ -234,7 +233,7 @@ void BPFFilter::block(const ComboAddress& addr) 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++) { @@ -245,6 +244,7 @@ void BPFFilter::block(const ComboAddress& addr) throw std::runtime_error("Table full when trying to block " + addr.toString()); } + std::lock_guard 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()); @@ -263,23 +263,23 @@ void BPFFilter::block(const ComboAddress& addr) void BPFFilter::unblock(const ComboAddress& addr) { - std::lock_guard 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 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 lock(d_mutex); res = bpf_delete_elem(d_v6map.fd, key); if (res == 0) { d_v6Count--; @@ -355,25 +355,15 @@ void BPFFilter::unblock(const DNSName& qname, uint16_t qtype) std::vector > BPFFilter::getAddrStats() { std::vector > result; - std::lock_guard 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]; @@ -386,6 +376,19 @@ std::vector > BPFFilter::getAddrStats() v6Key[idx] = 0; } + std::lock_guard 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) { @@ -404,12 +407,14 @@ std::vector > BPFFilter::getAddrStats() std::vector > BPFFilter::getQNameStats() { std::vector > result; - std::lock_guard lock(d_mutex); + result.reserve(d_qNamesCount); struct QNameKey key = { { 0 } }; struct QNameKey nextKey = { { 0 } }; struct QNameValue value; + std::lock_guard lock(d_mutex); + int res = bpf_get_next_key(d_qnamemap.fd, &key, &nextKey); while (res == 0) { @@ -422,4 +427,95 @@ std::vector > BPFFilter::getQNameStats() } 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 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 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 > BPFFilter::getAddrStats() +{ + std::vector > result; + return result; +} + +std::vector > BPFFilter::getQNameStats() +{ + std::vector > result; + return result; +} + +uint64_t BPFFilter::getHits(const ComboAddress& requestor) +{ + (void) requestor; + return 0; +} #endif /* HAVE_EBPF */ diff --git a/pdns/bpf-filter.hh b/pdns/bpf-filter.hh index f01d329d56..e14a517dcc 100644 --- a/pdns/bpf-filter.hh +++ b/pdns/bpf-filter.hh @@ -26,8 +26,6 @@ #include "iputils.hh" -#ifdef HAVE_EBPF - class BPFFilter { public: @@ -40,6 +38,8 @@ public: void unblock(const DNSName& qname, uint16_t qtype=255); std::vector > getAddrStats(); std::vector > getQNameStats(); + uint64_t getHits(const ComboAddress& requestor); + private: struct FDWrapper { @@ -65,5 +65,3 @@ private: FDWrapper d_mainfilter; FDWrapper d_qnamefilter; }; - -#endif /* HAVE_EBPF */ diff --git a/pdns/dnsdist-dynbpf.cc b/pdns/dnsdist-dynbpf.cc index 74f78f12ed..e4c2c0d90b 100644 --- a/pdns/dnsdist-dynbpf.cc +++ b/pdns/dnsdist-dynbpf.cc @@ -21,8 +21,6 @@ */ #include "dnsdist-dynbpf.hh" -#ifdef HAVE_EBPF - bool DynBPFFilter::block(const ComboAddress& addr, const struct timespec& until) { bool inserted = false; @@ -84,4 +82,3 @@ std::vector > DynBPFFilter:: return result; } -#endif /* HAVE_EBPF */ diff --git a/pdns/dnsdist-dynbpf.hh b/pdns/dnsdist-dynbpf.hh index cd69c5d13e..5cfa2e028e 100644 --- a/pdns/dnsdist-dynbpf.hh +++ b/pdns/dnsdist-dynbpf.hh @@ -27,8 +27,6 @@ #include "bpf-filter.hh" #include "iputils.hh" -#ifdef HAVE_EBPF - #include #include @@ -76,4 +74,3 @@ private: NetmaskGroup d_excludedSubnets; }; -#endif /* HAVE_EBPF */ diff --git a/pdns/dnsdist-lua-bindings.cc b/pdns/dnsdist-lua-bindings.cc index 50e95bf60a..89c075f22f 100644 --- a/pdns/dnsdist-lua-bindings.cc +++ b/pdns/dnsdist-lua-bindings.cc @@ -396,7 +396,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client) setLuaNoSideEffect(); std::string res; if (bpf) { - std::vector > 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"; @@ -405,7 +405,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client) res += "[" + value.first.toString() + "]: " + std::to_string(value.second) + "\n"; } } - std::vector > 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"; } diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 6de9aed771..8e8158fafa 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -1149,7 +1149,11 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) 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(); diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index e414ddbd29..05f58e82f9 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -99,10 +99,10 @@ string g_outputBuffer; std::vector> g_tlslocals; std::vector> g_dohlocals; std::vector> g_dnsCryptLocals; -#ifdef HAVE_EBPF -shared_ptr g_defaultBPFFilter; + +shared_ptr g_defaultBPFFilter{nullptr}; std::vector > g_dynBPFFilters; -#endif /* HAVE_EBPF */ + std::vector> g_frontends; GlobalStateHolder g_pools; size_t g_udpVectorSize{1}; diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index 81c13458f6..7c0699f2d0 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -207,12 +207,12 @@ struct DynBlock { } - 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); } @@ -225,6 +225,7 @@ struct DynBlock action = rhs.action; blocks.store(rhs.blocks); warning = rhs.warning; + bpf = rhs.bpf; return *this; } @@ -236,6 +237,7 @@ struct DynBlock action = rhs.action; blocks.store(rhs.blocks); warning = rhs.warning; + bpf = rhs.bpf; return *this; } @@ -245,6 +247,7 @@ struct DynBlock mutable std::atomic blocks; DNSAction::Action action{DNSAction::Action::None}; bool warning{false}; + bool bpf{false}; }; extern GlobalStateHolder> g_dynblockNMG; @@ -734,7 +737,6 @@ struct ClientState return result; } -#ifdef HAVE_EBPF shared_ptr d_filter; void detachFilter() @@ -752,7 +754,6 @@ struct ClientState bpf->addSocket(getSocket()); d_filter = bpf; } -#endif /* HAVE_EBPF */ void updateTCPMetrics(size_t nbQueries, uint64_t durationMs) { @@ -1150,10 +1151,8 @@ extern size_t g_udpVectorSize; extern bool g_preserveTrailingData; extern bool g_allowEmptyResponse; -#ifdef HAVE_EBPF extern shared_ptr g_defaultBPFFilter; extern std::vector > g_dynBPFFilters; -#endif /* HAVE_EBPF */ struct LocalHolders { diff --git a/pdns/dnsdistdist/dnsdist-dynblocks.cc b/pdns/dnsdistdist/dnsdist-dynblocks.cc index 574cd4c4c2..d689fdb3c4 100644 --- a/pdns/dnsdistdist/dnsdist-dynblocks.cc +++ b/pdns/dnsdistdist/dnsdist-dynblocks.cc @@ -176,8 +176,11 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optional 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 */ @@ -205,10 +208,26 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optional 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; } @@ -371,6 +390,14 @@ void DynBlockMaintenance::purgeExpired(const struct timespec& now) 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()) { @@ -410,8 +437,14 @@ std::map>> DynBlockMaint 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(); diff --git a/pdns/dnsdistdist/docs/advanced/ebpf.rst b/pdns/dnsdistdist/docs/advanced/ebpf.rst index 1b853145cc..75d59cf023 100644 --- a/pdns/dnsdistdist/docs/advanced/ebpf.rst +++ b/pdns/dnsdistdist/docs/advanced/ebpf.rst @@ -60,4 +60,6 @@ The dynamic eBPF blocks and the number of queries they blocked can be seen in th 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 diff --git a/pdns/dnsdistdist/docs/guides/dynblocks.rst b/pdns/dnsdistdist/docs/guides/dynblocks.rst index 8b3ed9186f..4936cdc465 100644 --- a/pdns/dnsdistdist/docs/guides/dynblocks.rst +++ b/pdns/dnsdistdist/docs/guides/dynblocks.rst @@ -85,3 +85,4 @@ action is applied. -- 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. diff --git a/pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc b/pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc index caee5adcff..6de07d5ff9 100644 --- a/pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc +++ b/pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc @@ -11,6 +11,7 @@ Rings g_rings; GlobalStateHolder> g_dynblockNMG; GlobalStateHolder> g_dynblockSMT; +shared_ptr g_defaultBPFFilter{nullptr}; BOOST_AUTO_TEST_SUITE(dnsdistdynblocks_hh)