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