]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Display the dyn eBPF filters stats in the web interface 4068/head
authorRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 29 Jun 2016 13:05:50 +0000 (15:05 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 11 Aug 2016 13:11:32 +0000 (15:11 +0200)
Dynamic BPF filters need to be registered to appear in the interface,
and unregistered when not needed anymore.
Automatic registration would mean that dangling dynamic BPF filters could
not be garbage collected without being unregistered first.

12 files changed:
pdns/README-dnsdist.md
pdns/bpf-filter.cc
pdns/bpf-filter.hh
pdns/dnsdist-console.cc
pdns/dnsdist-dynbpf.cc
pdns/dnsdist-dynbpf.hh
pdns/dnsdist-lua2.cc
pdns/dnsdist-web.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/html/index.html
pdns/dnsdistdist/html/local.js

index bd91fa39003dec15b9d89791aeddebe88d52b681..edbadb981e864edaf8b9dfdeaabdea2a264c0c76 100644 (file)
@@ -1160,6 +1160,7 @@ Here are all functions:
     * function `showBinds()`: list every local bind
     * function `getBind(n)`: return the corresponding `ClientState` object
     * member `attachFilter(BPFFilter)`: attach a BPF Filter to this bind
+    * member `detachFilter()`: detach the BPF Filter attached to this bind, if any
     * member `toString()`: print the address this bind listens to
  * Network related:
     * `addLocal(netmask, [true], [false], [TCP Fast Open queue size])`: add to addresses we listen on. Second optional parameter sets TCP or not. Third optional parameter sets SO_REUSEPORT when available. Last parameter sets the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0.
@@ -1413,6 +1414,8 @@ instantiate a server with additional parameters
     * function `newDynBPFFilter(BPFFilter)`: return a new DynBPFFilter object using this BPF Filter
     * member `block(ComboAddress[, seconds]): add this address to the underlying BPF Filter for `seconds` seconds (default to 10 seconds)
     * member `purgeExpired()`: remove expired entries
+    * function `registerDynBPFFilter(DynBPFFilter)`: register this dynamic BPF filter into the web interface so that its counters are displayed
+    * function `unregisterDynBPFFilter(DynBPFFilter)`: unregister this dynamic BPF filter
  * RemoteLogger related:
     * `newRemoteLogger(address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1])`: create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`
 
index 90a2031271585591bdb0ccb1f09d86f170d7e2b7..020d97c3e2d7caa3264948c39140c9f7d6ce56eb 100644 (file)
@@ -174,6 +174,15 @@ void BPFFilter::addSocket(int sock)
   }
 }
 
+void BPFFilter::removeSocket(int sock)
+{
+  int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &d_mainfilter.fd, sizeof(d_mainfilter.fd));
+
+  if (res != 0) {
+    throw std::runtime_error("Error detaching BPF filter from this socket: " + std::string(strerror(errno)));
+  }
+}
+
 void BPFFilter::block(const ComboAddress& addr)
 {
   std::unique_lock<std::mutex> lock(d_mutex);
index 38c65eb6404b44f5265bec59b0fe5ea6d4b38946..b23bd04314e5225f0309fe6ce5b88031a4b8558c 100644 (file)
@@ -12,6 +12,7 @@ class BPFFilter
 public:
   BPFFilter(uint32_t maxV4Addresses, uint32_t maxV6Addresses, uint32_t maxQNames);
   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);
index 4a7f05412a8f1d8caf679c606ccb36c896144d05..4f3dab28f190d4b84be5292ecaa46f8cefffdd8e 100644 (file)
@@ -282,6 +282,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "PoolAction", true, "poolname", "set the packet into the specified pool" },
   { "printDNSCryptProviderFingerprint", true, "\"/path/to/providerPublic.key\"", "display the fingerprint of the provided resolver public key" },
   { "RegexRule", true, "regex", "matches the query name against the supplied regex" },
+  { "registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed" },
   { "RemoteLogAction", true, "RemoteLogger", "send the content of this query to a remote logger via Protocol Buffer" },
   { "RemoteLogResponseAction", true, "RemoteLogger", "send the content of this response to a remote logger via Protocol Buffer" },
   { "rmResponseRule", true, "n", "remove response rule n" },
@@ -329,6 +330,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "topRule", true, "", "move the last rule to the first position" },
   { "topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds, grouped by last `labels` labels" },
   { "truncateTC", true, "bool", "if set (default) truncate TC=1 answers so they are actually empty. Fixes an issue for PowerDNS Authoritative Server 2.9.22" },
+  { "unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter" },
   { "webserver", true, "address:port, password [, apiKey [, customHeaders ]])", "launch a webserver with stats on that address with that password" },
   { "whashed", false, "", "Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter" },
   { "wrandom", false, "", "Weighted random over available servers, based on the server 'weight' parameter" },
index a8bd68a2dcf835cbf4de6593eeee656ac4ed2ef5..c3c0f68fc0a9baad3dccc3614fb2263e0c4cb64a 100644 (file)
@@ -37,4 +37,21 @@ void DynBPFFilter::purgeExpired(const struct timespec& now)
   }
 }
 
