]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Implement additional addresses on DoT/DoH frontends
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 1 Apr 2022 14:47:05 +0000 (16:47 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 30 Sep 2022 14:15:39 +0000 (16:15 +0200)
pdns/dnsdist-lua-bindings.cc
pdns/dnsdist-lua.cc
pdns/dnsdist-tcp.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/doh.cc

index f43b02a7adf420ce67a3646e2089c89e5c79aff9..e0337d3fa31bf18c3f3ea509401579903a0a3d20 100644 (file)
@@ -424,11 +424,11 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
 #ifdef HAVE_EBPF
   luaCtx.registerFunction<void(ClientState::*)(std::shared_ptr<BPFFilter>)>("attachFilter", [](ClientState& frontend, std::shared_ptr<BPFFilter> bpf) {
       if (bpf) {
-        frontend.attachFilter(bpf);
+        frontend.attachFilter(bpf, frontend.getSocket());
       }
     });
   luaCtx.registerFunction<void(ClientState::*)()>("detachFilter", [](ClientState& frontend) {
-      frontend.detachFilter();
+      frontend.detachFilter(frontend.getSocket());
     });
 #endif /* HAVE_EBPF */
 #endif /* DISABLE_CLIENT_STATE_BINDINGS */
@@ -629,7 +629,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
       }
       if (bpf) {
         for (const auto& frontend : g_frontends) {
-          frontend->attachFilter(bpf);
+          frontend->attachFilter(bpf, frontend->getSocket());
         }
       }
     });
index dfd4b4d4b06e70e993716bbaef2b5ceb8c6cc0a7..30926121920db86eb46dd1716a5ff8d3377f3e50 100644 (file)
@@ -2435,6 +2435,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     uint64_t tcpMaxConcurrentConnections = 0;
     std::string interface;
     std::set<int> cpus;
+    std::vector<std::pair<ComboAddress, int>> additionalAddresses;
 
     if (vars) {
       parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
@@ -2473,11 +2474,27 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         frontend->d_exactPathMatching = boost::get<bool>((*vars)["exactPathMatching"]);
       }
 
+      if (vars->count("additionalAddresses")) {
+        auto addresses = boost::get<LuaArray<std::string>>(vars->at("additionalAddresses"));
+        for (const auto& [_, add] : addresses) {
+          try {
+            ComboAddress address(add);
+            additionalAddresses.emplace_back(address, -1);
+          }
+          catch (const PDNSException& e) {
+            errlog("Unable to parse additional address %s for DOH bind: %s", add, e.reason);
+            return;
+          }
+        }
+      }
+
       parseTLSConfig(frontend->d_tlsConfig, "addDOHLocal", vars);
     }
     g_dohlocals.push_back(frontend);
     auto cs = std::make_unique<ClientState>(frontend->d_local, true, reusePort, tcpFastOpenQueueSize, interface, cpus);
     cs->dohFrontend = frontend;
+    cs->d_additionalAddresses = std::move(additionalAddresses);
+
     if (tcpListenQueueSize > 0) {
       cs->tcpListenQueueSize = tcpListenQueueSize;
     }
@@ -2649,6 +2666,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     uint64_t tcpMaxConcurrentConns = 0;
     std::string interface;
     std::set<int> cpus;
+    std::vector<std::pair<ComboAddress, int>> additionalAddresses;
 
     if (vars) {
       parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConns);
@@ -2658,6 +2676,20 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         boost::algorithm::to_lower(frontend->d_provider);
       }
 
+      if (vars->count("additionalAddresses")) {
+        auto addresses = boost::get<LuaArray<std::string>>(vars->at("additionalAddresses"));
+        for (const auto& [_, add] : addresses) {
+          try {
+            ComboAddress address(add);
+            additionalAddresses.emplace_back(address, -1);
+          }
+          catch (const PDNSException& e) {
+            errlog("Unable to parse additional address %s for DoT bind: %s", add, e.reason);
+            return;
+          }
+        }
+      }
+
       parseTLSConfig(frontend->d_tlsConfig, "addTLSLocal", vars);
     }
 
@@ -2676,6 +2708,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       // only works pre-startup, so no sync necessary
       auto cs = std::make_unique<ClientState>(frontend->d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus);
       cs->tlsFrontend = frontend;
