Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=50955
The problem which this patchset aims to solve is (sometimes) huge timeout on TCP listening port availability on Linux after the listening socket was closed one or another way. By default, the listening port will also be blocked for the whole time accepted socket waits through lengthy timeouts (sometimes; most surely when the listening process with an active connection was force killed, but not limited to this condition).
BSD SO_REUSEADDR socket option is aimed mainly to avoid that extra wait on binding already closed listening socket address. From [1]: "Indicates that the rules used in validating addresses supplied in a bind(2) call should allow reuse of local addresses. For AF_INET sockets this means that a socket may bind, except when there is an active listening socket bound to the address.".
Unix SO_REUSEADDR does not really allow reusing address in the Winsock sense. It will just allow to ditch the timeout (which is always the case on Windows without any specific options). Unfortunately it is not the only effect of the option. It still won't allow listening on the address simultaneously (unlike Winsock SO_REUSEADDR which allows simultaneous listening), or binding to an address which is being listened. But it will allow to bind different sockets for the same address which is not the Winsock behaviour when Winsock SO_REUSEADDR is set.
So the patchset enables SO_REUSEADDR on every TCP socket and introduces the bound address tracking which will allow to return an error from bind() when needed.
Not related to this patchset, but Winsock SO_REUSEADDR is somewhat closer to BSD SO_REUSEPORT, although is different in a way that _REUSEPORT will load balance connections between listeners while with Winsock _REUSEADDR the connections will always go to the first listener.
I hope that the bound addresses tracking introduced in these patches may be reused in the future. E. g., maybe it might be helpful on the way of implementing the todos introduced by my extended tests (those todos are not related to this patchset and exist both with and without it).
1. https://man7.org/linux/man-pages/man7/socket.7.html
-- v5: ws2_32/tests: Also test bind to any together with loopback in a different order.
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ws2_32/tests/sock.c | 102 +++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 47 deletions(-)
diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index adf16012951..879e4f7ddf7 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -2016,76 +2016,84 @@ static void test_set_getsockopt(void) static void test_so_reuseaddr(void) { struct sockaddr_in saddr; - SOCKET s1,s2; - unsigned int rc,reuse; + unsigned int rc, reuse; + SOCKET s1, s2, s3, s4; int size; - DWORD err;
saddr.sin_family = AF_INET; saddr.sin_port = htons(SERVERPORT+1); saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
+ /* Test with SO_REUSEADDR on second socket only. */ + s1=socket(AF_INET, SOCK_STREAM, 0); + ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + rc = bind(s1, (struct sockaddr*)&saddr, sizeof(saddr)); + ok(!rc, "got error %d.\n", WSAGetLastError()); + + s2 = socket(AF_INET, SOCK_STREAM, 0); + ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + reuse = 1; + rc = setsockopt(s2, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); + ok(!rc, "got error %d.\n", WSAGetLastError()); + + rc = bind(s2, (struct sockaddr*)&saddr, sizeof(saddr)); + ok(rc == SOCKET_ERROR, "got rc %d.\n", rc); + ok(WSAGetLastError() == WSAEACCES, "got error %d.\n", WSAGetLastError()); + + closesocket(s1); + closesocket(s2); + + /* Test with SO_REUSEADDR on both sockets. */ s1=socket(AF_INET, SOCK_STREAM, 0); - ok(s1!=INVALID_SOCKET, "socket() failed error: %d\n", WSAGetLastError()); + ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + reuse = 1; + rc = setsockopt(s1, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); + ok(!rc, "got error %d.\n", WSAGetLastError()); + rc = bind(s1, (struct sockaddr*)&saddr, sizeof(saddr)); - ok(rc!=SOCKET_ERROR, "bind(s1) failed error: %d\n", WSAGetLastError()); + ok(!rc, "got error %d.\n", WSAGetLastError());
- s2=socket(AF_INET, SOCK_STREAM, 0); - ok(s2!=INVALID_SOCKET, "socket() failed error: %d\n", WSAGetLastError()); + s2 = socket(AF_INET, SOCK_STREAM, 0); + ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
reuse=0x1234; size=sizeof(reuse); - rc=getsockopt(s2, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, &size ); - ok(rc==0 && reuse==0,"wrong result in getsockopt(SO_REUSEADDR): rc=%d reuse=%d\n",rc,reuse); + rc=getsockopt(s2, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, &size); + ok(!rc && !reuse,"got rc %d, reuse %d.\n", rc, reuse);
rc = bind(s2, (struct sockaddr*)&saddr, sizeof(saddr)); - ok(rc==SOCKET_ERROR, "bind() succeeded\n"); + ok(rc == SOCKET_ERROR, "got rc %d, error %d.\n", rc, WSAGetLastError());
reuse = 1; rc = setsockopt(s2, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); - ok(rc==0, "setsockopt() failed error: %d\n", WSAGetLastError()); + ok(!rc, "got error %d.\n", WSAGetLastError());
- /* On Win2k3 and above, all SO_REUSEADDR seems to do is to allow binding to - * a port immediately after closing another socket on that port, so - * basically following the BSD socket semantics here. */ rc = bind(s2, (struct sockaddr*)&saddr, sizeof(saddr)); - if(rc==0) - { - int s3=socket(AF_INET, SOCK_STREAM, 0), s4; - - /* If we could bind again in the same port this is Windows version <= XP. - * Lets test if we can really connect to one of them. */ - set_blocking(s1, FALSE); - set_blocking(s2, FALSE); - rc = listen(s1, 1); - ok(!rc, "listen() failed with error: %d\n", WSAGetLastError()); - rc = listen(s2, 1); - ok(!rc, "listen() failed with error: %d\n", WSAGetLastError()); - rc = connect(s3, (struct sockaddr*)&saddr, sizeof(saddr)); - ok(!rc, "connecting to accepting socket failed %d\n", WSAGetLastError()); - - /* the delivery of the connection is random so we need to try on both sockets */ - size = sizeof(saddr); - s4 = accept(s1, (struct sockaddr*)&saddr, &size); - if(s4 == INVALID_SOCKET) - s4 = accept(s2, (struct sockaddr*)&saddr, &size); - ok(s4 != INVALID_SOCKET, "none of the listening sockets could get the connection\n"); + ok(!rc, "got error %d.\n", WSAGetLastError());
- closesocket(s1); - closesocket(s3); - closesocket(s4); - } - else - { - err = WSAGetLastError(); - ok(err==WSAEACCES, "expected 10013, got %ld\n", err); + s3 = socket(AF_INET, SOCK_STREAM, 0); + ok(s3 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
- closesocket(s1); - rc = bind(s2, (struct sockaddr*)&saddr, sizeof(saddr)); - ok(rc==0, "bind() failed error: %d\n", WSAGetLastError()); - } + /* Test if we can really connect to one of them. */ + rc = listen(s1, 1); + ok(!rc, "got error %d.\n", WSAGetLastError()); + rc = listen(s2, 1); + todo_wine ok(!rc, "got error %d.\n", WSAGetLastError()); + rc = connect(s3, (struct sockaddr*)&saddr, sizeof(saddr)); + ok(!rc, "got error %d.\n", WSAGetLastError()); + + /* The connection is delivered to the first socket. */ + size = sizeof(saddr); + s4 = accept(s1, (struct sockaddr*)&saddr, &size); + ok(s4 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
+ closesocket(s1); closesocket(s2); + closesocket(s3); + closesocket(s4); }
#define IP_PKTINFO_LEN (sizeof(WSACMSGHDR) + WSA_CMSG_ALIGN(sizeof(struct in_pktinfo)))
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ws2_32/tests/sock.c | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+)
diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index 879e4f7ddf7..cc74e2ab971 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -2088,12 +2088,68 @@ static void test_so_reuseaddr(void) /* The connection is delivered to the first socket. */ size = sizeof(saddr); s4 = accept(s1, (struct sockaddr*)&saddr, &size); + saddr.sin_port = htons(SERVERPORT + 1); ok(s4 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
+ closesocket(s1); closesocket(s2); closesocket(s3); closesocket(s4); + + /* Test binding and listening on any addr together with loopback. */ + s1 = socket(AF_INET, SOCK_STREAM, 0); + ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + rc = bind(s1, (struct sockaddr *)&saddr, sizeof(saddr)); + ok(!rc, "got error %d.\n", WSAGetLastError()); + + rc = listen(s1, 1); + ok(!rc, "got error %d.\n", WSAGetLastError()); + + s2 = socket(AF_INET, SOCK_STREAM, 0); + ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); + rc = bind(s2, (struct sockaddr *)&saddr, sizeof(saddr)); + todo_wine ok(!rc, "got error %d.\n", WSAGetLastError()); + + rc = listen(s2, 1); + todo_wine ok(!rc, "got error %d.\n", WSAGetLastError()); + + s3 = socket(AF_INET, SOCK_STREAM, 0); + ok(s3 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + rc = connect(s3, (struct sockaddr *)&saddr, sizeof(saddr)); + ok(!rc, "got error %d.\n", WSAGetLastError()); + + size = sizeof(saddr); + s4 = accept(s2, (struct sockaddr *)&saddr, &size); + saddr.sin_port = htons(SERVERPORT + 1); + todo_wine ok(s4 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + closesocket(s1); + closesocket(s2); + closesocket(s3); + closesocket(s4); + + /* Test binding to INADDR_ANY on two sockets. */ + s1 = socket(AF_INET, SOCK_STREAM, 0); + ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + rc = bind(s1, (struct sockaddr *)&saddr, sizeof(saddr)); + ok(!rc, "got error %d.\n", WSAGetLastError()); + + s2 = socket(AF_INET, SOCK_STREAM, 0); + ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + rc = bind(s2, (struct sockaddr*)&saddr, sizeof(saddr)); + ok(rc == SOCKET_ERROR && WSAGetLastError() == WSAEADDRINUSE, "got rc %d, error %d.\n", rc, WSAGetLastError()); + + closesocket(s1); + closesocket(s2); }
#define IP_PKTINFO_LEN (sizeof(WSACMSGHDR) + WSA_CMSG_ALIGN(sizeof(struct in_pktinfo)))
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ws2_32/tests/sock.c | 219 ++++++++++++++++++++++----------------- 1 file changed, 122 insertions(+), 97 deletions(-)
diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index cc74e2ab971..943ee8539ea 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -2015,141 +2015,166 @@ static void test_set_getsockopt(void)
static void test_so_reuseaddr(void) { - struct sockaddr_in saddr; + static struct sockaddr_in6 saddr_in6_any, saddr_in6_loopback; + static struct sockaddr_in saddr_in_any, saddr_in_loopback; + + static const struct + { + int domain; + struct sockaddr *addr_any; + struct sockaddr *addr_loopback; + socklen_t addrlen; + } + tests[] = + { + { AF_INET, (struct sockaddr *)&saddr_in_any, (struct sockaddr *)&saddr_in_loopback, sizeof(saddr_in_any) }, + { AF_INET6, (struct sockaddr *)&saddr_in6_any, (struct sockaddr *)&saddr_in6_loopback, sizeof(saddr_in6_any) }, + }; unsigned int rc, reuse; + struct sockaddr saddr; SOCKET s1, s2, s3, s4; + unsigned int i; int size;
- saddr.sin_family = AF_INET; - saddr.sin_port = htons(SERVERPORT+1); - saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); + saddr_in_any.sin_family = AF_INET; + saddr_in_any.sin_port = htons(SERVERPORT + 1); + saddr_in_any.sin_addr.s_addr = htonl(INADDR_ANY); + saddr_in_loopback = saddr_in_any; + saddr_in_loopback.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + saddr_in6_any.sin6_family = AF_INET6; + saddr_in6_any.sin6_port = htons(SERVERPORT + 1); + memset( &saddr_in6_any.sin6_addr, 0, sizeof(saddr_in6_any.sin6_addr) ); + saddr_in6_loopback = saddr_in6_any; + inet_pton(AF_INET6, "::1", &saddr_in6_loopback.sin6_addr); + + for (i = 0; i < ARRAY_SIZE(tests); ++i) + { + winetest_push_context("test %u", i);
- /* Test with SO_REUSEADDR on second socket only. */ - s1=socket(AF_INET, SOCK_STREAM, 0); - ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + /* Test with SO_REUSEADDR on second socket only. */ + s1=socket(tests[i].domain, SOCK_STREAM, 0); + ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
- rc = bind(s1, (struct sockaddr*)&saddr, sizeof(saddr)); - ok(!rc, "got error %d.\n", WSAGetLastError()); + rc = bind(s1, tests[i].addr_loopback, tests[i].addrlen); + ok(!rc, "got error %d.\n", WSAGetLastError());
- s2 = socket(AF_INET, SOCK_STREAM, 0); - ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + s2 = socket(tests[i].domain, SOCK_STREAM, 0); + ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
- reuse = 1; - rc = setsockopt(s2, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); - ok(!rc, "got error %d.\n", WSAGetLastError()); + reuse = 1; + rc = setsockopt(s2, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); + ok(!rc, "got error %d.\n", WSAGetLastError());
- rc = bind(s2, (struct sockaddr*)&saddr, sizeof(saddr)); - ok(rc == SOCKET_ERROR, "got rc %d.\n", rc); - ok(WSAGetLastError() == WSAEACCES, "got error %d.\n", WSAGetLastError()); + rc = bind(s2, tests[i].addr_loopback, tests[i].addrlen); + ok(rc == SOCKET_ERROR, "got rc %d.\n", rc); + ok(WSAGetLastError() == WSAEACCES, "got error %d.\n", WSAGetLastError());
- closesocket(s1); - closesocket(s2); + closesocket(s1); + closesocket(s2);
- /* Test with SO_REUSEADDR on both sockets. */ - s1=socket(AF_INET, SOCK_STREAM, 0); - ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + /* Test with SO_REUSEADDR on both sockets. */ + s1 = socket(tests[i].domain, SOCK_STREAM, 0); + ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
- reuse = 1; - rc = setsockopt(s1, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); - ok(!rc, "got error %d.\n", WSAGetLastError()); + reuse = 1; + rc = setsockopt(s1, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); + ok(!rc, "got error %d.\n", WSAGetLastError());
- rc = bind(s1, (struct sockaddr*)&saddr, sizeof(saddr)); - ok(!rc, "got error %d.\n", WSAGetLastError()); + rc = bind(s1, tests[i].addr_loopback, tests[i].addrlen); + ok(!rc, "got error %d.\n", WSAGetLastError());
- s2 = socket(AF_INET, SOCK_STREAM, 0); - ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + s2 = socket(tests[i].domain, SOCK_STREAM, 0); + ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
- reuse=0x1234; - size=sizeof(reuse); - rc=getsockopt(s2, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, &size); - ok(!rc && !reuse,"got rc %d, reuse %d.\n", rc, reuse); + reuse = 0x1234; + size = sizeof(reuse); + rc = getsockopt(s2, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, &size); + ok(!rc && !reuse,"got rc %d, reuse %d.\n", rc, reuse);
- rc = bind(s2, (struct sockaddr*)&saddr, sizeof(saddr)); - ok(rc == SOCKET_ERROR, "got rc %d, error %d.\n", rc, WSAGetLastError()); + rc = bind(s2, tests[i].addr_loopback, tests[i].addrlen); + ok(rc == SOCKET_ERROR, "got rc %d, error %d.\n", rc, WSAGetLastError());
- reuse = 1; - rc = setsockopt(s2, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); - ok(!rc, "got error %d.\n", WSAGetLastError()); + reuse = 1; + rc = setsockopt(s2, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); + ok(!rc, "got error %d.\n", WSAGetLastError());
- rc = bind(s2, (struct sockaddr*)&saddr, sizeof(saddr)); - ok(!rc, "got error %d.\n", WSAGetLastError()); + rc = bind(s2, tests[i].addr_loopback, tests[i].addrlen); + ok(!rc, "got error %d.\n", WSAGetLastError());
- s3 = socket(AF_INET, SOCK_STREAM, 0); - ok(s3 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + s3 = socket(tests[i].domain, SOCK_STREAM, 0); + ok(s3 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
- /* Test if we can really connect to one of them. */ - rc = listen(s1, 1); - ok(!rc, "got error %d.\n", WSAGetLastError()); - rc = listen(s2, 1); - todo_wine ok(!rc, "got error %d.\n", WSAGetLastError()); - rc = connect(s3, (struct sockaddr*)&saddr, sizeof(saddr)); - ok(!rc, "got error %d.\n", WSAGetLastError()); + /* Test if we can really connect to one of them. */ + rc = listen(s1, 1); + ok(!rc, "got error %d.\n", WSAGetLastError()); + rc = listen(s2, 1); + todo_wine ok(!rc, "got error %d.\n", WSAGetLastError()); + rc = connect(s3, tests[i].addr_loopback, tests[i].addrlen); + ok(!rc, "got error %d.\n", WSAGetLastError());
- /* The connection is delivered to the first socket. */ - size = sizeof(saddr); - s4 = accept(s1, (struct sockaddr*)&saddr, &size); - saddr.sin_port = htons(SERVERPORT + 1); - ok(s4 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + /* The connection is delivered to the first socket. */ + size = tests[i].addrlen; + s4 = accept(s1, &saddr, &size); + ok(s4 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
+ closesocket(s1); + closesocket(s2); + closesocket(s3); + closesocket(s4);
- closesocket(s1); - closesocket(s2); - closesocket(s3); - closesocket(s4); + /* Test binding and listening on any addr together with loopback. */ + s1 = socket(tests[i].domain, SOCK_STREAM, 0); + ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
- /* Test binding and listening on any addr together with loopback. */ - s1 = socket(AF_INET, SOCK_STREAM, 0); - ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + rc = bind(s1, tests[i].addr_any, tests[i].addrlen); + ok(!rc, "got error %d.\n", WSAGetLastError());
- saddr.sin_addr.s_addr = htonl(INADDR_ANY); - rc = bind(s1, (struct sockaddr *)&saddr, sizeof(saddr)); - ok(!rc, "got error %d.\n", WSAGetLastError()); + rc = listen(s1, 1); + ok(!rc, "got error %d.\n", WSAGetLastError());
- rc = listen(s1, 1); - ok(!rc, "got error %d.\n", WSAGetLastError()); + s2 = socket(tests[i].domain, SOCK_STREAM, 0); + ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
- s2 = socket(AF_INET, SOCK_STREAM, 0); - ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + rc = bind(s2, tests[i].addr_loopback, tests[i].addrlen); + todo_wine ok(!rc, "got error %d.\n", WSAGetLastError());
- saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); - rc = bind(s2, (struct sockaddr *)&saddr, sizeof(saddr)); - todo_wine ok(!rc, "got error %d.\n", WSAGetLastError()); + rc = listen(s2, 1); + todo_wine ok(!rc, "got error %d.\n", WSAGetLastError());
- rc = listen(s2, 1); - todo_wine ok(!rc, "got error %d.\n", WSAGetLastError()); + s3 = socket(tests[i].domain, SOCK_STREAM, 0); + ok(s3 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
- s3 = socket(AF_INET, SOCK_STREAM, 0); - ok(s3 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + rc = connect(s3, tests[i].addr_loopback, tests[i].addrlen); + ok(!rc, "got error %d.\n", WSAGetLastError());
- rc = connect(s3, (struct sockaddr *)&saddr, sizeof(saddr)); - ok(!rc, "got error %d.\n", WSAGetLastError()); + size = tests[i].addrlen; + s4 = accept(s2, &saddr, &size); + todo_wine ok(s4 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
- size = sizeof(saddr); - s4 = accept(s2, (struct sockaddr *)&saddr, &size); - saddr.sin_port = htons(SERVERPORT + 1); - todo_wine ok(s4 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + closesocket(s1); + closesocket(s2); + closesocket(s3); + closesocket(s4);
- closesocket(s1); - closesocket(s2); - closesocket(s3); - closesocket(s4); + /* Test binding to INADDR_ANY on two sockets. */ + s1 = socket(tests[i].domain, SOCK_STREAM, 0); + ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
- /* Test binding to INADDR_ANY on two sockets. */ - s1 = socket(AF_INET, SOCK_STREAM, 0); - ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + rc = bind(s1, tests[i].addr_any, tests[i].addrlen); + ok(!rc, "got error %d.\n", WSAGetLastError());
- saddr.sin_addr.s_addr = htonl(INADDR_ANY); - rc = bind(s1, (struct sockaddr *)&saddr, sizeof(saddr)); - ok(!rc, "got error %d.\n", WSAGetLastError()); + s2 = socket(tests[i].domain, SOCK_STREAM, 0); + ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
- s2 = socket(AF_INET, SOCK_STREAM, 0); - ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + rc = bind(s2, tests[i].addr_any, tests[i].addrlen); + ok(rc == SOCKET_ERROR && WSAGetLastError() == WSAEADDRINUSE, "got rc %d, error %d.\n", rc, WSAGetLastError());
- rc = bind(s2, (struct sockaddr*)&saddr, sizeof(saddr)); - ok(rc == SOCKET_ERROR && WSAGetLastError() == WSAEADDRINUSE, "got rc %d, error %d.\n", rc, WSAGetLastError()); + closesocket(s1); + closesocket(s2);
- closesocket(s1); - closesocket(s2); + winetest_pop_context(); + } }
#define IP_PKTINFO_LEN (sizeof(WSACMSGHDR) + WSA_CMSG_ALIGN(sizeof(struct in_pktinfo)))
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ntdll/unix/socket.c | 21 -------------------- server/sock.c | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 21 deletions(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index b502d3a03cb..7dbf03365bb 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -1977,27 +1977,6 @@ NTSTATUS sock_ioctl( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc case IOCTL_AFD_WINE_SET_SO_OOBINLINE: return do_setsockopt( handle, io, SOL_SOCKET, SO_OOBINLINE, in_buffer, in_size );
- case IOCTL_AFD_WINE_GET_SO_REUSEADDR: - return do_getsockopt( handle, io, SOL_SOCKET, SO_REUSEADDR, out_buffer, out_size ); - - /* BSD socket SO_REUSEADDR is not 100% compatible to winsock semantics; - * however, using it the BSD way fixes bug 8513 and seems to be what - * most programmers assume, anyway */ - case IOCTL_AFD_WINE_SET_SO_REUSEADDR: - { - int ret; - - if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL ))) - return status; - - ret = setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, in_buffer, in_size ); -#ifdef __APPLE__ - if (!ret) ret = setsockopt( fd, SOL_SOCKET, SO_REUSEPORT, in_buffer, in_size ); -#endif - status = ret ? sock_errno_to_status( errno ) : STATUS_SUCCESS; - break; - } - case IOCTL_AFD_WINE_SET_IP_ADD_MEMBERSHIP: return do_setsockopt( handle, io, IPPROTO_IP, IP_ADD_MEMBERSHIP, in_buffer, in_size );
diff --git a/server/sock.c b/server/sock.c index 4e57d6774a6..87a536bb363 100644 --- a/server/sock.c +++ b/server/sock.c @@ -2905,6 +2905,29 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) return; }
+ /* BSD socket SO_REUSEADDR is not 100% compatible to winsock semantics; + * however, using it the BSD way fixes bug 8513 and seems to be what + * most programmers assume, anyway */ + case IOCTL_AFD_WINE_SET_SO_REUSEADDR: + { + int reuse, ret; + + if (get_req_data_size() < sizeof(reuse)) + { + set_error( STATUS_BUFFER_TOO_SMALL ); + return; + } + + reuse = *(int *)get_req_data(); + ret = setsockopt( unix_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse) ); +#ifdef __APPLE__ + if (!ret) ret = setsockopt( unix_fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse) ); +#endif + if (ret) + set_error( sock_get_ntstatus( errno ) ); + return; + } + case IOCTL_AFD_WINE_GET_SO_SNDBUF: { int sndbuf = sock->sndbuf; @@ -2992,6 +3015,24 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) return; }
+ case IOCTL_AFD_WINE_GET_SO_REUSEADDR: + { + int reuse; + socklen_t len = sizeof(reuse); + + if (!get_reply_max_size()) + { + set_error( STATUS_BUFFER_TOO_SMALL ); + return; + } + + if (!getsockopt( unix_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, &len )) + set_reply_data( &reuse, min( sizeof(reuse), get_reply_max_size() )); + else + set_error( sock_get_ntstatus( errno ) ); + return; + } + case IOCTL_AFD_POLL: { if (get_reply_max_size() < get_req_data_size())
From: Paul Gofman pgofman@codeweavers.com
--- server/sock.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-)
diff --git a/server/sock.c b/server/sock.c index 87a536bb363..7c5f0185fd5 100644 --- a/server/sock.c +++ b/server/sock.c @@ -233,6 +233,7 @@ struct sock unsigned int nonblocking : 1; /* is the socket nonblocking? */ unsigned int bound : 1; /* is the socket bound? */ unsigned int reset : 1; /* did we get a TCP reset? */ + unsigned int reuseaddr : 1; /* winsock SO_REUSEADDR option value */ };
static void sock_dump( struct object *obj, int verbose ); @@ -1545,6 +1546,7 @@ static struct sock *create_socket(void) sock->nonblocking = 0; sock->bound = 0; sock->reset = 0; + sock->reuseaddr = 0; sock->rcvbuf = 0; sock->sndbuf = 0; sock->rcvtimeo = 0; @@ -2720,14 +2722,8 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async )
if (bind( unix_fd, &bind_addr.addr, unix_len ) < 0) { - if (errno == EADDRINUSE) - { - int reuse; - socklen_t len = sizeof(reuse); - - if (!getsockopt( unix_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, &len ) && reuse) - errno = EACCES; - } + if (errno == EADDRINUSE && sock->reuseaddr) + errno = EACCES;
set_error( sock_get_ntstatus( errno ) ); return; @@ -2925,6 +2921,8 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) #endif if (ret) set_error( sock_get_ntstatus( errno ) ); + else + sock->reuseaddr = !!reuse; return; }
@@ -3018,7 +3016,6 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) case IOCTL_AFD_WINE_GET_SO_REUSEADDR: { int reuse; - socklen_t len = sizeof(reuse);
if (!get_reply_max_size()) { @@ -3026,10 +3023,8 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) return; }
- if (!getsockopt( unix_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, &len )) - set_reply_data( &reuse, min( sizeof(reuse), get_reply_max_size() )); - else - set_error( sock_get_ntstatus( errno ) ); + reuse = sock->reuseaddr; + set_reply_data( &reuse, min( sizeof(reuse), get_reply_max_size() )); return; }
From: Paul Gofman pgofman@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=50955 --- dlls/httpapi/tests/httpapi.c | 6 +- server/sock.c | 211 ++++++++++++++++++++++++++++++++--- 2 files changed, 197 insertions(+), 20 deletions(-)
diff --git a/dlls/httpapi/tests/httpapi.c b/dlls/httpapi/tests/httpapi.c index 1d00aa22c50..ec1be5bc82a 100644 --- a/dlls/httpapi/tests/httpapi.c +++ b/dlls/httpapi/tests/httpapi.c @@ -1397,7 +1397,7 @@ static void test_v2_bound_port(void) ok(!ret, "Failed to bind to port\n"); swprintf(url, ARRAY_SIZE(url), L"http://localhost:%u/", port); ret = pHttpAddUrlToUrlGroup(group, url, 0xdeadbeef, 0); - ok(ret == ERROR_SHARING_VIOLATION, "Unexpected failure adding %s, error %u.\n", debugstr_w(url), ret); + todo_wine ok(ret == ERROR_SHARING_VIOLATION, "Unexpected failure adding %s, error %u.\n", debugstr_w(url), ret); shutdown(s2, SD_BOTH); closesocket(s2);
@@ -1407,8 +1407,8 @@ static void test_v2_bound_port(void)
s = socket(AF_INET, SOCK_STREAM, 0); ret = connect(s, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); - ok(ret, "Connecting to socket succeeded, %lu.\n", GetLastError()); - ok(GetLastError() == WSAECONNREFUSED, "Unexpected error connecting to socket, %lu.\n", GetLastError()); + todo_wine ok(ret, "Connecting to socket succeeded, %lu.\n", GetLastError()); + todo_wine ok(GetLastError() == WSAECONNREFUSED, "Unexpected error connecting to socket, %lu.\n", GetLastError());
closesocket(s); ret = pHttpCloseRequestQueue(dummy_queue); diff --git a/server/sock.c b/server/sock.c index 7c5f0185fd5..2156a1a12c2 100644 --- a/server/sock.c +++ b/server/sock.c @@ -91,6 +91,7 @@ #include "wsipx.h" #include "af_irda.h" #include "wine/afd.h" +#include "wine/rbtree.h"
#include "process.h" #include "file.h" @@ -114,6 +115,19 @@ union win_sockaddr SOCKADDR_IRDA irda; };
+union unix_sockaddr +{ + struct sockaddr addr; + struct sockaddr_in in; + struct sockaddr_in6 in6; +#ifdef HAS_IPX + struct sockaddr_ipx ipx; +#endif +#ifdef HAS_IRDA + struct sockaddr_irda irda; +#endif +}; + static struct list poll_list = LIST_INIT( poll_list );
struct poll_req @@ -169,6 +183,13 @@ enum connection_state SOCK_CONNECTIONLESS, };
+struct bound_addr +{ + struct rb_entry entry; + union unix_sockaddr addr; + int reuse_count; +}; + #define MAX_ICMP_HISTORY_LENGTH 8
struct sock @@ -224,6 +245,7 @@ struct sock unsigned short icmp_seq; } icmp_fixup_data[MAX_ICMP_HISTORY_LENGTH]; /* Sent ICMP packets history used to fixup reply id. */ + struct bound_addr *bound_addr[2]; /* Links to the entries in bound addresses tree. */ unsigned int icmp_fixup_data_len; /* Sent ICMP packets history length. */ unsigned int rd_shutdown : 1; /* is the read end shut down? */ unsigned int wr_shutdown : 1; /* is the write end shut down? */ @@ -236,6 +258,137 @@ struct sock unsigned int reuseaddr : 1; /* winsock SO_REUSEADDR option value */ };
+static int is_tcp_socket( struct sock *sock ) +{ + return sock->type == WS_SOCK_STREAM && (sock->family == WS_AF_INET || sock->family == WS_AF_INET6); +} + +static int addr_compare( const void *key, const struct wine_rb_entry *entry ) +{ + const struct bound_addr *bound_addr = RB_ENTRY_VALUE(entry, struct bound_addr, entry); + const union unix_sockaddr *addr = key; + + if (addr->addr.sa_family != bound_addr->addr.addr.sa_family) + return addr->addr.sa_family < bound_addr->addr.addr.sa_family ? -1 : 1; + + if (addr->addr.sa_family == AF_INET) + { + if (addr->in.sin_port != bound_addr->addr.in.sin_port) + return addr->in.sin_port < bound_addr->addr.in.sin_port ? -1 : 1; + if (addr->in.sin_addr.s_addr == bound_addr->addr.in.sin_addr.s_addr) + return 0; + return addr->in.sin_addr.s_addr < bound_addr->addr.in.sin_addr.s_addr ? -1 : 1; + } + + assert( addr->addr.sa_family == AF_INET6 ); + if (addr->in6.sin6_port != bound_addr->addr.in6.sin6_port) + return addr->in6.sin6_port < bound_addr->addr.in6.sin6_port ? -1 : 1; + return memcmp( &addr->in6.sin6_addr, &bound_addr->addr.in6.sin6_addr, sizeof(addr->in6.sin6_addr) ); +} + +static int ipv4addr_from_v6( union unix_sockaddr *v4addr, const struct sockaddr_in6 *in6 ) +{ + v4addr->in.sin_family = AF_INET; + v4addr->in.sin_port = in6->sin6_port; + if (IN6_IS_ADDR_UNSPECIFIED(&in6->sin6_addr)) + { + v4addr->in.sin_addr.s_addr = INADDR_ANY; + return 1; + } + if (IN6_IS_ADDR_LOOPBACK(&in6->sin6_addr)) + { + v4addr->in.sin_addr.s_addr = INADDR_LOOPBACK; + return 1; + } + if (IN6_IS_ADDR_V4COMPAT(&in6->sin6_addr) || IN6_IS_ADDR_V4MAPPED(&in6->sin6_addr)) + { + memcpy( &v4addr->in.sin_addr.s_addr, &in6->sin6_addr.s6_addr[12], sizeof(v4addr->in.sin_addr.s_addr) ); + return 1; + } + return 0; +} + +static struct rb_tree bound_addresses_tree = { addr_compare }; + +static int should_track_conflicts_for_addr( struct sock *sock, const union unix_sockaddr *addr ) +{ + if (!is_tcp_socket( sock )) return 0; + + if (sock->family == WS_AF_INET && addr->addr.sa_family == AF_INET && addr->in.sin_port) + return 1; + else if (sock->family == WS_AF_INET6 && addr->addr.sa_family == AF_INET6 && addr->in6.sin6_port) + return 1; + + return 0; +} + +static int check_addr_usage( struct sock *sock, const union unix_sockaddr *addr, int v6only ) +{ + struct bound_addr *bound_addr; + union unix_sockaddr v4addr; + struct rb_entry *entry; + + if (!should_track_conflicts_for_addr( sock, addr )) return 0; + + if ((entry = rb_get( &bound_addresses_tree, addr ))) + { + bound_addr = WINE_RB_ENTRY_VALUE(entry, struct bound_addr, entry); + if (bound_addr->reuse_count == -1 || !sock->reuseaddr) return 1; + } + + if (sock->family != WS_AF_INET6 || v6only) return 0; + if (!ipv4addr_from_v6( &v4addr, &addr->in6 )) return 0; + if ((entry = rb_get( &bound_addresses_tree, &v4addr ))) + { + bound_addr = WINE_RB_ENTRY_VALUE(entry, struct bound_addr, entry); + if (bound_addr->reuse_count == -1 || !sock->reuseaddr) return 1; + } + return 0; +} + +static struct bound_addr *register_bound_address( struct sock *sock, const union unix_sockaddr *addr ) +{ + struct bound_addr *bound_addr; + + if (!(bound_addr = mem_alloc( sizeof(*bound_addr) ))) + return NULL; + + if (rb_put( &bound_addresses_tree, addr, &bound_addr->entry )) + { + free( bound_addr ); + bound_addr = WINE_RB_ENTRY_VALUE(rb_get( &bound_addresses_tree, addr ), struct bound_addr, entry); + if (bound_addr->reuse_count == -1) + { + if (debug_level) + fprintf( stderr, "register_bound_address: address being updated is already exclusively bound\n" ); + return NULL; + } + ++bound_addr->reuse_count; + } + else + { + bound_addr->addr = *addr; + bound_addr->reuse_count = sock->reuseaddr ? 1 : -1; + } + return bound_addr; +} + +static void update_addr_usage( struct sock *sock, const union unix_sockaddr *addr, int v6only ) +{ + union unix_sockaddr v4addr; + + assert( !sock->bound_addr[0] && !sock->bound_addr[1] ); + + if (!should_track_conflicts_for_addr( sock, addr )) return; + + sock->bound_addr[0] = register_bound_address( sock, addr ); + + if (sock->family != WS_AF_INET6 || v6only) return; + if (!ipv4addr_from_v6( &v4addr, &addr->in6 )) return; + + sock->bound_addr[1] = register_bound_address( sock, &v4addr ); +} + static void sock_dump( struct object *obj, int verbose ); static struct fd *sock_get_fd( struct object *obj ); static int sock_close_handle( struct object *obj, struct process *process, obj_handle_t handle ); @@ -297,19 +450,6 @@ static const struct fd_ops sock_fd_ops = sock_reselect_async /* reselect_async */ };
-union unix_sockaddr -{ - struct sockaddr addr; - struct sockaddr_in in; - struct sockaddr_in6 in6; -#ifdef HAS_IPX - struct sockaddr_ipx ipx; -#endif -#ifdef HAS_IRDA - struct sockaddr_irda irda; -#endif -}; - static int sockaddr_from_unix( const union unix_sockaddr *uaddr, struct WS_sockaddr *wsaddr, socklen_t wsaddrlen ) { memset( wsaddr, 0, wsaddrlen ); @@ -1493,11 +1633,21 @@ static int sock_close_handle( struct object *obj, struct process *process, obj_h static void sock_destroy( struct object *obj ) { struct sock *sock = (struct sock *)obj; + unsigned int i;
assert( obj->ops == &sock_ops );
/* FIXME: special socket shutdown stuff? */
+ for (i = 0; i < 2; ++i) + { + if (sock->bound_addr[i] && --sock->bound_addr[i]->reuse_count <= 0) + { + rb_remove( &bound_addresses_tree, &sock->bound_addr[i]->entry ); + free( sock->bound_addr[i] ); + } + } + if ( sock->deferred ) release_object( sock->deferred );
@@ -1552,6 +1702,7 @@ static struct sock *create_socket(void) sock->rcvtimeo = 0; sock->sndtimeo = 0; sock->icmp_fixup_data_len = 0; + sock->bound_addr[0] = sock->bound_addr[1] = NULL; init_async_queue( &sock->read_q ); init_async_queue( &sock->write_q ); init_async_queue( &sock->ifchange_q ); @@ -1740,6 +1891,13 @@ static int init_socket( struct sock *sock, int family, int type, int protocol ) sock->type = type; sock->family = family;
+ if (is_tcp_socket( sock )) + { + int reuse = 1; + + setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse) ); + } + if (sock->fd) { options = get_fd_options( sock->fd ); @@ -2671,6 +2829,7 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) union unix_sockaddr unix_addr, bind_addr; data_size_t in_size; socklen_t unix_len; + int v6only = 1;
/* the ioctl is METHOD_NEITHER, so ntdll gives us the output buffer as * input */ @@ -2720,6 +2879,21 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async )
set_async_pending( async );
+#ifdef IPV6_V6ONLY + if (sock->family == WS_AF_INET6) + { + socklen_t len = sizeof(v6only); + + getsockopt( get_unix_fd(sock->fd), IPPROTO_IPV6, IPV6_V6ONLY, &v6only, &len ); + } +#endif + + if (check_addr_usage( sock, &bind_addr, v6only )) + { + set_error( sock->reuseaddr ? STATUS_ACCESS_DENIED : STATUS_SHARING_VIOLATION ); + return; + } + if (bind( unix_fd, &bind_addr.addr, unix_len ) < 0) { if (errno == EADDRINUSE && sock->reuseaddr) @@ -2741,6 +2915,8 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) sock->addr_len = sockaddr_from_unix( &bind_addr, &sock->addr.addr, sizeof(sock->addr) ); }
+ update_addr_usage( sock, &bind_addr, v6only ); + if (get_reply_max_size() >= sock->addr_len) set_reply_data( &sock->addr, sock->addr_len ); return; @@ -2901,9 +3077,7 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) return; }
- /* BSD socket SO_REUSEADDR is not 100% compatible to winsock semantics; - * however, using it the BSD way fixes bug 8513 and seems to be what - * most programmers assume, anyway */ + /* BSD socket SO_REUSEADDR is not compatible with winsock semantics. */ case IOCTL_AFD_WINE_SET_SO_REUSEADDR: { int reuse, ret; @@ -2915,7 +3089,10 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) }
reuse = *(int *)get_req_data(); - ret = setsockopt( unix_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse) ); + if (is_tcp_socket( sock )) + ret = 0; + else + ret = setsockopt( unix_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse) ); #ifdef __APPLE__ if (!ret) ret = setsockopt( unix_fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse) ); #endif
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ws2_32/tests/sock.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-)
diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index 943ee8539ea..5460bc621d3 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -2123,7 +2123,7 @@ static void test_so_reuseaddr(void) closesocket(s3); closesocket(s4);
- /* Test binding and listening on any addr together with loopback. */ + /* Test binding and listening on any addr together with loopback, any addr first. */ s1 = socket(tests[i].domain, SOCK_STREAM, 0); ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
@@ -2157,6 +2157,41 @@ static void test_so_reuseaddr(void) closesocket(s3); closesocket(s4);
+ /* Test binding and listening on any addr together with loopback, loopback addr first. */ + + s1 = socket(tests[i].domain, SOCK_STREAM, 0); + ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + rc = bind(s1, tests[i].addr_loopback, tests[i].addrlen); + ok(!rc, "got error %d.\n", WSAGetLastError()); + + rc = listen(s1, 1); + ok(!rc, "got error %d.\n", WSAGetLastError()); + + s2 = socket(tests[i].domain, SOCK_STREAM, 0); + ok(s2 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + rc = bind(s2, tests[i].addr_any, tests[i].addrlen); + todo_wine ok(!rc, "got rc %d, error %d.\n", rc, WSAGetLastError()); + + rc = listen(s2, 1); + todo_wine ok(!rc, "got error %d.\n", WSAGetLastError()); + + s3 = socket(tests[i].domain, SOCK_STREAM, 0); + ok(s3 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + rc = connect(s3, tests[i].addr_loopback, tests[i].addrlen); + ok(!rc, "got error %d.\n", WSAGetLastError()); + size = tests[i].addrlen; + s4 = accept(s1, &saddr, &size); + + ok(s4 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError()); + + closesocket(s1); + closesocket(s2); + closesocket(s3); + closesocket(s4); + /* Test binding to INADDR_ANY on two sockets. */ s1 = socket(tests[i].domain, SOCK_STREAM, 0); ok(s1 != INVALID_SOCKET, "got error %d.\n", WSAGetLastError());
v5: - remove wrongly placed todo_wine in the last test.
GitLab
gitlab-to-mail https://gitlab.winehq.org/project_5_bot1 started a new discussion https://gitlab.winehq.org/wine/wine/-/merge_requests/1111#note_12406:
Zebediah Figura replied on the mailing list:
|> >> >> Should we add tests for the TIME_WAIT part as well? >> > I don't know how to do that 100% reliably for a non-flaky test, the way
I use locally is creating child processes which connect to each
other > and killing them. But that results in different TCP connection states on > ports and sometimes (rarely) they may get lucky and not hit the longest > timeout. Should I maybe attach a local program which reproduces that? Is it not sufficient to close sockets in-process?|
I think it is not always sufficient (especially if those are not localhost process) and also not possible. In the ultimate case if the process is force killed on host (kill -KILL) we don't have any sane way to close the sockets, and we want this case to work too. In the less ultimate case when the process is closed through winapi attempting that is still problematic. I am not sure it is valid to call 'closesocket' on behalf of the app (thus interfering with its communication with the peer). Then, it is not guaranteed that peer will answer in time and the socket gets fully closed without waits.
|I think in general there's value in tests that don't always fail without a fix, provided that they always succeed with it—i.e. they still can help prevent regressions—but in this case maybe it's not worth it if the test gets too ugly (or if the relevant case is too rarely hit).|
Well, it seems to me that the way it is done more or less reliably (like in my attached program) is ugly enough and I'd suggest not to add it. Please let me know if you think it still worth it.
This merge request was approved by Zebediah Figura.