[DnsPacketUtils] Knob for name compression support
- Allows clients of DnsRecordParser to explicitly mention
whether name compression is supported.
Test: atest NetworkStaticLibTests
Bug: b/207710965
Change-Id: Iac92f062a16f8f28f58cb180ab27e7626a9bd701
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacket.java b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
index 4c88000..080781c 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacket.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
@@ -131,7 +131,8 @@
*/
DnsRecord(int recordType, @NonNull ByteBuffer buf)
throws BufferUnderflowException, ParseException {
- dName = DnsRecordParser.parseName(buf, 0 /* Parse depth */);
+ dName = DnsRecordParser.parseName(buf, 0 /* Parse depth */,
+ /* isNameCompressionSupported= */ true);
if (dName.length() > MAXNAMESIZE) {
throw new ParseException(
"Parse name fail, name size is too long: " + dName.length());
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
index 677474c..3448cad 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
@@ -73,8 +73,14 @@
/**
* Parses the domain / target name of a DNS record.
+ *
+ * As described in RFC 1035 Section 4.1.3, the NAME field of a DNS Resource Record always
+ * supports Name Compression, whereas domain names contained in the RDATA payload of a DNS
+ * record may or may not support Name Compression, depending on the record TYPE. Moreover,
+ * even if Name Compression is supported, its usage is left to the implementation.
*/
- public static String parseName(ByteBuffer buf, int depth) throws
+ public static String parseName(ByteBuffer buf, int depth,
+ boolean isNameCompressionSupported) throws
BufferUnderflowException, DnsPacket.ParseException {
if (depth > MAXLABELCOUNT) {
throw new DnsPacket.ParseException("Failed to parse name, too many labels");
@@ -83,7 +89,8 @@
final int mask = len & NAME_COMPRESSION;
if (0 == len) {
return "";
- } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
+ } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION
+ || (!isNameCompressionSupported && 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
@@ -94,7 +101,7 @@
"Parse compression name fail, invalid compression");
}
buf.position(offset);
- final String pointed = parseName(buf, depth + 1);
+ final String pointed = parseName(buf, depth + 1, isNameCompressionSupported);
buf.position(oldPos);
return pointed;
} else {
@@ -104,7 +111,7 @@
if (head.length() > MAXLABELSIZE) {
throw new DnsPacket.ParseException("Parse name fail, invalid label length");
}
- final String tail = parseName(buf, depth + 1);
+ final String tail = parseName(buf, depth + 1, isNameCompressionSupported);
return TextUtils.isEmpty(tail) ? head : head + "." + tail;
}
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java
new file mode 100644
index 0000000..48777ac
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.DnsPacketUtils.DnsRecordParser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsPacketUtilsTest {
+
+ /**
+ * Verifies that the compressed NAME field in the answer section of the DNS message is parsed
+ * successfully when name compression is permitted. Additionally, verifies that a
+ * {@link DnsPacket.ParseException} is thrown in a hypothetical scenario where name compression
+ * is not expected.
+ */
+ @Test
+ public void testParsingAnswerSectionNameCompressed() throws Exception {
+ final byte[] v4blobNameCompressedAnswer = new byte[] {
+ /* Header */
+ 0x55, 0x66, /* Transaction ID */
+ (byte) 0x81, (byte) 0x80, /* Flags */
+ 0x00, 0x01, /* Questions */
+ 0x00, 0x01, /* Answer RRs */
+ 0x00, 0x00, /* Authority RRs */
+ 0x00, 0x00, /* Additional RRs */
+ /* Queries */
+ 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+ 0x00, 0x01, /* Type */
+ 0x00, 0x01, /* Class */
+ /* Answers */
+ (byte) 0xc0, 0x0c, /* Name */
+ 0x00, 0x01, /* Type */
+ 0x00, 0x01, /* Class */
+ 0x00, 0x00, 0x01, 0x2b, /* TTL */
+ 0x00, 0x04, /* Data length */
+ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+ };
+ final int answerOffsetBytePosition = 32;
+ final ByteBuffer nameCompressedBuf = ByteBuffer.wrap(v4blobNameCompressedAnswer);
+
+ nameCompressedBuf.position(answerOffsetBytePosition);
+ assertThrows(DnsPacket.ParseException.class, () -> DnsRecordParser.parseName(
+ nameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */false));
+
+ nameCompressedBuf.position(answerOffsetBytePosition);
+ String domainName = DnsRecordParser.parseName(
+ nameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */true);
+ assertEquals(domainName, "www.google.com");
+ }
+
+ /**
+ * Verifies that an uncompressed NAME field in the answer section of the DNS message is parsed
+ * successfully irrespective of whether name compression is permitted.
+ */
+ @Test
+ public void testParsingAnswerSectionNoNameCompression() throws Exception {
+ final byte[] v4blobNoNameCompression = new byte[] {
+ /* Header */
+ 0x55, 0x66, /* Transaction ID */
+ (byte) 0x81, (byte) 0x80, /* Flags */
+ 0x00, 0x01, /* Questions */
+ 0x00, 0x01, /* Answer RRs */
+ 0x00, 0x00, /* Authority RRs */
+ 0x00, 0x00, /* Additional RRs */
+ /* Queries */
+ 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+ 0x00, 0x01, /* Type */
+ 0x00, 0x01, /* Class */
+ /* Answers */
+ 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+ 0x00, 0x01, /* Type */
+ 0x00, 0x01, /* Class */
+ 0x00, 0x00, 0x01, 0x2b, /* TTL */
+ 0x00, 0x04, /* Data length */
+ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+ };
+ final int answerOffsetBytePosition = 32;
+ final ByteBuffer notNameCompressedBuf = ByteBuffer.wrap(v4blobNoNameCompression);
+
+ notNameCompressedBuf.position(answerOffsetBytePosition);
+ String domainName = DnsRecordParser.parseName(
+ notNameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */ true);
+ assertEquals(domainName, "www.google.com");
+
+ notNameCompressedBuf.position(answerOffsetBytePosition);
+ domainName = DnsRecordParser.parseName(
+ notNameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */ false);
+ assertEquals(domainName, "www.google.com");
+ }
+}