blob: 0ac02b1b7b37f686657910d94eb2d3b26e715dff [file] [log] [blame]
Luke Huang00b15f32019-01-04 19:56:29 +08001/*
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
17package android.net;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.text.TextUtils;
22
23import com.android.internal.util.BitUtils;
24
25import java.nio.BufferUnderflowException;
26import java.nio.ByteBuffer;
27import java.text.DecimalFormat;
28import java.text.FieldPosition;
29import java.util.ArrayList;
30import java.util.List;
Luke Huang00b15f32019-01-04 19:56:29 +080031
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 */
38public 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;
Luke Huang33bfef32019-01-23 21:53:13 +080044 private final int[] mRecordCount;
Luke Huang00b15f32019-01-04 19:56:29 +080045
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.
Luke Huang00b15f32019-01-04 19:56:29 +080054 */
55 DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException {
56 id = BitUtils.uint16(buf.getShort());
57 flags = BitUtils.uint16(buf.getShort());
58 rcode = flags & 0xF;
Luke Huang33bfef32019-01-23 21:53:13 +080059 mRecordCount = new int[NUM_SECTIONS];
Luke Huang00b15f32019-01-04 19:56:29 +080060 for (int i = 0; i < NUM_SECTIONS; ++i) {
Luke Huang33bfef32019-01-23 21:53:13 +080061 mRecordCount[i] = BitUtils.uint16(buf.getShort());
Luke Huang00b15f32019-01-04 19:56:29 +080062 }
63 }
64
65 /**
Luke Huang33bfef32019-01-23 21:53:13 +080066 * Get record count by type.
Luke Huang00b15f32019-01-04 19:56:29 +080067 */
Luke Huang33bfef32019-01-23 21:53:13 +080068 public int getRecordCount(int type) {
69 return mRecordCount[type];
Luke Huang00b15f32019-01-04 19:56:29 +080070 }
71 }
72
Luke Huang33bfef32019-01-23 21:53:13 +080073 /**
74 * It's used both 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 {
Luke Huang00b15f32019-01-04 19:56:29 +080080 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
Luke Huang33bfef32019-01-23 21:53:13 +080088 private static final String TAG = "DnsRecord";
Luke Huang00b15f32019-01-04 19:56:29 +080089
90 public final String dName;
91 public final int nsType;
92 public final int nsClass;
93 public final long ttl;
Luke Huang33bfef32019-01-23 21:53:13 +080094 private final byte[] mRdata;
Luke Huang00b15f32019-01-04 19:56:29 +080095
96 /**
Luke Huang33bfef32019-01-23 21:53:13 +080097 * Create a new DnsRecord from a positioned ByteBuffer.
Luke Huang00b15f32019-01-04 19:56:29 +080098 *
Luke Huang33bfef32019-01-23 21:53:13 +080099 * @param ByteBuffer input of record, must be in network byte order
100 * (which is the default).
101 * Reads the passed ByteBuffer from its current position and decodes a DNS record.
Luke Huang00b15f32019-01-04 19:56:29 +0800102 * When this constructor returns, the reading position of the ByteBuffer has been
103 * advanced to the end of the DNS header record.
104 * This is meant to chain with other methods reading a DNS response in sequence.
Luke Huang00b15f32019-01-04 19:56:29 +0800105 */
Luke Huang33bfef32019-01-23 21:53:13 +0800106 DnsRecord(int recordType, @NonNull ByteBuffer buf)
Luke Huang00b15f32019-01-04 19:56:29 +0800107 throws BufferUnderflowException, ParseException {
108 dName = parseName(buf, 0 /* Parse depth */);
109 if (dName.length() > MAXNAMESIZE) {
Luke Huang33bfef32019-01-23 21:53:13 +0800110 throw new ParseException(
111 "Parse name fail, name size is too long: " + dName.length());
Luke Huang00b15f32019-01-04 19:56:29 +0800112 }
113 nsType = BitUtils.uint16(buf.getShort());
114 nsClass = BitUtils.uint16(buf.getShort());
115
Luke Huang33bfef32019-01-23 21:53:13 +0800116 if (recordType != QDSECTION) {
Luke Huang00b15f32019-01-04 19:56:29 +0800117 ttl = BitUtils.uint32(buf.getInt());
118 final int length = BitUtils.uint16(buf.getShort());
Luke Huang33bfef32019-01-23 21:53:13 +0800119 mRdata = new byte[length];
120 buf.get(mRdata);
Luke Huang00b15f32019-01-04 19:56:29 +0800121 } else {
122 ttl = 0;
Luke Huang33bfef32019-01-23 21:53:13 +0800123 mRdata = null;
Luke Huang00b15f32019-01-04 19:56:29 +0800124 }
125 }
126
127 /**
Luke Huang33bfef32019-01-23 21:53:13 +0800128 * Get a copy of rdata.
Luke Huang00b15f32019-01-04 19:56:29 +0800129 */
Luke Huang33bfef32019-01-23 21:53:13 +0800130 @Nullable
131 public byte[] getRR() {
132 return (mRdata == null) ? null : mRdata.clone();
Luke Huang00b15f32019-01-04 19:56:29 +0800133 }
134
135 /**
136 * Convert label from {@code byte[]} to {@code String}
137 *
Luke Huang33bfef32019-01-23 21:53:13 +0800138 * Follows the same conversion rules of the native code (ns_name.c in libc)
Luke Huang00b15f32019-01-04 19:56:29 +0800139 */
140 private String labelToString(@NonNull byte[] label) {
141 final StringBuffer sb = new StringBuffer();
142 for (int i = 0; i < label.length; ++i) {
143 int b = BitUtils.uint8(label[i]);
144 // Control characters and non-ASCII characters.
145 if (b <= 0x20 || b >= 0x7f) {
Luke Huang33bfef32019-01-23 21:53:13 +0800146 // Append the byte as an escaped decimal number, e.g., "\19" for 0x13.
Luke Huang00b15f32019-01-04 19:56:29 +0800147 sb.append('\\');
148 byteFormat.format(b, sb, pos);
149 } else if (b == '"' || b == '.' || b == ';' || b == '\\'
150 || b == '(' || b == ')' || b == '@' || b == '$') {
Luke Huang33bfef32019-01-23 21:53:13 +0800151 // Append the byte as an escaped character, e.g., "\:" for 0x3a.
Luke Huang00b15f32019-01-04 19:56:29 +0800152 sb.append('\\');
153 sb.append((char) b);
154 } else {
Luke Huang33bfef32019-01-23 21:53:13 +0800155 // Append the byte as a character, e.g., "a" for 0x61.
Luke Huang00b15f32019-01-04 19:56:29 +0800156 sb.append((char) b);
157 }
158 }
159 return sb.toString();
160 }
161
162 private String parseName(@NonNull ByteBuffer buf, int depth) throws
163 BufferUnderflowException, ParseException {
Luke Huang33bfef32019-01-23 21:53:13 +0800164 if (depth > MAXLABELCOUNT) {
165 throw new ParseException("Failed to parse name, too many labels");
166 }
Luke Huang00b15f32019-01-04 19:56:29 +0800167 final int len = BitUtils.uint8(buf.get());
168 final int mask = len & NAME_COMPRESSION;
169 if (0 == len) {
170 return "";
171 } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
172 throw new ParseException("Parse name fail, bad label type");
173 } else if (mask == NAME_COMPRESSION) {
174 // Name compression based on RFC 1035 - 4.1.4 Message compression
175 final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get());
176 final int oldPos = buf.position();
177 if (offset >= oldPos - 2) {
178 throw new ParseException("Parse compression name fail, invalid compression");
179 }
180 buf.position(offset);
181 final String pointed = parseName(buf, depth + 1);
182 buf.position(oldPos);
183 return pointed;
184 } else {
185 final byte[] label = new byte[len];
186 buf.get(label);
187 final String head = labelToString(label);
188 if (head.length() > MAXLABELSIZE) {
189 throw new ParseException("Parse name fail, invalid label length");
190 }
191 final String tail = parseName(buf, depth + 1);
192 return TextUtils.isEmpty(tail) ? head : head + "." + tail;
193 }
194 }
195 }
196
197 public static final int QDSECTION = 0;
198 public static final int ANSECTION = 1;
199 public static final int NSSECTION = 2;
200 public static final int ARSECTION = 3;
201 private static final int NUM_SECTIONS = ARSECTION + 1;
202
203 private static final String TAG = DnsPacket.class.getSimpleName();
204
205 protected final DnsHeader mHeader;
Luke Huang33bfef32019-01-23 21:53:13 +0800206 protected final List<DnsRecord>[] mRecords;
Luke Huang00b15f32019-01-04 19:56:29 +0800207
208 public static class ParseException extends Exception {
209 public ParseException(String msg) {
210 super(msg);
211 }
212
213 public ParseException(String msg, Throwable cause) {
214 super(msg, cause);
215 }
216 }
217
218 protected DnsPacket(@NonNull byte[] data) throws ParseException {
219 if (null == data) throw new ParseException("Parse header failed, null input data");
220 final ByteBuffer buffer;
221 try {
222 buffer = ByteBuffer.wrap(data);
223 mHeader = new DnsHeader(buffer);
224 } catch (BufferUnderflowException e) {
225 throw new ParseException("Parse Header fail, bad input data", e);
226 }
227
Luke Huang33bfef32019-01-23 21:53:13 +0800228 mRecords = new ArrayList[NUM_SECTIONS];
Luke Huang00b15f32019-01-04 19:56:29 +0800229
230 for (int i = 0; i < NUM_SECTIONS; ++i) {
Luke Huang33bfef32019-01-23 21:53:13 +0800231 final int count = mHeader.getRecordCount(i);
Luke Huang00b15f32019-01-04 19:56:29 +0800232 if (count > 0) {
Luke Huang33bfef32019-01-23 21:53:13 +0800233 mRecords[i] = new ArrayList(count);
Luke Huang00b15f32019-01-04 19:56:29 +0800234 }
235 for (int j = 0; j < count; ++j) {
236 try {
Luke Huang33bfef32019-01-23 21:53:13 +0800237 mRecords[i].add(new DnsRecord(i, buffer));
Luke Huang00b15f32019-01-04 19:56:29 +0800238 } catch (BufferUnderflowException e) {
Luke Huang33bfef32019-01-23 21:53:13 +0800239 throw new ParseException("Parse record fail", e);
Luke Huang00b15f32019-01-04 19:56:29 +0800240 }
241 }
242 }
243 }
244}