+std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > DynBPFFilter::getAddrStats()
+{
+  std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > result;
+  if (!d_bpf) {
+    return result;
+  }
+
+  const auto& stats = d_bpf->getAddrStats();
+  for (const auto& stat : stats) {
+    const container_t::iterator it = d_entries.find(stat.first);
+    if (it != d_entries.end()) {
+      result.push_back({stat.first, stat.second, it->d_until});
+    }
+  }
+  return result;
+}
+
 #endif /* HAVE_EBPF */
index ebd98761129638c760d9040174ccaeb2060515f7..a05fe55dacf59b7a4a277a4d88807b96c341396c 100644 (file)
@@ -22,6 +22,7 @@ public:
   }
   void block(const ComboAddress& addr, const struct timespec& until);
   void purgeExpired(const struct timespec& now);
+  std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > getAddrStats();
 private:
   struct BlockEntry
   {
index 12066df03a3e88110cd7dbda32fbdc8fe44d44e5..ce2ae0b5b394b192ca3bf4d0ace562b658c078af 100644 (file)
@@ -871,15 +871,19 @@ void moreLua(bool client)
     g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)()>("attachToAllBinds", [](std::shared_ptr<BPFFilter> bpf) {
         std::string res;
         if (bpf) {
-          for (const auto& front : g_frontends) {
-            bpf->addSocket(front->udpFD != -1 ? front->udpFD : front->tcpFD);
+          for (const auto& frontend : g_frontends) {
+            frontend->attachFilter(bpf);
           }
         }
       });
 
+    g_lua.registerFunction<void(ClientState::*)()>("detachFilter", [](ClientState& frontend) {
+        frontend.detachFilter();
+    });
+
     g_lua.registerFunction<void(ClientState::*)(std::shared_ptr<BPFFilter>)>("attachFilter", [](ClientState& frontend, std::shared_ptr<BPFFilter> bpf) {
         if (bpf) {
-          bpf->addSocket(frontend.udpFD != -1 ? frontend.udpFD : frontend.tcpFD);
+          frontend.attachFilter(bpf);
         }
     });
 
@@ -895,6 +899,23 @@ void moreLua(bool client)
         return std::make_shared<DynBPFFilter>(bpf);
       });
 
