diff --git a/packages/flutter_driver/lib/src/common/fuchsia_compat.dart b/packages/flutter_driver/lib/src/common/fuchsia_compat.dart index e54d6e44e05..5875d476dfd 100644 --- a/packages/flutter_driver/lib/src/common/fuchsia_compat.dart +++ b/packages/flutter_driver/lib/src/common/fuchsia_compat.dart @@ -25,9 +25,6 @@ class _DummyPortForwarder implements PortForwarder { @override int get remotePort => _remotePort; - @override - String get openPortAddress => InternetAddress.loopbackIPv4.address; - @override Future stop() async { } } diff --git a/packages/fuchsia_remote_debug_protocol/lib/src/common/network.dart b/packages/fuchsia_remote_debug_protocol/lib/src/common/network.dart index 1c3f470f3b7..192371ec506 100644 --- a/packages/fuchsia_remote_debug_protocol/lib/src/common/network.dart +++ b/packages/fuchsia_remote_debug_protocol/lib/src/common/network.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:io'; +import 'dart:core'; /// Determines whether `address` is a valid IPv6 or IPv4 address. /// @@ -17,7 +17,7 @@ void validateAddress(String address) { /// Returns true if `address` is a valid IPv6 address. bool isIpV6Address(String address) { try { - InternetAddress(address, type:InternetAddressType.IPv6); + Uri.parseIPv6Address(address); return true; } on FormatException { return false; @@ -27,7 +27,7 @@ bool isIpV6Address(String address) { /// Returns true if `address` is a valid IPv4 address. bool isIpV4Address(String address) { try { - InternetAddress(address, type:InternetAddressType.IPv4); + Uri.parseIPv4Address(address); return true; } on FormatException { return false; diff --git a/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart b/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart index 0cb71c9f07f..abcf999d4cb 100644 --- a/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart +++ b/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart @@ -103,13 +103,13 @@ class DartVmEvent { /// This class can be connected to several instances of the Fuchsia device's /// Dart VM at any given time. class FuchsiaRemoteConnection { - FuchsiaRemoteConnection._(this._useIpV6, this._sshCommandRunner) + FuchsiaRemoteConnection._(this._useIpV6Loopback, this._sshCommandRunner) : _pollDartVms = false; bool _pollDartVms; final List _forwardedVmServicePorts = []; final SshCommandRunner _sshCommandRunner; - final bool _useIpV6; + final bool _useIpV6Loopback; /// A mapping of Dart VM ports (as seen on the target machine), to /// [PortForwarder] instances mapping from the local machine to the target @@ -126,15 +126,15 @@ class FuchsiaRemoteConnection { StreamController(); /// VM service cache to avoid repeating handshakes across function - /// calls. Keys a URI to a DartVm connection instance. - final Map _dartVmCache = {}; + /// calls. Keys a forwarded port to a DartVm connection instance. + final Map _dartVmCache = {}; /// Same as [FuchsiaRemoteConnection.connect] albeit with a provided /// [SshCommandRunner] instance. static Future connectWithSshCommandRunner(SshCommandRunner commandRunner) async { final FuchsiaRemoteConnection connection = FuchsiaRemoteConnection._( isIpV6Address(commandRunner.address), commandRunner); - await connection._forwardOpenPortsToDeviceServicePorts(); + await connection._forwardLocalPortsToDeviceServicePorts(); Stream dartVmStream() { Future listen() async { @@ -224,16 +224,14 @@ class FuchsiaRemoteConnection { for (final PortForwarder pf in _forwardedVmServicePorts) { // Closes VM service first to ensure that the connection is closed cleanly // on the target before shutting down the forwarding itself. - final Uri uri = _getDartVmUri(pf); - final DartVm vmService = _dartVmCache[uri]; - _dartVmCache[uri] = null; + final DartVm vmService = _dartVmCache[pf.port]; + _dartVmCache[pf.port] = null; await vmService?.stop(); await pf.stop(); } for (final PortForwarder pf in _dartVmPortMap.values) { - final Uri uri = _getDartVmUri(pf); - final DartVm vmService = _dartVmCache[uri]; - _dartVmCache[uri] = null; + final DartVm vmService = _dartVmCache[pf.port]; + _dartVmCache[pf.port] = null; await vmService?.stop(); await pf.stop(); } @@ -260,8 +258,8 @@ class FuchsiaRemoteConnection { if (event.eventType == DartVmEventType.started) { _log.fine('New VM found on port: ${event.servicePort}. Searching ' 'for Isolate: $pattern'); - final DartVm vmService = await _getDartVm(event.uri, - timeout: _kDartVmConnectionTimeout); + final DartVm vmService = await _getDartVm(event.uri.port, + timeout: _kDartVmConnectionTimeout); // If the VM service is null, set the result to the empty list. final List result = await vmService ?.getMainIsolatesByPattern(pattern, timeout: timeout) ?? @@ -309,7 +307,7 @@ class FuchsiaRemoteConnection { >>[]; for (final PortForwarder fp in _dartVmPortMap.values) { final DartVm vmService = - await _getDartVm(_getDartVmUri(fp), timeout: vmConnectionTimeout); + await _getDartVm(fp.port, timeout: vmConnectionTimeout); if (vmService == null) { continue; } @@ -387,13 +385,13 @@ class FuchsiaRemoteConnection { _dartVmEventController.add(DartVmEvent._( eventType: DartVmEventType.stopped, servicePort: pf.remotePort, - uri: _getDartVmUri(pf), + uri: _getDartVmUri(pf.port), )); } } for (final PortForwarder pf in _dartVmPortMap.values) { - final DartVm service = await _getDartVm(_getDartVmUri(pf)); + final DartVm service = await _getDartVm(pf.port); if (service == null) { await shutDownPortForwarder(pf); } else { @@ -404,14 +402,15 @@ class FuchsiaRemoteConnection { return result; } - Uri _getDartVmUri(PortForwarder pf) { - String addr; - if (pf.openPortAddress == null) { - addr = _useIpV6 ? '[$_ipv6Loopback]' : _ipv4Loopback; - } else { - addr = _useIpV6 ? '[${pf.openPortAddress}]' : pf.openPortAddress; - } - final Uri uri = Uri.http('$addr:${pf.port}', '/'); + Uri _getDartVmUri(int port) { + // While the IPv4 loopback can be used for the initial port forwarding + // (see [PortForwarder.start]), the address is actually bound to the IPv6 + // loopback device, so connecting to the IPv4 loopback would fail when the + // target address is IPv6 link-local. + final String addr = _useIpV6Loopback + ? 'http://[$_ipv6Loopback]:$port' + : 'http://$_ipv4Loopback:$port'; + final Uri uri = Uri.parse(addr); return uri; } @@ -420,16 +419,17 @@ class FuchsiaRemoteConnection { /// Returns null if either there is an [HttpException] or a /// [TimeoutException], else a [DartVm] instance. Future _getDartVm( - Uri uri, { + int port, { Duration timeout = _kDartVmConnectionTimeout, }) async { - if (!_dartVmCache.containsKey(uri)) { + if (!_dartVmCache.containsKey(port)) { // When raising an HttpException this means that there is no instance of // the Dart VM to communicate with. The TimeoutException is raised when // the Dart VM instance is shut down in the middle of communicating. try { - final DartVm dartVm = await DartVm.connect(uri, timeout: timeout); - _dartVmCache[uri] = dartVm; + final DartVm dartVm = + await DartVm.connect(_getDartVmUri(port), timeout: timeout); + _dartVmCache[port] = dartVm; } on HttpException { _log.warning('HTTP Exception encountered connecting to new VM'); return null; @@ -438,7 +438,7 @@ class FuchsiaRemoteConnection { return null; } } - return _dartVmCache[uri]; + return _dartVmCache[port]; } /// Checks for changes in the list of Dart VM instances. @@ -460,7 +460,7 @@ class FuchsiaRemoteConnection { _dartVmEventController.add(DartVmEvent._( eventType: DartVmEventType.started, servicePort: servicePort, - uri: _getDartVmUri(_dartVmPortMap[servicePort]), + uri: _getDartVmUri(_dartVmPortMap[servicePort].port), )); } } @@ -482,11 +482,11 @@ class FuchsiaRemoteConnection { ); } - /// Forwards a series of open ports to the remote device. + /// Forwards a series of local device ports to the remote device. /// /// When this function is run, all existing forwarded ports and connections /// are reset by way of [stop]. - Future _forwardOpenPortsToDeviceServicePorts() async { + Future _forwardLocalPortsToDeviceServicePorts() async { await stop(); final List servicePorts = await getDeviceServicePorts(); final List forwardedVmServicePorts = @@ -548,13 +548,9 @@ class FuchsiaRemoteConnection { /// /// To shut down a port forwarder you must call the [stop] function. abstract class PortForwarder { - /// Determines the port which is being forwarded. + /// Determines the port which is being forwarded from the local machine. int get port; - /// The address on which the open port is accessible. Defaults to null to - /// indicate local loopback. - String get openPortAddress => null; - /// The destination port on the other end of the port forwarding tunnel. int get remotePort; @@ -585,9 +581,6 @@ class _SshPortForwarder implements PortForwarder { @override int get port => _localSocket.port; - @override - String get openPortAddress => _ipV6 ? _ipv6Loopback : _ipv4Loopback; - @override int get remotePort => _remotePort; @@ -609,9 +602,8 @@ class _SshPortForwarder implements PortForwarder { // TODO(awdavies): The square-bracket enclosure for using the IPv6 loopback // didn't appear to work, but when assigning to the IPv4 loopback device, // netstat shows that the local port is actually being used on the IPv6 - // loopback (::1). Therefore, while the IPv4 loopback can be used for - // forwarding to the destination IPv6 interface, when connecting to the - // websocket, the IPV6 loopback should be used. + // loopback (::1). While this can be used for forwarding to the destination + // IPv6 interface, it cannot be used to connect to a websocket. final String formattedForwardingUrl = '${localSocket.port}:$_ipv4Loopback:$remotePort'; final String targetAddress = diff --git a/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart b/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart index 54923382af3..833d79b2bd3 100644 --- a/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart +++ b/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart @@ -31,6 +31,23 @@ void main() { const String interface = 'eno1'; when(mockRunner.address).thenReturn(address); when(mockRunner.interface).thenReturn(interface); + forwardedPorts = []; + int port = 0; + Future mockPortForwardingFunction( + String address, + int remotePort, [ + String interface = '', + String configFile, + ]) { + return Future(() { + final MockPortForwarder pf = MockPortForwarder(); + forwardedPorts.add(pf); + when(pf.port).thenReturn(port++); + when(pf.remotePort).thenReturn(remotePort); + return pf; + }); + } + final List> flutterViewCannedResponses = >[ { @@ -73,7 +90,6 @@ void main() { }, ]; - forwardedPorts = []; mockPeerConnections = []; uriConnections = []; Future mockVmConnectionFunction( @@ -93,6 +109,7 @@ void main() { }); } + fuchsiaPortForwardingFunction = mockPortForwardingFunction; fuchsiaVmServiceConnectionFunction = mockVmConnectionFunction; }); @@ -104,24 +121,6 @@ void main() { }); test('end-to-end with three vm connections and flutter view query', () async { - int port = 0; - Future mockPortForwardingFunction( - String address, - int remotePort, [ - String interface = '', - String configFile, - ]) { - return Future(() { - final MockPortForwarder pf = MockPortForwarder(); - forwardedPorts.add(pf); - when(pf.port).thenReturn(port++); - when(pf.remotePort).thenReturn(remotePort); - return pf; - }); - } - - fuchsiaPortForwardingFunction = mockPortForwardingFunction; - final FuchsiaRemoteConnection connection = await FuchsiaRemoteConnection.connectWithSshCommandRunner(mockRunner); @@ -136,77 +135,6 @@ void main() { expect(forwardedPorts[1].port, 1); expect(forwardedPorts[2].port, 2); - // VMs should be accessed via localhost ports given by - // [mockPortForwardingFunction]. - expect(uriConnections[0], - Uri(scheme:'ws', host:'[::1]', port:0, path:'/ws')); - expect(uriConnections[1], - Uri(scheme:'ws', host:'[::1]', port:1, path:'/ws')); - expect(uriConnections[2], - Uri(scheme:'ws', host:'[::1]', port:2, path:'/ws')); - - final List views = await connection.getFlutterViews(); - expect(views, isNot(null)); - expect(views.length, 3); - // Since name can be null, check for the ID on all of them. - expect(views[0].id, 'flutterView0'); - expect(views[1].id, 'flutterView1'); - expect(views[2].id, 'flutterView2'); - - expect(views[0].name, equals(null)); - expect(views[1].name, 'file://flutterBinary1'); - expect(views[2].name, 'file://flutterBinary2'); - - // Ensure the ports are all closed after stop was called. - await connection.stop(); - verify(forwardedPorts[0].stop()); - verify(forwardedPorts[1].stop()); - verify(forwardedPorts[2].stop()); - }); - - test('end-to-end with three vms and remote open port', () async { - int port = 0; - Future mockPortForwardingFunction( - String address, - int remotePort, [ - String interface = '', - String configFile, - ]) { - return Future(() { - final MockPortForwarder pf = MockPortForwarder(); - forwardedPorts.add(pf); - when(pf.port).thenReturn(port++); - when(pf.remotePort).thenReturn(remotePort); - when(pf.openPortAddress).thenReturn('fe80::1:2%eno2'); - return pf; - }); - } - - fuchsiaPortForwardingFunction = mockPortForwardingFunction; - - final FuchsiaRemoteConnection connection = - await FuchsiaRemoteConnection.connectWithSshCommandRunner(mockRunner); - - // [mockPortForwardingFunction] will have returned three different - // forwarded ports, incrementing the port each time by one. (Just a sanity - // check that the forwarding port was called). - expect(forwardedPorts.length, 3); - expect(forwardedPorts[0].remotePort, 123); - expect(forwardedPorts[1].remotePort, 456); - expect(forwardedPorts[2].remotePort, 789); - expect(forwardedPorts[0].port, 0); - expect(forwardedPorts[1].port, 1); - expect(forwardedPorts[2].port, 2); - - // VMs should be accessed via the alternate adddress given by - // [mockPortForwardingFunction]. - expect(uriConnections[0], - Uri(scheme:'ws', host:'[fe80::1:2%25eno2]', port:0, path:'/ws')); - expect(uriConnections[1], - Uri(scheme:'ws', host:'[fe80::1:2%25eno2]', port:1, path:'/ws')); - expect(uriConnections[2], - Uri(scheme:'ws', host:'[fe80::1:2%25eno2]', port:2, path:'/ws')); - final List views = await connection.getFlutterViews(); expect(views, isNot(null)); expect(views.length, 3);