Unverified Commit f8b1de3a authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Add publish-port flag to disable mDNS port discovery (#67452)

parent e1b4142a
......@@ -55,6 +55,11 @@ class DriveCommand extends RunCommandBase {
}) {
requiresPubspecYaml();
addEnableExperimentation(hide: !verboseHelp);
// By default, the drive app should not publish the VM service port over mDNS
// to prevent a local network permission dialog on iOS 14+,
// which cannot be accepted or dismissed in a CI environment.
addPublishPort(enabledByDefault: false, verboseHelp: verboseHelp);
argParser
..addFlag('keep-app-running',
defaultsTo: null,
......@@ -215,7 +220,8 @@ class DriveCommand extends RunCommandBase {
)
: DebuggingOptions.enabled(
getBuildInfo(),
port: stringArg('web-port')
port: stringArg('web-port'),
disablePortPublication: disablePortPublication,
),
stayResident: false,
urlTunneller: null,
......@@ -501,6 +507,7 @@ Future<LaunchResult> _startApp(
command.getBuildInfo(),
startPaused: true,
hostVmServicePort: webUri != null ? command.hostVmservicePort : 0,
disablePortPublication: command.disablePortPublication,
ddsPort: command.ddsPort,
verboseSystemLogs: command.verboseSystemLogs,
cacheSkSL: command.cacheSkSL,
......
......@@ -97,6 +97,11 @@ class RunCommand extends RunCommandBase {
usesFilesystemOptions(hide: !verboseHelp);
usesExtraFrontendOptions();
addEnableExperimentation(hide: !verboseHelp);
// By default, the app should to publish the VM service port over mDNS.
// This will allow subsequent "flutter attach" commands to connect to the VM
// without needing to know the port.
addPublishPort(enabledByDefault: true, verboseHelp: verboseHelp);
argParser
..addFlag('start-paused',
negatable: false,
......@@ -407,6 +412,7 @@ class RunCommand extends RunCommandBase {
purgePersistentCache: purgePersistentCache,
deviceVmServicePort: deviceVmservicePort,
hostVmServicePort: hostVmservicePort,
disablePortPublication: disablePortPublication,
ddsPort: ddsPort,
verboseSystemLogs: boolArg('verbose-system-logs'),
initializePlatform: boolArg('web-initialize-platform'),
......
......@@ -813,6 +813,7 @@ class DebuggingOptions {
this.useTestFonts = false,
this.verboseSystemLogs = false,
this.hostVmServicePort,
this.disablePortPublication = false,
this.deviceVmServicePort,
this.ddsPort,
this.initializePlatform = true,
......@@ -855,6 +856,7 @@ class DebuggingOptions {
purgePersistentCache = false,
verboseSystemLogs = false,
hostVmServicePort = null,
disablePortPublication = false,
deviceVmServicePort = null,
ddsPort = null,
vmserviceOutFile = null,
......@@ -884,6 +886,7 @@ class DebuggingOptions {
final bool initializePlatform;
final int hostVmServicePort;
final int deviceVmServicePort;
final bool disablePortPublication;
final int ddsPort;
final String port;
final String hostname;
......
......@@ -22,7 +22,6 @@ import '../convert.dart';
import '../device.dart';
import '../globals.dart' as globals;
import '../macos/xcode.dart';
import '../mdns_discovery.dart';
import '../project.dart';
import '../protocol_discovery.dart';
import '../vmservice.dart';
......@@ -369,6 +368,7 @@ class IOSDevice extends Device {
'--enable-service-port-fallback',
'--disable-service-auth-codes',
'--observatory-port=$assumedObservatoryPort',
if (debuggingOptions.disablePortPublication) '--disable-observatory-publication',
if (debuggingOptions.startPaused) '--start-paused',
if (dartVmFlags.isNotEmpty) '--dart-flags="$dartVmFlags"',
if (debuggingOptions.useTestFonts) '--use-test-fonts',
......@@ -453,7 +453,6 @@ class IOSDevice extends Device {
_logger.printTrace('Application launched on the device. Waiting for observatory port.');
final FallbackDiscovery fallbackDiscovery = FallbackDiscovery(
logger: _logger,
mDnsObservatoryDiscovery: MDnsObservatoryDiscovery.instance,
portForwarder: portForwarder,
protocolDiscovery: observatoryDiscovery,
flutterUsage: globals.flutterUsage,
......
......@@ -8,7 +8,6 @@ import 'package:vm_service/vm_service.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../device.dart';
import '../mdns_discovery.dart';
import '../protocol_discovery.dart';
import '../reporting/reporting.dart';
......@@ -17,36 +16,23 @@ typedef VmServiceConnector = Future<VmService> Function(String, {Log log});
/// A protocol for discovery of a vmservice on an attached iOS device with
/// multiple fallbacks.
///
/// On versions of iOS 13 and greater, libimobiledevice can no longer listen to
/// logs directly. The only way to discover an active observatory is through the
/// mDNS protocol. However, there are a number of circumstances where this breaks
/// down, such as when the device is connected to certain wifi networks or with
/// certain hotspot connections enabled.
///
/// Another approach to discover a vmservice is to attempt to assign a
/// First, it tries to discover a vmservice by assigning a
/// specific port and then attempt to connect. This may fail if the port is
/// not available. This port value should be either random, or otherwise
/// generated with application specific input. This reduces the chance of
/// accidentally connecting to another running flutter application.
///
/// Finally, if neither of the above approaches works, we can still attempt
/// to parse logs.
///
/// To improve the overall resilience of the process, this class combines the
/// three discovery strategies. First it assigns a port and attempts to connect.
/// Then if this fails it falls back to mDNS, then finally attempting to scan
/// logs.
/// If that does not work, attempt to scan logs from the attached debugger
/// and parse the connected port logged by the engine.
class FallbackDiscovery {
FallbackDiscovery({
@required DevicePortForwarder portForwarder,
@required MDnsObservatoryDiscovery mDnsObservatoryDiscovery,
@required Logger logger,
@required ProtocolDiscovery protocolDiscovery,
@required Usage flutterUsage,
@required VmServiceConnector vmServiceConnectUri,
Duration pollingDelay,
}) : _logger = logger,
_mDnsObservatoryDiscovery = mDnsObservatoryDiscovery,
_portForwarder = portForwarder,
_protocolDiscovery = protocolDiscovery,
_flutterUsage = flutterUsage,
......@@ -56,7 +42,6 @@ class FallbackDiscovery {
static const String _kEventName = 'ios-handshake';
final DevicePortForwarder _portForwarder;
final MDnsObservatoryDiscovery _mDnsObservatoryDiscovery;
final Logger _logger;
final ProtocolDiscovery _protocolDiscovery;
final Usage _flutterUsage;
......@@ -97,37 +82,13 @@ class FallbackDiscovery {
} on Exception catch (err) {
_logger.printTrace(err.toString());
}
_logger.printTrace('Failed to connect with log scanning, falling back to mDNS');
_logger.printTrace('Failed to connect with log scanning');
UsageEvent(
_kEventName,
'log-failure',
flutterUsage: _flutterUsage,
).send();
try {
final Uri result = await _mDnsObservatoryDiscovery.getObservatoryUri(
packageId,
device,
usesIpv6: usesIpv6,
hostVmservicePort: hostVmservicePort,
);
if (result != null) {
UsageEvent(
_kEventName,
'mdns-success',
flutterUsage: _flutterUsage,
).send();
return result;
}
} on Exception catch (err) {
_logger.printTrace(err.toString());
}
_logger.printTrace('Failed to connect with mDNS');
UsageEvent(
_kEventName,
'mdns-failure',
flutterUsage: _flutterUsage,
).send();
return null;
}
......@@ -200,7 +161,7 @@ class FallbackDiscovery {
await Future<void>.delayed(_pollingDelay);
attempts += 1;
}
_logger.printTrace('Failed to connect directly, falling back to mDNS');
_logger.printTrace('Failed to connect directly, falling back to log scanning');
_sendFailureEvent(firstException, assumedDevicePort);
return null;
}
......
......@@ -51,6 +51,7 @@ class MDnsObservatoryDiscovery {
/// it will return that instance's information regardless of what application
/// the Observatory instance is for.
// TODO(jonahwilliams): use `deviceVmservicePort` to filter mdns results.
@visibleForTesting
Future<MDnsObservatoryDiscoveryResult> query({String applicationId, int deviceVmservicePort}) async {
globals.printTrace('Checking for advertised Dart observatories...');
try {
......
......@@ -369,6 +369,17 @@ abstract class FlutterCommand extends Command<void> {
return null;
}
void addPublishPort({ bool enabledByDefault = true, bool verboseHelp = false }) {
argParser.addFlag('publish-port',
negatable: true,
hide: !verboseHelp,
help: 'Publish the VM service port over mDNS. Disable to prevent the'
'local network permission app dialog in debug and profile build modes (iOS devices only.)',
defaultsTo: enabledByDefault);
}
bool get disablePortPublication => !boolArg('publish-port');
void usesIpv6Flag() {
argParser.addFlag(ipv6Flag,
hide: true,
......
......@@ -708,6 +708,11 @@ void main() {
'--purge-persistent-cache',
() => debuggingOptions.purgePersistentCache,
);
testOptionThatDefaultsToFalse(
'--publish-port',
() => !debuggingOptions.disablePortPublication,
);
});
});
......
......@@ -7,7 +7,6 @@ import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/fallback_discovery.dart';
import 'package:flutter_tools/src/mdns_discovery.dart';
import 'package:flutter_tools/src/protocol_discovery.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart';
......@@ -19,7 +18,6 @@ import '../../src/mocks.dart';
void main() {
BufferLogger logger;
FallbackDiscovery fallbackDiscovery;
MockMDnsObservatoryDiscovery mockMDnsObservatoryDiscovery;
MockPrototcolDiscovery mockPrototcolDiscovery;
MockPortForwarder mockPortForwarder;
MockVmService mockVmService;
......@@ -30,12 +28,10 @@ void main() {
outputPreferences: OutputPreferences.test(),
);
mockVmService = MockVmService();
mockMDnsObservatoryDiscovery = MockMDnsObservatoryDiscovery();
mockPrototcolDiscovery = MockPrototcolDiscovery();
mockPortForwarder = MockPortForwarder();
fallbackDiscovery = FallbackDiscovery(
logger: logger,
mDnsObservatoryDiscovery: mockMDnsObservatoryDiscovery,
portForwarder: mockPortForwarder,
protocolDiscovery: mockPrototcolDiscovery,
flutterUsage: Usage.test(),
......@@ -94,7 +90,7 @@ void main() {
), Uri.parse('http://localhost:1'));
});
testWithoutContext('Selects mdns discovery if VM service connecton fails due to Sentinel', () async {
testWithoutContext('Selects log scanning if VM service connecton fails due to Sentinel', () async {
when(mockVmService.getVM()).thenAnswer((Invocation invocation) async {
return VM.parse(<String, Object>{})..isolates = <IsolateRef>[
IsolateRef(
......@@ -106,12 +102,7 @@ void main() {
});
when(mockVmService.getIsolate(any))
.thenThrow(SentinelException.parse('Something', <String, dynamic>{}));
when(mockMDnsObservatoryDiscovery.getObservatoryUri(
'hello',
null, // Device
usesIpv6: false,
hostVmservicePort: 1,
)).thenAnswer((Invocation invocation) async {
when(mockPrototcolDiscovery.uri).thenAnswer((Invocation invocation) async {
return Uri.parse('http://localhost:1234');
});
......@@ -125,15 +116,10 @@ void main() {
), Uri.parse('http://localhost:1234'));
});
testWithoutContext('Selects mdns discovery if VM service connecton fails', () async {
testWithoutContext('Selects log scanning if VM service connecton fails', () async {
when(mockVmService.getVM()).thenThrow(Exception());
when(mockMDnsObservatoryDiscovery.getObservatoryUri(
'hello',
null, // Device
usesIpv6: false,
hostVmservicePort: 1,
)).thenAnswer((Invocation invocation) async {
when(mockPrototcolDiscovery.uri).thenAnswer((Invocation invocation) async {
return Uri.parse('http://localhost:1234');
});
......@@ -147,17 +133,9 @@ void main() {
), Uri.parse('http://localhost:1234'));
});
testWithoutContext('Selects log scanning if both VM Service and mDNS fails', () async {
testWithoutContext('Fails if both VM Service and log scanning fails', () async {
when(mockVmService.getVM()).thenThrow(Exception());
when(mockMDnsObservatoryDiscovery.getObservatoryUri(
'hello',
null, // Device
usesIpv6: false,
hostVmservicePort: 1,
)).thenThrow(Exception());
when(mockPrototcolDiscovery.uri).thenAnswer((Invocation invocation) async {
return Uri.parse('http://localhost:5678');
});
when(mockPrototcolDiscovery.uri).thenAnswer((Invocation invocation) async => null);
expect(await fallbackDiscovery.discover(
assumedDevicePort: 23,
......@@ -166,11 +144,10 @@ void main() {
packageId: 'hello',
usesIpv6: false,
packageName: 'hello',
), Uri.parse('http://localhost:5678'));
), isNull);
});
}
class MockMDnsObservatoryDiscovery extends Mock implements MDnsObservatoryDiscovery {}
class MockPrototcolDiscovery extends Mock implements ProtocolDiscovery {}
class MockPortForwarder extends Mock implements DevicePortForwarder {}
class MockVmService extends Mock implements VmService {}
......@@ -21,7 +21,6 @@ import 'package:flutter_tools/src/ios/fallback_discovery.dart';
import 'package:flutter_tools/src/ios/ios_deploy.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/mdns_discovery.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart';
import 'package:vm_service/vm_service.dart';
......@@ -123,63 +122,7 @@ void main() {
verify(devicePortForwarder.dispose()).called(1);
});
// Still uses context for analytics and mDNS.
testUsingContext('IOSDevice.startApp succeeds in debug mode via mDNS discovery when log reading fails', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
kDeployCommand,
kAttachDebuggerCommand,
]);
final IOSDevice device = setUpIOSDevice(
processManager: processManager,
fileSystem: fileSystem,
vmServiceConnector: (String string, {Log log}) async {
throw const io.SocketException(
'OS Error: Connection refused, errno = 61, address = localhost, port '
'= 58943',
);
},
);
final IOSApp iosApp = PrebuiltIOSApp(
projectBundleId: 'app',
bundleName: 'Runner',
bundleDir: fileSystem.currentDirectory,
);
final Uri uri = Uri(
scheme: 'http',
host: '127.0.0.1',
port: 1234,
path: 'observatory',
);
device.portForwarder = const NoOpDevicePortForwarder();
device.setLogReader(iosApp, FakeDeviceLogReader());
when(MDnsObservatoryDiscovery.instance.getObservatoryUri(
any,
any,
usesIpv6: anyNamed('usesIpv6'),
hostVmservicePort: anyNamed('hostVmservicePort')
)).thenAnswer((Invocation invocation) async => uri);
final LaunchResult launchResult = await device.startApp(iosApp,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
platformArgs: <String, dynamic>{},
fallbackPollingDelay: Duration.zero,
fallbackThrottleTimeout: const Duration(milliseconds: 10),
);
verify(globals.flutterUsage.sendEvent('ios-handshake', 'mdns-success')).called(1);
expect(launchResult.started, true);
expect(launchResult.hasObservatory, true);
expect(await device.stopApp(iosApp), false);
}, overrides: <Type, Generator>{
MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(),
Usage: () => MockUsage(),
});
// Still uses context for analytics and mDNS.
// Still uses context for analytics.
testUsingContext('IOSDevice.startApp attaches in debug mode via log reading on iOS 13+', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
......@@ -223,14 +166,12 @@ void main() {
expect(launchResult.started, true);
expect(launchResult.hasObservatory, true);
verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-success')).called(1);
verifyNever(globals.flutterUsage.sendEvent('ios-handshake', 'mdns-failure'));
expect(await device.stopApp(iosApp), false);
}, overrides: <Type, Generator>{
MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(),
Usage: () => MockUsage(),
});
// Still uses context for analytics and mDNS.
// Still uses context for analytics.
testUsingContext('IOSDevice.startApp launches in debug mode via log reading on <iOS 13', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
......@@ -275,14 +216,12 @@ void main() {
expect(launchResult.started, true);
expect(launchResult.hasObservatory, true);
verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-success')).called(1);
verifyNever(globals.flutterUsage.sendEvent('ios-handshake', 'mdns-failure'));
expect(await device.stopApp(iosApp), false);
}, overrides: <Type, Generator>{
MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(),
Usage: () => MockUsage(),
});
// Still uses context for analytics and mDNS.
// Still uses context for analytics.
testUsingContext('IOSDevice.startApp fails in debug mode when Observatory URI is malformed', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
......@@ -332,9 +271,7 @@ void main() {
value: anyNamed('value'),
)).called(1);
verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-failure')).called(1);
verify(globals.flutterUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1);
}, overrides: <Type, Generator>{
MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(),
Usage: () => MockUsage(),
});
......@@ -371,7 +308,7 @@ void main() {
Usage: () => MockUsage(),
});
// Still uses context for analytics and mDNS.
// Still uses context for analytics.
testUsingContext('IOSDevice.startApp forwards all supported debugging options', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
......@@ -397,6 +334,7 @@ void main() {
'--enable-service-port-fallback',
'--disable-service-auth-codes',
'--observatory-port=60700',
'--disable-observatory-publication',
'--start-paused',
'--dart-flags="--foo,--null_assertions"',
'--enable-checked-mode',
......@@ -449,6 +387,7 @@ void main() {
BuildInfo.debug,
startPaused: true,
disableServiceAuthCodes: true,
disablePortPublication: true,
dartFlags: '--foo',
enableSoftwareRendering: true,
skiaDeterministicRendering: true,
......@@ -470,7 +409,6 @@ void main() {
expect(await device.stopApp(iosApp), false);
expect(processManager.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(),
Usage: () => MockUsage(),
});
}
......@@ -525,6 +463,5 @@ IOSDevice setUpIOSDevice({
class MockDevicePortForwarder extends Mock implements DevicePortForwarder {}
class MockDeviceLogReader extends Mock implements DeviceLogReader {}
class MockUsage extends Mock implements Usage {}
class MockMDnsObservatoryDiscovery extends Mock implements MDnsObservatoryDiscovery {}
class MockVmService extends Mock implements VmService {}
class MockDartDevelopmentService extends Mock implements DartDevelopmentService {}
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