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 static android.net.DnsResolver.TYPE_A; 20 import static android.net.DnsResolver.TYPE_AAAA; 21 22 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 23 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; 24 import static com.android.net.module.util.DnsPacketUtils.DnsRecordParser.domainNameToLabels; 25 26 import android.annotation.IntDef; 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.text.TextUtils; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.net.module.util.DnsPacketUtils.DnsRecordParser; 33 34 import java.io.ByteArrayOutputStream; 35 import java.io.DataOutputStream; 36 import java.io.IOException; 37 import java.lang.annotation.Retention; 38 import java.lang.annotation.RetentionPolicy; 39 import java.net.InetAddress; 40 import java.nio.BufferUnderflowException; 41 import java.nio.ByteBuffer; 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.Collections; 45 import java.util.List; 46 import java.util.Objects; 47 48 /** 49 * Defines basic data for DNS protocol based on RFC 1035. 50 * Subclasses create the specific format used in DNS packet. 51 * 52 * @hide 53 */ 54 public abstract class DnsPacket { 55 /** 56 * Type of the canonical name for an alias. Refer to RFC 1035 section 3.2.2. 57 */ 58 // TODO: Define the constant as a public constant in DnsResolver since it can never change. 59 private static final int TYPE_CNAME = 5; 60 61 /** 62 * Thrown when parsing packet failed. 63 */ 64 public static class ParseException extends RuntimeException { 65 public String reason; ParseException(@onNull String reason)66 public ParseException(@NonNull String reason) { 67 super(reason); 68 this.reason = reason; 69 } 70 ParseException(@onNull String reason, @NonNull Throwable cause)71 public ParseException(@NonNull String reason, @NonNull Throwable cause) { 72 super(reason, cause); 73 this.reason = reason; 74 } 75 } 76 77 /** 78 * DNS header for DNS protocol based on RFC 1035 section 4.1.1. 79 * 80 * 1 1 1 1 1 1 81 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 82 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 83 * | ID | 84 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 85 * |QR| Opcode |AA|TC|RD|RA| Z | RCODE | 86 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 87 * | QDCOUNT | 88 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 89 * | ANCOUNT | 90 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 91 * | NSCOUNT | 92 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 93 * | ARCOUNT | 94 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 95 */ 96 public static class DnsHeader { 97 private static final String TAG = "DnsHeader"; 98 private static final int SIZE_IN_BYTES = 12; 99 private final int mId; 100 private final int mFlags; 101 private final int[] mRecordCount; 102 103 /* If this bit in the 'flags' field is set to 0, the DNS message corresponding to this 104 * header is a query; otherwise, it is a response. 105 */ 106 private static final int FLAGS_SECTION_QR_BIT = 15; 107 108 /** 109 * Create a new DnsHeader from a positioned ByteBuffer. 110 * 111 * The ByteBuffer must be in network byte order (which is the default). 112 * Reads the passed ByteBuffer from its current position and decodes a DNS header. 113 * When this constructor returns, the reading position of the ByteBuffer has been 114 * advanced to the end of the DNS header record. 115 * This is meant to chain with other methods reading a DNS response in sequence. 116 */ 117 @VisibleForTesting DnsHeader(@onNull ByteBuffer buf)118 public DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException { 119 Objects.requireNonNull(buf); 120 mId = Short.toUnsignedInt(buf.getShort()); 121 mFlags = Short.toUnsignedInt(buf.getShort()); 122 mRecordCount = new int[NUM_SECTIONS]; 123 for (int i = 0; i < NUM_SECTIONS; ++i) { 124 mRecordCount[i] = Short.toUnsignedInt(buf.getShort()); 125 } 126 } 127 128 /** 129 * Determines if the DNS message corresponding to this header is a response, as defined in 130 * RFC 1035 Section 4.1.1. 131 */ isResponse()132 public boolean isResponse() { 133 return (mFlags & (1 << FLAGS_SECTION_QR_BIT)) != 0; 134 } 135 136 /** 137 * Create a new DnsHeader from specified parameters. 138 * 139 * This constructor only builds the question and answer sections. Authority 140 * and additional sections are not supported. Useful when synthesizing dns 141 * responses from query or reply packets. 142 */ 143 @VisibleForTesting DnsHeader(int id, int flags, int qdcount, int ancount)144 public DnsHeader(int id, int flags, int qdcount, int ancount) { 145 this.mId = id; 146 this.mFlags = flags; 147 mRecordCount = new int[NUM_SECTIONS]; 148 mRecordCount[QDSECTION] = qdcount; 149 mRecordCount[ANSECTION] = ancount; 150 } 151 152 /** 153 * Get record count by type. 154 */ getRecordCount(int type)155 public int getRecordCount(int type) { 156 return mRecordCount[type]; 157 } 158 159 /** 160 * Get flags of this instance. 161 */ getFlags()162 public int getFlags() { 163 return mFlags; 164 } 165 166 /** 167 * Get id of this instance. 168 */ getId()169 public int getId() { 170 return mId; 171 } 172 173 @Override toString()174 public String toString() { 175 return "DnsHeader{" + "id=" + mId + ", flags=" + mFlags 176 + ", recordCounts=" + Arrays.toString(mRecordCount) + '}'; 177 } 178 179 @Override equals(Object o)180 public boolean equals(Object o) { 181 if (this == o) return true; 182 if (o.getClass() != getClass()) return false; 183 final DnsHeader other = (DnsHeader) o; 184 return mId == other.mId 185 && mFlags == other.mFlags 186 && Arrays.equals(mRecordCount, other.mRecordCount); 187 } 188 189 @Override hashCode()190 public int hashCode() { 191 return 31 * mId + 37 * mFlags + Arrays.hashCode(mRecordCount); 192 } 193 194 /** 195 * Get DnsHeader as byte array. 196 */ 197 @NonNull getBytes()198 public byte[] getBytes() { 199 // TODO: if this is called often, optimize the ByteBuffer out and write to the 200 // array directly. 201 final ByteBuffer buf = ByteBuffer.allocate(SIZE_IN_BYTES); 202 buf.putShort((short) mId); 203 buf.putShort((short) mFlags); 204 for (int i = 0; i < NUM_SECTIONS; ++i) { 205 buf.putShort((short) mRecordCount[i]); 206 } 207 return buf.array(); 208 } 209 } 210 211 /** 212 * Superclass for DNS questions and DNS resource records. 213 * 214 * DNS questions (No TTL/RDLENGTH/RDATA) based on RFC 1035 section 4.1.2. 215 * 1 1 1 1 1 1 216 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 217 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 218 * | | 219 * / QNAME / 220 * / / 221 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 222 * | QTYPE | 223 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 224 * | QCLASS | 225 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 226 * 227 * DNS resource records (With TTL/RDLENGTH/RDATA) based on RFC 1035 section 4.1.3. 228 * 1 1 1 1 1 1 229 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 230 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 231 * | | 232 * / / 233 * / NAME / 234 * | | 235 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 236 * | TYPE | 237 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 238 * | CLASS | 239 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 240 * | TTL | 241 * | | 242 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 243 * | RDLENGTH | 244 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| 245 * / RDATA / 246 * / / 247 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 248 */ 249 // TODO: Make DnsResourceRecord and DnsQuestion subclasses of DnsRecord, and construct 250 // corresponding object from factory methods. 251 public static class DnsRecord { 252 // Refer to RFC 1035 section 2.3.4 for MAXNAMESIZE. 253 // NAME_NORMAL and NAME_COMPRESSION are used for checking name compression, 254 // refer to rfc 1035 section 4.1.4. 255 private static final int MAXNAMESIZE = 255; 256 public static final int NAME_NORMAL = 0; 257 public static final int NAME_COMPRESSION = 0xC0; 258 259 private static final String TAG = "DnsRecord"; 260 261 public final String dName; 262 public final int nsType; 263 public final int nsClass; 264 public final long ttl; 265 private final byte[] mRdata; 266 /** 267 * Type of this DNS record. 268 */ 269 @RecordType 270 public final int rType; 271 272 /** 273 * Create a new DnsRecord from a positioned ByteBuffer. 274 * 275 * Reads the passed ByteBuffer from its current position and decodes a DNS record. 276 * When this constructor returns, the reading position of the ByteBuffer has been 277 * advanced to the end of the DNS resource record. 278 * This is meant to chain with other methods reading a DNS response in sequence. 279 * 280 * @param rType Type of the record. 281 * @param buf ByteBuffer input of record, must be in network byte order 282 * (which is the default). 283 */ 284 @VisibleForTesting(visibility = PACKAGE) DnsRecord(@ecordType int rType, @NonNull ByteBuffer buf)285 public DnsRecord(@RecordType int rType, @NonNull ByteBuffer buf) 286 throws BufferUnderflowException, ParseException { 287 Objects.requireNonNull(buf); 288 this.rType = rType; 289 dName = DnsRecordParser.parseName(buf, 0 /* Parse depth */, 290 true /* isNameCompressionSupported */); 291 if (dName.length() > MAXNAMESIZE) { 292 throw new ParseException( 293 "Parse name fail, name size is too long: " + dName.length()); 294 } 295 nsType = Short.toUnsignedInt(buf.getShort()); 296 nsClass = Short.toUnsignedInt(buf.getShort()); 297 298 if (rType != QDSECTION) { 299 ttl = Integer.toUnsignedLong(buf.getInt()); 300 final int length = Short.toUnsignedInt(buf.getShort()); 301 mRdata = new byte[length]; 302 buf.get(mRdata); 303 } else { 304 ttl = 0; 305 mRdata = null; 306 } 307 } 308 309 /** 310 * Make an A or AAAA record based on the specified parameters. 311 * 312 * @param rType Type of the record, can be {@link #ANSECTION}, {@link #ARSECTION} 313 * or {@link #NSSECTION}. 314 * @param dName Domain name of the record. 315 * @param nsClass Class of the record. See RFC 1035 section 3.2.4. 316 * @param ttl time interval (in seconds) that the resource record may be 317 * cached before it should be discarded. Zero values are 318 * interpreted to mean that the RR can only be used for the 319 * transaction in progress, and should not be cached. 320 * @param address Instance of {@link InetAddress} 321 * @return A record if the {@code address} is an IPv4 address, or AAAA record if the 322 * {@code address} is an IPv6 address. 323 */ makeAOrAAAARecord(int rType, @NonNull String dName, int nsClass, long ttl, @NonNull InetAddress address)324 public static DnsRecord makeAOrAAAARecord(int rType, @NonNull String dName, 325 int nsClass, long ttl, @NonNull InetAddress address) throws IOException { 326 final int nsType = (address.getAddress().length == 4) ? TYPE_A : TYPE_AAAA; 327 return new DnsRecord(rType, dName, nsType, nsClass, ttl, address, null /* rDataStr */); 328 } 329 330 /** 331 * Make an CNAME record based on the specified parameters. 332 * 333 * @param rType Type of the record, can be {@link #ANSECTION}, {@link #ARSECTION} 334 * or {@link #NSSECTION}. 335 * @param dName Domain name of the record. 336 * @param nsClass Class of the record. See RFC 1035 section 3.2.4. 337 * @param ttl time interval (in seconds) that the resource record may be 338 * cached before it should be discarded. Zero values are 339 * interpreted to mean that the RR can only be used for the 340 * transaction in progress, and should not be cached. 341 * @param domainName Canonical name of the {@code dName}. 342 * @return A record if the {@code address} is an IPv4 address, or AAAA record if the 343 * {@code address} is an IPv6 address. 344 */ makeCNameRecord(int rType, @NonNull String dName, int nsClass, long ttl, @NonNull String domainName)345 public static DnsRecord makeCNameRecord(int rType, @NonNull String dName, int nsClass, 346 long ttl, @NonNull String domainName) throws IOException { 347 return new DnsRecord(rType, dName, TYPE_CNAME, nsClass, ttl, null /* address */, 348 domainName); 349 } 350 351 /** 352 * Make a DNS question based on the specified parameters. 353 */ makeQuestion(@onNull String dName, int nsType, int nsClass)354 public static DnsRecord makeQuestion(@NonNull String dName, int nsType, int nsClass) { 355 return new DnsRecord(dName, nsType, nsClass); 356 } 357 requireHostName(@onNull String name)358 private static String requireHostName(@NonNull String name) { 359 if (!DnsRecordParser.isHostName(name)) { 360 throw new IllegalArgumentException("Expected domain name but got " + name); 361 } 362 return name; 363 } 364 365 /** 366 * Create a new query DnsRecord from specified parameters, useful when synthesizing 367 * dns response. 368 */ DnsRecord(@onNull String dName, int nsType, int nsClass)369 private DnsRecord(@NonNull String dName, int nsType, int nsClass) { 370 this.rType = QDSECTION; 371 this.dName = requireHostName(dName); 372 this.nsType = nsType; 373 this.nsClass = nsClass; 374 mRdata = null; 375 this.ttl = 0; 376 } 377 378 /** 379 * Create a new CNAME/A/AAAA DnsRecord from specified parameters. 380 * 381 * @param address The address only used when synthesizing A or AAAA record. 382 * @param rDataStr The alias of the domain, only used when synthesizing CNAME record. 383 */ DnsRecord(@ecordType int rType, @NonNull String dName, int nsType, int nsClass, long ttl, @Nullable InetAddress address, @Nullable String rDataStr)384 private DnsRecord(@RecordType int rType, @NonNull String dName, int nsType, int nsClass, 385 long ttl, @Nullable InetAddress address, @Nullable String rDataStr) 386 throws IOException { 387 this.rType = rType; 388 this.dName = requireHostName(dName); 389 this.nsType = nsType; 390 this.nsClass = nsClass; 391 if (rType < 0 || rType >= NUM_SECTIONS || rType == QDSECTION) { 392 throw new IllegalArgumentException("Unexpected record type: " + rType); 393 } 394 mRdata = nsType == TYPE_CNAME ? domainNameToLabels(rDataStr) : address.getAddress(); 395 this.ttl = ttl; 396 } 397 398 /** 399 * Get a copy of rdata. 400 */ 401 @Nullable getRR()402 public byte[] getRR() { 403 return (mRdata == null) ? null : mRdata.clone(); 404 } 405 406 /** 407 * Get DnsRecord as byte array. 408 */ 409 @NonNull getBytes()410 public byte[] getBytes() throws IOException { 411 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 412 final DataOutputStream dos = new DataOutputStream(baos); 413 dos.write(domainNameToLabels(dName)); 414 dos.writeShort(nsType); 415 dos.writeShort(nsClass); 416 if (rType != QDSECTION) { 417 dos.writeInt((int) ttl); 418 if (mRdata == null) { 419 dos.writeShort(0); 420 } else { 421 dos.writeShort(mRdata.length); 422 dos.write(mRdata); 423 } 424 } 425 return baos.toByteArray(); 426 } 427 428 @Override equals(Object o)429 public boolean equals(Object o) { 430 if (this == o) return true; 431 if (o.getClass() != getClass()) return false; 432 final DnsRecord other = (DnsRecord) o; 433 return rType == other.rType 434 && nsType == other.nsType 435 && nsClass == other.nsClass 436 && ttl == other.ttl 437 && TextUtils.equals(dName, other.dName) 438 && Arrays.equals(mRdata, other.mRdata); 439 } 440 441 @Override hashCode()442 public int hashCode() { 443 return 31 * Objects.hash(dName) 444 + 37 * ((int) (ttl & 0xFFFFFFFF)) 445 + 41 * ((int) (ttl >> 32)) 446 + 43 * nsType 447 + 47 * nsClass 448 + 53 * rType 449 + Arrays.hashCode(mRdata); 450 } 451 452 @Override toString()453 public String toString() { 454 return "DnsRecord{" 455 + "rType=" + rType 456 + ", dName='" + dName + '\'' 457 + ", nsType=" + nsType 458 + ", nsClass=" + nsClass 459 + ", ttl=" + ttl 460 + ", mRdata=" + Arrays.toString(mRdata) 461 + '}'; 462 } 463 } 464 465 /** 466 * Header section types, refer to RFC 1035 section 4.1.1. 467 */ 468 public static final int QDSECTION = 0; 469 public static final int ANSECTION = 1; 470 public static final int NSSECTION = 2; 471 public static final int ARSECTION = 3; 472 @VisibleForTesting(visibility = PRIVATE) 473 static final int NUM_SECTIONS = ARSECTION + 1; 474 475 @Retention(RetentionPolicy.SOURCE) 476 @IntDef(value = { 477 QDSECTION, 478 ANSECTION, 479 NSSECTION, 480 ARSECTION, 481 }) 482 public @interface RecordType {} 483 484 485 private static final String TAG = DnsPacket.class.getSimpleName(); 486 487 protected final DnsHeader mHeader; 488 protected final List<DnsRecord>[] mRecords; 489 DnsPacket(@onNull byte[] data)490 protected DnsPacket(@NonNull byte[] data) throws ParseException { 491 if (null == data) { 492 throw new ParseException("Parse header failed, null input data"); 493 } 494 495 final ByteBuffer buffer; 496 try { 497 buffer = ByteBuffer.wrap(data); 498 mHeader = new DnsHeader(buffer); 499 } catch (BufferUnderflowException e) { 500 throw new ParseException("Parse Header fail, bad input data", e); 501 } 502 503 mRecords = new ArrayList[NUM_SECTIONS]; 504 505 for (int i = 0; i < NUM_SECTIONS; ++i) { 506 final int count = mHeader.getRecordCount(i); 507 mRecords[i] = new ArrayList(count); 508 for (int j = 0; j < count; ++j) { 509 try { 510 mRecords[i].add(new DnsRecord(i, buffer)); 511 } catch (BufferUnderflowException e) { 512 throw new ParseException("Parse record fail", e); 513 } 514 } 515 } 516 } 517 518 /** 519 * Create a new {@link #DnsPacket} from specified parameters. 520 * 521 * Note that authority records section and additional records section is not supported. 522 */ DnsPacket(@onNull DnsHeader header, @NonNull List<DnsRecord> qd, @NonNull List<DnsRecord> an)523 protected DnsPacket(@NonNull DnsHeader header, @NonNull List<DnsRecord> qd, 524 @NonNull List<DnsRecord> an) { 525 mHeader = Objects.requireNonNull(header); 526 mRecords = new List[NUM_SECTIONS]; 527 mRecords[QDSECTION] = Collections.unmodifiableList(new ArrayList<>(qd)); 528 mRecords[ANSECTION] = Collections.unmodifiableList(new ArrayList<>(an)); 529 mRecords[NSSECTION] = new ArrayList<>(); 530 mRecords[ARSECTION] = new ArrayList<>(); 531 for (int i = 0; i < NUM_SECTIONS; i++) { 532 if (mHeader.mRecordCount[i] != mRecords[i].size()) { 533 throw new IllegalArgumentException("Record count mismatch: expected " 534 + mHeader.mRecordCount[i] + " but was " + mRecords[i]); 535 } 536 } 537 } 538 539 /** 540 * Get DnsPacket as byte array. 541 */ getBytes()542 public @NonNull byte[] getBytes() throws IOException { 543 final ByteArrayOutputStream buf = new ByteArrayOutputStream(); 544 buf.write(mHeader.getBytes()); 545 546 for (int i = 0; i < NUM_SECTIONS; ++i) { 547 for (final DnsRecord record : mRecords[i]) { 548 buf.write(record.getBytes()); 549 } 550 } 551 return buf.toByteArray(); 552 } 553 554 @Override toString()555 public String toString() { 556 return "DnsPacket{" + "header=" + mHeader + ", records='" + Arrays.toString(mRecords) + '}'; 557 } 558 559 @Override equals(Object o)560 public boolean equals(Object o) { 561 if (this == o) return true; 562 if (o.getClass() != getClass()) return false; 563 final DnsPacket other = (DnsPacket) o; 564 return Objects.equals(mHeader, other.mHeader) 565 && Arrays.deepEquals(mRecords, other.mRecords); 566 } 567 568 @Override hashCode()569 public int hashCode() { 570 int result = Objects.hash(mHeader); 571 result = 31 * result + Arrays.hashCode(mRecords); 572 return result; 573 } 574 } 575