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.channel.AddressedEnvelope;
20  import io.netty.handler.codec.CorruptedFrameException;
21  import io.netty.util.internal.StringUtil;
22  
23  import java.net.SocketAddress;
24  
25  /**
26   * Provides some utility methods for DNS message implementations.
27   */
28  final class DnsMessageUtil {
29  
30      static StringBuilder appendQuery(StringBuilder buf, DnsQuery query) {
31          appendQueryHeader(buf, query);
32          appendAllRecords(buf, query);
33          return buf;
34      }
35  
36      static StringBuilder appendResponse(StringBuilder buf, DnsResponse response) {
37          appendResponseHeader(buf, response);
38          appendAllRecords(buf, response);
39          return buf;
40      }
41  
42      static StringBuilder appendRecordClass(StringBuilder buf, int dnsClass) {
43          final String name;
44          switch (dnsClass &= 0xFFFF) {
45          case DnsRecord.CLASS_IN:
46              name = "IN";
47              break;
48          case DnsRecord.CLASS_CSNET:
49              name = "CSNET";
50              break;
51          case DnsRecord.CLASS_CHAOS:
52              name = "CHAOS";
53              break;
54          case DnsRecord.CLASS_HESIOD:
55              name = "HESIOD";
56              break;
57          case DnsRecord.CLASS_NONE:
58              name = "NONE";
59              break;
60          case DnsRecord.CLASS_ANY:
61              name = "ANY";
62              break;
63          default:
64              name = null;
65              break;
66          }
67  
68          if (name != null) {
69              buf.append(name);
70          } else {
71              buf.append("UNKNOWN(").append(dnsClass).append(')');
72          }
73  
74          return buf;
75      }
76  
77      private static void appendQueryHeader(StringBuilder buf, DnsQuery msg) {
78          buf.append(StringUtil.simpleClassName(msg))
79             .append('(');
80  
81          appendAddresses(buf, msg)
82             .append(msg.id())
83             .append(", ")
84             .append(msg.opCode());
85  
86          if (msg.isRecursionDesired()) {
87              buf.append(", RD");
88          }
89          if (msg.z() != 0) {
90              buf.append(", Z: ")
91                 .append(msg.z());
92          }
93          buf.append(')');
94      }
95  
96      private static void appendResponseHeader(StringBuilder buf, DnsResponse msg) {
97          buf.append(StringUtil.simpleClassName(msg))
98             .append('(');
99  
100         appendAddresses(buf, msg)
101            .append(msg.id())
102            .append(", ")
103            .append(msg.opCode())
104            .append(", ")
105            .append(msg.code())
106            .append(',');
107 
108         boolean hasComma = true;
109         if (msg.isRecursionDesired()) {
110             hasComma = false;
111             buf.append(" RD");
112         }
113         if (msg.isAuthoritativeAnswer()) {
114             hasComma = false;
115             buf.append(" AA");
116         }
117         if (msg.isTruncated()) {
118             hasComma = false;
119             buf.append(" TC");
120         }
121         if (msg.isRecursionAvailable()) {
122             hasComma = false;
123             buf.append(" RA");
124         }
125         if (msg.z() != 0) {
126             if (!hasComma) {
127                 buf.append(',');
128             }
129             buf.append(" Z: ")
130                .append(msg.z());
131         }
132 
133         if (hasComma) {
134             buf.setCharAt(buf.length() - 1, ')');
135         } else {
136             buf.append(')');
137         }
138     }
139 
140     private static StringBuilder appendAddresses(StringBuilder buf, DnsMessage msg) {
141 
142         if (!(msg instanceof AddressedEnvelope)) {
143             return buf;
144         }
145 
146         @SuppressWarnings("unchecked")
147         AddressedEnvelope<?, SocketAddress> envelope = (AddressedEnvelope<?, SocketAddress>) msg;
148 
149         SocketAddress addr = envelope.sender();
150         if (addr != null) {
151             buf.append("from: ")
152                .append(addr)
153                .append(", ");
154         }
155 
156         addr = envelope.recipient();
157         if (addr != null) {
158             buf.append("to: ")
159                .append(addr)
160                .append(", ");
161         }
162 
163         return buf;
164     }
165 
166     private static void appendAllRecords(StringBuilder buf, DnsMessage msg) {
167         appendRecords(buf, msg, DnsSection.QUESTION);
168         appendRecords(buf, msg, DnsSection.ANSWER);
169         appendRecords(buf, msg, DnsSection.AUTHORITY);
170         appendRecords(buf, msg, DnsSection.ADDITIONAL);
171     }
172 
173     private static void appendRecords(StringBuilder buf, DnsMessage message, DnsSection section) {
174         final int count = message.count(section);
175         for (int i = 0; i < count; i ++) {
176             buf.append(StringUtil.NEWLINE)
177                .append(StringUtil.TAB)
178                .append(message.<DnsRecord>recordAt(section, i));
179         }
180     }
181 
182     static DnsQuery decodeDnsQuery(DnsRecordDecoder decoder, ByteBuf buf, DnsQueryFactory supplier) throws Exception {
183         DnsQuery query = newQuery(buf, supplier);
184         boolean success = false;
185         try {
186             int questionCount = buf.readUnsignedShort();
187             int answerCount = buf.readUnsignedShort();
188             int authorityRecordCount = buf.readUnsignedShort();
189             int additionalRecordCount = buf.readUnsignedShort();
190             decodeQuestions(decoder, query, buf, questionCount);
191             decodeRecords(decoder, query, DnsSection.ANSWER, buf, answerCount);
192             decodeRecords(decoder, query, DnsSection.AUTHORITY, buf, authorityRecordCount);
193             decodeRecords(decoder, query, DnsSection.ADDITIONAL, buf, additionalRecordCount);
194             success = true;
195             return query;
196         } finally {
197             if (!success) {
198                 query.release();
199             }
200         }
201     }
202 
203     private static DnsQuery newQuery(ByteBuf buf, DnsQueryFactory supplier) {
204         int id = buf.readUnsignedShort();
205         int flags = buf.readUnsignedShort();
206         if (flags >> 15 == 1) {
207             throw new CorruptedFrameException("not a query");
208         }
209 
210         DnsQuery query = supplier.newQuery(id, DnsOpCode.valueOf((byte) (flags >> 11 & 0xf)));
211         query.setRecursionDesired((flags >> 8 & 1) == 1);
212         query.setZ(flags >> 4 & 0x7);
213         return query;
214     }
215 
216     private static void decodeQuestions(DnsRecordDecoder decoder,
217                                         DnsQuery query, ByteBuf buf, int questionCount) throws Exception {
218         for (int i = questionCount; i > 0; --i) {
219             query.addRecord(DnsSection.QUESTION, decoder.decodeQuestion(buf));
220         }
221     }
222 
223     private static void decodeRecords(DnsRecordDecoder decoder,
224                                       DnsQuery query, DnsSection section, ByteBuf buf, int count) throws Exception {
225         for (int i = count; i > 0; --i) {
226             DnsRecord r = decoder.decodeRecord(buf);
227             if (r == null) {
228                 break;
229             }
230             query.addRecord(section, r);
231         }
232     }
233 
234     static void encodeDnsResponse(DnsRecordEncoder encoder, DnsResponse response, ByteBuf buf) throws Exception {
235         boolean success = false;
236         try {
237             encodeHeader(response, buf);
238             encodeQuestions(encoder, response, buf);
239             encodeRecords(encoder, response, DnsSection.ANSWER, buf);
240             encodeRecords(encoder, response, DnsSection.AUTHORITY, buf);
241             encodeRecords(encoder, response, DnsSection.ADDITIONAL, buf);
242             success = true;
243         } finally {
244             if (!success) {
245                 buf.release();
246             }
247         }
248     }
249 
250     /**
251      * Encodes the header that is always 12 bytes long.
252      *
253      * @param response the response header being encoded
254      * @param buf      the buffer the encoded data should be written to
255      */
256     private static void encodeHeader(DnsResponse response, ByteBuf buf) {
257         buf.writeShort(response.id());
258         int flags = 32768;
259         flags |= (response.opCode().byteValue() & 0xFF) << 11;
260         if (response.isAuthoritativeAnswer()) {
261             flags |= 1 << 10;
262         }
263         if (response.isTruncated()) {
264             flags |= 1 << 9;
265         }
266         if (response.isRecursionDesired()) {
267             flags |= 1 << 8;
268         }
269         if (response.isRecursionAvailable()) {
270             flags |= 1 << 7;
271         }
272         flags |= response.z() << 4;
273         flags |= response.code().intValue();
274         buf.writeShort(flags);
275         buf.writeShort(response.count(DnsSection.QUESTION));
276         buf.writeShort(response.count(DnsSection.ANSWER));
277         buf.writeShort(response.count(DnsSection.AUTHORITY));
278         buf.writeShort(response.count(DnsSection.ADDITIONAL));
279     }
280 
281     private static void encodeQuestions(DnsRecordEncoder encoder, DnsResponse response, ByteBuf buf) throws Exception {
282         int count = response.count(DnsSection.QUESTION);
283         for (int i = 0; i < count; ++i) {
284             encoder.encodeQuestion(response.<DnsQuestion>recordAt(DnsSection.QUESTION, i), buf);
285         }
286     }
287 
288     private static void encodeRecords(DnsRecordEncoder encoder,
289                                       DnsResponse response, DnsSection section, ByteBuf buf) throws Exception {
290         int count = response.count(section);
291         for (int i = 0; i < count; ++i) {
292             encoder.encodeRecord(response.recordAt(section, i), buf);
293         }
294     }
295 
296     interface DnsQueryFactory {
297         DnsQuery newQuery(int id, DnsOpCode dnsOpCode);
298     }
299 
300     private DnsMessageUtil() {
301     }
302 }