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.
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ws2_32/unixlib.c | 73 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+)
diff --git a/dlls/ws2_32/unixlib.c b/dlls/ws2_32/unixlib.c index 0625c5c72ce..5af4a075d8b 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,28 @@ 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 ) +{ + 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 +982,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 +1010,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 );
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=128922
Your paranoid android.
=== debian11 (build log) ===
/home/winetest/tools/testbot/var/wine-win32/../wine/dlls/ws2_32/unixlib.c:952: undefined reference to `pthread_once' collect2: error: ld returned 1 exit status Task: The win32 Wine build failed
=== debian11b (build log) ===
/home/winetest/tools/testbot/var/wine-wow64/../wine/dlls/ws2_32/unixlib.c:952: undefined reference to `pthread_once' collect2: error: ld returned 1 exit status Task: The wow64 Wine build failed
It would probably be nice to have this explanation in the code itself, for future readers wondering why we are sorting and hashing addresses.