]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
TCP support for cookies, taking into account idle outgoing connections
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 18 Feb 2025 11:21:02 +0000 (12:21 +0100)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Thu, 4 Sep 2025 09:05:15 +0000 (11:05 +0200)
pdns/recursordist/lwres.cc
pdns/recursordist/rec-tcpout.cc
pdns/recursordist/rec-tcpout.hh
pdns/recursordist/syncres.cc

index 36b0fd21065c9f68302b058b0b761f131474e3d2..2620709c38fdca86c76f824c5c972ed63de9d906 100644 (file)
@@ -306,22 +306,41 @@ static void logIncomingResponse(const std::shared_ptr<std::vector<std::unique_pt
   }
 }
 
-static bool tcpconnect(const ComboAddress& ip, TCPOutConnectionManager::Connection& connection, bool& dnsOverTLS, const std::string& nsName)
+class BindError
 {
-  dnsOverTLS = SyncRes::s_dot_to_port_853 && ip.getPort() == 853;
+};
 
-  connection = t_tcp_manager.get(ip);
+static bool tcpconnect(const ComboAddress& remote, const std::optional<ComboAddress> localBind, TCPOutConnectionManager::Connection& connection, bool& dnsOverTLS, const std::string& nsName)
+{
+  dnsOverTLS = SyncRes::s_dot_to_port_853 && remote.getPort() == 853;
+
+  connection = t_tcp_manager.get(std::make_pair(remote, localBind));
   if (connection.d_handler) {
     return false;
   }
 
   const struct timeval timeout{
     g_networkTimeoutMsec / 1000, static_cast<suseconds_t>(g_networkTimeoutMsec) % 1000 * 1000};
-  Socket s(ip.sin4.sin_family, SOCK_STREAM);
+  Socket s(remote.sin4.sin_family, SOCK_STREAM);
   s.setNonBlocking();
   setTCPNoDelay(s.getHandle());
-  ComboAddress localip = pdns::getQueryLocalAddress(ip.sin4.sin_family, 0);
-  s.bind(localip);
+  ComboAddress localip = localBind ? *localBind : pdns::getQueryLocalAddress(remote.sin4.sin_family, 0);
+  if (localBind) {
+    cerr << "Connecting TCP to " << remote.toString() << " with specific local address " << localip.toString() << endl;
+  }
+  else {
+    cerr << "Connecting TCP to " << remote.toString() << " with no specific local address" << endl;
+  }
+
+  try {
+    s.bind(localip);
+  }
+  catch (const NetworkError& e) {
+    if (localBind) {
+      throw BindError();
+    }
+    throw;
+  }
 
   std::shared_ptr<TLSCtx> tlsCtx{nullptr};
   if (dnsOverTLS) {
@@ -331,14 +350,15 @@ static bool tcpconnect(const ComboAddress& ip, TCPOutConnectionManager::Connecti
     // tlsParams.d_caStore
     tlsCtx = getTLSContext(tlsParams);
     if (tlsCtx == nullptr) {
-      g_slogout->info(Logr::Error, "DoT requested but not available", "server", Logging::Loggable(ip));
+      g_slogout->info(Logr::Error, "DoT requested but not available", "server", Logging::Loggable(remote));
       dnsOverTLS = false;
     }
   }
   connection.d_handler = std::make_shared<TCPIOHandler>(nsName, false, s.releaseHandle(), timeout, tlsCtx);
+  connection.d_local = localBind;
   // Returned state ignored
   // This can throw an exception, retry will need to happen at higher level
-  connection.d_handler->tryConnect(SyncRes::s_tcp_fast_open_connect, ip);
+  connection.d_handler->tryConnect(SyncRes::s_tcp_fast_open_connect, remote);
   return true;
 }
 
@@ -553,7 +573,7 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
     ret = arecvfrom(buf, 0, address, len, qid, domain, type, queryfd, subnetOpts, *now);
   }
   else {
-    bool isNew;
+    bool isNew{};
     do {
       try {
         // If we get a new (not re-used) TCP connection that does not
@@ -561,8 +581,7 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
         // peer has closed it on error, so we retry. At some point we
         // *will* get a new connection, so this loop is not endless.
         isNew = true; // tcpconnect() might throw for new connections. In that case, we want to break the loop, scanbuild complains here, which is a false positive afaik
-        // XXX cookie case: bind to local address
-        isNew = tcpconnect(address, connection, dnsOverTLS, nsName);
+        isNew = tcpconnect(address, addressToBindTo, connection, dnsOverTLS, nsName);
         ret = tcpsendrecv(address, connection, localip, vpacket, len, buf);
 #ifdef HAVE_FSTRM
         if (fstrmQEnabled) {
@@ -574,6 +593,12 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
         }
         connection.d_handler->close();
       }
+      catch (const BindError&) {
+        // Cookie info already has been added to packet, so we must retry from a higher level
+        auto lock = s_cookiestore.lock();
+        lock->erase(address);
+        return LWResult::Result::BindError;
+      }
       catch (const NetworkError&) {
         ret = LWResult::Result::OSLimitError; // OS limits error
       }
@@ -772,7 +797,7 @@ LWResult::Result asyncresolve(const ComboAddress& address, const DNSName& domain
 
   if (doTCP) {
     if (connection.d_handler && lwr->d_validpacket) {
-      t_tcp_manager.store(*now, address, std::move(connection));
+      t_tcp_manager.store(*now, std::make_pair(address, connection.d_local), std::move(connection));
     }
   }
   return ret;
index 46937018353987e69ed046d3a7098e8cb859f236..a4427771f2cac45b56b69106b5c003e1b54bb9ba 100644 (file)
@@ -51,32 +51,32 @@ void TCPOutConnectionManager::cleanup(const struct timeval& now)
   }
 }
 
-void TCPOutConnectionManager::store(const struct timeval& now, const ComboAddress& remoteAddress, Connection&& connection)
+void TCPOutConnectionManager::store(const struct timeval& now, const pair_t& pair, Connection&& connection)
 {
   ++connection.d_numqueries;
   if (s_maxQueries > 0 && connection.d_numqueries >= s_maxQueries) {
     return;
   }
 
-  if (d_idle_connections.size() >= s_maxIdlePerThread || d_idle_connections.count(remoteAddress) >= s_maxIdlePerAuth) {
+  if (d_idle_connections.size() >= s_maxIdlePerThread || d_idle_connections.count(pair) >= s_maxIdlePerAuth) {
     cleanup(now);
   }
 
   if (d_idle_connections.size() >= s_maxIdlePerThread) {
     return;
   }
-  if (d_idle_connections.count(remoteAddress) >= s_maxIdlePerAuth) {
+  if (d_idle_connections.count(pair) >= s_maxIdlePerAuth) {
     return;
   }
 
   gettimeofday(&connection.d_last_used, nullptr);
-  d_idle_connections.emplace(remoteAddress, std::move(connection));
+  d_idle_connections.emplace(pair, std::move(connection));
 }
 
-TCPOutConnectionManager::Connection TCPOutConnectionManager::get(const ComboAddress& remoteAddress)
+TCPOutConnectionManager::Connection TCPOutConnectionManager::get(const pair_t& pair)
 {
-  if (d_idle_connections.count(remoteAddress) > 0) {
-    auto connection = d_idle_connections.extract(remoteAddress);
+  if (d_idle_connections.count(pair) > 0) {
+    auto connection = d_idle_connections.extract(pair);
     return connection.mapped();
   }
   return Connection{};
index 9c433be2d58caad48c7f186527f7e3ab0985d277..e9bf48f8e3adfc171b5a94b6414c6e811269183a 100644 (file)
@@ -48,12 +48,15 @@ public:
     }
 
     std::shared_ptr<TCPIOHandler> d_handler;
+    std::optional<ComboAddress> d_local;
     timeval d_last_used{0, 0};
     size_t d_numqueries{0};
   };
 
-  void store(const struct timeval& now, const ComboAddress& remoteAddress, Connection&& connection);
-  Connection get(const ComboAddress& remoteAddress);
+  using pair_t = std::pair<ComboAddress, std::optional<ComboAddress>>;
+
+  void store(const struct timeval& now, const pair_t& pair, Connection&& connection);
+  Connection get(const pair_t& remoteAddress);
   void cleanup(const struct timeval& now);
 
   [[nodiscard]] size_t size() const
@@ -68,7 +71,7 @@ public:
 private:
   // This does not take into account that we can have multiple connections with different hosts (via SNI) to the same IP.
   // That is OK, since we are connecting by IP only at the moment.
-  std::multimap<ComboAddress, Connection> d_idle_connections;
+  std::multimap<pair_t, Connection> d_idle_connections;
 };
 
 extern thread_local TCPOutConnectionManager t_tcp_manager;
index 6769dd61b53a2d1df90c51d1bbc6c923647fc54a..8fafdba645062b238b29718db6a86b5901ad39e3 100644 (file)
@@ -5992,7 +5992,7 @@ int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, con
           if (SyncRes::s_dot_to_port_853 && remoteIP->getPort() == 853) {
             doDoT = true;
           }
-          bool forceTCP = doDoT;
+          bool forceTCP = doDoT | true;
 
           if (!doDoT && s_max_busy_dot_probes > 0) {
             submitTryDotTask(*remoteIP, auth, tns->first, d_now.tv_sec);