Home | History | Annotate | Download | only in impl
      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 
     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          */
     49         public MessageInputStream(byte[] buffer, int offset, int length) {
     50             super(buffer, offset, length);
     51             _names = new HashMap<Integer, String>();
     52         }
     53 
     54         public int readByte() {
     55             return this.read();
     56         }
     57 
     58         public int readUnsignedShort() {
     59             return (this.read() << 8) | this.read();
     60         }
     61 
     62         public int readInt() {
     63             return (this.readUnsignedShort() << 16) | this.readUnsignedShort();
     64         }
     65 
     66         public byte[] readBytes(int len) {
     67             byte bytes[] = new byte[len];
     68             this.read(bytes, 0, len);
     69             return bytes;
     70         }
     71 
     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 
    110         protected synchronized int peek() {
    111             return (pos < count) ? (buf[pos] & 0xff) : -1;
    112         }
    113 
    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 
    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      */
    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 
    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
    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 
    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 
    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      */
    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
    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      */
    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 
    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      */
    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      */
    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