Skip to content

Commit

Permalink
Update DNS codec
Browse files Browse the repository at this point in the history
  • Loading branch information
slandelle committed Mar 23, 2016
1 parent d7d18f0 commit 858cac9
Show file tree
Hide file tree
Showing 20 changed files with 538 additions and 152 deletions.
Expand Up @@ -15,9 +15,10 @@
*/
package io.netty.handler.codec.dns;

import io.netty.util.internal.StringUtil;

import java.net.IDN;

import io.netty.util.internal.StringUtil;
import static io.netty.util.internal.ObjectUtil.checkNotNull;

/**
Expand Down Expand Up @@ -62,16 +63,23 @@ protected AbstractDnsRecord(String name, DnsRecordType type, int dnsClass, long
if (timeToLive < 0) {
throw new IllegalArgumentException("timeToLive: " + timeToLive + " (expected: >= 0)");
}
// Convert to ASCII which will also check that the length is not too big.
// Convert to ASCII which will also check that the length is not too big.
// See:
// - https://github.com/netty/netty/issues/4937
// - https://github.com/netty/netty/issues/4935
this.name = IDN.toASCII(checkNotNull(name, "name"));
this.name = appendTrailingDot(IDN.toASCII(checkNotNull(name, "name")));
this.type = checkNotNull(type, "type");
this.dnsClass = (short) dnsClass;
this.timeToLive = timeToLive;
}

private static String appendTrailingDot(String name) {
if (name.length() > 0 && name.charAt(name.length() - 1) != '.') {
return name + '.';
}
return name;
}

@Override
public String name() {
return name;
Expand Down
@@ -0,0 +1,114 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.dns;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.MessageToMessageDecoder;

import java.net.InetSocketAddress;
import java.util.List;

import static io.netty.util.internal.ObjectUtil.checkNotNull;

/**
* Decodes a {@link DatagramPacket} into a {@link DatagramDnsQuery}.
*/
@ChannelHandler.Sharable
public class DatagramDnsQueryDecoder extends MessageToMessageDecoder<DatagramPacket> {

private final DnsRecordDecoder recordDecoder;

/**
* Creates a new decoder with {@linkplain DnsRecordDecoder#DEFAULT the default record decoder}.
*/
public DatagramDnsQueryDecoder() {
this(DnsRecordDecoder.DEFAULT);
}

/**
* Creates a new decoder with the specified {@code recordDecoder}.
*/
public DatagramDnsQueryDecoder(DnsRecordDecoder recordDecoder) {
this.recordDecoder = checkNotNull(recordDecoder, "recordDecoder");
}

@Override
protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List<Object> out) throws Exception {
final ByteBuf buf = packet.content();

final DnsQuery query = newQuery(packet, buf);
boolean success = false;
try {
final int questionCount = buf.readUnsignedShort();
final int answerCount = buf.readUnsignedShort();
final int authorityRecordCount = buf.readUnsignedShort();
final int additionalRecordCount = buf.readUnsignedShort();

decodeQuestions(query, buf, questionCount);
decodeRecords(query, DnsSection.ANSWER, buf, answerCount);
decodeRecords(query, DnsSection.AUTHORITY, buf, authorityRecordCount);
decodeRecords(query, DnsSection.ADDITIONAL, buf, additionalRecordCount);

out.add(query);
success = true;
} finally {
if (!success) {
query.release();
}
}
}

private static DnsQuery newQuery(DatagramPacket packet, ByteBuf buf) {
final int id = buf.readUnsignedShort();

final int flags = buf.readUnsignedShort();
if (flags >> 15 == 1) {
throw new CorruptedFrameException("not a query");
}
final DnsQuery query =
new DatagramDnsQuery(
packet.sender(),
packet.recipient(),
id,
DnsOpCode.valueOf((byte) (flags >> 11 & 0xf)));
query.setRecursionDesired((flags >> 8 & 1) == 1);
query.setZ(flags >> 4 & 0x7);
return query;
}

