• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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