|
| 1 | +// ignore_for_file: undefined_named_parameter |
| 2 | +// Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file |
| 3 | +// for details. All rights reserved. Use of this source code is governed by a |
| 4 | +// BSD-style license that can be found in the LICENSE file. |
| 5 | + |
| 6 | +import 'dart:async'; |
| 7 | +import 'dart:io'; |
| 8 | + |
| 9 | +import 'package:multicast_dns/src/constants.dart'; |
| 10 | +import 'package:multicast_dns/src/lookup_resolver.dart'; |
| 11 | +import 'package:multicast_dns/src/native_protocol_client.dart'; |
| 12 | +import 'package:multicast_dns/src/packet.dart'; |
| 13 | +import 'package:multicast_dns/src/resource_record.dart'; |
| 14 | + |
| 15 | +export 'package:multicast_dns/src/resource_record.dart'; |
| 16 | + |
| 17 | +/// Client for DNS lookup using the mDNS protocol. |
| 18 | +/// |
| 19 | +/// This client only support "One-Shot Multicast DNS Queries" as described in |
| 20 | +/// section 5.1 of https://tools.ietf.org/html/rfc6762 |
| 21 | +class MDnsClient { |
| 22 | + bool _starting = false; |
| 23 | + bool _started = false; |
| 24 | + RawDatagramSocket _incoming; |
| 25 | + final List<RawDatagramSocket> _sockets = <RawDatagramSocket>[]; |
| 26 | + final LookupResolver _resolver = LookupResolver(); |
| 27 | + final ResourceRecordCache _cache = ResourceRecordCache(); |
| 28 | + |
| 29 | + /// Start the mDNS client. |
| 30 | + Future<void> start() async { |
| 31 | + if (_started && _starting) { |
| 32 | + throw StateError('mDNS client already started'); |
| 33 | + } |
| 34 | + _starting = true; |
| 35 | + |
| 36 | + // Listen on all addresses. |
| 37 | + _incoming = await RawDatagramSocket.bind( |
| 38 | + InternetAddress.anyIPv4, |
| 39 | + mDnsPort, |
| 40 | + reuseAddress: true, |
| 41 | + reusePort: true, |
| 42 | + ttl: 255, |
| 43 | + ); |
| 44 | + // Find all network interfaces with an IPv4 address. |
| 45 | + final List<NetworkInterface> interfaces = await NetworkInterface.list( |
| 46 | + includeLinkLocal: true, |
| 47 | + type: InternetAddressType.IPv4, |
| 48 | + includeLoopback: true, |
| 49 | + ); |
| 50 | + |
| 51 | + _sockets.add(_incoming); |
| 52 | + |
| 53 | + for (NetworkInterface interface in interfaces) { |
| 54 | + // Create a socket for sending on each adapter. |
| 55 | + final RawDatagramSocket socket = await RawDatagramSocket.bind( |
| 56 | + interface.addresses[0], |
| 57 | + mDnsPort, |
| 58 | + reuseAddress: true, |
| 59 | + reusePort: true, |
| 60 | + ttl: 255, |
| 61 | + ); |
| 62 | + _sockets.add(socket); |
| 63 | + |
| 64 | + // Join multicast on this interface. |
| 65 | + _incoming.joinMulticast(mDnsAddress, interface); |
| 66 | + } |
| 67 | + _incoming.listen(_handleIncoming); |
| 68 | + |
| 69 | + _starting = false; |
| 70 | + _started = true; |
| 71 | + } |
| 72 | + |
| 73 | + /// Stop the client and close any associated sockets. |
| 74 | + void stop() { |
| 75 | + if (!_started) { |
| 76 | + return; |
| 77 | + } |
| 78 | + if (_starting) { |
| 79 | + throw StateError('Cannot stop mDNS client wile it is starting'); |
| 80 | + } |
| 81 | + |
| 82 | + for (RawDatagramSocket socket in _sockets) { |
| 83 | + socket.close(); |
| 84 | + } |
| 85 | + |
| 86 | + _resolver.clearPendingRequests(); |
| 87 | + |
| 88 | + _started = false; |
| 89 | + } |
| 90 | + |
| 91 | + /// Lookup a [ResourceRecord], potentially from cache. |
| 92 | + Stream<ResourceRecord> lookup(int type, String name, |
| 93 | + {Duration timeout = const Duration(seconds: 5)}) { |
| 94 | + if (!_started) { |
| 95 | + throw StateError('mDNS client is not started'); |
| 96 | + } |
| 97 | + // Look for entries in the cache. |
| 98 | + final List<ResourceRecord> cached = <ResourceRecord>[]; |
| 99 | + _cache.lookup(name, type, cached); |
| 100 | + if (cached.isNotEmpty) { |
| 101 | + final StreamController<ResourceRecord> controller = |
| 102 | + StreamController<ResourceRecord>(); |
| 103 | + cached.forEach(controller.add); |
| 104 | + controller.close(); |
| 105 | + return controller.stream; |
| 106 | + } |
| 107 | + |
| 108 | + // Add the pending request before sending the query. |
| 109 | + final Stream<ResourceRecord> results = |
| 110 | + _resolver.addPendingRequest(type, name, timeout); |
| 111 | + |
| 112 | + // Send the request on all interfaces. |
| 113 | + final List<int> packet = encodeMDnsQuery(name, type: type); |
| 114 | + for (RawDatagramSocket socket in _sockets) { |
| 115 | + socket.send(packet, mDnsAddress, mDnsPort); |
| 116 | + } |
| 117 | + return results; |
| 118 | + } |
| 119 | + |
| 120 | + // Process incoming datagrams. |
| 121 | + void _handleIncoming(RawSocketEvent event) { |
| 122 | + if (event == RawSocketEvent.read) { |
| 123 | + final Datagram datagram = _incoming.receive(); |
| 124 | + |
| 125 | + final List<ResourceRecord> response = decodeMDnsResponse(datagram.data); |
| 126 | + if (response != null) { |
| 127 | + _cache.updateRecords(response); |
| 128 | + _resolver.handleResponse(response); |
| 129 | + } |
| 130 | + } |
| 131 | + } |
| 132 | +} |
0 commit comments