From: Otto Moerbeek Date: Tue, 18 Feb 2025 11:21:02 +0000 (+0100) Subject: TCP support for cookies, taking into account idle outgoing connections X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7d7047a7d12be8b6858e96ce0081cedbc156a34c;p=thirdparty%2Fpdns.git TCP support for cookies, taking into account idle outgoing connections --- diff --git a/pdns/recursordist/lwres.cc b/pdns/recursordist/lwres.cc index 36b0fd210..2620709c3 100644 --- a/pdns/recursordist/lwres.cc +++ b/pdns/recursordist/lwres.cc @@ -306,22 +306,41 @@ static void logIncomingResponse(const std::shared_ptr 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(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{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(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; diff --git a/pdns/recursordist/rec-tcpout.cc b/pdns/recursordist/rec-tcpout.cc index 469370183..a4427771f 100644 --- a/pdns/recursordist/rec-tcpout.cc +++ b/pdns/recursordist/rec-tcpout.cc @@ -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{}; diff --git a/pdns/recursordist/rec-tcpout.hh b/pdns/recursordist/rec-tcpout.hh index 9c433be2d..e9bf48f8e 100644 --- a/pdns/recursordist/rec-tcpout.hh +++ b/pdns/recursordist/rec-tcpout.hh @@ -48,12 +48,15 @@ public: } std::shared_ptr d_handler; + std::optional 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>; + + 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 d_idle_connections; + std::multimap d_idle_connections; }; extern thread_local TCPOutConnectionManager t_tcp_manager; diff --git a/pdns/recursordist/syncres.cc b/pdns/recursordist/syncres.cc index 6769dd61b..8fafdba64 100644 --- a/pdns/recursordist/syncres.cc +++ b/pdns/recursordist/syncres.cc @@ -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);