]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Implement filesystem pinning for eBPF maps
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 22 Oct 2021 15:03:27 +0000 (17:03 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 16 Nov 2021 08:02:37 +0000 (09:02 +0100)
This makes the filter (v4, v6 and qnames) maps persistent across a
restart and allow external programs to read and update them without
the need to use dnsdist's console.

pdns/bpf-filter.cc
pdns/bpf-filter.hh
pdns/bpf-filter.main.ebpf
pdns/bpf-filter.qname.ebpf
pdns/dnsdist-lua-bindings.cc
pdns/dnsdistdist/docs/reference/ebpf.rst

index 31c4c2e0f38c45a123b2dba6b983e7bc388fcc2c..55dc241d9ade6d4e3c60290d4e6cdf9a5e4e3757 100644 (file)
@@ -47,6 +47,51 @@ int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
   return syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
 }
 
+int bpf_pin_map(int fd, const std::string& path)
+{
+  union bpf_attr attr;
+  memset(&attr, 0, sizeof(attr));
+  attr.bpf_fd = fd;
+  attr.pathname = ptr_to_u64(const_cast<char*>(path.c_str()));
+  return syscall(SYS_bpf, BPF_OBJ_PIN, &attr, sizeof(attr));
+}
+
+int bpf_load_pinned_map(const std::string& path)
+{
+  union bpf_attr attr;
+  memset(&attr, 0, sizeof(attr));
+  attr.pathname = ptr_to_u64(const_cast<char*>(path.c_str()));
+  return syscall(SYS_bpf, BPF_OBJ_GET, &attr, sizeof(attr));
+}
+
+void bpf_check_map_sizes(int fd, uint32_t expectedKeySize, uint32_t expectedValueSize)
+{
+  struct bpf_map_info info;
+  uint32_t info_len = sizeof(info);
+  memset(&info, 0, sizeof(info));
+
+  union bpf_attr attr;
+  memset(&attr, 0, sizeof(attr));
+  attr.info.bpf_fd = fd;
+  attr.info.info_len = info_len;
+  attr.info.info = ptr_to_u64(&info);
+
+  int err = syscall(SYS_bpf, BPF_OBJ_GET_INFO_BY_FD, &attr, sizeof(attr));
+  if (err != 0) {
+    throw std::runtime_error("Error checking the size of eBPF map: " + stringerror());
+  }
+  if (info_len != sizeof(info)) {
+    throw std::runtime_error("Error checking the size of eBPF map: invalid info size returned");
+  }
+  if (info.key_size != expectedKeySize) {
+    throw std::runtime_error("Error checking the size of eBPF map: key size mismatch (" + std::to_string(info.key_size) + " VS " + std::to_string(expectedKeySize) + ")");
+  }
+  if (info.value_size != expectedValueSize) {
+    throw std::runtime_error("Error checking the size of eBPF map: value size mismatch (" + std::to_string(info.value_size) + " VS " + std::to_string(expectedValueSize) + ")");
+  }
+
+}
+
 int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags)
 {
   union bpf_attr attr;
@@ -139,58 +184,139 @@ struct QNameValue
   uint16_t qtype;
 };
 
-BPFFilter::BPFFilter(uint32_t maxV4Addresses, uint32_t maxV6Addresses, uint32_t maxQNames): d_maps(Maps()), d_maxV4(maxV4Addresses), d_maxV6(maxV6Addresses), d_maxQNames(maxQNames)
+BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config): d_config(config)
 {
-  auto maps = d_maps.lock();
-  maps->d_v4map = FDWrapper(bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint64_t), (int) maxV4Addresses));
-  if (maps->d_v4map.getHandle() == -1) {
-    throw std::runtime_error("Error creating a BPF v4 map of size " + std::to_string(maxV4Addresses) + ": " + stringerror());
+  if (d_config.d_type == BPFFilter::MapType::Filters) {
+    /* special case, this is a map of eBPF programs */
+    d_fd = FDWrapper(bpf_create_map(BPF_MAP_TYPE_PROG_ARRAY, sizeof(uint32_t), sizeof(uint32_t), d_config.d_maxItems));
+    if (d_fd.getHandle() == -1) {
+      throw std::runtime_error("Error creating a BPF program map of size " + std::to_string(d_config.d_maxItems) + ": " + stringerror());
+    }
   }
+  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)));
+    }
 
