Line data Source code
1 : import 'dart:async';
2 : import 'dart:typed_data';
3 : import 'package:multicast_dns/multicast_dns.dart';
4 : import 'package:network_tools/network_tools.dart';
5 : import 'package:network_tools/src/mdns_scanner/get_srv_list_by_os/srv_list.dart';
6 : import 'package:network_tools/src/network_tools_utils.dart';
7 : import 'package:universal_io/io.dart';
8 :
9 : /// Converts a network address coming from either the Dart IO or universal_io
10 : /// implementation into the package's [InternetAddress] type.
11 1 : InternetAddress normalizeInternetAddress(dynamic address) {
12 1 : if (address is InternetAddress) {
13 : return address;
14 : }
15 :
16 1 : if (address is String) {
17 1 : final parsedAddress = InternetAddress.tryParse(address);
18 : if (parsedAddress != null) {
19 : return parsedAddress;
20 : }
21 : }
22 :
23 1 : if (address is Object) {
24 : try {
25 1 : final dynamic addressValue = (address as dynamic).address;
26 1 : if (addressValue is String) {
27 1 : final parsedAddress = InternetAddress.tryParse(addressValue);
28 : if (parsedAddress != null) {
29 : return parsedAddress;
30 : }
31 : }
32 : } catch (_) {}
33 :
34 : try {
35 1 : final dynamic rawAddress = (address as dynamic).rawAddress;
36 1 : if (rawAddress is List<int>) {
37 2 : return InternetAddress.fromRawAddress(Uint8List.fromList(rawAddress));
38 : }
39 : } catch (_) {}
40 : }
41 :
42 1 : throw ArgumentError.value(
43 : address,
44 : 'address',
45 : 'Unsupported InternetAddress implementation',
46 : );
47 : }
48 :
49 : /// Resolves the bind target for [RawDatagramSocket.bind].
50 : ///
51 : /// On Windows, binding with an [InternetAddress] whose hostname is empty fails
52 : /// with errno 10049. Always use the numeric address string instead.
53 1 : dynamic _mdnsDatagramBindHost(dynamic host) {
54 1 : if (host is InternetAddress) {
55 0 : return host.address;
56 : }
57 :
58 : try {
59 2 : return normalizeInternetAddress(host).address;
60 : } catch (_) {
61 : return host;
62 : }
63 : }
64 :
65 1 : Future<RawDatagramSocket> _mdnsRawDatagramSocketFactory(
66 : dynamic host,
67 : int port, {
68 : bool? reuseAddress,
69 : bool? reusePort,
70 : int? ttl,
71 : }) {
72 1 : return RawDatagramSocket.bind(
73 1 : _mdnsDatagramBindHost(host),
74 : port,
75 : reuseAddress: reuseAddress ?? true,
76 : reusePort:
77 2 : !(Platform.isWindows || Platform.isAndroid) && (reusePort ?? true),
78 : ttl: ttl ?? 255,
79 : );
80 : }
81 :
82 1 : Iterable<NetworkInterface> filterMdnsInterfaces(
83 : Iterable<NetworkInterface> interfaces,
84 : InternetAddressType type, {
85 : bool isWindows = false,
86 : }) {
87 : return interfaces
88 2 : .where((interface) {
89 3 : final addresses = interface.addresses.where((address) {
90 2 : if (address.type != type) {
91 : return false;
92 : }
93 1 : if (address.isLoopback) {
94 : return false;
95 : }
96 1 : if (isWindows && address.isLinkLocal) {
97 : return false;
98 : }
99 : return true;
100 : });
101 :
102 1 : return addresses.isNotEmpty;
103 : })
104 1 : .toList(growable: false);
105 : }
106 :
107 1 : Future<Iterable<NetworkInterface>> _mdnsNetworkInterfacesFactory(
108 : InternetAddressType type,
109 : ) async {
110 1 : final interfaces = await NetworkInterface.list(
111 1 : includeLinkLocal: !Platform.isWindows,
112 : type: type,
113 : );
114 2 : return filterMdnsInterfaces(interfaces, type, isWindows: Platform.isWindows);
115 : }
116 :
117 : class MdnsScannerServiceImpl extends MdnsScannerService {
118 : /// This method searching for all the mdns devices in the network.
119 : /// TODO: The implementation is **Lacking!** and will not find all the
120 : /// TODO: results that actual exist in the network!, only some of them.
121 : /// TODO: This is because missing functionality in dart
122 : /// TODO: https://github.com/flutter/flutter/issues/97210
123 : /// TODO: In some cases we resolve this missing functionality using
124 : /// TODO: specific os tools.
125 :
126 1 : @override
127 : Future<List<ActiveHost>> searchMdnsDevices({
128 : bool forceUseOfSavedSrvRecordList = false,
129 : }) async {
130 : List<String> srvRecordListToSearchIn;
131 :
132 : if (forceUseOfSavedSrvRecordList) {
133 1 : srvRecordListToSearchIn = tcpSrvRecordsList;
134 2 : srvRecordListToSearchIn.addAll(udpSrvRecordsList);
135 : } else {
136 1 : final List<String>? srvRecordsFromOs = await SrvList.getSrvRecordList();
137 :
138 1 : if (srvRecordsFromOs == null || srvRecordsFromOs.isEmpty) {
139 1 : srvRecordListToSearchIn = tcpSrvRecordsList;
140 2 : srvRecordListToSearchIn.addAll(udpSrvRecordsList);
141 : } else {
142 : srvRecordListToSearchIn = srvRecordsFromOs;
143 : }
144 : }
145 :
146 1 : final List<Future<List<ActiveHost>>> activeHostListsFuture = [];
147 2 : for (final String srvRecord in srvRecordListToSearchIn) {
148 2 : activeHostListsFuture.add(findingMdnsWithAddress(srvRecord));
149 : }
150 :
151 1 : final List<ActiveHost> activeHostList = [];
152 :
153 : for (final Future<List<ActiveHost>> activeHostListFuture
154 2 : in activeHostListsFuture) {
155 1 : activeHostList.addAll(await activeHostListFuture);
156 : }
157 :
158 : return activeHostList;
159 : }
160 :
161 1 : @override
162 : Future<List<ActiveHost>> findingMdnsWithAddress(String serviceType) async {
163 1 : final MDnsClient client = MDnsClient(
164 : rawDatagramSocketFactory: _mdnsRawDatagramSocketFactory,
165 : );
166 :
167 1 : final List<ActiveHost> listOfActiveHost = [];
168 1 : final Completer<void> completer = Completer<void>();
169 :
170 1 : runZonedGuarded(
171 1 : () async {
172 : try {
173 1 : await client.start(
174 1 : listenAddress: InternetAddress.anyIPv4,
175 : interfacesFactory: _mdnsNetworkInterfacesFactory,
176 : );
177 :
178 1 : await for (final PtrResourceRecord ptr
179 1 : in client.lookup<PtrResourceRecord>(
180 1 : ResourceRecordQuery.serverPointer(serviceType),
181 0 : )) {
182 0 : await for (final SrvResourceRecord srv
183 0 : in client.lookup<SrvResourceRecord>(
184 0 : ResourceRecordQuery.service(ptr.domainName),
185 0 : )) {
186 0 : await for (final TxtResourceRecord txtRecords
187 0 : in client.lookup<TxtResourceRecord>(
188 0 : ResourceRecordQuery.text(ptr.domainName),
189 0 : )) {
190 0 : listOfActiveHost.addAll(
191 0 : await findAllActiveHostForSrv(
192 0 : addressType: InternetAddress.anyIPv4,
193 : client: client,
194 : ptr: ptr,
195 : srv: srv,
196 : txt: txtRecords,
197 : ),
198 : );
199 0 : listOfActiveHost.addAll(
200 0 : await findAllActiveHostForSrv(
201 0 : addressType: InternetAddress.anyIPv6,
202 : client: client,
203 : ptr: ptr,
204 : srv: srv,
205 : txt: txtRecords,
206 : ),
207 : );
208 : }
209 : }
210 : }
211 : } catch (e) {
212 0 : logger.severe(
213 0 : 'Error finding mdns devices for serviceType $serviceType: $e',
214 : );
215 : } finally {
216 1 : client.stop();
217 2 : if (!completer.isCompleted) completer.complete();
218 : }
219 : },
220 0 : (Object error, StackTrace stack) {
221 0 : logger.severe(
222 0 : 'Unhandled async error in findingMdnsWithAddress for $serviceType: $error',
223 : );
224 0 : client.stop();
225 0 : if (!completer.isCompleted) completer.complete();
226 : },
227 : );
228 :
229 1 : await completer.future;
230 : return listOfActiveHost;
231 : }
232 :
233 0 : @override
234 : Future<List<ActiveHost>> findAllActiveHostForSrv({
235 : required InternetAddress addressType,
236 : required MDnsClient client,
237 : required PtrResourceRecord ptr,
238 : required SrvResourceRecord srv,
239 : required TxtResourceRecord txt,
240 : }) async {
241 0 : final List<ActiveHost> listOfActiveHost = [];
242 : try {
243 : Stream<IPAddressResourceRecord> iPAddressResourceRecordStream;
244 :
245 0 : if (addressType == InternetAddress.anyIPv4) {
246 0 : iPAddressResourceRecordStream = client.lookup<IPAddressResourceRecord>(
247 0 : ResourceRecordQuery.addressIPv4(srv.target),
248 : );
249 : } else {
250 0 : iPAddressResourceRecordStream = client.lookup<IPAddressResourceRecord>(
251 0 : ResourceRecordQuery.addressIPv6(srv.target),
252 : );
253 : }
254 0 : await for (final IPAddressResourceRecord ip
255 0 : in iPAddressResourceRecordStream) {
256 0 : final ActiveHost activeHost = convertSrvToHostName(
257 0 : internetAddress: normalizeInternetAddress(ip.address),
258 : ptr: ptr,
259 : srv: srv,
260 : txt: txt,
261 : );
262 :
263 0 : listOfActiveHost.add(activeHost);
264 : }
265 : } catch (e) {
266 0 : logger.severe(
267 0 : 'Error finding ip of mdns record ${ptr.name} srv target ${srv.target}, will add it with ip 0.0.0.0\n$e',
268 : );
269 0 : final ActiveHost activeHost = convertSrvToHostName(
270 0 : internetAddress: InternetAddress('0.0.0.0'),
271 : srv: srv,
272 : ptr: ptr,
273 : txt: txt,
274 : );
275 0 : listOfActiveHost.add(activeHost);
276 : }
277 : return listOfActiveHost;
278 : }
279 :
280 0 : @override
281 : ActiveHost convertSrvToHostName({
282 : required InternetAddress internetAddress,
283 : required PtrResourceRecord ptr,
284 : required SrvResourceRecord srv,
285 : required TxtResourceRecord txt,
286 : }) {
287 0 : final MdnsInfo mdnsInfo = MdnsInfo(
288 : srvResourceRecord: srv,
289 : ptrResourceRecord: ptr,
290 : txtResourceRecord: txt,
291 : );
292 0 : return ActiveHost(internetAddress: internetAddress, mdnsInfoVar: mdnsInfo);
293 : }
294 : }
|