blob: 458fb340b196f52a79c06c8eff8cf15641fcaff9 [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;
31import java.util.StringJoiner;
32
33/**
34 * Defines basic data for DNS protocol based on RFC 1035.
35 * Subclasses create the specific format used in DNS packet.
36 *
37 * @hide
38 */
39public abstract class DnsPacket {
40 public class DnsHeader {
41 private static final String TAG = "DnsHeader";
42 public final int id;
43 public final int flags;
44 public final int rcode;
45 private final int[] mSectionCount;
46
47 /**
48 * Create a new DnsHeader from a positioned ByteBuffer.
49 *
50 * The ByteBuffer must be in network byte order (which is the default).
51 * Reads the passed ByteBuffer from its current position and decodes a DNS header.
52 * When this constructor returns, the reading position of the ByteBuffer has been
53 * advanced to the end of the DNS header record.
54 * This is meant to chain with other methods reading a DNS response in sequence.
55 *
56 */
57 DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException {
58 id = BitUtils.uint16(buf.getShort());
59 flags = BitUtils.uint16(buf.getShort());
60 rcode = flags & 0xF;
61 mSectionCount = new int[NUM_SECTIONS];
62 for (int i = 0; i < NUM_SECTIONS; ++i) {
63 mSectionCount[i] = BitUtils.uint16(buf.getShort());
64 }
65 }
66
67 /**
68 * Get section count by section type.
69 */
70 public int getSectionCount(int sectionType) {
71 return mSectionCount[sectionType];
72 }
73 }
74
75 public class DnsSection {
76 private static final int MAXNAMESIZE = 255;
77 private static final int MAXLABELSIZE = 63;
78 private static final int MAXLABELCOUNT = 128;
79 private static final int NAME_NORMAL = 0;
80 private static final int NAME_COMPRESSION = 0xC0;
81 private final DecimalFormat byteFormat = new DecimalFormat();
82 private final FieldPosition pos = new FieldPosition(0);
83
84 private static final String TAG = "DnsSection";
85
86 public final String dName;
87 public final int nsType;
88 public final int nsClass;
89 public final long ttl;
90 private final byte[] mRR;
91
92 /**
93 * Create a new DnsSection from a positioned ByteBuffer.
94 *
95 * The ByteBuffer must be in network byte order (which is the default).
96 * Reads the passed ByteBuffer from its current position and decodes a DNS section.
97 * When this constructor returns, the reading position of the ByteBuffer has been
98 * advanced to the end of the DNS header record.
99 * This is meant to chain with other methods reading a DNS response in sequence.
100 *
101 */
102 DnsSection(int sectionType, @NonNull ByteBuffer buf)
103 throws BufferUnderflowException, ParseException {
104 dName = parseName(buf, 0 /* Parse depth */);
105 if (dName.length() > MAXNAMESIZE) {
106 throw new ParseException("Parse name fail, name size is too long");
107 }
108 nsType = BitUtils.uint16(buf.getShort());
109 nsClass = BitUtils.uint16(buf.getShort());
110
111 if (sectionType != QDSECTION) {
112 ttl = BitUtils.uint32(buf.getInt());
113 final int length = BitUtils.uint16(buf.getShort());
114 mRR = new byte[length];
115 buf.get(mRR);
116 } else {
117 ttl = 0;
118 mRR = null;
119 }
120 }
121
122 /**
123 * Get a copy of rr.
124 */
125 @Nullable public byte[] getRR() {
126 return (mRR == null) ? null : mRR.clone();
127 }
128
129 /**
130 * Convert label from {@code byte[]} to {@code String}
131 *
132 * It follows the same converting rule as native layer.
133 * (See ns_name.c in libc)
134 *
135 */
136 private String labelToString(@NonNull byte[] label) {
137 final StringBuffer sb = new StringBuffer();
138 for (int i = 0; i < label.length; ++i) {
139 int b = BitUtils.uint8(label[i]);
140 // Control characters and non-ASCII characters.
141 if (b <= 0x20 || b >= 0x7f) {
142 sb.append('\\');
143 byteFormat.format(b, sb, pos);
144 } else if (b == '"' || b == '.' || b == ';' || b == '\\'
145 || b == '(' || b == ')' || b == '@' || b == '$') {
146 sb.append('\\');
147 sb.append((char) b);
148 } else {
149 sb.append((char) b);
150 }
151 }
152 return sb.toString();
153 }
154
155 private String parseName(@NonNull ByteBuffer buf, int depth) throws
156 BufferUnderflowException, ParseException {
157 if (depth > MAXLABELCOUNT) throw new ParseException("Parse name fails, too many labels");
158 final int len = BitUtils.uint8(buf.get());
159 final int mask = len & NAME_COMPRESSION;
160 if (0 == len) {
161 return "";
162 } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
163 throw new ParseException("Parse name fail, bad label type");
164 } else if (mask == NAME_COMPRESSION) {
165 // Name compression based on RFC 1035 - 4.1.4 Message compression
166 final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get());
167 final int oldPos = buf.position();
168 if (offset >= oldPos - 2) {
169 throw new ParseException("Parse compression name fail, invalid compression");
170 }
171 buf.position(offset);
172 final String pointed = parseName(buf, depth + 1);
173 buf.position(oldPos);
174 return pointed;
175 } else {
176 final byte[] label = new byte[len];
177 buf.get(label);
178 final String head = labelToString(label);
179 if (head.length() > MAXLABELSIZE) {
180 throw new ParseException("Parse name fail, invalid label length");
181 }
182 final String tail = parseName(buf, depth + 1);
183 return TextUtils.isEmpty(tail) ? head : head + "." + tail;
184 }
185 }
186 }
187
188 public static final int QDSECTION = 0;
189 public static final int ANSECTION = 1;
190 public static final int NSSECTION = 2;
191 public static final int ARSECTION = 3;
192 private static final int NUM_SECTIONS = ARSECTION + 1;
193
194 private static final String TAG = DnsPacket.class.getSimpleName();
195
196 protected final DnsHeader mHeader;
197 protected final List<DnsSection>[] mSections;
198
199 public static class ParseException extends Exception {
200 public ParseException(String msg) {
201 super(msg);
202 }
203
204 public ParseException(String msg, Throwable cause) {
205 super(msg, cause);
206 }
207 }
208
209 protected DnsPacket(@NonNull byte[] data) throws ParseException {
210 if (null == data) throw new ParseException("Parse header failed, null input data");
211 final ByteBuffer buffer;
212 try {
213 buffer = ByteBuffer.wrap(data);
214 mHeader = new DnsHeader(buffer);
215 } catch (BufferUnderflowException e) {
216 throw new ParseException("Parse Header fail, bad input data", e);
217 }
218
219 mSections = new ArrayList[NUM_SECTIONS];
220
221 for (int i = 0; i < NUM_SECTIONS; ++i) {
222 final int count = mHeader.getSectionCount(i);
223 if (count > 0) {
224 mSections[i] = new ArrayList(count);
225 }
226 for (int j = 0; j < count; ++j) {
227 try {
228 mSections[i].add(new DnsSection(i, buffer));
229 } catch (BufferUnderflowException e) {
230 throw new ParseException("Parse section fail", e);
231 }
232 }
233 }
234 }
235}