View Javadoc
1   /*
2    * Copyright 2013 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    *   http://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.channel.ChannelHandler;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.channel.socket.DatagramPacket;
22  import io.netty.handler.codec.CorruptedFrameException;
23  import io.netty.handler.codec.MessageToMessageDecoder;
24  import io.netty.util.CharsetUtil;
25  
26  import java.util.List;
27  
28  /**
29   * DnsResponseDecoder accepts {@link io.netty.channel.socket.DatagramPacket} and encodes to
30   * {@link DnsResponse}. This class also contains methods for decoding parts of
31   * DnsResponses such as questions and resource records.
32   */
33  @ChannelHandler.Sharable
34  public class DnsResponseDecoder extends MessageToMessageDecoder<DatagramPacket> {
35  
36      @Override
37      protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List<Object> out) throws Exception {
38          ByteBuf buf = packet.content();
39  
40          int id = buf.readUnsignedShort();
41  
42          DnsResponse response = new DnsResponse(id, packet.sender());
43          DnsResponseHeader header = response.header();
44          int flags = buf.readUnsignedShort();
45          header.setType(flags >> 15);
46          header.setOpcode(flags >> 11 & 0xf);
47          header.setRecursionDesired((flags >> 8 & 1) == 1);
48          header.setAuthoritativeAnswer((flags >> 10 & 1) == 1);
49          header.setTruncated((flags >> 9 & 1) == 1);
50          header.setRecursionAvailable((flags >> 7 & 1) == 1);
51          header.setZ(flags >> 4 & 0x7);
52          header.setResponseCode(DnsResponseCode.valueOf(flags & 0xf));
53  
54          int questions = buf.readUnsignedShort();
55          int answers = buf.readUnsignedShort();
56          int authorities = buf.readUnsignedShort();
57          int additionals = buf.readUnsignedShort();
58  
59          for (int i = 0; i < questions; i++) {
60              response.addQuestion(decodeQuestion(buf));
61          }
62          if (header.responseCode() != DnsResponseCode.NOERROR) {
63              // response code for error
64              out.add(response);
65              return;
66          }
67          boolean release = true;
68          try {
69              for (int i = 0; i < answers; i++) {
70                  response.addAnswer(decodeResource(buf));
71              }
72              for (int i = 0; i < authorities; i++) {
73                  response.addAuthorityResource(decodeResource(buf));
74              }
75              for (int i = 0; i < additionals; i++) {
76                  response.addAdditionalResource(decodeResource(buf));
77              }
78              out.add(response);
79              release = false;
80          } finally {
81              if (release) {
82                  // We need to release te DnsResources in case of an Exception as we called retain() on the buffer.
83                  releaseDnsResources(response.answers());
84                  releaseDnsResources(response.authorityResources());
85                  releaseDnsResources(response.additionalResources());
86              }
87          }
88      }
89  
90      private static void releaseDnsResources(List<DnsResource> resources) {
91          int size = resources.size();
92          for (int i = 0; i < size; i++) {
93              DnsResource resource = resources.get(i);
94              resource.release();
95          }
96      }
97  
98      /**
99       * Retrieves a domain name given a buffer containing a DNS packet. If the
100      * name contains a pointer, the position of the buffer will be set to
101      * directly after the pointer's index after the name has been read.
102      *
103      * @param buf
104      *            the byte buffer containing the DNS packet
105      * @return the domain name for an entry
106      */
107     private static String readName(ByteBuf buf) {
108         int position = -1;
109         int checked = 0;
110         int length = buf.writerIndex();
111         StringBuilder name = new StringBuilder();
112         for (int len = buf.readUnsignedByte(); buf.isReadable() && len != 0; len = buf.readUnsignedByte()) {
113             boolean pointer = (len & 0xc0) == 0xc0;
114             if (pointer) {
115                 if (position == -1) {
116                     position = buf.readerIndex() + 1;
117                 }
118                 buf.readerIndex((len & 0x3f) << 8 | buf.readUnsignedByte());
119                 // check for loops
120                 checked += 2;
121                 if (checked >= length) {
122                     throw new CorruptedFrameException("name contains a loop.");
123                 }
124             } else {
125                 name.append(buf.toString(buf.readerIndex(), len, CharsetUtil.UTF_8)).append('.');
126                 buf.skipBytes(len);
127             }
128         }
129         if (position != -1) {
130             buf.readerIndex(position);
131         }
132         if (name.length() == 0) {
133             return "";
134         }
135 
136         return name.substring(0, name.length() - 1);
137     }
138 
139     /**
140      * Decodes a question, given a DNS packet in a byte buffer.
141      *
142      * @param buf
143      *            the byte buffer containing the DNS packet
144      * @return a decoded {@link DnsQuestion}
145      */
146     private static DnsQuestion decodeQuestion(ByteBuf buf) {
147         String name = readName(buf);
148         DnsType type = DnsType.valueOf(buf.readUnsignedShort());
149         DnsClass qClass = DnsClass.valueOf(buf.readUnsignedShort());
150         return new DnsQuestion(name, type, qClass);
151     }
152 
153     /**
154      * Decodes a resource record, given a DNS packet in a byte buffer.
155      *
156      * @param buf
157      *            the byte buffer containing the DNS packet
158      * @return a {@link DnsResource} record containing response data
159      */
160     private static DnsResource decodeResource(ByteBuf buf) {
161         String name = readName(buf);
162         DnsType type = DnsType.valueOf(buf.readUnsignedShort());
163         DnsClass aClass = DnsClass.valueOf(buf.readUnsignedShort());
164         long ttl = buf.readUnsignedInt();
165         int len = buf.readUnsignedShort();
166 
167         int readerIndex = buf.readerIndex();
168         ByteBuf payload = buf.duplicate().setIndex(readerIndex, readerIndex + len).retain();
169         buf.readerIndex(readerIndex + len);
170         return new DnsResource(name, type, aClass, ttl, payload);
171     }
172 }