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
|
||||
/// in `dart:io` can sometimes be hard to use in tests.
|
||||
import 'dart:async';
|
||||
import 'dart:io' as io show exit, IOSink, Process, ProcessInfo, ProcessSignal,
|
||||
stderr, stdin, Stdin, StdinException, Stdout, stdout;
|
||||
import 'dart:io' as io
|
||||
show
|
||||
exit,
|
||||
InternetAddress,
|
||||
InternetAddressType,
|
||||
IOSink,
|
||||
NetworkInterface,
|
||||
Process,
|
||||
ProcessInfo,
|
||||
ProcessSignal,
|
||||
stderr,
|
||||
stdin,
|
||||
Stdin,
|
||||
StdinException,
|
||||
Stdout,
|
||||
stdout;
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
|
@ -60,6 +74,7 @@ export 'dart:io'
|
|||
IOException,
|
||||
IOSink,
|
||||
// Link NO! Use `file_system.dart`
|
||||
// NetworkInterface NO! Use `io.dart`
|
||||
pid,
|
||||
// Platform NO! use `platform.dart`
|
||||
Process,
|
||||
|
@ -259,3 +274,66 @@ class _DefaultProcessInfo implements ProcessInfo {
|
|||
@override
|
||||
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/context.dart';
|
||||
import 'base/io.dart';
|
||||
import 'build_info.dart';
|
||||
import 'device.dart';
|
||||
import 'globals.dart';
|
||||
import 'reporting/reporting.dart';
|
||||
|
||||
/// A wrapper around [MDnsClient] to find a Dart observatory instance.
|
||||
class MDnsObservatoryDiscovery {
|
||||
|
@ -146,20 +148,71 @@ class MDnsObservatoryDiscovery {
|
|||
applicationId: applicationId,
|
||||
deviceVmservicePort: deviceVmservicePort,
|
||||
);
|
||||
Uri observatoryUri;
|
||||
if (result != null) {
|
||||
final String host = usesIpv6
|
||||
? InternetAddress.loopbackIPv6.address
|
||||
: InternetAddress.loopbackIPv4.address;
|
||||
observatoryUri = await buildObservatoryUri(
|
||||
device,
|
||||
host,
|
||||
result.port,
|
||||
hostVmservicePort,
|
||||
result.authCode,
|
||||
);
|
||||
if (result == null) {
|
||||
await _checkForIPv4LinkLocal(device);
|
||||
return null;
|
||||
}
|
||||
|
||||
final String host = usesIpv6
|
||||
? InternetAddress.loopbackIPv6.address
|
||||
: InternetAddress.loopbackIPv4.address;
|
||||
return await buildObservatoryUri(
|
||||
device,
|
||||
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', () {
|
||||
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 {}
|
||||
|
|
|
@ -4,17 +4,33 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/mdns_discovery.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:multicast_dns/multicast_dns.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
import '../src/mocks.dart';
|
||||
|
||||
void main() {
|
||||
group('mDNS Discovery', () {
|
||||
final int year3000 = DateTime(3000).millisecondsSinceEpoch;
|
||||
|
||||
setUp(() {
|
||||
setNetworkInterfaceLister(
|
||||
({
|
||||
bool includeLoopback,
|
||||
bool includeLinkLocal,
|
||||
InternetAddressType type,
|
||||
}) async => <NetworkInterface>[],
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
resetNetworkInterfaceLister();
|
||||
});
|
||||
|
||||
MDnsClient getMockClient(
|
||||
List<PtrResourceRecord> ptrRecords,
|
||||
Map<String, List<SrvResourceRecord>> srvResponse, {
|
||||
|
@ -48,6 +64,18 @@ void main() {
|
|||
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 {
|
||||
final MDnsClient client = getMockClient(
|
||||
<PtrResourceRecord>[
|
||||
|
|
Loading…
Reference in a new issue