+    g_lua.writeFunction("registerDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
+        if (dbpf) {
+          g_dynBPFFilters.push_back(dbpf);
+        }
+      });
+
+    g_lua.writeFunction("unregisterDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
+        if (dbpf) {
+          for (auto it = g_dynBPFFilters.cbegin(); it != g_dynBPFFilters.cend(); it++) {
+            if (*it == dbpf) {
+              g_dynBPFFilters.erase(it);
+              break;
+            }
+          }
+        }
+      });
+
     g_lua.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(const ComboAddress& addr, boost::optional<int> seconds)>("block", [](std::shared_ptr<DynBPFFilter> dbpf, const ComboAddress& addr, boost::optional<int> seconds) {
         if (dbpf) {
           struct timespec until;
index e5a6e9939cd877f1696562d1b9ec58b11fae1fd7..28d0460587d2361b35acd407e3c53c7ab456ddae 100644 (file)
@@ -180,8 +180,11 @@ static void connectionThread(int sock, ComboAddress remote, string password, str
         gettime(&now);
         for(const auto& e: slow) {
           if(now < e->second.until ) {
-            Json::object thing{{"reason", e->second.reason}, {"seconds", (double)(e->second.until.tv_sec - now.tv_sec)},
-                                                            {"blocks", (double)e->second.blocks} };
+            Json::object thing{
+              {"reason", e->second.reason},
+              {"seconds", (double)(e->second.until.tv_sec - now.tv_sec)},
+              {"blocks", (double)e->second.blocks}
+            };
             obj.insert({e->first.toString(), thing});
           }
         }
@@ -201,6 +204,27 @@ static void connectionThread(int sock, ComboAddress remote, string password, str
 
 
 
+        Json my_json = obj;
+        resp.body=my_json.dump();
+        resp.headers["Content-Type"] = "application/json";
+      }
+      else if(command=="ebpfblocklist") {
+        Json::object obj;
+#ifdef HAVE_EBPF
+        struct timespec now;
+        gettime(&now);
+        for (const auto& dynbpf : g_dynBPFFilters) {
+          std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > addrStats = dynbpf->getAddrStats();
+          for (const auto& entry : addrStats) {
+            Json::object thing
+            {
+              {"seconds", (double)(std::get<2>(entry).tv_sec - now.tv_sec)},
+              {"blocks", (double)(std::get<1>(entry))}
+            };
+            obj.insert({std::get<0>(entry).toString(), thing });
+          }
+        }
+#endif /* HAVE_EBPF */
         Json my_json = obj;
         resp.body=my_json.dump();
         resp.headers["Content-Type"] = "application/json";
index 605ba2b7f9a6c611d1ae30c99556ef2e42e4c2d8..71fa52d3b8670de64f08767630668b1465c2d393 100644 (file)
@@ -85,6 +85,7 @@ std::vector<std::tuple<ComboAddress,DnsCryptContext,bool, int>> g_dnsCryptLocals
 #endif
 #ifdef HAVE_EBPF
 shared_ptr<BPFFilter> g_defaultBPFFilter;
+std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
 #endif /* HAVE_EBPF */
 vector<ClientState *> g_frontends;
 GlobalStateHolder<pools_t> g_pools;
@@ -1688,7 +1689,7 @@ try
 
 #ifdef HAVE_EBPF
     if (g_defaultBPFFilter) {
-      g_defaultBPFFilter->addSocket(cs->udpFD);
+      cs->attachFilter(g_defaultBPFFilter);
       vinfolog("Attaching default BPF Filter to UDP frontend %s", cs->local.toStringWithPort());
     }
 #endif /* HAVE_EBPF */
@@ -1731,7 +1732,7 @@ try
 #endif
 #ifdef HAVE_EBPF
     if (g_defaultBPFFilter) {
-      g_defaultBPFFilter->addSocket(cs->tcpFD);
+      cs->attachFilter(g_defaultBPFFilter);
       vinfolog("Attaching default BPF Filter to TCP frontend %s", cs->local.toStringWithPort());
     }
 #endif /* HAVE_EBPF */
@@ -1773,7 +1774,7 @@ try
     }
 #ifdef HAVE_EBPF
     if (g_defaultBPFFilter) {
-      g_defaultBPFFilter->addSocket(cs->udpFD);
+      cs->attachFilter(g_defaultBPFFilter);
       vinfolog("Attaching default BPF Filter to UDP DNSCrypt frontend %s", cs->local.toStringWithPort());
     }
 #endif /* HAVE_EBPF */
@@ -1808,7 +1809,7 @@ try
     }
 #ifdef HAVE_EBPF
     if (g_defaultBPFFilter) {
-      g_defaultBPFFilter->addSocket(cs->tcpFD);
+      cs->attachFilter(g_defaultBPFFilter);
       vinfolog("Attaching default BPF Filter to TCP DNSCrypt frontend %s", cs->local.toStringWithPort());
     }
 #endif /* HAVE_EBPF */
index a91fd16e64e0567209007bcae865fb4f5651703c..78c0c7da8459d18599180aae875634d109abe117 100644 (file)
@@ -283,6 +283,31 @@ struct ClientState
   std::atomic<uint64_t> queries{0};
   int udpFD{-1};
   int tcpFD{-1};
+
+  int getSocket() const
+  {
+    return udpFD != -1 ? udpFD : tcpFD;
+  }
+
+#ifdef HAVE_EBPF
+  shared_ptr<BPFFilter> d_filter;
+
+  void detachFilter()
+  {
+    if (d_filter) {
+      d_filter->removeSocket(getSocket());
+      d_filter = nullptr;
+    }
+  }
+
+  void attachFilter(shared_ptr<BPFFilter> bpf)
+  {
+    detachFilter();
+
+    bpf->addSocket(getSocket());
+    d_filter = bpf;
+  }
+#endif /* HAVE_EBPF */
 };
 
 class TCPClientCollection {
@@ -629,6 +654,7 @@ extern const std::vector<ConsoleKeyword> g_consoleKeywords;
 
 #ifdef HAVE_EBPF
 extern shared_ptr<BPFFilter> g_defaultBPFFilter;
+extern std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
 #endif /* HAVE_EBPF */
 
 struct dnsheader;
index 1417c212a7bdbbba1c116228f0a2accbcd6ea20b..9641111bdab273402708c18da0f5b85564f52c13 100644 (file)
@@ -86,6 +86,9 @@
             <tr>
               <td><div id="dynblock"></div></td>
             </tr>
+            <tr>
+              <td><div id="ebpfblock"></div></td>
+            </tr>
 
           </table>
         </td>
index 4b20cd074f08c02762fd36ffbec343f475ebed72..c4f66441fc53d72744263edebca0cfd054b02e07 100644 (file)
@@ -236,6 +236,22 @@ $(document).ready(function() {
 
                  }});
 
+        $.ajax({ url: 'jsonstat?command=ebpfblocklist', type: 'GET', dataType: 'json', jsonp: false,
+                 success: function(data) {
+                     var bouw='<table width="100%"><tr align=left><th>Kernel-based dyn blocked netmask</th><th>Seconds</th></th><th>Blocks</th></tr>';
+                    var gotsome=false;
+                     $.each(data, function(a,b) {
+                         bouw=bouw+("<tr><td>"+a+"</td><td>"+b.seconds+"</td><td>"+b.blocks+"</td></tr>");
+                        gotsome=true;
+                     });
+
+                    if(!gotsome)
+                         bouw = bouw + '<tr><td align="center" colspan="4"><font color="#aaaaaa">No eBPF blocks active</font></td></tr>';
+
+                     bouw=bouw+"</table>";
+                     $("#ebpfblock").html(bouw);
+
+                 }});
 
     };