Repository: netty/netty
Issue: netty-resolver-dns "Got bad packet: bad label type"

I have built a simple DNS proxy server using the Netty DNS resolver library. [See the code here.](https://github.com/nzhenry/dns-proxy)

It works for most queries but not all. For example, when I try to query the A records for "www.apple.com" using nslookup or dig, it fails intermittently with this error:
```;; Got bad packet: bad label type
305 bytes
ca 20 80 00 00 01 00 04 00 00 00 01 03 77 77 77          .............www
05 61 70 70 6c 65 03 63 6f 6d 00 00 01 00 01 03          .apple.com......
77 77 77 05 61 70 70 6c 65 03 63 6f 6d 00 00 05          www.apple.com...
00 01 00 00 00 37 00 1b 03 77 77 77 05 61 70 70          .....7...www.app
6c 65 03 63 6f 6d 07 65 64 67 65 6b 65 79 03 6e          le.com.edgekey.n
65 74 00 03 77 77 77 05 61 70 70 6c 65 03 63 6f          et..www.apple.co
6d 07 65 64 67 65 6b 65 79 03 6e 65 74 00 00 05          m.edgekey.net...
00 01 00 00 30 2b 00 2f 03 77 77 77 05 61 70 70          ....0+./.www.app
6c 65 03 63 6f 6d 07 65 64 67 65 6b 65 79 03 6e          le.com.edgekey.n
65 74 0b 67 6c 6f 62 61 6c 72 65 64 69 72 06 61          et.globalredir.a
6b 61 64 6e 73 c0 41 03 77 77 77 05 61 70 70 6c          kadns.A.www.appl
65 03 63 6f 6d 07 65 64 67 65 6b 65 79 03 6e 65          e.com.edgekey.ne
74 0b 67 6c 6f 62 61 6c 72 65 64 69 72 06 61 6b          t.globalredir.ak
61 64 6e 73 03 6e 65 74 00 00 05 00 01 00 00 0c          adns.net........
b7 00 19 05 65 36 38 35 38 05 64 73 63 65 39 0a          ....e6858.dsce9.
61 6b 61 6d 61 69 65 64 67 65 c0 41 05 65 36 38          akamaiedge.A.e68
35 38 05 64 73 63 65 39 0a 61 6b 61 6d 61 69 65          58.dsce9.akamaie
64 67 65 03 6e 65 74 00 00 01 00 01 00 00 00 11          dge.net.........
00 04 68 5f a0 7e 00 00 29 10 00 00 00 00 00 00          ..h_....).......
00                                                       .
```
When it fails the debug log looks like this:
```DefaultDnsQuestion(www.apple.com. IN A)
DefaultDnsRawRecord(www.apple.com. 885 IN CNAME 27B)
DefaultDnsRawRecord(www.apple.com.edgekey.net. 10239 IN CNAME 47B)
DefaultDnsRawRecord(www.apple.com.edgekey.net.globalredir.akadns.net. 441 IN CNAME 25B)
DefaultDnsRawRecord(e6858.dsce9.akamaiedge.net. 9 IN A 4B)] RECEIVED: [[[id: 0xc9dafb10], 36804, /192.168.1.1:53, DatagramDnsResponse(from: /192.168.1.1:53, to: /0:0:0:0:0:0:0:0:51678, 36804, QUERY(0), NoError(0), RD RA)
DefaultDnsQuestion(www.apple.com. IN A)
DefaultDnsRawRecord(www.apple.com. 885 IN CNAME 27B)
DefaultDnsRawRecord(www.apple.com.edgekey.net. 10239 IN CNAME 47B)
DefaultDnsRawRecord(www.apple.com.edgekey.net.globalredir.akadns.net. 441 IN CNAME 25B)
DefaultDnsRawRecord(e6858.dsce9.akamaiedge.net. 9 IN A 4B)]: {}], {}
```
When it succeeds the log looks like this:
```DefaultDnsQuestion(www.apple.com. IN A)
DefaultDnsRawRecord(www.apple.com. 881 IN CNAME 27B)
DefaultDnsRawRecord(www.apple.com.edgekey.net. 10235 IN CNAME 50B)
DefaultDnsRawRecord(www.apple.com.edgekey.net.globalredir.akadns.net. 437 IN CNAME 28B)
DefaultDnsRawRecord(e6858.dsce9.akamaiedge.net. 5 IN A 4B)
DefaultDnsRawRecord(OPT flags:0 udp:4096 0B)] RECEIVED: [[[id: 0xc9dafb10], 37086, /192.168.1.1:53, DatagramDnsResponse(from: /192.168.1.1:53, to: /0:0:0:0:0:0:0:0:51678, 37086, QUERY(0), NoError(0), RD RA)
DefaultDnsQuestion(www.apple.com. IN A)
DefaultDnsRawRecord(www.apple.com. 881 IN CNAME 27B)
DefaultDnsRawRecord(www.apple.com.edgekey.net. 10235 IN CNAME 50B)
DefaultDnsRawRecord(www.apple.com.edgekey.net.globalredir.akadns.net. 437 IN CNAME 28B)
DefaultDnsRawRecord(e6858.dsce9.akamaiedge.net. 5 IN A 4B)
DefaultDnsRawRecord(OPT flags:0 udp:4096 0B)]: {}], {}
```
Notice the additional OPT record when it succeeds. This appears to be the key. I just don't know how I can fix the problem when it fails.

I've discovered that if I remove the CNAME records from the response, then the query resolves with no errors. I would like to return a proper copy of the server response including the CNAME records. Any help would be greatly appreciated!

Issue comments:
I suspect it has to do with the maximal datagram packet size. Try to use `new FixedRecvByteBufAllocator(4096)` when you bootstrap your server. 
I tried this:
```
NioDatagramChannel channel = new NioDatagramChannel();
channel.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(4096));
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
    .channelFactory(() -> channel)
    ...
```
No luck. Tried 8192 as well.
I tried to reproduce the problem.  This seems to be a bug in `DatagramDnsResponseDecoder`, if the server receives the compressed DNS response and decodes it, it will be found that if the compression pointer is included in the `RADATA` of `ANSWER SECTION`, this problem will occur.

The reason is that `DatagramDnsResponseDecoder` does not decompress the compression pointer contained in `RADATA` of `ANSWER SECTION`.

The `ANSWER SECTION` has a resource record format:
```
0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                                               |
/                                               /
/                      NAME                     /
|                                               |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      TYPE                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                     CLASS                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      TTL                      |
|                                               |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                   RDLENGTH                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
/                     RDATA                     /
/                                               /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
```

For Example:

Send `www.apple.com` DNS query request.

Expect `ANSWER SECTION`:
```
www.apple.com.          1376    IN      CNAME   www.apple.com.edgekey.net.
www.apple.com.edgekey.net. 134  IN      CNAME   www.apple.com.edgekey.net.globalredir.akadns.net.
www.apple.com.edgekey.net.globalredir.akadns.net. 2663 IN CNAME e6858.e19.s.tl88.net.
e6858.e19.s.tl88.net.   5       IN      A       222.163.207.56
```

Actually decoded by Netty
```
www.apple.com.          1376    IN      CNAME   .www.apple.com.edgekey.net.
                                    +------------------------+
                                    |
         +--------------------------v----------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+---------------------------------------------------------------------------+
|00000000| 03 77 77 77 05 61 70 70 6c 65 03 63 6f 6d 07 65 |.www.apple.com.e|
|00000010| 64 67 65 6b 65 79 03 6e 65 74 00                |dgekey.net.     |
+---------------------------------------------------------------------------+

```

```
www.apple.com.edgekey.net. 134  IN      CNAME   www.apple.com.edgekey.net.globalredir.akadns.A
                                    +------------------------------+
                                    |
         +--------------------------v----------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+---------------------------------------------------------------------------+
|00000000| 03 77 77 77 05 61 70 70 6c 65 03 63 6f 6d 07 65 |.www.apple.com.e|
|00000010| 64 67 65 6b 65 79 03 6e 65 74 0b 67 6c 6f 62 61 |dgekey.net.globa|
|00000020| 6c 72 65 64 69 72 06 61 6b 61 64 6e 73 c0 41    |lredir.akadns.A |
+---------------------------------------------------------------------------+
                                                  +---+
                                                    |
                                                    v
                                       Undecompressed Compression Pointer
```

```
www.apple.com.edgekey.net.globalredir.akadns.net. 2663 IN CNAME     e6858.e19.s.tl88.AA
                                    +-----------------------------------------+
                                    |
         +--------------------------v----------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+---------------------------------------------------------------------------+
|00000000| 05 65 36 38 35 38 03 65 31 39 01 73 04 74 6c 38 |.e6858.e19.s.tl8|
|00000010| 38 c0 41                                        |8.A             |
+---------------------------------------------------------------------------+
              +---+
                |
                v
  Undecompressed Compression Pointer
```

```
e6858.e19.s.tl88.net.   5       IN      A  222.163.207.56
                                    +-------------+
                                    |
         +--------------------------v----------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+---------------------------------------------------------------------------+
|00000000| da 3a 65 e5                                     |.:e.            |
+---------------------------------------------------------------------------+
```

When all ANSWER Records are combined in dns-proxy server and sent to the client, the original compression pointer will point to the illegal position, resulting in `bad label type` error.

 I'll do a PR.
@qeesung can you provide a focused fix for the issue without changing the whole class hierarchy etc and breaking the API or is this not possible at all ?
@normanmaurer Okay, I'll give it a try. :-)


@qeesung thanks a lot
@normanmaurer The reason for this problem is that rdata of DNS record cannot be parsed correctly when it contains compressed pointers. Generally, compression pointers are included only when rdata type is text type, such as `NS`, `CNAME` type. In other words, rdata can be parsed correctly only if Dns record type is known in advance.

In the original implementation, `DnsRecord` has only three subclasses, `DnsRawRecord`, `DnsPtrRecord` and `DnsOptPseudoRecord`. The text type of `DnsRecord` is currently hold in `DnsRawRecord`. To solve the problem of bad label, there are two solutions:

The original implementation of method `decodeRecord` of class `DefaultDnsRecordDecoder`

https://github.com/netty/netty/blob/00afb19d7a37de21b35ce4f6cb3fa7f74809f2ab/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordDecoder.java#L89-L103

**Solution1**: In the `decodeRecord` method of class `DefaultDnsRecordDecoder`, a new if branch is added to decode the record of text type. This approach has minimal impact on current implementations, but it should not be a good design. If you need to decode other types of records later, you need to add a new if branch.
```java
protected DnsRecord decodeRecord(
            String name, DnsRecordType type, int dnsClass, long timeToLive,
            ByteBuf in, int offset, int length) throws Exception {
	if (type == DnsRecordType.PTR) {
		return new DefaultDnsPtrRecord(
		                    name, dnsClass, timeToLive, decodeName0(in.duplicate().setIndex(offset, offset + length)));
	}
  	if (type == DnsRecordType.NS || type ==  DnsRecordType.CNAME /** || more types... */) {
        // decode normal text rdata or compressed pointer text rdata
        // return DnsRawRecord
    }
	return new DefaultDnsRawRecord(
	                name, type, dnsClass, timeToLive, in.retainedDuplicate().setIndex(offset, offset + length));
}
```

**Solution2**: Add an RData decoder for each type of `DnsRecord`, This method has been implemented in PR #9084 . If a new type of RData needs to be decoded, then only one decoder needs to be added and registered. But this will break some of the APIs and implementations

```java
protected DnsRecord decodeRecord(
            String name, DnsRecordType type, int dnsClass, long timeToLive,
            ByteBuf in, int offset, int length) throws Exception {
	ByteBuf rData = in.duplicate().setIndex(offset, offset + length);
	// Get the corresponding RData decoder by type
	DnsRDataDecoder<? extends DnsRecord> rDataDecoder = DnsRDataCodecs.rDataDecoder(type);
	if (rDataDecoder != null) {
		return rDataDecoder.decodeRData(name, dnsClass, timeToLive, rData);
	}
	return new DefaultDnsRawRecord(name, type, dnsClass, timeToLive, rData.retain());
}
```
WDYT？Which way is better?

@qeesung for 4.1 I think we should do 1) for master we can check 2)
@normanmaurer Okay, I'll submit a new PR for 1)