-  maps->d_v6map = FDWrapper(bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(struct KeyV6), sizeof(uint64_t), (int) maxV6Addresses));
-  if (maps->d_v6map.getHandle() == -1) {
-    throw std::runtime_error("Error creating a BPF v6 map of size " + std::to_string(maxV6Addresses) + ": " + stringerror());
-  }
+    if (!d_config.d_pinnedPath.empty()) {
+      /* try to load */
+      d_fd = FDWrapper(bpf_load_pinned_map(d_config.d_pinnedPath));
+      if (d_fd.getHandle() != -1) {
+        /* sanity checks: key and value size */
+        bpf_check_map_sizes(d_fd.getHandle(), keySize, valueSize);
+
+        if (d_config.d_type == MapType::IPv4) {
+          uint32_t key = 0;
+          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 if (d_config.d_type == MapType::IPv6) {
+          KeyV6 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 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 (d_fd.getHandle() == -1) {
+      d_fd = FDWrapper(bpf_create_map(BPF_MAP_TYPE_HASH, keySize, valueSize, static_cast<int>(d_config.d_maxItems)));
+      if (d_fd.getHandle() == -1) {
+        throw std::runtime_error("Error creating a BPF map of size " + std::to_string(d_config.d_maxItems) + ": " + stringerror());
+      }
 
-  maps->d_qnamemap = FDWrapper(bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(struct QNameKey), sizeof(struct QNameValue), (int) maxQNames));
-  if (maps->d_qnamemap.getHandle() == -1) {
-    throw std::runtime_error("Error creating a BPF qname map of size " + std::to_string(maxQNames) + ": " + stringerror());
+      if (!d_config.d_pinnedPath.empty()) {
+        if (bpf_pin_map(d_fd.getHandle(), d_config.d_pinnedPath) != 0) {
+          throw std::runtime_error("Unable to pin map to path '" + d_config.d_pinnedPath + "': " + stringerror());
+        }
+      }
+    }
   }
+}
 
-  maps->d_filtermap = FDWrapper(bpf_create_map(BPF_MAP_TYPE_PROG_ARRAY, sizeof(uint32_t), sizeof(uint32_t), 1));
-  if (maps->d_filtermap.getHandle() == -1) {
-    throw std::runtime_error("Error creating a BPF program map of size 1: " + stringerror());
+static FDWrapper loadProgram(const struct bpf_insn* filter, size_t filterSize)
+{
+  auto fd = FDWrapper(bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
+                                    filter,
+                                    filterSize,
+                                    "GPL",
+                                    0));
+  if (fd.getHandle() == -1) {
+    throw std::runtime_error("error loading BPF filter: " + stringerror());
   }
+  return fd;
+}
+
+
+BPFFilter::BPFFilter(const BPFFilter::MapConfiguration& v4, const BPFFilter::MapConfiguration& v6, const BPFFilter::MapConfiguration& qnames)
+{
+  auto maps = d_maps.lock();
 
-  struct bpf_insn main_filter[] = {
+  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);
+
+  const struct bpf_insn main_filter[] = {
 #include "bpf-filter.main.ebpf"
   };
 
-  d_mainfilter = FDWrapper(bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
-                                         main_filter,
-                                         sizeof(main_filter),
-                                         "GPL",
-                                         0));
-  if (d_mainfilter.getHandle() == -1) {
-    throw std::runtime_error("Error loading BPF main filter: " + stringerror());
-  }
-
-  struct bpf_insn qname_filter[] = {
+  const struct bpf_insn qname_filter[] = {
 #include "bpf-filter.qname.ebpf"
   };
 
-  d_qnamefilter = FDWrapper(bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
-                                          qname_filter,
-                                          sizeof(qname_filter),
-                                          "GPL",
-                                          0));
-  if (d_qnamefilter.getHandle() == -1) {
-    throw std::runtime_error("Error loading BPF qname filter: " + stringerror());
+  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()));
   }
 
   uint32_t key = 0;
   int qnamefd = d_qnamefilter.getHandle();
-  int res = bpf_update_elem(maps->d_filtermap.getHandle(), &key, &qnamefd, BPF_ANY);
+  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());
   }