private void decodeQuestions(DnsQuery query, ByteBuf buf, int questionCount) throws Exception {
for (int i = questionCount; i > 0; i--) {
query.addRecord(DnsSection.QUESTION, recordDecoder.decodeQuestion(buf));
}
}

private void decodeRecords(
DnsQuery query, DnsSection section, ByteBuf buf, int count) throws Exception {
for (int i = count; i > 0; i--) {
final DnsRecord r = recordDecoder.decodeRecord(buf);
if (r == null) {
// Truncated response
break;
}

query.addRecord(section, r);
}
}
}
Expand Up @@ -52,8 +52,8 @@ public DatagramDnsQueryEncoder(DnsRecordEncoder recordEncoder) {

@Override
protected void encode(
ChannelHandlerContext ctx,
AddressedEnvelope<DnsQuery, InetSocketAddress> in, List<Object> out) throws Exception {
ChannelHandlerContext ctx,
AddressedEnvelope<DnsQuery, InetSocketAddress> in, List<Object> out) throws Exception {

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

/**
* Encodes the header that is always 12 bytes long.
*
* @param query
* the query header being encoded
* @param buf
* the buffer the encoded data should be written to
* @param query the query header being encoded
* @param buf the buffer the encoded data should be written to
*/
private static void encodeHeader(DnsQuery query, ByteBuf buf) {
buf.writeShort(query.id());
int flags = 0;
flags |= (query.opCode().byteValue() & 0xFF) << 14;
flags |= query.isRecursionDesired()? 1 << 8 : 0;
if (query.isRecursionDesired()) {
flags |= 1 << 8;
}
buf.writeShort(flags);
buf.writeShort(query.count(DnsSection.QUESTION));
buf.writeShort(0); // answerCount
Expand All @@ -106,14 +106,14 @@ private static void encodeHeader(DnsQuery query, ByteBuf buf) {

private void encodeQuestions(DnsQuery query, ByteBuf buf) throws Exception {
final int count = query.count(DnsSection.QUESTION);
for (int i = 0; i < count; i ++) {
for (int i = 0; i < count; i++) {
recordEncoder.encodeQuestion((DnsQuestion) query.recordAt(DnsSection.QUESTION, i), buf);
}
}

private void encodeRecords(DnsQuery query, DnsSection section, ByteBuf buf) throws Exception {
final int count = query.count(section);
for (int i = 0; i < count; i ++) {
for (int i = 0; i < count; i++) {
recordEncoder.encodeRecord(query.recordAt(section, i), buf);
}
}
Expand Down
Expand Up @@ -51,10 +51,9 @@ public DatagramDnsResponseDecoder(DnsRecordDecoder recordDecoder) {

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

final DnsResponse response = newResponse(sender, buf);
final DnsResponse response = newResponse(packet, buf);
boolean success = false;
try {
final int questionCount = buf.readUnsignedShort();
Expand All @@ -76,7 +75,7 @@ protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List<Obj
}
}

private static DnsResponse newResponse(InetSocketAddress sender, ByteBuf buf) {
private static DnsResponse newResponse(DatagramPacket packet, ByteBuf buf) {
final int id = buf.readUnsignedShort();

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

final DnsResponse response = new DatagramDnsResponse(
sender, null,
id, DnsOpCode.valueOf((byte) (flags >> 11 & 0xf)), DnsResponseCode.valueOf((byte) (flags & 0xf)));
packet.sender(),
packet.recipient(),
id,
DnsOpCode.valueOf((byte) (flags >> 11 & 0xf)), DnsResponseCode.valueOf((byte) (flags & 0xf)));

response.setRecursionDesired((flags >> 8 & 1) == 1);
response.setAuthoritativeAnswer((flags >> 10 & 1) == 1);
Expand Down
@@ -0,0 +1,133 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.dns;

import io.netty.buffer.ByteBuf;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.MessageToMessageEncoder;

import java.net.InetSocketAddress;
import java.util.List;

import static io.netty.util.internal.ObjectUtil.checkNotNull;

/**
* Encodes a {@link DatagramDnsResponse} (or an {@link AddressedEnvelope} of {@link DnsResponse}} into a
* {@link DatagramPacket}.
*/
@ChannelHandler.Sharable
public class DatagramDnsResponseEncoder
extends MessageToMessageEncoder<AddressedEnvelope<DnsResponse, InetSocketAddress>> {

private final DnsRecordEncoder recordEncoder;

/**
* Creates a new encoder with {@linkplain DnsRecordEncoder#DEFAULT the default record encoder}.
*/
public DatagramDnsResponseEncoder() {
this(DnsRecordEncoder.DEFAULT);
}

/**
* Creates a new encoder with the specified {@code recordEncoder}.
*/
public DatagramDnsResponseEncoder(DnsRecordEncoder recordEncoder) {
this.recordEncoder = checkNotNull(recordEncoder, "recordEncoder");
}

@Override
protected void encode(ChannelHandlerContext ctx,
AddressedEnvelope<DnsResponse, InetSocketAddress> in, List<Object> out) throws Exception {

final InetSocketAddress recipient = in.recipient();
final DnsResponse response = in.content();
final ByteBuf buf = allocateBuffer(ctx, in);

boolean success = false;
try {
encodeHeader(response, buf);
encodeQuestions(response, buf);
encodeRecords(response, DnsSection.ANSWER, buf);
encodeRecords(response, DnsSection.AUTHORITY, buf);
encodeRecords(response, DnsSection.ADDITIONAL, buf);
success = true;
} finally {
if (!success) {
buf.release();
}
}

out.add(new DatagramPacket(buf, recipient, null));
}

/**
* Allocate a {@link ByteBuf} which will be used for constructing a datagram packet.
* Sub-classes may override this method to return a {@link ByteBuf} with a perfect matching initial capacity.
*/
protected ByteBuf allocateBuffer(
ChannelHandlerContext ctx,
@SuppressWarnings("unused") AddressedEnvelope<DnsResponse, InetSocketAddress> msg) throws Exception {
return ctx.alloc().ioBuffer(1024);
}

/**
* Encodes the header that is always 12 bytes long.
*
* @param response the response header being encoded
* @param buf the buffer the encoded data should be written to
*/
private static void encodeHeader(DnsResponse response, ByteBuf buf) {
buf.writeShort(response.id());
int flags = 32768;
flags |= (response.opCode().byteValue() & 0xFF) << 11;
if (response.isAuthoritativeAnswer()) {
flags |= 1 << 10;
}
if (response.isTruncated()) {
flags |= 1 << 9;
}
if (response.isRecursionDesired()) {
flags |= 1 << 8;
}
if (response.isRecursionAvailable()) {
flags |= 1 << 7;
}
flags |= response.z() << 4;
flags |= response.code().intValue();
buf.writeShort(flags);
buf.writeShort(response.count(DnsSection.QUESTION));
buf.writeShort(response.count(DnsSection.ANSWER));
buf.writeShort(response.count(DnsSection.AUTHORITY));
buf.writeShort(response.count(DnsSection.ADDITIONAL));
}

private void encodeQuestions(DnsResponse response, ByteBuf buf) throws Exception {
final int count = response.count(DnsSection.QUESTION);
for (int i = 0; i < count; i++) {
recordEncoder.encodeQuestion((DnsQuestion) response.recordAt(DnsSection.QUESTION, i), buf);
}
}

private void encodeRecords(DnsResponse response, DnsSection section, ByteBuf buf) throws Exception {
final int count = response.count(section);
for (int i = 0; i < count; i++) {
recordEncoder.encodeRecord(response.recordAt(section, i), buf);
}
}
}

0 comments on commit 858cac9

Please sign in to comment.