summaryrefslogtreecommitdiff
path: root/src/javax/jmdns/impl/DNSIncoming.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/javax/jmdns/impl/DNSIncoming.java')
-rw-r--r--src/javax/jmdns/impl/DNSIncoming.java578
1 files changed, 578 insertions, 0 deletions
diff --git a/src/javax/jmdns/impl/DNSIncoming.java b/src/javax/jmdns/impl/DNSIncoming.java
new file mode 100644
index 0000000..ed5de5c
--- /dev/null
+++ b/src/javax/jmdns/impl/DNSIncoming.java
@@ -0,0 +1,578 @@
+// /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.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.jmdns.impl.constants.DNSConstants;
+import javax.jmdns.impl.constants.DNSLabel;
+import javax.jmdns.impl.constants.DNSOptionCode;
+import javax.jmdns.impl.constants.DNSRecordClass;
+import javax.jmdns.impl.constants.DNSRecordType;
+import javax.jmdns.impl.constants.DNSResultCode;
+
+/**
+ * Parse an incoming DNS message into its components.
+ *
+ * @author Arthur van Hoff, Werner Randelshofer, Pierre Frisch, Daniel Bobbert
+ */
+public final class DNSIncoming extends DNSMessage {
+ 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;
+
+ public static class MessageInputStream extends ByteArrayInputStream {
+ private static Logger logger1 = Logger.getLogger(MessageInputStream.class.getName());
+
+ final Map<Integer, String> _names;
+
+ public MessageInputStream(byte[] buffer, int length) {
+ this(buffer, 0, length);
+ }
+
+ /**
+ * @param buffer
+ * @param offset
+ * @param length
+ */
+ public MessageInputStream(byte[] buffer, int offset, int length) {
+ super(buffer, offset, length);
+ _names = new HashMap<Integer, String>();
+ }
+
+ public int readByte() {
+ return this.read();
+ }
+
+ public int readUnsignedShort() {
+ return (this.read() << 8) | this.read();
+ }
+
+ public int readInt() {
+ return (this.readUnsignedShort() << 16) | this.readUnsignedShort();
+ }
+
+ public byte[] readBytes(int len) {
+ byte bytes[] = new byte[len];
+ this.read(bytes, 0, len);
+ return bytes;
+ }
+
+ public String readUTF(int len) {
+ StringBuilder buffer = new StringBuilder(len);
+ for (int index = 0; index < len; index++) {
+ int ch = this.read();
+ 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) | (this.read() & 0x3F);
+ index++;
+ break;
+ case 14:
+ // 1110 xxxx 10xx xxxx 10xx xxxx
+ ch = ((ch & 0x0f) << 12) | ((this.read() & 0x3F) << 6) | (this.read() & 0x3F);
+ index++;
+ index++;
+ break;
+ default:
+ // 10xx xxxx, 1111 xxxx
+ ch = ((ch & 0x3F) << 4) | (this.read() & 0x0f);
+ index++;
+ break;
+ }
+ buffer.append((char) ch);
+ }
+ return buffer.toString();
+ }
+
+ protected synchronized int peek() {
+ return (pos < count) ? (buf[pos] & 0xff) : -1;
+ }
+
+ public String readName() {
+ Map<Integer, StringBuilder> names = new HashMap<Integer, StringBuilder>();
+ StringBuilder buffer = new StringBuilder();
+ boolean finished = false;
+ while (!finished) {
+ int len = this.read();
+ if (len == 0) {
+ finished = true;
+ break;
+ }
+ switch (DNSLabel.labelForByte(len)) {
+ case Standard:
+ int offset = pos - 1;
+ String label = this.readUTF(len) + ".";
+ buffer.append(label);
+ for (StringBuilder previousLabel : names.values()) {
+ previousLabel.append(label);
+ }
+ names.put(Integer.valueOf(offset), new StringBuilder(label));
+ break;
+ case Compressed:
+ int index = (DNSLabel.labelValue(len) << 8) | this.read();
+ String compressedLabel = _names.get(Integer.valueOf(index));
+ if (compressedLabel == null) {
+ logger1.severe("bad domain name: possible circular name detected. Bad offset: 0x" + Integer.toHexString(index) + " at 0x" + Integer.toHexString(pos - 2));
+ compressedLabel = "";
+ }
+ buffer.append(compressedLabel);
+ for (StringBuilder previousLabel : names.values()) {
+ previousLabel.append(compressedLabel);
+ }
+ finished = true;
+ break;
+ case Extended:
+ // int extendedLabelClass = DNSLabel.labelValue(len);
+ logger1.severe("Extended label are not currently supported.");
+ break;
+ case Unknown:
+ default:
+ logger1.severe("unsupported dns label type: '" + Integer.toHexString(len & 0xC0) + "'");
+ }
+ }
+ for (Integer index : names.keySet()) {
+ _names.put(index, names.get(index).toString());
+ }
+ return buffer.toString();
+ }
+
+ public String readNonNameString() {
+ int len = this.read();
+ return this.readUTF(len);
+ }
+
+ }
+
+ private final DatagramPacket _packet;
+
+ private final long _receivedTime;
+
+ private final MessageInputStream _messageInputStream;
+
+ private int _senderUDPPayload;
+
+ /**
+ * Parse a message from a datagram packet.
+ *
+ * @param packet
+ * @exception IOException
+ */
+ public DNSIncoming(DatagramPacket packet) throws IOException {
+ super(0, 0, packet.getPort() == DNSConstants.MDNS_PORT);
+ this._packet = packet;
+ InetAddress source = packet.getAddress();
+ this._messageInputStream = new MessageInputStream(packet.getData(), packet.getLength());
+ this._receivedTime = System.currentTimeMillis();
+ this._senderUDPPayload = DNSConstants.MAX_MSG_TYPICAL;
+
+ try {
+ this.setId(_messageInputStream.readUnsignedShort());
+ this.setFlags(_messageInputStream.readUnsignedShort());
+ int numQuestions = _messageInputStream.readUnsignedShort();
+ int numAnswers = _messageInputStream.readUnsignedShort();
+ int numAuthorities = _messageInputStream.readUnsignedShort();
+ int numAdditionals = _messageInputStream.readUnsignedShort();
+
+ // parse questions
+ if (numQuestions > 0) {
+ for (int i = 0; i < numQuestions; i++) {
+ _questions.add(this.readQuestion());
+ }
+ }
+
+ // parse answers
+ if (numAnswers > 0) {
+ for (int i = 0; i < numAnswers; i++) {
+ DNSRecord rec = this.readAnswer(source);
+ if (rec != null) {
+ // Add a record, if we were able to create one.
+ _answers.add(rec);
+ }
+ }
+ }
+
+ if (numAuthorities > 0) {
+ for (int i = 0; i < numAuthorities; i++) {
+ DNSRecord rec = this.readAnswer(source);
+ if (rec != null) {
+ // Add a record, if we were able to create one.
+ _authoritativeAnswers.add(rec);
+ }
+ }
+ }
+
+ if (numAdditionals > 0) {
+ for (int i = 0; i < numAdditionals; i++) {
+ DNSRecord rec = this.readAnswer(source);
+ if (rec != null) {
+ // Add a record, if we were able to create one.
+ _additionals.add(rec);
+ }
+ }
+ }
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "DNSIncoming() dump " + print(true) + "\n exception ", e);
+ // This ugly but some JVM don't implement the cause on IOException
+ IOException ioe = new IOException("DNSIncoming corrupted message");
+ ioe.initCause(e);
+ throw ioe;
+ }
+ }
+
+ private DNSIncoming(int flags, int id, boolean multicast, DatagramPacket packet, long receivedTime) {
+ super(flags, id, multicast);
+ this._packet = packet;
+ this._messageInputStream = new MessageInputStream(packet.getData(), packet.getLength());
+ this._receivedTime = receivedTime;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#clone()
+ */
+ @Override
+ public DNSIncoming clone() {
+ DNSIncoming in = new DNSIncoming(this.getFlags(), this.getId(), this.isMulticast(), this._packet, this._receivedTime);
+ in._senderUDPPayload = this._senderUDPPayload;
+ in._questions.addAll(this._questions);
+ in._answers.addAll(this._answers);
+ in._authoritativeAnswers.addAll(this._authoritativeAnswers);
+ in._additionals.addAll(this._additionals);
+ return in;
+ }
+
+
+ private DNSQuestion readQuestion() {
+ String domain = _messageInputStream.readName();
+ DNSRecordType type = DNSRecordType.typeForIndex(_messageInputStream.readUnsignedShort());
+ if (type == DNSRecordType.TYPE_IGNORE) {
+ logger.log(Level.SEVERE, "Could not find record type: " + this.print(true));
+ }
+ int recordClassIndex = _messageInputStream.readUnsignedShort();
+ DNSRecordClass recordClass = DNSRecordClass.classForIndex(recordClassIndex);
+ boolean unique = recordClass.isUnique(recordClassIndex);
+ return DNSQuestion.newQuestion(domain, type, recordClass, unique);
+ }
+
+ private DNSRecord readAnswer(InetAddress source) {
+ String domain = _messageInputStream.readName();
+ DNSRecordType type = DNSRecordType.typeForIndex(_messageInputStream.readUnsignedShort());
+ if (type == DNSRecordType.TYPE_IGNORE) {
+ logger.log(Level.SEVERE, "Could not find record type. domain: " + domain + "\n" + this.print(true));
+ }
+ int recordClassIndex = _messageInputStream.readUnsignedShort();
+ DNSRecordClass recordClass = (type == DNSRecordType.TYPE_OPT ? DNSRecordClass.CLASS_UNKNOWN : DNSRecordClass.classForIndex(recordClassIndex));
+ if ((recordClass == DNSRecordClass.CLASS_UNKNOWN) && (type != DNSRecordType.TYPE_OPT)) {
+ logger.log(Level.SEVERE, "Could not find record class. domain: " + domain + " type: " + type + "\n" + this.print(true));
+ }
+ boolean unique = recordClass.isUnique(recordClassIndex);
+ int ttl = _messageInputStream.readInt();
+ int len = _messageInputStream.readUnsignedShort();
+ DNSRecord rec = null;
+
+ switch (type) {
+ case TYPE_A: // IPv4
+ rec = new DNSRecord.IPv4Address(domain, recordClass, unique, ttl, _messageInputStream.readBytes(len));
+ break;
+ case TYPE_AAAA: // IPv6
+ rec = new DNSRecord.IPv6Address(domain, recordClass, unique, ttl, _messageInputStream.readBytes(len));
+ break;
+ case TYPE_CNAME:
+ case TYPE_PTR:
+ String service = "";
+ service = _messageInputStream.readName();
+ if (service.length() > 0) {
+ rec = new DNSRecord.Pointer(domain, recordClass, unique, ttl, service);
+ } else {
+ logger.log(Level.WARNING, "PTR record of class: " + recordClass + ", there was a problem reading the service name of the answer for domain:" + domain);
+ }
+ break;
+ case TYPE_TXT:
+ rec = new DNSRecord.Text(domain, recordClass, unique, ttl, _messageInputStream.readBytes(len));
+ break;
+ case TYPE_SRV:
+ int priority = _messageInputStream.readUnsignedShort();
+ int weight = _messageInputStream.readUnsignedShort();
+ int port = _messageInputStream.readUnsignedShort();
+ String target = "";
+ // 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 = _messageInputStream.readName();
+ } else {
+ // [PJYF Nov 13 2010] Do we still need this? This looks really bad. All label are supposed to start by a length.
+ target = _messageInputStream.readNonNameString();
+ }
+ rec = new DNSRecord.Service(domain, recordClass, unique, ttl, priority, weight, port, target);
+ break;
+ case TYPE_HINFO:
+ StringBuilder buf = new StringBuilder();
+ buf.append(_messageInputStream.readUTF(len));
+ int index = buf.indexOf(" ");
+ String cpu = (index > 0 ? buf.substring(0, index) : buf.toString()).trim();
+ String os = (index > 0 ? buf.substring(index + 1) : "").trim();
+ rec = new DNSRecord.HostInformation(domain, recordClass, unique, ttl, cpu, os);
+ break;
+ case TYPE_OPT:
+ DNSResultCode extendedResultCode = DNSResultCode.resultCodeForFlags(this.getFlags(), ttl);
+ int version = (ttl & 0x00ff0000) >> 16;
+ if (version == 0) {
+ _senderUDPPayload = recordClassIndex;
+ while (_messageInputStream.available() > 0) {
+ // Read RDData
+ int optionCodeInt = 0;
+ DNSOptionCode optionCode = null;
+ if (_messageInputStream.available() >= 2) {
+ optionCodeInt = _messageInputStream.readUnsignedShort();
+ optionCode = DNSOptionCode.resultCodeForFlags(optionCodeInt);
+ } else {
+ logger.log(Level.WARNING, "There was a problem reading the OPT record. Ignoring.");
+ break;
+ }
+ int optionLength = 0;
+ if (_messageInputStream.available() >= 2) {
+ optionLength = _messageInputStream.readUnsignedShort();
+ } else {
+ logger.log(Level.WARNING, "There was a problem reading the OPT record. Ignoring.");
+ break;
+ }
+ byte[] optiondata = new byte[0];
+ if (_messageInputStream.available() >= optionLength) {
+ optiondata = _messageInputStream.readBytes(optionLength);
+ }
+ //
+ // We should really do something with those options.
+ switch (optionCode) {
+ case Owner:
+ // Valid length values are 8, 14, 18 and 20
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // |Opt|Len|V|S|Primary MAC|Wakeup MAC | Password |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ //
+ int ownerVersion = 0;
+ int ownerSequence = 0;
+ byte[] ownerPrimaryMacAddress = null;
+ byte[] ownerWakeupMacAddress = null;
+ byte[] ownerPassword = null;
+ try {
+ ownerVersion = optiondata[0];
+ ownerSequence = optiondata[1];
+ ownerPrimaryMacAddress = new byte[] { optiondata[2], optiondata[3], optiondata[4], optiondata[5], optiondata[6], optiondata[7] };
+ ownerWakeupMacAddress = ownerPrimaryMacAddress;
+ if (optiondata.length > 8) {
+ // We have a wakeupMacAddress.
+ ownerWakeupMacAddress = new byte[] { optiondata[8], optiondata[9], optiondata[10], optiondata[11], optiondata[12], optiondata[13] };
+ }
+ if (optiondata.length == 18) {
+ // We have a short password.
+ ownerPassword = new byte[] { optiondata[14], optiondata[15], optiondata[16], optiondata[17] };
+ }
+ if (optiondata.length == 22) {
+ // We have a long password.
+ ownerPassword = new byte[] { optiondata[14], optiondata[15], optiondata[16], optiondata[17], optiondata[18], optiondata[19], optiondata[20], optiondata[21] };
+ }
+ } catch (Exception exception) {
+ logger.warning("Malformed OPT answer. Option code: Owner data: " + this._hexString(optiondata));
+ }
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("Unhandled Owner OPT version: " + ownerVersion + " sequence: " + ownerSequence + " MAC address: " + this._hexString(ownerPrimaryMacAddress)
+ + (ownerWakeupMacAddress != ownerPrimaryMacAddress ? " wakeup MAC address: " + this._hexString(ownerWakeupMacAddress) : "") + (ownerPassword != null ? " password: " + this._hexString(ownerPassword) : ""));
+ }
+ break;
+ case LLQ:
+ case NSID:
+ case UL:
+ if (logger.isLoggable(Level.FINE)) {
+ logger.log(Level.FINE, "There was an OPT answer. Option code: " + optionCode + " data: " + this._hexString(optiondata));
+ }
+ break;
+ case Unknown:
+ logger.log(Level.WARNING, "There was an OPT answer. Not currently handled. Option code: " + optionCodeInt + " data: " + this._hexString(optiondata));
+ break;
+ default:
+ // This is to keep the compiler happy.
+ break;
+ }
+ }
+ } else {
+ logger.log(Level.WARNING, "There was an OPT answer. Wrong version number: " + version + " result code: " + extendedResultCode);
+ }
+ break;
+ default:
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("DNSIncoming() unknown type:" + type);
+ }
+ _messageInputStream.skip(len);
+ break;
+ }
+ if (rec != null) {
+ rec.setRecordSource(source);
+ }
+ return rec;
+ }
+
+ /**
+ * Debugging.
+ */
+ String print(boolean dump) {
+ StringBuilder buf = new StringBuilder();
+ buf.append(this.print());
+ if (dump) {
+ byte[] data = new byte[_packet.getLength()];
+ System.arraycopy(_packet.getData(), 0, data, 0, data.length);
+ buf.append(this.print(data));
+ }
+ return buf.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(isQuery() ? "dns[query," : "dns[response,");
+ if (_packet.getAddress() != null) {
+ buf.append(_packet.getAddress().getHostAddress());
+ }
+ buf.append(':');
+ buf.append(_packet.getPort());
+ buf.append(", length=");
+ buf.append(_packet.getLength());
+ buf.append(", id=0x");
+ buf.append(Integer.toHexString(this.getId()));
+ if (this.getFlags() != 0) {
+ buf.append(", flags=0x");
+ buf.append(Integer.toHexString(this.getFlags()));
+ if ((this.getFlags() & DNSConstants.FLAGS_QR_RESPONSE) != 0) {
+ buf.append(":r");
+ }
+ if ((this.getFlags() & DNSConstants.FLAGS_AA) != 0) {
+ buf.append(":aa");
+ }
+ if ((this.getFlags() & DNSConstants.FLAGS_TC) != 0) {
+ buf.append(":tc");
+ }
+ }
+ if (this.getNumberOfQuestions() > 0) {
+ buf.append(", questions=");
+ buf.append(this.getNumberOfQuestions());
+ }
+ if (this.getNumberOfAnswers() > 0) {
+ buf.append(", answers=");
+ buf.append(this.getNumberOfAnswers());
+ }
+ if (this.getNumberOfAuthorities() > 0) {
+ buf.append(", authorities=");
+ buf.append(this.getNumberOfAuthorities());
+ }
+ if (this.getNumberOfAdditionals() > 0) {
+ buf.append(", additionals=");
+ buf.append(this.getNumberOfAdditionals());
+ }
+ if (this.getNumberOfQuestions() > 0) {
+ buf.append("\nquestions:");
+ for (DNSQuestion question : _questions) {
+ buf.append("\n\t");
+ buf.append(question);
+ }
+ }
+ if (this.getNumberOfAnswers() > 0) {
+ buf.append("\nanswers:");
+ for (DNSRecord record : _answers) {
+ buf.append("\n\t");
+ buf.append(record);
+ }
+ }
+ if (this.getNumberOfAuthorities() > 0) {
+ buf.append("\nauthorities:");
+ for (DNSRecord record : _authoritativeAnswers) {
+ buf.append("\n\t");
+ buf.append(record);
+ }
+ }
+ if (this.getNumberOfAdditionals() > 0) {
+ buf.append("\nadditionals:");
+ for (DNSRecord record : _additionals) {
+ buf.append("\n\t");
+ buf.append(record);
+ }
+ }
+ buf.append("]");
+ return buf.toString();
+ }
+
+ /**
+ * Appends answers to this Incoming.
+ *
+ * @exception IllegalArgumentException
+ * If not a query or if Truncated.
+ */
+ void append(DNSIncoming that) {
+ if (this.isQuery() && this.isTruncated() && that.isQuery()) {
+ this._questions.addAll(that.getQuestions());
+ this._answers.addAll(that.getAnswers());
+ this._authoritativeAnswers.addAll(that.getAuthorities());
+ this._additionals.addAll(that.getAdditionals());
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public int elapseSinceArrival() {
+ return (int) (System.currentTimeMillis() - _receivedTime);
+ }
+
+ /**
+ * This will return the default UDP payload except if an OPT record was found with a different size.
+ *
+ * @return the senderUDPPayload
+ */
+ public int getSenderUDPPayload() {
+ return this._senderUDPPayload;
+ }
+
+ private static final char[] _nibbleToHex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ /**
+ * Returns a hex-string for printing
+ *
+ * @param bytes
+ * @return Returns a hex-string which can be used within a SQL expression
+ */
+ private String _hexString(byte[] bytes) {
+
+ StringBuilder result = new StringBuilder(2 * bytes.length);
+
+ for (int i = 0; i < bytes.length; i++) {
+ int b = bytes[i] & 0xFF;
+ result.append(_nibbleToHex[b / 16]);
+ result.append(_nibbleToHex[b % 16]);
+ }
+
+ return result.toString();
+ }
+
+}