]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add support for v4/v6 masks in dynamic blocks
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 23 Sep 2021 14:31:38 +0000 (16:31 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 21 Oct 2021 08:08:41 +0000 (10:08 +0200)
pdns/dnsdist-dynblocks.hh
pdns/dnsdist-lua-inspection.cc
pdns/dnsdistdist/dnsdist-dynblocks.cc
pdns/dnsdistdist/docs/reference/config.rst
pdns/iputils.hh

index 2833bfe42288ed1f0c5b4a548986cf456526c7bc..c8a7aaa8c60ddd31b0c6ff120578d389d89265ef 100644 (file)
@@ -212,7 +212,7 @@ private:
     double d_warningRatio{0.0};
   };
 
-  typedef std::unordered_map<ComboAddress, Counts, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
+  typedef std::unordered_map<Netmask, Counts, Netmask::hash> counts_t;
 
 public:
   DynBlockRulesGroup()
@@ -262,6 +262,12 @@ public:
     d_smtVisitorFFI = visitor;
   }
 
+  void setMasks(uint8_t v4, uint8_t v6)
+  {
+    d_v4Mask = v4;
+    d_v6Mask = v6;
+  }
+
   void apply()
   {
     struct timespec now;
@@ -330,15 +336,15 @@ private:
 
   bool checkIfQueryTypeMatches(const Rings::Query& query);
   bool checkIfResponseCodeMatches(const Rings::Response& response);
-  void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning);
+  void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const Netmask& requestor, const DynBlockRule& rule, bool& updated, bool warning);
   void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated);
 
-  void addBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
+  void addBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const Netmask& requestor, const DynBlockRule& rule, bool& updated)
   {
     addOrRefreshBlock(blocks, now, requestor, rule, updated, false);
   }
 
-  void handleWarning(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
+  void handleWarning(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const Netmask& requestor, const DynBlockRule& rule, bool& updated)
   {
     addOrRefreshBlock(blocks, now, requestor, rule, updated, true);
   }
@@ -376,6 +382,8 @@ private:
   SuffixMatchNode d_excludedDomains;
   smtVisitor_t d_smtVisitor;
   dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI;
+  uint8_t d_v6Mask{128};
+  uint8_t d_v4Mask{32};
   bool d_beQuiet{false};
 };
 
@@ -417,3 +425,110 @@ private:
   static std::list<MetricsSnapshot> s_metricsData;
   static size_t s_topN;
 };
