Skip to content

Commit 858cac9

Browse files
committed
Update DNS codec
1 parent d7d18f0 commit 858cac9

20 files changed

+538
-152
lines changed

netty-bp/codec-dns/src/main/java/io/netty/handler/codec/dns/AbstractDnsRecord.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
*/
1616
package io.netty.handler.codec.dns;
1717

18+
import io.netty.util.internal.StringUtil;
19+
1820
import java.net.IDN;
1921

20-
import io.netty.util.internal.StringUtil;
2122
import static io.netty.util.internal.ObjectUtil.checkNotNull;
2223

2324
/**
@@ -62,16 +63,23 @@ protected AbstractDnsRecord(String name, DnsRecordType type, int dnsClass, long
6263
if (timeToLive < 0) {
6364
throw new IllegalArgumentException("timeToLive: " + timeToLive + " (expected: >= 0)");
6465
}
65-
// Convert to ASCII which will also check that the length is not too big.
66+
// Convert to ASCII which will also check that the length is not too big.
6667
// See:
6768
// - https://github.com/netty/netty/issues/4937
6869
// - https://github.com/netty/netty/issues/4935
69-
this.name = IDN.toASCII(checkNotNull(name, "name"));
70+
this.name = appendTrailingDot(IDN.toASCII(checkNotNull(name, "name")));
7071
this.type = checkNotNull(type, "type");
7172
this.dnsClass = (short) dnsClass;
7273
this.timeToLive = timeToLive;
7374
}
7475

76+
private static String appendTrailingDot(String name) {
77+
if (name.length() > 0 && name.charAt(name.length() - 1) != '.') {
78+
return name + '.';
79+
}
80+
return name;
81+
}
82+
7583
@Override
7684
public String name() {
7785
return name;
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
* 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+
25+
import java.net.InetSocketAddress;
26+
import java.util.List;
27+
28+
import static io.netty.util.internal.ObjectUtil.checkNotNull;
29+
30+
/**
31+
* Decodes a {@link DatagramPacket} into a {@link DatagramDnsQuery}.
32+
*/
33+
@ChannelHandler.Sharable
34+
public class DatagramDnsQueryDecoder extends MessageToMessageDecoder<DatagramPacket> {
35+
36+
private final DnsRecordDecoder recordDecoder;
37+
38+
/**
39+
* Creates a new decoder with {@linkplain DnsRecordDecoder#DEFAULT the default record decoder}.
40+
*/
41+
public DatagramDnsQueryDecoder() {
42+
this(DnsRecordDecoder.DEFAULT);
43+
}
44+
45+
/**
46+
* Creates a new decoder with the specified {@code recordDecoder}.
47+
*/
48+
public DatagramDnsQueryDecoder(DnsRecordDecoder recordDecoder) {
49+
this.recordDecoder = checkNotNull(recordDecoder, "recordDecoder");
50+
}
51+
52+
@Override
53+
protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List<Object> out) throws Exception {
54+
final ByteBuf buf = packet.content();
55+
56+
final DnsQuery query = newQuery(packet, buf);
57+
boolean success = false;
58+
try {
59+
final int questionCount = buf.readUnsignedShort();
60+
final int answerCount = buf.readUnsignedShort();
61+
final int authorityRecordCount = buf.readUnsignedShort();
62+
final int additionalRecordCount = buf.readUnsignedShort();
63+
64+
decodeQuestions(query, buf, questionCount);
65+
decodeRecords(query, DnsSection.ANSWER, buf, answerCount);
66+
decodeRecords(query, DnsSection.AUTHORITY, buf, authorityRecordCount);
67+
decodeRecords(query, DnsSection.ADDITIONAL, buf, additionalRecordCount);
68+
69+
out.add(query);
70+
success = true;
71+
} finally {
72+
if (!success) {
73+
query.release();
74+
}
75+
}
76+
}
77+
78+
private static DnsQuery newQuery(DatagramPacket packet, ByteBuf buf) {
79+
final int id = buf.readUnsignedShort();
80+
81+
final int flags = buf.readUnsignedShort();
82+
if (flags >> 15 == 1) {
83+
throw new CorruptedFrameException("not a query");
84+
}
85+
final DnsQuery query =
86+
new DatagramDnsQuery(
87+
packet.sender(),
88+
packet.recipient(),
89+
id,
90+
DnsOpCode.valueOf((byte) (flags >> 11 & 0xf)));
91+
query.setRecursionDesired((flags >> 8 & 1) == 1);
92+
query.setZ(flags >> 4 & 0x7);
93+
return query;
94+
}
95+
96+
private void decodeQuestions(DnsQuery query, ByteBuf buf, int questionCount) throws Exception {
97+
for (int i = questionCount; i > 0; i--) {
98+
query.addRecord(DnsSection.QUESTION, recordDecoder.decodeQuestion(buf));
99+
}
100+
}
101+
102+
private void decodeRecords(
103+
DnsQuery query, DnsSection section, ByteBuf buf, int count) throws Exception {
104+
for (int i = count; i > 0; i--) {
105+
final DnsRecord r = recordDecoder.decodeRecord(buf);
106+
if (r == null) {
107+
// Truncated response
108+
break;
109+
}
110+
111+
query.addRecord(section, r);
112+
}
113+
}
114+
}

