ws2_32: Implement automatic broadcast for UDP sockets on sendto()

Bruno Jesus 00cpxxx at gmail.com
Tue Dec 22 05:08:44 CST 2015


Based on original Erich Hoover's patch.

Fixes bug https://bugs.winehq.org/show_bug.cgi?id=31994

As discussed with Francois in April 2015 [1] the XP and 2008 testbot
machines need the firewall to be off to receive broadcasts, that is
why the patch disables them and re-enables after the test.

Tests and changes can't be split because wine would hang without the changes.

[1] https://www.winehq.org/pipermail/wine-devel/2015-April/107230.html
(first from thread)

Signed-off-by: Bruno Jesus <00cpxxx at gmail.com>
-------------- next part --------------
diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c
index d31f0b4..4756286 100644
--- a/dlls/ws2_32/socket.c
+++ b/dlls/ws2_32/socket.c
@@ -2386,7 +2386,7 @@ static int WS2_send( int fd, struct ws2_async *wsa, int flags )
 {
     struct msghdr hdr;
     union generic_unix_sockaddr unix_addr;
-    int n, ret;
+    int n, ret, manual_broadcast = 0;
 
     hdr.msg_name = NULL;
     hdr.msg_namelen = 0;
@@ -2401,8 +2401,32 @@ static int WS2_send( int fd, struct ws2_async *wsa, int flags )
             return -1;
         }
 
+        if (wsa->addr->sa_family == WS_AF_INET)
+        {
+            /* When the target IPv4 address ends in 255 we must always send it as
+             * a broadcast. Trying to send the packet without setting SO_BROADCAST
+             * results in EACCES, to avoid that we will manually enable the flag
+             * and send the packet, after that we will restore the disabled flag
+             * behavior. This is the most common estimate as the broadcast address
+             * should actually be calculated using the netmask for the interface. */
+            struct sockaddr_in *addr = (struct sockaddr_in*) hdr.msg_name;
+            in_addr_t address = ntohl(addr->sin_addr.s_addr);
+
+            if (address != INADDR_BROADCAST && (address & 0x000000FF) == 0x000000FF)
+            {
+                unsigned int value = 0, optlen = sizeof(value);
+                if (_get_fd_type(fd) == SOCK_DGRAM &&
+                    !getsockopt(fd, SOL_SOCKET, SO_BROADCAST, &value, &optlen) &&
+                    !value)
+                {
+                    manual_broadcast = 1;
+                    setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &manual_broadcast, sizeof(manual_broadcast));
+                    TRACE("manually broadcasting packet to address %s\n", debugstr_sockaddr(wsa->addr));
+                }
+            }
+        }
 #if defined(HAS_IPX) && defined(SOL_IPX)
-        if(wsa->addr->sa_family == WS_AF_IPX)
+        else if(wsa->addr->sa_family == WS_AF_IPX)
         {
             struct sockaddr_ipx* uipx = (struct sockaddr_ipx*)hdr.msg_name;
             int val=0;
@@ -2433,7 +2457,7 @@ static int WS2_send( int fd, struct ws2_async *wsa, int flags )
     while ((ret = sendmsg(fd, &hdr, flags)) == -1)
     {
         if (errno != EINTR)
-            return -1;
+            goto out;
     }
 
     n = ret;
@@ -2444,6 +2468,13 @@ static int WS2_send( int fd, struct ws2_async *wsa, int flags )
         wsa->iovec[wsa->first_iovec].iov_base = (char*)wsa->iovec[wsa->first_iovec].iov_base + n;
         wsa->iovec[wsa->first_iovec].iov_len -= n;
     }
+out:
+    if (manual_broadcast)
+    {
+        /* Return the old broadcast settings */
+        manual_broadcast = 0;
+        setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &manual_broadcast, sizeof(manual_broadcast));
+    }
     return ret;
 }
 
diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c
index 8676b07..1ab272d 100644
--- a/dlls/ws2_32/tests/sock.c
+++ b/dlls/ws2_32/tests/sock.c
@@ -4942,14 +4942,17 @@ static void test_send(void)
 {
     SOCKET src = INVALID_SOCKET;
     SOCKET dst = INVALID_SOCKET;
+    struct sockaddr_in addr, from;
+    struct in_addr ip;
     HANDLE hThread = NULL;
     const int buflen = 1024*1024;
     char *buffer = NULL;
-    int ret, i, zero = 0;
+    int ret, i, zero = 0, len;
     WSABUF buf;
     OVERLAPPED ov;
+    struct hostent *h;
     BOOL bret;
-    DWORD id, bytes_sent, dwRet;
+    DWORD id, bytes_sent, dwRet, bytes_rec;
 
     memset(&ov, 0, sizeof(ov));
 
@@ -5057,6 +5060,122 @@ end:
     }
     if (ov.hEvent)
         CloseHandle(ov.hEvent);
+
+    /* Test sending a broadcast packet to a not enabled broadcast UDP socket - bug 31994
+     * Do it for all interfaces available. Sometimes broadcast packets are received more
+     * than once in the same interface so ensure we read all packets for each test. */
+    h = gethostbyname("");
+
+    if (h && h->h_length == 4) /* this test is only meaningful in IPv4 */
+    {
+        for (i = 0; h->h_addr_list[i]; i++)
+        {
+            ip.s_addr = *(ULONG *) h->h_addr_list[i];
+            /* Skip any local address */
+            if ((ntohl(ip.s_addr) & 0x7F000000) == 0x7F000000) continue;
+
+            trace("testing broadcast for interface IP %s\n", inet_ntoa(ip));
+
+            src = socket(AF_INET, SOCK_DGRAM, 0);
+            ok(src != INVALID_SOCKET, "socket failed with %d\n", GetLastError());
+            dst = socket(AF_INET, SOCK_DGRAM, 0);
+            ok(dst != INVALID_SOCKET, "socket failed with %d\n", GetLastError());
+
+            memset(&addr, 0, sizeof(addr));
+            addr.sin_family = AF_INET;
+            addr.sin_addr.s_addr = inet_addr("0.0.0.0");
+            ret = bind(src, (struct sockaddr*)&addr, sizeof(addr));
+            ok(ret == 0, "bind failed with %d\n", GetLastError());
+
+            memset(&addr, 0, sizeof(addr));
+            addr.sin_family = AF_INET;
+            addr.sin_addr.s_addr = ip.s_addr;
+            ret = bind(dst, (struct sockaddr*)&addr, sizeof(addr));
+            ok(ret == 0, "bind failed with %d\n", GetLastError());
+            len = sizeof(addr);
+            ret = getsockname(dst, (struct sockaddr*)&addr, &len);
+            addr.sin_addr.s_addr = ip.s_addr;
+            ok(ret == 0, "getsockname failed with %d\n", GetLastError());
+
+            /* Send directly to target */
+            ret = sendto(src, "1234", 4, 0, (struct sockaddr*)&addr, len);
+            ok(ret == 4, "sendto failed with %d\n", GetLastError());
+            ret = recvfrom(dst, buffer, buflen, 0, (struct sockaddr*)&from, &len);
+            ok(ret == 4, "recvfrom failed with %d\n", GetLastError());
+            buffer[ret] = '\0';
+            ok(!strcmp(buffer , "1234"), "buffer content differs, got %s\n", buffer);
+            /* Attempt an auto broadcast using the IP ending in 255 */
+            addr.sin_addr.s_addr = htonl(ntohl(addr.sin_addr.s_addr) | 0xFF);
+            ret = sendto(src, "5678", 4, 0, (struct sockaddr*)&addr, len);
+            ok(ret == 4, "sendto failed with %d\n", GetLastError());
+            if (ret > 0)
+                do
+                {
+                    ret = recvfrom(dst, buffer, buflen, 0, (struct sockaddr*)&from, &len);
+                    ok(ret == 4, "recvfrom failed with %d\n", GetLastError());
+                    buffer[ret] = '\0';
+                    ok(!strcmp(buffer , "5678"), "buffer content differs, got %s\n", buffer);
+                    Sleep(10);
+                    ret = ioctlsocket (dst, FIONREAD, &bytes_rec);
+                    ok (ret == 0, "ioctlsocket failed with %d\n", GetLastError());
+                }
+                while (bytes_rec);
+            /* Attempt a broadcast using the correct sockopt */
+            ret = getsockname(dst, (struct sockaddr*)&addr, &len);
+            addr.sin_addr.s_addr = ip.s_addr;
+            ok(ret == 0, "getsockname failed with %d\n", GetLastError());
+            zero = 1;
+            ret = setsockopt (src, SOL_SOCKET, SO_BROADCAST, (char *) &zero, sizeof(zero));
+            ok(ret == 0, "setsockopt failed with %d\n", GetLastError());
+            ret = sendto(src, "9ABC", 4, 0, (struct sockaddr*)&addr, len);
+            ok(ret == 4, "sendto failed with %d\n", GetLastError());
+            if (ret > 0)
+                do
+                {
+                    ret = recvfrom(dst, buffer, buflen, 0, (struct sockaddr*)&from, &len);
+                    ok(ret == 4, "recvfrom failed with %d\n", GetLastError());
+                    buffer[ret] = '\0';
+                    ok(!strcmp(buffer , "9ABC"), "buffer content differs, got %s\n", buffer);
+                    Sleep(10);
+                    ret = ioctlsocket (dst, FIONREAD, &bytes_rec);
+                    ok (ret == 0, "ioctlsocket failed with %d\n", GetLastError());
+                }
+                while (bytes_rec);
+            /* Try again sending with IP ending in 255 when SO_BROADCAST is enabled */
+            addr.sin_addr.s_addr = htonl(ntohl(addr.sin_addr.s_addr) | 0xFF);
+            ret = sendto(src, "DEFG", 4, 0, (struct sockaddr*)&addr, len);
+            ok(ret == 4, "sendto failed with %d\n", GetLastError());
+            if (ret > 0)
+                do
+                {
+                    ret = recvfrom(dst, buffer, buflen, 0, (struct sockaddr*)&from, &len);
+                    ok(ret == 4, "recvfrom failed with %d\n", GetLastError());
+                    buffer[ret] = '\0';
+                    ok(!strcmp(buffer , "DEFG"), "buffer content differs, got %s\n", buffer);
+                    Sleep(10);
+                    ret = ioctlsocket (dst, FIONREAD, &bytes_rec);
+                    ok (ret == 0, "ioctlsocket failed with %d\n", GetLastError());
+                }
+                while (bytes_rec);
+            /* Disable SO_BROADCAST and direct sending still works */
+            ret = getsockname(dst, (struct sockaddr*)&addr, &len);
+            addr.sin_addr.s_addr = ip.s_addr;
+            ok(ret == 0, "getsockname failed with %d\n", GetLastError());
+            zero = 0;
+            ret = setsockopt (src, SOL_SOCKET, SO_BROADCAST, (char *) &zero, sizeof(zero));
+            ret = sendto(src, "HIJL", 4, 0, (struct sockaddr*)&addr, len);
+            ok(ret == 4, "sendto failed with %d\n", GetLastError());
+            ret = recvfrom(dst, buffer, buflen, 0, (struct sockaddr*)&from, &len);
+            ok(ret == 4, "recvfrom failed with %d\n", GetLastError());
+            buffer[ret] = '\0';
+            ok(!strcmp(buffer , "HIJL"), "buffer content differs, got %s\n", buffer);
+            closesocket(dst);
+            closesocket(src);
+        }
+    }
+    else
+        win_skip("Skipping broadcast tests because there is no IPv4 interface\n");
+
     HeapFree(GetProcessHeap(), 0, buffer);
 }
 
@@ -9360,12 +9479,27 @@ todo_wine
     HeapFree(GetProcessHeap(), 0, name);
 }
 
+/* required for broadcast tests */
+static void disable_firewall(void)
+{
+    system("netsh firewall set opmode disable"); /* XP */
+    system("netsh Advfirewall set allprofiles state off"); /* >= Vista*/
+}
+
+static void enable_firewall(void)
+{
+    system("netsh firewall set opmode enable"); /* XP */
+    system("netsh Advfirewall set allprofiles state on"); /* >= Vista*/
+}
+
 /**************** Main program  ***************/
 
 START_TEST( sock )
 {
     int i;
 
+    disable_firewall();
+
 /* Leave these tests at the beginning. They depend on WSAStartup not having been
  * called, which is done by Init() below. */
     test_WithoutWSAStartup();
@@ -9445,5 +9579,7 @@ START_TEST( sock )
     test_send();
     test_synchronous_WSAIoctl();
 
+    enable_firewall();
+
     Exit();
 }


More information about the wine-patches mailing list