aboutsummaryrefslogtreecommitdiff
path: root/src/org/xbill/DNS/Message.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/xbill/DNS/Message.java')
-rw-r--r--src/org/xbill/DNS/Message.java611
1 files changed, 611 insertions, 0 deletions
diff --git a/src/org/xbill/DNS/Message.java b/src/org/xbill/DNS/Message.java
new file mode 100644
index 0000000..fe0c3c9
--- /dev/null
+++ b/src/org/xbill/DNS/Message.java
@@ -0,0 +1,611 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+import java.io.*;
+
+/**
+ * A DNS Message. A message is the basic unit of communication between
+ * the client and server of a DNS operation. A message consists of a Header
+ * and 4 message sections.
+ * @see Resolver
+ * @see Header
+ * @see Section
+ *
+ * @author Brian Wellington
+ */
+
+public class Message implements Cloneable {
+
+/** The maximum length of a message in wire format. */
+public static final int MAXLENGTH = 65535;
+
+private Header header;
+private List [] sections;
+private int size;
+private TSIG tsigkey;
+private TSIGRecord querytsig;
+private int tsigerror;
+
+int tsigstart;
+int tsigState;
+int sig0start;
+
+/* The message was not signed */
+static final int TSIG_UNSIGNED = 0;
+
+/* The message was signed and verification succeeded */
+static final int TSIG_VERIFIED = 1;
+
+/* The message was an unsigned message in multiple-message response */
+static final int TSIG_INTERMEDIATE = 2;
+
+/* The message was signed and no verification was attempted. */
+static final int TSIG_SIGNED = 3;
+
+/*
+ * The message was signed and verification failed, or was not signed
+ * when it should have been.
+ */
+static final int TSIG_FAILED = 4;
+
+private static Record [] emptyRecordArray = new Record[0];
+private static RRset [] emptyRRsetArray = new RRset[0];
+
+private
+Message(Header header) {
+ sections = new List[4];
+ this.header = header;
+}
+
+/** Creates a new Message with the specified Message ID */
+public
+Message(int id) {
+ this(new Header(id));
+}
+
+/** Creates a new Message with a random Message ID */
+public
+Message() {
+ this(new Header());
+}
+
+/**
+ * Creates a new Message with a random Message ID suitable for sending as a
+ * query.
+ * @param r A record containing the question
+ */
+public static Message
+newQuery(Record r) {
+ Message m = new Message();
+ m.header.setOpcode(Opcode.QUERY);
+ m.header.setFlag(Flags.RD);
+ m.addRecord(r, Section.QUESTION);
+ return m;
+}
+
+/**
+ * Creates a new Message to contain a dynamic update. A random Message ID
+ * and the zone are filled in.
+ * @param zone The zone to be updated
+ */
+public static Message
+newUpdate(Name zone) {
+ return new Update(zone);
+}
+
+Message(DNSInput in) throws IOException {
+ this(new Header(in));
+ boolean isUpdate = (header.getOpcode() == Opcode.UPDATE);
+ boolean truncated = header.getFlag(Flags.TC);
+ try {
+ for (int i = 0; i < 4; i++) {
+ int count = header.getCount(i);
+ if (count > 0)
+ sections[i] = new ArrayList(count);
+ for (int j = 0; j < count; j++) {
+ int pos = in.current();
+ Record rec = Record.fromWire(in, i, isUpdate);
+ sections[i].add(rec);
+ if (i == Section.ADDITIONAL) {
+ if (rec.getType() == Type.TSIG)
+ tsigstart = pos;
+ if (rec.getType() == Type.SIG) {
+ SIGRecord sig = (SIGRecord) rec;
+ if (sig.getTypeCovered() == 0)
+ sig0start = pos;
+ }
+ }
+ }
+ }
+ } catch (WireParseException e) {
+ if (!truncated)
+ throw e;
+ }
+ size = in.current();
+}
+
+/**
+ * Creates a new Message from its DNS wire format representation
+ * @param b A byte array containing the DNS Message.
+ */
+public
+Message(byte [] b) throws IOException {
+ this(new DNSInput(b));
+}
+
+/**
+ * Replaces the Header with a new one.
+ * @see Header
+ */
+public void
+setHeader(Header h) {
+ header = h;
+}
+
+/**
+ * Retrieves the Header.
+ * @see Header
+ */
+public Header
+getHeader() {
+ return header;
+}
+
+/**
+ * Adds a record to a section of the Message, and adjusts the header.
+ * @see Record
+ * @see Section
+ */
+public void
+addRecord(Record r, int section) {
+ if (sections[section] == null)
+ sections[section] = new LinkedList();
+ header.incCount(section);
+ sections[section].add(r);
+}
+
+/**
+ * Removes a record from a section of the Message, and adjusts the header.
+ * @see Record
+ * @see Section
+ */
+public boolean
+removeRecord(Record r, int section) {
+ if (sections[section] != null && sections[section].remove(r)) {
+ header.decCount(section);
+ return true;
+ }
+ else
+ return false;
+}
+
+/**
+ * Removes all records from a section of the Message, and adjusts the header.
+ * @see Record
+ * @see Section
+ */
+public void
+removeAllRecords(int section) {
+ sections[section] = null;
+ header.setCount(section, 0);
+}
+
+/**
+ * Determines if the given record is already present in the given section.
+ * @see Record
+ * @see Section
+ */
+public boolean
+findRecord(Record r, int section) {
+ return (sections[section] != null && sections[section].contains(r));
+}
+
+/**
+ * Determines if the given record is already present in any section.
+ * @see Record
+ * @see Section
+ */
+public boolean
+findRecord(Record r) {
+ for (int i = Section.ANSWER; i <= Section.ADDITIONAL; i++)
+ if (sections[i] != null && sections[i].contains(r))
+ return true;
+ return false;
+}
+
+/**
+ * Determines if an RRset with the given name and type is already
+ * present in the given section.
+ * @see RRset
+ * @see Section
+ */
+public boolean
+findRRset(Name name, int type, int section) {
+ if (sections[section] == null)
+ return false;
+ for (int i = 0; i < sections[section].size(); i++) {
+ Record r = (Record) sections[section].get(i);
+ if (r.getType() == type && name.equals(r.getName()))
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Determines if an RRset with the given name and type is already
+ * present in any section.
+ * @see RRset
+ * @see Section
+ */
+public boolean
+findRRset(Name name, int type) {
+ return (findRRset(name, type, Section.ANSWER) ||
+ findRRset(name, type, Section.AUTHORITY) ||
+ findRRset(name, type, Section.ADDITIONAL));
+}
+
+/**
+ * Returns the first record in the QUESTION section.
+ * @see Record
+ * @see Section
+ */
+public Record
+getQuestion() {
+ List l = sections[Section.QUESTION];
+ if (l == null || l.size() == 0)
+ return null;
+ return (Record) l.get(0);
+}
+
+/**
+ * Returns the TSIG record from the ADDITIONAL section, if one is present.
+ * @see TSIGRecord
+ * @see TSIG
+ * @see Section
+ */
+public TSIGRecord
+getTSIG() {
+ int count = header.getCount(Section.ADDITIONAL);
+ if (count == 0)
+ return null;
+ List l = sections[Section.ADDITIONAL];
+ Record rec = (Record) l.get(count - 1);
+ if (rec.type != Type.TSIG)
+ return null;
+ return (TSIGRecord) rec;
+}
+
+/**
+ * Was this message signed by a TSIG?
+ * @see TSIG
+ */
+public boolean
+isSigned() {
+ return (tsigState == TSIG_SIGNED ||
+ tsigState == TSIG_VERIFIED ||
+ tsigState == TSIG_FAILED);
+}
+
+/**
+ * If this message was signed by a TSIG, was the TSIG verified?
+ * @see TSIG
+ */
+public boolean
+isVerified() {
+ return (tsigState == TSIG_VERIFIED);
+}
+
+/**
+ * Returns the OPT record from the ADDITIONAL section, if one is present.
+ * @see OPTRecord
+ * @see Section
+ */
+public OPTRecord
+getOPT() {
+ Record [] additional = getSectionArray(Section.ADDITIONAL);
+ for (int i = 0; i < additional.length; i++)
+ if (additional[i] instanceof OPTRecord)
+ return (OPTRecord) additional[i];
+ return null;
+}
+
+/**
+ * Returns the message's rcode (error code). This incorporates the EDNS
+ * extended rcode.
+ */
+public int
+getRcode() {
+ int rcode = header.getRcode();
+ OPTRecord opt = getOPT();
+ if (opt != null)
+ rcode += (opt.getExtendedRcode() << 4);
+ return rcode;
+}
+
+/**
+ * Returns an array containing all records in the given section, or an
+ * empty array if the section is empty.
+ * @see Record
+ * @see Section
+ */
+public Record []
+getSectionArray(int section) {
+ if (sections[section] == null)
+ return emptyRecordArray;
+ List l = sections[section];
+ return (Record []) l.toArray(new Record[l.size()]);
+}
+
+private static boolean
+sameSet(Record r1, Record r2) {
+ return (r1.getRRsetType() == r2.getRRsetType() &&
+ r1.getDClass() == r2.getDClass() &&
+ r1.getName().equals(r2.getName()));
+}
+
+/**
+ * Returns an array containing all records in the given section grouped into
+ * RRsets.
+ * @see RRset
+ * @see Section
+ */
+public RRset []
+getSectionRRsets(int section) {
+ if (sections[section] == null)
+ return emptyRRsetArray;
+ List sets = new LinkedList();
+ Record [] recs = getSectionArray(section);
+ Set hash = new HashSet();
+ for (int i = 0; i < recs.length; i++) {
+ Name name = recs[i].getName();
+ boolean newset = true;
+ if (hash.contains(name)) {
+ for (int j = sets.size() - 1; j >= 0; j--) {
+ RRset set = (RRset) sets.get(j);
+ if (set.getType() == recs[i].getRRsetType() &&
+ set.getDClass() == recs[i].getDClass() &&
+ set.getName().equals(name))
+ {
+ set.addRR(recs[i]);
+ newset = false;
+ break;
+ }
+ }
+ }
+ if (newset) {
+ RRset set = new RRset(recs[i]);
+ sets.add(set);
+ hash.add(name);
+ }
+ }
+ return (RRset []) sets.toArray(new RRset[sets.size()]);
+}
+
+void
+toWire(DNSOutput out) {
+ header.toWire(out);
+ Compression c = new Compression();
+ for (int i = 0; i < 4; i++) {
+ if (sections[i] == null)
+ continue;
+ for (int j = 0; j < sections[i].size(); j++) {
+ Record rec = (Record)sections[i].get(j);
+ rec.toWire(out, i, c);
+ }
+ }
+}
+
+/* Returns the number of records not successfully rendered. */
+private int
+sectionToWire(DNSOutput out, int section, Compression c,
+ int maxLength)
+{
+ int n = sections[section].size();
+ int pos = out.current();
+ int rendered = 0;
+ Record lastrec = null;
+
+ for (int i = 0; i < n; i++) {
+ Record rec = (Record)sections[section].get(i);
+ if (lastrec != null && !sameSet(rec, lastrec)) {
+ pos = out.current();
+ rendered = i;
+ }
+ lastrec = rec;
+ rec.toWire(out, section, c);
+ if (out.current() > maxLength) {
+ out.jump(pos);
+ return n - rendered;
+ }
+ }
+ return 0;
+}
+
+/* Returns true if the message could be rendered. */
+private boolean
+toWire(DNSOutput out, int maxLength) {
+ if (maxLength < Header.LENGTH)
+ return false;
+
+ Header newheader = null;
+
+ int tempMaxLength = maxLength;
+ if (tsigkey != null)
+ tempMaxLength -= tsigkey.recordLength();
+
+ int startpos = out.current();
+ header.toWire(out);
+ Compression c = new Compression();
+ for (int i = 0; i < 4; i++) {
+ int skipped;
+ if (sections[i] == null)
+ continue;
+ skipped = sectionToWire(out, i, c, tempMaxLength);
+ if (skipped != 0) {
+ if (newheader == null)
+ newheader = (Header) header.clone();
+ if (i != Section.ADDITIONAL)
+ newheader.setFlag(Flags.TC);
+ int count = newheader.getCount(i);
+ newheader.setCount(i, count - skipped);
+ for (int j = i + 1; j < 4; j++)
+ newheader.setCount(j, 0);
+
+ out.save();
+ out.jump(startpos);
+ newheader.toWire(out);
+ out.restore();
+ break;
+ }
+ }
+
+ if (tsigkey != null) {
+ TSIGRecord tsigrec = tsigkey.generate(this, out.toByteArray(),
+ tsigerror, querytsig);
+
+ if (newheader == null)
+ newheader = (Header) header.clone();
+ tsigrec.toWire(out, Section.ADDITIONAL, c);
+ newheader.incCount(Section.ADDITIONAL);
+
+ out.save();
+ out.jump(startpos);
+ newheader.toWire(out);
+ out.restore();
+ }
+
+ return true;
+}
+
+/**
+ * Returns an array containing the wire format representation of the Message.
+ */
+public byte []
+toWire() {
+ DNSOutput out = new DNSOutput();
+ toWire(out);
+ size = out.current();
+ return out.toByteArray();
+}
+
+/**
+ * Returns an array containing the wire format representation of the Message
+ * with the specified maximum length. This will generate a truncated
+ * message (with the TC bit) if the message doesn't fit, and will also
+ * sign the message with the TSIG key set by a call to setTSIG(). This
+ * method may return null if the message could not be rendered at all; this
+ * could happen if maxLength is smaller than a DNS header, for example.
+ * @param maxLength The maximum length of the message.
+ * @return The wire format of the message, or null if the message could not be
+ * rendered into the specified length.
+ * @see Flags
+ * @see TSIG
+ */
+public byte []
+toWire(int maxLength) {
+ DNSOutput out = new DNSOutput();
+ toWire(out, maxLength);
+ size = out.current();
+ return out.toByteArray();
+}
+
+/**
+ * Sets the TSIG key and other necessary information to sign a message.
+ * @param key The TSIG key.
+ * @param error The value of the TSIG error field.
+ * @param querytsig If this is a response, the TSIG from the request.
+ */
+public void
+setTSIG(TSIG key, int error, TSIGRecord querytsig) {
+ this.tsigkey = key;
+ this.tsigerror = error;
+ this.querytsig = querytsig;
+}
+
+/**
+ * Returns the size of the message. Only valid if the message has been
+ * converted to or from wire format.
+ */
+public int
+numBytes() {
+ return size;
+}
+
+/**
+ * Converts the given section of the Message to a String.
+ * @see Section
+ */
+public String
+sectionToString(int i) {
+ if (i > 3)
+ return null;
+
+ StringBuffer sb = new StringBuffer();
+
+ Record [] records = getSectionArray(i);
+ for (int j = 0; j < records.length; j++) {
+ Record rec = records[j];
+ if (i == Section.QUESTION) {
+ sb.append(";;\t" + rec.name);
+ sb.append(", type = " + Type.string(rec.type));
+ sb.append(", class = " + DClass.string(rec.dclass));
+ }
+ else
+ sb.append(rec);
+ sb.append("\n");
+ }
+ return sb.toString();
+}
+
+/**
+ * Converts the Message to a String.
+ */
+public String
+toString() {
+ StringBuffer sb = new StringBuffer();
+ OPTRecord opt = getOPT();
+ if (opt != null)
+ sb.append(header.toStringWithRcode(getRcode()) + "\n");
+ else
+ sb.append(header + "\n");
+ if (isSigned()) {
+ sb.append(";; TSIG ");
+ if (isVerified())
+ sb.append("ok");
+ else
+ sb.append("invalid");
+ sb.append('\n');
+ }
+ for (int i = 0; i < 4; i++) {
+ if (header.getOpcode() != Opcode.UPDATE)
+ sb.append(";; " + Section.longString(i) + ":\n");
+ else
+ sb.append(";; " + Section.updString(i) + ":\n");
+ sb.append(sectionToString(i) + "\n");
+ }
+ sb.append(";; Message size: " + numBytes() + " bytes");
+ return sb.toString();
+}
+
+/**
+ * Creates a copy of this Message. This is done by the Resolver before adding
+ * TSIG and OPT records, for example.
+ * @see Resolver
+ * @see TSIGRecord
+ * @see OPTRecord
+ */
+public Object
+clone() {
+ Message m = new Message();
+ for (int i = 0; i < sections.length; i++) {
+ if (sections[i] != null)
+ m.sections[i] = new LinkedList(sections[i]);
+ }
+ m.header = (Header) header.clone();
+ m.size = size;
+ return m;
+}
+
+}