netty-bp/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQueryEncoder.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ public DatagramDnsQueryEncoder(DnsRecordEncoder recordEncoder) {
5252

5353
@Override
5454
protected void encode(
55-
ChannelHandlerContext ctx,
56-
AddressedEnvelope<DnsQuery, InetSocketAddress> in, List<Object> out) throws Exception {
55+
ChannelHandlerContext ctx,
56+
AddressedEnvelope<DnsQuery, InetSocketAddress> in, List<Object> out) throws Exception {
5757

5858
final InetSocketAddress recipient = in.recipient();
5959
final DnsQuery query = in.content();
@@ -79,24 +79,24 @@ protected void encode(
7979
* Sub-classes may override this method to return a {@link ByteBuf} with a perfect matching initial capacity.
8080
*/
8181
protected ByteBuf allocateBuffer(
82-
ChannelHandlerContext ctx,
83-
@SuppressWarnings("unused") AddressedEnvelope<DnsQuery, InetSocketAddress> msg) throws Exception {
82+
ChannelHandlerContext ctx,
83+
@SuppressWarnings("unused") AddressedEnvelope<DnsQuery, InetSocketAddress> msg) throws Exception {
8484
return ctx.alloc().ioBuffer(1024);
8585
}
8686

8787
/**
8888
* Encodes the header that is always 12 bytes long.
8989
*
90-
* @param query
91-
* the query header being encoded
92-
* @param buf
93-
* the buffer the encoded data should be written to
90+
* @param query the query header being encoded
91+
* @param buf the buffer the encoded data should be written to
9492
*/
9593
private static void encodeHeader(DnsQuery query, ByteBuf buf) {
9694
buf.writeShort(query.id());
9795
int flags = 0;
9896
flags |= (query.opCode().byteValue() & 0xFF) << 14;
99-
flags |= query.isRecursionDesired()? 1 << 8 : 0;
97+
if (query.isRecursionDesired()) {
98+
flags |= 1 << 8;
99+
}
100100
buf.writeShort(flags);
101101
buf.writeShort(query.count(DnsSection.QUESTION));
102102
buf.writeShort(0); // answerCount
@@ -106,14 +106,14 @@ private static void encodeHeader(DnsQuery query, ByteBuf buf) {
106106

107107
private void encodeQuestions(DnsQuery query, ByteBuf buf) throws Exception {
108108
final int count = query.count(DnsSection.QUESTION);
109-
for (int i = 0; i < count; i ++) {
109+
for (int i = 0; i < count; i++) {
110110
recordEncoder.encodeQuestion((DnsQuestion) query.recordAt(DnsSection.QUESTION, i), buf);
111111
}
112112
}
113113

114114
private void encodeRecords(DnsQuery query, DnsSection section, ByteBuf buf) throws Exception {
115115
final int count = query.count(section);
116-
for (int i = 0; i < count; i ++) {
116+
for (int i = 0; i < count; i++) {
117117
recordEncoder.encodeRecord(query.recordAt(section, i), buf);
118118
}
119119
}

netty-bp/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsResponseDecoder.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,9 @@ public DatagramDnsResponseDecoder(DnsRecordDecoder recordDecoder) {
5151

5252
@Override
5353
protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List<Object> out) throws Exception {
54-
final InetSocketAddress sender = packet.sender();
5554
final ByteBuf buf = packet.content();
5655

57-
final DnsResponse response = newResponse(sender, buf);
56+
final DnsResponse response = newResponse(packet, buf);
5857
boolean success = false;
5958
try {
6059
final int questionCount = buf.readUnsignedShort();
@@ -76,7 +75,7 @@ protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List<Obj
7675
}
7776
}
7877

79-
private static DnsResponse newResponse(InetSocketAddress sender, ByteBuf buf) {
78+
private static DnsResponse newResponse(DatagramPacket packet, ByteBuf buf) {
8079
final int id = buf.readUnsignedShort();
8180

8281
final int flags = buf.readUnsignedShort();
@@ -85,8 +84,10 @@ private static DnsResponse newResponse(InetSocketAddress sender, ByteBuf buf) {
8584
}
8685

8786
final DnsResponse response = new DatagramDnsResponse(
88-
sender, null,
89-
id, DnsOpCode.valueOf((byte) (flags >> 11 & 0xf)), DnsResponseCode.valueOf((byte) (flags & 0xf)));
87+
packet.sender(),
88+
packet.recipient(),
89+
id,
90+
DnsOpCode.valueOf((byte) (flags >> 11 & 0xf)), DnsResponseCode.valueOf((byte) (flags & 0xf)));
9091

9192
response.setRecursionDesired((flags >> 8 & 1) == 1);
9293
response.setAuthoritativeAnswer((flags >> 10 & 1) == 1);
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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+
* 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.AddressedEnvelope;
20+
import io.netty.channel.ChannelHandler;
21+
import io.netty.channel.ChannelHandlerContext;
22+
import io.netty.channel.socket.DatagramPacket;
23+
import io.netty.handler.codec.MessageToMessageEncoder;
24+
25+
import java.net.InetSocketAddress;
26+
import java.util.List;
27+
28+
import static io.netty.util.internal.ObjectUtil.checkNotNull;
29+
30+
/**
31+
* Encodes a {@link DatagramDnsResponse} (or an {@link AddressedEnvelope} of {@link DnsResponse}} into a
32+
* {@link DatagramPacket}.
33+
*/
34+
@ChannelHandler.Sharable
35+
public class DatagramDnsResponseEncoder
36+
extends MessageToMessageEncoder<AddressedEnvelope<DnsResponse, InetSocketAddress>> {
37+
38+
private final DnsRecordEncoder recordEncoder;
39+
40+
/**
41+
* Creates a new encoder with {@linkplain DnsRecordEncoder#DEFAULT the default record encoder}.
42+
*/
43+
public DatagramDnsResponseEncoder() {
44+
this(DnsRecordEncoder.DEFAULT);
45+
}
46+
47+
/**
48+
* Creates a new encoder with the specified {@code recordEncoder}.
49+
*/
50+
public DatagramDnsResponseEncoder(DnsRecordEncoder recordEncoder) {
51+
this.recordEncoder = checkNotNull(recordEncoder, "recordEncoder");
52+
}
53+
54+
@Override
55+
protected void encode(ChannelHandlerContext ctx,
56+
AddressedEnvelope<DnsResponse, InetSocketAddress> in, List<Object> out) throws Exception {
57+
58+
final InetSocketAddress recipient = in.recipient();
59+
final DnsResponse response = in.content();
60+
final ByteBuf buf = allocateBuffer(ctx, in);
61+
62+
boolean success = false;
63+
try {
64+
encodeHeader(response, buf);
65+
encodeQuestions(response, buf);
66+
encodeRecords(response, DnsSection.ANSWER, buf);
67+
encodeRecords(response, DnsSection.AUTHORITY, buf);
68+
encodeRecords(response, DnsSection.ADDITIONAL, buf);
69+
success = true;
70+
} finally {
71+
if (!success) {
72+
buf.release();
73+
}
74+
}
75+
76+
out.add(new DatagramPacket(buf, recipient, null));
77+
}
78+
79+
/**
80+
* Allocate a {@link ByteBuf} which will be used for constructing a datagram packet.
81+
* Sub-classes may override this method to return a {@link ByteBuf} with a perfect matching initial capacity.
82+
*/
83+
protected ByteBuf allocateBuffer(
84+
ChannelHandlerContext ctx,
85+
@SuppressWarnings("unused") AddressedEnvelope<DnsResponse, InetSocketAddress> msg) throws Exception {
86+
return ctx.alloc().ioBuffer(1024);
87+
}
88+
89+
/**
90+
* Encodes the header that is always 12 bytes long.
91+
*
92+
* @param response the response header being encoded
93+
* @param buf the buffer the encoded data should be written to
94+
*/
95+
private static void encodeHeader(DnsResponse response, ByteBuf buf) {
96+
buf.writeShort(response.id());
97+
int flags = 32768;
98+
flags |= (response.opCode().byteValue() & 0xFF) << 11;
99+
if (response.isAuthoritativeAnswer()) {
100+
flags |= 1 << 10;
101+
}
102+
if (response.isTruncated()) {
103+
flags |= 1 << 9;
104+
}
105+
if (response.isRecursionDesired()) {
106+
flags |= 1 << 8;
107+
}
108+
if (response.isRecursionAvailable()) {
109+
flags |= 1 << 7;
110+
}
111+
flags |= response.z() << 4;
112+
flags |= response.code().intValue();
113+
buf.writeShort(flags);
114+
buf.writeShort(response.count(DnsSection.QUESTION));
115+
buf.writeShort(response.count(DnsSection.ANSWER));
116+
buf.writeShort(response.count(DnsSection.AUTHORITY));
117+
buf.writeShort(response.count(DnsSection.ADDITIONAL));
118+
}
119+
120+
private void encodeQuestions(DnsResponse response, ByteBuf buf) throws Exception {
121+
final int count = response.count(DnsSection.QUESTION);
122+
for (int i = 0; i < count; i++) {
123+
recordEncoder.encodeQuestion((DnsQuestion) response.recordAt(DnsSection.QUESTION, i), buf);
124+
}
125+
}
126+
127+
private void encodeRecords(DnsResponse response, DnsSection section, ByteBuf buf) throws Exception {
128+
final int count = response.count(section);
129+
for (int i = 0; i < count; i++) {
130+
recordEncoder.encodeRecord(response.recordAt(section, i), buf);
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)