1 // /Copyright 2003-2005 Arthur van Hoff, Rick Blair 2 // Licensed under Apache License version 2.0 3 // Original license LGPL 4 5 package javax.jmdns.impl; 6 7 import java.io.ByteArrayInputStream; 8 import java.io.IOException; 9 import java.net.DatagramPacket; 10 import java.net.InetAddress; 11 import java.util.HashMap; 12 import java.util.Map; 13 import java.util.logging.Level; 14 import java.util.logging.Logger; 15 16 import javax.jmdns.impl.constants.DNSConstants; 17 import javax.jmdns.impl.constants.DNSLabel; 18 import javax.jmdns.impl.constants.DNSOptionCode; 19 import javax.jmdns.impl.constants.DNSRecordClass; 20 import javax.jmdns.impl.constants.DNSRecordType; 21 import javax.jmdns.impl.constants.DNSResultCode; 22 23 /** 24 * Parse an incoming DNS message into its components. 25 * 26 * @author Arthur van Hoff, Werner Randelshofer, Pierre Frisch, Daniel Bobbert 27 */ 28 public final class DNSIncoming extends DNSMessage { 29 private static Logger logger = Logger.getLogger(DNSIncoming.class.getName()); 30 31 // This is a hack to handle a bug in the BonjourConformanceTest 32 // It is sending out target strings that don't follow the "domain name" format. 33 public static boolean USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET = true; 34 35 public static class MessageInputStream extends ByteArrayInputStream { 36 private static Logger logger1 = Logger.getLogger(MessageInputStream.class.getName()); 37 38 final Map<Integer, String> _names; 39 MessageInputStream(byte[] buffer, int length)40 public MessageInputStream(byte[] buffer, int length) { 41 this(buffer, 0, length); 42 } 43 44 /** 45 * @param buffer 46 * @param offset 47 * @param length 48 */ MessageInputStream(byte[] buffer, int offset, int length)49 public MessageInputStream(byte[] buffer, int offset, int length) { 50 super(buffer, offset, length); 51 _names = new HashMap<Integer, String>(); 52 } 53 readByte()54 public int readByte() { 55 return this.read(); 56 } 57 readUnsignedShort()58 public int readUnsignedShort() { 59 return (this.read() << 8) | this.read(); 60 } 61 readInt()62 public int readInt() { 63 return (this.readUnsignedShort() << 16) | this.readUnsignedShort(); 64 } 65 readBytes(int len)66 public byte[] readBytes(int len) { 67 byte bytes[] = new byte[len]; 68 this.read(bytes, 0, len); 69 return bytes; 70 } 71 readUTF(int len)72 public String readUTF(int len) { 73 StringBuilder buffer = new StringBuilder(len); 74 for (int index = 0; index < len; index++) { 75 int ch = this.read(); 76 switch (ch >> 4) { 77 case 0: 78 case 1: 79 case 2: 80 case 3: 81 case 4: 82 case 5: 83 case 6: 84 case 7: 85 // 0xxxxxxx 86 break; 87 case 12: 88 case 13: 89 // 110x xxxx 10xx xxxx 90 ch = ((ch & 0x1F) << 6) | (this.read() & 0x3F); 91 index++; 92 break; 93 case 14: 94 // 1110 xxxx 10xx xxxx 10xx xxxx 95 ch = ((ch & 0x0f) << 12) | ((this.read() & 0x3F) << 6) | (this.read() & 0x3F); 96 index++; 97 index++; 98 break; 99 default: 100 // 10xx xxxx, 1111 xxxx 101 ch = ((ch & 0x3F) << 4) | (this.read() & 0x0f); 102 index++; 103 break; 104 } 105 buffer.append((char) ch); 106 } 107 return buffer.toString(); 108 } 109 peek()110 protected synchronized int peek() { 111 return (pos < count) ? (buf[pos] & 0xff) : -1; 112 } 113 readName()114 public String readName() { 115 Map<Integer, StringBuilder> names = new HashMap<Integer, StringBuilder>(); 116 StringBuilder buffer = new StringBuilder(); 117 boolean finished = false; 118 while (!finished) { 119 int len = this.read(); 120 if (len == 0) { 121 finished = true; 122 break; 123 } 124 switch (DNSLabel.labelForByte(len)) { 125 case Standard: 126 int offset = pos - 1; 127 String label = this.readUTF(len) + "."; 128 buffer.append(label); 129 for (StringBuilder previousLabel : names.values()) { 130 previousLabel.append(label); 131 } 132 names.put(Integer.valueOf(offset), new StringBuilder(label)); 133 break; 134 case Compressed: 135 int index = (DNSLabel.labelValue(len) << 8) | this.read(); 136 String compressedLabel = _names.get(Integer.valueOf(index)); 137 if (compressedLabel == null) { 138 logger1.severe("bad domain name: possible circular name detected. Bad offset: 0x" + Integer.toHexString(index) + " at 0x" + Integer.toHexString(pos - 2)); 139 compressedLabel = ""; 140 } 141 buffer.append(compressedLabel); 142 for (StringBuilder previousLabel : names.values()) { 143 previousLabel.append(compressedLabel); 144 } 145 finished = true; 146 break; 147 case Extended: 148 // int extendedLabelClass = DNSLabel.labelValue(len); 149 logger1.severe("Extended label are not currently supported."); 150 break; 151 case Unknown: 152 default: 153 logger1.severe("unsupported dns label type: '" + Integer.toHexString(len & 0xC0) + "'"); 154 } 155 } 156 for (Integer index : names.keySet()) { 157 _names.put(index, names.get(index).toString()); 158 } 159 return buffer.toString(); 160 } 161 readNonNameString()162 public String readNonNameString() { 163 int len = this.read(); 164 return this.readUTF(len); 165 } 166 167 } 168 169 private final DatagramPacket _packet; 170 171 private final long _receivedTime; 172 173 private final MessageInputStream _messageInputStream; 174 175 private int _senderUDPPayload; 176 177 /** 178 * Parse a message from a datagram packet. 179 * 180 * @param packet 181 * @exception IOException 182 */ DNSIncoming(DatagramPacket packet)183 public DNSIncoming(DatagramPacket packet) throws IOException { 184 super(0, 0, packet.getPort() == DNSConstants.MDNS_PORT); 185 this._packet = packet; 186 InetAddress source = packet.getAddress(); 187 this._messageInputStream = new MessageInputStream(packet.getData(), packet.getLength()); 188 this._receivedTime = System.currentTimeMillis(); 189 this._senderUDPPayload = DNSConstants.MAX_MSG_TYPICAL; 190 191 try { 192 this.setId(_messageInputStream.readUnsignedShort()); 193 this.setFlags(_messageInputStream.readUnsignedShort()); 194 int numQuestions = _messageInputStream.readUnsignedShort(); 195 int numAnswers = _messageInputStream.readUnsignedShort(); 196 int numAuthorities = _messageInputStream.readUnsignedShort(); 197 int numAdditionals = _messageInputStream.readUnsignedShort(); 198 199 // parse questions 200 if (numQuestions > 0) { 201 for (int i = 0; i < numQuestions; i++) { 202 _questions.add(this.readQuestion()); 203 } 204 } 205 206 // parse answers 207 if (numAnswers > 0) { 208 for (int i = 0; i < numAnswers; i++) { 209 DNSRecord rec = this.readAnswer(source); 210 if (rec != null) { 211 // Add a record, if we were able to create one. 212 _answers.add(rec); 213 } 214 } 215 } 216 217 if (numAuthorities > 0) { 218 for (int i = 0; i < numAuthorities; i++) { 219 DNSRecord rec = this.readAnswer(source); 220 if (rec != null) { 221 // Add a record, if we were able to create one. 222 _authoritativeAnswers.add(rec); 223 } 224 } 225 } 226 227 if (numAdditionals > 0) { 228 for (int i = 0; i < numAdditionals; i++) { 229 DNSRecord rec = this.readAnswer(source); 230 if (rec != null) { 231 // Add a record, if we were able to create one. 232 _additionals.add(rec); 233 } 234 } 235 } 236 } catch (Exception e) { 237 logger.log(Level.WARNING, "DNSIncoming() dump " + print(true) + "\n exception ", e); 238 // This ugly but some JVM don't implement the cause on IOException 239 IOException ioe = new IOException("DNSIncoming corrupted message"); 240 ioe.initCause(e); 241 throw ioe; 242 } 243 } 244 DNSIncoming(int flags, int id, boolean multicast, DatagramPacket packet, long receivedTime)245 private DNSIncoming(int flags, int id, boolean multicast, DatagramPacket packet, long receivedTime) { 246 super(flags, id, multicast); 247 this._packet = packet; 248 this._messageInputStream = new MessageInputStream(packet.getData(), packet.getLength()); 249 this._receivedTime = receivedTime; 250 } 251 252 253 /* 254 * (non-Javadoc) 255 * 256 * @see java.lang.Object#clone() 257 */ 258 @Override clone()259 public DNSIncoming clone() { 260 DNSIncoming in = new DNSIncoming(this.getFlags(), this.getId(), this.isMulticast(), this._packet, this._receivedTime); 261 in._senderUDPPayload = this._senderUDPPayload; 262 in._questions.addAll(this._questions); 263 in._answers.addAll(this._answers); 264 in._authoritativeAnswers.addAll(this._authoritativeAnswers); 265 in._additionals.addAll(this._additionals); 266 return in; 267 } 268 269 readQuestion()270 private DNSQuestion readQuestion() { 271 String domain = _messageInputStream.readName(); 272 DNSRecordType type = DNSRecordType.typeForIndex(_messageInputStream.readUnsignedShort()); 273 if (type == DNSRecordType.TYPE_IGNORE) { 274 logger.log(Level.SEVERE, "Could not find record type: " + this.print(true)); 275 } 276 int recordClassIndex = _messageInputStream.readUnsignedShort(); 277 DNSRecordClass recordClass = DNSRecordClass.classForIndex(recordClassIndex); 278 boolean unique = recordClass.isUnique(recordClassIndex); 279 return DNSQuestion.newQuestion(domain, type, recordClass, unique); 280 } 281 readAnswer(InetAddress source)282 private DNSRecord readAnswer(InetAddress source) { 283 String domain = _messageInputStream.readName(); 284 DNSRecordType type = DNSRecordType.typeForIndex(_messageInputStream.readUnsignedShort()); 285 if (type == DNSRecordType.TYPE_IGNORE) { 286 logger.log(Level.SEVERE, "Could not find record type. domain: " + domain + "\n" + this.print(true)); 287 } 288 int recordClassIndex = _messageInputStream.readUnsignedShort(); 289 DNSRecordClass recordClass = (type == DNSRecordType.TYPE_OPT ? DNSRecordClass.CLASS_UNKNOWN : DNSRecordClass.classForIndex(recordClassIndex)); 290 if ((recordClass == DNSRecordClass.CLASS_UNKNOWN) && (type != DNSRecordType.TYPE_OPT)) { 291 logger.log(Level.SEVERE, "Could not find record class. domain: " + domain + " type: " + type + "\n" + this.print(true)); 292 } 293 boolean unique = recordClass.isUnique(recordClassIndex); 294 int ttl = _messageInputStream.readInt(); 295 int len = _messageInputStream.readUnsignedShort(); 296 DNSRecord rec = null; 297 298 switch (type) { 299 case TYPE_A: // IPv4 300 rec = new DNSRecord.IPv4Address(domain, recordClass, unique, ttl, _messageInputStream.readBytes(len)); 301 break; 302 case TYPE_AAAA: // IPv6 303 rec = new DNSRecord.IPv6Address(domain, recordClass, unique, ttl, _messageInputStream.readBytes(len)); 304 break; 305 case TYPE_CNAME: 306 case TYPE_PTR: 307 String service = ""; 308 service = _messageInputStream.readName(); 309 if (service.length() > 0) { 310 rec = new DNSRecord.Pointer(domain, recordClass, unique, ttl, service); 311 } else { 312 logger.log(Level.WARNING, "PTR record of class: " + recordClass + ", there was a problem reading the service name of the answer for domain:" + domain); 313 } 314 break; 315 case TYPE_TXT: 316 rec = new DNSRecord.Text(domain, recordClass, unique, ttl, _messageInputStream.readBytes(len)); 317 break; 318 case TYPE_SRV: 319 int priority = _messageInputStream.readUnsignedShort(); 320 int weight = _messageInputStream.readUnsignedShort(); 321 int port = _messageInputStream.readUnsignedShort(); 322 String target = ""; 323 // This is a hack to handle a bug in the BonjourConformanceTest 324 // It is sending out target strings that don't follow the "domain name" format. 325 if (USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET) { 326 target = _messageInputStream.readName(); 327 } else { 328 // [PJYF Nov 13 2010] Do we still need this? This looks really bad. All label are supposed to start by a length. 329 target = _messageInputStream.readNonNameString(); 330 } 331 rec = new DNSRecord.Service(domain, recordClass, unique, ttl, priority, weight, port, target); 332 break; 333 case TYPE_HINFO: 334 StringBuilder buf = new StringBuilder(); 335 buf.append(_messageInputStream.readUTF(len)); 336 int index = buf.indexOf(" "); 337 String cpu = (index > 0 ? buf.substring(0, index) : buf.toString()).trim(); 338 String os = (index > 0 ? buf.substring(index + 1) : "").trim(); 339 rec = new DNSRecord.HostInformation(domain, recordClass, unique, ttl, cpu, os); 340 break; 341 case TYPE_OPT: 342 DNSResultCode extendedResultCode = DNSResultCode.resultCodeForFlags(this.getFlags(), ttl); 343 int version = (ttl & 0x00ff0000) >> 16; 344 if (version == 0) { 345 _senderUDPPayload = recordClassIndex; 346 while (_messageInputStream.available() > 0) { 347 // Read RDData 348 int optionCodeInt = 0; 349 DNSOptionCode optionCode = null; 350 if (_messageInputStream.available() >= 2) { 351 optionCodeInt = _messageInputStream.readUnsignedShort(); 352 optionCode = DNSOptionCode.resultCodeForFlags(optionCodeInt); 353 } else { 354 logger.log(Level.WARNING, "There was a problem reading the OPT record. Ignoring."); 355 break; 356 } 357 int optionLength = 0; 358 if (_messageInputStream.available() >= 2) { 359 optionLength = _messageInputStream.readUnsignedShort(); 360 } else { 361 logger.log(Level.WARNING, "There was a problem reading the OPT record. Ignoring."); 362 break; 363 } 364 byte[] optiondata = new byte[0]; 365 if (_messageInputStream.available() >= optionLength) { 366 optiondata = _messageInputStream.readBytes(optionLength); 367 } 368 // 369 // We should really do something with those options. 370 switch (optionCode) { 371 case Owner: 372 // Valid length values are 8, 14, 18 and 20 373 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 374 // |Opt|Len|V|S|Primary MAC|Wakeup MAC | Password | 375 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 376 // 377 int ownerVersion = 0; 378 int ownerSequence = 0; 379 byte[] ownerPrimaryMacAddress = null; 380 byte[] ownerWakeupMacAddress = null; 381 byte[] ownerPassword = null; 382 try { 383 ownerVersion = optiondata[0]; 384 ownerSequence = optiondata[1]; 385 ownerPrimaryMacAddress = new byte[] { optiondata[2], optiondata[3], optiondata[4], optiondata[5], optiondata[6], optiondata[7] }; 386 ownerWakeupMacAddress = ownerPrimaryMacAddress; 387 if (optiondata.length > 8) { 388 // We have a wakeupMacAddress. 389 ownerWakeupMacAddress = new byte[] { optiondata[8], optiondata[9], optiondata[10], optiondata[11], optiondata[12], optiondata[13] }; 390 } 391 if (optiondata.length == 18) { 392 // We have a short password. 393 ownerPassword = new byte[] { optiondata[14], optiondata[15], optiondata[16], optiondata[17] }; 394 } 395 if (optiondata.length == 22) { 396 // We have a long password. 397 ownerPassword = new byte[] { optiondata[14], optiondata[15], optiondata[16], optiondata[17], optiondata[18], optiondata[19], optiondata[20], optiondata[21] }; 398 } 399 } catch (Exception exception) { 400 logger.warning("Malformed OPT answer. Option code: Owner data: " + this._hexString(optiondata)); 401 } 402 if (logger.isLoggable(Level.FINE)) { 403 logger.fine("Unhandled Owner OPT version: " + ownerVersion + " sequence: " + ownerSequence + " MAC address: " + this._hexString(ownerPrimaryMacAddress) 404 + (ownerWakeupMacAddress != ownerPrimaryMacAddress ? " wakeup MAC address: " + this._hexString(ownerWakeupMacAddress) : "") + (ownerPassword != null ? " password: " + this._hexString(ownerPassword) : "")); 405 } 406 break; 407 case LLQ: 408 case NSID: 409 case UL: 410 if (logger.isLoggable(Level.FINE)) { 411 logger.log(Level.FINE, "There was an OPT answer. Option code: " + optionCode + " data: " + this._hexString(optiondata)); 412 } 413 break; 414 case Unknown: 415 logger.log(Level.WARNING, "There was an OPT answer. Not currently handled. Option code: " + optionCodeInt + " data: " + this._hexString(optiondata)); 416 break; 417 default: 418 // This is to keep the compiler happy. 419 break; 420 } 421 } 422 } else { 423 logger.log(Level.WARNING, "There was an OPT answer. Wrong version number: " + version + " result code: " + extendedResultCode); 424 } 425 break; 426 default: 427 if (logger.isLoggable(Level.FINER)) { 428 logger.finer("DNSIncoming() unknown type:" + type); 429 } 430 _messageInputStream.skip(len); 431 break; 432 } 433 if (rec != null) { 434 rec.setRecordSource(source); 435 } 436 return rec; 437 } 438 439 /** 440 * Debugging. 441 */ print(boolean dump)442 String print(boolean dump) { 443 StringBuilder buf = new StringBuilder(); 444 buf.append(this.print()); 445 if (dump) { 446 byte[] data = new byte[_packet.getLength()]; 447 System.arraycopy(_packet.getData(), 0, data, 0, data.length); 448 buf.append(this.print(data)); 449 } 450 return buf.toString(); 451 } 452 453 @Override toString()454 public String toString() { 455 StringBuilder buf = new StringBuilder(); 456 buf.append(isQuery() ? "dns[query," : "dns[response,"); 457 if (_packet.getAddress() != null) { 458 buf.append(_packet.getAddress().getHostAddress()); 459 } 460 buf.append(':'); 461 buf.append(_packet.getPort()); 462 buf.append(", length="); 463 buf.append(_packet.getLength()); 464 buf.append(", id=0x"); 465 buf.append(Integer.toHexString(this.getId())); 466 if (this.getFlags() != 0) { 467 buf.append(", flags=0x"); 468 buf.append(Integer.toHexString(this.getFlags())); 469 if ((this.getFlags() & DNSConstants.FLAGS_QR_RESPONSE) != 0) { 470 buf.append(":r"); 471 } 472 if ((this.getFlags() & DNSConstants.FLAGS_AA) != 0) { 473 buf.append(":aa"); 474 } 475 if ((this.getFlags() & DNSConstants.FLAGS_TC) != 0) { 476 buf.append(":tc"); 477 } 478 } 479 if (this.getNumberOfQuestions() > 0) { 480 buf.append(", questions="); 481 buf.append(this.getNumberOfQuestions()); 482 } 483 if (this.getNumberOfAnswers() > 0) { 484 buf.append(", answers="); 485 buf.append(this.getNumberOfAnswers()); 486 } 487 if (this.getNumberOfAuthorities() > 0) { 488 buf.append(", authorities="); 489 buf.append(this.getNumberOfAuthorities()); 490 } 491 if (this.getNumberOfAdditionals() > 0) { 492 buf.append(", additionals="); 493 buf.append(this.getNumberOfAdditionals()); 494 } 495 if (this.getNumberOfQuestions() > 0) { 496 buf.append("\nquestions:"); 497 for (DNSQuestion question : _questions) { 498 buf.append("\n\t"); 499 buf.append(question); 500 } 501 } 502 if (this.getNumberOfAnswers() > 0) { 503 buf.append("\nanswers:"); 504 for (DNSRecord record : _answers) { 505 buf.append("\n\t"); 506 buf.append(record); 507 } 508 } 509 if (this.getNumberOfAuthorities() > 0) { 510 buf.append("\nauthorities:"); 511 for (DNSRecord record : _authoritativeAnswers) { 512 buf.append("\n\t"); 513 buf.append(record); 514 } 515 } 516 if (this.getNumberOfAdditionals() > 0) { 517 buf.append("\nadditionals:"); 518 for (DNSRecord record : _additionals) { 519 buf.append("\n\t"); 520 buf.append(record); 521 } 522 } 523 buf.append("]"); 524 return buf.toString(); 525 } 526 527 /** 528 * Appends answers to this Incoming. 529 * 530 * @exception IllegalArgumentException 531 * If not a query or if Truncated. 532 */ append(DNSIncoming that)533 void append(DNSIncoming that) { 534 if (this.isQuery() && this.isTruncated() && that.isQuery()) { 535 this._questions.addAll(that.getQuestions()); 536 this._answers.addAll(that.getAnswers()); 537 this._authoritativeAnswers.addAll(that.getAuthorities()); 538 this._additionals.addAll(that.getAdditionals()); 539 } else { 540 throw new IllegalArgumentException(); 541 } 542 } 543 elapseSinceArrival()544 public int elapseSinceArrival() { 545 return (int) (System.currentTimeMillis() - _receivedTime); 546 } 547 548 /** 549 * This will return the default UDP payload except if an OPT record was found with a different size. 550 * 551 * @return the senderUDPPayload 552 */ getSenderUDPPayload()553 public int getSenderUDPPayload() { 554 return this._senderUDPPayload; 555 } 556 557 private static final char[] _nibbleToHex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 558 559 /** 560 * Returns a hex-string for printing 561 * 562 * @param bytes 563 * @return Returns a hex-string which can be used within a SQL expression 564 */ _hexString(byte[] bytes)565 private String _hexString(byte[] bytes) { 566 567 StringBuilder result = new StringBuilder(2 * bytes.length); 568 569 for (int i = 0; i < bytes.length; i++) { 570 int b = bytes[i] & 0xFF; 571 result.append(_nibbleToHex[b / 16]); 572 result.append(_nibbleToHex[b % 16]); 573 } 574 575 return result.toString(); 576 } 577 578 } 579