Spin off DnsPacket into separate library
- Packages that don't need the entire 'net-utils-framework-common'
library can depend on this lightweight library.
- This library does not depend on 'framework-annotations' which
was a problem for the mainline modularization team in allowing
other libs to depend on it.
- Spins off the DnsRecord parsing logic into its own class so that
custom DNS resolvers can re-use it.
Test: atest NetworkStaticLibTests
Bug: 193442330
Change-Id: I27d6ee4591d8ab126b7df31f0df8377794fa50f1
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index e8918c6..0585c09 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -84,6 +84,19 @@
],
}
+java_library {
+ name: "net-utils-dnspacket-common",
+ srcs: [
+ "framework/**/DnsPacket.java",
+ "framework/**/DnsPacketUtils.java",
+ ],
+ sdk_version: "module_current",
+ visibility: [
+ "//packages/services/Iwlan:__subpackages__",
+ ],
+ libs: ["framework-annotations-lib"],
+}
+
filegroup {
name: "net-utils-framework-common-srcs",
srcs: ["framework/**/*.java"],
@@ -91,7 +104,6 @@
visibility: [
"//frameworks/base",
"//packages/modules/Connectivity:__subpackages__",
- "//frameworks/base/packages/Connectivity/framework",
],
}
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacket.java b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
index 5ac731c..4c88000 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacket.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
@@ -18,12 +18,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.text.TextUtils;
+
+import com.android.net.module.util.DnsPacketUtils.DnsRecordParser;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
-import java.text.DecimalFormat;
-import java.text.FieldPosition;
import java.util.ArrayList;
import java.util.List;
@@ -60,6 +59,11 @@
public final int rcode;
private final int[] mRecordCount;
+ /* If this bit in the 'flags' field is set to 0, the DNS message corresponding to this
+ * header is a query; otherwise, it is a response.
+ */
+ private static final int FLAGS_SECTION_QR_BIT = 15;
+
/**
* Create a new DnsHeader from a positioned ByteBuffer.
*
@@ -80,6 +84,14 @@
}
/**
+ * Determines if the DNS message corresponding to this header is a response, as defined in
+ * RFC 1035 Section 4.1.1.
+ */
+ public boolean isResponse() {
+ return (flags & (1 << FLAGS_SECTION_QR_BIT)) != 0;
+ }
+
+ /**
* Get record count by type.
*/
public int getRecordCount(int type) {
@@ -95,12 +107,8 @@
*/
public class DnsRecord {
private static final int MAXNAMESIZE = 255;
- private static final int MAXLABELSIZE = 63;
- private static final int MAXLABELCOUNT = 128;
public static final int NAME_NORMAL = 0;
public static final int NAME_COMPRESSION = 0xC0;
- private final DecimalFormat mByteFormat = new DecimalFormat();
- private final FieldPosition mPos = new FieldPosition(0);
private static final String TAG = "DnsRecord";
@@ -118,12 +126,12 @@
* advanced to the end of the DNS header record.
* This is meant to chain with other methods reading a DNS response in sequence.
*
- * @param ByteBuffer input of record, must be in network byte order
+ * @param buf ByteBuffer input of record, must be in network byte order
* (which is the default).
*/
DnsRecord(int recordType, @NonNull ByteBuffer buf)
throws BufferUnderflowException, ParseException {
- dName = parseName(buf, 0 /* Parse depth */);
+ dName = DnsRecordParser.parseName(buf, 0 /* Parse depth */);
if (dName.length() > MAXNAMESIZE) {
throw new ParseException(
"Parse name fail, name size is too long: " + dName.length());
@@ -150,66 +158,6 @@
return (mRdata == null) ? null : mRdata.clone();
}
- /**
- * Convert label from {@code byte[]} to {@code String}
- *
- * Follows the same conversion rules of the native code (ns_name.c in libc)
- */
- private String labelToString(@NonNull byte[] label) {
- final StringBuffer sb = new StringBuffer();
- for (int i = 0; i < label.length; ++i) {
- int b = Byte.toUnsignedInt(label[i]);
- // Control characters and non-ASCII characters.
- if (b <= 0x20 || b >= 0x7f) {
- // Append the byte as an escaped decimal number, e.g., "\19" for 0x13.
- sb.append('\\');
- mByteFormat.format(b, sb, mPos);
- } else if (b == '"' || b == '.' || b == ';' || b == '\\'
- || b == '(' || b == ')' || b == '@' || b == '$') {
- // Append the byte as an escaped character, e.g., "\:" for 0x3a.
- sb.append('\\');
- sb.append((char) b);
- } else {
- // Append the byte as a character, e.g., "a" for 0x61.
- sb.append((char) b);
- }
- }
- return sb.toString();
- }
-
- private String parseName(@NonNull ByteBuffer buf, int depth) throws
- BufferUnderflowException, ParseException {
- if (depth > MAXLABELCOUNT) {
- throw new ParseException("Failed to parse name, too many labels");
- }
- final int len = Byte.toUnsignedInt(buf.get());
- final int mask = len & NAME_COMPRESSION;
- if (0 == len) {
- return "";
- } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
- throw new ParseException("Parse name fail, bad label type");
- } else if (mask == NAME_COMPRESSION) {
- // Name compression based on RFC 1035 - 4.1.4 Message compression
- final int offset = ((len & ~NAME_COMPRESSION) << 8) + Byte.toUnsignedInt(buf.get());
- final int oldPos = buf.position();
- if (offset >= oldPos - 2) {
- throw new ParseException("Parse compression name fail, invalid compression");
- }
- buf.position(offset);
- final String pointed = parseName(buf, depth + 1);
- buf.position(oldPos);
- return pointed;
- } else {
- final byte[] label = new byte[len];
- buf.get(label);
- final String head = labelToString(label);
- if (head.length() > MAXLABELSIZE) {
- throw new ParseException("Parse name fail, invalid label length");
- }
- final String tail = parseName(buf, depth + 1);
- return TextUtils.isEmpty(tail) ? head : head + "." + tail;
- }
- }
}
public static final int QDSECTION = 0;
@@ -224,7 +172,10 @@
protected final List<DnsRecord>[] mRecords;
protected DnsPacket(@NonNull byte[] data) throws ParseException {
- if (null == data) throw new ParseException("Parse header failed, null input data");
+ if (null == data) {
+ throw new ParseException("Parse header failed, null input data");
+ }
+
final ByteBuffer buffer;
try {
buffer = ByteBuffer.wrap(data);
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
new file mode 100644
index 0000000..677474c
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util;
+
+import static com.android.net.module.util.DnsPacket.DnsRecord.NAME_COMPRESSION;
+import static com.android.net.module.util.DnsPacket.DnsRecord.NAME_NORMAL;
+
+import android.annotation.NonNull;
+import android.text.TextUtils;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+
+/**
+ * Utilities for decoding the contents of a DnsPacket.
+ *
+ * @hide
+ */
+public final class DnsPacketUtils {
+ /**
+ * Reads the passed ByteBuffer from its current position and decodes a DNS record.
+ */
+ public static class DnsRecordParser {
+ private static final int MAXLABELSIZE = 63;
+ private static final int MAXLABELCOUNT = 128;
+
+ private static final DecimalFormat sByteFormat = new DecimalFormat();
+ private static final FieldPosition sPos = new FieldPosition(0);
+
+ /**
+ * Convert label from {@code byte[]} to {@code String}
+ *
+ * <p>Follows the same conversion rules of the native code (ns_name.c in libc).
+ */
+ private static String labelToString(@NonNull byte[] label) {
+ final StringBuffer sb = new StringBuffer();
+
+ for (int i = 0; i < label.length; ++i) {
+ int b = Byte.toUnsignedInt(label[i]);
+ // Control characters and non-ASCII characters.
+ if (b <= 0x20 || b >= 0x7f) {
+ // Append the byte as an escaped decimal number, e.g., "\19" for 0x13.
+ sb.append('\\');
+ sByteFormat.format(b, sb, sPos);
+ } else if (b == '"' || b == '.' || b == ';' || b == '\\' || b == '(' || b == ')'
+ || b == '@' || b == '$') {
+ // Append the byte as an escaped character, e.g., "\:" for 0x3a.
+ sb.append('\\');
+ sb.append((char) b);
+ } else {
+ // Append the byte as a character, e.g., "a" for 0x61.
+ sb.append((char) b);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Parses the domain / target name of a DNS record.
+ */
+ public static String parseName(ByteBuffer buf, int depth) throws
+ BufferUnderflowException, DnsPacket.ParseException {
+ if (depth > MAXLABELCOUNT) {
+ throw new DnsPacket.ParseException("Failed to parse name, too many labels");
+ }
+ final int len = Byte.toUnsignedInt(buf.get());
+ final int mask = len & NAME_COMPRESSION;
+ if (0 == len) {
+ return "";
+ } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
+ throw new DnsPacket.ParseException("Parse name fail, bad label type: " + mask);
+ } else if (mask == NAME_COMPRESSION) {
+ // Name compression based on RFC 1035 - 4.1.4 Message compression
+ final int offset = ((len & ~NAME_COMPRESSION) << 8) + Byte.toUnsignedInt(buf.get());
+ final int oldPos = buf.position();
+ if (offset >= oldPos - 2) {
+ throw new DnsPacket.ParseException(
+ "Parse compression name fail, invalid compression");
+ }
+ buf.position(offset);
+ final String pointed = parseName(buf, depth + 1);
+ buf.position(oldPos);
+ return pointed;
+ } else {
+ final byte[] label = new byte[len];
+ buf.get(label);
+ final String head = labelToString(label);
+ if (head.length() > MAXLABELSIZE) {
+ throw new DnsPacket.ParseException("Parse name fail, invalid label length");
+ }
+ final String tail = parseName(buf, depth + 1);
+ return TextUtils.isEmpty(tail) ? head : head + "." + tail;
+ }
+ }
+
+ private DnsRecordParser() {}
+ }
+
+ private DnsPacketUtils() {}
+}