]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add a new eBPF map format, support external eBPF programs
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 28 Oct 2021 15:58:53 +0000 (17:58 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 16 Nov 2021 08:02:48 +0000 (09:02 +0100)
Supporting external eBPF programs makes it possible to populate the
eBPF tables from dnsdist, manually or via our dynamic blocking mechanisms,
but to actually do the filtering in an external program, like an XDP one.

We cannot increase the size of eBPF programs if we want to stay
below 4k instructions for older kernels, so this commit implements
a compatibility layer with the new map format.

The 4k limit for unprivileged was removed in 5.2 but the complexity limit remains:
The complexity limit was actually changed several times since the
32k value from its introduction in Linux 3.18: it was raised to 64k
in Linux 4.7, then to 96k in Linux 4.12, again to 128k in Linux 4.14,
and at last to 1M in Linux 5.2.

pdns/bpf-filter.cc
pdns/bpf-filter.hh
pdns/dnsdist-dynbpf.cc
pdns/dnsdist-lua-bindings.cc
pdns/dnsdist.cc
pdns/dnsdistdist/dnsdist-dynblocks.cc

index 55dc241d9ade6d4e3c60290d4e6cdf9a5e4e3757..2b5df7a07f4bdc5e678114787e88893c1c08cc0b 100644 (file)
@@ -178,13 +178,25 @@ struct QNameKey
   uint8_t qname[255];
 };
 
-struct QNameValue
+struct QNameAndQTypeKey
 {
-  uint64_t counter;
+  uint8_t qname[255];
   uint16_t qtype;
 };
 
-BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config): d_config(config)
+struct QNameValue
+{
+  uint64_t counter{0};
+  uint16_t qtype{0};
+};
+
+struct CounterAndActionValue
+{
+  uint64_t counter{0};
+  BPFFilter::MatchAction action{BPFFilter::MatchAction::Pass};
+};
+
+BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config, BPFFilter::MapFormat format): d_config(config)
 {
   if (d_config.d_type == BPFFilter::MapType::Filters) {
     /* special case, this is a map of eBPF programs */
@@ -196,21 +208,41 @@ BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config): d_config(config)
   else {
     int keySize = 0;
     int valueSize = 0;
-    switch (d_config.d_type) {
-    case MapType::IPv4:
-      keySize = sizeof(uint32_t);
-      valueSize = sizeof(uint64_t);
-      break;
-    case MapType::IPv6:
-      keySize = sizeof(KeyV6);
-      valueSize = sizeof(uint64_t);
-      break;
-    case MapType::QNames:
-      keySize = sizeof(QNameKey);
-      valueSize = sizeof(QNameValue);
-      break;
-    default:
-      throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
+    if (format == MapFormat::Legacy) {
+      switch (d_config.d_type) {
+      case MapType::IPv4:
+        keySize = sizeof(uint32_t);
+        valueSize = sizeof(uint64_t);
+        break;
+      case MapType::IPv6:
+        keySize = sizeof(KeyV6);
+        valueSize = sizeof(uint64_t);
+        break;
+      case MapType::QNames:
+        keySize = sizeof(QNameKey);
+        valueSize = sizeof(QNameValue);
+        break;
+      default:
+        throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
+      }
+    }
+    else {
+      switch (d_config.d_type) {
+      case MapType::IPv4:
+        keySize = sizeof(uint32_t);
+        valueSize = sizeof(CounterAndActionValue);
+        break;
+      case MapType::IPv6:
+        keySize = sizeof(KeyV6);
+        valueSize = sizeof(CounterAndActionValue);
+        break;
+      case MapType::QNames:
+        keySize = sizeof(QNameAndQTypeKey);
+        valueSize = sizeof(CounterAndActionValue);
+        break;
+      default:
+        throw std::runtime_error("Unsupported eBPF map type: " + std::to_string(static_cast<uint8_t>(d_config.d_type)));
+      }
     }
 
     if (!d_config.d_pinnedPath.empty()) {
@@ -238,12 +270,23 @@ BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config): d_config(config)
           }
         }
         else if (d_config.d_type == MapType::QNames) {
-          QNameKey key;
-          memset(&key, 0, sizeof(key));
-          int res = bpf_get_next_key(d_fd.getHandle(), &key, &key);
-          while (res == 0) {
-            ++d_count;
-            res = bpf_get_next_key(d_fd.getHandle(), &key, &key);
+          if (format == MapFormat::Legacy) {
+            QNameKey key;
+            memset(&key, 0, sizeof(key));
+            int res = bpf_get_next_key(d_fd.getHandle(), &key, &key);
+            while (res == 0) {
+              ++d_count;
+              res = bpf_get_next_key(d_fd.getHandle(), &key, &key);
+            }
+          }
+          else {
+            QNameAndQTypeKey key;
+            memset(&key, 0, sizeof(key));
+            int res = bpf_get_next_key(d_fd.getHandle(), &key, &key);
+            while (res == 0) {
+              ++d_count;
+              res = bpf_get_next_key(d_fd.getHandle(), &key, &key);
+            }
           }
         }
       }
@@ -278,47 +321,53 @@ static FDWrapper loadProgram(const struct bpf_insn* filter, size_t filterSize)
 }
 
 
