Implement Happy Eyeballs for C4Network2HTTPClient

This should make masterserver requests more reliable for users with a
bad IPv6 connection.

See RFC6555
ipv6
Lukas Werling 2017-01-15 20:20:49 +01:00
parent f9c97e91f0
commit d7659713dc
2 changed files with 33 additions and 7 deletions

View File

@ -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<time_t>(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(), ':');

View File

@ -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;