From: Bruno Jesus <00cpxxx@gmail.com> Subject: ws2_32: Restore the UDP socket address when it was bound with filter for getsockname() Message-Id: Date: Thu, 2 Apr 2015 23:18:08 -0300 To be able to receive UDP broadcasts wine bind() instead of binding to the passed IP binds to 0.0.0.0 and set a filter for a specific interface (which was the original requested bind address). This fixed many directplay games that work on LAN by sending broadcasts to find and advertise games. But there is a problem at least for Age of Mythology, right after binding it will use getsockname() to read the address back but the function will return 0.0.0.0 as expected by our code but unexpected by the application, so this patch checks if the socket is interface bound and retrieve the correct address to it. //Sample output of wine-git (note the wrong 0.0.0.0 returned on getsockname): trace:winsock:WS_bind socket 01b8, ptr 0x2434248 { family AF_INET, address 192.168.0.190, port 2300 }, length 16 trace:winsock:interface_bind Socket 01b8 bound to interface index 3 trace:winsock:WS_getsockname socket 01b8, ptr 0x2434248, len 00000010 trace:winsock:WS_getsockname => { family AF_INET, address 0.0.0.0, port 2300 } //Sample after patch: trace:winsock:WS_bind socket 01b8, ptr 0x2434248 { family AF_INET, address 192.168.0.190, port 2300 }, length 16 trace:winsock:interface_bind Socket 01b8 bound to interface index 3 trace:winsock:WS_getsockname socket 01b8, ptr 0x2434248, len 00000010 trace:winsock:tune_interface_address Reporting interface bound address from adapter 3 trace:winsock:WS_getsockname => { family AF_INET, address 192.168.0.190, port 2300 } Also tested on NT4. Fix a problem detected from bug https://bugs.winehq.org/show_bug.cgi?id=35831 which probably is the bug subject. This is also a prequel to fixing bug https://bugs.winehq.org/show_bug.cgi?id=31994 diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c index 09c8416..38ac5a4 100644 --- a/dlls/ws2_32/socket.c +++ b/dlls/ws2_32/socket.c @@ -2839,6 +2839,64 @@ cleanup: return ret; } +/* When binding to an UDP address with filter support the getsockname call on the socket + * will always return 0.0.0.0 instead of the filtered interface address. This function + * checks if the socket is interface-bound on UDP and return the correct address. + * This is required because applications often do a bind() with port zero followed by a + * getsockname() to retrieve the port and address acquired. + */ +static void tune_interface_address(int fd, struct sockaddr_in *addr) +{ +#if !defined(IP_BOUND_IF) && !defined(LINUX_BOUND_IF) + return; +#else + int value; + socklen_t len = sizeof(value); + + /* Check for IPv4, address 0.0.0.0 and UDP socket */ + if (addr->sin_family != AF_INET || addr->sin_addr.s_addr != 0) + return; + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &value, &len) || value != SOCK_DGRAM) + return; + + value = -1; + len = sizeof(value); +#if defined(IP_BOUND_IF) + getsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &value, &len); +#elif defined(LINUX_BOUND_IF) + getsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &value, &len); + if (value > 0) value = ntohl(value); +#endif + if (value > 0) + { + PIP_ADAPTER_INFO adapters, adapter; + DWORD adap_size; + + if (GetAdaptersInfo(NULL, &adap_size) != ERROR_BUFFER_OVERFLOW) + return; + adapters = HeapAlloc(GetProcessHeap(), 0, adap_size); + if (adapters && GetAdaptersInfo(adapters, &adap_size) == NO_ERROR) + { + /* Search the IPv4 adapter list for the appropriate bound interface */ + for (adapter = adapters; adapter != NULL; adapter = adapter->Next) + { + in_addr_t adapter_addr; + if (adapter->Index != value) continue; + + adapter_addr = inet_addr(adapter->IpAddressList.IpAddress.String); + if (adapter_addr) /* Ensure we have an IP */ + { + addr->sin_addr.s_addr = adapter_addr; + TRACE("Reporting interface bound address from adapter %d\n", value); + } + break; + } + } + HeapFree(GetProcessHeap(), 0, adapters); + } +#endif +} + /*********************************************************************** * bind (WS2_32.2) */ @@ -3247,15 +3305,19 @@ int WINAPI WS_getsockname(SOCKET s, struct WS_sockaddr *name, int *namelen) { SetLastError(bound == -1 ? wsaErrno() : WSAEINVAL); } - else if (ws_sockaddr_u2ws(&uaddr.addr, name, namelen) != 0) - { - /* The buffer was too small */ - SetLastError(WSAEFAULT); - } else { - res = 0; - TRACE("=> %s\n", debugstr_sockaddr(name)); + tune_interface_address(fd, (struct sockaddr_in*) &uaddr); + if (ws_sockaddr_u2ws(&uaddr.addr, name, namelen) != 0) + { + /* The buffer was too small */ + SetLastError(WSAEFAULT); + } + else + { + res = 0; + TRACE("=> %s\n", debugstr_sockaddr(name)); + } } release_sock_fd( s, fd ); } diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index 287a24b..9718bfe 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -4150,6 +4150,7 @@ static void test_getsockname(void) int sa_get_len = sa_set_len; static const unsigned char null_padding[] = {0,0,0,0,0,0,0,0}; int ret; + struct hostent *h; if(WSAStartup(MAKEWORD(2,0), &wsa)){ trace("Winsock failed: %d. Aborting test\n", WSAGetLastError()); @@ -4197,6 +4198,38 @@ static void test_getsockname(void) "getsockname did not zero the sockaddr_in structure\n"); closesocket(sock); + + h = gethostbyname(""); + if (h && h->h_length == 4) /* this test is only meaningful in IPv4 */ + { + int i; + for (i = 0; h->h_addr_list[i]; i++) + { + char ipstr[32]; + struct in_addr ip; + ip.s_addr = *(ULONG *) h->h_addr_list[i]; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + ok(sock != INVALID_SOCKET, "socket failed with %d\n", GetLastError()); + + memset(&sa_set, 0, sizeof(sa_set)); + sa_set.sin_family = AF_INET; + sa_set.sin_addr.s_addr = ip.s_addr; + /* The same address we bind must be the same address we get */ + ret = bind(sock, (struct sockaddr*)&sa_set, sizeof(sa_set)); + ok(ret == 0, "bind failed with %d\n", GetLastError()); + sa_get_len = sizeof(sa_get); + ret = getsockname(sock, (struct sockaddr*)&sa_get, &sa_get_len); + ok(ret == 0, "getsockname failed with %d\n", GetLastError()); + strcpy(ipstr, inet_ntoa(sa_get.sin_addr)); + trace("testing bind on interface %s\n", ipstr); + ok(sa_get.sin_addr.s_addr == sa_set.sin_addr.s_addr, + "address does not match: %s != %s", ipstr, inet_ntoa(sa_set.sin_addr)); + + closesocket(sock); + } + } + WSACleanup(); }