-BPFFilter::BPFFilter(const BPFFilter::MapConfiguration& v4, const BPFFilter::MapConfiguration& v6, const BPFFilter::MapConfiguration& qnames)
+BPFFilter::BPFFilter(const BPFFilter::MapConfiguration& v4, const BPFFilter::MapConfiguration& v6, const BPFFilter::MapConfiguration& qnames, BPFFilter::MapFormat format, bool external): d_mapFormat(format), d_external(external)
 {
+  if (d_mapFormat != BPFFilter::MapFormat::Legacy && !d_external) {
+    throw std::runtime_error("Unsupported eBPF map format, the current internal implemenation only supports the legacy format");
+  }
+
   auto maps = d_maps.lock();
 
-  maps->d_v4 = BPFFilter::Map(v4);
-  maps->d_v6 = BPFFilter::Map(v6);
-  maps->d_qnames = BPFFilter::Map(qnames);
-  BPFFilter::MapConfiguration filters;
-  filters.d_maxItems = 1;
-  filters.d_type = BPFFilter::MapType::Filters;
-  maps->d_filters = BPFFilter::Map(filters);
+  maps->d_v4 = BPFFilter::Map(v4, d_mapFormat);
+  maps->d_v6 = BPFFilter::Map(v6, d_mapFormat);
+  maps->d_qnames = BPFFilter::Map(qnames, d_mapFormat);
+  if (!external) {
+    BPFFilter::MapConfiguration filters;
+    filters.d_maxItems = 1;
+    filters.d_type = BPFFilter::MapType::Filters;
+    maps->d_filters = BPFFilter::Map(filters, d_mapFormat);
 
-  const struct bpf_insn main_filter[] = {
+    const struct bpf_insn main_filter[] = {
 #include "bpf-filter.main.ebpf"
-  };
+    };
 
-  const struct bpf_insn qname_filter[] = {
+    const struct bpf_insn qname_filter[] = {
 #include "bpf-filter.qname.ebpf"
-  };
+    };
 
-  try {
-    d_mainfilter = loadProgram(main_filter,
-                               sizeof(main_filter));
-  }
-  catch (const std::exception& e) {
-    throw std::runtime_error("Error load the main eBPF filter: " + std::string(e.what()));
-  }
+    try {
+      d_mainfilter = loadProgram(main_filter,
+                                 sizeof(main_filter));
+    }
+    catch (const std::exception& e) {
+      throw std::runtime_error("Error load the main eBPF filter: " + std::string(e.what()));
+    }
 
-  try {
-    d_qnamefilter = loadProgram(qname_filter,
-                               sizeof(qname_filter));
-  }
-  catch (const std::exception& e) {
-    throw std::runtime_error("Error load the qname eBPF filter: " + std::string(e.what()));
-  }
+    try {
+      d_qnamefilter = loadProgram(qname_filter,
+                                  sizeof(qname_filter));
+    }
+    catch (const std::exception& e) {
+      throw std::runtime_error("Error load the qname eBPF filter: " + std::string(e.what()));
+    }
 
-  uint32_t key = 0;
-  int qnamefd = d_qnamefilter.getHandle();
-  int res = bpf_update_elem(maps->d_filters.d_fd.getHandle(), &key, &qnamefd, BPF_ANY);
-  if (res != 0) {
-    throw std::runtime_error("Error updating BPF filters map: " + stringerror());
+    uint32_t key = 0;
+    int qnamefd = d_qnamefilter.getHandle();
+    int res = bpf_update_elem(maps->d_filters.d_fd.getHandle(), &key, &qnamefd, BPF_ANY);
+    if (res != 0) {
+      throw std::runtime_error("Error updating BPF filters map: " + stringerror());
+    }
   }
 }
 
@@ -342,9 +391,12 @@ void BPFFilter::removeSocket(int sock)
   }
 }
 