@@ -223,18 +349,19 @@ void BPFFilter::block(const ComboAddress& addr)
   if (addr.isIPv4()) {
     uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
     auto maps = d_maps.lock();
-    if (maps->d_v4Count >= d_maxV4) {
+    auto& map = maps->d_v4;
+    if (map.d_count >= map.d_config.d_maxItems) {
       throw std::runtime_error("Table full when trying to block " + addr.toString());
     }
 
-    res = bpf_lookup_elem(maps->d_v4map.getHandle(), &key, &counter);
+    res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
     if (res != -1) {
       throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
     }
 
-    res = bpf_update_elem(maps->d_v4map.getHandle(), &key, &counter, BPF_NOEXIST);
+    res = bpf_update_elem(map.d_fd.getHandle(), &key, &counter, BPF_NOEXIST);
     if (res == 0) {
-      maps->d_v4Count++;
+      ++map.d_count;
     }
   }
   else if (addr.isIPv6()) {
@@ -245,18 +372,19 @@ void BPFFilter::block(const ComboAddress& addr)
     }
 
     auto maps = d_maps.lock();
-    if (maps->d_v6Count >= d_maxV6) {
+    auto& map = maps->d_v6;
+    if (map.d_count >= map.d_config.d_maxItems) {
       throw std::runtime_error("Table full when trying to block " + addr.toString());
     }
 
-    res = bpf_lookup_elem(maps->d_v6map.getHandle(), &key, &counter);
+    res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
     if (res != -1) {
       throw std::runtime_error("Trying to block an already blocked address: " + addr.toString());
     }
 
-    res = bpf_update_elem(maps->d_v6map.getHandle(), key, &counter, BPF_NOEXIST);
+    res = bpf_update_elem(map.d_fd.getHandle(), key, &counter, BPF_NOEXIST);
     if (res == 0) {
-      maps->d_v6Count++;
+      map.d_count++;
     }
   }
 
@@ -271,9 +399,10 @@ void BPFFilter::unblock(const ComboAddress& addr)
   if (addr.isIPv4()) {
     uint32_t key = htonl(addr.sin4.sin_addr.s_addr);
     auto maps = d_maps.lock();
-    res = bpf_delete_elem(maps->d_v4map.getHandle(), &key);
+    auto& map = maps->d_v4;
+    res = bpf_delete_elem(map.d_fd.getHandle(), &key);
     if (res == 0) {
-      maps->d_v4Count--;
+      --map.d_count;
     }
   }
   else if (addr.isIPv6()) {
@@ -284,9 +413,10 @@ void BPFFilter::unblock(const ComboAddress& addr)
     }
 
     auto maps = d_maps.lock();
-    res = bpf_delete_elem(maps->d_v6map.getHandle(), key);
+    auto& map = maps->d_v6;
+    res = bpf_delete_elem(map.d_fd.getHandle(), key);
     if (res == 0) {
-      maps->d_v6Count--;
+      --map.d_count;
     }
   }
 
@@ -312,18 +442,19 @@ void BPFFilter::block(const DNSName& qname, uint16_t qtype)
 
   {
     auto maps = d_maps.lock();
-    if (maps->d_qNamesCount >= d_maxQNames) {
+    auto& map = maps->d_qnames;
+    if (map.d_count >= map.d_config.d_maxItems) {
       throw std::runtime_error("Table full when trying to block " + qname.toLogString());
     }
 
-    int res = bpf_lookup_elem(maps->d_qnamemap.getHandle(), &key, &value);
+    int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &value);
     if (res != -1) {
       throw std::runtime_error("Trying to block an already blocked qname: " + qname.toLogString());
     }
 
-    res = bpf_update_elem(maps->d_qnamemap.getHandle(), &key, &value, BPF_NOEXIST);
+    res = bpf_update_elem(map.d_fd.getHandle(), &key, &value, BPF_NOEXIST);
     if (res == 0) {
-      maps->d_qNamesCount++;
+      ++map.d_count;
     }
 
     if (res != 0) {
@@ -345,9 +476,10 @@ void BPFFilter::unblock(const DNSName& qname, uint16_t qtype)
 
   {
     auto maps = d_maps.lock();
-    int res = bpf_delete_elem(maps->d_qnamemap.getHandle(), &key);
+    auto& map = maps->d_qnames;
+    int res = bpf_delete_elem(map.d_fd.getHandle(), &key);
     if (res == 0) {
-      maps->d_qNamesCount--;
+      --map.d_count;
     }
     else {
       throw std::runtime_error("Error removing qname address " + qname.toLogString() + ": " + stringerror());
@@ -360,7 +492,7 @@ std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
   std::vector<std::pair<ComboAddress, uint64_t> > result;
   {
     auto maps = d_maps.lock();
-    result.reserve(maps->d_v4Count + maps->d_v6Count);
+    result.reserve(maps->d_v4.d_count + maps->d_v6.d_count);
   }
 
   sockaddr_in v4Addr;
@@ -381,29 +513,37 @@ std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
   memset(&v6Key, 0, sizeof(v6Key));
 
   auto maps = d_maps.lock();
-  int res = bpf_get_next_key(maps->d_v4map.getHandle(), &v4Key, &nextV4Key);
 
-  while (res == 0) {
-    v4Key = nextV4Key;
-    if (bpf_lookup_elem(maps->d_v4map.getHandle(), &v4Key, &value) == 0) {
+  {
+    auto& map = maps->d_v4;
+    int res = bpf_get_next_key(map.d_fd.getHandle(), &v4Key, &nextV4Key);
+
+    while (res == 0) {
+      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);
-    }
+      }
 
-    res = bpf_get_next_key(maps->d_v4map.getHandle(), &v4Key, &nextV4Key);
+      res = bpf_get_next_key(map.d_fd.getHandle(), &v4Key, &nextV4Key);
+    }
   }
 
-  res = bpf_get_next_key(maps->d_v6map.getHandle(), &v6Key, &nextV6Key);
+  {
+    auto& map = maps->d_v6;
+    int res = bpf_get_next_key(map.d_fd.getHandle(), &v6Key, &nextV6Key);
 
-  while (res == 0) {
-    if (bpf_lookup_elem(maps->d_v6map.getHandle(), &nextV6Key, &value) == 0) {
-      memcpy(&v6Addr.sin6_addr.s6_addr, &nextV6Key, sizeof(nextV6Key));
+    while (res == 0) {
+      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);
+      }
 
-    res = bpf_get_next_key(maps->d_v6map.getHandle(), &nextV6Key, &nextV6Key);
+      res = bpf_get_next_key(map.d_fd.getHandle(), &nextV6Key, &nextV6Key);
+    }
   }
+
   return result;
 }
 
@@ -416,16 +556,17 @@ std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats()
   struct QNameValue value;
 
   auto maps = d_maps.lock();
-  result.reserve(maps->d_qNamesCount);
-  int res = bpf_get_next_key(maps->d_qnamemap.getHandle(), &key, &nextKey);
+  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(maps->d_qnamemap.getHandle(), &nextKey, &value) == 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));
     }
 
-    res = bpf_get_next_key(maps->d_qnamemap.getHandle(), &nextKey, &nextKey);
+    res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
   }
   return result;
 }
@@ -437,7 +578,8 @@ uint64_t BPFFilter::getHits(const ComboAddress& requestor)
     uint32_t key = htonl(requestor.sin4.sin_addr.s_addr);
 
     auto maps = d_maps.lock();
-    int res = bpf_lookup_elem(maps->d_v4map.getHandle(), &key, &counter);
+    auto& map = maps->d_v4;
+    int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
     if (res == 0) {
       return counter;
     }
@@ -450,7 +592,8 @@ uint64_t BPFFilter::getHits(const ComboAddress& requestor)
     }
 
     auto maps = d_maps.lock();
-    int res = bpf_lookup_elem(maps->d_v6map.getHandle(), &key, &counter);
+    auto& map = maps->d_v6;
+    int res = bpf_lookup_elem(map.d_fd.getHandle(), &key, &counter);
     if (res == 0) {
       return counter;
     }
@@ -461,48 +604,37 @@ uint64_t BPFFilter::getHits(const ComboAddress& requestor)
 
 #else
 
-BPFFilter::BPFFilter(uint32_t maxV4Addresses, uint32_t maxV6Addresses, uint32_t maxQNames)
+BPFFilter::BPFFilter(const BPFFilter::MapConfiguration&, const BPFFilter::MapConfiguration&, const BPFFilter::MapConfiguration&)
 {
-  (void) maxV4Addresses;
-  (void) maxV6Addresses;
-  (void) maxQNames;
 }
 
-void BPFFilter::addSocket(int sock)
+void BPFFilter::addSocket(int)
 {
-  (void) sock;
   throw std::runtime_error("eBPF support not enabled");
 }
 