+
+class AddressAndPortRange
+{
+public:
+  AddressAndPortRange(): d_addrMask(0), d_portMask(0)
+  {
+    d_addr.sin4.sin_family = 0; // disable this doing anything useful
+    d_addr.sin4.sin_port = 0; // this guarantees d_network compares identical
+  }
+
+  AddressAndPortRange(ComboAddress ca, uint8_t addrMask, uint8_t portMask): d_addr(std::move(ca)), d_addrMask(addrMask), d_portMask(portMask)
+  {
+    cerr<<"creating a address and port range "<<ca.toStringWithPort()<<"/"<<std::to_string(addrMask)<<" "<<std::to_string(portMask)<<endl;
+    if (d_addrMask < d_addr.getBits()) {
+      uint16_t port = d_addr.getPort();
+      d_addr = Netmask(d_addr, d_addrMask).getMaskedNetwork();
+      d_addr.setPort(port);
+    }
+  }
+
+  uint8_t getFullBits() const
+  {
+    cerr<<"in getFullbits returning "<<(d_addr.getBits() + 16)<<endl;
+    return d_addr.getBits() + 16;
+  }
+
+  uint8_t getBits() const
+  {
+    if (d_addrMask < d_addr.getBits()) {
+      cerr<<"in getBits returning "<<std::to_string(d_addrMask)<<endl;
+      return d_addrMask;
+    }
+
+    cerr<<"in getBits returning "<<std::to_string(d_addr.getBits() + d_portMask)<<endl;
+    return d_addr.getBits() + d_portMask;
+  }
+
+  /** Get the value of the bit at the provided bit index. When the index >= 0,
+      the index is relative to the LSB starting at index zero. When the index < 0,
+      the index is relative to the MSB starting at index -1 and counting down.
+  */
+  bool getBit(int index) const
+  {
+    cerr<<"in getBit "<<index<<" for "<<d_addr.toStringWithPort()<<endl;
+    if (index >= getFullBits()) {
+      cerr<<"flse 1"<<endl;
+      return false;
+    }
+    if (index < 0) {
+      index = getFullBits() + index;
+      cerr<<"normalized index to "<<index<<endl;
+    }
+
+    if (index < 16) {
+      /* we are into the port bits */
+      uint16_t port = d_addr.getPort();
+      cerr<<"return (2) "<<((port & (1U<<index)) != 0x0000)<<endl;
+      return ((port & (1U<<index)) != 0x0000);
+    }
+
+    index -= 16;
+
+    cerr<<"return (d_addr) "<<d_addr.getBit(index)<<endl;
+    return d_addr.getBit(index);
+  }
+
+  bool isIPv4() const
+  {
+    return d_addr.isIPv4();
+  }
+
+  bool isIPv6() const
+  {
+    return d_addr.isIPv6();
+  }
+
+  AddressAndPortRange getNormalized() const
+  {
+    cerr<<"in getNormalized"<<endl;
+    return AddressAndPortRange(d_addr, d_addrMask, d_portMask);
+  }
+
+  AddressAndPortRange getSuper(uint8_t bits) const
+  {
+    cerr<<"in getSuper("<<std::to_string(bits)<<")"<<endl;
+    if (bits <= d_addrMask) {
+      return AddressAndPortRange(d_addr, bits, 0);
+    }
+    if (bits <= d_addrMask + d_portMask) {
+      return AddressAndPortRange(d_addr, d_addrMask, d_portMask - (bits - d_addrMask));
+    }
+
+    return AddressAndPortRange(d_addr, d_addrMask, d_portMask);
+  }
+
+  const ComboAddress& getNetwork() const
+  {
+    cerr<<"in getNetwork"<<endl;
+    return d_addr;
+  }
+
+private:
+  ComboAddress d_addr;
+  uint8_t d_addrMask;
+  uint8_t d_portMask;
+};
+
index 3e09fef8cbf8959181d85ea7579806ea8facbbf0..dc210c9e229245c48aac2ca21298f5b82b489bcc 100644 (file)
@@ -796,6 +796,11 @@ void setupLuaInspection(LuaContext& luaCtx)
         group->setQTypeRate(qtype, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
       }
     });
