From b9ae48589b59e4e5f90f73057c36de5d614556dc Mon Sep 17 00:00:00 2001 From: Yeti Sno Date: Sun, 10 Jan 2016 21:55:07 +0800 Subject: [PATCH] Make codec-dns can support build a dns server, reply answer from client. Motivation: codec-dns has great function to solve dns packet, but only make a query, not answer query from other client. i make a change of add two classes to fill last pieces of map, finish the server function. Modifications: in this change, add two classes of DatagramDnsQueryDecoder and DatagramDnsResponseEncoder to handle client query, reply answer. Result: nothing code change after this commit, except two new classes. --- .../codec/dns/DatagramDnsQueryDecoder.java | 114 +++++++++++++++ .../codec/dns/DatagramDnsQueryEncoder.java | 22 +-- .../codec/dns/DatagramDnsResponseDecoder.java | 11 +- .../codec/dns/DatagramDnsResponseEncoder.java | 133 ++++++++++++++++++ 4 files changed, 264 insertions(+), 16 deletions(-) create mode 100644 codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQueryDecoder.java create mode 100644 codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsResponseEncoder.java diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQueryDecoder.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQueryDecoder.java new file mode 100644 index 0000000000..c932075572 --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQueryDecoder.java @@ -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 { + + 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 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); + } + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQueryEncoder.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQueryEncoder.java index 62b607324b..8344801a45 100644 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQueryEncoder.java +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQueryEncoder.java @@ -52,8 +52,8 @@ public class DatagramDnsQueryEncoder extends MessageToMessageEncoder in, List out) throws Exception { + ChannelHandlerContext ctx, + AddressedEnvelope in, List out) throws Exception { final InetSocketAddress recipient = in.recipient(); final DnsQuery query = in.content(); @@ -79,24 +79,24 @@ public class DatagramDnsQueryEncoder extends MessageToMessageEncoder msg) throws Exception { + ChannelHandlerContext ctx, + @SuppressWarnings("unused") AddressedEnvelope 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 @@ -106,14 +106,14 @@ public class DatagramDnsQueryEncoder extends MessageToMessageEncoder 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(); @@ -76,7 +75,7 @@ public class DatagramDnsResponseDecoder extends MessageToMessageDecoder> 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); diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsResponseEncoder.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsResponseEncoder.java new file mode 100644 index 0000000000..ac7d909156 --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsResponseEncoder.java @@ -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> { + + 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 in, List 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 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); + } + } +}