View Javadoc
1   /*
2    * Copyright 2015 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.dns;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.Unpooled;
20  import io.netty.handler.codec.CorruptedFrameException;
21  
22  /**
23   * The default {@link DnsRecordDecoder} implementation.
24   *
25   * @see DefaultDnsRecordEncoder
26   */
27  public class DefaultDnsRecordDecoder implements DnsRecordDecoder {
28  
29      static final String ROOT = ".";
30  
31      /**
32       * Creates a new instance.
33       */
34      protected DefaultDnsRecordDecoder() { }
35  
36      @Override
37      public final DnsQuestion decodeQuestion(ByteBuf in) throws Exception {
38          String name = decodeName(in);
39          DnsRecordType type = DnsRecordType.valueOf(in.readUnsignedShort());
40          int qClass = in.readUnsignedShort();
41          return new DefaultDnsQuestion(name, type, qClass);
42      }
43  
44      @Override
45      public final <T extends DnsRecord> T decodeRecord(ByteBuf in) throws Exception {
46          final int startOffset = in.readerIndex();
47          final String name = decodeName(in);
48  
49          final int endOffset = in.writerIndex();
50          if (endOffset - in.readerIndex() < 10) {
51              // Not enough data
52              in.readerIndex(startOffset);
53              return null;
54          }
55  
56          final DnsRecordType type = DnsRecordType.valueOf(in.readUnsignedShort());
57          final int aClass = in.readUnsignedShort();
58          final long ttl = in.readUnsignedInt();
59          final int length = in.readUnsignedShort();
60          final int offset = in.readerIndex();
61  
62          if (endOffset - offset < length) {
63              // Not enough data
64              in.readerIndex(startOffset);
65              return null;
66          }
67  
68          @SuppressWarnings("unchecked")
69          T record = (T) decodeRecord(name, type, aClass, ttl, in, offset, length);
70          in.readerIndex(offset + length);
71          return record;
72      }
73  
74      /**
75       * Decodes a record from the information decoded so far by {@link #decodeRecord(ByteBuf)}.
76       *
77       * @param name the domain name of the record
78       * @param type the type of the record
79       * @param dnsClass the class of the record
80       * @param timeToLive the TTL of the record
81       * @param in the {@link ByteBuf} that contains the RDATA
82       * @param offset the start offset of the RDATA in {@code in}
83       * @param length the length of the RDATA
84       *
85       * @return a {@link DnsRawRecord}. Override this method to decode RDATA and return other record implementation.
86       */
87      protected DnsRecord decodeRecord(
88              String name, DnsRecordType type, int dnsClass, long timeToLive,
89              ByteBuf in, int offset, int length) throws Exception {
90  
91          // DNS message compression means that domain names may contain "pointers" to other positions in the packet
92          // to build a full message. This means the indexes are meaningful and we need the ability to reference the
93          // indexes un-obstructed, and thus we cannot use a slice here.
94          // See https://www.ietf.org/rfc/rfc1035 [4.1.4. Message compression]
95          if (type == DnsRecordType.PTR) {
96              return new DefaultDnsPtrRecord(
97                      name, dnsClass, timeToLive, decodeName0(in.duplicate().setIndex(offset, offset + length)));
98          }
99          if (type == DnsRecordType.CNAME || type == DnsRecordType.NS) {
100             return new DefaultDnsRawRecord(name, type, dnsClass, timeToLive,
101                                            DnsCodecUtil.decompressDomainName(
102                                                    in.duplicate().setIndex(offset, offset + length)));
103         }
104         if (type ==  DnsRecordType.MX) {
105             // MX RDATA: 16-bit preference + exchange (domain name, possibly compressed)
106             if (length < 3) {
107                 throw new CorruptedFrameException("MX record RDATA is too short: " + length);
108             }
109             final int pref = in.getUnsignedShort(offset);
110             ByteBuf exchange = null;
111             try {
112                 exchange = DnsCodecUtil.decompressDomainName(
113                         in.duplicate().setIndex(offset + 2, offset + length));
114 
115                 // Build decompressed RDATA = [preference][expanded exchange name]
116                 final ByteBuf out = in.alloc().buffer(2 + exchange.readableBytes());
117                 out.writeShort(pref);
118                 out.writeBytes(exchange);
119 
120                 return new DefaultDnsRawRecord(name, type, dnsClass, timeToLive, out);
121             } finally {
122                 if (exchange != null) {
123                     exchange.release();
124                 }
125             }
126         }
127 
128         return new DefaultDnsRawRecord(
129                 name, type, dnsClass, timeToLive, in.retainedDuplicate().setIndex(offset, offset + length));
130     }
131 
132     /**
133      * Retrieves a domain name given a buffer containing a DNS packet. If the
134      * name contains a pointer, the position of the buffer will be set to
135      * directly after the pointer's index after the name has been read.
136      *
137      * @param in the byte buffer containing the DNS packet
138      * @return the domain name for an entry
139      */
140     protected String decodeName0(ByteBuf in) {
141         return decodeName(in);
142     }
143 
144     /**
145      * Retrieves a domain name given a buffer containing a DNS packet. If the
146      * name contains a pointer, the position of the buffer will be set to
147      * directly after the pointer's index after the name has been read.
148      *
149      * @param in the byte buffer containing the DNS packet
150      * @return the domain name for an entry
151      */
152     public static String decodeName(ByteBuf in) {
153         return DnsCodecUtil.decodeDomainName(in);
154     }
155 }