mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
[flutter_tool] Print a helpful message on some mDNS failures (#47157)
This commit is contained in:
parent
ba73cfc149
commit
a72cca137d
|
@ -26,8 +26,22 @@
|
||||||
/// increase the API surface that we have to test in Flutter tools, and the APIs
|
/// increase the API surface that we have to test in Flutter tools, and the APIs
|
||||||
/// in `dart:io` can sometimes be hard to use in tests.
|
/// in `dart:io` can sometimes be hard to use in tests.
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io' as io show exit, IOSink, Process, ProcessInfo, ProcessSignal,
|
import 'dart:io' as io
|
||||||
stderr, stdin, Stdin, StdinException, Stdout, stdout;
|
show
|
||||||
|
exit,
|
||||||
|
InternetAddress,
|
||||||
|
InternetAddressType,
|
||||||
|
IOSink,
|
||||||
|
NetworkInterface,
|
||||||
|
Process,
|
||||||
|
ProcessInfo,
|
||||||
|
ProcessSignal,
|
||||||
|
stderr,
|
||||||
|
stdin,
|
||||||
|
Stdin,
|
||||||
|
StdinException,
|
||||||
|
Stdout,
|
||||||
|
stdout;
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
@ -60,6 +74,7 @@ export 'dart:io'
|
||||||
IOException,
|
IOException,
|
||||||
IOSink,
|
IOSink,
|
||||||
// Link NO! Use `file_system.dart`
|
// Link NO! Use `file_system.dart`
|
||||||
|
// NetworkInterface NO! Use `io.dart`
|
||||||
pid,
|
pid,
|
||||||
// Platform NO! use `platform.dart`
|
// Platform NO! use `platform.dart`
|
||||||
Process,
|
Process,
|
||||||
|
@ -259,3 +274,66 @@ class _DefaultProcessInfo implements ProcessInfo {
|
||||||
@override
|
@override
|
||||||
int get maxRss => io.ProcessInfo.maxRss;
|
int get maxRss => io.ProcessInfo.maxRss;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The return type for [listNetworkInterfaces].
|
||||||
|
class NetworkInterface implements io.NetworkInterface {
|
||||||
|
NetworkInterface(this._delegate);
|
||||||
|
|
||||||
|
final io.NetworkInterface _delegate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<io.InternetAddress> get addresses => _delegate.addresses;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get index => _delegate.index;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => _delegate.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => "NetworkInterface('$name', $addresses)";
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef NetworkInterfaceLister = Future<List<NetworkInterface>> Function({
|
||||||
|
bool includeLoopback,
|
||||||
|
bool includeLinkLocal,
|
||||||
|
io.InternetAddressType type,
|
||||||
|
});
|
||||||
|
|
||||||
|
NetworkInterfaceLister _networkInterfaceListerOverride;
|
||||||
|
|
||||||
|
// Tests can set up a non-default network interface lister.
|
||||||
|
@visibleForTesting
|
||||||
|
void setNetworkInterfaceLister(NetworkInterfaceLister lister) {
|
||||||
|
_networkInterfaceListerOverride = lister;
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
void resetNetworkInterfaceLister() {
|
||||||
|
_networkInterfaceListerOverride = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This calls [NetworkInterface.list] from `dart:io` unless it is overridden by
|
||||||
|
/// [setNetworkInterfaceLister] for a test. If it is overridden for a test,
|
||||||
|
/// it should be reset with [resetNetworkInterfaceLister].
|
||||||
|
Future<List<NetworkInterface>> listNetworkInterfaces({
|
||||||
|
bool includeLoopback = false,
|
||||||
|
bool includeLinkLocal = false,
|
||||||
|
io.InternetAddressType type = io.InternetAddressType.any,
|
||||||
|
}) async {
|
||||||
|
if (_networkInterfaceListerOverride != null) {
|
||||||
|
return _networkInterfaceListerOverride(
|
||||||
|
includeLoopback: includeLoopback,
|
||||||
|
includeLinkLocal: includeLinkLocal,
|
||||||
|
type: type,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final List<io.NetworkInterface> interfaces = await io.NetworkInterface.list(
|
||||||
|
includeLoopback: includeLoopback,
|
||||||
|
includeLinkLocal: includeLinkLocal,
|
||||||
|
type: type,
|
||||||
|
);
|
||||||
|
return interfaces.map(
|
||||||
|
(io.NetworkInterface interface) => NetworkInterface(interface),
|
||||||
|
).toList();
|
||||||
|
}
|
||||||
|
|
|
@ -10,8 +10,10 @@ import 'package:multicast_dns/multicast_dns.dart';
|
||||||
import 'base/common.dart';
|
import 'base/common.dart';
|
||||||
import 'base/context.dart';
|
import 'base/context.dart';
|
||||||
import 'base/io.dart';
|
import 'base/io.dart';
|
||||||
|
import 'build_info.dart';
|
||||||
import 'device.dart';
|
import 'device.dart';
|
||||||
import 'globals.dart';
|
import 'globals.dart';
|
||||||
|
import 'reporting/reporting.dart';
|
||||||
|
|
||||||
/// A wrapper around [MDnsClient] to find a Dart observatory instance.
|
/// A wrapper around [MDnsClient] to find a Dart observatory instance.
|
||||||
class MDnsObservatoryDiscovery {
|
class MDnsObservatoryDiscovery {
|
||||||
|
@ -146,20 +148,71 @@ class MDnsObservatoryDiscovery {
|
||||||
applicationId: applicationId,
|
applicationId: applicationId,
|
||||||
deviceVmservicePort: deviceVmservicePort,
|
deviceVmservicePort: deviceVmservicePort,
|
||||||
);
|
);
|
||||||
Uri observatoryUri;
|
if (result == null) {
|
||||||
if (result != null) {
|
await _checkForIPv4LinkLocal(device);
|
||||||
final String host = usesIpv6
|
return null;
|
||||||
? InternetAddress.loopbackIPv6.address
|
}
|
||||||
: InternetAddress.loopbackIPv4.address;
|
|
||||||
observatoryUri = await buildObservatoryUri(
|
final String host = usesIpv6
|
||||||
device,
|
? InternetAddress.loopbackIPv6.address
|
||||||
host,
|
: InternetAddress.loopbackIPv4.address;
|
||||||
result.port,
|
return await buildObservatoryUri(
|
||||||
hostVmservicePort,
|
device,
|
||||||
result.authCode,
|
host,
|
||||||
);
|
result.port,
|
||||||
|
hostVmservicePort,
|
||||||
|
result.authCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's not an ipv4 link local address in `NetworkInterfaces.list`,
|
||||||
|
// then request user interventions with a `printError()` if possible.
|
||||||
|
Future<void> _checkForIPv4LinkLocal(Device device) async {
|
||||||
|
printTrace(
|
||||||
|
'mDNS query failed. Checking for an interface with a ipv4 link local address.'
|
||||||
|
);
|
||||||
|
final List<NetworkInterface> interfaces = await listNetworkInterfaces(
|
||||||
|
includeLinkLocal: true,
|
||||||
|
type: InternetAddressType.IPv4,
|
||||||
|
);
|
||||||
|
if (logger.isVerbose) {
|
||||||
|
_logInterfaces(interfaces);
|
||||||
|
}
|
||||||
|
final bool hasIPv4LinkLocal = interfaces.any(
|
||||||
|
(NetworkInterface interface) => interface.addresses.any(
|
||||||
|
(InternetAddress address) => address.isLinkLocal,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (hasIPv4LinkLocal) {
|
||||||
|
printTrace('An interface with an ipv4 link local address was found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final TargetPlatform targetPlatform = await device.targetPlatform;
|
||||||
|
switch (targetPlatform) {
|
||||||
|
case TargetPlatform.ios:
|
||||||
|
UsageEvent('ios-mdns', 'no-ipv4-link-local').send();
|
||||||
|
printError(
|
||||||
|
'The mDNS query for an attached iOS device failed. It may '
|
||||||
|
'be necessary to disable the "Personal Hotspot" on the device. '
|
||||||
|
'See https://github.com/flutter/flutter/issues/46698 for details.'
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printTrace('No interface with an ipv4 link local address was found.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _logInterfaces(List<NetworkInterface> interfaces) {
|
||||||
|
for (NetworkInterface interface in interfaces) {
|
||||||
|
if (logger.isVerbose) {
|
||||||
|
printTrace('Found interface "${interface.name}":');
|
||||||
|
for (InternetAddress address in interface.addresses) {
|
||||||
|
final String linkLocal = address.isLinkLocal ? 'link local' : '';
|
||||||
|
printTrace('\tBound address: "${address.address}" $linkLocal');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return observatoryUri;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,20 @@ void main() {
|
||||||
test('test_api defines the Declarer in a known place', () {
|
test('test_api defines the Declarer in a known place', () {
|
||||||
expect(Zone.current[#test.declarer], isNotNull);
|
expect(Zone.current[#test.declarer], isNotNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('listNetworkInterfaces() uses overrides', () async {
|
||||||
|
setNetworkInterfaceLister(
|
||||||
|
({
|
||||||
|
bool includeLoopback,
|
||||||
|
bool includeLinkLocal,
|
||||||
|
InternetAddressType type,
|
||||||
|
}) async => <NetworkInterface>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await listNetworkInterfaces(), isEmpty);
|
||||||
|
|
||||||
|
resetNetworkInterfaceLister();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockIoProcessSignal extends Mock implements io.ProcessSignal {}
|
class MockIoProcessSignal extends Mock implements io.ProcessSignal {}
|
||||||
|
|
|
@ -4,17 +4,33 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_tools/src/base/io.dart';
|
||||||
import 'package:flutter_tools/src/mdns_discovery.dart';
|
import 'package:flutter_tools/src/mdns_discovery.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
import 'package:multicast_dns/multicast_dns.dart';
|
import 'package:multicast_dns/multicast_dns.dart';
|
||||||
|
|
||||||
import '../src/common.dart';
|
import '../src/common.dart';
|
||||||
import '../src/context.dart';
|
import '../src/context.dart';
|
||||||
|
import '../src/mocks.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('mDNS Discovery', () {
|
group('mDNS Discovery', () {
|
||||||
final int year3000 = DateTime(3000).millisecondsSinceEpoch;
|
final int year3000 = DateTime(3000).millisecondsSinceEpoch;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
setNetworkInterfaceLister(
|
||||||
|
({
|
||||||
|
bool includeLoopback,
|
||||||
|
bool includeLinkLocal,
|
||||||
|
InternetAddressType type,
|
||||||
|
}) async => <NetworkInterface>[],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
resetNetworkInterfaceLister();
|
||||||
|
});
|
||||||
|
|
||||||
MDnsClient getMockClient(
|
MDnsClient getMockClient(
|
||||||
List<PtrResourceRecord> ptrRecords,
|
List<PtrResourceRecord> ptrRecords,
|
||||||
Map<String, List<SrvResourceRecord>> srvResponse, {
|
Map<String, List<SrvResourceRecord>> srvResponse, {
|
||||||
|
@ -48,6 +64,18 @@ void main() {
|
||||||
expect(port, isNull);
|
expect(port, isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testUsingContext('Prints helpful message when there is no ipv4 link local address.', () async {
|
||||||
|
final MDnsClient client = getMockClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
|
||||||
|
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
|
||||||
|
|
||||||
|
final Uri uri = await portDiscovery.getObservatoryUri(
|
||||||
|
'',
|
||||||
|
MockIOSDevice(),
|
||||||
|
);
|
||||||
|
expect(uri, isNull);
|
||||||
|
expect(testLogger.errorText, contains('Personal Hotspot'));
|
||||||
|
});
|
||||||
|
|
||||||
testUsingContext('One port available, no appId', () async {
|
testUsingContext('One port available, no appId', () async {
|
||||||
final MDnsClient client = getMockClient(
|
final MDnsClient client = getMockClient(
|
||||||
<PtrResourceRecord>[
|
<PtrResourceRecord>[
|
||||||
|
|
Loading…
Reference in a new issue