From: Jason Beetham beefers331@gmail.com
--- On Windows it seems sending to port 0 does nothing and does not error. Presently sendmsg errors with EINVAL. This works around it, by checking if it's port 0 then skipping the data. --- dlls/ntdll/unix/socket.c | 57 +++++++++++++++++++++++++++++++++++----- dlls/ws2_32/tests/sock.c | 4 +-- 2 files changed, 52 insertions(+), 9 deletions(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index ac351a17a70..c2f8629ad1e 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -849,6 +849,19 @@ static BOOL is_icmp_over_dgram( int fd ) #endif }
+static BOOL is_using_udp( int fd ) +{ +#ifdef linux + socklen_t len; + int val; + + len = sizeof(val); + return !getsockopt( fd, SOL_SOCKET, SO_PROTOCOL, (char *)&val, &len ) && val == IPPROTO_UDP; +#else + return FALSE; +#endif +} + static NTSTATUS sock_recv( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc_user, IO_STATUS_BLOCK *io, int fd, struct async_recv_ioctl *async, int force_async ) { @@ -979,6 +992,18 @@ NTSTATUS sock_read( HANDLE handle, int fd, HANDLE event, PIO_APC_ROUTINE apc, return sock_recv( handle, event, apc, apc_user, io, fd, async, 1 ); }
+static BOOL is_port0( const union unix_sockaddr *uaddr) +{ + switch (uaddr->addr.sa_family) + { + case AF_INET: + return uaddr->in.sin_port == 0; + case AF_INET6: + return uaddr->in6.sin6_port == 0; + default: + return FALSE; + } +}
static NTSTATUS try_send( int fd, struct async_send_ioctl *async ) { @@ -986,6 +1011,7 @@ static NTSTATUS try_send( int fd, struct async_send_ioctl *async ) struct msghdr hdr; ssize_t ret;
+ memset( &hdr, 0, sizeof(hdr) ); if (async->addr) { @@ -1018,17 +1044,34 @@ static NTSTATUS try_send( int fd, struct async_send_ioctl *async ) hdr.msg_iov = async->iov + async->iov_cursor; hdr.msg_iovlen = async->count - async->iov_cursor;
- while ((ret = sendmsg( fd, &hdr, async->unix_flags )) == -1) + if (is_using_udp(fd) && is_port0(&unix_addr)) { - if (errno == EISCONN) + /* Some Windows applications(Spellforce 3 is known to) send to port 0. + * This causes 'sendmsg' to throw a EINVAL error on Windows this does nothing but consume the data. + * This workaround says we successfully sent data even though we didnt send anything. + * Matching the Windows behaviour, making the program work(in theory). + */ + ret = 0; + for(ssize_t i = 0; i < hdr.msg_iovlen; i++) { - hdr.msg_name = NULL; - hdr.msg_namelen = 0; + ret += hdr.msg_iov[i].iov_len; } - else if (errno != EINTR) + WARN("Attempting to send to port 0, skipping over %zd bytes\n", ret); + } + else + { + while ((ret = sendmsg( fd, &hdr, async->unix_flags )) == -1) { - if (errno != EWOULDBLOCK) WARN( "sendmsg: %s\n", strerror( errno ) ); - return sock_errno_to_status( errno ); + if (errno == EISCONN) + { + hdr.msg_name = NULL; + hdr.msg_namelen = 0; + } + else if (errno != EINTR) + { + if (errno != EWOULDBLOCK) WARN( "sendmsg: %s\n", strerror( errno ) ); + return sock_errno_to_status( errno ); + } } }
diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index a388a35d1e1..ca0527ae438 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -13678,8 +13678,8 @@ static void test_WSASendto_port0(void) ok(s != INVALID_SOCKET, "failed to create socket, error %u\n", WSAGetLastError());
ret = WSASendTo(s, &data_buf, 1, &bytes_sent, 0, (struct sockaddr *)&addr, sizeof(addr), NULL, NULL); - todo_wine ok(!ret, "got error %u\n", WSAGetLastError()); - todo_wine ok(bytes_sent == sizeof(buf), "Failed to send full data(%lu) only sent(%lu)\n", sizeof(buf), bytes_sent); + ok(!ret, "got error %u\n", WSAGetLastError()); + ok(bytes_sent == sizeof(buf), "Failed to send full data(%lu) only sent(%lu)\n", (unsigned long) sizeof(buf), (unsigned long) bytes_sent); closesocket(s); }