OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library mdns.src.packet; | 5 library mdns.src.packet; |
6 | 6 |
7 import 'dart:convert'; | 7 import 'dart:convert'; |
8 import 'dart:io'; | 8 import 'dart:io'; |
9 import 'dart:typed_data'; | 9 import 'dart:typed_data'; |
10 | 10 |
11 import 'package:mdns/src/constants.dart'; | 11 import 'package:mdns/src/constants.dart'; |
12 | 12 |
13 // Encode a mDNS query packet. | 13 // Encode a mDNS query packet. |
14 List<int> encodeMDnsQuery(String hostname) { | 14 List<int> encodeMDnsQuery(String name, [int type = RRType.A]) { |
15 List parts = hostname.split('.'); | 15 List parts = name.split('.'); |
16 | 16 |
17 // Calculate the size of the packet. | 17 // Calculate the size of the packet. |
18 int size = headerSize; | 18 int size = headerSize; |
19 for (int i = 0; i < parts.length; i++) { | 19 for (int i = 0; i < parts.length; i++) { |
20 parts[i] = UTF8.encode(parts[i]); | 20 parts[i] = UTF8.encode(parts[i]); |
21 size += 1 + parts[i].length; | 21 size += 1 + parts[i].length; |
22 } | 22 } |
23 size += 1; // End with empty part | 23 size += 1; // End with empty part |
24 size += 4; // Trailer (QTYPE and QCLASS). | 24 size += 4; // Trailer (QTYPE and QCLASS). |
25 Uint8List data = new Uint8List(size); | 25 Uint8List data = new Uint8List(size); |
(...skipping 11 matching lines...) Expand all Loading... |
37 // Number of resource records - 0 for query. | 37 // Number of resource records - 0 for query. |
38 bd.setUint16(arcountOffset, 0); | 38 bd.setUint16(arcountOffset, 0); |
39 int offset = headerSize; | 39 int offset = headerSize; |
40 for (int i = 0; i < parts.length; i++) { | 40 for (int i = 0; i < parts.length; i++) { |
41 data[offset++] = parts[i].length; | 41 data[offset++] = parts[i].length; |
42 data.setRange(offset, offset + parts[i].length, parts[i]); | 42 data.setRange(offset, offset + parts[i].length, parts[i]); |
43 offset += parts[i].length; | 43 offset += parts[i].length; |
44 } | 44 } |
45 data[offset] = 0; // Empty part. | 45 data[offset] = 0; // Empty part. |
46 offset++; | 46 offset++; |
47 bd.setUint16(offset, 1); // QTYPE. | 47 bd.setUint16(offset, type); // QTYPE. |
48 offset += 2; | 48 offset += 2; |
49 bd.setUint16(offset, 1); // QCLASS. | 49 bd.setUint16(offset, RRClass.IN | 0x8000); // QCLASS + QU. |
50 | 50 |
51 return data; | 51 return data; |
52 } | 52 } |
53 | 53 |
54 /// FQDN and address decoded from response. | 54 /// Partial implementation of DNS resource records (RRs). |
55 class DecodeResult { | 55 class ResourceRecord { |
| 56 final int type; |
56 final String name; | 57 final String name; |
57 final InternetAddress address; | 58 final _data; |
58 DecodeResult(this.name, this.address); | 59 final int validUntil; |
| 60 // TODO(karlklose): add missing header bits. |
| 61 |
| 62 ResourceRecord(this.type, this.name, this._data, this.validUntil); |
| 63 |
| 64 InternetAddress get address { |
| 65 if (type != RRType.A) { |
| 66 // TODO(karlklose): add IPv6 address support. |
| 67 throw new StateError("'address' is only supported for type A."); |
| 68 } |
| 69 return _data; |
| 70 } |
| 71 |
| 72 String get domainName { |
| 73 if (type != RRType.PTR) { |
| 74 throw new StateError("'domain name' is only supported for type PTR."); |
| 75 } |
| 76 return _data; |
| 77 } |
| 78 |
| 79 String get target { |
| 80 if (type != RRType.SRV) { |
| 81 throw new StateError("'target' is only supported for type SRV."); |
| 82 } |
| 83 return _data; |
| 84 } |
| 85 |
| 86 toString() => 'RR $type $_data'; |
59 } | 87 } |
60 | 88 |
61 /// Result of reading a FQDN. The FQDN parts and the bytes consumed. | 89 /// Result of reading a FQDN. |
62 class FQDNReadResult { | 90 class FQDNReadResult { |
63 final List<String> fqdn; | 91 final List<String> fqdn; |
64 final int bytesRead; | 92 final int bytesRead; |
65 FQDNReadResult(this.fqdn, this.bytesRead); | 93 FQDNReadResult(this.fqdn, this.bytesRead); |
66 } | 94 } |
67 | 95 |
68 /// Decode a mDNS package. | 96 /// Decode a mDNS package. |
69 /// | 97 /// |
70 /// If decoding fails (e.g. due to an invalid packet) `null` is returned. | 98 /// If decoding fails (e.g. due to an invalid packet) `null` is returned. |
71 /// | 99 /// |
72 /// See https://tools.ietf.org/html/rfc1035 for the format. | 100 /// See https://tools.ietf.org/html/rfc1035 for the format. |
73 List<DecodeResult> decodeMDnsResponse(List<int> packet) { | 101 List<ResourceRecord> decodeMDnsResponse(List<int> packet) { |
74 int length = packet.length; | 102 int length = packet.length; |
75 if (length < headerSize) return null; | 103 if (length < headerSize) return null; |
76 | 104 |
77 Uint8List data = | 105 Uint8List data = |
78 packet is Uint8List ? packet : new Uint8List.fromList(packet); | 106 packet is Uint8List ? packet : new Uint8List.fromList(packet); |
79 ByteData bd = new ByteData.view(data.buffer); | 107 ByteData bd = new ByteData.view(data.buffer); |
80 // Query identifier. | 108 // Query identifier. |
81 int id = bd.getUint16(idOffset); | 109 int id = bd.getUint16(idOffset); |
82 // Flags. | 110 // Flags. |
83 int flags = bd.getUint16(flagsOffset); | 111 int flags = bd.getUint16(flagsOffset); |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
126 offset += partLength; | 154 offset += partLength; |
127 parts.add(UTF8.decode(partBytes)); | 155 parts.add(UTF8.decode(partBytes)); |
128 } else { | 156 } else { |
129 break; | 157 break; |
130 } | 158 } |
131 } | 159 } |
132 } | 160 } |
133 return new FQDNReadResult(parts, offset - prevOffset); | 161 return new FQDNReadResult(parts, offset - prevOffset); |
134 } | 162 } |
135 | 163 |
136 DecodeResult readAddress() { | 164 ResourceRecord readResourceRecord() { |
137 // First read the FQDN. | 165 // First read the FQDN. |
138 FQDNReadResult result = readFQDN(offset); | 166 FQDNReadResult result = readFQDN(offset); |
139 var fqdn = result.fqdn.join('.'); | 167 var fqdn = result.fqdn.join('.'); |
140 offset += result.bytesRead; | 168 offset += result.bytesRead; |
141 checkLength(offset + 2); | 169 checkLength(offset + 2); |
142 int type = bd.getUint16(offset); | 170 int type = bd.getUint16(offset); |
143 offset += 2; | 171 offset += 2; |
| 172 // The first bit of the rrclass field is set to indicate that the answer is |
| 173 // unique and the querier should flush the cached answer for this name |
| 174 // (RFC 6762, Sec. 10.2). We ignore it for now since we don't cache answers. |
144 checkLength(offset + 2); | 175 checkLength(offset + 2); |
145 int cls = bd.getUint16(offset); | 176 int cls = bd.getUint16(offset) & 0x7fff; |
146 offset += 2; | 177 offset += 2; |
147 checkLength(offset + 4); | 178 checkLength(offset + 4); |
148 int ttl = bd.getInt32(offset); | 179 int ttl = bd.getInt32(offset); |
149 offset += 4; | 180 offset += 4; |
| 181 |
| 182 var rData; |
150 checkLength(offset + 2); | 183 checkLength(offset + 2); |
151 int addressLength = bd.getUint16(offset); | 184 int rDataLength = bd.getUint16(offset); |
152 offset += 2; | 185 offset += 2; |
153 checkLength(offset + addressLength); | 186 switch (type) { |
154 var addressBytes = new Uint8List.view(data.buffer, offset, addressLength); | 187 case RRType.A: |
155 offset += addressLength; | 188 checkLength(offset + rDataLength); |
| 189 rData = new Uint8List.view(data.buffer, offset, rDataLength); |
| 190 String addr = rData.map((n) => n.toString()).join('.'); |
| 191 rData = new InternetAddress(addr); |
| 192 offset += rDataLength; |
| 193 break; |
| 194 case RRType.SRV: |
| 195 checkLength(offset + 2); |
| 196 int priority = bd.getUint16(offset); |
| 197 offset += 2; |
| 198 checkLength(offset + 2); |
| 199 int weight = bd.getUint16(offset); |
| 200 offset += 2; |
| 201 checkLength(offset + 2); |
| 202 int port = bd.getUint16(offset); |
| 203 offset += 2; |
| 204 FQDNReadResult result = readFQDN(offset); |
| 205 rData = result.fqdn.join('.'); |
| 206 offset += rDataLength - 6; |
| 207 break; |
| 208 case RRType.PTR: |
| 209 checkLength(offset + rDataLength); |
| 210 FQDNReadResult result = readFQDN(offset); |
| 211 offset += rDataLength; |
| 212 rData = result.fqdn.join('.'); |
| 213 break; |
| 214 case RRType.TXT: |
| 215 // TODO(karlklose): convert to a String or Map. |
| 216 default: |
| 217 checkLength(offset + rDataLength); |
| 218 rData = new Uint8List.view(data.buffer, offset, rDataLength); |
| 219 offset += rDataLength; |
| 220 break; |
| 221 } |
| 222 assert(rData != null); |
156 | 223 |
157 if (type == ipV4AddressType && cls == ipV4Class && addressLength == 4) { | 224 if (cls != RRClass.IN) { |
158 String addr = addressBytes.map((n) => n.toString()).join('.'); | 225 // We do not support other classes at the moment. |
159 return new DecodeResult(fqdn, new InternetAddress(addr)); | |
160 } else { | |
161 return null; | 226 return null; |
162 } | 227 } |
| 228 |
| 229 int validUntil = new DateTime.now().millisecondsSinceEpoch + |
| 230 ttl * 1000; |
| 231 return new ResourceRecord(type, fqdn, rData, validUntil); |
163 } | 232 } |
164 | 233 |
165 // We don't use the number of records - just read through all | 234 List<ResourceRecord> result = <ResourceRecord>[]; |
166 // resource records and filter. | |
167 var result = []; | |
168 try { | 235 try { |
169 while (data.length - offset >= 16) { | 236 for (int i = 0; i < ancount; i++) { |
170 var address = readAddress(); | 237 ResourceRecord record = readResourceRecord(); |
171 if (address != null) result.add(address); | 238 if (record != null) { |
| 239 result.add(record); |
| 240 } |
172 } | 241 } |
173 } on MDnsDecodeException catch (e, s) { | 242 } on MDnsDecodeException catch (e, s) { |
174 // If decoding fails return null. | 243 // If decoding fails return null. |
175 return null; | 244 return null; |
176 } | 245 } |
177 | |
178 return result; | 246 return result; |
179 } | 247 } |
180 | 248 |
181 /// Exceptions thrown by decoder when the packet is invalid. | 249 /// Exceptions thrown by decoder when the packet is invalid. |
182 class MDnsDecodeException implements Exception { | 250 class MDnsDecodeException implements Exception { |
183 /// Exception message. | 251 /// Exception message. |
184 final int offset; | 252 final int offset; |
185 const MDnsDecodeException(this.offset); | 253 const MDnsDecodeException(this.offset); |
186 String toString() => 'Decoding error at $offset'; | 254 String toString() => 'Decoding error at $offset'; |
187 } | 255 } |
OLD | NEW |