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