• 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 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