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.PRIVATE; 23 import static com.android.net.module.util.DnsPacketUtils.DnsRecordParser.domainNameToLabels; 24 25 import android.annotation.IntDef; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.text.TextUtils; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.net.module.util.DnsPacketUtils.DnsRecordParser; 32 33 import java.io.ByteArrayOutputStream; 34 import java.io.DataOutputStream; 35 import java.io.IOException; 36 import java.lang.annotation.Retention; 37 import java.lang.annotation.RetentionPolicy; 38 import java.net.InetAddress; 39 import java.nio.BufferUnderflowException; 40 import java.nio.ByteBuffer; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Collections; 44 import java.util.List; 45 import java.util.Objects; 46 47 /** 48 * Defines basic data for DNS protocol based on RFC 1035. 49 * Subclasses create the specific format used in DNS packet. 50 * 51 * @hide 52 */ 53 public abstract class DnsPacket { 54 /** 55 * Type of the canonical name for an alias. Refer to RFC 1035 section 3.2.2. 56 */ 57 // TODO: Define the constant as a public constant in DnsResolver since it can never change. 58 private static final int TYPE_CNAME = 5; 59 public static final int TYPE_SVCB = 64; 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 * Note that this class is meant to be used by composition and not inheritance, and 250 * that classes implementing more specific DNS records should call #parse. 251 */ 252 // TODO: Make DnsResourceRecord and DnsQuestion subclasses of DnsRecord. 253 public static class DnsRecord { 254 // Refer to RFC 1035 section 2.3.4 for MAXNAMESIZE. 255 // NAME_NORMAL and NAME_COMPRESSION are used for checking name compression, 256 // refer to rfc 1035 section 4.1.4. 257 public static final int MAXNAMESIZE = 255; 258 public static final int NAME_NORMAL = 0; 259 public static final int NAME_COMPRESSION = 0xC0; 260 261 private static final String TAG = "DnsRecord"; 262 263 public final String dName; 264 public final int nsType; 265 public final int nsClass; 266 public final long ttl; 267 private final byte[] mRdata; 268 /** 269 * Type of this DNS record. 270 */ 271 @RecordType 272 public final int rType; 273 274 /** 275 * Create a new DnsRecord from a positioned ByteBuffer. 276 * 277 * Reads the passed ByteBuffer from its current position and decodes a DNS record. 278 * When this constructor returns, the reading position of the ByteBuffer has been 279 * advanced to the end of the DNS resource record. 280 * This is meant to chain with other methods reading a DNS response in sequence. 281 * 282 * @param rType Type of the record. 283 * @param buf ByteBuffer input of record, must be in network byte order 284 * (which is the default). 285 */ DnsRecord(@ecordType int rType, @NonNull ByteBuffer buf)286 protected DnsRecord(@RecordType int rType, @NonNull ByteBuffer buf) 287 throws BufferUnderflowException, ParseException { 288 Objects.requireNonNull(buf); 289 this.rType = rType; 290 dName = DnsRecordParser.parseName(buf, 0 /* Parse depth */, 291 true /* isNameCompressionSupported */); 292 if (dName.length() > MAXNAMESIZE) { 293 throw new ParseException( 294 "Parse name fail, name size is too long: " + dName.length()); 295 } 296 nsType = Short.toUnsignedInt(buf.getShort()); 297 nsClass = Short.toUnsignedInt(buf.getShort()); 298 299 if (rType != QDSECTION) { 300 ttl = Integer.toUnsignedLong(buf.getInt()); 301 final int length = Short.toUnsignedInt(buf.getShort()); 302 mRdata = new byte[length]; 303 buf.get(mRdata); 304 } else { 305 ttl = 0; 306 mRdata = null; 307 } 308 } 309 310 /** 311 * Create a new DnsRecord or subclass of DnsRecord instance from a positioned ByteBuffer. 312 * 313 * Peek the nsType, sending the buffer to corresponding DnsRecord subclass constructors 314 * to allow constructing the corresponding object. 315 */ 316 @VisibleForTesting(visibility = PRIVATE) parse(@ecordType int rType, @NonNull ByteBuffer buf)317 public static DnsRecord parse(@RecordType int rType, @NonNull ByteBuffer buf) 318 throws BufferUnderflowException, ParseException { 319 Objects.requireNonNull(buf); 320 final int oldPos = buf.position(); 321 // Parsed name not used, just for jumping to nsType position. 322 DnsRecordParser.parseName(buf, 0 /* Parse depth */, 323 true /* isNameCompressionSupported */); 324 // Peek the nsType. 325 final int nsType = Short.toUnsignedInt(buf.getShort()); 326 buf.position(oldPos); 327 // Return a DnsRecord instance by default for backward compatibility, this is useful 328 // when a partner supports new type of DnsRecord but does not inherit DnsRecord. 329 switch (nsType) { 330 case TYPE_SVCB: 331 return new DnsSvcbRecord(rType, buf); 332 default: 333 return new DnsRecord(rType, buf); 334 } 335 } 336 337 /** 338 * Make an A or AAAA record based on the specified parameters. 339 * 340 * @param rType Type of the record, can be {@link #ANSECTION}, {@link #ARSECTION} 341 * or {@link #NSSECTION}. 342 * @param dName Domain name of the record. 343 * @param nsClass Class of the record. See RFC 1035 section 3.2.4. 344 * @param ttl time interval (in seconds) that the resource record may be 345 * cached before it should be discarded. Zero values are 346 * interpreted to mean that the RR can only be used for the 347 * transaction in progress, and should not be cached. 348 * @param address Instance of {@link InetAddress} 349 * @return A record if the {@code address} is an IPv4 address, or AAAA record if the 350 * {@code address} is an IPv6 address. 351 */ makeAOrAAAARecord(int rType, @NonNull String dName, int nsClass, long ttl, @NonNull InetAddress address)352 public static DnsRecord makeAOrAAAARecord(int rType, @NonNull String dName, 353 int nsClass, long ttl, @NonNull InetAddress address) throws IOException { 354 final int nsType = (address.getAddress().length == 4) ? TYPE_A : TYPE_AAAA; 355 return new DnsRecord(rType, dName, nsType, nsClass, ttl, address, null /* rDataStr */); 356 } 357 358 /** 359 * Make an CNAME record based on the specified parameters. 360 * 361 * @param rType Type of the record, can be {@link #ANSECTION}, {@link #ARSECTION} 362 * or {@link #NSSECTION}. 363 * @param dName Domain name of the record. 364 * @param nsClass Class of the record. See RFC 1035 section 3.2.4. 365 * @param ttl time interval (in seconds) that the resource record may be 366 * cached before it should be discarded. Zero values are 367 * interpreted to mean that the RR can only be used for the 368 * transaction in progress, and should not be cached. 369 * @param domainName Canonical name of the {@code dName}. 370 * @return A record if the {@code address} is an IPv4 address, or AAAA record if the 371 * {@code address} is an IPv6 address. 372 */ makeCNameRecord(int rType, @NonNull String dName, int nsClass, long ttl, @NonNull String domainName)373 public static DnsRecord makeCNameRecord(int rType, @NonNull String dName, int nsClass, 374 long ttl, @NonNull String domainName) throws IOException { 375 return new DnsRecord(rType, dName, TYPE_CNAME, nsClass, ttl, null /* address */, 376 domainName); 377 } 378 379 /** 380 * Make a DNS question based on the specified parameters. 381 */ makeQuestion(@onNull String dName, int nsType, int nsClass)382 public static DnsRecord makeQuestion(@NonNull String dName, int nsType, int nsClass) { 383 return new DnsRecord(dName, nsType, nsClass); 384 } 385 requireHostName(@onNull String name)386 private static String requireHostName(@NonNull String name) { 387 if (!DnsRecordParser.isHostName(name)) { 388 throw new IllegalArgumentException("Expected domain name but got " + name); 389 } 390 return name; 391 } 392 393 /** 394 * Create a new query DnsRecord from specified parameters, useful when synthesizing 395 * dns response. 396 */ DnsRecord(@onNull String dName, int nsType, int nsClass)397 private DnsRecord(@NonNull String dName, int nsType, int nsClass) { 398 this.rType = QDSECTION; 399 this.dName = requireHostName(dName); 400 this.nsType = nsType; 401 this.nsClass = nsClass; 402 mRdata = null; 403 this.ttl = 0; 404 } 405 406 /** 407 * Create a new CNAME/A/AAAA DnsRecord from specified parameters. 408 * 409 * @param address The address only used when synthesizing A or AAAA record. 410 * @param rDataStr The alias of the domain, only used when synthesizing CNAME record. 411 */ DnsRecord(@ecordType int rType, @NonNull String dName, int nsType, int nsClass, long ttl, @Nullable InetAddress address, @Nullable String rDataStr)412 private DnsRecord(@RecordType int rType, @NonNull String dName, int nsType, int nsClass, 413 long ttl, @Nullable InetAddress address, @Nullable String rDataStr) 414 throws IOException { 415 this.rType = rType; 416 this.dName = requireHostName(dName); 417 this.nsType = nsType; 418 this.nsClass = nsClass; 419 if (rType < 0 || rType >= NUM_SECTIONS || rType == QDSECTION) { 420 throw new IllegalArgumentException("Unexpected record type: " + rType); 421 } 422 mRdata = nsType == TYPE_CNAME ? domainNameToLabels(rDataStr) : address.getAddress(); 423 this.ttl = ttl; 424 } 425 426 /** 427 * Get a copy of rdata. 428 */ 429 @Nullable getRR()430 public byte[] getRR() { 431 return (mRdata == null) ? null : mRdata.clone(); 432 } 433 434 /** 435 * Get DnsRecord as byte array. 436 */ 437 @NonNull getBytes()438 public byte[] getBytes() throws IOException { 439 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 440 final DataOutputStream dos = new DataOutputStream(baos); 441 dos.write(domainNameToLabels(dName)); 442 dos.writeShort(nsType); 443 dos.writeShort(nsClass); 444 if (rType != QDSECTION) { 445 dos.writeInt((int) ttl); 446 if (mRdata == null) { 447 dos.writeShort(0); 448 } else { 449 dos.writeShort(mRdata.length); 450 dos.write(mRdata); 451 } 452 } 453 return baos.toByteArray(); 454 } 455 456 @Override equals(Object o)457 public boolean equals(Object o) { 458 if (this == o) return true; 459 if (o.getClass() != getClass()) return false; 460 final DnsRecord other = (DnsRecord) o; 461 return rType == other.rType 462 && nsType == other.nsType 463 && nsClass == other.nsClass 464 && ttl == other.ttl 465 && TextUtils.equals(dName, other.dName) 466 && Arrays.equals(mRdata, other.mRdata); 467 } 468 469 @Override hashCode()470 public int hashCode() { 471 return 31 * Objects.hash(dName) 472 + 37 * ((int) (ttl & 0xFFFFFFFF)) 473 + 41 * ((int) (ttl >> 32)) 474 + 43 * nsType 475 + 47 * nsClass 476 + 53 * rType 477 + Arrays.hashCode(mRdata); 478 } 479 480 @Override toString()481 public String toString() { 482 return "DnsRecord{" 483 + "rType=" + rType 484 + ", dName='" + dName + '\'' 485 + ", nsType=" + nsType 486 + ", nsClass=" + nsClass 487 + ", ttl=" + ttl 488 + ", mRdata=" + Arrays.toString(mRdata) 489 + '}'; 490 } 491 } 492 493 /** 494 * Header section types, refer to RFC 1035 section 4.1.1. 495 */ 496 public static final int QDSECTION = 0; 497 public static final int ANSECTION = 1; 498 public static final int NSSECTION = 2; 499 public static final int ARSECTION = 3; 500 @VisibleForTesting(visibility = PRIVATE) 501 static final int NUM_SECTIONS = ARSECTION + 1; 502 503 @Retention(RetentionPolicy.SOURCE) 504 @IntDef(value = { 505 QDSECTION, 506 ANSECTION, 507 NSSECTION, 508 ARSECTION, 509 }) 510 public @interface RecordType {} 511 512 513 private static final String TAG = DnsPacket.class.getSimpleName(); 514 515 protected final DnsHeader mHeader; 516 protected final List<DnsRecord>[] mRecords; 517 DnsPacket(@onNull byte[] data)518 protected DnsPacket(@NonNull byte[] data) throws ParseException { 519 if (null == data) { 520 throw new ParseException("Parse header failed, null input data"); 521 } 522 523 final ByteBuffer buffer; 524 try { 525 buffer = ByteBuffer.wrap(data); 526 mHeader = new DnsHeader(buffer); 527 } catch (BufferUnderflowException e) { 528 throw new ParseException("Parse Header fail, bad input data", e); 529 } 530 531 mRecords = new ArrayList[NUM_SECTIONS]; 532 533 for (int i = 0; i < NUM_SECTIONS; ++i) { 534 final int count = mHeader.getRecordCount(i); 535 mRecords[i] = new ArrayList(count); 536 for (int j = 0; j < count; ++j) { 537 try { 538 mRecords[i].add(DnsRecord.parse(i, buffer)); 539 } catch (BufferUnderflowException e) { 540 throw new ParseException("Parse record fail", e); 541 } 542 } 543 } 544 } 545 546 /** 547 * Create a new {@link #DnsPacket} from specified parameters. 548 * 549 * Note that authority records section and additional records section is not supported. 550 */ DnsPacket(@onNull DnsHeader header, @NonNull List<DnsRecord> qd, @NonNull List<DnsRecord> an)551 protected DnsPacket(@NonNull DnsHeader header, @NonNull List<DnsRecord> qd, 552 @NonNull List<DnsRecord> an) { 553 mHeader = Objects.requireNonNull(header); 554 mRecords = new List[NUM_SECTIONS]; 555 mRecords[QDSECTION] = Collections.unmodifiableList(new ArrayList<>(qd)); 556 mRecords[ANSECTION] = Collections.unmodifiableList(new ArrayList<>(an)); 557 mRecords[NSSECTION] = new ArrayList<>(); 558 mRecords[ARSECTION] = new ArrayList<>(); 559 for (int i = 0; i < NUM_SECTIONS; i++) { 560 if (mHeader.mRecordCount[i] != mRecords[i].size()) { 561 throw new IllegalArgumentException("Record count mismatch: expected " 562 + mHeader.mRecordCount[i] + " but was " + mRecords[i]); 563 } 564 } 565 } 566 567 /** 568 * Get DnsPacket as byte array. 569 */ getBytes()570 public @NonNull byte[] getBytes() throws IOException { 571 final ByteArrayOutputStream buf = new ByteArrayOutputStream(); 572 buf.write(mHeader.getBytes()); 573 574 for (int i = 0; i < NUM_SECTIONS; ++i) { 575 for (final DnsRecord record : mRecords[i]) { 576 buf.write(record.getBytes()); 577 } 578 } 579 return buf.toByteArray(); 580 } 581 582 @Override toString()583 public String toString() { 584 return "DnsPacket{" + "header=" + mHeader + ", records='" + Arrays.toString(mRecords) + '}'; 585 } 586 587 @Override equals(Object o)588 public boolean equals(Object o) { 589 if (this == o) return true; 590 if (o.getClass() != getClass()) return false; 591 final DnsPacket other = (DnsPacket) o; 592 return Objects.equals(mHeader, other.mHeader) 593 && Arrays.deepEquals(mRecords, other.mRecords); 594 } 595 596 @Override hashCode()597 public int hashCode() { 598 int result = Objects.hash(mHeader); 599 result = 31 * result + Arrays.hashCode(mRecords); 600 return result; 601 } 602 } 603