| ///Copyright 2003-2005 Arthur van Hoff, Rick Blair |
| //Licensed under Apache License version 2.0 |
| //Original license LGPL |
| |
| |
| |
| package javax.jmdns.impl; |
| |
| import java.io.IOException; |
| import java.net.DatagramPacket; |
| import java.net.InetAddress; |
| 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, Daniel Bobbert |
| */ |
| public final class DNSIncoming |
| { |
| // private static Logger logger = Logger.getLogger(DNSIncoming.class.getName()); |
| |
| // This is a hack to handle a bug in the BonjourConformanceTest |
| // It is sending out target strings that don't follow the "domain name" |
| // format. |
| public static boolean USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET = true; |
| |
| // 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; |
| |
| private List questions; |
| List answers; |
| |
| /** |
| * Parse a message from a datagram packet. |
| */ |
| DNSIncoming(DatagramPacket packet) throws IOException |
| { |
| this.packet = packet; |
| InetAddress source = packet.getAddress(); |
| 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: |
| String service = ""; |
| try { |
| service = readName(); |
| } catch (IOException e){ |
| // there was a problem reading the service name |
| e.printStackTrace(); |
| } |
| rec = new DNSRecord.Pointer(domain, type, clazz, ttl, service); |
| break; |
| case DNSConstants.TYPE_TXT: |
| rec = new DNSRecord.Text(domain, type, clazz, ttl, readBytes(off, len)); |
| break; |
| case DNSConstants.TYPE_SRV: |
| int priority = readUnsignedShort(); |
| int weight = readUnsignedShort(); |
| int port = readUnsignedShort(); |
| String target = ""; |
| try { |
| // This is a hack to handle a bug in the BonjourConformanceTest |
| // It is sending out target strings that don't follow the "domain name" |
| // format. |
| |
| if(USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET){ |
| target = readName(); |
| } else { |
| target = readNonNameString(); |
| } |
| } catch (IOException e) { |
| // this can happen if the type of the label |
| // cannot be handled. |
| // down below the offset gets advanced to the end |
| // of the record |
| e.printStackTrace(); |
| } |
| rec = new DNSRecord.Service(domain, type, clazz, ttl, |
| priority, weight, port, target); |
| break; |
| case DNSConstants.TYPE_HINFO: |
| // Maybe we should do something with those |
| break; |
| default : |
| // logger.finer("DNSIncoming() unknown type:" + type); |
| break; |
| } |
| |
| if (rec != null) |
| { |
| rec.setRecordSource(source); |
| // 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. |
| */ |
| public 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 readNonNameString() throws IOException |
| { |
| StringBuffer buf = new StringBuffer(); |
| int off = this.off; |
| int len = get(off++); |
| readUTF(buf, off, len); |
| |
| return buf.toString(); |
| } |
| |
| 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." + |
| " name start: " + first + |
| " bad offset: 0x" + Integer.toHexString(off)); |
| } |
| first = off; |
| break; |
| default: |
| throw new IOException("unsupported dns label type: '" + Integer.toHexString(len & 0xC0) +"' at " + (off-1)); |
| } |
| } |
| 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()) |
| { |
| if (that.numQuestions > 0) { |
| if (Collections.EMPTY_LIST.equals(this.questions)) |
| this.questions = Collections.synchronizedList(new ArrayList(that.numQuestions)); |
| |
| 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(); |
| } |
| } |
| |
| public int elapseSinceArrival() |
| { |
| return (int) (System.currentTimeMillis() - receivedTime); |
| } |
| |
| public List getQuestions() |
| { |
| return questions; |
| } |
| |
| public List getAnswers() |
| { |
| return answers; |
| } |
| } |