[CP-beta]Adds a new helpful tool exit message for SocketExceptions thrown during mdns discovery (#158950)

This pull request is created by [automatic cherry pick
workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request)
Please fill in the form below, and a flutter domain expert will evaluate
this cherry pick request.

### Issue Link:
What is the link to the issue this cherry-pick is addressing?

https://github.com/flutter/flutter/issues/150131

### Changelog Description:
Explain this cherry pick in one line that is accessible to most Flutter
developers. See [best
practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md)
for examples

Suggest macOS Sequoia Local Network permissions instead of CLI tool
SocketException crash.

### Impact Description:
What is the impact (ex. visual jank on Samsung phones, app crash, cannot
ship an iOS app)? Does it impact development (ex. flutter doctor crashes
when Android Studio is installed), or the shipping production app (the
app crashes on launch)

The app fatally crashes on `flutter run ios` with a non-actionable
error.

### Workaround:
Is there a workaround for this issue?

The potential workarounds are listed in the error message added by this
PR.

### Risk:
What is the risk level of this cherry-pick?

  - [x] Low
  - [ ] Medium
  - [ ] High

### Test Coverage:
Are you confident that your fix is well-tested by automated tests?

  - [x] Yes
  - [ ] No

### Validation Steps:
What are the steps to validate that this fix works?

It's hard to validate this PR because the original issue is hard to
reproduce, but you can manually add a socket exception here
https://github.com/flutter/flutter/blob/4b818b56c2607ed0f952db48613e0704b77a238f/packages/flutter_tools/lib/src/mdns_discovery.dart#L235-L239
and `flutter run ios`, and inspect the output.

Co-authored-by: LouiseHsu <louisehsu@google.com>
diff --git a/packages/flutter_tools/lib/src/mdns_discovery.dart b/packages/flutter_tools/lib/src/mdns_discovery.dart
index a2f0dec..1568757 100644
--- a/packages/flutter_tools/lib/src/mdns_discovery.dart
+++ b/packages/flutter_tools/lib/src/mdns_discovery.dart
@@ -15,6 +15,7 @@
 import 'build_info.dart';
 import 'convert.dart';
 import 'device.dart';
+import 'globals.dart' as globals;
 import 'reporting/reporting.dart';
 
 /// A wrapper around [MDnsClient] to find a Dart VM Service instance.
@@ -229,10 +230,28 @@
       final Set<String> uniqueDomainNamesInResults = <String>{};
 
       // Listen for mDNS connections until timeout.
-      final Stream<PtrResourceRecord> ptrResourceStream = client.lookup<PtrResourceRecord>(
-        ResourceRecordQuery.serverPointer(dartVmServiceName),
-        timeout: timeout
-      );
+      final Stream<PtrResourceRecord> ptrResourceStream;
+
+      try {
+        ptrResourceStream = client.lookup<PtrResourceRecord>(
+          ResourceRecordQuery.serverPointer(dartVmServiceName),
+          timeout: timeout,
+        );
+      } on SocketException catch (e, stacktrace) {
+        _logger.printError(e.message);
+        _logger.printTrace(stacktrace.toString());
+        if (globals.platform.isMacOS) {
+          throwToolExit(
+            'You might be having a permissions issue with your IDE. '
+            'Please try going to '
+            'System Settings -> Privacy & Security -> Local Network -> '
+            '[Find your IDE] -> Toggle ON, then restart your phone.'
+          );
+        } else {
+          rethrow;
+        }
+      }
+
       await for (final PtrResourceRecord ptr in ptrResourceStream) {
         uniqueDomainNames.add(ptr.domainName);
 
diff --git a/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart b/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart
index 27eb5a9..7b14545 100644
--- a/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart
+++ b/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart
@@ -7,6 +7,7 @@
 import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/build_info.dart';
 import 'package:flutter_tools/src/device_port_forwarder.dart';
+import 'package:flutter_tools/src/globals.dart' as globals;
 import 'package:flutter_tools/src/ios/devices.dart';
 import 'package:flutter_tools/src/mdns_discovery.dart';
 import 'package:flutter_tools/src/project.dart';
@@ -577,6 +578,30 @@
         );
       });
 
+      test('On macOS, throw tool exit with a helpful message when client throws a SocketException on lookup', () async {
+        final MDnsClient client = FakeMDnsClient(
+          <PtrResourceRecord>[], <String, List<SrvResourceRecord>>{},
+          socketExceptionOnStart: true);
+
+        final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery(
+          mdnsClient: client,
+          logger: BufferLogger.test(),
+          flutterUsage: TestUsage(),
+          analytics: const NoOpAnalytics(),
+        );
+
+        expect(
+          portDiscovery.firstMatchingVmService(client),
+            throwsToolExit(
+              message:
+              'You might be having a permissions issue with your IDE. '
+                  'Please try going to '
+                  'System Settings -> Privacy & Security -> Local Network -> '
+                  '[Find your IDE] -> Toggle ON, then restart your phone.',
+          )
+        );
+      }, skip: !globals.platform.isMacOS); // [intended] This tool exit message only works for macOS
+
       testWithoutContext('Correctly builds VM Service URI with hostVmservicePort == 0', () async {
         final MDnsClient client = FakeMDnsClient(
           <PtrResourceRecord>[
@@ -991,6 +1016,7 @@
     this.txtResponse = const <String, List<TxtResourceRecord>>{},
     this.ipResponse = const <String, List<IPAddressResourceRecord>>{},
     this.osErrorOnStart = false,
+    this.socketExceptionOnStart = false
   });
 
   final List<PtrResourceRecord> ptrRecords;
@@ -998,6 +1024,7 @@
   final Map<String, List<TxtResourceRecord>> txtResponse;
   final Map<String, List<IPAddressResourceRecord>> ipResponse;
   final bool osErrorOnStart;
+  final bool socketExceptionOnStart;
 
   @override
   Future<void> start({
@@ -1016,6 +1043,9 @@
     ResourceRecordQuery query, {
     Duration timeout = const Duration(seconds: 5),
   }) {
+    if (socketExceptionOnStart) {
+      throw const SocketException('Socket Exception');
+    }
     if (T == PtrResourceRecord && query.fullyQualifiedName == MDnsVmServiceDiscovery.dartVmServiceName) {
       return Stream<PtrResourceRecord>.fromIterable(ptrRecords) as Stream<T>;
     }