-void BPFFilter::block(const ComboAddress& addr)
+void BPFFilter::block(const ComboAddress& addr, BPFFilter::MatchAction action)
 {
-  uint64_t counter = 0;
+  CounterAndActionValue value;
+  value.counter = 0;
+  value.action = action;
+
   int res = 0;
   if (addr.isIPv4()) {
     uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
@@ -354,12 +406,12 @@ void BPFFilter::block(const ComboAddress& addr)
       throw std::runtime_error("Table full when trying to block " + addr.toString());
     }
 
-    res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
+    res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
     if (res != -1) {
       throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
     }
 
-    res = bpf_update_elem(map.d_fd.getHandle(), &key, &counter, BPF_NOEXIST);
+    res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, BPF_NOEXIST);
     if (res == 0) {
       ++map.d_count;
     }
@@ -377,12 +429,12 @@ void BPFFilter::block(const ComboAddress& addr)
       throw std::runtime_error("Table full when trying to block " + addr.toString());
     }
 
-    res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
+    res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
     if (res != -1) {
       throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
     }
 
-    res = bpf_update_elem(map.d_fd.getHandle(), key, &counter, BPF_NOEXIST);
+    res = bpf_update_elem(map.d_fd.getHandle(), key, &value, BPF_NOEXIST);
     if (res == 0) {
       map.d_count++;
     }
@@ -425,20 +477,34 @@ void BPFFilter::unblock(const ComboAddress& addr)
   }
 }
 
-void BPFFilter::block(const DNSName& qname, uint16_t qtype)
+void BPFFilter::block(const DNSName& qname, BPFFilter::MatchAction action, uint16_t qtype)
 {
-  struct QNameKey key;
-  struct QNameValue value;
+  CounterAndActionValue cadvalue;
+  QNameValue qvalue;
+  void* value = nullptr;
+
+  if (d_external) {
+    memset(&cadvalue, 0, sizeof(cadvalue));
+    cadvalue.counter = 0;
+    cadvalue.action = action;
+    value = &cadvalue;
+  }
+  else {
+    memset(&qvalue, 0, sizeof(qvalue));
+    qvalue.counter = 0;
+    qvalue.qtype = qtype;
+    value = &qvalue;
+  }
+
+  QNameAndQTypeKey key;
   memset(&key, 0, sizeof(key));
-  memset(&value, 0, sizeof(value));
-  value.counter = 0;
-  value.qtype = qtype;
 
   std::string keyStr = qname.toDNSStringLC();
   if (keyStr.size() > sizeof(key.qname)) {
     throw std::runtime_error("Invalid QName to block " + qname.toLogString());
   }
   memcpy(key.qname, keyStr.c_str(), keyStr.size());
+  key.qtype = qtype;
 
   {
     auto maps = d_maps.lock();
@@ -465,14 +531,15 @@ void BPFFilter::block(const DNSName& qname, uint16_t qtype)
 
 void BPFFilter::unblock(const DNSName& qname, uint16_t qtype)
 {
-  struct QNameKey key = { { 0 } };
+  QNameAndQTypeKey key;
+  memset(&key, 0, sizeof(key));
   std::string keyStr = qname.toDNSStringLC();
-  (void) qtype;
 
   if (keyStr.size() > sizeof(key.qname)) {
     throw std::runtime_error("Invalid QName to block " + qname.toLogString());
   }
   memcpy(key.qname, keyStr.c_str(), keyStr.size());
+  key.qtype = qtype;
 
   {
     auto maps = d_maps.lock();
@@ -501,7 +568,7 @@ std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
 
   uint32_t v4Key = 0;
   uint32_t nextV4Key;
-  uint64_t value;
+  CounterAndActionValue value;
 
   uint8_t v6Key[16];
   uint8_t nextV6Key[16];
@@ -522,7 +589,7 @@ std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
       v4Key = nextV4Key;
       if (bpf_lookup_elem(map.d_fd.getHandle(), &v4Key, &value) == 0) {
       v4Addr.sin_addr.s_addr = ntohl(v4Key);
-      result.emplace_back(ComboAddress(&v4Addr), value);
+      result.emplace_back(ComboAddress(&v4Addr), value.counter);
       }
 
       res = bpf_get_next_key(map.d_fd.getHandle(), &v4Key, &nextV4Key);
@@ -537,7 +604,7 @@ std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
       if (bpf_lookup_elem(map.d_fd.getHandle(), &nextV6Key, &value) == 0) {
         memcpy(&v6Addr.sin6_addr.s6_addr, &nextV6Key, sizeof(nextV6Key));
 
-        result.emplace_back(ComboAddress(&v6Addr), value);
+        result.emplace_back(ComboAddress(&v6Addr), value.counter);
       }
 
       res = bpf_get_next_key(map.d_fd.getHandle(), &nextV6Key, &nextV6Key);
@@ -551,29 +618,54 @@ std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats()
 {
   std::vector<std::tuple<DNSName, uint16_t, uint64_t> > result;
 
-  struct QNameKey key = { { 0 } };
-  struct QNameKey nextKey = { { 0 } };
-  struct QNameValue value;
+  if (d_mapFormat == MapFormat::Legacy) {
+    QNameKey key = { { 0 } };
+    QNameKey nextKey = { { 0 } };
+    QNameValue value;
 
-  auto maps = d_maps.lock();
-  auto& map = maps->d_qnames;
-  result.reserve(map.d_count);
-  int res = bpf_get_next_key(map.d_fd.getHandle(), &key, &nextKey);
+    auto maps = d_maps.lock();
+    auto& map = maps->d_qnames;
+    result.reserve(map.d_count);
+    int res = bpf_get_next_key(map.d_fd.getHandle(), &key, &nextKey);
 
-  while (res == 0) {
-    if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
-      nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
-      result.push_back(std::make_tuple(DNSName((const char*) nextKey.qname, sizeof(nextKey.qname), 0, false), value.qtype, value.counter));
+    while (res == 0) {
+      if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
+        nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
+        result.push_back(std::make_tuple(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), value.qtype, value.counter));
+      }
+
+      res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
     }
+  }
+  else {
+    QNameAndQTypeKey key;
+    QNameAndQTypeKey nextKey;
+    memset(&key, 0, sizeof(key));
+    memset(&nextKey, 0, sizeof(nextKey));
+    CounterAndActionValue value;
+
+    auto maps = d_maps.lock();
+    auto& map = maps->d_qnames;
+    result.reserve(map.d_count);
+    int res = bpf_get_next_key(map.d_fd.getHandle(), &key, &nextKey);
 
-    res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
+    while (res == 0) {
+      if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
+        nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
+        result.push_back(std::make_tuple(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), key.qtype, value.counter));
+      }
+
+      res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
+    }
   }
+
   return result;
 }
 
 uint64_t BPFFilter::getHits(const ComboAddress& requestor)
 {
-  uint64_t counter = 0;
+  CounterAndActionValue counter;
+
   if (requestor.isIPv4()) {
     uint32_t key = htonl(requestor.sin4.sin_addr.s_addr);
 
@@ -581,7 +673,7 @@ uint64_t BPFFilter::getHits(const ComboAddress& requestor)
     auto& map = maps->d_v4;
     int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
     if (res == 0) {
-      return counter;
+      return counter.counter;
     }
   }
   else if (requestor.isIPv6()) {
@@ -595,7 +687,7 @@ uint64_t BPFFilter::getHits(const ComboAddress& requestor)
     auto& map = maps->d_v6;
     int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
     if (res == 0) {
-      return counter;
+      return counter.counter;
     }
   }
 
@@ -604,7 +696,7 @@ uint64_t BPFFilter::getHits(const ComboAddress& requestor)
 
 #else
 
-BPFFilter::BPFFilter(const BPFFilter::MapConfiguration&, const BPFFilter::MapConfiguration&, const BPFFilter::MapConfiguration&)
+BPFFilter::BPFFilter(const BPFFilter::MapConfiguration&, const BPFFilter::MapConfiguration&, const BPFFilter::MapConfiguration&, BPFFilter::MapFormat, bool)
 {
 }
 
@@ -618,7 +710,7 @@ void BPFFilter::removeSocket(int)
   throw std::runtime_error("eBPF support not enabled");
 }
 
-void BPFFilter::block(const ComboAddress&)
+void BPFFilter::block(const ComboAddress&, BPFFilter::MatchAction)
 {
   throw std::runtime_error("eBPF support not enabled");
 }
@@ -628,7 +720,7 @@ void BPFFilter::unblock(const ComboAddress&)
   throw std::runtime_error("eBPF support not enabled");
 }
 
