Skip to content

Conversation

@loic-sharma
Copy link
Member

@loic-sharma loic-sharma commented Jan 17, 2025

Background

macOS Sequoia requires user consent to do multicast operations, which the Flutter tool does to connect to the Dart VM. We'd like to make the Flutter tool provide a more helpful error message when mDNS fails due to lack of permission. See: flutter/flutter#150131

If the app does not have permission to communicate with devices on the local network, the following happens:

  1. Flutter tool starts a multicast lookup
  2. The mDNS client sends data on the socket
  3. macOS blocks the operation. Dart's native socket implementation throws an OSError
  4. Dart's Socket.send catches the OSError, wraps it in a SocketException, and schedules a microtask that reports the exception through the socket's stream (Socket is a Stream)
  5. Currently, the mDNS client does not listen to the socket stream's errors, so the error is send to the current Zone's uncaught error handler.

Solution

This patch adds a onSocketError callback. This allows the caller to catch errors reported by the underlying Socket and provide a better error message to the user.

I'm not very happy with this API. Unfortunately, the SocketException doesn't give us enough information to match it back to a pending MDnsClient request. Please let me know if you have suggestions on a better API to handle this error!

This will help us improve the error message for: flutter/flutter#150131

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Comment on lines +87 to +90
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@loic-sharma loic-sharma force-pushed the mdns_on_error branch 2 times, most recently from 15c7c79 to d54919a Compare January 17, 2025 17:53
@loic-sharma loic-sharma marked this pull request as ready for review January 17, 2025 20:48
@loic-sharma loic-sharma requested a review from jmagman as a code owner January 17, 2025 20:48
@loic-sharma
Copy link
Member Author

On second thought, Jenn's suggestion of using a Zone in the tool seems like it might be a cleaner approach. I'll close this for now.

github-merge-queue bot pushed a commit to flutter/flutter that referenced this pull request Jan 23, 2025
### Background

macOS Sequoia requires the user's permission to do multicast operations,
which the Flutter tool does to connect to the Dart VM. If the app does
not have permission to communicate with devices on the local network,
the following happens:

