diff --git a/src/network/C4Network2Reference.cpp b/src/network/C4Network2Reference.cpp index a8c4b9b0e..0a78809e7 100644 --- a/src/network/C4Network2Reference.cpp +++ b/src/network/C4Network2Reference.cpp @@ -407,7 +407,7 @@ bool C4Network2HTTPClient::Decompress(StdBuf *pData) bool C4Network2HTTPClient::OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO) { // Make sure we're actually waiting for this connection - if (AddrConnect != ServerAddr) + if (fConnected || (AddrConnect != ServerAddr && AddrConnect != ServerAddrFallback)) return false; // Save pack peer address PeerAddr = AddrPeer; @@ -445,10 +445,19 @@ void C4Network2HTTPClient::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO bool C4Network2HTTPClient::Execute(int iMaxTime) { // Check timeout - if (fBusy && time(nullptr) > iRequestTimeout) + if (fBusy) { - Cancel("Request timeout"); - return true; + if (C4TimeMilliseconds::Now() > HappyEyeballsTimeout) + { + HappyEyeballsTimeout = C4TimeMilliseconds::PositiveInfinity; + Application.InteractiveThread.ThreadLogS("HTTP: Starting fallback connection to %s (%s)", Server.getData(), ServerAddrFallback.ToString().getData()); + Connect(ServerAddrFallback); + } + if (time(nullptr) > iRequestTimeout) + { + Cancel("Request timeout"); + return true; + } } // Execute normally return C4NetIOTCP::Execute(iMaxTime); @@ -462,7 +471,9 @@ C4TimeMilliseconds C4Network2HTTPClient::GetNextTick(C4TimeMilliseconds tNow) C4TimeMilliseconds tHTTPClientTick = tNow + 1000 * std::max(iRequestTimeout - time(nullptr), 0); - return std::max(tNetIOTCPTick, tHTTPClientTick); + C4TimeMilliseconds HappyEyeballsTick = tNow + std::max(HappyEyeballsTimeout - C4TimeMilliseconds::Now(), 0); + + return std::min({tNetIOTCPTick, tHTTPClientTick, HappyEyeballsTick}); } bool C4Network2HTTPClient::Query(const StdBuf &Data, bool fBinary) @@ -512,6 +523,11 @@ bool C4Network2HTTPClient::Query(const StdBuf &Data, bool fBinary) // Start connecting if (!Connect(ServerAddr)) return false; + // Also try the fallback address after some time (if there is one) + if (!ServerAddrFallback.IsNull()) + HappyEyeballsTimeout = C4TimeMilliseconds::Now() + C4Network2HTTPHappyEyeballsTimeout; + else + HappyEyeballsTimeout = C4TimeMilliseconds::PositiveInfinity; // Okay, request will be performed when connection is complete fBusy = true; iDataOffset = 0; @@ -529,7 +545,7 @@ void C4Network2HTTPClient::ResetRequestTimeout() void C4Network2HTTPClient::Cancel(const char *szReason) { // Close connection - and connection attempt - Close(ServerAddr); Close(PeerAddr); + Close(ServerAddr); Close(ServerAddrFallback); Close(PeerAddr); // Reset flags fBusy = fSuccess = fConnected = fBinary = false; iDownloadedSize = iTotalSize = iDataOffset = 0; @@ -567,6 +583,14 @@ bool C4Network2HTTPClient::SetServer(const char *szServerAddress) return false; } ServerAddr.SetDefaultPort(GetDefaultPort()); + if (ServerAddr.GetFamily() == C4NetIO::HostAddress::IPv6) + { + // Try to find a fallback IPv4 address for Happy Eyeballs. + ServerAddrFallback.SetAddress(Server, C4NetIO::HostAddress::IPv4); + ServerAddrFallback.SetDefaultPort(GetDefaultPort()); + } + else + ServerAddrFallback.Clear(); // Remove port const char *firstColon = strchr(Server.getData(), ':'); const char *lastColon = strrchr(Server.getData(), ':'); diff --git a/src/network/C4Network2Reference.h b/src/network/C4Network2Reference.h index 37705ec52..12a3cee35 100644 --- a/src/network/C4Network2Reference.h +++ b/src/network/C4Network2Reference.h @@ -24,6 +24,7 @@ #include "lib/C4InputValidation.h" const int C4Network2HTTPQueryTimeout = 10; // (s) +const uint32_t C4Network2HTTPHappyEyeballsTimeout = 300; // (ms) // Session data class C4Network2Reference @@ -129,7 +130,7 @@ public: private: // Address information - C4NetIO::addr_t ServerAddr, PeerAddr; + C4NetIO::addr_t ServerAddr, ServerAddrFallback, PeerAddr; StdCopyStrBuf Server, RequestPath; bool fBinary; @@ -137,6 +138,7 @@ private: size_t iDataOffset; StdCopyBuf Request; time_t iRequestTimeout; + C4TimeMilliseconds HappyEyeballsTimeout; // Response header data size_t iDownloadedSize, iTotalSize;