-void BPFFilter::block(const DNSName&, uint16_t)
+void BPFFilter::block(const DNSName&, BPFFilter::MatchAction, uint16_t)
 {
   throw std::runtime_error("eBPF support not enabled");
 }
@@ -655,3 +747,22 @@ uint64_t BPFFilter::getHits(const ComboAddress&)
   return 0;
 }
 #endif /* HAVE_EBPF */
+
+bool BPFFilter::supportsMatchAction(MatchAction action) const
+{
+#ifdef HAVE_EBPF
+  if (action == BPFFilter::MatchAction::Drop) {
+    return true;
+  }
+  return d_mapFormat == BPFFilter::MapFormat::WithActions;
+#endif /* HAVE_EBPF */
+  return false;
+}
+
+bool BPFFilter::isExternal() const
+{
+#ifdef HAVE_EBPF
+  return d_external;
+#endif /* HAVE_EBPF */
+  return false;
+}
index 6dce2dbc97eeb5ff8abeeb58c0200ed602e8f09c..b56bd452664f152d977b1acd4b3024a64e15efcb 100644 (file)
@@ -35,6 +35,17 @@ public:
     Filters
   };
 
+  enum class MapFormat : uint8_t {
+    Legacy = 0,
+    WithActions = 1
+  };
+
+  enum class MatchAction : uint32_t {
+    Pass = 0,
+    Drop = 1,
+    Truncate = 2
+  };
+
   struct MapConfiguration
   {
     std::string d_pinnedPath;
@@ -42,7 +53,8 @@ public:
     MapType d_type;
   };
 
-  BPFFilter(const BPFFilter::MapConfiguration& v4, const BPFFilter::MapConfiguration& v6, const BPFFilter::MapConfiguration& qnames);
+
+  BPFFilter(const BPFFilter::MapConfiguration& v4, const BPFFilter::MapConfiguration& v6, const BPFFilter::MapConfiguration& qnames, BPFFilter::MapFormat format, bool external);
   BPFFilter(const BPFFilter&) = delete;
   BPFFilter(BPFFilter&&) = delete;
   BPFFilter& operator=(const BPFFilter&) = delete;
