From: Remi Gacogne Date: Fri, 10 Nov 2023 14:51:52 +0000 (+0100) Subject: dnsdist: Add a better, modern interface for adding DynBlocks from Lua X-Git-Tag: rec-5.0.0-rc1~31^2~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c9551e0d547384cefad5c1ea572c4a2e073fc17d;p=thirdparty%2Fpdns.git dnsdist: Add a better, modern interface for adding DynBlocks from Lua --- diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index 9dfe8159fe..18af1c4a51 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -471,6 +471,7 @@ const std::vector g_consoleKeywords{ { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters" }, { "addDOHLocal", true, "addr, certFile, keyFile [, urls [, vars]]", "listen to incoming DNS over HTTPS queries on the specified address using the specified certificate and key. The last two parameters are tables" }, { "addDOQLocal", true, "addr, certFile, keyFile [, vars]", "listen to incoming DNS over QUIC queries on the specified address using the specified certificate and key. The last parameter is a table" }, + { "addDynamicBlock", true, "address, message[, action [, seconds [clientIPMask [, clientIPPortMask]]]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" }, { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" }, { "addDynBlockSMT", true, "names, message[, seconds [, action]]", "block the set of names with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" }, { "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}]", "add `addr` to the list of addresses we listen on" }, diff --git a/pdns/dnsdist-dynblocks.hh b/pdns/dnsdist-dynblocks.hh index 9228ce2024..25c1036c23 100644 --- a/pdns/dnsdist-dynblocks.hh +++ b/pdns/dnsdist-dynblocks.hh @@ -449,4 +449,8 @@ private: static size_t s_topN; }; +namespace dnsdist::DynamicBlocks { +bool addOrRefreshBlock(NetmaskTree& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const std::string& reason, unsigned int duration, DNSAction::Action action, bool warning, bool beQuiet); +bool addOrRefreshBlockSMT(SuffixMatchTree& blocks, const struct timespec& now, const DNSName& name, const std::string& reason, unsigned int duration, DNSAction::Action action, bool beQuiet); +} #endif /* DISABLE_DYNBLOCKS */ diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 2b47f26007..c9fdeb72e5 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -1468,43 +1468,6 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) g_dynblockNMG.setState(slow); }); - luaCtx.writeFunction("addDynBlockSMT", - [](const LuaArray& names, const std::string& msg, boost::optional seconds, boost::optional action) { - if (names.empty()) { - return; - } - setLuaSideEffect(); - auto slow = g_dynblockSMT.getCopy(); - struct timespec until, now; - gettime(&now); - until = now; - int actualSeconds = seconds ? *seconds : 10; - until.tv_sec += actualSeconds; - - for (const auto& capair : names) { - unsigned int count = 0; - DNSName domain(capair.second); - domain.makeUsLowerCase(); - auto got = slow.lookup(domain); - bool expired = false; - if (got) { - if (until < got->until) // had a longer policy - continue; - if (now < got->until) // only inherit count on fresh query we are extending - count = got->blocks; - else - expired = true; - } - - DynBlock db{msg, until, domain, (action ? *action : DNSAction::Action::None)}; - db.blocks = count; - if (!got || expired) - warnlog("Inserting dynamic block for %s for %d seconds: %s", domain, actualSeconds, msg); - slow.add(domain, std::move(db)); - } - g_dynblockSMT.setState(slow); - }); - luaCtx.writeFunction("setDynBlocksAction", [](DNSAction::Action action) { if (!checkConfigurationTime("setDynBlocksAction")) { return; @@ -1519,6 +1482,65 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) }); #endif /* DISABLE_DEPRECATED_DYNBLOCK */ + luaCtx.writeFunction("addDynBlockSMT", + [](const LuaArray& names, const std::string& msg, boost::optional seconds, boost::optional action) { + if (names.empty()) { + return; + } + setLuaSideEffect(); + struct timespec now; + gettime(&now); + unsigned int actualSeconds = seconds ? *seconds : 10; + + bool needUpdate = false; + auto slow = g_dynblockSMT.getCopy(); + for (const auto& capair : names) { + DNSName domain(capair.second); + domain.makeUsLowerCase(); + + if (dnsdist::DynamicBlocks::addOrRefreshBlockSMT(slow, now, domain, msg, actualSeconds, action ? *action : DNSAction::Action::None, false)) { + needUpdate = true; + } + } + + if (needUpdate) { + g_dynblockSMT.setState(slow); + } + }); + + luaCtx.writeFunction("addDynamicBlock", + [](const boost::variant& clientIP, const std::string& msg, const boost::optional action, const boost::optional seconds, boost::optional clientIPMask, boost::optional clientIPPortMask) { + setLuaSideEffect(); + + ComboAddress clientIPCA; + if (clientIP.type() == typeid(ComboAddress)) { + clientIPCA = boost::get(clientIP); + } + else { + auto clientIPStr = boost::get(clientIP); + try { + clientIPCA = ComboAddress(clientIPStr); + } + catch (const std::exception& exp) { + errlog("addDynamicBlock: Unable to parse '%s': %s", clientIPStr, exp.what()); + return; + } + catch (const PDNSException& exp) { + errlog("addDynamicBlock: Unable to parse '%s': %s", clientIPStr, exp.reason); + return; + } + } + AddressAndPortRange target(clientIPCA, clientIPMask ? *clientIPMask : (clientIPCA.isIPv4() ? 32 : 128), clientIPPortMask ? *clientIPPortMask : 0); + unsigned int actualSeconds = seconds ? *seconds : 10; + + struct timespec now; + gettime(&now); + auto slow = g_dynblockNMG.getCopy(); + if (dnsdist::DynamicBlocks::addOrRefreshBlock(slow, now, target, msg, actualSeconds, action ? *action : DNSAction::Action::None, false, false)) { + g_dynblockNMG.setState(slow); + } + }); + luaCtx.writeFunction("setDynBlocksPurgeInterval", [](uint64_t interval) { DynBlockMaintenance::s_expiredDynBlocksPurgeInterval = interval; }); diff --git a/pdns/dnsdistdist/dnsdist-dynblocks.cc b/pdns/dnsdistdist/dnsdist-dynblocks.cc index 928aff7f9b..fbb2fefa2f 100644 --- a/pdns/dnsdistdist/dnsdist-dynblocks.cc +++ b/pdns/dnsdistdist/dnsdist-dynblocks.cc @@ -193,32 +193,27 @@ static DNSAction::Action getActualAction(const DynBlock& block) return g_dynBlockAction; } -void DynBlockRulesGroup::addOrRefreshBlock(boost::optional >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning) +namespace dnsdist::DynamicBlocks { + bool addOrRefreshBlock(NetmaskTree& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const std::string& reason, unsigned int duration, DNSAction::Action action, bool warning, bool beQuiet) { - /* network exclusions are address-based only (no port) */ - if (d_excludedSubnets.match(requestor.getNetwork())) { - /* do not add a block for excluded subnets */ - return; - } - - if (!blocks) { - blocks = g_dynblockNMG.getCopy(); - } - struct timespec until = now; - until.tv_sec += rule.d_blockDuration; unsigned int count = 0; - const auto& got = blocks->lookup(requestor); bool expired = false; bool wasWarning = false; bool bpf = false; + struct timespec until = now; + until.tv_sec += duration; + DynBlock db{reason, until, DNSName(), warning ? DNSAction::Action::NoOp : action}; + db.warning = warning; + + const auto& got = blocks.lookup(requestor); 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 */ - return; + return false; } else if (!warning && got->second.warning) { wasWarning = true; @@ -226,7 +221,7 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optionalsecond.until) { // had a longer policy - return; + return false; } } @@ -239,9 +234,8 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optionalinsert(requestor).second = std::move(db); + blocks.insert(requestor).second = std::move(db); - updated = true; + return true; } -void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated) +bool addOrRefreshBlockSMT(SuffixMatchTree& blocks, const struct timespec& now, const DNSName& name, const std::string& reason, unsigned int duration, DNSAction::Action action, bool beQuiet) { - if (d_excludedDomains.check(name)) { - /* do not add a block for excluded domains */ - return; - } - struct timespec until = now; - until.tv_sec += rule.d_blockDuration; + until.tv_sec += duration; unsigned int count = 0; + DynBlock db{reason, until, name.makeLowerCase(), action}; /* be careful, if you try to insert a longer suffix lookup() might return a shorter one if it is already in the tree as a final node */ @@ -294,7 +284,7 @@ void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree& blocks, if (got) { if (until < got->until) { // had a longer policy - return; + return false; } if (now < got->until) { @@ -306,14 +296,39 @@ void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree& blocks, } } - DynBlock db{rule.d_blockReason, until, name.makeLowerCase(), rule.d_action}; db.blocks = count; - if (!d_beQuiet && (!got || expired)) { - warnlog("Inserting dynamic block for %s for %d seconds: %s", name, rule.d_blockDuration, rule.d_blockReason); + if (!beQuiet && (!got || expired)) { + warnlog("Inserting dynamic block for %s for %d seconds: %s", name, duration, reason); } blocks.add(name, std::move(db)); - updated = true; + return true; +} +} + +void DynBlockRulesGroup::addOrRefreshBlock(boost::optional >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning) +{ + /* network exclusions are address-based only (no port) */ + if (d_excludedSubnets.match(requestor.getNetwork())) { + /* do not add a block for excluded subnets */ + return; + } + + if (!blocks) { + blocks = g_dynblockNMG.getCopy(); + } + + updated = dnsdist::DynamicBlocks::addOrRefreshBlock(*blocks, now, requestor, rule.d_blockReason, rule.d_blockDuration, rule.d_action, warning, d_beQuiet); +} + +void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated) +{ + if (d_excludedDomains.check(name)) { + /* do not add a block for excluded domains */ + return; + } + + updated = dnsdist::DynamicBlocks::addOrRefreshBlockSMT(blocks, now, name, rule.d_blockReason, rule.d_blockDuration, rule.d_action, d_beQuiet); } void DynBlockRulesGroup::processQueryRules(counts_t& counts, const struct timespec& now) @@ -332,7 +347,7 @@ void DynBlockRulesGroup::processQueryRules(counts_t& counts, const struct timesp for (const auto& shard : g_rings.d_shards) { auto rl = shard->queryRing.lock(); - for(const auto& c : *rl) { + for (const auto& c : *rl) { if (now < c.when) { continue; } diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index 6d59930d51..95a65724a4 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -1407,6 +1407,22 @@ Status, Statistics and More Dynamic Blocks -------------- +.. function:: addDynamicBlock(address, message[, action [, seconds [, clientIPMask [, clientIPPortMask]]]]) + + .. versionadded:: 1.9.0 + + Manually block an IP address or range with ``message`` for (optionally) a number of seconds. + The default number of seconds to block for is 10. + + :param address: A :class:`ComboAddress` or string representing an IPv4 or IPv6 address + :param string message: The message to show next to the blocks + :param int action: The action to take when the dynamic block matches, see :ref:`DNSAction `. (default to DNSAction.None, meaning the one set with :func:`setDynBlocksAction` is used) + :param int seconds: The number of seconds this block to expire + :param int clientIPMask: The network mask to apply to the address. Default is 32 for IPv4, 128 for IPv6. + :param int clientIPPortMask: The port mask to use to specify a range of ports to match, when the clients are behind a CG-NAT. + + Please see the documentation for :func:`setDynBlocksAction` to confirm which actions are supported by the action paramater. + .. function:: addDynBlocks(addresses, message[, seconds=10[, action]]) Block a set of addresses with ``message`` for (optionally) a number of seconds.