From: Bruno Jesus <00cpxxx@gmail.com> Subject: [PATCH] ws2_32: Implement automatic broadcast for UDP sockets on sendto() (try 2) Message-Id: <20161203021544.14832-1-00cpxxx@gmail.com> Date: Sat, 3 Dec 2016 00:15:44 -0200 Based on original Erich Hoover's patch. Fixes bug https://bugs.winehq.org/show_bug.cgi?id=31994 try2: Check if the IP is really a broadcast address as requested by Hans Leidekker Avoid ntohl to save time by checking the IP in network order original: 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@gmail.com> --- dlls/ws2_32/socket.c | 67 ++++++++++++++++++++++- dlls/ws2_32/tests/sock.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 202 insertions(+), 5 deletions(-) diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c index d863b3e..4fd9beb 100644 --- a/dlls/ws2_32/socket.c +++ b/dlls/ws2_32/socket.c @@ -1150,6 +1150,36 @@ static int _get_fd_type(int fd) return sock_type; } +static BOOL is_ipv4_broadcast_addr(in_addr_t ip) +{ + PMIB_IPADDRTABLE addr_table; + DWORD size, bcast; + int i; + + if (GetIpAddrTable(NULL, &size, FALSE) != ERROR_INSUFFICIENT_BUFFER) + return FALSE; + + addr_table = HeapAlloc(GetProcessHeap(), 0, size); + if (!addr_table) + return FALSE; + + if (GetIpAddrTable(addr_table, &size, FALSE)) + { + HeapFree(GetProcessHeap(), 0, addr_table); + return FALSE; + } + + for (i = 0; i < addr_table->dwNumEntries; i++) + { + bcast = addr_table->table[i].dwAddr | ~addr_table->table[i].dwMask; + if (bcast == ip) + break; + } + + HeapFree(GetProcessHeap(), 0, addr_table); + return i != addr_table->dwNumEntries; +} + static struct per_thread_data *get_per_thread_data(void) { struct per_thread_data * ptb = NtCurrentTeb()->WinSockData; @@ -2431,7 +2461,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; @@ -2446,8 +2476,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 are going to check if + * it matches the broadcast address. Trying to send the packet without + * setting SO_BROADCAST results in EACCES, to avoid that we will enable + * the flag and send the packet, after that we will restore the flag + * This is the most common estimate to reduce the number of UDP packets + * that we need to check. */ + struct sockaddr_in *addr = (struct sockaddr_in*) hdr.msg_name; + in_addr_t address = addr->sin_addr.s_addr; + + if (address != INADDR_BROADCAST && (address & 0xFF000000) == 0xFF000000) + { + unsigned int value = 0, optlen = sizeof(value); + if (_get_fd_type(fd) == SOCK_DGRAM && + !getsockopt(fd, SOL_SOCKET, SO_BROADCAST, &value, &optlen) && + !value && is_ipv4_broadcast_addr(address)) + { + 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; @@ -2478,7 +2532,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; @@ -2489,6 +2543,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 83a931e..fa1947d 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -5133,14 +5133,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)); @@ -5248,6 +5251,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); } @@ -9973,12 +10092,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(); @@ -10060,5 +10194,7 @@ START_TEST( sock ) test_send(); test_synchronous_WSAIoctl(); + enable_firewall(); + Exit(); } -- 2.9.3