From 8caaa1b9cfefb03fcd8a736b6c776d74fe853c61 Mon Sep 17 00:00:00 2001 From: Mo Date: Wed, 8 Dec 2021 18:33:00 +0000 Subject: [PATCH] Added sourcePort to Socket.connect()/startConnect() as optional parameter. This allows to specify the local port for TCP client sockets. All prototypes in Socket,RawSocket,_Rawsocket,_NativeSocket have been adopted, aswell as the native counterpart to nativeCreateBindConnect. TEST=new tests added Change-Id: I3408b687cbfd7eaaaeafdda29f7093d92c92aea0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/217604 Reviewed-by: Siva Annamalai Auto-Submit: Moritz Feldmann Reviewed-by: Kevin Moore Commit-Queue: Kevin Moore --- CHANGELOG.md | 1 + runtime/bin/io_natives.cc | 2 +- runtime/bin/socket.cc | 7 +- .../js_dev_runtime/patch/io_patch.dart | 8 +- .../_internal/js_runtime/lib/io_patch.dart | 8 +- sdk/lib/_internal/vm/bin/socket_patch.dart | 59 ++++--- sdk/lib/io/overrides.dart | 39 +++-- sdk/lib/io/socket.dart | 29 ++-- tests/standalone/io/io_override_test.dart | 4 +- .../standalone/io/socket_local_port_test.dart | 162 ++++++++++++++++++ tests/standalone_2/io/io_override_test.dart | 4 +- .../io/socket_local_port_test.dart | 162 ++++++++++++++++++ 12 files changed, 421 insertions(+), 64 deletions(-) create mode 100644 tests/standalone/io/socket_local_port_test.dart create mode 100644 tests/standalone_2/io/socket_local_port_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 92ea9c77e12..2852bedc6d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - **Breaking Change** [#47769](https://github.com/dart-lang/sdk/issues/47769): The `Platform.packageRoot` API has been removed. It had been marked deprecated in 2018, as it doesn't work with any Dart 2.x release. +- Add optional `sourcePort` parameter to `Socket.connect`, `Socket.startConnect`, `RawSocket.connect` and `RawSocket.startConnect` #### `dart:isolate` diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc index efa37c8ef6b..17b9404277e 100644 --- a/runtime/bin/io_natives.cc +++ b/runtime/bin/io_natives.cc @@ -144,7 +144,7 @@ namespace bin { V(SocketBase_IsBindError, 2) \ V(Socket_Available, 1) \ V(Socket_AvailableDatagram, 1) \ - V(Socket_CreateBindConnect, 5) \ + V(Socket_CreateBindConnect, 6) \ V(Socket_CreateUnixDomainBindConnect, 4) \ V(Socket_CreateBindDatagram, 6) \ V(Socket_CreateConnect, 4) \ diff --git a/runtime/bin/socket.cc b/runtime/bin/socket.cc index 62cc613f446..21bfadf3cff 100644 --- a/runtime/bin/socket.cc +++ b/runtime/bin/socket.cc @@ -396,8 +396,13 @@ void FUNCTION_NAME(Socket_CreateBindConnect)(Dart_NativeArguments args) { SocketAddress::SetAddrPort(&addr, static_cast(port)); RawAddr sourceAddr; SocketAddress::GetSockAddr(Dart_GetNativeArgument(args, 3), &sourceAddr); + Dart_Handle source_port_arg = Dart_GetNativeArgument(args, 4); + int64_t source_port = + DartUtils::GetInt64ValueCheckRange(source_port_arg, 0, 65535); + SocketAddress::SetAddrPort(&sourceAddr, static_cast(source_port)); + if (addr.addr.sa_family == AF_INET6) { - Dart_Handle scope_id_arg = Dart_GetNativeArgument(args, 4); + Dart_Handle scope_id_arg = Dart_GetNativeArgument(args, 5); int64_t scope_id = DartUtils::GetInt64ValueCheckRange(scope_id_arg, 0, 65535); SocketAddress::SetAddrScope(&addr, scope_id); diff --git a/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart index 4960213be14..413116cbc63 100644 --- a/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart +++ b/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart @@ -475,13 +475,13 @@ class ServerSocket { class RawSocket { @patch static Future connect(dynamic host, int port, - {dynamic sourceAddress, Duration? timeout}) { + {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) { throw UnsupportedError("RawSocket constructor"); } @patch static Future> startConnect(dynamic host, int port, - {dynamic sourceAddress}) { + {dynamic sourceAddress, int sourcePort = 0}) { throw UnsupportedError("RawSocket constructor"); } } @@ -490,13 +490,13 @@ class RawSocket { class Socket { @patch static Future _connect(dynamic host, int port, - {dynamic sourceAddress, Duration? timeout}) { + {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) { throw UnsupportedError("Socket constructor"); } @patch static Future> _startConnect(dynamic host, int port, - {dynamic sourceAddress}) { + {dynamic sourceAddress, int sourcePort = 0}) { throw UnsupportedError("Socket constructor"); } } diff --git a/sdk/lib/_internal/js_runtime/lib/io_patch.dart b/sdk/lib/_internal/js_runtime/lib/io_patch.dart index 6a88d0f9e28..ae50e7b586b 100644 --- a/sdk/lib/_internal/js_runtime/lib/io_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/io_patch.dart @@ -475,13 +475,13 @@ class ServerSocket { class RawSocket { @patch static Future connect(dynamic host, int port, - {dynamic sourceAddress, Duration? timeout}) { + {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) { throw new UnsupportedError("RawSocket constructor"); } @patch static Future> startConnect(dynamic host, int port, - {dynamic sourceAddress}) { + {dynamic sourceAddress, int sourcePort = 0}) { throw new UnsupportedError("RawSocket constructor"); } } @@ -490,13 +490,13 @@ class RawSocket { class Socket { @patch static Future _connect(dynamic host, int port, - {dynamic sourceAddress, Duration? timeout}) { + {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) { throw new UnsupportedError("Socket constructor"); } @patch static Future> _startConnect(dynamic host, int port, - {dynamic sourceAddress}) { + {dynamic sourceAddress, int sourcePort = 0}) { throw new UnsupportedError("Socket constructor"); } } diff --git a/sdk/lib/_internal/vm/bin/socket_patch.dart b/sdk/lib/_internal/vm/bin/socket_patch.dart index 8812068d753..d681626b943 100644 --- a/sdk/lib/_internal/vm/bin/socket_patch.dart +++ b/sdk/lib/_internal/vm/bin/socket_patch.dart @@ -17,14 +17,14 @@ class RawServerSocket { class RawSocket { @patch static Future connect(dynamic host, int port, - {dynamic sourceAddress, Duration? timeout}) { - return _RawSocket.connect(host, port, sourceAddress, timeout); + {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) { + return _RawSocket.connect(host, port, sourceAddress, sourcePort, timeout); } @patch static Future> startConnect(dynamic host, int port, - {dynamic sourceAddress}) { - return _RawSocket.startConnect(host, port, sourceAddress); + {dynamic sourceAddress, int sourcePort = 0}) { + return _RawSocket.startConnect(host, port, sourceAddress, sourcePort); } } @@ -654,7 +654,7 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { } static Future> startConnect( - dynamic host, int port, dynamic sourceAddress) { + dynamic host, int port, dynamic sourceAddress, int sourcePort) { // Looks up [sourceAddress] to one or more IP addresses, // then tries connecting to each one until a connection succeeds. // Attempts are staggered by a minimum delay, so a new @@ -666,6 +666,7 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { host = escapeLinkLocalAddress(host); } _throwOnBadPort(port); + _throwOnBadPort(sourcePort); _InternetAddress? source; if (sourceAddress != null) { if (sourceAddress is _InternetAddress) { @@ -682,7 +683,7 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { return new Future.value(host).then>((host) { if (host is _InternetAddress) { - return tryConnectToResolvedAddresses(host, port, source, + return tryConnectToResolvedAddresses(host, port, source, sourcePort, Stream.value(<_InternetAddress>[host]), stackTrace); } final hostname = host as String; @@ -700,7 +701,7 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { : lookupAsStream(hostname); return tryConnectToResolvedAddresses( - host, port, source, stream, stackTrace); + host, port, source, sourcePort, stream, stackTrace); }); } @@ -708,6 +709,7 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { dynamic host, int port, _InternetAddress? source, + int sourcePort, Stream> addresses, StackTrace callerStackTrace) { // Completer for result. @@ -758,10 +760,16 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { connectionResult is OSError); } else { final address_ = address as _InternetAddress; - if (source == null) { + if (source == null && sourcePort == 0) { connectionResult = socket.nativeCreateConnect( address_._in_addr, port, address_._scope_id); } else { + // allow specified port without address + if (source == null) { + source = address_.type == InternetAddressType.IPv4 + ? _InternetAddress.anyIPv4 + : _InternetAddress.anyIPv6; + } if (source.type != InternetAddressType.IPv4 && source.type != InternetAddressType.IPv6) { return SocketException( @@ -773,8 +781,8 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { "${InternetAddressType.IPv6} but was ${source.type}", address: address); } - connectionResult = socket.nativeCreateBindConnect( - address_._in_addr, port, source._in_addr, address_._scope_id); + connectionResult = socket.nativeCreateBindConnect(address_._in_addr, + port, source._in_addr, sourcePort, address_._scope_id); } assert(connectionResult == true || connectionResult is OSError); } @@ -935,9 +943,9 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { return new ConnectionTask<_NativeSocket>._(result.future, onCancel); } - static Future<_NativeSocket> connect( - dynamic host, int port, dynamic sourceAddress, Duration? timeout) { - return startConnect(host, port, sourceAddress) + static Future<_NativeSocket> connect(dynamic host, int port, + dynamic sourceAddress, int sourcePort, Duration? timeout) { + return startConnect(host, port, sourceAddress, sourcePort) .then((ConnectionTask<_NativeSocket> task) { Future<_NativeSocket> socketFuture = task.socket; if (timeout != null) { @@ -1653,8 +1661,8 @@ class _NativeSocket extends _NativeSocketNativeWrapper with _ServiceObject { @pragma("vm:external-name", "Socket_CreateUnixDomainConnect") external nativeCreateUnixDomainConnect(String addr, _Namespace namespace); @pragma("vm:external-name", "Socket_CreateBindConnect") - external nativeCreateBindConnect( - Uint8List addr, int port, Uint8List sourceAddr, int scope_id); + external nativeCreateBindConnect(Uint8List addr, int port, + Uint8List sourceAddr, int sourcePort, int scope_id); @pragma("vm:external-name", "Socket_CreateUnixDomainBindConnect") external nativeCreateUnixDomainBindConnect( String addr, String sourceAddr, _Namespace namespace); @@ -1792,9 +1800,9 @@ class _RawSocket extends Stream implements RawSocket { // Flag to handle Ctrl-D closing of stdio on Mac OS. bool _isMacOSTerminalInput = false; - static Future connect( - dynamic host, int port, dynamic sourceAddress, Duration? timeout) { - return _NativeSocket.connect(host, port, sourceAddress, timeout) + static Future connect(dynamic host, int port, + dynamic sourceAddress, int sourcePort, Duration? timeout) { + return _NativeSocket.connect(host, port, sourceAddress, sourcePort, timeout) .then((socket) { if (!const bool.fromEnvironment("dart.vm.product")) { _SocketProfile.collectNewSocket( @@ -1805,8 +1813,8 @@ class _RawSocket extends Stream implements RawSocket { } static Future> startConnect( - dynamic host, int port, dynamic sourceAddress) { - return _NativeSocket.startConnect(host, port, sourceAddress) + dynamic host, int port, dynamic sourceAddress, int sourcePort) { + return _NativeSocket.startConnect(host, port, sourceAddress, sourcePort) .then((ConnectionTask<_NativeSocket> nativeTask) { final Future<_RawSocket> raw = nativeTask.socket.then((_NativeSocket nativeSocket) { @@ -2016,16 +2024,19 @@ class _ServerSocket extends Stream implements ServerSocket { class Socket { @patch static Future _connect(dynamic host, int port, - {dynamic sourceAddress, Duration? timeout}) { + {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) { return RawSocket.connect(host, port, - sourceAddress: sourceAddress, timeout: timeout) + sourceAddress: sourceAddress, + sourcePort: sourcePort, + timeout: timeout) .then((socket) => new _Socket(socket)); } @patch static Future> _startConnect(dynamic host, int port, - {dynamic sourceAddress}) { - return RawSocket.startConnect(host, port, sourceAddress: sourceAddress) + {dynamic sourceAddress, int sourcePort = 0}) { + return RawSocket.startConnect(host, port, + sourceAddress: sourceAddress, sourcePort: sourcePort) .then((rawTask) { Future socket = rawTask.socket.then((rawSocket) => new _Socket(rawSocket)); diff --git a/sdk/lib/io/overrides.dart b/sdk/lib/io/overrides.dart index c97e84d3d6a..a124c767cdf 100644 --- a/sdk/lib/io/overrides.dart +++ b/sdk/lib/io/overrides.dart @@ -81,10 +81,10 @@ abstract class IOOverrides { // Socket Future Function(dynamic, int, - {dynamic sourceAddress, Duration? timeout})? + {dynamic sourceAddress, int sourcePort, Duration? timeout})? socketConnect, Future> Function(dynamic, int, - {dynamic sourceAddress})? + {dynamic sourceAddress, int sourcePort})? socketStartConnect, // ServerSocket @@ -269,9 +269,9 @@ abstract class IOOverrides { /// When this override is installed, this functions overrides the behavior of /// `Socket.connect(...)`. Future socketConnect(host, int port, - {sourceAddress, Duration? timeout}) { + {sourceAddress, int sourcePort = 0, Duration? timeout}) { return Socket._connect(host, port, - sourceAddress: sourceAddress, timeout: timeout); + sourceAddress: sourceAddress, sourcePort: sourcePort, timeout: timeout); } /// Asynchronously returns a [ConnectionTask] that connects to the given host @@ -280,8 +280,9 @@ abstract class IOOverrides { /// When this override is installed, this functions overrides the behavior of /// `Socket.startConnect(...)`. Future> socketStartConnect(host, int port, - {sourceAddress}) { - return Socket._startConnect(host, port, sourceAddress: sourceAddress); + {sourceAddress, int sourcePort = 0}) { + return Socket._startConnect(host, port, + sourceAddress: sourceAddress, sourcePort: sourcePort); } // ServerSocket @@ -355,9 +356,11 @@ class _IOOverridesScope extends IOOverrides { // Socket Future Function(dynamic, int, - {dynamic sourceAddress, Duration? timeout})? _socketConnect; + {dynamic sourceAddress, + int sourcePort, + Duration? timeout})? _socketConnect; Future> Function(dynamic, int, - {dynamic sourceAddress})? _socketStartConnect; + {dynamic sourceAddress, int sourcePort})? _socketStartConnect; // ServerSocket Future Function(dynamic, int, @@ -518,30 +521,34 @@ class _IOOverridesScope extends IOOverrides { // Socket @override Future socketConnect(host, int port, - {sourceAddress, Duration? timeout}) { + {sourceAddress, int sourcePort = 0, Duration? timeout}) { if (_socketConnect != null) { return _socketConnect!(host, port, sourceAddress: sourceAddress, timeout: timeout); } if (_previous != null) { return _previous!.socketConnect(host, port, - sourceAddress: sourceAddress, timeout: timeout); + sourceAddress: sourceAddress, + sourcePort: sourcePort, + timeout: timeout); } return super.socketConnect(host, port, - sourceAddress: sourceAddress, timeout: timeout); + sourceAddress: sourceAddress, sourcePort: sourcePort, timeout: timeout); } @override Future> socketStartConnect(host, int port, - {sourceAddress}) { + {sourceAddress, int sourcePort = 0}) { if (_socketStartConnect != null) { - return _socketStartConnect!(host, port, sourceAddress: sourceAddress); + return _socketStartConnect!(host, port, + sourceAddress: sourceAddress, sourcePort: sourcePort); } if (_previous != null) { - return _previous! - .socketStartConnect(host, port, sourceAddress: sourceAddress); + return _previous!.socketStartConnect(host, port, + sourceAddress: sourceAddress, sourcePort: sourcePort); } - return super.socketStartConnect(host, port, sourceAddress: sourceAddress); + return super.socketStartConnect(host, port, + sourceAddress: sourceAddress, sourcePort: sourcePort); } // ServerSocket diff --git a/sdk/lib/io/socket.dart b/sdk/lib/io/socket.dart index 26cb3efd232..3c102c53c6e 100644 --- a/sdk/lib/io/socket.dart +++ b/sdk/lib/io/socket.dart @@ -589,19 +589,22 @@ abstract class RawSocket implements Stream { /// be a [String] or an [InternetAddress]. If a [String] is passed it must /// hold a numeric IP address. /// + /// The [sourcePort] defines the local port to bind to. If [sourcePort] is + /// not specified or zero, a port will be chosen. + /// /// The argument [timeout] is used to specify the maximum allowed time to wait /// for a connection to be established. If [timeout] is longer than the system /// level timeout duration, a timeout may occur sooner than specified in /// [timeout]. On timeout, a [SocketException] is thrown and all ongoing /// connection attempts to [host] are cancelled. external static Future connect(host, int port, - {sourceAddress, Duration? timeout}); + {sourceAddress, int sourcePort = 0, Duration? timeout}); /// Like [connect], but returns a [Future] that completes with a /// [ConnectionTask] that can be cancelled if the [RawSocket] is no /// longer needed. external static Future> startConnect(host, int port, - {sourceAddress}); + {sourceAddress, int sourcePort = 0}); /// The number of received and non-read bytes in the socket that can be read. int available(); @@ -758,40 +761,46 @@ abstract class Socket implements Stream, IOSink { /// be a [String] or an [InternetAddress]. If a [String] is passed it must /// hold a numeric IP address. /// + /// The [sourcePort] defines the local port to bind to. If [sourcePort] is + /// not specified or zero, a port will be chosen. + /// /// The argument [timeout] is used to specify the maximum allowed time to wait /// for a connection to be established. If [timeout] is longer than the system /// level timeout duration, a timeout may occur sooner than specified in /// [timeout]. On timeout, a [SocketException] is thrown and all ongoing /// connection attempts to [host] are cancelled. static Future connect(host, int port, - {sourceAddress, Duration? timeout}) { + {sourceAddress, int sourcePort = 0, Duration? timeout}) { final IOOverrides? overrides = IOOverrides.current; if (overrides == null) { return Socket._connect(host, port, - sourceAddress: sourceAddress, timeout: timeout); + sourceAddress: sourceAddress, + sourcePort: sourcePort, + timeout: timeout); } return overrides.socketConnect(host, port, - sourceAddress: sourceAddress, timeout: timeout); + sourceAddress: sourceAddress, sourcePort: sourcePort, timeout: timeout); } /// Like [connect], but returns a [Future] that completes with a /// [ConnectionTask] that can be cancelled if the [Socket] is no /// longer needed. static Future> startConnect(host, int port, - {sourceAddress}) { + {sourceAddress, int sourcePort = 0}) { final IOOverrides? overrides = IOOverrides.current; if (overrides == null) { - return Socket._startConnect(host, port, sourceAddress: sourceAddress); + return Socket._startConnect(host, port, + sourceAddress: sourceAddress, sourcePort: sourcePort); } return overrides.socketStartConnect(host, port, - sourceAddress: sourceAddress); + sourceAddress: sourceAddress, sourcePort: sourcePort); } external static Future _connect(host, int port, - {sourceAddress, Duration? timeout}); + {sourceAddress, int sourcePort = 0, Duration? timeout}); external static Future> _startConnect(host, int port, - {sourceAddress}); + {sourceAddress, int sourcePort = 0}); /// Destroys the socket in both directions. /// diff --git a/tests/standalone/io/io_override_test.dart b/tests/standalone/io/io_override_test.dart index fb8c7809458..fdd49ba88cf 100644 --- a/tests/standalone/io/io_override_test.dart +++ b/tests/standalone/io/io_override_test.dart @@ -161,12 +161,12 @@ class LinkMock extends FileSystemEntity implements Link { } Future socketConnect(dynamic host, int port, - {dynamic sourceAddress, Duration? timeout}) { + {dynamic sourceAddress, int sourcePort = 0, Duration? timeout}) { throw ""; } Future> socketStartConnect(dynamic host, int port, - {dynamic sourceAddress}) { + {dynamic sourceAddress, int sourcePort = 0}) { throw ""; } diff --git a/tests/standalone/io/socket_local_port_test.dart b/tests/standalone/io/socket_local_port_test.dart new file mode 100644 index 00000000000..f7251ec3a10 --- /dev/null +++ b/tests/standalone/io/socket_local_port_test.dart @@ -0,0 +1,162 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import "dart:io"; + +import "package:expect/expect.dart"; + +Future testCustomPortIPv4() async { + String clientAddress = "127.0.0.1"; + int customLocalPort = 50988; + String serverAddress = clientAddress; + int port = 50989; + + testCustomPort(serverAddress, port, clientAddress, customLocalPort); +} + +Future testCustomPortIPv6() async { + String clientAddress = "::1"; + int customLocalPort = 50988; + String serverAddress = clientAddress; + int port = 50989; + + testCustomPort(serverAddress, port, clientAddress, customLocalPort); +} + +Future testCustomPortIPv4NoSourceAddress() async { + String expectedClientAddress = "127.0.0.1"; + int customLocalPort = 50988; + String serverAddress = expectedClientAddress; + int port = 50989; + + testCustomPort(serverAddress, port, expectedClientAddress, customLocalPort); +} + +Future testCustomPortIPv6NoSourceAddress() async { + String expectedClientAddress = "::1"; + int customLocalPort = 50988; + String serverAddress = expectedClientAddress; + int port = 50989; + + testCustomPort(serverAddress, port, expectedClientAddress, customLocalPort); +} + +Future testNoCustomPortIPv4() async { + String host = "127.0.0.1"; + String clientAddress = host; + int serverPort = 39998; + + await testNoCustomPortNoSourceAddress(host, serverPort, clientAddress); +} + +Future testNoCustomPortIPv6() async { + String host = "::1"; + String clientAddress = host; + int serverPort = 39998; + + await testNoCustomPortNoSourceAddress(host, serverPort, clientAddress); +} + +Future testNoCustomPortNoSourceAddressIPv4() async { + String host = "127.0.0.1"; + String expectedAddress = host; + int serverPort = 39998; + + await testNoCustomPortNoSourceAddress(host, serverPort, expectedAddress); +} + +Future testNoCustomPortNoSourceAddressIPv6() async { + String host = "::1"; + String expectedAddress = host; + int serverPort = 39998; + + await testNoCustomPortNoSourceAddress(host, serverPort, expectedAddress); +} + +// Core functionality +void 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); + Expect.equals(client.remotePort, sourcePort); + Expect.equals(client.address.address, sourceAddress); + client.destroy(); + }); + + Socket s = await Socket.connect(host, port, + sourceAddress: sourceAddress, sourcePort: sourcePort); + s.destroy(); + 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); + Expect.equals(client.remotePort, sourcePort); + Expect.equals(client.address.address, expectedAddress); + client.destroy(); + completer.complete(); + }); + + Socket s = await Socket.connect(host, port, sourcePort: sourcePort); + s.destroy(); + server.close(); + + return completer.future; +} + +Future testNoCustomPort(String host, int port, String sourceAddress) async { + Completer completer = new Completer(); + var server = await ServerSocket.bind(host, port); + Socket.connect(host, port, sourceAddress: sourceAddress).then((clientSocket) { + server.listen((client) { + Expect.equals(server.port, port); + Expect.equals(client.remotePort, clientSocket.port); + Expect.equals(client.address.address, sourceAddress); + + client.destroy(); + completer.complete(); + }); + + clientSocket.destroy(); + server.close(); + }); + + return completer.future; +} + +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) { + Expect.equals(server.port, port); + Expect.equals(client.remotePort, clientSocket.port); + Expect.equals(client.address.address, expectedAddress); + clientSocket.destroy(); + client.destroy(); + server.close(); + completer.complete(); + }); + }); + return completer.future; +} + +Future main() async { + await testCustomPortIPv4(); + await testCustomPortIPv6(); + + await testNoCustomPortIPv4(); + await testNoCustomPortIPv6(); + + await testNoCustomPortNoSourceAddressIPv4(); + await testNoCustomPortNoSourceAddressIPv6(); +} diff --git a/tests/standalone_2/io/io_override_test.dart b/tests/standalone_2/io/io_override_test.dart index 5ac4160def7..e7d9a8183d2 100644 --- a/tests/standalone_2/io/io_override_test.dart +++ b/tests/standalone_2/io/io_override_test.dart @@ -161,12 +161,12 @@ class LinkMock extends FileSystemEntity implements Link { } Future socketConnect(host, int port, - {sourceAddress, Duration timeout}) { + {sourceAddress, int sourcePort, Duration timeout}) { return null; } Future> socketStartConnect(host, int port, - {sourceAddress}) { + {sourceAddress, int sourcePort}) { return null; } diff --git a/tests/standalone_2/io/socket_local_port_test.dart b/tests/standalone_2/io/socket_local_port_test.dart new file mode 100644 index 00000000000..f7251ec3a10 --- /dev/null +++ b/tests/standalone_2/io/socket_local_port_test.dart @@ -0,0 +1,162 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import "dart:io"; + +import "package:expect/expect.dart"; + +Future testCustomPortIPv4() async { + String clientAddress = "127.0.0.1"; + int customLocalPort = 50988; + String serverAddress = clientAddress; + int port = 50989; + + testCustomPort(serverAddress, port, clientAddress, customLocalPort); +} + +Future testCustomPortIPv6() async { + String clientAddress = "::1"; + int customLocalPort = 50988; + String serverAddress = clientAddress; + int port = 50989; + + testCustomPort(serverAddress, port, clientAddress, customLocalPort); +} + +Future testCustomPortIPv4NoSourceAddress() async { + String expectedClientAddress = "127.0.0.1"; + int customLocalPort = 50988; + String serverAddress = expectedClientAddress; + int port = 50989; + + testCustomPort(serverAddress, port, expectedClientAddress, customLocalPort); +} + +Future testCustomPortIPv6NoSourceAddress() async { + String expectedClientAddress = "::1"; + int customLocalPort = 50988; + String serverAddress = expectedClientAddress; + int port = 50989; + + testCustomPort(serverAddress, port, expectedClientAddress, customLocalPort); +} + +Future testNoCustomPortIPv4() async { + String host = "127.0.0.1"; + String clientAddress = host; + int serverPort = 39998; + + await testNoCustomPortNoSourceAddress(host, serverPort, clientAddress); +} + +Future testNoCustomPortIPv6() async { + String host = "::1"; + String clientAddress = host; + int serverPort = 39998; + + await testNoCustomPortNoSourceAddress(host, serverPort, clientAddress); +} + +Future testNoCustomPortNoSourceAddressIPv4() async { + String host = "127.0.0.1"; + String expectedAddress = host; + int serverPort = 39998; + + await testNoCustomPortNoSourceAddress(host, serverPort, expectedAddress); +} + +Future testNoCustomPortNoSourceAddressIPv6() async { + String host = "::1"; + String expectedAddress = host; + int serverPort = 39998; + + await testNoCustomPortNoSourceAddress(host, serverPort, expectedAddress); +} + +// Core functionality +void 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); + Expect.equals(client.remotePort, sourcePort); + Expect.equals(client.address.address, sourceAddress); + client.destroy(); + }); + + Socket s = await Socket.connect(host, port, + sourceAddress: sourceAddress, sourcePort: sourcePort); + s.destroy(); + 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); + Expect.equals(client.remotePort, sourcePort); + Expect.equals(client.address.address, expectedAddress); + client.destroy(); + completer.complete(); + }); + + Socket s = await Socket.connect(host, port, sourcePort: sourcePort); + s.destroy(); + server.close(); + + return completer.future; +} + +Future testNoCustomPort(String host, int port, String sourceAddress) async { + Completer completer = new Completer(); + var server = await ServerSocket.bind(host, port); + Socket.connect(host, port, sourceAddress: sourceAddress).then((clientSocket) { + server.listen((client) { + Expect.equals(server.port, port); + Expect.equals(client.remotePort, clientSocket.port); + Expect.equals(client.address.address, sourceAddress); + + client.destroy(); + completer.complete(); + }); + + clientSocket.destroy(); + server.close(); + }); + + return completer.future; +} + +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) { + Expect.equals(server.port, port); + Expect.equals(client.remotePort, clientSocket.port); + Expect.equals(client.address.address, expectedAddress); + clientSocket.destroy(); + client.destroy(); + server.close(); + completer.complete(); + }); + }); + return completer.future; +} + +Future main() async { + await testCustomPortIPv4(); + await testCustomPortIPv6(); + + await testNoCustomPortIPv4(); + await testNoCustomPortIPv6(); + + await testNoCustomPortNoSourceAddressIPv4(); + await testNoCustomPortNoSourceAddressIPv6(); +}