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

diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart
index f3c520f..7fa8e81 100644
--- a/packages/flutter_tools/lib/src/commands/drive.dart
+++ b/packages/flutter_tools/lib/src/commands/drive.dart
@@ -55,6 +55,11 @@
   }) {
     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 @@
             )
             : DebuggingOptions.enabled(
               getBuildInfo(),
-              port: stringArg('web-port')
+              port: stringArg('web-port'),
+              disablePortPublication: disablePortPublication,
             ),
           stayResident: false,
           urlTunneller: null,
@@ -501,6 +507,7 @@
       command.getBuildInfo(),
       startPaused: true,
       hostVmServicePort: webUri != null ? command.hostVmservicePort : 0,
+      disablePortPublication: command.disablePortPublication,
       ddsPort: command.ddsPort,
       verboseSystemLogs: command.verboseSystemLogs,
       cacheSkSL: command.cacheSkSL,
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 63c00d2..092abe1 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -97,6 +97,11 @@
     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 @@
         purgePersistentCache: purgePersistentCache,
         deviceVmServicePort: deviceVmservicePort,
         hostVmServicePort: hostVmservicePort,
+        disablePortPublication: disablePortPublication,
         ddsPort: ddsPort,
         verboseSystemLogs: boolArg('verbose-system-logs'),
         initializePlatform: boolArg('web-initialize-platform'),
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 769b0ee..92d4ebf 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -813,6 +813,7 @@
     this.useTestFonts = false,
     this.verboseSystemLogs = false,
     this.hostVmServicePort,
+    this.disablePortPublication = false,
     this.deviceVmServicePort,
     this.ddsPort,
     this.initializePlatform = true,
@@ -855,6 +856,7 @@
       purgePersistentCache = false,
       verboseSystemLogs = false,
       hostVmServicePort = null,
+      disablePortPublication = false,
       deviceVmServicePort = null,
       ddsPort = null,
       vmserviceOutFile = null,
@@ -884,6 +886,7 @@
   final bool initializePlatform;
   final int hostVmServicePort;
   final int deviceVmServicePort;
+  final bool disablePortPublication;
   final int ddsPort;
   final String port;
   final String hostname;
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index a7b0abd..e8e053b 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -22,7 +22,6 @@
 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 @@
       '--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 @@
       _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,
diff --git a/packages/flutter_tools/lib/src/ios/fallback_discovery.dart b/packages/flutter_tools/lib/src/ios/fallback_discovery.dart
index b321114..127c0fc 100644
--- a/packages/flutter_tools/lib/src/ios/fallback_discovery.dart
+++ b/packages/flutter_tools/lib/src/ios/fallback_discovery.dart
@@ -8,7 +8,6 @@
 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 @@
 /// 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 @@
   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 @@
     } 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 @@
       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;
   }
diff --git a/packages/flutter_tools/lib/src/mdns_discovery.dart b/packages/flutter_tools/lib/src/mdns_discovery.dart
index 3375e8d..ac76abd 100644
--- a/packages/flutter_tools/lib/src/mdns_discovery.dart
+++ b/packages/flutter_tools/lib/src/mdns_discovery.dart
@@ -51,6 +51,7 @@
   /// 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 {
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 1e3ab0f..7003262 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -369,6 +369,17 @@
     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,