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 }