Unverified Commit bf41c782 authored by Satsuki Ueno's avatar Satsuki Ueno Committed by GitHub

Reland fuchsia_remote_debug_protocol allows open port on remote device (#66271)

parent 82bd7cf8
......@@ -24,6 +24,9 @@ class _DummyPortForwarder implements PortForwarder {
@override
int get remotePort => _remotePort;
@override
String get openPortAddress => InternetAddress.loopbackIPv4.address;
@override
Future<void> stop() async { }
}
......
......@@ -17,7 +17,10 @@ void validateAddress(String address) {
/// Returns true if `address` is a valid IPv6 address.
bool isIpV6Address(String address) {
try {
Uri.parseIPv6Address(address);
// parseIpv6Address fails if there's a zone ID. Since this is still a valid
// IP, remove any zone ID before parsing.
final List<String> addressParts = address.split('%');
Uri.parseIPv6Address(addressParts[0]);
return true;
} on FormatException {
return false;
......
......@@ -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._useIpV6Loopback, this._sshCommandRunner)
FuchsiaRemoteConnection._(this._useIpV6, this._sshCommandRunner)
: _pollDartVms = false;
bool _pollDartVms;
final List<PortForwarder> _forwardedVmServicePorts = <PortForwarder>[];
final SshCommandRunner _sshCommandRunner;
final bool _useIpV6Loopback;
final bool _useIpV6;
/// 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<DartVmEvent>();
/// VM service cache to avoid repeating handshakes across function
/// calls. Keys a forwarded port to a DartVm connection instance.
final Map<int, DartVm> _dartVmCache = <int, DartVm>{};
/// calls. Keys a URI to a DartVm connection instance.
final Map<Uri, DartVm> _dartVmCache = <Uri, DartVm>{};
/// Same as [FuchsiaRemoteConnection.connect] albeit with a provided
/// [SshCommandRunner] instance.
static Future<FuchsiaRemoteConnection> connectWithSshCommandRunner(SshCommandRunner commandRunner) async {
final FuchsiaRemoteConnection connection = FuchsiaRemoteConnection._(
isIpV6Address(commandRunner.address), commandRunner);
await connection._forwardLocalPortsToDeviceServicePorts();
await connection._forwardOpenPortsToDeviceServicePorts();
Stream<DartVmEvent> dartVmStream() {
Future<void> listen() async {
......@@ -224,14 +224,16 @@ 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 DartVm vmService = _dartVmCache[pf.port];
_dartVmCache[pf.port] = null;
final Uri uri = _getDartVmUri(pf);
final DartVm vmService = _dartVmCache[uri];
_dartVmCache[uri] = null;
await vmService?.stop();
await pf.stop();
}
for (final PortForwarder pf in _dartVmPortMap.values) {
final DartVm vmService = _dartVmCache[pf.port];
_dartVmCache[pf.port] = null;
final Uri uri = _getDartVmUri(pf);
final DartVm vmService = _dartVmCache[uri];
_dartVmCache[uri] = null;
await vmService?.stop();
await pf.stop();
}
......@@ -258,8 +260,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.port,
timeout: _kDartVmConnectionTimeout);
final DartVm vmService = await _getDartVm(event.uri,
timeout: _kDartVmConnectionTimeout);
// If the VM service is null, set the result to the empty list.
final List<IsolateRef> result = await vmService
?.getMainIsolatesByPattern(pattern, timeout: timeout) ??
......@@ -307,7 +309,7 @@ class FuchsiaRemoteConnection {
<Future<List<IsolateRef>>>[];
for (final PortForwarder fp in _dartVmPortMap.values) {
final DartVm vmService =
await _getDartVm(fp.port, timeout: vmConnectionTimeout);
await _getDartVm(_getDartVmUri(fp), timeout: vmConnectionTimeout);
if (vmService == null) {
continue;
}
......@@ -385,13 +387,13 @@ class FuchsiaRemoteConnection {
_dartVmEventController.add(DartVmEvent._(
eventType: DartVmEventType.stopped,
servicePort: pf.remotePort,
uri: _getDartVmUri(pf.port),
uri: _getDartVmUri(pf),
));
}
}
for (final PortForwarder pf in _dartVmPortMap.values) {
final DartVm service = await _getDartVm(pf.port);
final DartVm service = await _getDartVm(_getDartVmUri(pf));
if (service == null) {
await shutDownPortForwarder(pf);
} else {
......@@ -402,15 +404,16 @@ class FuchsiaRemoteConnection {
return result;
}
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);
Uri _getDartVmUri(PortForwarder pf) {
String addr;
if (pf.openPortAddress == null) {
addr = _useIpV6 ? '[$_ipv6Loopback]' : _ipv4Loopback;
} else {
addr = isIpV6Address(pf.openPortAddress)
? '[${pf.openPortAddress}]'
: pf.openPortAddress;
}
final Uri uri = Uri.http('$addr:${pf.port}', '/');
return uri;
}
......@@ -419,17 +422,16 @@ class FuchsiaRemoteConnection {
/// Returns null if either there is an [HttpException] or a
/// [TimeoutException], else a [DartVm] instance.
Future<DartVm> _getDartVm(
int port, {
Uri uri, {
Duration timeout = _kDartVmConnectionTimeout,
}) async {
if (!_dartVmCache.containsKey(port)) {
if (!_dartVmCache.containsKey(uri)) {
// 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(_getDartVmUri(port), timeout: timeout);
_dartVmCache[port] = dartVm;
final DartVm dartVm = await DartVm.connect(uri, timeout: timeout);
_dartVmCache[uri] = dartVm;
} on HttpException {
_log.warning('HTTP Exception encountered connecting to new VM');
return null;
......@@ -438,7 +440,7 @@ class FuchsiaRemoteConnection {
return null;
}
}
return _dartVmCache[port];
return _dartVmCache[uri];
}
/// Checks for changes in the list of Dart VM instances.
......@@ -460,7 +462,7 @@ class FuchsiaRemoteConnection {
_dartVmEventController.add(DartVmEvent._(
eventType: DartVmEventType.started,
servicePort: servicePort,
uri: _getDartVmUri(_dartVmPortMap[servicePort].port),
uri: _getDartVmUri(_dartVmPortMap[servicePort]),
));
}
}
......@@ -482,11 +484,11 @@ class FuchsiaRemoteConnection {
);
}
/// Forwards a series of local device ports to the remote device.
/// Forwards a series of open ports to the remote device.
///
/// When this function is run, all existing forwarded ports and connections
/// are reset by way of [stop].
Future<void> _forwardLocalPortsToDeviceServicePorts() async {
Future<void> _forwardOpenPortsToDeviceServicePorts() async {
await stop();
final List<int> servicePorts = await getDeviceServicePorts();
final List<PortForwarder> forwardedVmServicePorts =
......@@ -548,9 +550,13 @@ class FuchsiaRemoteConnection {
///
/// To shut down a port forwarder you must call the [stop] function.
abstract class PortForwarder {
/// Determines the port which is being forwarded from the local machine.
/// Determines the port which is being forwarded.
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;
......@@ -581,6 +587,9 @@ class _SshPortForwarder implements PortForwarder {
@override
int get port => _localSocket.port;
@override
String get openPortAddress => _ipV6 ? _ipv6Loopback : _ipv4Loopback;
@override
int get remotePort => _remotePort;
......@@ -602,8 +611,9 @@ 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). While this can be used for forwarding to the destination
// IPv6 interface, it cannot be used to connect to a websocket.
// 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.
final String formattedForwardingUrl =
'${localSocket.port}:$_ipv4Loopback:$remotePort';
final String targetAddress =
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:fuchsia_remote_debug_protocol/src/common/network.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
final List<String> ipv4Addresses = <String>['127.0.0.1', '8.8.8.8'];
final List<String> ipv6Addresses = <String>['::1',
'fe80::8eae:4cff:fef4:9247', 'fe80::8eae:4cff:fef4:9247%e0'];
group('test validation', () {
test('isIpV4Address', () {
expect(ipv4Addresses.map(isIpV4Address), everyElement(isTrue));
expect(ipv6Addresses.map(isIpV4Address), everyElement(isFalse));
});
test('isIpV6Address', () {
expect(ipv4Addresses.map(isIpV6Address), everyElement(isFalse));
expect(ipv6Addresses.map(isIpV6Address), everyElement(isTrue));
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment