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

org.minidns.dnsmessage.DnsMessage Maven / Gradle / Ivy

/*
 * Copyright 2015-2024 the original author or authors
 *
 * This software is licensed under the Apache License, Version 2.0,
 * the GNU Lesser General Public License version 2 or later ("LGPL")
 * and the WTFPL.
 * You may choose either license to govern your use of this software only
 * upon the condition that you accept all of the terms of either
 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
 */
package org.minidns.dnsmessage;

import org.minidns.edns.Edns;
import org.minidns.record.Data;
import org.minidns.record.OPT;
import org.minidns.record.Record;
import org.minidns.record.Record.TYPE;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A DNS message as defined by RFC 1035. The message consists of a header and
 * 4 sections: question, answer, nameserver and addition resource record
 * section.
 * A message can either be parsed ({@link #DnsMessage(byte[])}) or serialized
 * ({@link DnsMessage#toArray()}).
 * 
 * @see RFC 1035
 */
public class DnsMessage {

    private static final Logger LOGGER = Logger.getLogger(DnsMessage.class.getName());

    /**
     * Possible DNS response codes.
     * 
     * @see 
     *      IANA Domain Name System (DNS) Paramters - DNS RCODEs
     * @see RFC 6895 § 2.3
     */
    public enum RESPONSE_CODE {
        NO_ERROR(0),
        FORMAT_ERR(1),
        SERVER_FAIL(2),
        NX_DOMAIN(3),
        NO_IMP(4),
        REFUSED(5),
        YXDOMAIN(6),
        YXRRSET(7),
        NXRRSET(8),
        NOT_AUTH(9),
        NOT_ZONE(10),
        BADVERS_BADSIG(16),
        BADKEY(17),
        BADTIME(18),
        BADMODE(19),
        BADNAME(20),
        BADALG(21),
        BADTRUNC(22),
        BADCOOKIE(23),
        ;

        /**
         * Reverse lookup table for response codes.
         */
        private static final Map INVERSE_LUT = new HashMap<>(RESPONSE_CODE.values().length);

        static {
            for (RESPONSE_CODE responseCode : RESPONSE_CODE.values()) {
                INVERSE_LUT.put((int) responseCode.value, responseCode);
            }
        }

        /**
         * The response code value.
         */
        private final byte value;

        /**
         * Create a new response code.
         *
         * @param value The response code value.
         */
        RESPONSE_CODE(int value) {
            this.value = (byte) value;
        }

        /**
         * Retrieve the byte value of the response code.
         *
         * @return the response code.
         */
        public byte getValue() {
            return value;
        }

        /**
         * Retrieve the response code for a byte value.
         *
         * @param value The byte value.
         * @return The symbolic response code or null.
         * @throws IllegalArgumentException if the value is not in the range of 0..15.
         */
        public static RESPONSE_CODE getResponseCode(int value) throws IllegalArgumentException {
            if (value < 0 || value > 65535) {
                throw new IllegalArgumentException();
            }
            return INVERSE_LUT.get(value);
        }

    }

    /**
     * Symbolic DNS Opcode values.
     * 
     * @see 
     *      IANA Domain Name System (DNS) Paramters - DNS OpCodes
     */
    public enum OPCODE {
        QUERY,
        INVERSE_QUERY,
        STATUS,
        UNASSIGNED3,
        NOTIFY,
        UPDATE,
        ;

        /**
         * Lookup table for for opcode resolution.
         */
        private static final OPCODE[] INVERSE_LUT = new OPCODE[OPCODE.values().length];

        static {
            for (OPCODE opcode : OPCODE.values()) {
                if (INVERSE_LUT[opcode.getValue()] != null) {
                    throw new IllegalStateException();
                }
                INVERSE_LUT[opcode.getValue()] = opcode;
            }
        }

        /**
         * The value of this opcode.
         */
        private final byte value;

        /**
         * Create a new opcode for a given byte value.
         *
         */
        OPCODE() {
            this.value = (byte) this.ordinal();
        }

        /**
         * Retrieve the byte value of this opcode.
         *
         * @return The byte value of this opcode.
         */
        public byte getValue() {
            return value;
        }

        /**
         * Retrieve the symbolic name of an opcode byte.
         *
         * @param value The byte value of the opcode.
         * @return The symbolic opcode or null.
         * @throws IllegalArgumentException If the byte value is not in the
         *                                  range 0..15.
         */
        public static OPCODE getOpcode(int value) throws IllegalArgumentException {
            if (value < 0 || value > 15) {
                throw new IllegalArgumentException();
            }
            if (value >= INVERSE_LUT.length) {
                return null;
            }
            return INVERSE_LUT[value];
        }

    }

    /**
     * The DNS message id.
     */
    public final int id;

    /**
     * The DNS message opcode.
     */
    public final OPCODE opcode;

    /**
     * The response code of this dns message.
     */
    public final RESPONSE_CODE responseCode;

    /**
     * The QR flag of the DNS message header. Note that this will be true if the message is a
     * response and false if it is a query.
     * 
     * @see RFC 1035 § 4.1.1
     */
    public final boolean qr;

    /**
     * True if this is a authorative response. If set, the responding nameserver is an authority for the domain name in
     * the question section. Note that the answer section may have multiple owner names because of aliases. This flag
     * corresponds to the name which matches the query name, or the first owner name in the query section.
     *
     * @see RFC 1035 § 4.1.1. Header section format
     */
    public final boolean authoritativeAnswer;

    /**
     * True if message is truncated. Then TCP should be used.
     */
    public final boolean truncated;

    /**
     * True if the server should recurse.
     */
    public final boolean recursionDesired;

    /**
     * True if recursion is possible.
     */
    public final boolean recursionAvailable;

    /**
     * True if the server regarded the response as authentic.
     */
    public final boolean authenticData;

    /**
     * True if the server should not perform DNSSEC validation before returning the result.
     */
    public final boolean checkingDisabled;

    /**
     * The question section content. Usually there will be only one question.
     * 

* This list is unmodifiable. *

*/ public final List questions; /** * The answers section records. Note that it is not guaranteed that all records found in this section will be direct * answers to the question in the query. If DNSSEC is used, then this section also contains the RRSIG record. *

* This list is unmodifiable. *

*/ public final List> answerSection; /** * The Authority Section. Note that it is not guaranteed that this section only contains nameserver records. If DNSSEC is used, then this section could also contain a NSEC(3) record. *

* This list is unmodifiable. *

*/ public final List> authoritySection; /** * The additional section. It eventually contains RRs which relate to the query. *

* This list is unmodifiable. *

*/ public final List> additionalSection; public final int optRrPosition; /** * The optional but very common EDNS information. Note that this field is lazily populated. * */ private Edns edns; /** * The receive timestamp. Set only if this message was created via parse. * This should be used to evaluate TTLs. */ public final long receiveTimestamp; protected DnsMessage(Builder builder) { this.id = builder.id; this.opcode = builder.opcode; this.responseCode = builder.responseCode; this.receiveTimestamp = builder.receiveTimestamp; this.qr = builder.query; this.authoritativeAnswer = builder.authoritativeAnswer; this.truncated = builder.truncated; this.recursionDesired = builder.recursionDesired; this.recursionAvailable = builder.recursionAvailable; this.authenticData = builder.authenticData; this.checkingDisabled = builder.checkingDisabled; if (builder.questions == null) { this.questions = Collections.emptyList(); } else { List q = new ArrayList<>(builder.questions.size()); q.addAll(builder.questions); this.questions = Collections.unmodifiableList(q); } if (builder.answerSection == null) { this.answerSection = Collections.emptyList(); } else { List> a = new ArrayList<>(builder.answerSection.size()); a.addAll(builder.answerSection); this.answerSection = Collections.unmodifiableList(a); } if (builder.authoritySection == null) { this.authoritySection = Collections.emptyList(); } else { List> n = new ArrayList<>(builder.authoritySection.size()); n.addAll(builder.authoritySection); this.authoritySection = Collections.unmodifiableList(n); } if (builder.additionalSection == null && builder.ednsBuilder == null) { this.additionalSection = Collections.emptyList(); } else { int size = 0; if (builder.additionalSection != null) { size += builder.additionalSection.size(); } if (builder.ednsBuilder != null) { size++; } List> a = new ArrayList<>(size); if (builder.additionalSection != null) { a.addAll(builder.additionalSection); } if (builder.ednsBuilder != null) { Edns edns = builder.ednsBuilder.build(); this.edns = edns; a.add(edns.asRecord()); } this.additionalSection = Collections.unmodifiableList(a); } optRrPosition = getOptRrPosition(this.additionalSection); if (optRrPosition != -1) { // Verify that there are no further OPT records but the one we already found. for (int i = optRrPosition + 1; i < this.additionalSection.size(); i++) { if (this.additionalSection.get(i).type == TYPE.OPT) { throw new IllegalArgumentException("There must be only one OPT pseudo RR in the additional section"); } } } // TODO Add verification of dns message state here } /** * Build a DNS Message based on a binary DNS message. * * @param data The DNS message data. * @throws IOException On read errors. */ public DnsMessage(byte[] data) throws IOException { ByteArrayInputStream bis = new ByteArrayInputStream(data); DataInputStream dis = new DataInputStream(bis); id = dis.readUnsignedShort(); int header = dis.readUnsignedShort(); qr = ((header >> 15) & 1) == 1; opcode = OPCODE.getOpcode((header >> 11) & 0xf); authoritativeAnswer = ((header >> 10) & 1) == 1; truncated = ((header >> 9) & 1) == 1; recursionDesired = ((header >> 8) & 1) == 1; recursionAvailable = ((header >> 7) & 1) == 1; authenticData = ((header >> 5) & 1) == 1; checkingDisabled = ((header >> 4) & 1) == 1; responseCode = RESPONSE_CODE.getResponseCode(header & 0xf); receiveTimestamp = System.currentTimeMillis(); int questionCount = dis.readUnsignedShort(); int answerCount = dis.readUnsignedShort(); int nameserverCount = dis.readUnsignedShort(); int additionalResourceRecordCount = dis.readUnsignedShort(); questions = new ArrayList<>(questionCount); for (int i = 0; i < questionCount; i++) { questions.add(new Question(dis, data)); } answerSection = new ArrayList<>(answerCount); for (int i = 0; i < answerCount; i++) { answerSection.add(Record.parse(dis, data)); } authoritySection = new ArrayList<>(nameserverCount); for (int i = 0; i < nameserverCount; i++) { authoritySection.add(Record.parse(dis, data)); } additionalSection = new ArrayList<>(additionalResourceRecordCount); for (int i = 0; i < additionalResourceRecordCount; i++) { additionalSection.add(Record.parse(dis, data)); } optRrPosition = getOptRrPosition(additionalSection); } /** * Constructs an normalized version of the given DnsMessage by setting the id to '0'. * * @param message the message of which normalized version should be constructed. */ private DnsMessage(DnsMessage message) { id = 0; qr = message.qr; opcode = message.opcode; authoritativeAnswer = message.authoritativeAnswer; truncated = message.truncated; recursionDesired = message.recursionDesired; recursionAvailable = message.recursionAvailable; authenticData = message.authenticData; checkingDisabled = message.checkingDisabled; responseCode = message.responseCode; receiveTimestamp = message.receiveTimestamp; questions = message.questions; answerSection = message.answerSection; authoritySection = message.authoritySection; additionalSection = message.additionalSection; optRrPosition = message.optRrPosition; } private static int getOptRrPosition(List> additionalSection) { int optRrPosition = -1; for (int i = 0; i < additionalSection.size(); i++) { Record record = additionalSection.get(i); if (record.type == Record.TYPE.OPT) { optRrPosition = i; break; } } return optRrPosition; } /** * Generate a binary dns packet out of this message. * * @return byte[] the binary representation. */ public byte[] toArray() { return serialize().clone(); } public DatagramPacket asDatagram(InetAddress address, int port) { byte[] bytes = serialize(); return new DatagramPacket(bytes, bytes.length, address, port); } public void writeTo(OutputStream outputStream) throws IOException { writeTo(outputStream, true); } public void writeTo(OutputStream outputStream, boolean writeLength) throws IOException { byte[] bytes = serialize(); DataOutputStream dataOutputStream = new DataOutputStream(outputStream); if (writeLength) { dataOutputStream.writeShort(bytes.length); } dataOutputStream.write(bytes); } public ByteBuffer getInByteBuffer() { byte[] bytes = serialize().clone(); return ByteBuffer.wrap(bytes); } private byte[] byteCache; private byte[] serialize() { if (byteCache != null) { return byteCache; } ByteArrayOutputStream baos = new ByteArrayOutputStream(512); DataOutputStream dos = new DataOutputStream(baos); int header = calculateHeaderBitmap(); try { dos.writeShort((short) id); dos.writeShort((short) header); if (questions == null) { dos.writeShort(0); } else { dos.writeShort((short) questions.size()); } if (answerSection == null) { dos.writeShort(0); } else { dos.writeShort((short) answerSection.size()); } if (authoritySection == null) { dos.writeShort(0); } else { dos.writeShort((short) authoritySection.size()); } if (additionalSection == null) { dos.writeShort(0); } else { dos.writeShort((short) additionalSection.size()); } if (questions != null) { for (Question question : questions) { dos.write(question.toByteArray()); } } if (answerSection != null) { for (Record answer : answerSection) { dos.write(answer.toByteArray()); } } if (authoritySection != null) { for (Record nameserverRecord : authoritySection) { dos.write(nameserverRecord.toByteArray()); } } if (additionalSection != null) { for (Record additionalResourceRecord : additionalSection) { dos.write(additionalResourceRecord.toByteArray()); } } dos.flush(); } catch (IOException e) { // Should never happen. throw new AssertionError(e); } byteCache = baos.toByteArray(); return byteCache; } int calculateHeaderBitmap() { int header = 0; if (qr) { header += 1 << 15; } if (opcode != null) { header += opcode.getValue() << 11; } if (authoritativeAnswer) { header += 1 << 10; } if (truncated) { header += 1 << 9; } if (recursionDesired) { header += 1 << 8; } if (recursionAvailable) { header += 1 << 7; } if (authenticData) { header += 1 << 5; } if (checkingDisabled) { header += 1 << 4; } if (responseCode != null) { header += responseCode.getValue(); } return header; } public Question getQuestion() { return questions.get(0); } /** * Copy the questions found in the question section. * * @return a copy of the question section questions. * @see #questions */ public List copyQuestions() { List copy = new ArrayList<>(questions.size()); copy.addAll(questions); return copy; } /** * Copy the records found in the answer section into a new list. * * @return a copy of the answer section records. * @see #answerSection */ public List> copyAnswers() { List> res = new ArrayList<>(answerSection.size()); res.addAll(answerSection); return res; } /** * Copy the records found in the authority section into a new list. * * @return a copy of the authority section records. * @see #authoritySection */ public List> copyAuthority() { List> res = new ArrayList<>(authoritySection.size()); res.addAll(authoritySection); return res; } public Edns getEdns() { if (edns != null) return edns; Record optRecord = getOptPseudoRecord(); if (optRecord == null) return null; edns = new Edns(optRecord); return edns; } @SuppressWarnings("unchecked") public Record getOptPseudoRecord() { if (optRrPosition == -1) return null; return (Record) additionalSection.get(optRrPosition); } /** * Check if the EDNS DO (DNSSEC OK) flag is set. * * @return true if the DO flag is set. */ public boolean isDnssecOk() { Edns edns = getEdns(); if (edns == null) return false; return edns.dnssecOk; } private String toStringCache; @Override public String toString() { if (toStringCache != null) return toStringCache; StringBuilder sb = new StringBuilder("DnsMessage"); asBuilder().writeToStringBuilder(sb); toStringCache = sb.toString(); return toStringCache; } private String terminalOutputCache; /** * Format the DnsMessage object in a way suitable for terminal output. * The format is loosely based on the output provided by {@code dig}. * * @return This message as a String suitable for terminal output. */ public String asTerminalOutput() { if (terminalOutputCache != null) return terminalOutputCache; StringBuilder sb = new StringBuilder(";; ->>HEADER<<-") .append(" opcode: ").append(opcode) .append(", status: ").append(responseCode) .append(", id: ").append(id).append("\n") .append(";; flags:"); if (!qr) sb.append(" qr"); if (authoritativeAnswer) sb.append(" aa"); if (truncated) sb.append(" tr"); if (recursionDesired) sb.append(" rd"); if (recursionAvailable) sb.append(" ra"); if (authenticData) sb.append(" ad"); if (checkingDisabled) sb.append(" cd"); sb.append("; QUERY: ").append(questions.size()) .append(", ANSWER: ").append(answerSection.size()) .append(", AUTHORITY: ").append(authoritySection.size()) .append(", ADDITIONAL: ").append(additionalSection.size()) .append("\n\n"); for (Record record : additionalSection) { Edns edns = Edns.fromRecord(record); if (edns != null) { sb.append(";; OPT PSEUDOSECTION:\n; ").append(edns.asTerminalOutput()); break; } } if (questions.size() != 0) { sb.append(";; QUESTION SECTION:\n"); for (Question question : questions) { sb.append(';').append(question.toString()).append('\n'); } } if (authoritySection.size() != 0) { sb.append("\n;; AUTHORITY SECTION:\n"); for (Record record : authoritySection) { sb.append(record.toString()).append('\n'); } } if (answerSection.size() != 0) { sb.append("\n;; ANSWER SECTION:\n"); for (Record record : answerSection) { sb.append(record.toString()).append('\n'); } } if (additionalSection.size() != 0) { boolean hasNonOptArr = false; for (Record record : additionalSection) { if (record.type != Record.TYPE.OPT) { if (!hasNonOptArr) { hasNonOptArr = true; sb.append("\n;; ADDITIONAL SECTION:\n"); } sb.append(record.toString()).append('\n'); } } } if (receiveTimestamp > 0) { sb.append("\n;; WHEN: ").append(new Date(receiveTimestamp).toString()); } terminalOutputCache = sb.toString(); return terminalOutputCache; } public Set getAnswersFor(Question q) { if (responseCode != RESPONSE_CODE.NO_ERROR) return null; // It would be great if we could verify that D matches q.type at this // point. But on the other hand, if it does not, then the cast to D // below will fail. Set res = new HashSet<>(answerSection.size()); for (Record record : answerSection) { if (!record.isAnswer(q)) continue; Data data = record.getPayload(); @SuppressWarnings("unchecked") D d = (D) data; boolean isNew = res.add(d); if (!isNew) { LOGGER.log(Level.WARNING, "DnsMessage contains duplicate answers. Record: " + record + "; DnsMessage: " + this); } } return res; } private long answersMinTtlCache = -1; /** * Get the minimum TTL from all answers in seconds. * * @return the minimum TTL from all answers in seconds. */ public long getAnswersMinTtl() { if (answersMinTtlCache >= 0) { return answersMinTtlCache; } answersMinTtlCache = Long.MAX_VALUE; for (Record r : answerSection) { answersMinTtlCache = Math.min(answersMinTtlCache, r.ttl); } return answersMinTtlCache; } public Builder asBuilder() { return new Builder(this); } private DnsMessage normalizedVersionCache; public DnsMessage asNormalizedVersion() { if (normalizedVersionCache == null) { normalizedVersionCache = new DnsMessage(this); } return normalizedVersionCache; } public Builder getResponseBuilder(RESPONSE_CODE responseCode) { if (qr) { throw new IllegalStateException(); } Builder responseBuilder = DnsMessage.builder() .setQrFlag(true) .setResponseCode(responseCode) .setId(id) .setQuestion(getQuestion()); return responseBuilder; } private transient Integer hashCodeCache; @Override public int hashCode() { if (hashCodeCache == null) { byte[] bytes = serialize(); hashCodeCache = Arrays.hashCode(bytes); } return hashCodeCache; } private enum SectionName { answer, authority, additional, } private List> filterSectionByType(boolean stopOnFirst, SectionName sectionName, Class type) { List> sectionToFilter; switch (sectionName) { case answer: sectionToFilter = answerSection; break; case authority: sectionToFilter = authoritySection; break; case additional: sectionToFilter = additionalSection; break; default: throw new AssertionError("Unknown section name " + sectionName); } List> res = new ArrayList<>(stopOnFirst ? 1 : sectionToFilter.size()); for (Record record : sectionToFilter) { Record target = record.ifPossibleAs(type); if (target != null) { res.add(target); if (stopOnFirst) { return res; } } } return res; } private List> filterSectionByType(SectionName sectionName, Class type) { return filterSectionByType(false, sectionName, type); } private Record getFirstOfType(SectionName sectionName, Class type) { List> result = filterSectionByType(true, sectionName, type); if (result.isEmpty()) { return null; } return result.get(0); } public List> filterAnswerSectionBy(Class type) { return filterSectionByType(SectionName.answer, type); } public List> filterAuthoritySectionBy(Class type) { return filterSectionByType(SectionName.authority, type); } public List> filterAdditionalSectionBy(Class type) { return filterSectionByType(SectionName.additional, type); } public Record getFirstOfTypeFromAnswerSection(Class type) { return getFirstOfType(SectionName.answer, type); } public Record getFirstOfTypeFromAuthoritySection(Class type) { return getFirstOfType(SectionName.authority, type); } public Record getFirstOfTypeFromAdditionalSection(Class type) { return getFirstOfType(SectionName.additional, type); } @Override public boolean equals(Object other) { if (!(other instanceof DnsMessage)) { return false; } if (other == this) { return true; } DnsMessage otherDnsMessage = (DnsMessage) other; byte[] otherBytes = otherDnsMessage.serialize(); byte[] myBytes = serialize(); return Arrays.equals(myBytes, otherBytes); } public static Builder builder() { return new DnsMessage.Builder(); } public static final class Builder { private Builder() { } private Builder(DnsMessage message) { id = message.id; opcode = message.opcode; responseCode = message.responseCode; query = message.qr; authoritativeAnswer = message.authoritativeAnswer; truncated = message.truncated; recursionDesired = message.recursionDesired; recursionAvailable = message.recursionAvailable; authenticData = message.authenticData; checkingDisabled = message.checkingDisabled; receiveTimestamp = message.receiveTimestamp; // Copy the unmodifiable lists over into this new builder. questions = new ArrayList<>(message.questions.size()); questions.addAll(message.questions); answerSection = new ArrayList<>(message.answerSection.size()); answerSection.addAll(message.answerSection); authoritySection = new ArrayList<>(message.authoritySection.size()); authoritySection.addAll(message.authoritySection); additionalSection = new ArrayList<>(message.additionalSection.size()); additionalSection.addAll(message.additionalSection); } private int id; private OPCODE opcode = OPCODE.QUERY; private RESPONSE_CODE responseCode = RESPONSE_CODE.NO_ERROR; private boolean query; private boolean authoritativeAnswer; private boolean truncated; private boolean recursionDesired; private boolean recursionAvailable; private boolean authenticData; private boolean checkingDisabled; private long receiveTimestamp = -1; private List questions; private List> answerSection; private List> authoritySection; private List> additionalSection; private Edns.Builder ednsBuilder; /** * Set the current DNS message id. * * @param id The new DNS message id. * @return a reference to this builder. */ public Builder setId(int id) { this.id = id & 0xffff; return this; } public Builder setOpcode(OPCODE opcode) { this.opcode = opcode; return this; } public Builder setResponseCode(RESPONSE_CODE responseCode) { this.responseCode = responseCode; return this; } /** * Set the QR flag. Note that this will be true if the message is a * response and false if it is a query. * * @param query The new QR flag status. * @return a reference to this builder. */ public Builder setQrFlag(boolean query) { this.query = query; return this; } /** * Set the authoritative answer flag. * * @param authoritativeAnswer Tge new authoritative answer value. * @return a reference to this builder. */ public Builder setAuthoritativeAnswer(boolean authoritativeAnswer) { this.authoritativeAnswer = authoritativeAnswer; return this; } /** * Set the truncation bit on this DNS message. * * @param truncated The new truncated bit status. * @return a reference to this builder. */ public Builder setTruncated(boolean truncated) { this.truncated = truncated; return this; } /** * Set the recursion desired flag on this message. * * @param recursionDesired The new recusrion setting. * @return a reference to this builder. */ public Builder setRecursionDesired(boolean recursionDesired) { this.recursionDesired = recursionDesired; return this; } /** * Set the recursion available flog from this DNS message. * * @param recursionAvailable The new recursion available status. * @return a reference to this builder. */ public Builder setRecursionAvailable(boolean recursionAvailable) { this.recursionAvailable = recursionAvailable; return this; } /** * Set the authentic data flag on this DNS message. * * @param authenticData The new authentic data flag value. * @return a reference to this builder. */ public Builder setAuthenticData(boolean authenticData) { this.authenticData = authenticData; return this; } /** * Change the check status of this packet. * * @param checkingDisabled The new check disabled value. * @return a reference to this builder. */ @Deprecated public Builder setCheckDisabled(boolean checkingDisabled) { this.checkingDisabled = checkingDisabled; return this; } /** * Change the check status of this packet. * * @param checkingDisabled The new check disabled value. * @return a reference to this builder. */ public Builder setCheckingDisabled(boolean checkingDisabled) { this.checkingDisabled = checkingDisabled; return this; } public void copyFlagsFrom(DnsMessage dnsMessage) { this.query = dnsMessage.qr; this.authoritativeAnswer = dnsMessage.authenticData; this.truncated = dnsMessage.truncated; this.recursionDesired = dnsMessage.recursionDesired; this.recursionAvailable = dnsMessage.recursionAvailable; this.authenticData = dnsMessage.authenticData; this.checkingDisabled = dnsMessage.checkingDisabled; } public Builder setReceiveTimestamp(long receiveTimestamp) { this.receiveTimestamp = receiveTimestamp; return this; } public Builder addQuestion(Question question) { if (questions == null) { questions = new ArrayList<>(1); } questions.add(question); return this; } /** * Set the question part of this message. * * @param questions The questions. * @return a reference to this builder. */ public Builder setQuestions(List questions) { this.questions = questions; return this; } /** * Set the question part of this message. * * @param question The question. * @return a reference to this builder. */ public Builder setQuestion(Question question) { this.questions = new ArrayList<>(1); this.questions.add(question); return this; } public Builder addAnswer(Record answer) { if (answerSection == null) { answerSection = new ArrayList<>(1); } answerSection.add(answer); return this; } public Builder addAnswers(Collection> records) { if (answerSection == null) { answerSection = new ArrayList<>(records.size()); } answerSection.addAll(records); return this; } public Builder setAnswers(Collection> records) { answerSection = new ArrayList<>(records.size()); answerSection.addAll(records); return this; } public List> getAnswers() { if (answerSection == null) { return Collections.emptyList(); } return answerSection; } public Builder addNameserverRecords(Record record) { if (authoritySection == null) { authoritySection = new ArrayList<>(8); } authoritySection.add(record); return this; } public Builder setNameserverRecords(Collection> records) { authoritySection = new ArrayList<>(records.size()); authoritySection.addAll(records); return this; } public Builder setAdditionalResourceRecords(Collection> records) { additionalSection = new ArrayList<>(records.size()); additionalSection.addAll(records); return this; } public Builder addAdditionalResourceRecord(Record record) { if (additionalSection == null) { additionalSection = new ArrayList<>(); } additionalSection.add(record); return this; } public Builder addAdditionalResourceRecords(List> records) { if (additionalSection == null) { additionalSection = new ArrayList<>(records.size()); } additionalSection.addAll(records); return this; } public List> getAdditionalResourceRecords() { if (additionalSection == null) { return Collections.emptyList(); } return additionalSection; } /** * Get the @{link EDNS} builder. If no builder has been set so far, then a new one will be created. *

* The EDNS record can be used to announce the supported size of UDP payload as well as additional flags. *

*

* Note that some networks and firewalls are known to block big UDP payloads. 1280 should be a reasonable value, * everything below 512 is treated as 512 and should work on all networks. *

* * @return a EDNS builder. */ public Edns.Builder getEdnsBuilder() { if (ednsBuilder == null) { ednsBuilder = Edns.builder(); } return ednsBuilder; } public DnsMessage build() { return new DnsMessage(this); } private void writeToStringBuilder(StringBuilder sb) { sb.append('(') .append(id) .append(' ') .append(opcode) .append(' ') .append(responseCode) .append(' '); if (query) { sb.append("resp[qr=1]"); } else { sb.append("query[qr=0]"); } if (authoritativeAnswer) sb.append(" aa"); if (truncated) sb.append(" tr"); if (recursionDesired) sb.append(" rd"); if (recursionAvailable) sb.append(" ra"); if (authenticData) sb.append(" ad"); if (checkingDisabled) sb.append(" cd"); sb.append(")\n"); if (questions != null) { for (Question question : questions) { sb.append("[Q: ").append(question).append("]\n"); } } if (answerSection != null) { for (Record record : answerSection) { sb.append("[A: ").append(record).append("]\n"); } } if (authoritySection != null) { for (Record record : authoritySection) { sb.append("[N: ").append(record).append("]\n"); } } if (additionalSection != null) { for (Record record : additionalSection) { sb.append("[X: "); Edns edns = Edns.fromRecord(record); if (edns != null) { sb.append(edns.toString()); } else { sb.append(record); } sb.append("]\n"); } } // Strip trailing newline. if (sb.charAt(sb.length() - 1) == '\n') { sb.setLength(sb.length() - 1); } } @Override public String toString() { StringBuilder sb = new StringBuilder("Builder of DnsMessage"); writeToStringBuilder(sb); return sb.toString(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy