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 com.android.net.module.util.DnsPacket.DnsRecord.NAME_COMPRESSION; 20 import static com.android.net.module.util.DnsPacket.DnsRecord.NAME_NORMAL; 21 22 import android.annotation.NonNull; 23 import android.text.TextUtils; 24 25 import java.nio.BufferUnderflowException; 26 import java.nio.ByteBuffer; 27 import java.text.DecimalFormat; 28 import java.text.FieldPosition; 29 30 /** 31 * Utilities for decoding the contents of a DnsPacket. 32 * 33 * @hide 34 */ 35 public final class DnsPacketUtils { 36 /** 37 * Reads the passed ByteBuffer from its current position and decodes a DNS record. 38 */ 39 public static class DnsRecordParser { 40 private static final int MAXLABELSIZE = 63; 41 private static final int MAXLABELCOUNT = 128; 42 43 private static final DecimalFormat sByteFormat = new DecimalFormat(); 44 private static final FieldPosition sPos = new FieldPosition(0); 45 46 /** 47 * Convert label from {@code byte[]} to {@code String} 48 * 49 * <p>Follows the same conversion rules of the native code (ns_name.c in libc). 50 */ labelToString(@onNull byte[] label)51 private static String labelToString(@NonNull byte[] label) { 52 final StringBuffer sb = new StringBuffer(); 53 54 for (int i = 0; i < label.length; ++i) { 55 int b = Byte.toUnsignedInt(label[i]); 56 // Control characters and non-ASCII characters. 57 if (b <= 0x20 || b >= 0x7f) { 58 // Append the byte as an escaped decimal number, e.g., "\19" for 0x13. 59 sb.append('\\'); 60 sByteFormat.format(b, sb, sPos); 61 } else if (b == '"' || b == '.' || b == ';' || b == '\\' || b == '(' || b == ')' 62 || b == '@' || b == '$') { 63 // Append the byte as an escaped character, e.g., "\:" for 0x3a. 64 sb.append('\\'); 65 sb.append((char) b); 66 } else { 67 // Append the byte as a character, e.g., "a" for 0x61. 68 sb.append((char) b); 69 } 70 } 71 return sb.toString(); 72 } 73 74 /** 75 * Parses the domain / target name of a DNS record. 76 * 77 * As described in RFC 1035 Section 4.1.3, the NAME field of a DNS Resource Record always 78 * supports Name Compression, whereas domain names contained in the RDATA payload of a DNS 79 * record may or may not support Name Compression, depending on the record TYPE. Moreover, 80 * even if Name Compression is supported, its usage is left to the implementation. 81 */ parseName(ByteBuffer buf, int depth, boolean isNameCompressionSupported)82 public static String parseName(ByteBuffer buf, int depth, 83 boolean isNameCompressionSupported) throws 84 BufferUnderflowException, DnsPacket.ParseException { 85 if (depth > MAXLABELCOUNT) { 86 throw new DnsPacket.ParseException("Failed to parse name, too many labels"); 87 } 88 final int len = Byte.toUnsignedInt(buf.get()); 89 final int mask = len & NAME_COMPRESSION; 90 if (0 == len) { 91 return ""; 92 } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION 93 || (!isNameCompressionSupported && mask == NAME_COMPRESSION)) { 94 throw new DnsPacket.ParseException("Parse name fail, bad label type: " + mask); 95 } else if (mask == NAME_COMPRESSION) { 96 // Name compression based on RFC 1035 - 4.1.4 Message compression 97 final int offset = ((len & ~NAME_COMPRESSION) << 8) + Byte.toUnsignedInt(buf.get()); 98 final int oldPos = buf.position(); 99 if (offset >= oldPos - 2) { 100 throw new DnsPacket.ParseException( 101 "Parse compression name fail, invalid compression"); 102 } 103 buf.position(offset); 104 final String pointed = parseName(buf, depth + 1, isNameCompressionSupported); 105 buf.position(oldPos); 106 return pointed; 107 } else { 108 final byte[] label = new byte[len]; 109 buf.get(label); 110 final String head = labelToString(label); 111 if (head.length() > MAXLABELSIZE) { 112 throw new DnsPacket.ParseException("Parse name fail, invalid label length"); 113 } 114 final String tail = parseName(buf, depth + 1, isNameCompressionSupported); 115 return TextUtils.isEmpty(tail) ? head : head + "." + tail; 116 } 117 } 118 DnsRecordParser()119 private DnsRecordParser() {} 120 } 121 DnsPacketUtils()122 private DnsPacketUtils() {} 123 } 124