Pull request review comments:
Package private ?
Package private
Package private
Package private
Done!
Done!
Done!
Done
use `compression.alloc().buffer(...)` to ensure the same allocator is used (which may be a pooled allocated) 
Done
nit: typo... should be "uncompressed"
nit: should be "uncompressed"
Done
Done

Patch that resolved the issue:
diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordDecoder.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordDecoder.java
index b4e50ff402e..e61d46cc204 100644
--- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordDecoder.java
+++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordDecoder.java
@@ -16,8 +16,6 @@
 package io.netty.handler.codec.dns;
 
 import io.netty.buffer.ByteBuf;
-import io.netty.handler.codec.CorruptedFrameException;
-import io.netty.util.CharsetUtil;
 import io.netty.util.internal.UnstableApi;
 
 /**
@@ -98,6 +96,11 @@ protected DnsRecord decodeRecord(
             return new DefaultDnsPtrRecord(
                     name, dnsClass, timeToLive, decodeName0(in.duplicate().setIndex(offset, offset + length)));
         }
+        if (type == DnsRecordType.CNAME || type == DnsRecordType.NS) {
+            return new DefaultDnsRawRecord(name, type, dnsClass, timeToLive,
+                                           DnsCodecUtil.decompressDomainName(
+                                                   in.duplicate().setIndex(offset, offset + length)));
+        }
         return new DefaultDnsRawRecord(
                 name, type, dnsClass, timeToLive, in.retainedDuplicate().setIndex(offset, offset + length));
     }
@@ -123,69 +126,6 @@ protected String decodeName0(ByteBuf in) {
      * @return the domain name for an entry
      */
     public static String decodeName(ByteBuf in) {
-        int position = -1;
-        int checked = 0;
-        final int end = in.writerIndex();
-        final int readable = in.readableBytes();
-
-        // Looking at the spec we should always have at least enough readable bytes to read a byte here but it seems
-        // some servers do not respect this for empty names. So just workaround this and return an empty name in this
-        // case.
-        //
-        // See:
-        // - https://github.com/netty/netty/issues/5014
-        // - https://www.ietf.org/rfc/rfc1035.txt , Section 3.1
-        if (readable == 0) {
-            return ROOT;
-        }
-
-        final StringBuilder name = new StringBuilder(readable << 1);
-        while (in.isReadable()) {
-            final int len = in.readUnsignedByte();
-            final boolean pointer = (len & 0xc0) == 0xc0;
-            if (pointer) {
-                if (position == -1) {
-                    position = in.readerIndex() + 1;
-                }
-
-                if (!in.isReadable()) {
-                    throw new CorruptedFrameException("truncated pointer in a name");
-                }
-
-                final int next = (len & 0x3f) << 8 | in.readUnsignedByte();
-                if (next >= end) {
-                    throw new CorruptedFrameException("name has an out-of-range pointer");
-                }
-                in.readerIndex(next);
-
-                // check for loops
-                checked += 2;
-                if (checked >= end) {
-                    throw new CorruptedFrameException("name contains a loop.");
-                }
-            } else if (len != 0) {
-                if (!in.isReadable(len)) {
-                    throw new CorruptedFrameException("truncated label in a name");
-                }
-                name.append(in.toString(in.readerIndex(), len, CharsetUtil.UTF_8)).append('.');
-                in.skipBytes(len);
-            } else { // len == 0
-                break;
-            }
-        }
-
-        if (position != -1) {
-            in.readerIndex(position);
-        }
-
-        if (name.length() == 0) {
-            return ROOT;
-        }
-
-        if (name.charAt(name.length() - 1) != '.') {
-            name.append('.');
-        }
-
-        return name.toString();
+        return DnsCodecUtil.decodeDomainName(in);
     }
 }
diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordEncoder.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordEncoder.java
index 48f60bcc951..45914ea0656 100644
--- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordEncoder.java
+++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordEncoder.java
@@ -16,14 +16,11 @@
 package io.netty.handler.codec.dns;
 
 import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufUtil;
 import io.netty.channel.socket.InternetProtocolFamily;
 import io.netty.handler.codec.UnsupportedMessageTypeException;
 import io.netty.util.internal.StringUtil;
 import io.netty.util.internal.UnstableApi;
 
-import static io.netty.handler.codec.dns.DefaultDnsRecordDecoder.ROOT;
-
 /**
  * The default {@link DnsRecordEncoder} implementation.
  *
@@ -141,25 +138,7 @@ private void encodeRawRecord(DnsRawRecord record, ByteBuf out) throws Exception
     }
 
     protected void encodeName(String name, ByteBuf buf) throws Exception {
-        if (ROOT.equals(name)) {
-            // Root domain
-            buf.writeByte(0);
-            return;
-        }
-
-        final String[] labels = name.split("\\.");
-        for (String label : labels) {
-            final int labelLen = label.length();
-            if (labelLen == 0) {
-                // zero-length label means the end of the name.
-                break;
-            }
-
-            buf.writeByte(labelLen);
-            ByteBufUtil.writeAscii(buf, label);
-        }
-
-        buf.writeByte(0); // marks end of name field
+        DnsCodecUtil.encodeDomainName(name, buf);
     }
 
     private static byte padWithZeros(byte b, int lowOrderBitsToPreserve) {
diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsCodecUtil.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsCodecUtil.java
new file mode 100644
index 00000000000..8804cf748e4
--- /dev/null
+++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsCodecUtil.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2019 The Netty Project
+ *
+ * The Netty Project licenses this file to you 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 io.netty.handler.codec.dns;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.CorruptedFrameException;
+import io.netty.util.CharsetUtil;
+
+import static io.netty.handler.codec.dns.DefaultDnsRecordDecoder.*;
+
+final class DnsCodecUtil {
+    private DnsCodecUtil() {
+        // Util class
+    }
+
+    static void encodeDomainName(String name, ByteBuf buf) {
+        if (ROOT.equals(name)) {
+            // Root domain
+            buf.writeByte(0);
+            return;
+        }
+
+        final String[] labels = name.split("\\.");
+        for (String label : labels) {
+            final int labelLen = label.length();
+            if (labelLen == 0) {
+                // zero-length label means the end of the name.
+                break;
+            }
+
+            buf.writeByte(labelLen);
+            ByteBufUtil.writeAscii(buf, label);
+        }
+
+        buf.writeByte(0); // marks end of name field
+    }
+
+    static String decodeDomainName(ByteBuf in) {
+        int position = -1;
+        int checked = 0;
+        final int end = in.writerIndex();
+        final int readable = in.readableBytes();
+
+        // Looking at the spec we should always have at least enough readable bytes to read a byte here but it seems
+        // some servers do not respect this for empty names. So just workaround this and return an empty name in this
+        // case.
+        //
+        // See:
+        // - https://github.com/netty/netty/issues/5014
+        // - https://www.ietf.org/rfc/rfc1035.txt , Section 3.1
+        if (readable == 0) {
+            return ROOT;
+        }
+
+        final StringBuilder name = new StringBuilder(readable << 1);
+        while (in.isReadable()) {
+            final int len = in.readUnsignedByte();
+            final boolean pointer = (len & 0xc0) == 0xc0;
+            if (pointer) {
+                if (position == -1) {
+                    position = in.readerIndex() + 1;
+                }
+
+                if (!in.isReadable()) {
+                    throw new CorruptedFrameException("truncated pointer in a name");
+                }
+
+                final int next = (len & 0x3f) << 8 | in.readUnsignedByte();
+                if (next >= end) {
+                    throw new CorruptedFrameException("name has an out-of-range pointer");
+                }
+                in.readerIndex(next);
+
+                // check for loops
+                checked += 2;
+                if (checked >= end) {
+                    throw new CorruptedFrameException("name contains a loop.");
+                }
+            } else if (len != 0) {
+                if (!in.isReadable(len)) {
+                    throw new CorruptedFrameException("truncated label in a name");
+                }
+                name.append(in.toString(in.readerIndex(), len, CharsetUtil.UTF_8)).append('.');
+                in.skipBytes(len);
+            } else { // len == 0
+                break;
+            }
+        }
+
+        if (position != -1) {
+            in.readerIndex(position);
+        }
+
+        if (name.length() == 0) {
+            return ROOT;
+        }
+
+        if (name.charAt(name.length() - 1) != '.') {
+            name.append('.');
+        }
+
+        return name.toString();
+    }
+
+    /**
+     * Decompress pointer data.
+     * @param compression comporession data
+     * @return decompressed data
+     */
+    static ByteBuf decompressDomainName(ByteBuf compression) {
+        String domainName = decodeDomainName(compression);
+        ByteBuf result = compression.alloc().buffer(domainName.length() << 1);
+        encodeDomainName(domainName, result);
+        return result;
+    }
+}


Test patch:
diff --git a/codec-dns/src/test/java/io/netty/handler/codec/dns/DefaultDnsRecordDecoderTest.java b/codec-dns/src/test/java/io/netty/handler/codec/dns/DefaultDnsRecordDecoderTest.java
index 6de6ce5d724..a90acaa2833 100644
--- a/codec-dns/src/test/java/io/netty/handler/codec/dns/DefaultDnsRecordDecoderTest.java
+++ b/codec-dns/src/test/java/io/netty/handler/codec/dns/DefaultDnsRecordDecoderTest.java
@@ -16,10 +16,11 @@
 package io.netty.handler.codec.dns;
 
 import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
 import io.netty.buffer.Unpooled;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.*;
 
 public class DefaultDnsRecordDecoderTest {
 
@@ -89,6 +90,81 @@ public void testDecodePtrRecord() throws Exception {
         }
     }
 
+    @Test
+    public void testdecompressCompressPointer() {
+        byte[] compressionPointer = {
+                5, 'n', 'e', 't', 't', 'y', 2, 'i', 'o', 0,
+                (byte) 0xC0, 0
+        };
+        ByteBuf buffer = Unpooled.wrappedBuffer(compressionPointer);
+        ByteBuf uncompressed = null;
+        try {
+            uncompressed = DnsCodecUtil.decompressDomainName(buffer.duplicate().setIndex(10, 12));
+            assertEquals(0, ByteBufUtil.compare(buffer.duplicate().setIndex(0, 10), uncompressed));
+        } finally {
+            buffer.release();
+            if (uncompressed != null) {
+                uncompressed.release();
+            }
+        }
+    }
+
+    @Test
+    public void testdecompressNestedCompressionPointer() {
+        byte[] nestedCompressionPointer = {
+                6, 'g', 'i', 't', 'h', 'u', 'b', 2, 'i', 'o', 0, // github.io
+                5, 'n', 'e', 't', 't', 'y', (byte) 0xC0, 0, // netty.github.io
+                (byte) 0xC0, 11, // netty.github.io
+        };
+        ByteBuf buffer = Unpooled.wrappedBuffer(nestedCompressionPointer);
+        ByteBuf uncompressed = null;
+        try {
+            uncompressed = DnsCodecUtil.decompressDomainName(buffer.duplicate().setIndex(19, 21));
+            assertEquals(0, ByteBufUtil.compare(
+                    Unpooled.wrappedBuffer(new byte[] {
+                            5, 'n', 'e', 't', 't', 'y', 6, 'g', 'i', 't', 'h', 'u', 'b', 2, 'i', 'o', 0
+                    }), uncompressed));
+        } finally {
+            buffer.release();
+            if (uncompressed != null) {
+                uncompressed.release();
+            }
+        }
+    }
+
+    @Test
+    public void testDecodeCompressionRDataPointer() throws Exception {
+        DefaultDnsRecordDecoder decoder = new DefaultDnsRecordDecoder();
+        byte[] compressionPointer = {
+                5, 'n', 'e', 't', 't', 'y', 2, 'i', 'o', 0,
+                (byte) 0xC0, 0
+        };
+        ByteBuf buffer = Unpooled.wrappedBuffer(compressionPointer);
+        DefaultDnsRawRecord cnameRecord = null;
+        DefaultDnsRawRecord nsRecord = null;
+        try {
+            cnameRecord = (DefaultDnsRawRecord) decoder.decodeRecord(
+                    "netty.github.io", DnsRecordType.CNAME, DnsRecord.CLASS_IN, 60, buffer, 10, 2);
+            assertEquals("The rdata of CNAME-type record should be decompressed in advance",
+                         0, ByteBufUtil.compare(buffer.duplicate().setIndex(0, 10), cnameRecord.content()));
+            assertEquals("netty.io.", DnsCodecUtil.decodeDomainName(cnameRecord.content()));
+            nsRecord = (DefaultDnsRawRecord) decoder.decodeRecord(
+                    "netty.github.io", DnsRecordType.NS, DnsRecord.CLASS_IN, 60, buffer, 10, 2);
+            assertEquals("The rdata of NS-type record should be decompressed in advance",
+                         0, ByteBufUtil.compare(buffer.duplicate().setIndex(0, 10), nsRecord.content()));
+            assertEquals("netty.io.", DnsCodecUtil.decodeDomainName(nsRecord.content()));
+        } finally {
+            buffer.release();
+            if (cnameRecord != null) {
+                cnameRecord.release();
+            }
+
+            if (nsRecord != null) {
+                nsRecord.release();
+            }
+        }
+    }
+
     @Test
     public void testDecodeMessageCompression() throws Exception {
         // See https://www.ietf.org/rfc/rfc1035 [4.1.4. Message compression]