-void BPFFilter::removeSocket(int sock)
+void BPFFilter::removeSocket(int)
 {
-  (void) sock;
   throw std::runtime_error("eBPF support not enabled");
 }
 
-void BPFFilter::block(const ComboAddress& addr)
+void BPFFilter::block(const ComboAddress&)
 {
-  (void) addr;
   throw std::runtime_error("eBPF support not enabled");
 }
 
-void BPFFilter::unblock(const ComboAddress& addr)
+void BPFFilter::unblock(const ComboAddress&)
 {
-  (void) addr;
   throw std::runtime_error("eBPF support not enabled");
 }
 
-void BPFFilter::block(const DNSName& qname, uint16_t qtype)
+void BPFFilter::block(const DNSName&, uint16_t)
 {
-  (void) qname;
-  (void) qtype;
   throw std::runtime_error("eBPF support not enabled");
 }
 
-void BPFFilter::unblock(const DNSName& qname, uint16_t qtype)
+void BPFFilter::unblock(const DNSName&, uint16_t)
 {
-  (void) qname;
-  (void) qtype;
   throw std::runtime_error("eBPF support not enabled");
 }
 
@@ -518,9 +650,8 @@ std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats()
   return result;
 }
 
-uint64_t BPFFilter::getHits(const ComboAddress& requestor)
+uint64_t BPFFilter::getHits(const ComboAddress&)
 {
-  (void) requestor;
   return 0;
 }
 #endif /* HAVE_EBPF */
index 482b61e31abd6e6b16614e0b0d69b425ee533793..6dce2dbc97eeb5ff8abeeb58c0200ed602e8f09c 100644 (file)
 class BPFFilter
 {
 public:
-  BPFFilter(uint32_t maxV4Addresses, uint32_t maxV6Addresses, uint32_t maxQNames);
+  enum class MapType : uint8_t {
+    IPv4,
+    IPv6,
+    QNames,
+    Filters
+  };
+
+  struct MapConfiguration
+  {
+    std::string d_pinnedPath;
+    uint32_t d_maxItems{0};
+    MapType d_type;
+  };
+
+  BPFFilter(const BPFFilter::MapConfiguration& v4, const BPFFilter::MapConfiguration& v6, const BPFFilter::MapConfiguration& qnames);
+  BPFFilter(const BPFFilter&) = delete;
+  BPFFilter(BPFFilter&&) = delete;
+  BPFFilter& operator=(const BPFFilter&) = delete;
+  BPFFilter& operator=(BPFFilter&&) = delete;
+
   void addSocket(int sock);
   void removeSocket(int sock);
   void block(const ComboAddress& addr);
   void block(const DNSName& qname, uint16_t qtype=255);
   void unblock(const ComboAddress& addr);
   void unblock(const DNSName& qname, uint16_t qtype=255);
+
   std::vector<std::pair<ComboAddress, uint64_t> > getAddrStats();
   std::vector<std::tuple<DNSName, uint16_t, uint64_t> > getQNameStats();
+
   uint64_t getHits(const ComboAddress& requestor);
 
 private:
 #ifdef HAVE_EBPF
+  struct Map
+  {
+    Map()
+    {
+    }
+    Map(const MapConfiguration&);
+    MapConfiguration d_config;
+    uint32_t d_count{0};
+    FDWrapper d_fd;
+  };
+
   struct Maps
   {
-    FDWrapper d_v4map;
-    FDWrapper d_v6map;
-    FDWrapper d_qnamemap;
-    FDWrapper d_filtermap;
-    uint32_t d_v4Count{0};
-    uint32_t d_v6Count{0};
-    uint32_t d_qNamesCount{0};
+    Map d_v4;
+    Map d_v6;
+    Map d_qnames;
+    /* The qname filter program held in d_qnamefilter is
+       stored in an eBPF map, so we can call it from the
+       main filter. This is the only entry in that map. */
+    Map d_filters;
   };
+
   LockGuarded<Maps> d_maps;
+
+  /* main eBPF program */
   FDWrapper d_mainfilter;
+  /* qname filtering program */
   FDWrapper d_qnamefilter;
-  uint32_t d_maxV4;
-  uint32_t d_maxV6;
-  uint32_t d_maxQNames;
 #endif /* HAVE_EBPF */
 };
