ws2_32: Restore the UDP socket address when it was bound with filter for getsockname()

Bruno Jesus 00cpxxx at gmail.com
Thu Apr 2 21:18:08 CDT 2015


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
-------------- next part --------------
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();
 }
 


More information about the wine-patches mailing list