1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.net.module.util; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 22 import com.android.net.module.util.DnsPacketUtils.DnsRecordParser; 23 24 import java.nio.BufferUnderflowException; 25 import java.nio.ByteBuffer; 26 import java.util.ArrayList; 27 import java.util.List; 28 29 /** 30 * Defines basic data for DNS protocol based on RFC 1035. 31 * Subclasses create the specific format used in DNS packet. 32 * 33 * @hide 34 */ 35 public abstract class DnsPacket { 36 /** 37 * Thrown when parsing packet failed. 38 */ 39 public static class ParseException extends RuntimeException { 40 public String reason; ParseException(@onNull String reason)41 public ParseException(@NonNull String reason) { 42 super(reason); 43 this.reason = reason; 44 } 45 ParseException(@onNull String reason, @NonNull Throwable cause)46 public ParseException(@NonNull String reason, @NonNull Throwable cause) { 47 super(reason, cause); 48 this.reason = reason; 49 } 50 } 51 52 /** 53 * DNS header for DNS protocol based on RFC 1035. 54 */ 55 public class DnsHeader { 56 private static final String TAG = "DnsHeader"; 57 public final int id; 58 public final int flags; 59 public final int rcode; 60 private final int[] mRecordCount; 61 62 /* If this bit in the 'flags' field is set to 0, the DNS message corresponding to this 63 * header is a query; otherwise, it is a response. 64 */ 65 private static final int FLAGS_SECTION_QR_BIT = 15; 66 67 /** 68 * Create a new DnsHeader from a positioned ByteBuffer. 69 * 70 * The ByteBuffer must be in network byte order (which is the default). 71 * Reads the passed ByteBuffer from its current position and decodes a DNS header. 72 * When this constructor returns, the reading position of the ByteBuffer has been 73 * advanced to the end of the DNS header record. 74 * This is meant to chain with other methods reading a DNS response in sequence. 75 */ DnsHeader(@onNull ByteBuffer buf)76 DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException { 77 id = Short.toUnsignedInt(buf.getShort()); 78 flags = Short.toUnsignedInt(buf.getShort()); 79 rcode = flags & 0xF; 80 mRecordCount = new int[NUM_SECTIONS]; 81 for (int i = 0; i < NUM_SECTIONS; ++i) { 82 mRecordCount[i] = Short.toUnsignedInt(buf.getShort()); 83 } 84 } 85 86 /** 87 * Determines if the DNS message corresponding to this header is a response, as defined in 88 * RFC 1035 Section 4.1.1. 89 */ isResponse()90 public boolean isResponse() { 91 return (flags & (1 << FLAGS_SECTION_QR_BIT)) != 0; 92 } 93 94 /** 95 * Get record count by type. 96 */ getRecordCount(int type)97 public int getRecordCount(int type) { 98 return mRecordCount[type]; 99 } 100 } 101 102 /** 103 * Superclass for DNS questions and DNS resource records. 104 * 105 * DNS questions (No TTL/RDATA) 106 * DNS resource records (With TTL/RDATA) 107 */ 108 public class DnsRecord { 109 private static final int MAXNAMESIZE = 255; 110 public static final int NAME_NORMAL = 0; 111 public static final int NAME_COMPRESSION = 0xC0; 112 113 private static final String TAG = "DnsRecord"; 114 115 public final String dName; 116 public final int nsType; 117 public final int nsClass; 118 public final long ttl; 119 private final byte[] mRdata; 120 121 /** 122 * Create a new DnsRecord from a positioned ByteBuffer. 123 * 124 * Reads the passed ByteBuffer from its current position and decodes a DNS record. 125 * When this constructor returns, the reading position of the ByteBuffer has been 126 * advanced to the end of the DNS header record. 127 * This is meant to chain with other methods reading a DNS response in sequence. 128 * 129 * @param buf ByteBuffer input of record, must be in network byte order 130 * (which is the default). 131 */ DnsRecord(int recordType, @NonNull ByteBuffer buf)132 DnsRecord(int recordType, @NonNull ByteBuffer buf) 133 throws BufferUnderflowException, ParseException { 134 dName = DnsRecordParser.parseName(buf, 0 /* Parse depth */, 135 /* isNameCompressionSupported= */ true); 136 if (dName.length() > MAXNAMESIZE) { 137 throw new ParseException( 138 "Parse name fail, name size is too long: " + dName.length()); 139 } 140 nsType = Short.toUnsignedInt(buf.getShort()); 141 nsClass = Short.toUnsignedInt(buf.getShort()); 142 143 if (recordType != QDSECTION) { 144 ttl = Integer.toUnsignedLong(buf.getInt()); 145 final int length = Short.toUnsignedInt(buf.getShort()); 146 mRdata = new byte[length]; 147 buf.get(mRdata); 148 } else { 149 ttl = 0; 150 mRdata = null; 151 } 152 } 153 154 /** 155 * Get a copy of rdata. 156 */ 157 @Nullable getRR()158 public byte[] getRR() { 159 return (mRdata == null) ? null : mRdata.clone(); 160 } 161 162 } 163 164 public static final int QDSECTION = 0; 165 public static final int ANSECTION = 1; 166 public static final int NSSECTION = 2; 167 public static final int ARSECTION = 3; 168 private static final int NUM_SECTIONS = ARSECTION + 1; 169 170 private static final String TAG = DnsPacket.class.getSimpleName(); 171 172 protected final DnsHeader mHeader; 173 protected final List<DnsRecord>[] mRecords; 174 DnsPacket(@onNull byte[] data)175 protected DnsPacket(@NonNull byte[] data) throws ParseException { 176 if (null == data) { 177 throw new ParseException("Parse header failed, null input data"); 178 } 179 180 final ByteBuffer buffer; 181 try { 182 buffer = ByteBuffer.wrap(data); 183 mHeader = new DnsHeader(buffer); 184 } catch (BufferUnderflowException e) { 185 throw new ParseException("Parse Header fail, bad input data", e); 186 } 187 188 mRecords = new ArrayList[NUM_SECTIONS]; 189 190 for (int i = 0; i < NUM_SECTIONS; ++i) { 191 final int count = mHeader.getRecordCount(i); 192 if (count > 0) { 193 mRecords[i] = new ArrayList(count); 194 } 195 for (int j = 0; j < count; ++j) { 196 try { 197 mRecords[i].add(new DnsRecord(i, buffer)); 198 } catch (BufferUnderflowException e) { 199 throw new ParseException("Parse record fail", e); 200 } 201 } 202 } 203 } 204 } 205