Fixes error connecting to servers in Life is Strange Remastered (due to timeout).
When there are multiple IP addresses for a host name Linux gethostbyname() returns those in random order upon each call (meaning to provide server load balancing). On Windows the order of IP addresses is not determined as well but it is the same on consequent calls (changes after network resets and probably DNS timeout expiration).
The game executes multiple http requests over TLS connection through its own libraries using only winsock from Wine. Upon executing each next request it calls gethostbyname() for the server DNS name and uses the first returned IP address. When that's different from the previous IP address used for established TLS connection it doesn't reuse the connection and establishes the connection from scratch which is very long process due to how the game does it (on Windows as well).
One obvious way to make gethostbyname() behave like Windows would be to cache the results we get from host. But caching gethostbyname() results is tricky, there are already host-side caches which take into account DNS timeouts and network interfaces change. It is possible in theory to cache the result and then still call native gethostbyname() and keep the order if nothing else has changed in the reply, but it seems to me that would complicate things more than this patch does. The patch sorts the output by IP address randomly hashed with a random transform hash established only once per process run. So it will still provide load balancing between the IP addresses between different clients and different process instances while will be returning the same order of IP addresses on consequent calls.
-- v2: ws2_32: Provide same address order from gethostbyname() on consequent calls.
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ws2_32/Makefile.in | 1 + dlls/ws2_32/unixlib.c | 81 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+)
diff --git a/dlls/ws2_32/Makefile.in b/dlls/ws2_32/Makefile.in index 13937dc6ee7..fc13323b930 100644 --- a/dlls/ws2_32/Makefile.in +++ b/dlls/ws2_32/Makefile.in @@ -3,6 +3,7 @@ MODULE = ws2_32.dll UNIXLIB = ws2_32.so IMPORTLIB = ws2_32 DELAYIMPORTS = dnsapi advapi32 iphlpapi user32 +UNIX_LIBS = $(PTHREAD_LIBS)
C_SRCS = \ async.c \ diff --git a/dlls/ws2_32/unixlib.c b/dlls/ws2_32/unixlib.c index 0625c5c72ce..b979316dc6a 100644 --- a/dlls/ws2_32/unixlib.c +++ b/dlls/ws2_32/unixlib.c @@ -167,6 +167,51 @@ static const int ip_protocol_map[][2] =
#undef MAP
+static pthread_once_t hash_init_once = PTHREAD_ONCE_INIT; +static BYTE byte_hash[256]; + +static void init_hash(void) +{ + unsigned i, index; + NTSTATUS status; + BYTE *buf, tmp; + ULONG buf_len; + + for (i = 0; i < sizeof(byte_hash); ++i) + byte_hash[i] = i; + + buf_len = sizeof(SYSTEM_INTERRUPT_INFORMATION) * NtCurrentTeb()->Peb->NumberOfProcessors; + if (!(buf = malloc( buf_len ))) + { + ERR( "No memory.\n" ); + return; + } + + for (i = 0; i < sizeof(byte_hash) - 1; ++i) + { + if (!(i % buf_len) && (status = NtQuerySystemInformation( SystemInterruptInformation, buf, + buf_len, &buf_len ))) + { + ERR( "Failed to get random bytes.\n" ); + free(buf); + return; + } + index = i + buf[i % buf_len] % (sizeof(byte_hash) - i); + tmp = byte_hash[index]; + byte_hash[index] = byte_hash[i]; + byte_hash[i] = tmp; + } + free( buf ); +} + +static void hash_random( BYTE *d, const BYTE *s, unsigned int len ) +{ + unsigned int i; + + for (i = 0; i < len; ++i) + d[i] = byte_hash[s[i]]; +} + static int addrinfo_flags_from_unix( int flags ) { int ws_flags = 0; @@ -889,6 +934,36 @@ static NTSTATUS unix_gethostbyaddr( void *args ) #endif }
+static int compare_addrs_hashed(const void *a1, const void *a2, void *length) +{ + unsigned int addr_len = (uintptr_t)length; + char a1_hashed[16], a2_hashed[16]; + + assert( addr_len <= sizeof(a1_hashed) ); + hash_random( (BYTE *)a1_hashed, *(void **)a1, addr_len ); + hash_random( (BYTE *)a2_hashed, *(void **)a2, addr_len ); + return memcmp( a1_hashed, a2_hashed, addr_len ); +} + +static void sort_addrs_hashed( struct hostent *host ) +{ + /* On Unix gethostbyname() may return IP addresses in random order on each call. On Windows the order of + * IP addresses is not determined as well but it is the same on consequent calls (changes after network + * resets and probably DNS timeout expiration). + * Life is Strange Remastered depends on gethostbyname() returning IP addresses in the same order to reuse + * the established TLS connection and avoid timeouts that happen in game when establishing multiple extra TLS + * connections. + * Just sorting the addresses would break server load balancing provided by gethostbyname(), so randomize the + * sort once per process. */ + unsigned int count; + + pthread_once(&hash_init_once, init_hash); + + for (count = 0; host->h_addr_list[count]; ++count) + ; + qsort_r( host->h_addr_list, count, sizeof(*host->h_addr_list), compare_addrs_hashed, + (void *)(uintptr_t)host->h_length ); +}
#ifdef HAVE_LINUX_GETHOSTBYNAME_R_6 static NTSTATUS unix_gethostbyname( void *args ) @@ -915,9 +990,14 @@ static NTSTATUS unix_gethostbyname( void *args ) }
if (!unix_host) + { ret = (locerr < 0 ? errno_from_unix( errno ) : host_errno_from_unix( locerr )); + } else + { + sort_addrs_hashed( unix_host ); ret = hostent_from_unix( unix_host, params->host, params->size ); + }
free( unix_buffer ); return ret; @@ -938,6 +1018,7 @@ static NTSTATUS unix_gethostbyname( void *args ) return ret; }
+ sort_addrs_hashed( unix_host ); ret = hostent_from_unix( unix_host, params->host, params->size );
pthread_mutex_unlock( &host_mutex );
v2: - Add $(PTHREAD_LIBS) to Makefile; - Add a comment.
This merge request was approved by Zebediah Figura.
Jinoh Kang (@iamahuman) commented about dlls/ws2_32/unixlib.c:
+{
- /* On Unix gethostbyname() may return IP addresses in random order on each call. On Windows the order of
* IP addresses is not determined as well but it is the same on consequent calls (changes after network
* resets and probably DNS timeout expiration).
* Life is Strange Remastered depends on gethostbyname() returning IP addresses in the same order to reuse
* the established TLS connection and avoid timeouts that happen in game when establishing multiple extra TLS
* connections.
* Just sorting the addresses would break server load balancing provided by gethostbyname(), so randomize the
* sort once per process. */
- unsigned int count;
- pthread_once(&hash_init_once, init_hash);
- for (count = 0; host->h_addr_list[count]; ++count)
;
- qsort_r( host->h_addr_list, count, sizeof(*host->h_addr_list), compare_addrs_hashed,
`qsort_r` is a glibc extension. Note that `gethostbyname_r`'s usage is guarded by `#ifdef HAVE_LINUX_GETHOSTBYNAME_R_6`.