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