According to MSDN [1], the `TCP_NODELAY` parameter should be of type `BOOL` which is 4 bytes. Due to a bug [2] in rustc passing a byte instead of an int, any program written in rust that tries to set that option on a socket will fail with a "Invalid parameter supplied" error.
Turns out that setsockopt on linux does not want optlen to be less than 4 bytes [3].
Windows' behavior is the following: - For optlen <= 0, return SOCKET_ERROR and set last error to WSAEFAULT - For optlen > 0, ignore the optlen value and set the TCP_NODELAY value to one if the first byte of the given optvalue is not 0.
This will fix any rust program using the hyper library to do HTTP requests.
[1]: https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-tcp-socket-op... [2]: https://github.com/rust-lang/rust/blob/44593aeb1387b1be355aeaf0040d5927bd80f... [3]: https://github.com/torvalds/linux/blob/d58071a8a76d779eedab38033ae4c821c3029...
Signed-off-by: Bastien Orivel eijebong@bananium.fr --- dlls/ws2_32/socket.c | 10 +++++-- dlls/ws2_32/tests/sock.c | 63 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-)
diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c index a75aaee68be..4b3ca3c2507 100644 --- a/dlls/ws2_32/socket.c +++ b/dlls/ws2_32/socket.c @@ -2903,8 +2903,14 @@ int WINAPI setsockopt( SOCKET s, int level, int optname, const char *optval, int switch(optname) { case TCP_NODELAY: - return server_setsockopt( s, IOCTL_AFD_WINE_SET_TCP_NODELAY, optval, optlen ); - + { + INT nodelay = *optval; + if (optlen <= 0) { + SetLastError(WSAEFAULT); + return SOCKET_ERROR; + } + return server_setsockopt( s, IOCTL_AFD_WINE_SET_TCP_NODELAY, (char*)&nodelay, sizeof(nodelay) ); + } default: FIXME("Unknown IPPROTO_TCP optname 0x%08x\n", optname); SetLastError(WSAENOPROTOOPT); diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index b38357954b7..d208c17e43f 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -1259,6 +1259,69 @@ static void test_set_getsockopt(void) ok(err == SOCKET_ERROR && WSAGetLastError() == WSAEFAULT, "got %d with %d (expected SOCKET_ERROR with WSAEFAULT)\n", err, WSAGetLastError());
+ /* TCP_NODELAY: optlen doesn't matter on windows, it should work with any positive value */ + size = sizeof(value); + + value = 1; + err = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, 1); + ok (!err, "setsockopt TCP_NODELAY failed with optlen == 1\n"); + value = 0xff; + err = getsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, &size); + ok(!err, "getsockopt TCP_NODELAY failed\n"); + ok(value == 1, "TCP_NODELAY should be 1\n"); + value = 0; + err = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, sizeof(value)); + ok(!err, "Failed to reset TCP_NODELAY to 0\n"); + + value = 1; + err = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, 4); + ok (!err, "setsockopt TCP_NODELAY failed with optlen == 4\n"); + value = 0xff; + err = getsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, &size); + ok(!err, "getsockopt TCP_NODELAY failed\n"); + ok(value == 1, "TCP_NODELAY should be 1\n"); + value = 0; + err = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, sizeof(value)); + ok(!err, "Failed to reset TCP_NODELAY to 0\n"); + + value = 1; + err = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, 42); + ok (!err, "setsockopt TCP_NODELAY failed with optlen == 42\n"); + value = 0xff; + err = getsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, &size); + ok(!err, "getsockopt TCP_NODELAY failed\n"); + ok(value == 1, "TCP_NODELAY should be 1\n"); + value = 0; + err = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, sizeof(value)); + ok(!err, "Failed to reset TCP_NODELAY to 0\n"); + + value = 1; + err = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, 0); + ok(err == SOCKET_ERROR && WSAGetLastError() == WSAEFAULT, + "got %d with %d (expected SOCKET_ERROR with WSAEFAULT)\n", err, WSAGetLastError()); + value = 0xff; + err = getsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, &size); + ok(!err, "getsockopt TCP_NODELAY failed\n"); + ok(!value, "TCP_NODELAY should be 0\n"); + + value = 1; + err = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, -1); + /* On win 10 pro, this sets the error to WSAENOBUFS instead of WSAEFAULT */ + ok(err == SOCKET_ERROR && (WSAGetLastError() == WSAEFAULT || WSAGetLastError() == WSAENOBUFS), + "got %d with %d (expected SOCKET_ERROR with either WSAEFAULT or WSAENOBUFS)\n", err, WSAGetLastError()); + value = 0xff; + err = getsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, &size); + ok(!err, "getsockopt TCP_NODELAY failed\n"); + ok(!value, "TCP_NODELAY should be 0\n"); + + value = 0x100; + err = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, 4); + ok (!err, "setsockopt TCP_NODELAY failed with optlen == 4 and optvalue = 0x100\n"); + value = 0xff; + err = getsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&value, &size); + ok(!err, "getsockopt TCP_NODELAY failed\n"); + ok(!value, "TCP_NODELAY should be 0\n"); + /* Test for erroneously passing a value instead of a pointer as optval */ size = sizeof(char); err = setsockopt(s, SOL_SOCKET, SO_DONTROUTE, (char *)1, size);