]> granicus.if.org Git - pdns/commitdiff
dnsdist: Restrict remote connection to the console via an ACL
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 27 Mar 2018 08:24:48 +0000 (10:24 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 28 Mar 2018 08:22:05 +0000 (10:22 +0200)
pdns/dnsdist-console.cc
pdns/dnsdist-console.hh [new file with mode: 0644]
pdns/dnsdist-lua.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-console.hh [new symlink]
pdns/dnsdistdist/docs/guides/console.rst
pdns/dnsdistdist/docs/reference/config.rst

index 403262672ef166dcffb374e72c175cc4295be243..8c03c1d880b659f70eeb6958c46d71294c89f158 100644 (file)
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
-#include "dnsdist.hh"
-#include "sodcrypto.hh"
-#include "pwd.h"
+
+#include <fstream>
+#include <pwd.h>
+#include <thread>
 
 #if defined (__OpenBSD__) || defined(__NetBSD__)
 #include <readline/readline.h>
 #include <editline/readline.h>
 #endif
 
-#include <fstream>
-#include "dolog.hh"
 #include "ext/json11/json11.hpp"
 
+#include "dolog.hh"
+#include "dnsdist.hh"
+#include "dnsdist-console.hh"
+#include "sodcrypto.hh"
+
+GlobalStateHolder<NetmaskGroup> g_consoleACL;
 vector<pair<struct timeval, string> > g_confDelta;
+std::string g_consoleKey;
 bool g_logConsoleConnections{true};
 
 // MUST BE CALLED UNDER A LOCK - right now the LuaLock
-void feedConfigDelta(const std::string& line)
+static void feedConfigDelta(const std::string& line)
 {
   if(line.empty())
     return;
@@ -47,7 +53,7 @@ void feedConfigDelta(const std::string& line)
   g_confDelta.push_back({now,line});
 }
 
-string historyFile(const bool &ignoreHOME = false)
+static string historyFile(const bool &ignoreHOME = false)
 {
   string ret;
 
@@ -71,6 +77,7 @@ void doClient(ComboAddress server, const std::string& command)
 {
   if(g_verbose)
     cout<<"Connecting to "<<server.toStringWithPort()<<endl;
+
   int fd=socket(server.sin4.sin_family, SOCK_STREAM, 0);
   if (fd < 0) {
     cerr<<"Unable to connect to "<<server.toStringWithPort()<<endl;
@@ -87,7 +94,7 @@ void doClient(ComboAddress server, const std::string& command)
   writingNonce.merge(theirs, ours);
 
   if(!command.empty()) {
-    string msg=sodEncryptSym(command, g_key, writingNonce);
+    string msg=sodEncryptSym(command, g_consoleKey, writingNonce);
     putMsgLen32(fd, (uint32_t) msg.length());
     if(!msg.empty())
       writen2(fd, msg);
@@ -97,7 +104,7 @@ void doClient(ComboAddress server, const std::string& command)
         boost::scoped_array<char> resp(new char[len]);
         readn2(fd, resp.get(), len);
         msg.assign(resp.get(), len);
-        msg=sodDecryptSym(msg, g_key, readingNonce);
+        msg=sodDecryptSym(msg, g_consoleKey, readingNonce);
         cout<<msg;
         cout.flush();
       }
@@ -143,7 +150,7 @@ void doClient(ComboAddress server, const std::string& command)
     if(line.empty())
       continue;
 
-    string msg=sodEncryptSym(line, g_key, writingNonce);
+    string msg=sodEncryptSym(line, g_consoleKey, writingNonce);
     putMsgLen32(fd, (uint32_t) msg.length());
     writen2(fd, msg);
     uint32_t len;
@@ -156,7 +163,7 @@ void doClient(ComboAddress server, const std::string& command)
       boost::scoped_array<char> resp(new char[len]);
       readn2(fd, resp.get(), len);
       msg.assign(resp.get(), len);
-      msg=sodDecryptSym(msg, g_key, readingNonce);
+      msg=sodDecryptSym(msg, g_consoleKey, readingNonce);
       cout<<msg;
       cout.flush();
     }
@@ -285,6 +292,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   /* keyword, function, parameters, description */
   { "addACL", true, "netmask", "add to the ACL set who can use this server" },
   { "addAction", true, "DNS rule, DNS action [, {uuid=\"UUID\"}]", "add a rule" },
+  { "addConsoleACL", true, "netmask", "add a netmask to the console ACL" },
   { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters" },
   { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
   { "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}]", "add `addr` to the list of addresses we listen on" },
@@ -374,6 +382,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
   { "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
   { "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" },
+  { "setConsoleACL", true, "{netmask, netmask}", "replace the console ACL set with these netmasks" },
   { "setConsoleConnectionsLogging", true, "enabled", "whether to log the opening and closing of console connections" },
   { "setDNSSECPool", true, "pool name", "move queries requesting DNSSEC processing to this pool" },
   { "setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported" },
@@ -409,6 +418,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "showACL", true, "", "show our ACL set" },
   { "showBinds", true, "", "show listening addresses (frontends)" },
   { "showCacheHitResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined cache hit response rules, optionally with their UUIDs and optionally truncated to a given width" },
+  { "showConsoleACL", true, "", "show our current console ACL set" },
   { "showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds" },
   { "showDynBlocks", true, "", "show dynamic blocks in force" },
   { "showPools", true, "", "show the available pools" },
@@ -492,7 +502,7 @@ char** my_completion( const char * text , int start,  int end)
 }
 }
 
-void controlClientThread(int fd, ComboAddress client)
+static void controlClientThread(int fd, ComboAddress client)
 try
 {
   setTCPNoDelay(fd);
@@ -519,7 +529,7 @@ try
     readn2(fd, msg.get(), len);
     
     string line(msg.get(), len);
-    line = sodDecryptSym(line, g_key, readingNonce);
+    line = sodDecryptSym(line, g_consoleKey, readingNonce);
     //    cerr<<"Have decrypted line: "<<line<<endl;
     string response;
     try {
@@ -604,7 +614,7 @@ try
     catch(const LuaContext::SyntaxErrorException& e) {
       response = "Error: " + string(e.what()) + ": ";
     }
-    response = sodEncryptSym(response, g_key, writingNonce);
+    response = sodEncryptSym(response, g_consoleKey, writingNonce);
     putMsgLen32(fd, response.length());
     writen2(fd, response.c_str(), response.length());
   }
@@ -621,3 +631,32 @@ catch(std::exception& e)
     close(fd);
 }
 
+void controlThread(int fd, ComboAddress local)
+try
+{
+  ComboAddress client;
+  int sock;
+  auto localACL = g_consoleACL.getLocal();
+  infolog("Accepting control connections on %s", local.toStringWithPort());
+
+  while ((sock = SAccept(fd, client)) >= 0) {
+
+    if (!localACL->match(client)) {
+      vinfolog("Control connection from %s dropped because of ACL", client.toStringWithPort());
+      close(sock);
+      continue;
+    }
+
+    if (g_logConsoleConnections) {
+      warnlog("Got control connection from %s", client.toStringWithPort());
+    }
+
+    std::thread t(controlClientThread, sock, client);
+    t.detach();
+  }
+}
+catch(const std::exception& e)
+{
+  close(fd);
+  errlog("Control connection died: %s", e.what());
+}
diff --git a/pdns/dnsdist-console.hh b/pdns/dnsdist-console.hh
new file mode 100644 (file)
index 0000000..01eb5d4
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+struct ConsoleKeyword {
+  std::string name;
+  bool function;
+  std::string parameters;
+  std::string description;
+  std::string toString() const
+  {
+    std::string res(name);
+    if (function) {
+      res += "(" + parameters + ")";
+    }
+    res += ": ";
+    res += description;
+    return res;
+  }
+};
+
+extern GlobalStateHolder<NetmaskGroup> g_consoleACL;
+extern const std::vector<ConsoleKeyword> g_consoleKeywords;
+extern std::string g_consoleKey; // in theory needs locking
+extern bool g_logConsoleConnections;
+
+void doClient(ComboAddress server, const std::string& command);
+void doConsole();
+extern "C" {
+char** my_completion( const char * text , int start,  int end);
+}
+void controlThread(int fd, ComboAddress local);
index 6f46af20a50c5acd10cdf8c896346a0f14a60301..7f9e1858f5a82771f3e597f309ed2137b747260a 100644 (file)
@@ -29,6 +29,7 @@
 #include <thread>
 
 #include "dnsdist.hh"
+#include "dnsdist-console.hh"
 #include "dnsdist-lua.hh"
 
 #include "base64.hh"
@@ -607,6 +608,47 @@ void setupLuaConfig(bool client)
       }
     });
 
