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("id: ")
83             .append(msg.id())
84             .append(", ")
85             .append(msg.opCode());
86  
87          if (msg.isRecursionDesired()) {
88              buf.append(", RD");
89          }
90          if (msg.z() != 0) {
91              buf.append(", Z: ")
92                 .append(msg.z());
93          }
94          buf.append(')');
95      }
96  
97      private static void appendResponseHeader(StringBuilder buf, DnsResponse msg) {
98          buf.append(StringUtil.simpleClassName(msg))
99             .append('(');
100 
101         appendAddresses(buf, msg)
102            .append("id: ")
103            .append(msg.id())
104            .append(", ")
105            .append(msg.opCode())
106            .append(", ")
107            .append(msg.code())
108            .append(',');
109 
110         boolean hasComma = true;
111         if (msg.isRecursionDesired()) {
112             hasComma = false;
113             buf.append(" RD");
114         }
115         if (msg.isAuthoritativeAnswer()) {
116             hasComma = false;
117             buf.append(" AA");
118         }
119         if (msg.isTruncated()) {
120             hasComma = false;
121             buf.append(" TC");
122         }
123         if (msg.isRecursionAvailable()) {
124             hasComma = false;
125             buf.append(" RA");
126         }
127         if (msg.z() != 0) {
128             if (!hasComma) {
129                 buf.append(',');
130             }
131             buf.append(" Z: ")
132                .append(msg.z());
133         }
134 
135         if (hasComma) {
136             buf.setCharAt(buf.length() - 1, ')');
137         } else {
138             buf.append(')');
139         }
140     }
141 
142     private static StringBuilder appendAddresses(StringBuilder buf, DnsMessage msg) {
143 
144         if (!(msg instanceof AddressedEnvelope)) {
145             return buf;
146         }
147 
148         @SuppressWarnings("unchecked")
149         AddressedEnvelope<?, SocketAddress> envelope = (AddressedEnvelope<?, SocketAddress>) msg;
150 
151         SocketAddress addr = envelope.sender();
152         if (addr != null) {
153             buf.append("from: ")
154                .append(addr)
155                .append(", ");
156         }
157 
158         addr = envelope.recipient();
159         if (addr != null) {
160             buf.append("to: ")
161                .append(addr)
162                .append(", ");
163         }
164 
165         return buf;
166     }
167 
168     private static void appendAllRecords(StringBuilder buf, DnsMessage msg) {
169         appendRecords(buf, msg, DnsSection.QUESTION);
170         appendRecords(buf, msg, DnsSection.ANSWER);
171         appendRecords(buf, msg, DnsSection.AUTHORITY);
172         appendRecords(buf, msg, DnsSection.ADDITIONAL);
173     }
174 
175     private static void appendRecords(StringBuilder buf, DnsMessage message, DnsSection section) {
176         final int count = message.count(section);
177         for (int i = 0; i < count; i ++) {
178             buf.append(StringUtil.NEWLINE)
179                .append(StringUtil.TAB)
180                .append(message.<DnsRecord>recordAt(section, i));
181         }
182     }
183 
184     static DnsQuery decodeDnsQuery(DnsRecordDecoder decoder, ByteBuf buf, DnsQueryFactory supplier) throws Exception {
185         DnsQuery query = newQuery(buf, supplier);
186         boolean success = false;
187         try {
188             int questionCount = buf.readUnsignedShort();
189             int answerCount = buf.readUnsignedShort();
190             int authorityRecordCount = buf.readUnsignedShort();
191             int additionalRecordCount = buf.readUnsignedShort();
192             decodeQuestions(decoder, query, buf, questionCount);
193             decodeRecords(decoder, query, DnsSection.ANSWER, buf, answerCount);
194             decodeRecords(decoder, query, DnsSection.AUTHORITY, buf, authorityRecordCount);
195             decodeRecords(decoder, query, DnsSection.ADDITIONAL, buf, additionalRecordCount);
196             success = true;
197             return query;
198         } finally {
199             if (!success) {
200                 query.release();
201             }
202         }
203     }
204 
205     private static DnsQuery newQuery(ByteBuf buf, DnsQueryFactory supplier) {
206         int id = buf.readUnsignedShort();
207         int flags = buf.readUnsignedShort();
208         if (flags >> 15 == 1) {
209             throw new CorruptedFrameException("not a query");
210         }
211 
212         DnsQuery query = supplier.newQuery(id, DnsOpCode.valueOf((byte) (flags >> 11 & 0xf)));
213         query.setRecursionDesired((flags >> 8 & 1) == 1);
214         query.setZ(flags >> 4 & 0x7);
215         return query;
216     }
217 
218     private static void decodeQuestions(DnsRecordDecoder decoder,
219                                         DnsQuery query, ByteBuf buf, int questionCount) throws Exception {
220         for (int i = questionCount; i > 0; --i) {
221             query.addRecord(DnsSection.QUESTION, decoder.decodeQuestion(buf));
222         }
223     }
224 
225     private static void decodeRecords(DnsRecordDecoder decoder,
226                                       DnsQuery query, DnsSection section, ByteBuf buf, int count) throws Exception {
227         for (int i = count; i > 0; --i) {
228             DnsRecord r = decoder.decodeRecord(buf);
229             if (r == null) {
230                 break;
231             }
232             query.addRecord(section, r);
233         }
234     }
235 
236     static void encodeDnsResponse(DnsRecordEncoder encoder, DnsResponse response, ByteBuf buf) throws Exception {
237         boolean success = false;
238         try {
239             encodeHeader(response, buf);
240             encodeQuestions(encoder, response, buf);
241             encodeRecords(encoder, response, DnsSection.ANSWER, buf);
242             encodeRecords(encoder, response, DnsSection.AUTHORITY, buf);
243             encodeRecords(encoder, response, DnsSection.ADDITIONAL, buf);
244             success = true;
245         } finally {
246             if (!success) {
247                 buf.release();
248             }
249         }
250     }
251 
252     /**
253      * Encodes the header that is always 12 bytes long.
254      *
255      * @param response the response header being encoded
256      * @param buf      the buffer the encoded data should be written to
257      */
258     private static void encodeHeader(DnsResponse response, ByteBuf buf) {
259         buf.writeShort(response.id());
260         int flags = 32768;
261         flags |= (response.opCode().byteValue() & 0xFF) << 11;
262         if (response.isAuthoritativeAnswer()) {
263             flags |= 1 << 10;
264         }
265         if (response.isTruncated()) {
266             flags |= 1 << 9;
267         }
268         if (response.isRecursionDesired()) {
269             flags |= 1 << 8;
270         }
271         if (response.isRecursionAvailable()) {
272             flags |= 1 << 7;
273         }
274         flags |= response.z() << 4;
275         flags |= response.code().intValue();
276         buf.writeShort(flags);
277         buf.writeShort(response.count(DnsSection.QUESTION));
278         buf.writeShort(response.count(DnsSection.ANSWER));
279         buf.writeShort(response.count(DnsSection.AUTHORITY));
280         buf.writeShort(response.count(DnsSection.ADDITIONAL));
281     }
282 
283     private static void encodeQuestions(DnsRecordEncoder encoder, DnsResponse response, ByteBuf buf) throws Exception {
284         int count = response.count(DnsSection.QUESTION);
285         for (int i = 0; i < count; ++i) {
286             encoder.encodeQuestion(response.<DnsQuestion>recordAt(DnsSection.QUESTION, i), buf);
287         }
288     }
289 
290     private static void encodeRecords(DnsRecordEncoder encoder,
291                                       DnsResponse response, DnsSection section, ByteBuf buf) throws Exception {
292         int count = response.count(section);
293         for (int i = 0; i < count; ++i) {
294             encoder.encodeRecord(response.recordAt(section, i), buf);
295         }
296     }
297 
298     interface DnsQueryFactory {
299         DnsQuery newQuery(int id, DnsOpCode dnsOpCode);
300     }
301 
302     private DnsMessageUtil() {
303     }
304 }