@@ -50,8 +62,8 @@ public:
 
   void addSocket(int sock);
   void removeSocket(int sock);
-  void block(const ComboAddress& addr);
-  void block(const DNSName& qname, uint16_t qtype=255);
+  void block(const ComboAddress& addr, MatchAction action);
+  void block(const DNSName& qname, MatchAction action, uint16_t qtype=255);
   void unblock(const ComboAddress& addr);
   void unblock(const DNSName& qname, uint16_t qtype=255);
 
@@ -60,6 +72,9 @@ public:
 
   uint64_t getHits(const ComboAddress& requestor);
 
+  bool supportsMatchAction(MatchAction action) const;
+  bool isExternal() const;
+
 private:
 #ifdef HAVE_EBPF
   struct Map
@@ -67,7 +82,7 @@ private:
     Map()
     {
     }
-    Map(const MapConfiguration&);
+    Map(const MapConfiguration&, MapFormat);
     MapConfiguration d_config;
     uint32_t d_count{0};
     FDWrapper d_fd;
@@ -90,5 +105,20 @@ private:
   FDWrapper d_mainfilter;
   /* qname filtering program */
   FDWrapper d_qnamefilter;
+
+  /* whether the maps are in the 'old' format, which we need
+     to keep to prevent going over the 4k instructions per eBPF
+     program limit in kernels < 5.2, as well as the complexity limit:
+     - 32k in Linux 3.18
+     - 64k in Linux 4.7
+     - 96k in Linux 4.12
+     - 128k in Linux 4.14,
+     - 1M in Linux 5.2 */
+  MapFormat d_mapFormat;
+
+  /* whether the filter is internal, using our own eBPF programs,
+     or external where we only update the maps but the filtering is
+     done by an external program. */
+  bool d_external;
 #endif /* HAVE_EBPF */
 };
index a11c928f95cdb682298b315f434ef537b65d8018..ec549c32f96b4cb0cfc590b972564c4c2fdc9919 100644 (file)
@@ -38,7 +38,7 @@ bool DynBPFFilter::block(const ComboAddress& addr, const struct timespec& until)
     }
   }
   else {
-    data->d_bpf->block(addr);
+    data->d_bpf->block(addr, BPFFilter::MatchAction::Drop);
     data->d_entries.insert(BlockEntry(addr, until));
     inserted = true;
   }
index 3830e9d8752e8ed89079e3616343be65c5f55790..43c3d0412271552cc42a57ac14cd3408e62acf37 100644 (file)
@@ -398,10 +398,11 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
   /* BPF Filter */
 #ifdef HAVE_EBPF
   using bpfFilterMapParams = boost::variant<uint32_t, std::unordered_map<std::string, boost::variant<uint32_t, std::string>>>;