+  g_lua.writeFunction("addConsoleACL", [](const std::string& netmask) {
+      setLuaSideEffect();
+#ifndef HAVE_LIBSODIUM
+      warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications");
+#endif
+
+      g_consoleACL.modify([netmask](NetmaskGroup& nmg) { nmg.addMask(netmask); });
+    });
+
+  g_lua.writeFunction("setConsoleACL", [](boost::variant<string,vector<pair<int, string>>> inp) {
+      setLuaSideEffect();
+
+#ifndef HAVE_LIBSODIUM
+      warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications");
+#endif
+
+      NetmaskGroup nmg;
+      if(auto str = boost::get<string>(&inp)) {
+       nmg.addMask(*str);
+      }
+      else for(const auto& p : boost::get<vector<pair<int,string>>>(inp)) {
+       nmg.addMask(p.second);
+      }
+      g_consoleACL.setState(nmg);
+  });
+
+  g_lua.writeFunction("showConsoleACL", []() {
+      setLuaNoSideEffect();
+
+#ifndef HAVE_LIBSODIUM
+      warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications");
+#endif
+
+      vector<string> vec;
+      g_consoleACL.getLocal()->toStringVector(&vec);
+
+      for(const auto& s : vec) {
+        g_outputBuffer += s + "\n";
+      }
+    });
+
   g_lua.writeFunction("clearQueryCounters", []() {
       unsigned int size{0};
       {
@@ -647,9 +689,12 @@ void setupLuaConfig(bool client)
     });
 
   g_lua.writeFunction("setKey", [](const std::string& key) {
-      if(!g_configurationDone && ! g_key.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf
+      if(!g_configurationDone && ! g_consoleKey.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf
         return;                                     // but later setKeys() trump the -k value again
       }
+#ifndef HAVE_LIBSODIUM
+      warnlog("Calling setKey() while libsodium support has not been enabled is not secure, and will result in cleartext communications");
+#endif
 
       setLuaSideEffect();
       string newkey;
@@ -658,7 +703,7 @@ void setupLuaConfig(bool client)
         errlog("%s", g_outputBuffer);
       }
       else
-       g_key=newkey;
+       g_consoleKey=newkey;
     });
 
   g_lua.writeFunction("testCrypto", [](boost::optional<string> optTestMsg)
@@ -678,14 +723,14 @@ void setupLuaConfig(bool client)
        SodiumNonce sn, sn2;
        sn.init();
        sn2=sn;
-       string encrypted = sodEncryptSym(testmsg, g_key, sn);
-       string decrypted = sodDecryptSym(encrypted, g_key, sn2);
+       string encrypted = sodEncryptSym(testmsg, g_consoleKey, sn);
+       string decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2);
 
        sn.increment();
        sn2.increment();
 
-       encrypted = sodEncryptSym(testmsg, g_key, sn);
-       decrypted = sodDecryptSym(encrypted, g_key, sn2);
+       encrypted = sodEncryptSym(testmsg, g_consoleKey, sn);
+       decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2);
 
        if(testmsg == decrypted)
         g_outputBuffer="Everything is ok!\n";
