From 931ec382d40f67e89788fa97d6be37fc55084fb7 Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Wed, 25 Jun 2025 02:56:21 -0700 Subject: [PATCH] [io/gardening] Fix client address binding & fix standalone/io/socket_local_port_test Various changes to the stanalone/io/socket_local_port_test: * Make server ports ephemeral: The test is testing client-side binding of address & port, the server part can use ephemeral ports. => This eliminates the issue of another process using the hard-coded port * Close sockets normally instead of using `Socket.destroy()` * Make dead code alive: There were test in the tests that were not invoked by `main()`. * Align the individual test helper functions. * ... Then we change the dart:io implementation of `Socket::CreateBindConnect` to set the `SO_REUSEADDR` socket option. We do this already for the server side in `Socket::CreateBindListen`, now we do it also for the client side. => This will ensure that one can bind the client side socket to specific address/source despite there being an old closed socket that's now in `TIME_WAIT` state. => This is the same reason we also do it in `Socket::CreateBindListen`. Fuchsia doesn't implement `Socket::CreateBindConnect` and on Windows the socket option seemingly has different semantics (we also don't use `SO_REUSEADDR` on windows for the server socket, but another option). Issue https://github.com/dart-lang/sdk/issues/51477 TEST=standalone/io/socket_local_port_test Change-Id: I7d07becad0cd98c3a9b973ef2f9037730d3f8b19 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/436902 Commit-Queue: Martin Kustermann Reviewed-by: Slava Egorov --- runtime/bin/socket_linux.cc | 4 + runtime/bin/socket_macos.cc | 4 + .../standalone/io/socket_local_port_test.dart | 166 ++++++++---------- 3 files changed, 80 insertions(+), 94 deletions(-) diff --git a/runtime/bin/socket_linux.cc b/runtime/bin/socket_linux.cc index b0e4aec307e..99fa9754f15 100644 --- a/runtime/bin/socket_linux.cc +++ b/runtime/bin/socket_linux.cc @@ -81,6 +81,10 @@ intptr_t Socket::CreateBindConnect(const RawAddr& addr, return fd; } + int optval = 1; + VOID_NO_RETRY_EXPECTED( + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))); + intptr_t result = TEMP_FAILURE_RETRY( bind(fd, &source_addr.addr, SocketAddress::GetAddrLength(source_addr))); if (result != 0) { diff --git a/runtime/bin/socket_macos.cc b/runtime/bin/socket_macos.cc index 0760f2f04e4..6aa863f5a35 100644 --- a/runtime/bin/socket_macos.cc +++ b/runtime/bin/socket_macos.cc @@ -86,6 +86,10 @@ intptr_t Socket::CreateBindConnect(const RawAddr& addr, return fd; } + int optval = 1; + VOID_NO_RETRY_EXPECTED( + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))); + intptr_t result = TEMP_FAILURE_RETRY( bind(fd, &source_addr.addr, SocketAddress::GetAddrLength(source_addr))); if (result != 0) { diff --git a/tests/standalone/io/socket_local_port_test.dart b/tests/standalone/io/socket_local_port_test.dart index 05b8d6c0cb1..a0dbcf10b06 100644 --- a/tests/standalone/io/socket_local_port_test.dart +++ b/tests/standalone/io/socket_local_port_test.dart @@ -8,46 +8,40 @@ import "dart:io"; import "package:expect/expect.dart"; Future testCustomPortIPv4() { - String clientAddress = "127.0.0.1"; + String host = "127.0.0.1"; int customLocalPort = 50988; - String serverAddress = clientAddress; - int port = 50989; + String customAddress = host; - return testCustomPort(serverAddress, port, clientAddress, customLocalPort); + return testCustomPort(host, customAddress, customLocalPort); } Future testCustomPortIPv6() { - String clientAddress = "::1"; - int customLocalPort = 50988; - String serverAddress = clientAddress; - int port = 50989; + String host = "::1"; + int customLocalPort = 50989; + String customAddress = host; - return testCustomPort(serverAddress, port, clientAddress, customLocalPort); + return testCustomPort(host, customAddress, customLocalPort); } Future testCustomPortIPv4NoSourceAddress() { - String expectedClientAddress = "127.0.0.1"; - int customLocalPort = 50988; - String serverAddress = expectedClientAddress; - int port = 50989; + String host = "127.0.0.1"; + int customLocalPort = 50990; + String expectedClientAddress = host; - return testCustomPort( - serverAddress, - port, + return testCustomPortNoSourceAddress( + host, expectedClientAddress, customLocalPort, ); } Future testCustomPortIPv6NoSourceAddress() { - String expectedClientAddress = "::1"; - int customLocalPort = 50988; - String serverAddress = expectedClientAddress; - int port = 50989; + String host = "::1"; + int customLocalPort = 50991; + String expectedClientAddress = host; - return testCustomPort( - serverAddress, - port, + return testCustomPortNoSourceAddress( + host, expectedClientAddress, customLocalPort, ); @@ -56,134 +50,118 @@ Future testCustomPortIPv6NoSourceAddress() { Future testNoCustomPortIPv4() { String host = "127.0.0.1"; String clientAddress = host; - int serverPort = 39998; - return testNoCustomPortNoSourceAddress(host, serverPort, clientAddress); + return testNoCustomPort(host, clientAddress); } Future testNoCustomPortIPv6() { String host = "::1"; String clientAddress = host; - int serverPort = 39998; - return testNoCustomPortNoSourceAddress(host, serverPort, clientAddress); + return testNoCustomPort(host, clientAddress); } Future testNoCustomPortNoSourceAddressIPv4() { String host = "127.0.0.1"; String expectedAddress = host; - int serverPort = 39998; - return testNoCustomPortNoSourceAddress(host, serverPort, expectedAddress); + return testNoCustomPortNoSourceAddress(host, expectedAddress); } Future testNoCustomPortNoSourceAddressIPv6() { String host = "::1"; String expectedAddress = host; - int serverPort = 39998; - return testNoCustomPortNoSourceAddress(host, serverPort, expectedAddress); + return testNoCustomPortNoSourceAddress(host, expectedAddress); } // Core functionality -Future testCustomPort( - String host, - int port, - String sourceAddress, - int sourcePort, -) async { - var server = await ServerSocket.bind(host, port); - server.listen((client) { - Expect.equals(server.port, port); + +Future testCustomPort(String host, String sourceAddress, int sourcePort) async { + final serverTestDone = Completer(); + final server = await ServerSocket.bind(host, 0); + server.listen((Socket client) async { Expect.equals(client.remotePort, sourcePort); Expect.equals(client.address.address, sourceAddress); - client.destroy(); + await (client.close(), client.drain()).wait; + serverTestDone.complete(); }); - - Socket s = await Socket.connect( + final client = await Socket.connect( host, - port, + server.port, sourceAddress: sourceAddress, sourcePort: sourcePort, ); - s.destroy(); + await (client.close(), client.drain()).wait; + await serverTestDone.future; await server.close(); } Future testCustomPortNoSourceAddress( String host, - int port, String expectedAddress, int sourcePort, ) async { - Completer completer = new Completer(); - var server = await ServerSocket.bind(host, port); - - server.listen((client) { - Expect.equals(server.port, port); + final serverTestDone = Completer(); + final server = await ServerSocket.bind(host, 0); + server.listen((Socket client) async { Expect.equals(client.remotePort, sourcePort); Expect.equals(client.address.address, expectedAddress); - client.destroy(); - completer.complete(); + await (client.close(), client.drain()).wait; + serverTestDone.complete(); }); - - Socket s = await Socket.connect(host, port, sourcePort: sourcePort); - s.destroy(); + final client = await Socket.connect( + host, + server.port, + sourcePort: sourcePort, + ); + await (client.close(), client.drain()).wait; + await serverTestDone.future; await server.close(); - - return completer.future; } -Future testNoCustomPort(String host, int port, String sourceAddress) async { - Completer serverCompleter = new Completer(); - Completer clientCompleter = new Completer(); - var server = await ServerSocket.bind(host, port); - Socket.connect(host, port, sourceAddress: sourceAddress).then(( - clientSocket, - ) async { - server.listen((client) async { - Expect.equals(server.port, port); - Expect.equals(client.remotePort, clientSocket.port); - Expect.equals(client.address.address, sourceAddress); - - client.destroy(); - clientCompleter.complete(); - }); - - clientSocket.destroy(); - await server.close(); - serverCompleter.complete(); +Future testNoCustomPort(String host, String sourceAddress) async { + final serverTestDone = Completer(); + final server = await ServerSocket.bind(host, 0); + server.listen((Socket client) async { + Expect.equals(client.address.address, sourceAddress); + await (client.close(), client.drain()).wait; + serverTestDone.complete(); }); - - await serverCompleter.future; - await clientCompleter.future; + final client = await Socket.connect( + host, + server.port, + sourceAddress: sourceAddress, + ); + await (client.close(), client.drain()).wait; + await serverTestDone.future; + await server.close(); } Future testNoCustomPortNoSourceAddress( String host, - int port, String expectedAddress, ) async { - Completer completer = new Completer(); - var server = await ServerSocket.bind(host, port); - Socket.connect(host, port).then((clientSocket) { - server.listen((client) async { - Expect.equals(server.port, port); - Expect.equals(client.remotePort, clientSocket.port); - Expect.equals(client.address.address, expectedAddress); - clientSocket.destroy(); - client.destroy(); - await server.close(); - completer.complete(); - }); + final serverTestDone = Completer(); + final server = await ServerSocket.bind(host, 0); + server.listen((Socket client) async { + Expect.equals(client.address.address, expectedAddress); + await (client.close(), client.drain()).wait; + serverTestDone.complete(); }); - return completer.future; + final client = await Socket.connect(host, server.port); + await (client.close(), client.drain()).wait; + await serverTestDone.future; + await server.close(); } Future main() async { await testCustomPortIPv4(); await testCustomPortIPv6(); + await testCustomPortIPv4NoSourceAddress(); + await testCustomPortIPv6NoSourceAddress(); + await testNoCustomPortIPv4(); await testNoCustomPortIPv6();