DNSIncoming.java
Go to the documentation of this file.
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 }


zeroconf_jmdns_suite
Author(s): Daniel Stonier
autogenerated on Thu Aug 27 2015 15:50:27