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 android.net; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.text.TextUtils; 22 23 import com.android.internal.util.BitUtils; 24 25 import java.nio.BufferUnderflowException; 26 import java.nio.ByteBuffer; 27 import java.text.DecimalFormat; 28 import java.text.FieldPosition; 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * Defines basic data for DNS protocol based on RFC 1035. 34 * Subclasses create the specific format used in DNS packet. 35 * 36 * @hide 37 */ 38 public abstract class DnsPacket { 39 public class DnsHeader { 40 private static final String TAG = "DnsHeader"; 41 public final int id; 42 public final int flags; 43 public final int rcode; 44 private final int[] mRecordCount; 45 46 /** 47 * Create a new DnsHeader from a positioned ByteBuffer. 48 * 49 * The ByteBuffer must be in network byte order (which is the default). 50 * Reads the passed ByteBuffer from its current position and decodes a DNS header. 51 * When this constructor returns, the reading position of the ByteBuffer has been 52 * advanced to the end of the DNS header record. 53 * This is meant to chain with other methods reading a DNS response in sequence. 54 */ DnsHeader(@onNull ByteBuffer buf)55 DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException { 56 id = BitUtils.uint16(buf.getShort()); 57 flags = BitUtils.uint16(buf.getShort()); 58 rcode = flags & 0xF; 59 mRecordCount = new int[NUM_SECTIONS]; 60 for (int i = 0; i < NUM_SECTIONS; ++i) { 61 mRecordCount[i] = BitUtils.uint16(buf.getShort()); 62 } 63 } 64 65 /** 66 * Get record count by type. 67 */ getRecordCount(int type)68 public int getRecordCount(int type) { 69 return mRecordCount[type]; 70 } 71 } 72 73 /** 74 * Superclass for DNS questions and DNS resource records. 75 * 76 * DNS questions (No TTL/RDATA) 77 * DNS resource records (With TTL/RDATA) 78 */ 79 public class DnsRecord { 80 private static final int MAXNAMESIZE = 255; 81 private static final int MAXLABELSIZE = 63; 82 private static final int MAXLABELCOUNT = 128; 83 private static final int NAME_NORMAL = 0; 84 private static final int NAME_COMPRESSION = 0xC0; 85 private final DecimalFormat byteFormat = new DecimalFormat(); 86 private final FieldPosition pos = new FieldPosition(0); 87 88 private static final String TAG = "DnsRecord"; 89 90 public final String dName; 91 public final int nsType; 92 public final int nsClass; 93 public final long ttl; 94 private final byte[] mRdata; 95 96 /** 97 * Create a new DnsRecord from a positioned ByteBuffer. 98 * 99 * Reads the passed ByteBuffer from its current position and decodes a DNS record. 100 * When this constructor returns, the reading position of the ByteBuffer has been 101 * advanced to the end of the DNS header record. 102 * This is meant to chain with other methods reading a DNS response in sequence. 103 * 104 * @param ByteBuffer input of record, must be in network byte order 105 * (which is the default). 106 */ DnsRecord(int recordType, @NonNull ByteBuffer buf)107 DnsRecord(int recordType, @NonNull ByteBuffer buf) 108 throws BufferUnderflowException, ParseException { 109 dName = parseName(buf, 0 /* Parse depth */); 110 if (dName.length() > MAXNAMESIZE) { 111 throw new ParseException( 112 "Parse name fail, name size is too long: " + dName.length()); 113 } 114 nsType = BitUtils.uint16(buf.getShort()); 115 nsClass = BitUtils.uint16(buf.getShort()); 116 117 if (recordType != QDSECTION) { 118 ttl = BitUtils.uint32(buf.getInt()); 119 final int length = BitUtils.uint16(buf.getShort()); 120 mRdata = new byte[length]; 121 buf.get(mRdata); 122 } else { 123 ttl = 0; 124 mRdata = null; 125 } 126 } 127 128 /** 129 * Get a copy of rdata. 130 */ 131 @Nullable getRR()132 public byte[] getRR() { 133 return (mRdata == null) ? null : mRdata.clone(); 134 } 135 136 /** 137 * Convert label from {@code byte[]} to {@code String} 138 * 139 * Follows the same conversion rules of the native code (ns_name.c in libc) 140 */ labelToString(@onNull byte[] label)141 private String labelToString(@NonNull byte[] label) { 142 final StringBuffer sb = new StringBuffer(); 143 for (int i = 0; i < label.length; ++i) { 144 int b = BitUtils.uint8(label[i]); 145 // Control characters and non-ASCII characters. 146 if (b <= 0x20 || b >= 0x7f) { 147 // Append the byte as an escaped decimal number, e.g., "\19" for 0x13. 148 sb.append('\\'); 149 byteFormat.format(b, sb, pos); 150 } else if (b == '"' || b == '.' || b == ';' || b == '\\' 151 || b == '(' || b == ')' || b == '@' || b == '$') { 152 // Append the byte as an escaped character, e.g., "\:" for 0x3a. 153 sb.append('\\'); 154 sb.append((char) b); 155 } else { 156 // Append the byte as a character, e.g., "a" for 0x61. 157 sb.append((char) b); 158 } 159 } 160 return sb.toString(); 161 } 162 parseName(@onNull ByteBuffer buf, int depth)163 private String parseName(@NonNull ByteBuffer buf, int depth) throws 164 BufferUnderflowException, ParseException { 165 if (depth > MAXLABELCOUNT) { 166 throw new ParseException("Failed to parse name, too many labels"); 167 } 168 final int len = BitUtils.uint8(buf.get()); 169 final int mask = len & NAME_COMPRESSION; 170 if (0 == len) { 171 return ""; 172 } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) { 173 throw new ParseException("Parse name fail, bad label type"); 174 } else if (mask == NAME_COMPRESSION) { 175 // Name compression based on RFC 1035 - 4.1.4 Message compression 176 final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get()); 177 final int oldPos = buf.position(); 178 if (offset >= oldPos - 2) { 179 throw new ParseException("Parse compression name fail, invalid compression"); 180 } 181 buf.position(offset); 182 final String pointed = parseName(buf, depth + 1); 183 buf.position(oldPos); 184 return pointed; 185 } else { 186 final byte[] label = new byte[len]; 187 buf.get(label); 188 final String head = labelToString(label); 189 if (head.length() > MAXLABELSIZE) { 190 throw new ParseException("Parse name fail, invalid label length"); 191 } 192 final String tail = parseName(buf, depth + 1); 193 return TextUtils.isEmpty(tail) ? head : head + "." + tail; 194 } 195 } 196 } 197 198 public static final int QDSECTION = 0; 199 public static final int ANSECTION = 1; 200 public static final int NSSECTION = 2; 201 public static final int ARSECTION = 3; 202 private static final int NUM_SECTIONS = ARSECTION + 1; 203 204 private static final String TAG = DnsPacket.class.getSimpleName(); 205 206 protected final DnsHeader mHeader; 207 protected final List<DnsRecord>[] mRecords; 208 DnsPacket(@onNull byte[] data)209 protected DnsPacket(@NonNull byte[] data) throws ParseException { 210 if (null == data) throw new ParseException("Parse header failed, null input data"); 211 final ByteBuffer buffer; 212 try { 213 buffer = ByteBuffer.wrap(data); 214 mHeader = new DnsHeader(buffer); 215 } catch (BufferUnderflowException e) { 216 throw new ParseException("Parse Header fail, bad input data", e); 217 } 218 219 mRecords = new ArrayList[NUM_SECTIONS]; 220 221 for (int i = 0; i < NUM_SECTIONS; ++i) { 222 final int count = mHeader.getRecordCount(i); 223 if (count > 0) { 224 mRecords[i] = new ArrayList(count); 225 } 226 for (int j = 0; j < count; ++j) { 227 try { 228 mRecords[i].add(new DnsRecord(i, buffer)); 229 } catch (BufferUnderflowException e) { 230 throw new ParseException("Parse record fail", e); 231 } 232 } 233 } 234 } 235 } 236