1. Flutter tool starts a [multicast
lookup](https://github.com/flutter/flutter/blob/bb2d34126cc8161dbe4a1bf23c925e48b732f670/packages/flutter_tools/lib/src/mdns_discovery.dart#L238-L241)
2. The mDNS client [sends data on the
socket](https://github.com/flutter/packages/blob/973e8b59e24ba80d3c36a2bcfa914fcfd5e19943/packages/multicast_dns/lib/multicast_dns.dart#L219)
4. macOS blocks the operation. Dart's native socket implementation
throws an `OSError`
5. Dart's `Socket.send` [catches the
`OSError`](https://github.com/dart-lang/sdk/blob/da6dc03a15822d83d9180bd766c02d11aacdc06b/sdk/lib/_internal/vm/bin/socket_patch.dart#L1511-L1515),
wraps it in a `SocketException`, and [schedules a
microtask](https://github.com/dart-lang/sdk/blob/da6dc03a15822d83d9180bd766c02d11aacdc06b/sdk/lib/_internal/vm/bin/socket_patch.dart#L1513)
that [reports the exception through the socket's
stream](https://github.com/dart-lang/sdk/blob/95f00522676dff03f64fc715cb1835ad451faa4c/sdk/lib/_internal/vm/bin/socket_patch.dart#L3011)
([`Socket` is a
`Stream`](https://api.dart.dev/dart-io/Socket-class.html))
6. The mDNS client [does not listen to the socket stream's
errors](https://github.com/flutter/packages/blob/973e8b59e24ba80d3c36a2bcfa914fcfd5e19943/packages/multicast_dns/lib/multicast_dns.dart#L155),
so [the error is sent to the current `Zone`'s uncaught error
handler](https://github.com/dart-lang/sdk/blob/95f00522676dff03f64fc715cb1835ad451faa4c/sdk/lib/async/stream_impl.dart#L553).

### Reproduction

To reproduce this error on macOS...

1. Open System Settings > Privacy & Security > Local Network and toggle
off Visual Studio Code
2. Run a Flutter app using a physical device

### Fix

Ideally, we'd make `MDnsClient.lookup` throw an exception for this
scenario. Unfortunately, the `MDnsClient` can have multiple lookup
operations in parallel, and the `SocketException` doesn't give us enough
information to match it back to a pending `MDnsClient` request. See
flutter/packages#8450 as an attempt to solve
this in the `MDnsClient` layer.

Instead, this fix introduces a `Zone` in the tool to catch the socket's
uncaught exception.

Follow-up to #157638

See: #150131

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
loic-sharma added a commit to loic-sharma/flutter that referenced this pull request Jan 23, 2025
)

### Background

macOS Sequoia requires the user's permission to do multicast operations,
which the Flutter tool does to connect to the Dart VM. If the app does
not have permission to communicate with devices on the local network,
the following happens:

1. Flutter tool starts a [multicast
lookup](https://github.com/flutter/flutter/blob/bb2d34126cc8161dbe4a1bf23c925e48b732f670/packages/flutter_tools/lib/src/mdns_discovery.dart#L238-L241)
2. The mDNS client [sends data on the
socket](https://github.com/flutter/packages/blob/973e8b59e24ba80d3c36a2bcfa914fcfd5e19943/packages/multicast_dns/lib/multicast_dns.dart#L219)
4. macOS blocks the operation. Dart's native socket implementation
throws an `OSError`
5. Dart's `Socket.send` [catches the
`OSError`](https://github.com/dart-lang/sdk/blob/da6dc03a15822d83d9180bd766c02d11aacdc06b/sdk/lib/_internal/vm/bin/socket_patch.dart#L1511-L1515),
wraps it in a `SocketException`, and [schedules a
microtask](https://github.com/dart-lang/sdk/blob/da6dc03a15822d83d9180bd766c02d11aacdc06b/sdk/lib/_internal/vm/bin/socket_patch.dart#L1513)
that [reports the exception through the socket's
stream](https://github.com/dart-lang/sdk/blob/95f00522676dff03f64fc715cb1835ad451faa4c/sdk/lib/_internal/vm/bin/socket_patch.dart#L3011)
([`Socket` is a
`Stream`](https://api.dart.dev/dart-io/Socket-class.html))
6. The mDNS client [does not listen to the socket stream's
errors](https://github.com/flutter/packages/blob/973e8b59e24ba80d3c36a2bcfa914fcfd5e19943/packages/multicast_dns/lib/multicast_dns.dart#L155),
so [the error is sent to the current `Zone`'s uncaught error
handler](https://github.com/dart-lang/sdk/blob/95f00522676dff03f64fc715cb1835ad451faa4c/sdk/lib/async/stream_impl.dart#L553).

### Reproduction

To reproduce this error on macOS...

1. Open System Settings > Privacy & Security > Local Network and toggle
off Visual Studio Code
2. Run a Flutter app using a physical device

### Fix

Ideally, we'd make `MDnsClient.lookup` throw an exception for this
scenario. Unfortunately, the `MDnsClient` can have multiple lookup
operations in parallel, and the `SocketException` doesn't give us enough
information to match it back to a pending `MDnsClient` request. See
flutter/packages#8450 as an attempt to solve
this in the `MDnsClient` layer.

Instead, this fix introduces a `Zone` in the tool to catch the socket's
uncaught exception.

Follow-up to flutter#157638

See: flutter#150131

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Wasmund1 pushed a commit to Wasmund1/flutter that referenced this pull request Jan 24, 2025
)

### Background

macOS Sequoia requires the user's permission to do multicast operations,
which the Flutter tool does to connect to the Dart VM. If the app does
not have permission to communicate with devices on the local network,
the following happens:

1. Flutter tool starts a [multicast
lookup](https://github.com/flutter/flutter/blob/bb2d34126cc8161dbe4a1bf23c925e48b732f670/packages/flutter_tools/lib/src/mdns_discovery.dart#L238-L241)
2. The mDNS client [sends data on the
socket](https://github.com/flutter/packages/blob/973e8b59e24ba80d3c36a2bcfa914fcfd5e19943/packages/multicast_dns/lib/multicast_dns.dart#L219)
4. macOS blocks the operation. Dart's native socket implementation
throws an `OSError`
5. Dart's `Socket.send` [catches the
`OSError`](https://github.com/dart-lang/sdk/blob/da6dc03a15822d83d9180bd766c02d11aacdc06b/sdk/lib/_internal/vm/bin/socket_patch.dart#L1511-L1515),
wraps it in a `SocketException`, and [schedules a
microtask](https://github.com/dart-lang/sdk/blob/da6dc03a15822d83d9180bd766c02d11aacdc06b/sdk/lib/_internal/vm/bin/socket_patch.dart#L1513)
that [reports the exception through the socket's
stream](https://github.com/dart-lang/sdk/blob/95f00522676dff03f64fc715cb1835ad451faa4c/sdk/lib/_internal/vm/bin/socket_patch.dart#L3011)
([`Socket` is a
`Stream`](https://api.dart.dev/dart-io/Socket-class.html))
6. The mDNS client [does not listen to the socket stream's
errors](https://github.com/flutter/packages/blob/973e8b59e24ba80d3c36a2bcfa914fcfd5e19943/packages/multicast_dns/lib/multicast_dns.dart#L155),
so [the error is sent to the current `Zone`'s uncaught error
handler](https://github.com/dart-lang/sdk/blob/95f00522676dff03f64fc715cb1835ad451faa4c/sdk/lib/async/stream_impl.dart#L553).

### Reproduction

To reproduce this error on macOS...

1. Open System Settings > Privacy & Security > Local Network and toggle
off Visual Studio Code
2. Run a Flutter app using a physical device

### Fix

Ideally, we'd make `MDnsClient.lookup` throw an exception for this
scenario. Unfortunately, the `MDnsClient` can have multiple lookup
operations in parallel, and the `SocketException` doesn't give us enough
information to match it back to a pending `MDnsClient` request. See
flutter/packages#8450 as an attempt to solve
this in the `MDnsClient` layer.

Instead, this fix introduces a `Zone` in the tool to catch the socket's
uncaught exception.

Follow-up to flutter#157638

See: flutter#150131

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant