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

org.pcap4j.packet.DnsPacket Maven / Gradle / Ivy

/*_##########################################################################
  _##
  _##  Copyright (C) 2016  Pcap4J.org
  _##
  _##########################################################################
*/

package org.pcap4j.packet;

import static org.pcap4j.util.ByteArrays.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.pcap4j.packet.namednumber.DnsOpCode;
import org.pcap4j.packet.namednumber.DnsRCode;
import org.pcap4j.util.ByteArrays;

/**
 * DNS packet.
 *
 * @see RFC 1035
 * @see DNS
 *     Header Flags
 * @author Kaito Yamada
 * @since pcap4j 1.7.1
 */
public final class DnsPacket extends AbstractPacket {

  /** */
  private static final long serialVersionUID = 2804715680374557063L;

  private final DnsHeader header;

  /**
   * A static factory method. This method validates the arguments by {@link
   * ByteArrays#validateBounds(byte[], int, int)}, which may throw exceptions undocumented here.
   *
   * @param rawData rawData
   * @param offset offset
   * @param length length
   * @return a new DnsPacket object.
   * @throws IllegalRawDataException if parsing the raw data fails.
   */
  public static DnsPacket newPacket(byte[] rawData, int offset, int length)
      throws IllegalRawDataException {
    ByteArrays.validateBounds(rawData, offset, length);
    return new DnsPacket(rawData, offset, length);
  }

  private DnsPacket(byte[] rawData, int offset, int length) throws IllegalRawDataException {
    this.header = new DnsHeader(rawData, offset, length);
  }

  private DnsPacket(Builder builder) {
    if (builder == null || builder.opCode == null || builder.rCode == null) {
      StringBuilder sb = new StringBuilder();
      sb.append("builder: ")
          .append(builder)
          .append(" builder.opCode: ")
          .append(builder.opCode)
          .append(" builder.rCode: ")
          .append(builder.rCode);
      throw new NullPointerException(sb.toString());
    }

    this.header = new DnsHeader(builder);
  }

  @Override
  public DnsHeader getHeader() {
    return header;
  }

  @Override
  public Builder getBuilder() {
    return new Builder(this);
  }

  /**
   * @author Kaito Yamada
   * @since pcap4j 1.7.1
   */
  public static final class Builder extends AbstractBuilder {

    private short id;
    private boolean response;
    private DnsOpCode opCode;
    private boolean authoritativeAnswer;
    private boolean truncated;
    private boolean recursionDesired;
    private boolean recursionAvailable;
    private boolean reserved;
    private boolean authenticData;
    private boolean checkingDisabled;
    private DnsRCode rCode;
    private short qdCount;
    private short anCount;
    private short nsCount;
    private short arCount;
    private List questions;
    private List answers;
    private List authorities;
    private List additionalInfo;

    /** */
    public Builder() {}

    private Builder(DnsPacket packet) {
      this.id = packet.header.id;
      this.response = packet.header.response;
      this.opCode = packet.header.opCode;
      this.authoritativeAnswer = packet.header.authoritativeAnswer;
      this.truncated = packet.header.truncated;
      this.recursionDesired = packet.header.recursionDesired;
      this.recursionAvailable = packet.header.recursionAvailable;
      this.reserved = packet.header.reserved;
      this.authenticData = packet.header.authenticData;
      this.checkingDisabled = packet.header.checkingDisabled;
      this.rCode = packet.header.rCode;
      this.qdCount = packet.header.qdCount;
      this.anCount = packet.header.anCount;
      this.nsCount = packet.header.nsCount;
      this.arCount = packet.header.arCount;
      this.questions = packet.header.questions;
      this.answers = packet.header.answers;
      this.authorities = packet.header.authorities;
      this.additionalInfo = packet.header.additionalInfo;
    }

    /**
     * @param id id
     * @return this Builder object for method chaining.
     */
    public Builder id(short id) {
      this.id = id;
      return this;
    }

    /**
     * @param response response
     * @return this Builder object for method chaining.
     */
    public Builder response(boolean response) {
      this.response = response;
      return this;
    }

    /**
     * @param opCode opCode
     * @return this Builder object for method chaining.
     */
    public Builder opCode(DnsOpCode opCode) {
      this.opCode = opCode;
      return this;
    }

    /**
     * @param authoritativeAnswer authoritativeAnswer
     * @return this Builder object for method chaining.
     */
    public Builder authoritativeAnswer(boolean authoritativeAnswer) {
      this.authoritativeAnswer = authoritativeAnswer;
      return this;
    }

    /**
     * @param truncated truncated
     * @return this Builder object for method chaining.
     */
    public Builder truncated(boolean truncated) {
      this.truncated = truncated;
      return this;
    }

    /**
     * @param recursionDesired recursionDesired
     * @return this Builder object for method chaining.
     */
    public Builder recursionDesired(boolean recursionDesired) {
      this.recursionDesired = recursionDesired;
      return this;
    }

    /**
     * @param recursionAvailable recursionAvailable
     * @return this Builder object for method chaining.
     */
    public Builder recursionAvailable(boolean recursionAvailable) {
      this.recursionAvailable = recursionAvailable;
      return this;
    }

    /**
     * @param reserved reserved
     * @return this Builder object for method chaining.
     */
    public Builder reserved(boolean reserved) {
      this.reserved = reserved;
      return this;
    }

    /**
     * @param authenticData authenticData
     * @return this Builder object for method chaining.
     */
    public Builder authenticData(boolean authenticData) {
      this.authenticData = authenticData;
      return this;
    }

    /**
     * @param checkingDisabled checkingDisabled
     * @return this Builder object for method chaining.
     */
    public Builder checkingDisabled(boolean checkingDisabled) {
      this.checkingDisabled = checkingDisabled;
      return this;
    }

    /**
     * @param rCode rCode
     * @return this Builder object for method chaining.
     */
    public Builder rCode(DnsRCode rCode) {
      this.rCode = rCode;
      return this;
    }

    /**
     * @param qdCount qdCount
     * @return this Builder object for method chaining.
     */
    public Builder qdCount(short qdCount) {
      this.qdCount = qdCount;
      return this;
    }

    /**
     * @param anCount anCount
     * @return this Builder object for method chaining.
     */
    public Builder anCount(short anCount) {
      this.anCount = anCount;
      return this;
    }

    /**
     * @param nsCount nsCount
     * @return this Builder object for method chaining.
     */
    public Builder nsCount(short nsCount) {
      this.nsCount = nsCount;
      return this;
    }

    /**
     * @param arCount arCount
     * @return this Builder object for method chaining.
     */
    public Builder arCount(short arCount) {
      this.arCount = arCount;
      return this;
    }

    /**
     * @param questions questions
     * @return this Builder object for method chaining.
     */
    public Builder questions(List questions) {
      this.questions = questions;
      return this;
    }

    /**
     * @param answers answers
     * @return this Builder object for method chaining.
     */
    public Builder answers(List answers) {
      this.answers = answers;
      return this;
    }

    /**
     * @param authorities authorities
     * @return this Builder object for method chaining.
     */
    public Builder authorities(List authorities) {
      this.authorities = authorities;
      return this;
    }

    /**
     * @param additionalInfo additionalInfo
     * @return this Builder object for method chaining.
     */
    public Builder additionalInfo(List additionalInfo) {
      this.additionalInfo = additionalInfo;
      return this;
    }

    @Override
    public DnsPacket build() {
      return new DnsPacket(this);
    }
  }

  /**
   * DNS header
   *
   * 
   * +---------------------+
   * |        Header       |
   * +---------------------+
   * |       Question      | the question for the name server
   * +---------------------+
   * |        Answer       | RRs answering the question
   * +---------------------+
   * |      Authority      | RRs pointing toward an authority
   * +---------------------+
   * |      Additional     | RRs holding additional information
   * +---------------------+
   *
   * Header:
   *                                 1  1  1  1  1  1
   *   0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
   * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   * |                      ID                       |
   * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   * |QR|   Opcode  |AA|TC|RD|RA| Z|AD|CD|   RCODE   |
   * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   * |                    QDCOUNT                    |
   * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   * |                    ANCOUNT                    |
   * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   * |                    NSCOUNT                    |
   * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   * |                    ARCOUNT                    |
   * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   * 
* * @see RFC 1035 * @see RFC 2535 * @author Kaito Yamada * @version pcap4j 1.7.1 */ public static final class DnsHeader extends AbstractHeader { /** */ private static final long serialVersionUID = -2779530760536525672L; private static final int ID_OFFSET = 0; private static final int ID_SIZE = SHORT_SIZE_IN_BYTES; private static final int FLAGS_OFFSET = ID_OFFSET + ID_SIZE; private static final int FLAGS_SIZE = SHORT_SIZE_IN_BYTES; private static final int QDCOUNT_OFFSET = FLAGS_OFFSET + FLAGS_SIZE; private static final int QDCOUNT_SIZE = SHORT_SIZE_IN_BYTES; private static final int ANCOUNT_OFFSET = QDCOUNT_OFFSET + QDCOUNT_SIZE; private static final int ANCOUNT_SIZE = SHORT_SIZE_IN_BYTES; private static final int NSCOUNT_OFFSET = ANCOUNT_OFFSET + ANCOUNT_SIZE; private static final int NSCOUNT_SIZE = SHORT_SIZE_IN_BYTES; private static final int ARCOUNT_OFFSET = NSCOUNT_OFFSET + NSCOUNT_SIZE; private static final int ARCOUNT_SIZE = SHORT_SIZE_IN_BYTES; private static final int DNS_MIN_HEADER_SIZE = ARCOUNT_OFFSET + ARCOUNT_SIZE; private final short id; private final boolean response; private final DnsOpCode opCode; private final boolean authoritativeAnswer; private final boolean truncated; private final boolean recursionDesired; private final boolean recursionAvailable; private final boolean reserved; private final boolean authenticData; private final boolean checkingDisabled; private final DnsRCode rCode; private final short qdCount; private final short anCount; private final short nsCount; private final short arCount; private final List questions; private final List answers; private final List authorities; private final List additionalInfo; private DnsHeader(byte[] rawData, int offset, int length) throws IllegalRawDataException { if (length < DNS_MIN_HEADER_SIZE) { StringBuilder sb = new StringBuilder(200); sb.append("The data is too short to build a DnsHeader (") .append(DNS_MIN_HEADER_SIZE) .append(" bytes). data: ") .append(ByteArrays.toHexString(rawData, " ")) .append(", offset: ") .append(offset) .append(", length: ") .append(length); throw new IllegalRawDataException(sb.toString()); } this.id = ByteArrays.getShort(rawData, ID_OFFSET + offset); short flags = ByteArrays.getShort(rawData, FLAGS_OFFSET + offset); this.response = (flags & 0x8000) != 0; this.opCode = DnsOpCode.getInstance((byte) ((flags >> 11) & 0x0F)); this.authoritativeAnswer = (flags & 0x0400) != 0; this.truncated = (flags & 0x0200) != 0; this.recursionDesired = (flags & 0x0100) != 0; this.recursionAvailable = (flags & 0x0080) != 0; this.reserved = (flags & 0x0040) != 0; this.authenticData = (flags & 0x0020) != 0; this.checkingDisabled = (flags & 0x0010) != 0; this.rCode = DnsRCode.getInstance((byte) (flags & 0x0F)); this.qdCount = ByteArrays.getShort(rawData, QDCOUNT_OFFSET + offset); this.anCount = ByteArrays.getShort(rawData, ANCOUNT_OFFSET + offset); this.nsCount = ByteArrays.getShort(rawData, NSCOUNT_OFFSET + offset); this.arCount = ByteArrays.getShort(rawData, ARCOUNT_OFFSET + offset); int qdCnt = getQdCountAsInt(); int anCnt = getAnCountAsInt(); int nsCnt = getNsCountAsInt(); int arCnt = getArCountAsInt(); this.questions = new ArrayList(qdCnt); this.answers = new ArrayList(anCnt); this.authorities = new ArrayList(nsCnt); this.additionalInfo = new ArrayList(arCnt); int cursor = DNS_MIN_HEADER_SIZE; for (int i = 0; i < qdCnt; i++) { int remainingLen = length - cursor; if (remainingLen == 0) { StringBuilder sb = new StringBuilder(200); sb.append("The data is too short to build a question in DnsHeader. data: ") .append(ByteArrays.toHexString(rawData, " ")) .append(", offset: ") .append(offset) .append(", length: ") .append(length) .append(", cursor: ") .append(cursor); throw new IllegalRawDataException(sb.toString()); } DnsQuestion question = DnsQuestion.newInstance(rawData, offset + cursor, remainingLen); questions.add(question); cursor += question.length(); } for (int i = 0; i < anCnt; i++) { int remainingLen = length - cursor; if (remainingLen == 0) { StringBuilder sb = new StringBuilder(200); sb.append("The data is too short to build an answer in DnsHeader. data: ") .append(ByteArrays.toHexString(rawData, " ")) .append(", offset: ") .append(offset) .append(", length: ") .append(length) .append(", cursor: ") .append(cursor); throw new IllegalRawDataException(sb.toString()); } DnsResourceRecord answer = DnsResourceRecord.newInstance(rawData, offset + cursor, remainingLen); answers.add(answer); cursor += answer.length(); } for (int i = 0; i < nsCnt; i++) { int remainingLen = length - cursor; if (remainingLen == 0) { StringBuilder sb = new StringBuilder(200); sb.append("The data is too short to build an authority in DnsHeader. data: ") .append(ByteArrays.toHexString(rawData, " ")) .append(", offset: ") .append(offset) .append(", length: ") .append(length) .append(", cursor: ") .append(cursor); throw new IllegalRawDataException(sb.toString()); } DnsResourceRecord authority = DnsResourceRecord.newInstance(rawData, offset + cursor, remainingLen); authorities.add(authority); cursor += authority.length(); } for (int i = 0; i < arCnt; i++) { int remainingLen = length - cursor; if (remainingLen == 0) { StringBuilder sb = new StringBuilder(200); sb.append("The data is too short to build additional info in DnsHeader. data: ") .append(ByteArrays.toHexString(rawData, " ")) .append(", offset: ") .append(offset) .append(", length: ") .append(length) .append(", cursor: ") .append(cursor); throw new IllegalRawDataException(sb.toString()); } DnsResourceRecord info = DnsResourceRecord.newInstance(rawData, offset + cursor, remainingLen); additionalInfo.add(info); cursor += info.length(); } } private DnsHeader(Builder builder) { this.id = builder.id; this.response = builder.response; this.opCode = builder.opCode; this.authoritativeAnswer = builder.authoritativeAnswer; this.truncated = builder.truncated; this.recursionDesired = builder.recursionDesired; this.recursionAvailable = builder.recursionAvailable; this.reserved = builder.reserved; this.authenticData = builder.authenticData; this.checkingDisabled = builder.checkingDisabled; this.rCode = builder.rCode; this.qdCount = builder.qdCount; this.anCount = builder.anCount; this.nsCount = builder.nsCount; this.arCount = builder.arCount; if (builder.questions != null) { if (builder.questions.size() > 65535) { throw new IllegalArgumentException( "The number of questions must be less than 65536. builder.questions.size(): " + builder.questions.size()); } this.questions = new ArrayList(builder.questions); } else { this.questions = Collections.emptyList(); } if (builder.answers != null) { if (builder.answers.size() > 65535) { throw new IllegalArgumentException( "The number of answers must be less than 65536. builder.answers.size(): " + builder.answers.size()); } this.answers = new ArrayList(builder.answers); } else { this.answers = Collections.emptyList(); } if (builder.authorities != null) { if (builder.authorities.size() > 65535) { throw new IllegalArgumentException( "The number of authorities must be less than 65536. builder.authorities.size(): " + builder.authorities.size()); } this.authorities = new ArrayList(builder.authorities); } else { this.authorities = Collections.emptyList(); } if (builder.additionalInfo != null) { if (builder.additionalInfo.size() > 65535) { throw new IllegalArgumentException( "The number of additionalInfo elements must be less than 65536." + " builder.additionalInfo.size(): " + builder.additionalInfo.size()); } this.additionalInfo = new ArrayList(builder.additionalInfo); } else { this.additionalInfo = Collections.emptyList(); } } /** @return id */ public short getId() { return id; } /** @return true if the QR bit is set to 1; false otherwise. */ public boolean isResponse() { return response; } /** @return opCode */ public DnsOpCode getOpCode() { return opCode; } /** @return true if the AA bit is set to 1; false otherwise. */ public boolean isAuthoritativeAnswer() { return authoritativeAnswer; } /** @return true if the TC bit is set to 1; false otherwise. */ public boolean isTruncated() { return truncated; } /** @return true if the RD bit is set to 1; false otherwise. */ public boolean isRecursionDesired() { return recursionDesired; } /** @return true if the RA bit is set to 1; false otherwise. */ public boolean isRecursionAvailable() { return recursionAvailable; } /** @return true if the Z bit is set to 1; false otherwise. */ public boolean getReservedBit() { return reserved; } /** @return true if the AD bit is set to 1; false otherwise. */ public boolean isAuthenticData() { return authenticData; } /** @return true if the CD bit is set to 1; false otherwise. */ public boolean isCheckingDisabled() { return checkingDisabled; } /** @return rCode */ public DnsRCode getrCode() { return rCode; } /** @return qdCount */ public short getQdCount() { return qdCount; } /** @return qdCount */ public int getQdCountAsInt() { return qdCount & 0xFFFF; } /** @return anCount */ public short getAnCount() { return anCount; } /** @return anCount */ public int getAnCountAsInt() { return anCount & 0xFFFF; } /** @return nsCount */ public short getNsCount() { return nsCount; } /** @return nsCount */ public int getNsCountAsInt() { return nsCount & 0xFFFF; } /** @return arCount */ public short getArCount() { return arCount; } /** @return arCount */ public int getArCountAsInt() { return arCount & 0xFFFF; } /** @return questions */ public List getQuestions() { return new ArrayList(questions); } /** @return answers */ public List getAnswers() { return new ArrayList(answers); } /** @return authorities */ public List getAuthorities() { return new ArrayList(authorities); } /** @return additionalInfo */ public List getAdditionalInfo() { return new ArrayList(additionalInfo); } @Override protected List getRawFields() { List rawFields = new ArrayList(); rawFields.add(ByteArrays.toByteArray(id)); byte[] flags = new byte[2]; flags[0] = (byte) (opCode.value() << 3); if (response) { flags[0] |= 0x80; } if (authoritativeAnswer) { flags[0] |= 0x04; } if (truncated) { flags[0] |= 0x02; } if (recursionDesired) { flags[0] |= 0x01; } flags[1] = rCode.value(); if (recursionAvailable) { flags[1] |= 0x80; } if (reserved) { flags[1] |= 0x40; } if (authenticData) { flags[1] |= 0x20; } if (checkingDisabled) { flags[1] |= 0x10; } rawFields.add(flags); rawFields.add(ByteArrays.toByteArray(qdCount)); rawFields.add(ByteArrays.toByteArray(anCount)); rawFields.add(ByteArrays.toByteArray(nsCount)); rawFields.add(ByteArrays.toByteArray(arCount)); for (DnsQuestion question : questions) { rawFields.add(question.getRawData()); } for (DnsResourceRecord answer : answers) { rawFields.add(answer.getRawData()); } for (DnsResourceRecord authority : authorities) { rawFields.add(authority.getRawData()); } for (DnsResourceRecord info : additionalInfo) { rawFields.add(info.getRawData()); } return rawFields; } @Override public int length() { int len = DNS_MIN_HEADER_SIZE; for (DnsQuestion question : questions) { len += question.length(); } for (DnsResourceRecord answer : answers) { len += answer.length(); } for (DnsResourceRecord authority : authorities) { len += authority.length(); } for (DnsResourceRecord info : additionalInfo) { len += info.length(); } return len; } @Override protected String buildString() { StringBuilder sb = new StringBuilder(); String ls = System.getProperty("line.separator"); sb.append("[DNS Header (").append(length()).append(" bytes)]").append(ls); sb.append(" ID: ").append("0x" + ByteArrays.toHexString(id, "")).append(ls); sb.append(" QR: ").append(response ? "response" : "query").append(ls); sb.append(" OPCODE: ").append(opCode).append(ls); sb.append(" Authoritative Answer: ").append(authoritativeAnswer).append(ls); sb.append(" Truncated: ").append(truncated).append(ls); sb.append(" Recursion Desired: ").append(recursionDesired).append(ls); sb.append(" Recursion Available: ").append(recursionAvailable).append(ls); sb.append(" Reserved Bit: ").append(reserved ? 1 : 0).append(ls); sb.append(" Authentic Data: ").append(authenticData).append(ls); sb.append(" Checking Disabled: ").append(checkingDisabled).append(ls); sb.append(" RCODE: ").append(rCode).append(ls); sb.append(" QDCOUNT: ").append(qdCount).append(ls); sb.append(" ANCOUNT: ").append(anCount).append(ls); sb.append(" NSCOUNT: ").append(nsCount).append(ls); sb.append(" ARCOUNT: ").append(arCount).append(ls); byte[] headerRawData = getRawData(); for (DnsQuestion question : questions) { sb.append(" Question:").append(ls).append(question.toString(" ", headerRawData)); } for (DnsResourceRecord answer : answers) { sb.append(" Answer:").append(ls).append(answer.toString(" ", headerRawData)); } for (DnsResourceRecord authority : authorities) { sb.append(" Authority:").append(ls).append(authority.toString(" ", headerRawData)); } for (DnsResourceRecord info : additionalInfo) { sb.append(" Additional:").append(ls).append(info.toString(" ", headerRawData)); } return sb.toString(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!super.equals(obj)) { return false; } if (getClass() != obj.getClass()) { return false; } DnsHeader other = (DnsHeader) obj; if (!additionalInfo.equals(other.additionalInfo)) { return false; } if (anCount != other.anCount) { return false; } if (!answers.equals(other.answers)) { return false; } if (arCount != other.arCount) { return false; } if (authenticData != other.authenticData) { return false; } if (authoritativeAnswer != other.authoritativeAnswer) { return false; } if (!authorities.equals(other.authorities)) { return false; } if (checkingDisabled != other.checkingDisabled) { return false; } if (id != other.id) { return false; } if (nsCount != other.nsCount) { return false; } if (!opCode.equals(other.opCode)) { return false; } if (qdCount != other.qdCount) { return false; } if (!questions.equals(other.questions)) { return false; } if (!rCode.equals(other.rCode)) { return false; } if (recursionAvailable != other.recursionAvailable) { return false; } if (recursionDesired != other.recursionDesired) { return false; } if (reserved != other.reserved) { return false; } if (response != other.response) { return false; } if (truncated != other.truncated) { return false; } return true; } @Override protected int calcHashCode() { final int prime = 31; int result = 17; result = prime * result + additionalInfo.hashCode(); result = prime * result + anCount; result = prime * result + answers.hashCode(); result = prime * result + arCount; result = prime * result + (authenticData ? 1231 : 1237); result = prime * result + (authoritativeAnswer ? 1231 : 1237); result = prime * result + authorities.hashCode(); result = prime * result + (checkingDisabled ? 1231 : 1237); result = prime * result + id; result = prime * result + nsCount; result = prime * result + opCode.hashCode(); result = prime * result + qdCount; result = prime * result + questions.hashCode(); result = prime * result + rCode.hashCode(); result = prime * result + (recursionAvailable ? 1231 : 1237); result = prime * result + (recursionDesired ? 1231 : 1237); result = prime * result + (reserved ? 1231 : 1237); result = prime * result + (response ? 1231 : 1237); result = prime * result + (truncated ? 1231 : 1237); return result; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy