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