index 8a82f3bce4723e43f20d4fd4bf26b3dded8224bb..4b42a2ec6b71ea0237e7808d2782716bfee9b35f 100644 (file)
@@ -6,7 +6,7 @@ BPF_JMP_IMM(BPF_JEQ,BPF_REG_1,ntohs(0x86dd),11),
 BPF_JMP_IMM(BPF_JNE,BPF_REG_1,ntohs(0x0800),109),
 BPF_LD_ABS(BPF_W,-2097126),
 BPF_STX_MEM(BPF_W,BPF_REG_10,BPF_REG_0,-256),
-BPF_LD_MAP_FD(BPF_REG_1,maps->d_v4map.getHandle()),
+BPF_LD_MAP_FD(BPF_REG_1,maps->d_v4.d_fd.getHandle()),
 BPF_MOV64_REG(BPF_REG_2,BPF_REG_10),
 BPF_ALU64_IMM(BPF_ADD,BPF_REG_2,-256),
 BPF_RAW_INSN(BPF_JMP|BPF_CALL,0,0,0,BPF_FUNC_map_lookup_elem),
@@ -45,7 +45,7 @@ BPF_LD_ABS(BPF_B,-2097116),
 BPF_STX_MEM(BPF_B,BPF_REG_10,BPF_REG_0,-242),
 BPF_LD_ABS(BPF_B,-2097115),
 BPF_STX_MEM(BPF_B,BPF_REG_10,BPF_REG_0,-241),
-BPF_LD_MAP_FD(BPF_REG_1,maps->d_v6map.getHandle()),
+BPF_LD_MAP_FD(BPF_REG_1,maps->d_v6.d_fd.getHandle()),
 BPF_MOV64_REG(BPF_REG_2,BPF_REG_10),
 BPF_ALU64_IMM(BPF_ADD,BPF_REG_2,-256),
 BPF_RAW_INSN(BPF_JMP|BPF_CALL,0,0,0,BPF_FUNC_map_lookup_elem),
@@ -97,7 +97,7 @@ BPF_JMP_IMM(BPF_JGT,BPF_REG_8,63,17),
 BPF_JMP_IMM(BPF_JNE,BPF_REG_8,0,18),
 BPF_LD_ABS(BPF_H,21),
 BPF_MOV64_REG(BPF_REG_6,BPF_REG_0),
-BPF_LD_MAP_FD(BPF_REG_1,maps->d_qnamemap.getHandle()),
+BPF_LD_MAP_FD(BPF_REG_1,maps->d_qnames.d_fd.getHandle()),
 BPF_MOV64_REG(BPF_REG_2,BPF_REG_10),
 BPF_ALU64_IMM(BPF_ADD,BPF_REG_2,-256),
 BPF_RAW_INSN(BPF_JMP|BPF_CALL,0,0,0,BPF_FUNC_map_lookup_elem),
@@ -128,7 +128,7 @@ BPF_ALU64_IMM(BPF_ADD,BPF_REG_8,-1),
 BPF_STX_MEM(BPF_W,BPF_REG_6,BPF_REG_8,60),
 BPF_ALU64_IMM(BPF_AND,BPF_REG_1,255),
 BPF_STX_MEM(BPF_W,BPF_REG_6,BPF_REG_1,56),
-BPF_LD_MAP_FD(BPF_REG_2,maps->d_filtermap.getHandle()),
+BPF_LD_MAP_FD(BPF_REG_2,maps->d_filters.d_fd.getHandle()),
 BPF_MOV64_REG(BPF_REG_1,BPF_REG_6),
 BPF_MOV64_IMM(BPF_REG_3,0),
 BPF_RAW_INSN(BPF_JMP|BPF_CALL,0,0,0,BPF_FUNC_tail_call),
index 7eb0519bd10e092e0266f2db5233cf06de7ed66d..c7d6094c0872ed85619131878bdc6b595d7ba037 100644 (file)
@@ -4078,7 +4078,7 @@ BPF_MOV64_IMM(BPF_REG_9,255),
 BPF_ALU64_REG(BPF_ADD,BPF_REG_9,BPF_REG_7),
 BPF_RAW_INSN(BPF_LD|BPF_IND|BPF_H,BPF_REG_0,BPF_REG_9,0,0),
 BPF_MOV64_REG(BPF_REG_6,BPF_REG_0),