-  luaCtx.writeFunction("newBPFFilter", [client](bpfFilterMapParams v4Params, bpfFilterMapParams v6Params, bpfFilterMapParams qnameParams) {
+  luaCtx.writeFunction("newBPFFilter", [client](bpfFilterMapParams v4Params, bpfFilterMapParams v6Params, bpfFilterMapParams qnameParams, boost::optional<bool> external) {
       if (client) {
         return std::shared_ptr<BPFFilter>(nullptr);
       }
+
       BPFFilter::MapConfiguration v4Config, v6Config, qnameConfig;
 
       auto convertParamsToConfig = [](bpfFilterMapParams& params, BPFFilter::MapType type, BPFFilter::MapConfiguration& config) {
@@ -424,18 +425,63 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
       convertParamsToConfig(v6Params, BPFFilter::MapType::IPv6, v6Config);
       convertParamsToConfig(qnameParams, BPFFilter::MapType::QNames, qnameConfig);
 
-      return std::make_shared<BPFFilter>(v4Config, v6Config, qnameConfig);
+      BPFFilter::MapFormat format = BPFFilter::MapFormat::Legacy;
+      if (external && *external) {
+        format = BPFFilter::MapFormat::WithActions;
+      }
+
+      return std::make_shared<BPFFilter>(v4Config, v6Config, qnameConfig, format, external.value_or(false));
     });
 
-  luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("block", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) {
+  luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca, boost::optional<uint32_t> action)>("block", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca, boost::optional<uint32_t> action) {
       if (bpf) {
-        return bpf->block(ca);
+        if (!action) {
+          return bpf->block(ca, BPFFilter::MatchAction::Drop);
+        }
+        else {
+          BPFFilter::MatchAction match;
+
+          switch (*action) {
+          case 0:
+            match = BPFFilter::MatchAction::Pass;
+            break;
+          case 1:
+            match = BPFFilter::MatchAction::Drop;
+            break;
+          case 2:
+            match = BPFFilter::MatchAction::Truncate;
+            break;
+          default:
+            throw std::runtime_error("Unsupported action for BPFFilter::block");
+          }
+          return bpf->block(ca, match);
+        }
       }
     });
 
-  luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("blockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
+  luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action)>("blockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action) {
       if (bpf) {
-        return bpf->block(qname, qtype ? *qtype : 255);
+        if (!action) {
+          return bpf->block(qname, BPFFilter::MatchAction::Drop, qtype.value_or(255));
+        }
+        else {
+          BPFFilter::MatchAction match;
+
+          switch (*action) {
+          case 0:
+            match = BPFFilter::MatchAction::Pass;
+            break;
+          case 1:
+            match = BPFFilter::MatchAction::Drop;
+            break;
+          case 2:
+            match = BPFFilter::MatchAction::Truncate;
+            break;
+          default:
+            throw std::runtime_error("Unsupported action for BPFFilter::block");
+          }
+          return bpf->block(qname, match, qtype.value_or(255));
+        }
       }
     });
 
index 4f9027585f2816998ab219525baebe35f334ddc4..4d43eadec2868f88e1c12b04525606fefd2fbf3a 100644 (file)
@@ -2088,7 +2088,7 @@ static void setUpLocalBind(std::unique_ptr<ClientState>& cs)
   }
 
 #ifdef HAVE_EBPF
-  if (g_defaultBPFFilter) {
+  if (g_defaultBPFFilter && !g_defaultBPFFilter->isExternal()) {
     cs->attachFilter(g_defaultBPFFilter);
     vinfolog("Attaching default BPF Filter to %s frontend %s", (!cs->tcp ? "UDP" : "TCP"), cs->local.toStringWithPort());
   }
index dccf2aa8c12ef41353c1a8179f512fa6bfcf0dd2..3aa007830822d953f8485c53ffa8268894776e2a 100644 (file)
@@ -224,12 +224,16 @@ 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 &&
-        ((requestor.isIPv4() && requestor.getBits() == 32) || (requestor.isIPv6() && requestor.getBits() == 128))) {
+    if (g_defaultBPFFilter &&
+        ((requestor.isIPv4() && requestor.getBits() == 32) || (requestor.isIPv6() && requestor.getBits() == 128)) &&
+        (db.action == DNSAction::Action::Drop || db.action == DNSAction::Action::Truncate)) {
       try {
-        /* the current BPF filter implementation only supports full addresses (/32 or /128) and no port */
-        g_defaultBPFFilter->block(requestor.getNetwork());
-        bpf = true;
+        BPFFilter::MatchAction action = db.action == DNSAction::Action::Drop ? BPFFilter::MatchAction::Drop : BPFFilter::MatchAction::Truncate;
+        if (g_defaultBPFFilter->supportsMatchAction(action)) {
+          /* the current BPF filter implementation only supports full addresses (/32 or /128) and no port */
+          g_defaultBPFFilter->block(requestor.getNetwork(), action);
+          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());