From 5a2f328720017c1e32c0d0cf60ad5120a46e4c26 Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Tue, 26 Jan 2021 17:39:00 +0100 Subject: [PATCH] dnsdist: Accept a NMG to fill DynBlockRulesGroup ranges --- pdns/dnsdist-dynblocks.hh | 10 ++++ pdns/dnsdist-lua-inspection.cc | 10 +++- pdns/dnsdistdist/docs/reference/config.rst | 10 +++- pdns/dnsdistdist/docs/reference/ebpf.rst | 2 +- pdns/iputils.hh | 7 +++ regression-tests.dnsdist/test_DynBlocks.py | 62 ++++++++++++++++++++++ 6 files changed, 96 insertions(+), 5 deletions(-) diff --git a/pdns/dnsdist-dynblocks.hh b/pdns/dnsdist-dynblocks.hh index 8f031f457a..80a0dca603 100644 --- a/pdns/dnsdist-dynblocks.hh +++ b/pdns/dnsdist-dynblocks.hh @@ -276,11 +276,21 @@ public: d_excludedSubnets.addMask(range); } + void excludeRange(const NetmaskGroup& group) + { + d_excludedSubnets.addMasks(group, true); + } + void includeRange(const Netmask& range) { d_excludedSubnets.addMask(range, false); } + void includeRange(const NetmaskGroup& group) + { + d_excludedSubnets.addMasks(group, false); + } + void excludeDomain(const DNSName& domain) { d_excludedDomains.add(domain); diff --git a/pdns/dnsdist-lua-inspection.cc b/pdns/dnsdist-lua-inspection.cc index 3cdc790d91..f0915d6c82 100644 --- a/pdns/dnsdist-lua-inspection.cc +++ b/pdns/dnsdist-lua-inspection.cc @@ -772,22 +772,28 @@ void setupLuaInspection(LuaContext& luaCtx) group->setQTypeRate(qtype, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None); } }); - luaCtx.registerFunction::*)(boost::variant>>)>("excludeRange", [](std::shared_ptr& group, boost::variant>> ranges) { + luaCtx.registerFunction::*)(boost::variant>, NetmaskGroup>)>("excludeRange", [](std::shared_ptr& group, boost::variant>, NetmaskGroup> ranges) { if (ranges.type() == typeid(std::vector>)) { for (const auto& range : *boost::get>>(&ranges)) { group->excludeRange(Netmask(range.second)); } } + else if (ranges.type() == typeid(NetmaskGroup)) { + group->excludeRange(*boost::get(&ranges)); + } else { group->excludeRange(Netmask(*boost::get(&ranges))); } }); - luaCtx.registerFunction::*)(boost::variant>>)>("includeRange", [](std::shared_ptr& group, boost::variant>> ranges) { + luaCtx.registerFunction::*)(boost::variant>, NetmaskGroup>)>("includeRange", [](std::shared_ptr& group, boost::variant>, NetmaskGroup> ranges) { if (ranges.type() == typeid(std::vector>)) { for (const auto& range : *boost::get>>(&ranges)) { group->includeRange(Netmask(range.second)); } } + else if (ranges.type() == typeid(NetmaskGroup)) { + group->includeRange(*boost::get(&ranges)); + } else { group->includeRange(Netmask(*boost::get(&ranges))); } diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index 7ce9702720..cc23fb72d9 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -1417,17 +1417,23 @@ faster than the existing rules. .. versionadded:: 1.3.1 + .. versionchanged:: 1.6.0 + This method now accepts a :class:`NetmaskGroup` object. + Exclude this range, or list of ranges, meaning that no dynamic block will ever be inserted for clients in that range. Default to empty, meaning rules are applied to all ranges. When used in combination with :meth:`DynBlockRulesGroup:includeRange`, the more specific entry wins. - :param list netmasks: A netmask, or list of netmasks, as strings, like for example "192.0.2.1/24" + :param list netmasks: A :class:`NetmaskGroup` object, or a netmask or list of netmasks as strings, like for example "192.0.2.1/24" .. method:: DynBlockRulesGroup:includeRange(netmasks) .. versionadded:: 1.3.1 + .. versionchanged:: 1.6.0 + This method now accepts a :class:`NetmaskGroup` object. + Include this range, or list of ranges, meaning that rules will be applied to this range. When used in combination with :meth:`DynBlockRulesGroup:excludeRange`, the more specific entry wins. - :param list netmasks: A netmask, or list of netmasks, as strings, like for example "192.0.2.1/24" + :param list netmasks: A :class:`NetmaskGroup` object, or a netmask or list of netmasks as strings, like for example "192.0.2.1/24" .. method:: DynBlockRulesGroup:toString() diff --git a/pdns/dnsdistdist/docs/reference/ebpf.rst b/pdns/dnsdistdist/docs/reference/ebpf.rst index 689dddd481..48782ed3eb 100644 --- a/pdns/dnsdistdist/docs/reference/ebpf.rst +++ b/pdns/dnsdistdist/docs/reference/ebpf.rst @@ -89,7 +89,7 @@ These are all the functions, objects and methods related to the :doc:`../advance .. class:: DynBPFFilter - Represents an dynamic eBPF filter, allowing the use of ephemeral rules to an existing eBPF filter. + Represents an dynamic eBPF filter, allowing the use of ephemeral rules to an existing eBPF filter. Note that since 1.6.0 the default BPF filter set via :func:`setDefaultBPFFilter` will automatically be used by a :ref:`DynBlockRulesGroup`, becoming the preferred way of dealing with ephemeral rules. .. method:: DynBPFFilter:purgeExpired() diff --git a/pdns/iputils.hh b/pdns/iputils.hh index 2d9f5af444..6b36d1e19d 100644 --- a/pdns/iputils.hh +++ b/pdns/iputils.hh @@ -1326,6 +1326,13 @@ public: tree.insert(nm).second=positive; } + void addMasks(const NetmaskGroup& group, boost::optional positive) + { + for (const auto& entry : group.tree) { + addMask(entry.first, positive ? *positive : entry.second); + } + } + //! Delete this Netmask from the list of possible matches void deleteMask(const Netmask& nm) { diff --git a/regression-tests.dnsdist/test_DynBlocks.py b/regression-tests.dnsdist/test_DynBlocks.py index c7abead05a..44ad7598d7 100644 --- a/regression-tests.dnsdist/test_DynBlocks.py +++ b/regression-tests.dnsdist/test_DynBlocks.py @@ -1070,6 +1070,68 @@ class TestDynBlockGroupExcluded(DynBlocksTest): self.assertEquals(query, receivedQuery) self.assertEquals(receivedResponse, receivedResponse) +class TestDynBlockGroupExcludedViaNMG(DynBlocksTest): + + _dynBlockQPS = 10 + _dynBlockPeriod = 2 + _dynBlockDuration = 5 + _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort'] + _config_template = """ + local nmg = newNMG() + nmg:addMask("127.0.0.1/32") + + local dbr = dynBlockRulesGroup() + dbr:setQueryRate(%d, %d, "Exceeded query rate", %d) + dbr:excludeRange(nmg) + + function maintenance() + dbr:apply() + end + + newServer{address="127.0.0.1:%s"} + """ + + def testExcluded(self): + """ + Dyn Blocks (group) : Excluded (via NMG) from the dynamic block rules + """ + name = 'excluded-nmg.group.dynblocks.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + response = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 60, + dns.rdataclass.IN, + dns.rdatatype.A, + '192.0.2.1') + response.answer.append(rrset) + + allowed = 0 + sent = 0 + for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1): + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + sent = sent + 1 + if receivedQuery: + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(response, receivedResponse) + allowed = allowed + 1 + else: + # the query has not reached the responder, + # let's clear the response queue + self.clearToResponderQueue() + + # we should not have been blocked + self.assertEqual(allowed, sent) + + # wait for the maintenance function to run + time.sleep(2) + + # we should still not be blocked + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, receivedResponse) + class TestDynBlockGroupNoOp(DynBlocksTest): _dynBlockQPS = 10 -- 2.47.2