| /** |
| * Copyright 2003-2005 Arthur van Hoff, Rick Blair |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.activemq.jmdns; |
| |
| import java.io.IOException; |
| import java.net.DatagramPacket; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * Parse an incoming DNS message into its components. |
| * |
| * @version %I%, %G% |
| * @author Arthur van Hoff, Werner Randelshofer, Pierre Frisch |
| */ |
| final class DNSIncoming |
| { |
| private static Logger logger = Logger.getLogger(DNSIncoming.class.toString()); |
| // Implementation note: This vector should be immutable. |
| // If a client of DNSIncoming changes the contents of this vector, |
| // we get undesired results. To fix this, we have to migrate to |
| // the Collections API of Java 1.2. i.e we replace Vector by List. |
| // final static Vector EMPTY = new Vector(); |
| |
| private DatagramPacket packet; |
| private int off; |
| private int len; |
| private byte data[]; |
| |
| int id; |
| private int flags; |
| private int numQuestions; |
| int numAnswers; |
| private int numAuthorities; |
| private int numAdditionals; |
| private long receivedTime; |
| |
| List questions; |
| List answers; |
| |
| /** |
| * Parse a message from a datagram packet. |
| */ |
| DNSIncoming(DatagramPacket packet) throws IOException |
| { |
| this.packet = packet; |
| this.data = packet.getData(); |
| this.len = packet.getLength(); |
| this.off = packet.getOffset(); |
| this.questions = Collections.EMPTY_LIST; |
| this.answers = Collections.EMPTY_LIST; |
| this.receivedTime = System.currentTimeMillis(); |
| |
| try |
| { |
| id = readUnsignedShort(); |
| flags = readUnsignedShort(); |
| numQuestions = readUnsignedShort(); |
| numAnswers = readUnsignedShort(); |
| numAuthorities = readUnsignedShort(); |
| numAdditionals = readUnsignedShort(); |
| |
| // parse questions |
| if (numQuestions > 0) |
| { |
| questions = Collections.synchronizedList(new ArrayList(numQuestions)); |
| for (int i = 0; i < numQuestions; i++) |
| { |
| DNSQuestion question = new DNSQuestion(readName(), readUnsignedShort(), readUnsignedShort()); |
| questions.add(question); |
| } |
| } |
| |
| // parse answers |
| int n = numAnswers + numAuthorities + numAdditionals; |
| if (n > 0) |
| { |
| answers = Collections.synchronizedList(new ArrayList(n)); |
| for (int i = 0; i < n; i++) |
| { |
| String domain = readName(); |
| int type = readUnsignedShort(); |
| int clazz = readUnsignedShort(); |
| int ttl = readInt(); |
| int len = readUnsignedShort(); |
| int end = off + len; |
| DNSRecord rec = null; |
| |
| switch (type) |
| { |
| case DNSConstants.TYPE_A: // IPv4 |
| case DNSConstants.TYPE_AAAA: // IPv6 FIXME [PJYF Oct 14 2004] This has not been tested |
| rec = new DNSRecord.Address(domain, type, clazz, ttl, readBytes(off, len)); |
| break; |
| case DNSConstants.TYPE_CNAME: |
| case DNSConstants.TYPE_PTR: |
| rec = new DNSRecord.Pointer(domain, type, clazz, ttl, readName()); |
| break; |
| case DNSConstants.TYPE_TXT: |
| rec = new DNSRecord.Text(domain, type, clazz, ttl, readBytes(off, len)); |
| break; |
| case DNSConstants.TYPE_SRV: |
| rec = new DNSRecord.Service(domain, type, clazz, ttl, |
| readUnsignedShort(), readUnsignedShort(), readUnsignedShort(), readName()); |
| break; |
| case DNSConstants.TYPE_HINFO: |
| // Maybe we should do something with those |
| break; |
| default : |
| logger.finer("DNSIncoming() unknown type:" + type); |
| break; |
| } |
| |
| if (rec != null) |
| { |
| // Add a record, if we were able to create one. |
| answers.add(rec); |
| } |
| else |
| { |
| // Addjust the numbers for the skipped record |
| if (answers.size() < numAnswers) |
| { |
| numAnswers--; |
| } |
| else |
| { |
| if (answers.size() < numAnswers + numAuthorities) |
| { |
| numAuthorities--; |
| } |
| else |
| { |
| if (answers.size() < numAnswers + numAuthorities + numAdditionals) |
| { |
| numAdditionals--; |
| } |
| } |
| } |
| } |
| off = end; |
| } |
| } |
| } |
| catch (IOException e) |
| { |
| logger.log(Level.WARNING, "DNSIncoming() dump " + print(true) + "\n exception ", e); |
| throw e; |
| } |
| } |
| |
| /** |
| * Check if the message is a query. |
| */ |
| boolean isQuery() |
| { |
| return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_QUERY; |
| } |
| |
| /** |
| * Check if the message is truncated. |
| */ |
| boolean isTruncated() |
| { |
| return (flags & DNSConstants.FLAGS_TC) != 0; |
| } |
| |
| /** |
| * Check if the message is a response. |
| */ |
| boolean isResponse() |
| { |
| return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_RESPONSE; |
| } |
| |
| private int get(int off) throws IOException |
| { |
| if ((off < 0) || (off >= len)) |
| { |
| throw new IOException("parser error: offset=" + off); |
| } |
| return data[off] & 0xFF; |
| } |
| |
| private int readUnsignedShort() throws IOException |
| { |
| return (get(off++) << 8) + get(off++); |
| } |
| |
| private int readInt() throws IOException |
| { |
| return (readUnsignedShort() << 16) + readUnsignedShort(); |
| } |
| |
| private byte[] readBytes(int off, int len) throws IOException |
| { |
| byte bytes[] = new byte[len]; |
| System.arraycopy(data, off, bytes, 0, len); |
| return bytes; |
| } |
| |
| private void readUTF(StringBuffer buf, int off, int len) throws IOException |
| { |
| for (int end = off + len; off < end;) |
| { |
| int ch = get(off++); |
| switch (ch >> 4) |
| { |
| case 0: |
| case 1: |
| case 2: |
| case 3: |
| case 4: |
| case 5: |
| case 6: |
| case 7: |
| // 0xxxxxxx |
| break; |
| case 12: |
| case 13: |
| // 110x xxxx 10xx xxxx |
| ch = ((ch & 0x1F) << 6) | (get(off++) & 0x3F); |
| break; |
| case 14: |
| // 1110 xxxx 10xx xxxx 10xx xxxx |
| ch = ((ch & 0x0f) << 12) | ((get(off++) & 0x3F) << 6) | (get(off++) & 0x3F); |
| break; |
| default: |
| // 10xx xxxx, 1111 xxxx |
| ch = ((ch & 0x3F) << 4) | (get(off++) & 0x0f); |
| break; |
| } |
| buf.append((char) ch); |
| } |
| } |
| |
| private String readName() throws IOException |
| { |
| StringBuffer buf = new StringBuffer(); |
| int off = this.off; |
| int next = -1; |
| int first = off; |
| |
| while (true) |
| { |
| int len = get(off++); |
| if (len == 0) |
| { |
| break; |
| } |
| switch (len & 0xC0) |
| { |
| case 0x00: |
| //buf.append("[" + off + "]"); |
| readUTF(buf, off, len); |
| off += len; |
| buf.append('.'); |
| break; |
| case 0xC0: |
| //buf.append("<" + (off - 1) + ">"); |
| if (next < 0) |
| { |
| next = off + 1; |
| } |
| off = ((len & 0x3F) << 8) | get(off++); |
| if (off >= first) |
| { |
| throw new IOException("bad domain name: possible circular name detected"); |
| } |
| first = off; |
| break; |
| default: |
| throw new IOException("bad domain name: '" + buf + "' at " + off); |
| } |
| } |
| this.off = (next >= 0) ? next : off; |
| return buf.toString(); |
| } |
| |
| /** |
| * Debugging. |
| */ |
| String print(boolean dump) |
| { |
| StringBuffer buf = new StringBuffer(); |
| buf.append(toString() + "\n"); |
| for (Iterator iterator = questions.iterator(); iterator.hasNext();) |
| { |
| buf.append(" ques:" + iterator.next() + "\n"); |
| } |
| int count = 0; |
| for (Iterator iterator = answers.iterator(); iterator.hasNext(); count++) |
| { |
| if (count < numAnswers) |
| { |
| buf.append(" answ:"); |
| } |
| else |
| { |
| if (count < numAnswers + numAuthorities) |
| { |
| buf.append(" auth:"); |
| } |
| else |
| { |
| buf.append(" addi:"); |
| } |
| } |
| buf.append(iterator.next() + "\n"); |
| } |
| if (dump) |
| { |
| for (int off = 0, len = packet.getLength(); off < len; off += 32) |
| { |
| int n = Math.min(32, len - off); |
| if (off < 10) |
| { |
| buf.append(' '); |
| } |
| if (off < 100) |
| { |
| buf.append(' '); |
| } |
| buf.append(off); |
| buf.append(':'); |
| for (int i = 0; i < n; i++) |
| { |
| if ((i % 8) == 0) |
| { |
| buf.append(' '); |
| } |
| buf.append(Integer.toHexString((data[off + i] & 0xF0) >> 4)); |
| buf.append(Integer.toHexString((data[off + i] & 0x0F) >> 0)); |
| } |
| buf.append("\n"); |
| buf.append(" "); |
| for (int i = 0; i < n; i++) |
| { |
| if ((i % 8) == 0) |
| { |
| buf.append(' '); |
| } |
| buf.append(' '); |
| int ch = data[off + i] & 0xFF; |
| buf.append(((ch > ' ') && (ch < 127)) ? (char) ch : '.'); |
| } |
| buf.append("\n"); |
| |
| // limit message size |
| if (off + 32 >= 256) |
| { |
| buf.append("....\n"); |
| break; |
| } |
| } |
| } |
| return buf.toString(); |
| } |
| |
| public String toString() |
| { |
| StringBuffer buf = new StringBuffer(); |
| buf.append(isQuery() ? "dns[query," : "dns[response,"); |
| if (packet.getAddress() != null) |
| { |
| buf.append(packet.getAddress().getHostAddress()); |
| } |
| buf.append(':'); |
| buf.append(packet.getPort()); |
| buf.append(",len="); |
| buf.append(packet.getLength()); |
| buf.append(",id=0x"); |
| buf.append(Integer.toHexString(id)); |
| if (flags != 0) |
| { |
| buf.append(",flags=0x"); |
| buf.append(Integer.toHexString(flags)); |
| if ((flags & DNSConstants.FLAGS_QR_RESPONSE) != 0) |
| { |
| buf.append(":r"); |
| } |
| if ((flags & DNSConstants.FLAGS_AA) != 0) |
| { |
| buf.append(":aa"); |
| } |
| if ((flags & DNSConstants.FLAGS_TC) != 0) |
| { |
| buf.append(":tc"); |
| } |
| } |
| if (numQuestions > 0) |
| { |
| buf.append(",questions="); |
| buf.append(numQuestions); |
| } |
| if (numAnswers > 0) |
| { |
| buf.append(",answers="); |
| buf.append(numAnswers); |
| } |
| if (numAuthorities > 0) |
| { |
| buf.append(",authorities="); |
| buf.append(numAuthorities); |
| } |
| if (numAdditionals > 0) |
| { |
| buf.append(",additionals="); |
| buf.append(numAdditionals); |
| } |
| buf.append("]"); |
| return buf.toString(); |
| } |
| |
| /** |
| * Appends answers to this Incoming. |
| * |
| * @throws IllegalArgumentException If not a query or if Truncated. |
| */ |
| void append(DNSIncoming that) |
| { |
| if (this.isQuery() && this.isTruncated() && that.isQuery()) |
| { |
| this.questions.addAll(that.questions); |
| this.numQuestions += that.numQuestions; |
| |
| if (Collections.EMPTY_LIST.equals(answers)) |
| { |
| answers = Collections.synchronizedList(new ArrayList()); |
| } |
| |
| if (that.numAnswers > 0) |
| { |
| this.answers.addAll(this.numAnswers, that.answers.subList(0, that.numAnswers)); |
| this.numAnswers += that.numAnswers; |
| } |
| if (that.numAuthorities > 0) |
| { |
| this.answers.addAll(this.numAnswers + this.numAuthorities, that.answers.subList(that.numAnswers, that.numAnswers + that.numAuthorities)); |
| this.numAuthorities += that.numAuthorities; |
| } |
| if (that.numAdditionals > 0) |
| { |
| this.answers.addAll(that.answers.subList(that.numAnswers + that.numAuthorities, that.numAnswers + that.numAuthorities + that.numAdditionals)); |
| this.numAdditionals += that.numAdditionals; |
| } |
| } |
| else |
| { |
| throw new IllegalArgumentException(); |
| } |
| } |
| |
| int elapseSinceArrival() |
| { |
| return (int) (System.currentTimeMillis() - receivedTime); |
| } |
| } |