$search
00001 // /Copyright 2003-2005 Arthur van Hoff, Rick Blair 00002 // Licensed under Apache License version 2.0 00003 // Original license LGPL 00004 00005 package javax.jmdns.impl; 00006 00007 import java.io.ByteArrayInputStream; 00008 import java.io.IOException; 00009 import java.net.DatagramPacket; 00010 import java.net.Inet4Address; 00011 import java.net.InetAddress; 00012 import java.util.HashMap; 00013 import java.util.Map; 00014 import java.util.logging.Level; 00015 import java.util.logging.Logger; 00016 00017 import javax.jmdns.impl.constants.DNSConstants; 00018 import javax.jmdns.impl.constants.DNSLabel; 00019 import javax.jmdns.impl.constants.DNSOptionCode; 00020 import javax.jmdns.impl.constants.DNSRecordClass; 00021 import javax.jmdns.impl.constants.DNSRecordType; 00022 import javax.jmdns.impl.constants.DNSResultCode; 00023 00029 public final class DNSIncoming extends DNSMessage { 00030 private static Logger logger = Logger.getLogger(DNSIncoming.class.getName()); 00031 00032 // This is a hack to handle a bug in the BonjourConformanceTest 00033 // It is sending out target strings that don't follow the "domain name" format. 00034 public static boolean USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET = true; 00035 00036 public static class MessageInputStream extends ByteArrayInputStream { 00037 private static Logger logger1 = Logger.getLogger(MessageInputStream.class.getName()); 00038 00039 final Map<Integer, String> _names; 00040 00041 public MessageInputStream(byte[] buffer, int length) { 00042 this(buffer, 0, length); 00043 } 00044 00050 public MessageInputStream(byte[] buffer, int offset, int length) { 00051 super(buffer, offset, length); 00052 _names = new HashMap<Integer, String>(); 00053 } 00054 00055 public int readByte() { 00056 return this.read(); 00057 } 00058 00059 public int readUnsignedShort() { 00060 return (this.read() << 8) | this.read(); 00061 } 00062 00063 public int readInt() { 00064 return (this.readUnsignedShort() << 16) | this.readUnsignedShort(); 00065 } 00066 00067 public byte[] readBytes(int len) { 00068 byte bytes[] = new byte[len]; 00069 this.read(bytes, 0, len); 00070 return bytes; 00071 } 00072 00073 public String readUTF(int len) { 00074 StringBuilder buffer = new StringBuilder(len); 00075 for (int index = 0; index < len; index++) { 00076 int ch = this.read(); 00077 switch (ch >> 4) { 00078 case 0: 00079 case 1: 00080 case 2: 00081 case 3: 00082 case 4: 00083 case 5: 00084 case 6: 00085 case 7: 00086 // 0xxxxxxx 00087 break; 00088 case 12: 00089 case 13: 00090 // 110x xxxx 10xx xxxx 00091 ch = ((ch & 0x1F) << 6) | (this.read() & 0x3F); 00092 index++; 00093 break; 00094 case 14: 00095 // 1110 xxxx 10xx xxxx 10xx xxxx 00096 ch = ((ch & 0x0f) << 12) | ((this.read() & 0x3F) << 6) | (this.read() & 0x3F); 00097 index++; 00098 index++; 00099 break; 00100 default: 00101 // 10xx xxxx, 1111 xxxx 00102 ch = ((ch & 0x3F) << 4) | (this.read() & 0x0f); 00103 index++; 00104 break; 00105 } 00106 buffer.append((char) ch); 00107 } 00108 return buffer.toString(); 00109 } 00110 00111 protected synchronized int peek() { 00112 return (pos < count) ? (buf[pos] & 0xff) : -1; 00113 } 00114 00115 public String readName() { 00116 Map<Integer, StringBuilder> names = new HashMap<Integer, StringBuilder>(); 00117 StringBuilder buffer = new StringBuilder(); 00118 boolean finished = false; 00119 while (!finished) { 00120 int len = this.read(); 00121 if (len == 0) { 00122 finished = true; 00123 break; 00124 } 00125 switch (DNSLabel.labelForByte(len)) { 00126 case Standard: 00127 int offset = pos - 1; 00128 String label = this.readUTF(len) + "."; 00129 buffer.append(label); 00130 for (StringBuilder previousLabel : names.values()) { 00131 previousLabel.append(label); 00132 } 00133 names.put(Integer.valueOf(offset), new StringBuilder(label)); 00134 break; 00135 case Compressed: 00136 int index = (DNSLabel.labelValue(len) << 8) | this.read(); 00137 String compressedLabel = _names.get(Integer.valueOf(index)); 00138 if (compressedLabel == null) { 00139 logger1.severe("bad domain name: possible circular name detected. Bad offset: 0x" + Integer.toHexString(index) + " at 0x" + Integer.toHexString(pos - 2)); 00140 compressedLabel = ""; 00141 } 00142 buffer.append(compressedLabel); 00143 for (StringBuilder previousLabel : names.values()) { 00144 previousLabel.append(compressedLabel); 00145 } 00146 finished = true; 00147 break; 00148 case Extended: 00149 // int extendedLabelClass = DNSLabel.labelValue(len); 00150 logger1.severe("Extended label are not currently supported."); 00151 break; 00152 case Unknown: 00153 default: 00154 logger1.severe("unsupported dns label type: '" + Integer.toHexString(len & 0xC0) + "'"); 00155 } 00156 } 00157 for (Integer index : names.keySet()) { 00158 _names.put(index, names.get(index).toString()); 00159 } 00160 return buffer.toString(); 00161 } 00162 00163 public String readNonNameString() { 00164 int len = this.read(); 00165 return this.readUTF(len); 00166 } 00167 00168 } 00169 00170 private final DatagramPacket _packet; 00171 00172 private final long _receivedTime; 00173 00174 private final MessageInputStream _messageInputStream; 00175 00176 private int _senderUDPPayload; 00177 00184 public DNSIncoming(DatagramPacket packet) throws IOException { 00185 super(0, 0, packet.getPort() == DNSConstants.MDNS_PORT); 00186 this._packet = packet; 00187 InetAddress source = packet.getAddress(); 00188 this._messageInputStream = new MessageInputStream(packet.getData(), packet.getLength()); 00189 this._receivedTime = System.currentTimeMillis(); 00190 this._senderUDPPayload = DNSConstants.MAX_MSG_TYPICAL; 00191 00192 try { 00193 this.setId(_messageInputStream.readUnsignedShort()); 00194 this.setFlags(_messageInputStream.readUnsignedShort()); 00195 int numQuestions = _messageInputStream.readUnsignedShort(); 00196 int numAnswers = _messageInputStream.readUnsignedShort(); 00197 int numAuthorities = _messageInputStream.readUnsignedShort(); 00198 int numAdditionals = _messageInputStream.readUnsignedShort(); 00199 00200 // parse questions 00201 if (numQuestions > 0) { 00202 for (int i = 0; i < numQuestions; i++) { 00203 _questions.add(this.readQuestion()); 00204 } 00205 } 00206 00207 // parse answers 00208 if (numAnswers > 0) { 00209 for (int i = 0; i < numAnswers; i++) { 00210 DNSRecord rec = this.readAnswer(source); 00211 if (rec != null) { 00212 // Add a record, if we were able to create one. 00213 _answers.add(rec); 00214 } 00215 } 00216 } 00217 00218 if (numAuthorities > 0) { 00219 for (int i = 0; i < numAuthorities; i++) { 00220 DNSRecord rec = this.readAnswer(source); 00221 if (rec != null) { 00222 // Add a record, if we were able to create one. 00223 _authoritativeAnswers.add(rec); 00224 } 00225 } 00226 } 00227 00228 if (numAdditionals > 0) { 00229 for (int i = 0; i < numAdditionals; i++) { 00230 DNSRecord rec = this.readAnswer(source); 00231 if (rec != null) { 00232 // Add a record, if we were able to create one. 00233 _additionals.add(rec); 00234 } 00235 } 00236 } 00237 } catch (Exception e) { 00238 logger.log(Level.WARNING, "DNSIncoming() dump " + print(true) + "\n exception ", e); 00239 // This ugly but some JVM don't implement the cause on IOException 00240 IOException ioe = new IOException("DNSIncoming corrupted message"); 00241 ioe.initCause(e); 00242 throw ioe; 00243 } 00244 } 00245 00246 private DNSIncoming(int flags, int id, boolean multicast, DatagramPacket packet, long receivedTime) { 00247 super(flags, id, multicast); 00248 this._packet = packet; 00249 this._messageInputStream = new MessageInputStream(packet.getData(), packet.getLength()); 00250 this._receivedTime = receivedTime; 00251 } 00252 00253 00254 /* 00255 * (non-Javadoc) 00256 * 00257 * @see java.lang.Object#clone() 00258 */ 00259 @Override 00260 public DNSIncoming clone() { 00261 DNSIncoming in = new DNSIncoming(this.getFlags(), this.getId(), this.isMulticast(), this._packet, this._receivedTime); 00262 in._senderUDPPayload = this._senderUDPPayload; 00263 in._questions.addAll(this._questions); 00264 in._answers.addAll(this._answers); 00265 in._authoritativeAnswers.addAll(this._authoritativeAnswers); 00266 in._additionals.addAll(this._additionals); 00267 return in; 00268 } 00269 00270 00271 private DNSQuestion readQuestion() { 00272 String domain = _messageInputStream.readName(); 00273 DNSRecordType type = DNSRecordType.typeForIndex(_messageInputStream.readUnsignedShort()); 00274 if (type == DNSRecordType.TYPE_IGNORE) { 00275 logger.log(Level.SEVERE, "Could not find record type: " + this.print(true)); 00276 } 00277 int recordClassIndex = _messageInputStream.readUnsignedShort(); 00278 DNSRecordClass recordClass = DNSRecordClass.classForIndex(recordClassIndex); 00279 boolean unique = recordClass.isUnique(recordClassIndex); 00280 return DNSQuestion.newQuestion(domain, type, recordClass, unique); 00281 } 00282 00283 private DNSRecord readAnswer(InetAddress source) { 00284 String domain = _messageInputStream.readName(); 00285 int type_value = _messageInputStream.readUnsignedShort(); 00286 DNSRecordType type = DNSRecordType.typeForIndex(type_value); 00287 if (type == DNSRecordType.TYPE_IGNORE) { 00288 logger.log(Level.SEVERE, "Could not find record type. domain: " + domain + "\n" + this.print(true)); 00289 } 00290 int recordClassIndex = _messageInputStream.readUnsignedShort(); 00291 DNSRecordClass recordClass = (type == DNSRecordType.TYPE_OPT ? DNSRecordClass.CLASS_UNKNOWN : DNSRecordClass.classForIndex(recordClassIndex)); 00292 if ((recordClass == DNSRecordClass.CLASS_UNKNOWN) && (type != DNSRecordType.TYPE_OPT)) { 00293 logger.log(Level.SEVERE, "Could not find record class. domain: " + domain + " type: " + type + "\n" + this.print(true)); 00294 } 00295 boolean unique = recordClass.isUnique(recordClassIndex); 00296 int ttl = _messageInputStream.readInt(); 00297 int len = _messageInputStream.readUnsignedShort(); 00298 DNSRecord rec = null; 00299 00300 switch (type) { 00301 case TYPE_A: // IPv4 00302 rec = new DNSRecord.IPv4Address(domain, recordClass, unique, ttl, _messageInputStream.readBytes(len)); 00303 //System.out.printf(" Answer: [%s] %s\n", _packet.getAddress().getHostAddress(), rec.toString()); 00304 break; 00305 case TYPE_AAAA: // IPv6 00306 rec = new DNSRecord.IPv6Address(domain, recordClass, unique, ttl, _messageInputStream.readBytes(len)); // last bit is raw address 00307 //System.out.printf(" Answer: [%s] %s\n", _packet.getAddress().getHostAddress(), rec.toString()); 00308 break; 00309 case TYPE_CNAME: 00310 case TYPE_PTR: 00311 String service = ""; 00312 service = _messageInputStream.readName(); 00313 if (service.length() > 0) { 00314 rec = new DNSRecord.Pointer(domain, recordClass, unique, ttl, service); 00315 } else { 00316 logger.log(Level.WARNING, "PTR record of class: " + recordClass + ", there was a problem reading the service name of the answer for domain:" + domain); 00317 } 00318 break; 00319 case TYPE_TXT: 00320 rec = new DNSRecord.Text(domain, recordClass, unique, ttl, _messageInputStream.readBytes(len)); 00321 break; 00322 case TYPE_SRV: 00323 int priority = _messageInputStream.readUnsignedShort(); 00324 int weight = _messageInputStream.readUnsignedShort(); 00325 int port = _messageInputStream.readUnsignedShort(); 00326 String target = ""; 00327 // This is a hack to handle a bug in the BonjourConformanceTest 00328 // It is sending out target strings that don't follow the "domain name" format. 00329 if (USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET) { 00330 target = _messageInputStream.readName(); 00331 } else { 00332 // [PJYF Nov 13 2010] Do we still need this? This looks really bad. All label are supposed to start by a length. 00333 target = _messageInputStream.readNonNameString(); 00334 } 00335 rec = new DNSRecord.Service(domain, recordClass, unique, ttl, priority, weight, port, target); 00336 //System.out.printf(" Answer: [%s] %s\n", _packet.getAddress().getHostAddress(), rec.toString()); 00337 break; 00338 case TYPE_HINFO: 00339 StringBuilder buf = new StringBuilder(); 00340 buf.append(_messageInputStream.readUTF(len)); 00341 int index = buf.indexOf(" "); 00342 String cpu = (index > 0 ? buf.substring(0, index) : buf.toString()).trim(); 00343 String os = (index > 0 ? buf.substring(index + 1) : "").trim(); 00344 rec = new DNSRecord.HostInformation(domain, recordClass, unique, ttl, cpu, os); 00345 break; 00346 case TYPE_OPT: 00347 DNSResultCode extendedResultCode = DNSResultCode.resultCodeForFlags(this.getFlags(), ttl); 00348 int version = (ttl & 0x00ff0000) >> 16; 00349 if (version == 0) { 00350 _senderUDPPayload = recordClassIndex; 00351 while (_messageInputStream.available() > 0) { 00352 // Read RDData 00353 int optionCodeInt = 0; 00354 DNSOptionCode optionCode = null; 00355 if (_messageInputStream.available() >= 2) { 00356 optionCodeInt = _messageInputStream.readUnsignedShort(); 00357 optionCode = DNSOptionCode.resultCodeForFlags(optionCodeInt); 00358 } else { 00359 logger.log(Level.WARNING, "There was a problem reading the OPT record. Ignoring."); 00360 break; 00361 } 00362 int optionLength = 0; 00363 if (_messageInputStream.available() >= 2) { 00364 optionLength = _messageInputStream.readUnsignedShort(); 00365 } else { 00366 logger.log(Level.WARNING, "There was a problem reading the OPT record. Ignoring."); 00367 break; 00368 } 00369 byte[] optiondata = new byte[0]; 00370 if (_messageInputStream.available() >= optionLength) { 00371 optiondata = _messageInputStream.readBytes(optionLength); 00372 } 00373 // 00374 // We should really do something with those options. 00375 switch (optionCode) { 00376 case Owner: 00377 // Valid length values are 8, 14, 18 and 20 00378 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 00379 // |Opt|Len|V|S|Primary MAC|Wakeup MAC | Password | 00380 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 00381 // 00382 int ownerVersion = 0; 00383 int ownerSequence = 0; 00384 byte[] ownerPrimaryMacAddress = null; 00385 byte[] ownerWakeupMacAddress = null; 00386 byte[] ownerPassword = null; 00387 try { 00388 ownerVersion = optiondata[0]; 00389 ownerSequence = optiondata[1]; 00390 ownerPrimaryMacAddress = new byte[] { optiondata[2], optiondata[3], optiondata[4], optiondata[5], optiondata[6], optiondata[7] }; 00391 ownerWakeupMacAddress = ownerPrimaryMacAddress; 00392 if (optiondata.length > 8) { 00393 // We have a wakeupMacAddress. 00394 ownerWakeupMacAddress = new byte[] { optiondata[8], optiondata[9], optiondata[10], optiondata[11], optiondata[12], optiondata[13] }; 00395 } 00396 if (optiondata.length == 18) { 00397 // We have a short password. 00398 ownerPassword = new byte[] { optiondata[14], optiondata[15], optiondata[16], optiondata[17] }; 00399 } 00400 if (optiondata.length == 22) { 00401 // We have a long password. 00402 ownerPassword = new byte[] { optiondata[14], optiondata[15], optiondata[16], optiondata[17], optiondata[18], optiondata[19], optiondata[20], optiondata[21] }; 00403 } 00404 } catch (Exception exception) { 00405 logger.warning("Malformed OPT answer. Option code: Owner data: " + this._hexString(optiondata)); 00406 } 00407 if (logger.isLoggable(Level.FINE)) { 00408 logger.fine("Unhandled Owner OPT version: " + ownerVersion + " sequence: " + ownerSequence + " MAC address: " + this._hexString(ownerPrimaryMacAddress) 00409 + (ownerWakeupMacAddress != ownerPrimaryMacAddress ? " wakeup MAC address: " + this._hexString(ownerWakeupMacAddress) : "") + (ownerPassword != null ? " password: " + this._hexString(ownerPassword) : "")); 00410 } 00411 break; 00412 case LLQ: 00413 case NSID: 00414 case UL: 00415 if (logger.isLoggable(Level.FINE)) { 00416 logger.log(Level.FINE, "There was an OPT answer. Option code: " + optionCode + " data: " + this._hexString(optiondata)); 00417 } 00418 break; 00419 case Unknown: 00420 logger.log(Level.WARNING, "There was an OPT answer. Not currently handled. Option code: " + optionCodeInt + " data: " + this._hexString(optiondata)); 00421 break; 00422 default: 00423 // This is to keep the compiler happy. 00424 break; 00425 } 00426 } 00427 } else { 00428 logger.log(Level.WARNING, "There was an OPT answer. Wrong version number: " + version + " result code: " + extendedResultCode); 00429 } 00430 break; 00431 default: 00432 if (logger.isLoggable(Level.FINER)) { 00433 logger.finer("DNSIncoming() unknown type:" + type); 00434 } 00435 _messageInputStream.skip(len); 00436 break; 00437 } 00438 if (rec != null) { 00439 rec.setRecordSource(source); 00440 } 00441 return rec; 00442 } 00443 00447 String print(boolean dump) { 00448 StringBuilder buf = new StringBuilder(); 00449 buf.append(this.print()); 00450 if (dump) { 00451 byte[] data = new byte[_packet.getLength()]; 00452 System.arraycopy(_packet.getData(), 0, data, 0, data.length); 00453 buf.append(this.print(data)); 00454 } 00455 return buf.toString(); 00456 } 00457 00458 @Override 00459 public String toString() { 00460 StringBuilder buf = new StringBuilder(); 00461 buf.append(isQuery() ? "dns[query," : "dns[response,"); 00462 if (_packet.getAddress() != null) { 00463 buf.append(_packet.getAddress().getHostAddress()); 00464 } 00465 buf.append(':'); 00466 buf.append(_packet.getPort()); 00467 buf.append(", length="); 00468 buf.append(_packet.getLength()); 00469 buf.append(", id=0x"); 00470 buf.append(Integer.toHexString(this.getId())); 00471 if (this.getFlags() != 0) { 00472 buf.append(", flags=0x"); 00473 buf.append(Integer.toHexString(this.getFlags())); 00474 if ((this.getFlags() & DNSConstants.FLAGS_QR_RESPONSE) != 0) { 00475 buf.append(":r"); 00476 } 00477 if ((this.getFlags() & DNSConstants.FLAGS_AA) != 0) { 00478 buf.append(":aa"); 00479 } 00480 if ((this.getFlags() & DNSConstants.FLAGS_TC) != 0) { 00481 buf.append(":tc"); 00482 } 00483 } 00484 if (this.getNumberOfQuestions() > 0) { 00485 buf.append(", questions="); 00486 buf.append(this.getNumberOfQuestions()); 00487 } 00488 if (this.getNumberOfAnswers() > 0) { 00489 buf.append(", answers="); 00490 buf.append(this.getNumberOfAnswers()); 00491 } 00492 if (this.getNumberOfAuthorities() > 0) { 00493 buf.append(", authorities="); 00494 buf.append(this.getNumberOfAuthorities()); 00495 } 00496 if (this.getNumberOfAdditionals() > 0) { 00497 buf.append(", additionals="); 00498 buf.append(this.getNumberOfAdditionals()); 00499 } 00500 if (this.getNumberOfQuestions() > 0) { 00501 buf.append("\nquestions:"); 00502 for (DNSQuestion question : _questions) { 00503 buf.append("\n\t"); 00504 buf.append(question); 00505 } 00506 } 00507 if (this.getNumberOfAnswers() > 0) { 00508 buf.append("\nanswers:"); 00509 for (DNSRecord record : _answers) { 00510 buf.append("\n\t"); 00511 buf.append(record); 00512 } 00513 } 00514 if (this.getNumberOfAuthorities() > 0) { 00515 buf.append("\nauthorities:"); 00516 for (DNSRecord record : _authoritativeAnswers) { 00517 buf.append("\n\t"); 00518 buf.append(record); 00519 } 00520 } 00521 if (this.getNumberOfAdditionals() > 0) { 00522 buf.append("\nadditionals:"); 00523 for (DNSRecord record : _additionals) { 00524 buf.append("\n\t"); 00525 buf.append(record); 00526 } 00527 } 00528 buf.append("]"); 00529 return buf.toString(); 00530 } 00531 00538 void append(DNSIncoming that) { 00539 if (this.isQuery() && this.isTruncated() && that.isQuery()) { 00540 this._questions.addAll(that.getQuestions()); 00541 this._answers.addAll(that.getAnswers()); 00542 this._authoritativeAnswers.addAll(that.getAuthorities()); 00543 this._additionals.addAll(that.getAdditionals()); 00544 } else { 00545 throw new IllegalArgumentException(); 00546 } 00547 } 00548 00549 public int elapseSinceArrival() { 00550 return (int) (System.currentTimeMillis() - _receivedTime); 00551 } 00552 00558 public int getSenderUDPPayload() { 00559 return this._senderUDPPayload; 00560 } 00561 00562 private static final char[] _nibbleToHex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 00563 00570 private String _hexString(byte[] bytes) { 00571 00572 StringBuilder result = new StringBuilder(2 * bytes.length); 00573 00574 for (int i = 0; i < bytes.length; i++) { 00575 int b = bytes[i] & 0xFF; 00576 result.append(_nibbleToHex[b / 16]); 00577 result.append(_nibbleToHex[b % 16]); 00578 } 00579 00580 return result.toString(); 00581 } 00582 00583 }