index 221979a31ad206dfcebd95dc3900fefaeff0170a..8c62d962bcadeb9946063b2ddf4f53a53b9aa108 100644 (file)
@@ -43,6 +43,7 @@
 
 #include "dnsdist.hh"
 #include "dnsdist-cache.hh"
+#include "dnsdist-console.hh"
 #include "dnsdist-ecs.hh"
 #include "dnsdist-lua.hh"
 
@@ -1859,32 +1860,6 @@ void* healthChecksThread()
   return 0;
 }
 
-string g_key;
-
-
-void controlThread(int fd, ComboAddress local)
-try
-{
-  ComboAddress client;
-  int sock;
-  warnlog("Accepting control connections on %s", local.toStringWithPort());
-  while((sock=SAccept(fd, client)) >= 0) {
-    if (g_logConsoleConnections) {
-      warnlog("Got control connection from %s", client.toStringWithPort());
-    }
-
-    thread t(controlClientThread, sock, client);
-    t.detach();
-  }
-}
-catch(std::exception& e) 
-{
-  close(fd);
-  errlog("Control connection died: %s", e.what());
-}
-
-
-
 static void bindAny(int af, int sock)
 {
   __attribute__((unused)) int one = 1;
@@ -2117,7 +2092,7 @@ try
       break;
 #ifdef HAVE_LIBSODIUM
     case 'k':
-      if (B64Decode(string(optarg), g_key) < 0) {
+      if (B64Decode(string(optarg), g_consoleKey) < 0) {
         cerr<<"Unable to decode key '"<<optarg<<"'."<<endl;
         exit(EXIT_FAILURE);
       }
@@ -2218,6 +2193,10 @@ try
     g_ACL.setState(acl);
   }
 
+  auto consoleACL = g_consoleACL.getCopy();
+  consoleACL.addMask("127.0.0.1/8");
+  g_consoleACL.setState(consoleACL);
+
   if (g_cmdLine.checkConfig) {
     setupLua(true, g_cmdLine.config);
     // No exception was thrown
@@ -2527,6 +2506,16 @@ try
     acls += s;
   }
   infolog("ACL allowing queries from: %s", acls.c_str());
+  vec.clear();
+  acls.clear();
+  g_consoleACL.getLocal()->toStringVector(&vec);
+  for (const auto& entry : vec) {
+    if (!acls.empty()) {
+      acls += ", ";
+    }
+    acls += entry;
+  }
+  infolog("Console ACL allowing connections from: %s", acls.c_str());
 
   uid_t newgid=0;
   gid_t newuid=0;
index 862efa60b5ac51a3890bbd5f7f1f16c3bb87d8bf..953db257a6cf12296c31d56cb1c61d534d4fc5ed 100644 (file)
@@ -773,7 +773,6 @@ extern ComboAddress g_serverControl; // not changed during runtime
 extern std::vector<std::tuple<ComboAddress, bool, bool, int, std::string, std::set<int>>> g_locals; // not changed at runtime (we hope XXX)
 extern std::vector<shared_ptr<TLSFrontend>> g_tlslocals;
 extern vector<ClientState*> g_frontends;
-extern std::string g_key; // in theory needs locking
 extern bool g_truncateTC;
 extern bool g_fixupCase;
 extern int g_tcpRecvTimeout;
@@ -798,25 +797,6 @@ extern bool g_useTCPSinglePipe;
 extern std::atomic<uint16_t> g_downstreamTCPCleanupInterval;
 extern size_t g_udpVectorSize;
 
-struct ConsoleKeyword {
-  std::string name;
-  bool function;
-  std::string parameters;
-  std::string description;
-  std::string toString() const
-  {
-    std::string res(name);
-    if (function) {
-      res += "(" + parameters + ")";
-    }
-    res += ": ";
-    res += description;
-    return res;
-  }
-};
-extern const std::vector<ConsoleKeyword> g_consoleKeywords;
-extern bool g_logConsoleConnections;
-
 #ifdef HAVE_EBPF
 extern shared_ptr<BPFFilter> g_defaultBPFFilter;
 extern std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
@@ -860,12 +840,6 @@ bool getMsgLen32(int fd, uint32_t* len);
 bool putMsgLen32(int fd, uint32_t len);
 void* tcpAcceptorThread(void* p);
 
-void doClient(ComboAddress server, const std::string& command);
-void doConsole();
-void controlClientThread(int fd, ComboAddress client);
-extern "C" {
-char** my_completion( const char * text , int start,  int end);
-}
 void setLuaNoSideEffect(); // if nothing has been declared, set that there are no side effects
 void setLuaSideEffect();   // set to report a side effect, cancelling all _no_ side effect calls
 bool getLuaNoSideEffect(); // set if there were only explicit declarations of _no_ side effect
index 1bcab9a84f9cfaa9c05d8bebc2776b130be0e730..7467ab509d2d45f384013873275e7746ce34daf1 100644 (file)
@@ -86,7 +86,7 @@ dnsdist_SOURCES = \
        dnsdist-dynbpf.cc dnsdist-dynbpf.hh \
        dnsdist-cache.cc dnsdist-cache.hh \
        dnsdist-carbon.cc \
-       dnsdist-console.cc \
+       dnsdist-console.cc dnsdist-console.hh \
        dnsdist-dnscrypt.cc \
        dnsdist-ecs.cc dnsdist-ecs.hh \
        dnsdist-lua.hh dnsdist-lua.cc \
diff --git a/pdns/dnsdistdist/dnsdist-console.hh b/pdns/dnsdistdist/dnsdist-console.hh
new file mode 120000 (symlink)
index 0000000..c92b2ad
--- /dev/null
@@ -0,0 +1 @@
+../dnsdist-console.hh
\ No newline at end of file
index 7c736dc1e1a9495b69ffb8243670bbb3579ee9ba..3238f9db72bc5dc2d6bbe62720c3a554439b8947 100644 (file)
@@ -11,7 +11,7 @@ The console can be enabled with :func:`controlSocket`:
 
   controlSocket('192.0.2.53:5199')
 
-To enable encryption, first generate a key with :func:`makeKey`::
+Exposing the console to the network without encryption enabled is not recommended. To enable encryption, first generate a key with :func:`makeKey`::
 
   $ ./dnsdist -l 127.0.0.1:5300
   [..]
@@ -37,3 +37,15 @@ Alternatively, you can specify the address and key on the client commandline::
 .. warning::
 
   This will leak the key into your shell's history and is **not** recommended.
+
+Note that encryption requires building dnsdist with libsodium support enabled.
+
+Since 1.3.0, dnsdist supports restricting which client can connect to the console with an ACL:
+
+.. code-block:: lua
+
+  controlSocket('192.0.2.53:5199')
+  setConsoleACL('192.0.2.0/24')
+
+The default value is '127.0.0.1', restricting the use of the console to local users. Please make sure that encryption is enabled
+before using :func:`addConsoleACL` or :func:`setConsoleACL` to allow connection from remote clients.
index b136c5ceaa9fb66c106f20d7c0ca518388fc8a03..b629207c43d8e3640b63ca6d2960d8dbbb3c03f9 100644 (file)
@@ -137,9 +137,20 @@ Listen Sockets
 Control Socket, Console and Webserver
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. function:: addConsoleACL(netmask)
+
+  .. versionadded:: 1.3.0
+
+  Add a netmask to the existing console ACL, allowing remote clients to connect to the console. Please make sure that encryption
+  has been enabled with :func:`setKey` before doing so.
+
+  :param str netmask: A CIDR netmask, e.g. ``"192.0.2.0/24"``. Without a subnetmask, only the specific address is allowed.
+
 .. function:: controlSocket(address)
 
-  Bind to ``addr`` and listen for a connection for the console
+  Bind to ``addr`` and listen for a connection for the console. Since 1.3.0 only connections from local users are allowed
+  by default, :func:`addConsoleACL` and :func:`setConsoleACL` can be used to enable remote connections. Please make sure
+  that encryption has been enabled with :func:`setKey` before doing so.
 
   :param str address: An IP address with optional port. By default, the port is 5199.
 
@@ -165,6 +176,19 @@ Control Socket, Console and Webserver
 
   :param str key: An encoded key, as generated by :func:`makeKey`
 
+.. function:: setConsoleACL(netmasks)
+
+  .. versionadded:: 1.3.0
+
+  Remove the existing console ACL and add the netmasks from the table, allowing remote clients to connect to the console. Please make sure that encryption
+  has been enabled with :func:`setKey` before doing so.
+
+  :param {str} netmasks: A table of CIDR netmask, e.g. ``{"192.0.2.0/24", "2001:DB8:14::/56"}``. Without a subnetmask, only the specific address is allowed.
+
+.. function:: showConsoleACL()
+
+  Print a list of all netmasks allowed to connect to the console.
+
 .. function:: testCrypto()
 
   Test the crypto code, will report errors when something is not ok.
@@ -205,6 +229,10 @@ Access Control Lists
 
   :param {str} netmasks: A table of CIDR netmask, e.g. ``{"192.0.2.0/24", "2001:DB8:14::/56"}``. Without a subnetmask, only the specific address is allowed.
 
+.. function:: showACL()
+
+  Print a list of all allowed netmasks.
+
 EDNS Client Subnet
 ~~~~~~~~~~~~~~~~~~
 
@@ -558,10 +586,6 @@ Status, Statistics and More
   :param {str} selectors: A lua table of selectors. Only queries matching all selectors are shown
   :param int num: Show a maximum of ``num`` recent queries, default is 10.
 
-.. function:: showACL()
-
-  Print a list of all allowed netmasks.
-
 .. function:: showBinds()
 
   Print a list of all the current addresses and ports dnsdist is listening on, also called ``frontends``