-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Add mDNS client #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
cda0d64
5242a77
8394100
3147614
1aecdd0
b753a49
bd21b36
5eae734
be10902
67e73fc
51c2879
8ba84d1
2b2fe61
afd0ffa
27e32bb
a02b198
372e481
13440cc
a062a28
0a158b2
cf98f1f
2d8f0ab
f962358
a223011
1ae7747
8a711b9
60f3635
a935aee
4aba212
cefc13b
dcf69e4
2c09d26
d0071bd
af7a0df
14826cb
f3a79a8
b9572ba
9dbb4dd
5bcabe2
1961d9f
0eb9bc1
d379223
8d3c4e2
64902f4
b8251d9
2ad7aea
09b47d2
0962e7d
3d2f2a4
4a0974d
de88b93
3602baa
bceebbd
b5b1d4e
554ac6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,8 @@ | |
| .idea | ||
| .packages | ||
| .pub/ | ||
| .dart_tool/ | ||
|
|
||
| pubspec.lock | ||
|
|
||
| Podfile.lock | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| ## 0.1.0 | ||
|
|
||
| * Initial Open Source release. | ||
| * Migrates the dartino-sdk's mDNS client to Dart 2.0 and Flutter's analysis rules | ||
| * Breaks from original Dartino code, as it does not use native libraries for macOS and overhauls the `ResourceRecord` class. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| Copyright 2014 The Chromium Authors. All rights reserved. | ||
|
|
||
| Redistribution and use in source and binary forms, with or without | ||
| modification, are permitted provided that the following conditions are | ||
| met: | ||
|
|
||
| * Redistributions of source code must retain the above copyright | ||
| notice, this list of conditions and the following disclaimer. | ||
| * Redistributions in binary form must reproduce the above | ||
| copyright notice, this list of conditions and the following | ||
| disclaimer in the documentation and/or other materials provided | ||
| with the distribution. | ||
| * Neither the name of Google Inc. nor the names of its | ||
| contributors may be used to endorse or promote products derived | ||
| from this software without specific prior written permission. | ||
|
|
||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | ||
| // for details. All rights reserved. Use of this source code is governed by a | ||
| // BSD-style license that can be found in the LICENSE file. | ||
|
|
||
| // Example script to illustrate how to use the mdns package to discover services | ||
| // on the local network. | ||
|
|
||
| import 'package:args/args.dart'; | ||
|
|
||
| import 'package:multicast_dns/mdns_client.dart'; | ||
|
|
||
| void main(List<String> args) async { | ||
| // Parse the command line arguments. | ||
| final ArgParser parser = ArgParser(); | ||
| parser.addOption('timeout', abbr: 't', defaultsTo: '5'); | ||
| final ArgResults arguments = parser.parse(args); | ||
|
|
||
| if (arguments.rest.length != 1) { | ||
| print(''' | ||
| Please provide the name of a service as argument. | ||
|
|
||
| For example: | ||
| dart mdns-sd.dart [--timeout <timeout>] _workstation._tcp.local'''); | ||
| return; | ||
| } | ||
|
|
||
| final String name = arguments.rest[0]; | ||
|
|
||
| final MDnsClient client = MDnsClient(); | ||
| await client.start(); | ||
| await for (PtrResourceRecord ptr in client.lookup(RRType.ptr, name)) { | ||
| final String domain = ptr.domainName; | ||
| print(ptr); | ||
| await for (SrvResourceRecord srv in client.lookup(RRType.srv, domain)) { | ||
| final String target = srv.target; | ||
| print(srv); | ||
| await client.lookup(RRType.txt, domain).forEach(print); | ||
| await for (IPAddressResourceRecord ip | ||
| in client.lookup(RRType.a, target)) { | ||
| print(ip); | ||
| print('Service instance found at $target (${ip.address}).'); | ||
| } | ||
| } | ||
| } | ||
| client.stop(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| // ignore_for_file: undefined_named_parameter | ||
| // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | ||
| // for details. All rights reserved. Use of this source code is governed by a | ||
| // BSD-style license that can be found in the LICENSE file. | ||
|
|
||
| import 'dart:async'; | ||
| import 'dart:io'; | ||
|
|
||
| import 'package:multicast_dns/src/constants.dart'; | ||
| import 'package:multicast_dns/src/lookup_resolver.dart'; | ||
| import 'package:multicast_dns/src/native_protocol_client.dart'; | ||
| import 'package:multicast_dns/src/packet.dart'; | ||
| import 'package:multicast_dns/src/resource_record.dart'; | ||
|
|
||
| export 'package:multicast_dns/src/resource_record.dart'; | ||
|
|
||
| /// Client for DNS lookup using the mDNS protocol. | ||
| /// | ||
| /// This client only support "One-Shot Multicast DNS Queries" as described in | ||
| /// section 5.1 of https://tools.ietf.org/html/rfc6762 | ||
| class MDnsClient { | ||
| bool _starting = false; | ||
| bool _started = false; | ||
| RawDatagramSocket _incoming; | ||
| final List<RawDatagramSocket> _sockets = <RawDatagramSocket>[]; | ||
| final LookupResolver _resolver = LookupResolver(); | ||
| final ResourceRecordCache _cache = ResourceRecordCache(); | ||
|
|
||
| /// Start the mDNS client. | ||
| Future<void> start() async { | ||
| if (_started && _starting) { | ||
| throw StateError('mDNS client already started'); | ||
| } | ||
| _starting = true; | ||
|
|
||
| // Listen on all addresses. | ||
| _incoming = await RawDatagramSocket.bind( | ||
| InternetAddress.anyIPv4, | ||
| mDnsPort, | ||
| reuseAddress: true, | ||
| reusePort: true, | ||
| ttl: 255, | ||
| ); | ||
| // Find all network interfaces with an IPv4 address. | ||
| final List<NetworkInterface> interfaces = await NetworkInterface.list( | ||
| includeLinkLocal: true, | ||
| type: InternetAddressType.IPv4, | ||
| includeLoopback: true, | ||
| ); | ||
|
|
||
| _sockets.add(_incoming); | ||
|
|
||
| for (NetworkInterface interface in interfaces) { | ||
| // Create a socket for sending on each adapter. | ||
| final RawDatagramSocket socket = await RawDatagramSocket.bind( | ||
| interface.addresses[0], | ||
| mDnsPort, | ||
| reuseAddress: true, | ||
| reusePort: true, | ||
| ttl: 255, | ||
| ); | ||
| _sockets.add(socket); | ||
|
|
||
| // Join multicast on this interface. | ||
| _incoming.joinMulticast(mDnsAddress, interface); | ||
| } | ||
| _incoming.listen(_handleIncoming); | ||
|
|
||
| _starting = false; | ||
| _started = true; | ||
| } | ||
|
|
||
| /// Stop the client and close any associated sockets. | ||
| void stop() { | ||
| if (!_started) { | ||
| return; | ||
| } | ||
| if (_starting) { | ||
| throw StateError('Cannot stop mDNS client wile it is starting'); | ||
| } | ||
|
|
||
| for (RawDatagramSocket socket in _sockets) { | ||
| socket.close(); | ||
| } | ||
|
|
||
| _resolver.clearPendingRequests(); | ||
|
|
||
| _started = false; | ||
| } | ||
|
|
||
| /// Lookup a [ResourceRecord], potentially from cache. | ||
| Stream<ResourceRecord> lookup(int type, String name, | ||
| {Duration timeout = const Duration(seconds: 5)}) { | ||
| if (!_started) { | ||
| throw StateError('mDNS client is not started'); | ||
| } | ||
| // Look for entries in the cache. | ||
| final List<ResourceRecord> cached = <ResourceRecord>[]; | ||
| _cache.lookup(name, type, cached); | ||
| if (cached.isNotEmpty) { | ||
| final StreamController<ResourceRecord> controller = | ||
| StreamController<ResourceRecord>(); | ||
| cached.forEach(controller.add); | ||
| controller.close(); | ||
| return controller.stream; | ||
| } | ||
|
|
||
| // Add the pending request before sending the query. | ||
| final Stream<ResourceRecord> results = | ||
| _resolver.addPendingRequest(type, name, timeout); | ||
|
|
||
| // Send the request on all interfaces. | ||
| final List<int> packet = encodeMDnsQuery(name, type: type); | ||
| for (RawDatagramSocket socket in _sockets) { | ||
| socket.send(packet, mDnsAddress, mDnsPort); | ||
| } | ||
| return results; | ||
| } | ||
|
|
||
| // Process incoming datagrams. | ||
| void _handleIncoming(RawSocketEvent event) { | ||
| if (event == RawSocketEvent.read) { | ||
| final Datagram datagram = _incoming.receive(); | ||
|
|
||
| final List<ResourceRecord> response = decodeMDnsResponse(datagram.data); | ||
| if (response != null) { | ||
| _cache.updateRecords(response); | ||
| _resolver.handleResponse(response); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export 'package:multicast_dns/src/resource_record.dart'; | ||
|
|
||
| export 'mdns_client.dart'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | ||
| // for details. All rights reserved. Use of this source code is governed by a | ||
| // BSD-style license that can be found in the LICENSE file. | ||
|
|
||
| import 'dart:io'; | ||
|
|
||
| /// The IPv4 mDNS Address. | ||
| InternetAddress mDnsAddress = InternetAddress('224.0.0.251'); | ||
|
|
||
| /// The mDNS port. | ||
| const int mDnsPort = 5353; | ||
|
|
||
| /// Enumeration of supported resource record class types. | ||
| class RRClass { | ||
| /// Internet address class ("IN"). | ||
| static const int internet = 1; | ||
| } | ||
|
|
||
| /// Enumeration of DNS question types. | ||
| class QuestionType { | ||
| /// "QU" Question. | ||
| static const int unicast = 0x8000; | ||
|
|
||
| /// "QM" Question. | ||
| static const int multicast = 0x0000; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | ||
| // for details. All rights reserved. Use of this source code is governed by a | ||
| // BSD-style license that can be found in the LICENSE file. | ||
|
|
||
| import 'dart:async'; | ||
| import 'dart:collection'; | ||
|
|
||
| import 'package:multicast_dns/src/resource_record.dart'; | ||
|
|
||
| /// Class for maintaining state about pending mDNS requests. | ||
| class PendingRequest extends LinkedListEntry<PendingRequest> { | ||
| /// Creates a new PendingRequest. | ||
| PendingRequest(this.type, this.name, this.controller); | ||
|
|
||
| /// The [RRType] of the request. | ||
| final int type; | ||
|
|
||
| /// The domain name. | ||
|
||
| final String name; | ||
|
||
|
|
||
| /// A StreamController managing the request. | ||
| final StreamController<ResourceRecord> controller; | ||
|
|
||
| /// The timer for the request. | ||
| Timer timer; | ||
| } | ||
|
|
||
| /// Class for keeping track of pending lookups and process incoming | ||
|
||
| /// query responses. | ||
| class LookupResolver { | ||
| /// The requests the process. | ||
|
||
| final LinkedList<PendingRequest> pendingRequests = | ||
|
||
| LinkedList<PendingRequest>(); | ||
|
|
||
| /// Adds a request and returns a [Stream] of [ResourceRecord] responses. | ||
| Stream<ResourceRecord> addPendingRequest( | ||
| int type, String name, Duration timeout) { | ||
| final StreamController<ResourceRecord> controller = | ||
| StreamController<ResourceRecord>(); | ||
| final PendingRequest request = PendingRequest(type, name, controller); | ||
| final Timer timer = Timer(timeout, () { | ||
| request.unlink(); | ||
| controller.close(); | ||
| }); | ||
| request.timer = timer; | ||
| pendingRequests.add(request); | ||
| return controller.stream; | ||
| } | ||
|
|
||
| /// Processes responses back to the caller. | ||
|
||
| void handleResponse(List<ResourceRecord> response) { | ||
| for (ResourceRecord r in response) { | ||
| final int type = r.rrValue; | ||
| String name = r.name.toLowerCase(); | ||
| if (name.endsWith('.')) { | ||
| name = name.substring(0, name.length - 1); | ||
| } | ||
|
|
||
| bool responseMatches(PendingRequest request) { | ||
| return request.name.toLowerCase() == name && request.type == type; | ||
| } | ||
|
|
||
| for (PendingRequest pendingRequest in pendingRequests) { | ||
| if (responseMatches(pendingRequest)) { | ||
| if (pendingRequest.controller.isClosed) { | ||
| return; | ||
| } | ||
| pendingRequest.controller.add(r); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Removes any pending requests and ends processing. | ||
| void clearPendingRequests() { | ||
| while (pendingRequests.isNotEmpty) { | ||
| final PendingRequest request = pendingRequests.first; | ||
| request.unlink(); | ||
| request.timer.cancel(); | ||
| request.controller.close(); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.