+      cs->d_additionalAddresses = std::move(additionalAddresses);
       if (tcpListenQueueSize > 0) {
         cs->tcpListenQueueSize = tcpListenQueueSize;
       }
index 311b5c7ef54e27b1471dffe24717c28484e79dc9..bf58871e6c4e317cfd700c731ec0d0b3d3338968 100644 (file)
@@ -1366,86 +1366,129 @@ static void tcpClientThread(int pipefd, int crossProtocolQueriesPipeFD, int cros
   }
 }
 
-/* spawn as many of these as required, they call Accept on a socket on which they will accept queries, and
-   they will hand off to worker threads & spawn more of them if required
-*/
-void tcpAcceptorThread(ClientState* cs)
+struct TCPAcceptorParam
 {
-  setThreadName("dnsdist/tcpAcce");
+  ClientState& cs;
+  ComboAddress local;
+  LocalStateHolder<NetmaskGroup>& acl;
+};
 
+static void acceptNewConnection(int socket, TCPAcceptorParam& param)
+{
+  auto& cs = param.cs;
+  auto& acl = param.acl;
   bool tcpClientCountIncremented = false;
   ComboAddress remote;
-  remote.sin4.sin_family = cs->local.sin4.sin_family;
+  remote.sin4.sin_family = param.local.sin4.sin_family;
 
-  auto acl = g_ACL.getLocal();
-  for(;;) {
-    std::unique_ptr<ConnectionInfo> ci;
-    tcpClientCountIncremented = false;
-    try {
-      socklen_t remlen = remote.getSocklen();
-      ci = std::make_unique<ConnectionInfo>(cs);
+  std::unique_ptr<ConnectionInfo> ci;
+  tcpClientCountIncremented = false;
+  try {
+    socklen_t remlen = remote.getSocklen();
+    ci = std::make_unique<ConnectionInfo>(&cs);
 #ifdef HAVE_ACCEPT4
-      ci->fd = accept4(cs->tcpFD, reinterpret_cast<struct sockaddr*>(&remote), &remlen, SOCK_NONBLOCK);
+    ci->fd = accept4(socket, reinterpret_cast<struct sockaddr*>(&remote), &remlen, SOCK_NONBLOCK);
 #else
-      ci->fd = accept(cs->tcpFD, reinterpret_cast<struct sockaddr*>(&remote), &remlen);
+    ci->fd = accept(socket, reinterpret_cast<struct sockaddr*>(&remote), &remlen);
 #endif
-      // will be decremented when the ConnectionInfo object is destroyed, no matter the reason
-      auto concurrentConnections = ++cs->tcpCurrentConnections;
-      if (cs->d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > cs->d_tcpConcurrentConnectionsLimit) {
-        continue;
-      }
+    if (ci->fd < 0) {
+      throw std::runtime_error((boost::format("accepting new connection on socket: %s") % stringerror()).str());
+    }
 
-      if (concurrentConnections > cs->tcpMaxConcurrentConnections.load()) {
-        cs->tcpMaxConcurrentConnections.store(concurrentConnections);
-      }
+    if (!acl->match(remote)) {
+      ++g_stats.aclDrops;
+      vinfolog("Dropped TCP connection from %s because of ACL", remote.toStringWithPort());
+      return;
+    }
 
-      if (ci->fd < 0) {
-        throw std::runtime_error((boost::format("accepting new connection on socket: %s") % stringerror()).str());
-      }
+    // will be decremented when the ConnectionInfo object is destroyed, no matter the reason
+    auto concurrentConnections = ++cs.tcpCurrentConnections;
+    if (cs.d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > cs.d_tcpConcurrentConnectionsLimit) {
+      return;
+    }
 
-      if (!acl->match(remote)) {
-       ++g_stats.aclDrops;
-       vinfolog("Dropped TCP connection from %s because of ACL", remote.toStringWithPort());
-       continue;
-      }
+    if (concurrentConnections > cs.tcpMaxConcurrentConnections.load()) {
+      cs.tcpMaxConcurrentConnections.store(concurrentConnections);
+    }
 
 #ifndef HAVE_ACCEPT4
-      if (!setNonBlocking(ci->fd)) {
-        continue;
-      }
+    if (!setNonBlocking(ci->fd)) {
+      return;
+    }
 #endif
-      setTCPNoDelay(ci->fd);  // disable NAGLE
-      if (g_maxTCPQueuedConnections > 0 && g_tcpclientthreads->getQueuedCount() >= g_maxTCPQueuedConnections) {
-        vinfolog("Dropping TCP connection from %s because we have too many queued already", remote.toStringWithPort());
-        continue;
-      }
 
-      if (g_maxTCPConnectionsPerClient) {
-        auto tcpClientsCount = s_tcpClientsCount.lock();
+    setTCPNoDelay(ci->fd);  // disable NAGLE
 
-        if ((*tcpClientsCount)[remote] >= g_maxTCPConnectionsPerClient) {
-          vinfolog("Dropping TCP connection from %s because we have too many from this client already", remote.toStringWithPort());
-          continue;
-        }
-        (*tcpClientsCount)[remote]++;
-        tcpClientCountIncremented = true;
-      }
+    if (g_maxTCPQueuedConnections > 0 && g_tcpclientthreads->getQueuedCount() >= g_maxTCPQueuedConnections) {
+      vinfolog("Dropping TCP connection from %s because we have too many queued already", remote.toStringWithPort());
+      return;
+    }
 
-      vinfolog("Got TCP connection from %s", remote.toStringWithPort());
+    if (g_maxTCPConnectionsPerClient) {
+      auto tcpClientsCount = s_tcpClientsCount.lock();
 
-      ci->remote = remote;
-      if (!g_tcpclientthreads->passConnectionToThread(std::move(ci))) {
-        if (tcpClientCountIncremented) {
-          decrementTCPClientCount(remote);
-        }
+      if ((*tcpClientsCount)[remote] >= g_maxTCPConnectionsPerClient) {
+        vinfolog("Dropping TCP connection from %s because we have too many from this client already", remote.toStringWithPort());
+        return;
       }
+      (*tcpClientsCount)[remote]++;
+      tcpClientCountIncremented = true;
     }
-    catch (const std::exception& e) {
-      errlog("While reading a TCP question: %s", e.what());
+
+    vinfolog("Got TCP connection from %s", remote.toStringWithPort());
+
+    ci->remote = remote;
+    if (!g_tcpclientthreads->passConnectionToThread(std::move(ci))) {
       if (tcpClientCountIncremented) {
         decrementTCPClientCount(remote);
       }
     }
-    catch (...){}
+  }
+  catch (const std::exception& e) {
+    errlog("While reading a TCP question: %s", e.what());
+    if (tcpClientCountIncremented) {
+      decrementTCPClientCount(remote);
+    }
+  }
+  catch (...){}
+}
+
+/* spawn as many of these as required, they call Accept on a socket on which they will accept queries, and
+   they will hand off to worker threads & spawn more of them if required
+*/
+void tcpAcceptorThread(ClientState* cs)
+{
+  setThreadName("dnsdist/tcpAcce");
+
+  auto acl = g_ACL.getLocal();
+  struct TCPAcceptorParam param{*cs, cs->local, acl};
+
+  if (cs->d_additionalAddresses.empty()) {
+    while (true) {
+      acceptNewConnection(cs->tcpFD, param);
+    }
+  }
+  else {
+    auto acceptCallback = [](int socket, FDMultiplexer::funcparam_t& funcparam) {
+      auto acceptorParam = boost::any_cast<TCPAcceptorParam*>(funcparam);
+      acceptNewConnection(socket, *acceptorParam);
+    };
+
+    std::vector<TCPAcceptorParam> additionalParams;
+    auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
+    mplexer->addReadFD(cs->tcpFD, acceptCallback, &param);
+    for (const auto& [addr, socket] : cs->d_additionalAddresses) {
+      additionalParams.emplace_back(TCPAcceptorParam{*cs, addr, acl});
+    }
+    size_t idx = 0;
+    for (const auto& [addr, socket] : cs->d_additionalAddresses) {
+      mplexer->addReadFD(socket, acceptCallback, &additionalParams.at(idx));
+      idx++;
+    }
+
+    struct timeval tv;
+    while (true) {
+      mplexer->run(&tv);
+    }
   }
 }
index 353f19feb58b6ad85b9e5f695acb250cd2283464..0002181e321fa9e533366ab88aed1767b6724cf0 100644 (file)
@@ -2050,149 +2050,159 @@ static void checkFileDescriptorsLimits(size_t udpBindsCount, size_t tcpBindsCoun
 
 static bool g_warned_ipv6_recvpktinfo = false;
 
-static void setUpLocalBind(std::unique_ptr<ClientState>& cs)
+static void setUpLocalBind(std::unique_ptr<ClientState>& cstate)
 {
-  /* skip some warnings if there is an identical UDP context */
-  bool warn = cs->tcp == false || cs->tlsFrontend != nullptr || cs->dohFrontend != nullptr;
-  int& fd = cs->tcp == false ? cs->udpFD : cs->tcpFD;
-  (void) warn;
-
-  fd = SSocket(cs->local.sin4.sin_family, cs->tcp == false ? SOCK_DGRAM : SOCK_STREAM, 0);
+  auto setupSocket = [](ClientState& cs, const ComboAddress& addr, int& socket, bool tcp, bool warn) {
+    (void) warn;
+    socket = SSocket(addr.sin4.sin_family, tcp == false ? SOCK_DGRAM : SOCK_STREAM, 0);
 
-  if (cs->tcp) {
-    SSetsockopt(fd, SOL_SOCKET, SO_REUSEADDR, 1);
+    if (tcp) {
+      SSetsockopt(socket, SOL_SOCKET, SO_REUSEADDR, 1);
 #ifdef TCP_DEFER_ACCEPT
-    SSetsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, 1);
+      SSetsockopt(socket, IPPROTO_TCP, TCP_DEFER_ACCEPT, 1);
 #endif
-    if (cs->fastOpenQueueSize > 0) {
+      if (cs.fastOpenQueueSize > 0) {
 #ifdef TCP_FASTOPEN
-      SSetsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, cs->fastOpenQueueSize);
+        SSetsockopt(socket, IPPROTO_TCP, TCP_FASTOPEN, cs.fastOpenQueueSize);
 #ifdef TCP_FASTOPEN_KEY
-      if (!g_TCPFastOpenKey.empty()) {
-        auto res = setsockopt(fd, IPPROTO_IP, TCP_FASTOPEN_KEY, g_TCPFastOpenKey.data(), g_TCPFastOpenKey.size() * sizeof(g_TCPFastOpenKey[0]));
-        if (res == -1)
-          throw runtime_error("setsockopt for level IPPROTO_TCP and opname TCP_FASTOPEN_KEY failed: " + stringerror());
-      }
-#endif
-#else
-      if (warn) {
-        warnlog("TCP Fast Open has been configured on local address '%s' but is not supported", cs->local.toStringWithPort());
+        if (!g_TCPFastOpenKey.empty()) {
+          auto res = setsockopt(socket, IPPROTO_IP, TCP_FASTOPEN_KEY, g_TCPFastOpenKey.data(), g_TCPFastOpenKey.size() * sizeof(g_TCPFastOpenKey[0]));
+          if (res == -1) {
+            throw runtime_error("setsockopt for level IPPROTO_TCP and opname TCP_FASTOPEN_KEY failed: " + stringerror());
+          }
+        }
+#endif /* TCP_FASTOPEN_KEY */
+#else /* TCP_FASTOPEN */
+        if (warn) {
+          warnlog("TCP Fast Open has been configured on local address '%s' but is not supported", addr.toStringWithPort());
+        }
+#endif /* TCP_FASTOPEN */
       }
-#endif
     }
-  }
 
-  if(cs->local.sin4.sin_family == AF_INET6) {
-    SSetsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, 1);
-  }
+    if (addr.sin4.sin_family == AF_INET6) {
+      SSetsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, 1);
+    }
 
-  bindAny(cs->local.sin4.sin_family, fd);
+    bindAny(addr.sin4.sin_family, socket);
 
-  if(!cs->tcp && IsAnyAddress(cs->local)) {
-    int one=1;
-    (void)setsockopt(fd, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)); // linux supports this, so why not - might fail on other systems
+    if (!tcp && IsAnyAddress(addr)) {
+      int one = 1;
+      (void) setsockopt(socket, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)); // linux supports this, so why not - might fail on other systems
 #ifdef IPV6_RECVPKTINFO
-    if (cs->local.isIPv6() && setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)) < 0 &&
-        !g_warned_ipv6_recvpktinfo) {
+      if (addr.isIPv6() && setsockopt(socket, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)) < 0 &&
+          !g_warned_ipv6_recvpktinfo) {
         warnlog("Warning: IPV6_RECVPKTINFO setsockopt failed: %s", stringerror());
         g_warned_ipv6_recvpktinfo = true;
-    }
-#endif
-  }
-
-  if (cs->reuseport) {
-    if (!setReusePort(fd)) {
-      if (warn) {
-        /* no need to warn again if configured but support is not available, we already did for UDP */
-        warnlog("SO_REUSEPORT has been configured on local address '%s' but is not supported", cs->local.toStringWithPort());
       }
+#endif
     }
-  }
 
-  /* Only set this on IPv4 UDP sockets.
-     Don't set it for DNSCrypt binds. DNSCrypt pads queries for privacy
-     purposes, so we do receive large, sometimes fragmented datagrams. */
-  if (!cs->tcp && !cs->dnscryptCtx) {
-    try {
-      setSocketIgnorePMTU(cs->udpFD, cs->local.sin4.sin_family);
-    }
-    catch(const std::exception& e) {
-      warnlog("Failed to set IP_MTU_DISCOVER on UDP server socket for local address '%s': %s", cs->local.toStringWithPort(), e.what());
+    if (cs.reuseport) {
+      if (!setReusePort(socket)) {
+        if (warn) {
+          /* no need to warn again if configured but support is not available, we already did for UDP */
+          warnlog("SO_REUSEPORT has been configured on local address '%s' but is not supported", addr.toStringWithPort());
+        }
+      }
     }
-  }
 
-  if (!cs->tcp) {
-    if (g_socketUDPSendBuffer > 0) {
+    /* Only set this on IPv4 UDP sockets.
+       Don't set it for DNSCrypt binds. DNSCrypt pads queries for privacy
+       purposes, so we do receive large, sometimes fragmented datagrams. */
+    if (!tcp && !cs.dnscryptCtx) {
       try {
-        setSocketSendBuffer(cs->udpFD, g_socketUDPSendBuffer);
+        setSocketIgnorePMTU(socket, addr.sin4.sin_family);
       }
       catch (const std::exception& e) {
-        warnlog(e.what());
+        warnlog("Failed to set IP_MTU_DISCOVER on UDP server socket for local address '%s': %s", addr.toStringWithPort(), e.what());
       }
     }
 
-    if (g_socketUDPRecvBuffer > 0) {
-      try {
-        setSocketReceiveBuffer(cs->udpFD, g_socketUDPRecvBuffer);
+    if (!tcp) {
+      if (g_socketUDPSendBuffer > 0) {
+        try {
+          setSocketSendBuffer(socket, g_socketUDPSendBuffer);
+        }
+        catch (const std::exception& e) {
+          warnlog(e.what());
+        }
       }
-      catch (const std::exception& e) {
-        warnlog(e.what());
+
+      if (g_socketUDPRecvBuffer > 0) {
+        try {
+          setSocketReceiveBuffer(socket, g_socketUDPRecvBuffer);
+        }
+        catch (const std::exception& e) {
+          warnlog(e.what());
+        }
       }
     }
-  }
 
-  const std::string& itf = cs->interface;
-  if (!itf.empty()) {
+    const std::string& itf = cs.interface;
+    if (!itf.empty()) {
 #ifdef SO_BINDTODEVICE
-    int res = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, itf.c_str(), itf.length());
-    if (res != 0) {
-      warnlog("Error setting up the interface on local address '%s': %s", cs->local.toStringWithPort(), stringerror());
-    }
+      int res = setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, itf.c_str(), itf.length());
+      if (res != 0) {
+        warnlog("Error setting up the interface on local address '%s': %s", addr.toStringWithPort(), stringerror());
+      }
 #else
-    if (warn) {
-      warnlog("An interface has been configured on local address '%s' but SO_BINDTODEVICE is not supported", cs->local.toStringWithPort());
-    }
+      if (warn) {
+        warnlog("An interface has been configured on local address '%s' but SO_BINDTODEVICE is not supported", addr.toStringWithPort());
+      }
 #endif
-  }
+    }
 
 #ifdef HAVE_EBPF
-  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());
-  }
+    if (g_defaultBPFFilter && !g_defaultBPFFilter->isExternal()) {
+      cs.attachFilter(g_defaultBPFFilter, socket);
+      vinfolog("Attaching default BPF Filter to %s frontend %s", (!tcp ? "UDP" : "TCP"), addr.toStringWithPort());
+    }
 #endif /* HAVE_EBPF */
 
-  if (cs->tlsFrontend != nullptr) {
-    if (!cs->tlsFrontend->setupTLS()) {
-      errlog("Error while setting up TLS on local address '%s', exiting", cs->local.toStringWithPort());
-      _exit(EXIT_FAILURE);
+    SBind(socket, addr);
+
+    if (tcp) {
+      SListen(socket, cs.tcpListenQueueSize);
+
+      if (cs.tlsFrontend != nullptr) {
+        infolog("Listening on %s for TLS", addr.toStringWithPort());
+      }
+      else if (cs.dohFrontend != nullptr) {
+        infolog("Listening on %s for DoH", addr.toStringWithPort());
+      }
+      else if (cs.dnscryptCtx != nullptr) {
+        infolog("Listening on %s for DNSCrypt", addr.toStringWithPort());
+      }
+      else {
+        infolog("Listening on %s", addr.toStringWithPort());
+      }
     }
-  }
+  };
 
-  if (cs->dohFrontend != nullptr) {
-    cs->dohFrontend->setup();
-  }
+  /* skip some warnings if there is an identical UDP context */
+  bool warn = cstate->tcp == false || cstate->tlsFrontend != nullptr || cstate->dohFrontend != nullptr;
+  int& fd = cstate->tcp == false ? cstate->udpFD : cstate->tcpFD;
+  (void) warn;
 
-  SBind(fd, cs->local);
+  setupSocket(*cstate, cstate->local, fd, cstate->tcp, warn);
 
-  if (cs->tcp) {
-    SListen(cs->tcpFD, cs->tcpListenQueueSize);
+  for (auto& [addr, socket] : cstate->d_additionalAddresses) {
+    setupSocket(*cstate, addr, socket, true, false);
+  }
 
-    if (cs->tlsFrontend != nullptr) {
-      infolog("Listening on %s for TLS", cs->local.toStringWithPort());
-    }
-    else if (cs->dohFrontend != nullptr) {
-      infolog("Listening on %s for DoH", cs->local.toStringWithPort());
-    }
-    else if (cs->dnscryptCtx != nullptr) {
-      infolog("Listening on %s for DNSCrypt", cs->local.toStringWithPort());
-    }
-    else {
-      infolog("Listening on %s", cs->local.toStringWithPort());
+  if (cstate->tlsFrontend != nullptr) {
+    if (!cstate->tlsFrontend->setupTLS()) {
+      errlog("Error while setting up TLS on local address '%s', exiting", cstate->local.toStringWithPort());
+      _exit(EXIT_FAILURE);
     }
   }
 
-  cs->ready = true;
+  if (cstate->dohFrontend != nullptr) {
+    cstate->dohFrontend->setup();
+  }
+
+  cstate->ready = true;
 }
 
 struct
index e8b2abe80b01b7af23ebecf917c957f6d51d6d95..95129a82a6061d834e28edd6f12931f44e936206 100644 (file)
@@ -618,6 +618,7 @@ struct ClientState
   std::set<int> cpus;
   std::string interface;
   ComboAddress local;
+  std::vector<std::pair<ComboAddress, int>> d_additionalAddresses;
   std::shared_ptr<DNSCryptContext> dnscryptCtx{nullptr};
   std::shared_ptr<TLSFrontend> tlsFrontend{nullptr};
   std::shared_ptr<DOHFrontend> dohFrontend{nullptr};
@@ -702,19 +703,19 @@ struct ClientState
     return result;
   }
 
-  void detachFilter()
+  void detachFilter(int socket)
   {
     if (d_filter) {
-      d_filter->removeSocket(getSocket());
+      d_filter->removeSocket(socket);
       d_filter = nullptr;
     }
   }
 
-  void attachFilter(shared_ptr<BPFFilter> bpf)
+  void attachFilter(shared_ptr<BPFFilter> bpf, int socket)
   {
-    detachFilter();
+    detachFilter(socket);
 
-    bpf->addSocket(getSocket());
+    bpf->addSocket(socket);
     d_filter = bpf;
   }
 
index 767604da95e4257ad639e12c990f92de34120a9d..ef3dd48a33aee7b775657eff0604935f4ea9e974 100644 (file)
@@ -120,7 +120,7 @@ Listen Sockets
 
   .. versionchanged:: 1.8.0
      ``certFile`` now accepts a TLSCertificate object or a list of such objects (see :func:`newTLSCertificate`)
-     ``keepIncomingHeaders`` option added.
+     ``additionalAddresses`` and ``keepIncomingHeaders`` options added.
 
   Listen on the specified address and TCP port for incoming DNS over HTTPS connections, presenting the specified X.509 certificate.
   If no certificate (or key) files are specified, listen for incoming DNS over HTTP connections instead.
@@ -162,6 +162,7 @@ Listen Sockets
   * ``releaseBuffers=true``: bool - Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection.
   * ``enableRenegotiation=false``: bool - Whether secure TLS renegotiation should be enabled. Disabled by default since it increases the attack surface and is seldom used for DNS.
   * ``keepIncomingHeaders``: bool - Whether to retain the incoming headers in memory, to be able to use :func:`HTTPHeaderRule` or :meth:`DNSQuestion.getHTTPHeaders`. Default is false. Before 1.8.0 the headers were always kept in-memory.
+  * ``additionalAddresses``: list - List of additional addresses (with port) to listen on. Using this option instead of creating a new frontend for each address avoids the creation of new thread and Frontend objects, reducing the memory usage. The drawback is that there will be a single set of metrics for all addresses.
 
 .. function:: addTLSLocal(address, certFile(s), keyFile(s) [, options])
 
@@ -174,7 +175,8 @@ Listen Sockets
   .. versionchanged:: 1.8.0
     ``tlsAsyncMode`` option added.
   .. versionchanged:: 1.8.0
-     ``certFile`` now accepts a TLSCertificate object or a list of such objects (see :func:`newTLSCertificate`)
+     ``certFile`` now accepts a TLSCertificate object or a list of such objects (see :func:`newTLSCertificate`).
+     ``additionalAddresses`` option added.
 
   Listen on the specified address and TCP port for incoming DNS over TLS connections, presenting the specified X.509 certificate.
 
@@ -209,6 +211,7 @@ Listen Sockets
   * ``releaseBuffers=true``: bool - Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection.
   * ``enableRenegotiation=false``: bool - Whether secure TLS renegotiation should be enabled (OpenSSL only, the GnuTLS provider does not support it). Disabled by default since it increases the attack surface and is seldom used for DNS.
   * ``tlsAsyncMode=false``: bool - Whether to enable experimental asynchronous TLS I/O operations if OpenSSL is used as the TLS provider and an asynchronous capable SSL engine is loaded. See also :func:`loadTLSEngine` to load the engine.
+  * ``additionalAddresses``: list - List of additional addresses (with port) to listen on. Using this option instead of creating a new frontend for each address avoids the creation of new thread and Frontend objects, reducing the memory usage. The drawback is that there will be a single set of metrics for all addresses.
 
 .. function:: setLocal(address[, options])
 
index 7aedbd6ba824e5b86adcc2d63d65892230db5b0f..cf9c3166908fcd426f8c339907bfec2f28b32d01 100644 (file)
@@ -1404,7 +1404,7 @@ static void on_accept(h2o_socket_t *listener, const char *err)
   h2o_accept(conn.d_acceptCtx->get(), sock);
 }
 
-static int create_listener(const ComboAddress& addr, std::shared_ptr<DOHServerConfig>& dsc, int fd)
+static int create_listener(std::shared_ptr<DOHServerConfig>& dsc, int fd)
 {
   auto sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ);
   sock->data = dsc.get();
@@ -1629,9 +1629,14 @@ void dohThread(ClientState* cs)
 
     setupAcceptContext(*dsc->accept_ctx, *dsc, false);
 
-    if (create_listener(df->d_local, dsc, cs->tcpFD) != 0) {
+    if (create_listener(dsc, cs->tcpFD) != 0) {
       throw std::runtime_error("DOH server failed to listen on " + df->d_local.toStringWithPort() + ": " + strerror(errno));
     }
+    for (const auto& [addr, fd] : cs->d_additionalAddresses) {
+      if (create_listener(dsc, fd) != 0) {
+        throw std::runtime_error("DOH server failed to listen on additional address " + addr.toStringWithPort() + " for DOH local" + df->d_local.toStringWithPort() + ": " + strerror(errno));
+      }
+    }
 
     bool stop = false;
     do {