All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.milton.dns.record.Message Maven / Gradle / Ivy

/*
 * Copied from the DnsJava project
 *
 * Copyright (c) 1998-2011, Brian Wellington.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package io.milton.dns.record;

import io.milton.dns.Name;

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 final List[] sections;
	private int size;
	private TSIG tsigkey;
	private TSIGRecord querytsig;
	private int tsigerror;
	int tsigstart;
	int tsigState;
	public 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 final Record[] emptyRecordArray = new Record[0];
	private static final 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 (rec.getType() == Type.TSIG) {
						tsigstart = pos;
					}
					if (rec.getType() == Type.SIG
							&& ((SIGRecord) rec).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.isEmpty()) {
			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 (Record record : additional) {
			if (record instanceof OPTRecord) {
				return (OPTRecord) record;
			}
		}
		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[0]);
	}

	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 (Record rec : recs) {
			Name name = rec.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() == rec.getRRsetType()
							&& set.getDClass() == rec.getDClass()
							&& set.getName().equals(name)) {
						set.addRR(rec);
						newset = false;
						break;
					}
				}
			}
			if (newset) {
				RRset set = new RRset(rec);
				sets.add(set);
				hash.add(name);
			}
		}
		return (RRset[]) sets.toArray(new RRset[0]);
	}

	public 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;
		}

		StringBuilder sb = new StringBuilder();

		Record[] records = getSectionArray(i);
		for (Record rec : records) {
			if (i == Section.QUESTION) {
				sb.append(";;\t").append(rec.name);
				sb.append(", type = ").append(Type.string(rec.type));
				sb.append(", class = ").append(DClass.string(rec.dclass));
			} else {
				sb.append(rec);
			}
			sb.append("\n");
		}
		return sb.toString();
	}

	/**
	 * Converts the Message to a String.
	 */
	public String toString() {
		StringBuilder sb = new StringBuilder();
		OPTRecord opt = getOPT();
		if (opt != null) {
			sb.append(header.toStringWithRcode(getRcode())).append("\n");
		} else {
			sb.append(header).append("\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(";; ").append(Section.longString(i)).append(":\n");
			} else {
				sb.append(";; ").append(Section.updString(i)).append(":\n");
			}
			sb.append(sectionToString(i)).append("\n");
		}
		sb.append(";; Message size: ").append(numBytes()).append(" 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;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy