diff options
Diffstat (limited to 'common/framework/com/android/net/module/util/DnsPacket.java')
-rw-r--r-- | common/framework/com/android/net/module/util/DnsPacket.java | 599 |
1 files changed, 0 insertions, 599 deletions
diff --git a/common/framework/com/android/net/module/util/DnsPacket.java b/common/framework/com/android/net/module/util/DnsPacket.java deleted file mode 100644 index 0dcdf1e6..00000000 --- a/common/framework/com/android/net/module/util/DnsPacket.java +++ /dev/null @@ -1,599 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.net.module.util; - -import static android.net.DnsResolver.TYPE_A; -import static android.net.DnsResolver.TYPE_AAAA; - -import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; -import static com.android.net.module.util.DnsPacketUtils.DnsRecordParser.domainNameToLabels; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.text.TextUtils; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.net.module.util.DnsPacketUtils.DnsRecordParser; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.net.InetAddress; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** - * Defines basic data for DNS protocol based on RFC 1035. - * Subclasses create the specific format used in DNS packet. - * - * @hide - */ -public abstract class DnsPacket { - /** - * Type of the canonical name for an alias. Refer to RFC 1035 section 3.2.2. - */ - // TODO: Define the constant as a public constant in DnsResolver since it can never change. - private static final int TYPE_CNAME = 5; - - /** - * Thrown when parsing packet failed. - */ - public static class ParseException extends RuntimeException { - public String reason; - public ParseException(@NonNull String reason) { - super(reason); - this.reason = reason; - } - - public ParseException(@NonNull String reason, @NonNull Throwable cause) { - super(reason, cause); - this.reason = reason; - } - } - - /** - * DNS header for DNS protocol based on RFC 1035 section 4.1.1. - * - * 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 | RCODE | - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * | QDCOUNT | - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * | ANCOUNT | - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * | NSCOUNT | - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * | ARCOUNT | - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - */ - public static class DnsHeader { - private static final String TAG = "DnsHeader"; - private static final int SIZE_IN_BYTES = 12; - private final int mId; - private final int mFlags; - private final int[] mRecordCount; - - /* If this bit in the 'flags' field is set to 0, the DNS message corresponding to this - * header is a query; otherwise, it is a response. - */ - private static final int FLAGS_SECTION_QR_BIT = 15; - - /** - * Create a new DnsHeader from a positioned ByteBuffer. - * - * The ByteBuffer must be in network byte order (which is the default). - * Reads the passed ByteBuffer from its current position and decodes a DNS header. - * When this constructor returns, the reading position of the ByteBuffer has been - * advanced to the end of the DNS header record. - * This is meant to chain with other methods reading a DNS response in sequence. - */ - @VisibleForTesting - public DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException { - Objects.requireNonNull(buf); - mId = Short.toUnsignedInt(buf.getShort()); - mFlags = Short.toUnsignedInt(buf.getShort()); - mRecordCount = new int[NUM_SECTIONS]; - for (int i = 0; i < NUM_SECTIONS; ++i) { - mRecordCount[i] = Short.toUnsignedInt(buf.getShort()); - } - } - - /** - * Determines if the DNS message corresponding to this header is a response, as defined in - * RFC 1035 Section 4.1.1. - */ - public boolean isResponse() { - return (mFlags & (1 << FLAGS_SECTION_QR_BIT)) != 0; - } - - /** - * Create a new DnsHeader from specified parameters. - * - * This constructor only builds the question and answer sections. Authority - * and additional sections are not supported. Useful when synthesizing dns - * responses from query or reply packets. - */ - @VisibleForTesting - public DnsHeader(int id, int flags, int qdcount, int ancount) { - this.mId = id; - this.mFlags = flags; - mRecordCount = new int[NUM_SECTIONS]; - mRecordCount[QDSECTION] = qdcount; - mRecordCount[ANSECTION] = ancount; - } - - /** - * Get record count by type. - */ - public int getRecordCount(int type) { - return mRecordCount[type]; - } - - /** - * Get flags of this instance. - */ - public int getFlags() { - return mFlags; - } - - /** - * Get id of this instance. - */ - public int getId() { - return mId; - } - - @Override - public String toString() { - return "DnsHeader{" + "id=" + mId + ", flags=" + mFlags - + ", recordCounts=" + Arrays.toString(mRecordCount) + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o.getClass() != getClass()) return false; - final DnsHeader other = (DnsHeader) o; - return mId == other.mId - && mFlags == other.mFlags - && Arrays.equals(mRecordCount, other.mRecordCount); - } - - @Override - public int hashCode() { - return 31 * mId + 37 * mFlags + Arrays.hashCode(mRecordCount); - } - - /** - * Get DnsHeader as byte array. - */ - @NonNull - public byte[] getBytes() { - // TODO: if this is called often, optimize the ByteBuffer out and write to the - // array directly. - final ByteBuffer buf = ByteBuffer.allocate(SIZE_IN_BYTES); - buf.putShort((short) mId); - buf.putShort((short) mFlags); - for (int i = 0; i < NUM_SECTIONS; ++i) { - buf.putShort((short) mRecordCount[i]); - } - return buf.array(); - } - } - - /** - * Superclass for DNS questions and DNS resource records. - * - * DNS questions (No TTL/RDLENGTH/RDATA) based on RFC 1035 section 4.1.2. - * 1 1 1 1 1 1 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * | | - * / QNAME / - * / / - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * | QTYPE | - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * | QCLASS | - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * - * DNS resource records (With TTL/RDLENGTH/RDATA) based on RFC 1035 section 4.1.3. - * 1 1 1 1 1 1 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * | | - * / / - * / NAME / - * | | - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * | TYPE | - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * | CLASS | - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * | TTL | - * | | - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * | RDLENGTH | - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| - * / RDATA / - * / / - * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * - * Note that this class is meant to be used by composition and not inheritance, and - * that classes implementing more specific DNS records should call #parse. - */ - // TODO: Make DnsResourceRecord and DnsQuestion subclasses of DnsRecord. - public static class DnsRecord { - // Refer to RFC 1035 section 2.3.4 for MAXNAMESIZE. - // NAME_NORMAL and NAME_COMPRESSION are used for checking name compression, - // refer to rfc 1035 section 4.1.4. - public static final int MAXNAMESIZE = 255; - public static final int NAME_NORMAL = 0; - public static final int NAME_COMPRESSION = 0xC0; - - private static final String TAG = "DnsRecord"; - - public final String dName; - public final int nsType; - public final int nsClass; - public final long ttl; - private final byte[] mRdata; - /** - * Type of this DNS record. - */ - @RecordType - public final int rType; - - /** - * Create a new DnsRecord from a positioned ByteBuffer. - * - * Reads the passed ByteBuffer from its current position and decodes a DNS record. - * When this constructor returns, the reading position of the ByteBuffer has been - * advanced to the end of the DNS resource record. - * This is meant to chain with other methods reading a DNS response in sequence. - * - * @param rType Type of the record. - * @param buf ByteBuffer input of record, must be in network byte order - * (which is the default). - */ - private DnsRecord(@RecordType int rType, @NonNull ByteBuffer buf) - throws BufferUnderflowException, ParseException { - Objects.requireNonNull(buf); - this.rType = rType; - dName = DnsRecordParser.parseName(buf, 0 /* Parse depth */, - true /* isNameCompressionSupported */); - if (dName.length() > MAXNAMESIZE) { - throw new ParseException( - "Parse name fail, name size is too long: " + dName.length()); - } - nsType = Short.toUnsignedInt(buf.getShort()); - nsClass = Short.toUnsignedInt(buf.getShort()); - - if (rType != QDSECTION) { - ttl = Integer.toUnsignedLong(buf.getInt()); - final int length = Short.toUnsignedInt(buf.getShort()); - mRdata = new byte[length]; - buf.get(mRdata); - } else { - ttl = 0; - mRdata = null; - } - } - - /** - * Create a new DnsRecord or subclass of DnsRecord instance from a positioned ByteBuffer. - * - * Peek the nsType, sending the buffer to corresponding DnsRecord subclass constructors - * to allow constructing the corresponding object. - */ - @VisibleForTesting(visibility = PRIVATE) - public static DnsRecord parse(@RecordType int rType, @NonNull ByteBuffer buf) - throws BufferUnderflowException, ParseException { - Objects.requireNonNull(buf); - final int oldPos = buf.position(); - // Parsed name not used, just for jumping to nsType position. - DnsRecordParser.parseName(buf, 0 /* Parse depth */, - true /* isNameCompressionSupported */); - // Peek the nsType. - final int nsType = Short.toUnsignedInt(buf.getShort()); - buf.position(oldPos); - // Return a DnsRecord instance by default for backward compatibility, this is useful - // when a partner supports new type of DnsRecord but does not inherit DnsRecord. - switch (nsType) { - default: - return new DnsRecord(rType, buf); - } - } - - /** - * Make an A or AAAA record based on the specified parameters. - * - * @param rType Type of the record, can be {@link #ANSECTION}, {@link #ARSECTION} - * or {@link #NSSECTION}. - * @param dName Domain name of the record. - * @param nsClass Class of the record. See RFC 1035 section 3.2.4. - * @param ttl time interval (in seconds) that the resource record may be - * cached before it should be discarded. Zero values are - * interpreted to mean that the RR can only be used for the - * transaction in progress, and should not be cached. - * @param address Instance of {@link InetAddress} - * @return A record if the {@code address} is an IPv4 address, or AAAA record if the - * {@code address} is an IPv6 address. - */ - public static DnsRecord makeAOrAAAARecord(int rType, @NonNull String dName, - int nsClass, long ttl, @NonNull InetAddress address) throws IOException { - final int nsType = (address.getAddress().length == 4) ? TYPE_A : TYPE_AAAA; - return new DnsRecord(rType, dName, nsType, nsClass, ttl, address, null /* rDataStr */); - } - - /** - * Make an CNAME record based on the specified parameters. - * - * @param rType Type of the record, can be {@link #ANSECTION}, {@link #ARSECTION} - * or {@link #NSSECTION}. - * @param dName Domain name of the record. - * @param nsClass Class of the record. See RFC 1035 section 3.2.4. - * @param ttl time interval (in seconds) that the resource record may be - * cached before it should be discarded. Zero values are - * interpreted to mean that the RR can only be used for the - * transaction in progress, and should not be cached. - * @param domainName Canonical name of the {@code dName}. - * @return A record if the {@code address} is an IPv4 address, or AAAA record if the - * {@code address} is an IPv6 address. - */ - public static DnsRecord makeCNameRecord(int rType, @NonNull String dName, int nsClass, - long ttl, @NonNull String domainName) throws IOException { - return new DnsRecord(rType, dName, TYPE_CNAME, nsClass, ttl, null /* address */, - domainName); - } - - /** - * Make a DNS question based on the specified parameters. - */ - public static DnsRecord makeQuestion(@NonNull String dName, int nsType, int nsClass) { - return new DnsRecord(dName, nsType, nsClass); - } - - private static String requireHostName(@NonNull String name) { - if (!DnsRecordParser.isHostName(name)) { - throw new IllegalArgumentException("Expected domain name but got " + name); - } - return name; - } - - /** - * Create a new query DnsRecord from specified parameters, useful when synthesizing - * dns response. - */ - private DnsRecord(@NonNull String dName, int nsType, int nsClass) { - this.rType = QDSECTION; - this.dName = requireHostName(dName); - this.nsType = nsType; - this.nsClass = nsClass; - mRdata = null; - this.ttl = 0; - } - - /** - * Create a new CNAME/A/AAAA DnsRecord from specified parameters. - * - * @param address The address only used when synthesizing A or AAAA record. - * @param rDataStr The alias of the domain, only used when synthesizing CNAME record. - */ - private DnsRecord(@RecordType int rType, @NonNull String dName, int nsType, int nsClass, - long ttl, @Nullable InetAddress address, @Nullable String rDataStr) - throws IOException { - this.rType = rType; - this.dName = requireHostName(dName); - this.nsType = nsType; - this.nsClass = nsClass; - if (rType < 0 || rType >= NUM_SECTIONS || rType == QDSECTION) { - throw new IllegalArgumentException("Unexpected record type: " + rType); - } - mRdata = nsType == TYPE_CNAME ? domainNameToLabels(rDataStr) : address.getAddress(); - this.ttl = ttl; - } - - /** - * Get a copy of rdata. - */ - @Nullable - public byte[] getRR() { - return (mRdata == null) ? null : mRdata.clone(); - } - - /** - * Get DnsRecord as byte array. - */ - @NonNull - public byte[] getBytes() throws IOException { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final DataOutputStream dos = new DataOutputStream(baos); - dos.write(domainNameToLabels(dName)); - dos.writeShort(nsType); - dos.writeShort(nsClass); - if (rType != QDSECTION) { - dos.writeInt((int) ttl); - if (mRdata == null) { - dos.writeShort(0); - } else { - dos.writeShort(mRdata.length); - dos.write(mRdata); - } - } - return baos.toByteArray(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o.getClass() != getClass()) return false; - final DnsRecord other = (DnsRecord) o; - return rType == other.rType - && nsType == other.nsType - && nsClass == other.nsClass - && ttl == other.ttl - && TextUtils.equals(dName, other.dName) - && Arrays.equals(mRdata, other.mRdata); - } - - @Override - public int hashCode() { - return 31 * Objects.hash(dName) - + 37 * ((int) (ttl & 0xFFFFFFFF)) - + 41 * ((int) (ttl >> 32)) - + 43 * nsType - + 47 * nsClass - + 53 * rType - + Arrays.hashCode(mRdata); - } - - @Override - public String toString() { - return "DnsRecord{" - + "rType=" + rType - + ", dName='" + dName + '\'' - + ", nsType=" + nsType - + ", nsClass=" + nsClass - + ", ttl=" + ttl - + ", mRdata=" + Arrays.toString(mRdata) - + '}'; - } - } - - /** - * Header section types, refer to RFC 1035 section 4.1.1. - */ - public static final int QDSECTION = 0; - public static final int ANSECTION = 1; - public static final int NSSECTION = 2; - public static final int ARSECTION = 3; - @VisibleForTesting(visibility = PRIVATE) - static final int NUM_SECTIONS = ARSECTION + 1; - - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - QDSECTION, - ANSECTION, - NSSECTION, - ARSECTION, - }) - public @interface RecordType {} - - - private static final String TAG = DnsPacket.class.getSimpleName(); - - protected final DnsHeader mHeader; - protected final List<DnsRecord>[] mRecords; - - protected DnsPacket(@NonNull byte[] data) throws ParseException { - if (null == data) { - throw new ParseException("Parse header failed, null input data"); - } - - final ByteBuffer buffer; - try { - buffer = ByteBuffer.wrap(data); - mHeader = new DnsHeader(buffer); - } catch (BufferUnderflowException e) { - throw new ParseException("Parse Header fail, bad input data", e); - } - - mRecords = new ArrayList[NUM_SECTIONS]; - - for (int i = 0; i < NUM_SECTIONS; ++i) { - final int count = mHeader.getRecordCount(i); - mRecords[i] = new ArrayList(count); - for (int j = 0; j < count; ++j) { - try { - mRecords[i].add(DnsRecord.parse(i, buffer)); - } catch (BufferUnderflowException e) { - throw new ParseException("Parse record fail", e); - } - } - } - } - - /** - * Create a new {@link #DnsPacket} from specified parameters. - * - * Note that authority records section and additional records section is not supported. - */ - protected DnsPacket(@NonNull DnsHeader header, @NonNull List<DnsRecord> qd, - @NonNull List<DnsRecord> an) { - mHeader = Objects.requireNonNull(header); - mRecords = new List[NUM_SECTIONS]; - mRecords[QDSECTION] = Collections.unmodifiableList(new ArrayList<>(qd)); - mRecords[ANSECTION] = Collections.unmodifiableList(new ArrayList<>(an)); - mRecords[NSSECTION] = new ArrayList<>(); - mRecords[ARSECTION] = new ArrayList<>(); - for (int i = 0; i < NUM_SECTIONS; i++) { - if (mHeader.mRecordCount[i] != mRecords[i].size()) { - throw new IllegalArgumentException("Record count mismatch: expected " - + mHeader.mRecordCount[i] + " but was " + mRecords[i]); - } - } - } - - /** - * Get DnsPacket as byte array. - */ - public @NonNull byte[] getBytes() throws IOException { - final ByteArrayOutputStream buf = new ByteArrayOutputStream(); - buf.write(mHeader.getBytes()); - - for (int i = 0; i < NUM_SECTIONS; ++i) { - for (final DnsRecord record : mRecords[i]) { - buf.write(record.getBytes()); - } - } - return buf.toByteArray(); - } - - @Override - public String toString() { - return "DnsPacket{" + "header=" + mHeader + ", records='" + Arrays.toString(mRecords) + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o.getClass() != getClass()) return false; - final DnsPacket other = (DnsPacket) o; - return Objects.equals(mHeader, other.mHeader) - && Arrays.deepEquals(mRecords, other.mRecords); - } - - @Override - public int hashCode() { - int result = Objects.hash(mHeader); - result = 31 * result + Arrays.hashCode(mRecords); - return result; - } -} |