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