-BPF_LD_MAP_FD(BPF_REG_1,maps->d_qnamemap.getHandle()),
+BPF_LD_MAP_FD(BPF_REG_1,maps->d_qnames.d_fd.getHandle()),
 BPF_MOV64_REG(BPF_REG_2,BPF_REG_10),
 BPF_ALU64_IMM(BPF_ADD,BPF_REG_2,-256),
 BPF_RAW_INSN(BPF_JMP|BPF_CALL,0,0,0,BPF_FUNC_map_lookup_elem),
index f68f855b9bf7cc6c4d857c47d7aa4672bf084e6a..3830e9d8752e8ed89079e3616343be65c5f55790 100644 (file)
@@ -397,11 +397,34 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
 
   /* BPF Filter */
 #ifdef HAVE_EBPF
-  luaCtx.writeFunction("newBPFFilter", [client](uint32_t maxV4, uint32_t maxV6, uint32_t maxQNames) {
+  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) {
       if (client) {
         return std::shared_ptr<BPFFilter>(nullptr);
       }
-      return std::make_shared<BPFFilter>(maxV4, maxV6, maxQNames);
+      BPFFilter::MapConfiguration v4Config, v6Config, qnameConfig;
+
+      auto convertParamsToConfig = [](bpfFilterMapParams& params, BPFFilter::MapType type, BPFFilter::MapConfiguration& config) {
+        config.d_type = type;
+        if (params.type() == typeid(uint32_t)) {
+          config.d_maxItems = boost::get<uint32_t>(params);
+        }
+        else if (params.type() == typeid(std::unordered_map<std::string, boost::variant<uint32_t, std::string>>)) {
+          auto map = boost::get<std::unordered_map<std::string, boost::variant<uint32_t, std::string>>>(params);
+          if (map.count("maxItems")) {
+            config.d_maxItems = boost::get<uint32_t>(map.at("maxItems"));
+          }
+          if (map.count("pinnedPath")) {
+            config.d_pinnedPath = boost::get<std::string>(map.at("pinnedPath"));
+          }
+        }
+      };
+
+      convertParamsToConfig(v4Params, BPFFilter::MapType::IPv4, v4Config);
+      convertParamsToConfig(v6Params, BPFFilter::MapType::IPv6, v6Config);
+      convertParamsToConfig(qnameParams, BPFFilter::MapType::QNames, qnameConfig);
+
+      return std::make_shared<BPFFilter>(v4Config, v6Config, qnameConfig);
     });
 
   luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("block", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) {
index 3b12f57d6ab51c9d4bb21a22e6b469c91ff9215e..f723c1b51c30599ac672c903a4803e40ef110553 100644 (file)
@@ -14,13 +14,27 @@ These are all the functions, objects and methods related to the :doc:`../advance
   :param str msg: A message to display while inserting the block
 
 .. function:: newBPFFilter(maxV4, maxV6, maxQNames) -> BPFFilter
+              newBPFFilter(v4Parameters, v6Parameters, qnamesParameters) -> BPFFilter
 
-  Return a new eBPF socket filter with a maximum of maxV4 IPv4, maxV6 IPv6 and maxQNames qname entries in the block table.
+  .. versionchanged:: 1.7.0
+    This function now supports a table for each parameters, and the ability to use pinned eBPF maps.
+
+  Return a new eBPF socket filter with a maximum of maxV4 IPv4, maxV6 IPv6 and maxQNames qname entries in the block tables.
+  Maps can be pinned to a filesystem path, which makes their content persistent across restarts and allows external programs to read their content and to add new entries. dnsdist will try to load maps that are pinned to a filesystem path on startups, inheriting any existing entries, and fall back to creating them if they do not exist yet. Note that the user dnsdist is running under must have the right privileges to read and write to the given file, and to go through all the directories in the path leading to that file.
 
   :param int maxV4: Maximum number of IPv4 entries in this filter
   :param int maxV6: Maximum number of IPv6 entries in this filter
   :param int maxQNames: Maximum number of QName entries in this filter
 
+  :param table v4Params: A table of options for the IPv4 filter map, see below
+  :param table v6Params: A table of options for the IPv6 filter map, see below
+  :param table qnameParams: A table of options for the qnames filter map, see below
+
+  Options:
+
+  * ``maxItems``: int - The maximum number of entries in a given map. Default is 0 which will not allow any entry at all.
+  * ``pinnedPaths``: str - The filesystem path this map should be pinned to.
+
 .. function:: newDynBPFFilter(bpf) -> DynBPFFilter
 
   Return a new dynamic eBPF filter associated to a given BPF Filter.