+  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, uint8_t)>("setMasks", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t v4, uint8_t v6) {
+      if (group) {
+        group->setMasks(v4, v6);
+      }
+    });
   luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>, NetmaskGroup>)>("excludeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, std::vector<std::pair<int, std::string>>, NetmaskGroup> ranges) {
       if (ranges.type() == typeid(std::vector<std::pair<int, std::string>>)) {
         for (const auto& range : *boost::get<std::vector<std::pair<int, std::string>>>(&ranges)) {
index 4381b4a3967bbf13d25ffa5108c91f94726e8a79..2b73743b9ddb631d01e004cac33cd2e6008663e0 100644 (file)
@@ -174,9 +174,9 @@ bool DynBlockRulesGroup::checkIfResponseCodeMatches(const Rings::Response& respo
   return false;
 }
 
-void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning)
+void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const Netmask& requestor, const DynBlockRule& rule, bool& updated, bool warning)
 {
-  if (d_excludedSubnets.match(requestor)) {
+  if (d_excludedSubnets.match(requestor.getMaskedNetwork())) {
     /* do not add a block for excluded subnets */
     return;
   }
@@ -187,7 +187,7 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock>
   struct timespec until = now;
   until.tv_sec += rule.d_blockDuration;
   unsigned int count = 0;
-  const auto& got = blocks->lookup(Netmask(requestor));
+  const auto& got = blocks->lookup(requestor.getMaskedNetwork());
   bool expired = false;
   bool wasWarning = false;
   bool bpf = false;
@@ -223,9 +223,10 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock>
   db.blocks = count;
   db.warning = warning;
   if (!got || expired || wasWarning) {
-    if (db.action == DNSAction::Action::Drop && g_defaultBPFFilter) {
+    if (db.action == DNSAction::Action::Drop && g_defaultBPFFilter &&
+        ((requestor.isIPv4() && requestor.getBits() == 32) || (requestor.isIPv6() && requestor.getBits() == 128))) {
       try {
-        g_defaultBPFFilter->block(requestor);
+        g_defaultBPFFilter->block(requestor.getMaskedNetwork());
         bpf = true;
       }
       catch (const std::exception& e) {
@@ -240,7 +241,7 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock>
 
   db.bpf = bpf;
 
-  blocks->insert(Netmask(requestor)).second = std::move(db);
+  blocks->insert(requestor).second = std::move(db);
 
   updated = true;
 }
@@ -314,7 +315,7 @@ void DynBlockRulesGroup::processQueryRules(counts_t& counts, const struct timesp
       bool typeRuleMatches = checkIfQueryTypeMatches(c);
 
       if (qRateMatches || typeRuleMatches) {
-        auto& entry = counts[c.requestor];
+        auto& entry = counts[Netmask(c.requestor, c.requestor.isIPv4() ? d_v4Mask : d_v6Mask)];
         if (qRateMatches) {
           ++entry.queries;
         }
@@ -373,7 +374,7 @@ void DynBlockRulesGroup::processResponseRules(counts_t& counts, StatNode& root,
         continue;
       }
 
-      auto& entry = counts[c.requestor];
+      auto& entry = counts[Netmask(c.requestor, c.requestor.isIPv4() ? d_v4Mask : d_v6Mask)];
       ++entry.responses;
 
       bool respRateMatches = d_respRateRule.matches(c.when);
index aa43e908ee1c5ab8c06364074005c3dd241d1f36..73557c48913548960a279d844af7ebfbe74c02d4 100644 (file)
@@ -1258,6 +1258,17 @@ faster than the existing rules.
 
   Represents a group of dynamic block rules.
 
+  .. method:: DynBlockRulesGroup:setMasks(v4, v6)
+
+    .. versionadded:: 1.7.0
+
+    Set the number of bits to keep in the IP address when inserting a block. The default is 32 for IPv4 and 128 for IPv6, meaning
+    that only the exact address is blocked, but in some scenarios it might make sense to block a whole /64 IPv6 range instead of a
+    single address, for example.
+
+    :param int v4: Number of bits of to keep for IPv4 addresses. Default is 32
+    :param int v6: Number of bits of to keep for IPv6 addresses. Default is 128
+
   .. method:: DynBlockRulesGroup:setQueryRate(rate, seconds, reason, blockingTime [, action [, warningRate]])
 
     Adds a query rate-limiting rule, equivalent to:
index 45690731a7b5da600d83a2f0f540c248b41ba2b0..2ec43220f3eb2cfdc6e7074a9e5cf418cee6690f 100644 (file)
@@ -637,6 +637,11 @@ public:
     return d_network.getBits();
   }
 
+  uint8_t getFullBits() const
+  {
+    return getAddressBits();
+  }
+
   /** Get the value of the bit at the provided bit index. When the index >= 0,
       the index is relative to the LSB starting at index zero. When the index < 0,
       the index is relative to the MSB starting at index -1 and counting down.
@@ -658,6 +663,15 @@ public:
     }
     return d_network.getBit(bit);
   }
+
+  struct hash
+  {
+    uint32_t operator()(const Netmask& nm) const
+    {
+      ComboAddress::addressOnlyHash hashOp;
+      return hashOp(nm.d_network);
+    }
+  };
 private:
   ComboAddress d_network;
   uint32_t d_mask;
@@ -685,12 +699,12 @@ private:
  * Please see NetmaskGroup for example of simple use case. Other usecases can be found
  * from GeoIPBackend and Sortlist, and from dnsdist.
  */
-template <typename T>
+template <typename T, class K = Netmask>
 class NetmaskTree {
 public:
   class Iterator;
 
-  typedef Netmask key_type;
+  typedef K key_type;
   typedef T value_type;
   typedef std::pair<const key_type,value_type> node_type;
   typedef size_t size_type;
@@ -706,7 +720,7 @@ private:
     }
     explicit TreeNode(const key_type& key) noexcept :
       parent(nullptr), node({key.getNormalized(), value_type()}),
-      assigned(false), d_bits(key.getAddressBits()) {
+      assigned(false), d_bits(key.getFullBits()) {
     }
 
     //<! Makes a left leaf node with specified key.