diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/AbstractDnsMessage.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/AbstractDnsMessage.java new file mode 100644 index 0000000000..40b5aa8f53 --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/AbstractDnsMessage.java @@ -0,0 +1,465 @@ +/* + * 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.util.AbstractReferenceCounted; +import io.netty.util.ReferenceCountUtil; +import io.netty.util.ReferenceCounted; +import io.netty.util.ResourceLeak; +import io.netty.util.ResourceLeakDetector; +import io.netty.util.internal.StringUtil; + +import java.util.ArrayList; +import java.util.List; + +import static io.netty.util.internal.ObjectUtil.checkNotNull; + +/** + * A skeletal implementation of {@link DnsMessage}. + */ +public abstract class AbstractDnsMessage extends AbstractReferenceCounted implements DnsMessage { + + private static final ResourceLeakDetector leakDetector = + new ResourceLeakDetector(DnsMessage.class); + + private static final int SECTION_QUESTION = DnsSection.QUESTION.ordinal(); + private static final int SECTION_COUNT = 4; + + private final ResourceLeak leak = leakDetector.open(this); + private short id; + private DnsOpCode opCode; + private boolean recursionDesired; + private byte z; + + // To reduce the memory footprint of a message, + // each of the following fields is a single record or a list of records. + private Object questions; + private Object answers; + private Object authorities; + private Object additionals; + + /** + * Creates a new instance with the specified {@code id} and {@link DnsOpCode#QUERY} opCode. + */ + protected AbstractDnsMessage(int id) { + this(id, DnsOpCode.QUERY); + } + + /** + * Creates a new instance with the specified {@code id} and {@code opCode}. + */ + protected AbstractDnsMessage(int id, DnsOpCode opCode) { + setId(id); + setOpCode(opCode); + } + + @Override + public int id() { + return id & 0xFFFF; + } + + @Override + public DnsMessage setId(int id) { + this.id = (short) id; + return this; + } + + @Override + public DnsOpCode opCode() { + return opCode; + } + + @Override + public DnsMessage setOpCode(DnsOpCode opCode) { + this.opCode = checkNotNull(opCode, "opCode"); + return this; + } + + @Override + public boolean isRecursionDesired() { + return recursionDesired; + } + + @Override + public DnsMessage setRecursionDesired(boolean recursionDesired) { + this.recursionDesired = recursionDesired; + return this; + } + + @Override + public int z() { + return z; + } + + @Override + public DnsMessage setZ(int z) { + this.z = (byte) (z & 7); + return this; + } + + @Override + public int count(DnsSection section) { + return count(sectionOrdinal(section)); + } + + private int count(int section) { + final Object records = sectionAt(section); + if (records == null) { + return 0; + } + if (records instanceof DnsRecord) { + return 1; + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + return recordList.size(); + } + + @Override + public int count() { + int count = 0; + for (int i = 0; i < SECTION_COUNT; i ++) { + count += count(i); + } + return count; + } + + @Override + public T recordAt(DnsSection section) { + return recordAt(sectionOrdinal(section)); + } + + private T recordAt(int section) { + final Object records = sectionAt(section); + if (records == null) { + return null; + } + + if (records instanceof DnsRecord) { + return castRecord(records); + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + if (recordList.isEmpty()) { + return null; + } + + return castRecord(recordList.get(0)); + } + + @Override + public T recordAt(DnsSection section, int index) { + return recordAt(sectionOrdinal(section), index); + } + + private T recordAt(int section, int index) { + final Object records = sectionAt(section); + if (records == null) { + throw new IndexOutOfBoundsException("index: " + index + " (expected: none)"); + } + + if (records instanceof DnsRecord) { + if (index == 0) { + return castRecord(records); + } else { + throw new IndexOutOfBoundsException("index: " + index + "' (expected: 0)"); + } + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + return castRecord(recordList.get(index)); + } + + @Override + public DnsMessage setRecord(DnsSection section, DnsRecord record) { + setRecord(sectionOrdinal(section), record); + return this; + } + + private void setRecord(int section, DnsRecord record) { + clear(section); + setSection(section, checkQuestion(section, record)); + } + + @Override + public T setRecord(DnsSection section, int index, DnsRecord record) { + return setRecord(sectionOrdinal(section), index, record); + } + + private T setRecord(int section, int index, DnsRecord record) { + checkQuestion(section, record); + + final Object records = sectionAt(section); + if (records == null) { + throw new IndexOutOfBoundsException("index: " + index + " (expected: none)"); + } + + if (records instanceof DnsRecord) { + if (index == 0) { + setSection(section, record); + return castRecord(records); + } else { + throw new IndexOutOfBoundsException("index: " + index + " (expected: 0)"); + } + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + return castRecord(recordList.set(index, record)); + } + + @Override + public DnsMessage addRecord(DnsSection section, DnsRecord record) { + addRecord(sectionOrdinal(section), record); + return this; + } + + private void addRecord(int section, DnsRecord record) { + checkQuestion(section, record); + + final Object records = sectionAt(section); + if (records == null) { + setSection(section, record); + return; + } + + if (records instanceof DnsRecord) { + final List recordList = newRecordList(); + recordList.add(castRecord(records)); + recordList.add(record); + setSection(section, recordList); + return; + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + recordList.add(record); + } + + @Override + public DnsMessage addRecord(DnsSection section, int index, DnsRecord record) { + addRecord(sectionOrdinal(section), index, record); + return this; + } + + private void addRecord(int section, int index, DnsRecord record) { + checkQuestion(section, record); + + final Object records = sectionAt(section); + if (records == null) { + if (index != 0) { + throw new IndexOutOfBoundsException("index: " + index + " (expected: 0)"); + } + + setSection(section, record); + return; + } + + if (records instanceof DnsRecord) { + final List recordList; + if (index == 0) { + recordList = newRecordList(); + recordList.add(record); + recordList.add(castRecord(records)); + } else if (index == 1) { + recordList = newRecordList(); + recordList.add(castRecord(records)); + recordList.add(record); + } else { + throw new IndexOutOfBoundsException("index: " + index + " (expected: 0 or 1)"); + } + setSection(section, recordList); + return; + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + recordList.add(index, record); + } + + @Override + public T removeRecord(DnsSection section, int index) { + return removeRecord(sectionOrdinal(section), index); + } + + private T removeRecord(int section, int index) { + final Object records = sectionAt(section); + if (records == null) { + throw new IndexOutOfBoundsException("index: " + index + " (expected: none)"); + } + + if (records instanceof DnsRecord) { + if (index != 0) { + throw new IndexOutOfBoundsException("index: " + index + " (expected: 0)"); + } + + T record = castRecord(records); + setSection(section, null); + return record; + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + return castRecord(recordList.remove(index)); + } + + @Override + public DnsMessage clear(DnsSection section) { + clear(sectionOrdinal(section)); + return this; + } + + @Override + public DnsMessage clear() { + for (int i = 0; i < SECTION_COUNT; i ++) { + clear(i); + } + return this; + } + + private void clear(int section) { + final Object recordOrList = sectionAt(section); + setSection(section, null); + if (recordOrList instanceof ReferenceCounted) { + ((ReferenceCounted) recordOrList).release(); + } else if (recordOrList instanceof List) { + @SuppressWarnings("unchecked") + List list = (List) recordOrList; + if (!list.isEmpty()) { + for (Object r : list) { + ReferenceCountUtil.release(r); + } + } + } + } + + @Override + public DnsMessage touch() { + return (DnsMessage) super.touch(); + } + + @Override + public DnsMessage touch(Object hint) { + if (leak != null) { + leak.record(hint); + } + return this; + } + + @Override + public DnsMessage retain() { + return (DnsMessage) super.retain(); + } + + @Override + public DnsMessage retain(int increment) { + return (DnsMessage) super.retain(increment); + } + + @Override + protected void deallocate() { + clear(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof DnsMessage)) { + return false; + } + + final DnsMessage that = (DnsMessage) obj; + if (id() != that.id()) { + return false; + } + + if (this instanceof DnsQuery) { + if (!(that instanceof DnsQuery)) { + return false; + } + } else if (that instanceof DnsQuery) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return id() * 31 + (this instanceof DnsQuery? 0 : 1); + } + + private Object sectionAt(int section) { + switch (section) { + case 0: + return questions; + case 1: + return answers; + case 2: + return authorities; + case 3: + return additionals; + } + + throw new Error(); // Should never reach here. + } + + private void setSection(int section, Object value) { + switch (section) { + case 0: + questions = value; + return; + case 1: + answers = value; + return; + case 2: + authorities = value; + return; + case 3: + additionals = value; + return; + } + + throw new Error(); // Should never reach here. + } + + private static int sectionOrdinal(DnsSection section) { + return checkNotNull(section, "section").ordinal(); + } + + private static DnsRecord checkQuestion(int section, DnsRecord record) { + if (section == SECTION_QUESTION && !(checkNotNull(record, "record") instanceof DnsQuestion)) { + throw new IllegalArgumentException( + "record: " + record + " (expected: " + StringUtil.simpleClassName(DnsQuestion.class) + ')'); + } + return record; + } + + @SuppressWarnings("unchecked") + private static T castRecord(Object record) { + return (T) record; + } + + private static ArrayList newRecordList() { + return new ArrayList(2); + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/AbstractDnsRecord.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/AbstractDnsRecord.java new file mode 100644 index 0000000000..6a62077bbf --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/AbstractDnsRecord.java @@ -0,0 +1,139 @@ +/* + * 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.util.internal.StringUtil; + +import static io.netty.util.internal.ObjectUtil.checkNotNull; + +/** + * A skeletal implementation of {@link DnsRecord}. + */ +public abstract class AbstractDnsRecord implements DnsRecord { + + private final String name; + private final DnsRecordType type; + private final short dnsClass; + private final long timeToLive; + private int hashCode; + + /** + * Creates a new {@link #CLASS_IN IN-class} record. + * + * @param name the domain name + * @param type the type of the record + * @param timeToLive the TTL value of the record + */ + protected AbstractDnsRecord(String name, DnsRecordType type, long timeToLive) { + this(name, type, CLASS_IN, timeToLive); + } + + /** + * Creates a new record. + * + * @param name the domain name + * @param type the type of the record + * @param dnsClass the class of the record, usually one of the following: + *
    + *
  • {@link #CLASS_IN}
  • + *
  • {@link #CLASS_CSNET}
  • + *
  • {@link #CLASS_CHAOS}
  • + *
  • {@link #CLASS_HESIOD}
  • + *
  • {@link #CLASS_NONE}
  • + *
  • {@link #CLASS_ANY}
  • + *
+ * @param timeToLive the TTL value of the record + */ + protected AbstractDnsRecord(String name, DnsRecordType type, int dnsClass, long timeToLive) { + if (timeToLive < 0) { + throw new IllegalArgumentException("timeToLive: " + timeToLive + " (expected: >= 0)"); + } + this.name = checkNotNull(name, "name"); + this.type = checkNotNull(type, "type"); + this.dnsClass = (short) dnsClass; + this.timeToLive = timeToLive; + } + + @Override + public String name() { + return name; + } + + @Override + public DnsRecordType type() { + return type; + } + + @Override + public int dnsClass() { + return dnsClass & 0xFFFF; + } + + @Override + public long timeToLive() { + return timeToLive; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof DnsRecord)) { + return false; + } + + final DnsRecord that = (DnsRecord) obj; + final int hashCode = this.hashCode; + if (hashCode != 0 && hashCode != that.hashCode()) { + return false; + } + + return type().intValue() == that.type().intValue() && + dnsClass() == that.dnsClass() && + name().equals(that.name()); + } + + @Override + public int hashCode() { + final int hashCode = this.hashCode; + if (hashCode != 0) { + return hashCode; + } + + return this.hashCode = name.hashCode() * 31 + type().intValue() * 31 + dnsClass(); + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(64); + + buf.append(StringUtil.simpleClassName(this)) + .append('(') + .append(name()) + .append(' ') + .append(timeToLive()) + .append(' '); + + DnsMessageUtil.appendRecordClass(buf, dnsClass()) + .append(' ') + .append(type().name()) + .append(')'); + + return buf.toString(); + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQuery.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQuery.java new file mode 100644 index 0000000000..daeda8b178 --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQuery.java @@ -0,0 +1,190 @@ +/* + * 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.channel.AddressedEnvelope; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +/** + * A {@link DnsQuery} implementation for UDP/IP. + */ +public class DatagramDnsQuery extends DefaultDnsQuery + implements AddressedEnvelope { + + private final InetSocketAddress sender; + private final InetSocketAddress recipient; + + /** + * Creates a new instance with the {@link DnsOpCode#QUERY} {@code opCode}. + * + * @param sender the address of the sender + * @param recipient the address of the recipient + * @param id the {@code ID} of the DNS query + */ + public DatagramDnsQuery( + InetSocketAddress sender, InetSocketAddress recipient, int id) { + this(sender, recipient, id, DnsOpCode.QUERY); + } + + /** + * Creates a new instance. + * + * @param sender the address of the sender + * @param recipient the address of the recipient + * @param id the {@code ID} of the DNS query + * @param opCode the {@code opCode} of the DNS query + */ + public DatagramDnsQuery( + InetSocketAddress sender, InetSocketAddress recipient, int id, DnsOpCode opCode) { + super(id, opCode); + + if (recipient == null && sender == null) { + throw new NullPointerException("recipient and sender"); + } + + this.sender = sender; + this.recipient = recipient; + } + + @Override + public DatagramDnsQuery content() { + return this; + } + + @Override + public InetSocketAddress sender() { + return sender; + } + + @Override + public InetSocketAddress recipient() { + return recipient; + } + + @Override + public DatagramDnsQuery setId(int id) { + return (DatagramDnsQuery) super.setId(id); + } + + @Override + public DatagramDnsQuery setOpCode(DnsOpCode opCode) { + return (DatagramDnsQuery) super.setOpCode(opCode); + } + + @Override + public DatagramDnsQuery setRecursionDesired(boolean recursionDesired) { + return (DatagramDnsQuery) super.setRecursionDesired(recursionDesired); + } + + @Override + public DatagramDnsQuery setZ(int z) { + return (DatagramDnsQuery) super.setZ(z); + } + + @Override + public DatagramDnsQuery setRecord(DnsSection section, DnsRecord record) { + return (DatagramDnsQuery) super.setRecord(section, record); + } + + @Override + public DatagramDnsQuery addRecord(DnsSection section, DnsRecord record) { + return (DatagramDnsQuery) super.addRecord(section, record); + } + + @Override + public DatagramDnsQuery addRecord(DnsSection section, int index, DnsRecord record) { + return (DatagramDnsQuery) super.addRecord(section, index, record); + } + + @Override + public DatagramDnsQuery clear(DnsSection section) { + return (DatagramDnsQuery) super.clear(section); + } + + @Override + public DatagramDnsQuery clear() { + return (DatagramDnsQuery) super.clear(); + } + + @Override + public DatagramDnsQuery touch() { + return (DatagramDnsQuery) super.touch(); + } + + @Override + public DatagramDnsQuery touch(Object hint) { + return (DatagramDnsQuery) super.touch(hint); + } + + @Override + public DatagramDnsQuery retain() { + return (DatagramDnsQuery) super.retain(); + } + + @Override + public DatagramDnsQuery retain(int increment) { + return (DatagramDnsQuery) super.retain(increment); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!super.equals(obj)) { + return false; + } + + if (!(obj instanceof AddressedEnvelope)) { + return false; + } + + @SuppressWarnings("unchecked") + final AddressedEnvelope that = (AddressedEnvelope) obj; + if (sender() == null) { + if (that.sender() != null) { + return false; + } + } else if (!sender().equals(that.sender())) { + return false; + } + + if (recipient() == null) { + if (that.recipient() != null) { + return false; + } + } else if (!recipient().equals(that.recipient())) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = super.hashCode(); + if (sender() != null) { + hashCode = hashCode * 31 + sender().hashCode(); + } + if (recipient() != null) { + hashCode = hashCode * 31 + recipient().hashCode(); + } + return hashCode; + } +} 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 new file mode 100644 index 0000000000..62b607324b --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsQueryEncoder.java @@ -0,0 +1,120 @@ +/* + * 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 DatagramDnsQuery} (or an {@link AddressedEnvelope} of {@link DnsQuery}} into a + * {@link DatagramPacket}. + */ +@ChannelHandler.Sharable +public class DatagramDnsQueryEncoder extends MessageToMessageEncoder> { + + private final DnsRecordEncoder recordEncoder; + + /** + * Creates a new encoder with {@linkplain DnsRecordEncoder#DEFAULT the default record encoder}. + */ + public DatagramDnsQueryEncoder() { + this(DnsRecordEncoder.DEFAULT); + } + + /** + * Creates a new encoder with the specified {@code recordEncoder}. + */ + public DatagramDnsQueryEncoder(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 DnsQuery query = in.content(); + final ByteBuf buf = allocateBuffer(ctx, in); + + boolean success = false; + try { + encodeHeader(query, buf); + encodeQuestions(query, buf); + encodeRecords(query, 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 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; + buf.writeShort(flags); + buf.writeShort(query.count(DnsSection.QUESTION)); + buf.writeShort(0); // answerCount + buf.writeShort(0); // authorityResourceCount + buf.writeShort(query.count(DnsSection.ADDITIONAL)); + } + + private void encodeQuestions(DnsQuery query, ByteBuf buf) throws Exception { + final int count = query.count(DnsSection.QUESTION); + 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 ++) { + recordEncoder.encodeRecord(query.recordAt(section, i), buf); + } + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsResponse.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsResponse.java new file mode 100644 index 0000000000..20dddd8026 --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsResponse.java @@ -0,0 +1,224 @@ +/* + * 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.channel.AddressedEnvelope; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +/** + * A {@link DnsResponse} implementation for UDP/IP. + */ +public class DatagramDnsResponse extends DefaultDnsResponse + implements AddressedEnvelope { + + private final InetSocketAddress sender; + private final InetSocketAddress recipient; + + /** + * Creates a new instance with the {@link DnsOpCode#QUERY} {@code opCode} and + * the {@link DnsResponseCode#NOERROR} {@code RCODE}. + * + * @param sender the address of the sender + * @param recipient the address of the recipient + * @param id the {@code ID} of the DNS response + */ + public DatagramDnsResponse(InetSocketAddress sender, InetSocketAddress recipient, int id) { + this(sender, recipient, id, DnsOpCode.QUERY, DnsResponseCode.NOERROR); + } + + /** + * Creates a new instance with the {@link DnsResponseCode#NOERROR} responseCode. + * + * @param sender the address of the sender + * @param recipient the address of the recipient + * @param id the {@code ID} of the DNS response + * @param opCode the {@code opCode} of the DNS response + */ + public DatagramDnsResponse(InetSocketAddress sender, InetSocketAddress recipient, int id, DnsOpCode opCode) { + this(sender, recipient, id, opCode, DnsResponseCode.NOERROR); + } + + /** + * Creates a new instance. + * + * @param sender the address of the sender + * @param recipient the address of the recipient + * @param id the {@code ID} of the DNS response + * @param opCode the {@code opCode} of the DNS response + * @param responseCode the {@code RCODE} of the DNS response + */ + public DatagramDnsResponse( + InetSocketAddress sender, InetSocketAddress recipient, + int id, DnsOpCode opCode, DnsResponseCode responseCode) { + super(id, opCode, responseCode); + + if (recipient == null && sender == null) { + throw new NullPointerException("recipient and sender"); + } + + this.sender = sender; + this.recipient = recipient; + } + + @Override + public DatagramDnsResponse content() { + return this; + } + + @Override + public InetSocketAddress sender() { + return sender; + } + + @Override + public InetSocketAddress recipient() { + return recipient; + } + + @Override + public DatagramDnsResponse setAuthoritativeAnswer(boolean authoritativeAnswer) { + return (DatagramDnsResponse) super.setAuthoritativeAnswer(authoritativeAnswer); + } + + @Override + public DatagramDnsResponse setTruncated(boolean truncated) { + return (DatagramDnsResponse) super.setTruncated(truncated); + } + + @Override + public DatagramDnsResponse setRecursionAvailable(boolean recursionAvailable) { + return (DatagramDnsResponse) super.setRecursionAvailable(recursionAvailable); + } + + @Override + public DatagramDnsResponse setCode(DnsResponseCode code) { + return (DatagramDnsResponse) super.setCode(code); + } + + @Override + public DatagramDnsResponse setId(int id) { + return (DatagramDnsResponse) super.setId(id); + } + + @Override + public DatagramDnsResponse setOpCode(DnsOpCode opCode) { + return (DatagramDnsResponse) super.setOpCode(opCode); + } + + @Override + public DatagramDnsResponse setRecursionDesired(boolean recursionDesired) { + return (DatagramDnsResponse) super.setRecursionDesired(recursionDesired); + } + + @Override + public DatagramDnsResponse setZ(int z) { + return (DatagramDnsResponse) super.setZ(z); + } + + @Override + public DatagramDnsResponse setRecord(DnsSection section, DnsRecord record) { + return (DatagramDnsResponse) super.setRecord(section, record); + } + + @Override + public DatagramDnsResponse addRecord(DnsSection section, DnsRecord record) { + return (DatagramDnsResponse) super.addRecord(section, record); + } + + @Override + public DatagramDnsResponse addRecord(DnsSection section, int index, DnsRecord record) { + return (DatagramDnsResponse) super.addRecord(section, index, record); + } + + @Override + public DatagramDnsResponse clear(DnsSection section) { + return (DatagramDnsResponse) super.clear(section); + } + + @Override + public DatagramDnsResponse clear() { + return (DatagramDnsResponse) super.clear(); + } + + @Override + public DatagramDnsResponse touch() { + return (DatagramDnsResponse) super.touch(); + } + + @Override + public DatagramDnsResponse touch(Object hint) { + return (DatagramDnsResponse) super.touch(hint); + } + + @Override + public DatagramDnsResponse retain() { + return (DatagramDnsResponse) super.retain(); + } + + @Override + public DatagramDnsResponse retain(int increment) { + return (DatagramDnsResponse) super.retain(increment); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!super.equals(obj)) { + return false; + } + + if (!(obj instanceof AddressedEnvelope)) { + return false; + } + + @SuppressWarnings("unchecked") + final AddressedEnvelope that = (AddressedEnvelope) obj; + if (sender() == null) { + if (that.sender() != null) { + return false; + } + } else if (!sender().equals(that.sender())) { + return false; + } + + if (recipient() == null) { + if (that.recipient() != null) { + return false; + } + } else if (!recipient().equals(that.recipient())) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = super.hashCode(); + if (sender() != null) { + hashCode = hashCode * 31 + sender().hashCode(); + } + if (recipient() != null) { + hashCode = hashCode * 31 + recipient().hashCode(); + } + return hashCode; + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsResponseDecoder.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsResponseDecoder.java new file mode 100644 index 0000000000..e9872015b1 --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DatagramDnsResponseDecoder.java @@ -0,0 +1,111 @@ +/* + * 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 DatagramDnsResponse}. + */ +@ChannelHandler.Sharable +public class DatagramDnsResponseDecoder extends MessageToMessageDecoder { + + private final DnsRecordDecoder recordDecoder; + + /** + * Creates a new decoder with {@linkplain DnsRecordDecoder#DEFAULT the default record decoder}. + */ + public DatagramDnsResponseDecoder() { + this(DnsRecordDecoder.DEFAULT); + } + + /** + * Creates a new decoder with the specified {@code recordDecoder}. + */ + public DatagramDnsResponseDecoder(DnsRecordDecoder recordDecoder) { + this.recordDecoder = checkNotNull(recordDecoder, "recordDecoder"); + } + + @Override + protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List out) throws Exception { + final InetSocketAddress sender = packet.sender(); + final ByteBuf buf = packet.content(); + + final DnsResponse response = newResponse(sender, 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(response, buf, questionCount); + decodeRecords(response, DnsSection.ANSWER, buf, answerCount); + decodeRecords(response, DnsSection.AUTHORITY, buf, authorityRecordCount); + decodeRecords(response, DnsSection.ADDITIONAL, buf, additionalRecordCount); + + out.add(response); + success = true; + } finally { + if (!success) { + response.release(); + } + } + } + + private static DnsResponse newResponse(InetSocketAddress sender, ByteBuf buf) { + final int id = buf.readUnsignedShort(); + + final int flags = buf.readUnsignedShort(); + if (flags >> 15 == 0) { + throw new CorruptedFrameException("not a response"); + } + + final DnsResponse response = new DatagramDnsResponse( + sender, null, + id, DnsOpCode.valueOf((byte) (flags >> 11 & 0xf)), DnsResponseCode.valueOf((byte) (flags & 0xf))); + + response.setRecursionDesired((flags >> 8 & 1) == 1); + response.setAuthoritativeAnswer((flags >> 10 & 1) == 1); + response.setTruncated((flags >> 9 & 1) == 1); + response.setRecursionAvailable((flags >> 7 & 1) == 1); + response.setZ(flags >> 4 & 0x7); + return response; + } + + private void decodeQuestions(DnsResponse response, ByteBuf buf, int questionCount) throws Exception { + for (int i = questionCount; i > 0; i --) { + response.addRecord(DnsSection.QUESTION, recordDecoder.decodeQuestion(buf)); + } + } + + private void decodeRecords( + DnsResponse response, DnsSection section, ByteBuf buf, int count) throws Exception { + for (int i = count; i > 0; i --) { + response.addRecord(section, recordDecoder.decodeRecord(buf)); + } + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsQuery.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsQuery.java new file mode 100644 index 0000000000..422d34bce9 --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsQuery.java @@ -0,0 +1,111 @@ +/* + * 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; + +/** + * The default {@link DnsQuery} implementation. + */ +public class DefaultDnsQuery extends AbstractDnsMessage implements DnsQuery { + + /** + * Creates a new instance with the {@link DnsOpCode#QUERY} {@code opCode}. + * + * @param id the {@code ID} of the DNS query + */ + public DefaultDnsQuery(int id) { + super(id); + } + + /** + * Creates a new instance. + * + * @param id the {@code ID} of the DNS query + * @param opCode the {@code opCode} of the DNS query + */ + public DefaultDnsQuery(int id, DnsOpCode opCode) { + super(id, opCode); + } + + @Override + public DnsQuery setId(int id) { + return (DnsQuery) super.setId(id); + } + + @Override + public DnsQuery setOpCode(DnsOpCode opCode) { + return (DnsQuery) super.setOpCode(opCode); + } + + @Override + public DnsQuery setRecursionDesired(boolean recursionDesired) { + return (DnsQuery) super.setRecursionDesired(recursionDesired); + } + + @Override + public DnsQuery setZ(int z) { + return (DnsQuery) super.setZ(z); + } + + @Override + public DnsQuery setRecord(DnsSection section, DnsRecord record) { + return (DnsQuery) super.setRecord(section, record); + } + + @Override + public DnsQuery addRecord(DnsSection section, DnsRecord record) { + return (DnsQuery) super.addRecord(section, record); + } + + @Override + public DnsQuery addRecord(DnsSection section, int index, DnsRecord record) { + return (DnsQuery) super.addRecord(section, index, record); + } + + @Override + public DnsQuery clear(DnsSection section) { + return (DnsQuery) super.clear(section); + } + + @Override + public DnsQuery clear() { + return (DnsQuery) super.clear(); + } + + @Override + public DnsQuery touch() { + return (DnsQuery) super.touch(); + } + + @Override + public DnsQuery touch(Object hint) { + return (DnsQuery) super.touch(hint); + } + + @Override + public DnsQuery retain() { + return (DnsQuery) super.retain(); + } + + @Override + public DnsQuery retain(int increment) { + return (DnsQuery) super.retain(increment); + } + + @Override + public String toString() { + return DnsMessageUtil.appendQuery(new StringBuilder(128), this).toString(); + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsQuestion.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsQuestion.java new file mode 100644 index 0000000000..09ceb1e62c --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsQuestion.java @@ -0,0 +1,70 @@ +/* + * 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.util.internal.StringUtil; + +/** + * The default {@link DnsQuestion} implementation. + */ +public class DefaultDnsQuestion extends AbstractDnsRecord implements DnsQuestion { + + /** + * Creates a new {@link #CLASS_IN IN-class} question. + * + * @param name the domain name of the DNS question + * @param type the type of the DNS question + */ + public DefaultDnsQuestion(String name, DnsRecordType type) { + super(name, type, 0); + } + + /** + * Creates a new question. + * + * @param name the domain name of the DNS question + * @param type the type of the DNS question + * @param dnsClass the class of the record, usually one of the following: + *
    + *
  • {@link #CLASS_IN}
  • + *
  • {@link #CLASS_CSNET}
  • + *
  • {@link #CLASS_CHAOS}
  • + *
  • {@link #CLASS_HESIOD}
  • + *
  • {@link #CLASS_NONE}
  • + *
  • {@link #CLASS_ANY}
  • + *
+ */ + public DefaultDnsQuestion(String name, DnsRecordType type, int dnsClass) { + super(name, type, dnsClass, 0); + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(64); + + buf.append(StringUtil.simpleClassName(this)) + .append('(') + .append(name()) + .append(' '); + + DnsMessageUtil.appendRecordClass(buf, dnsClass()) + .append(' ') + .append(type().name()) + .append(')'); + + return buf.toString(); + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRawRecord.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRawRecord.java new file mode 100644 index 0000000000..cba07c55d2 --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRawRecord.java @@ -0,0 +1,143 @@ +/* + * 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.util.internal.StringUtil; + +import static io.netty.util.internal.ObjectUtil.checkNotNull; + +/** + * The default {@code DnsRawRecord} implementation. + */ +public class DefaultDnsRawRecord extends AbstractDnsRecord implements DnsRawRecord { + + private final ByteBuf content; + + /** + * Creates a new {@link #CLASS_IN IN-class} record. + * + * @param name the domain name + * @param type the type of the record + * @param timeToLive the TTL value of the record + */ + public DefaultDnsRawRecord(String name, DnsRecordType type, long timeToLive, ByteBuf content) { + this(name, type, DnsRecord.CLASS_IN, timeToLive, content); + } + + /** + * Creates a new record. + * + * @param name the domain name + * @param type the type of the record + * @param dnsClass the class of the record, usually one of the following: + *
    + *
  • {@link #CLASS_IN}
  • + *
  • {@link #CLASS_CSNET}
  • + *
  • {@link #CLASS_CHAOS}
  • + *
  • {@link #CLASS_HESIOD}
  • + *
  • {@link #CLASS_NONE}
  • + *
  • {@link #CLASS_ANY}
  • + *
+ * @param timeToLive the TTL value of the record + */ + public DefaultDnsRawRecord( + String name, DnsRecordType type, int dnsClass, long timeToLive, ByteBuf content) { + super(name, type, dnsClass, timeToLive); + this.content = checkNotNull(content, "content"); + } + + @Override + public ByteBuf content() { + return content; + } + + @Override + public DnsRawRecord copy() { + return new DefaultDnsRawRecord(name(), type(), dnsClass(), timeToLive(), content().copy()); + } + + @Override + public DnsRawRecord duplicate() { + return new DefaultDnsRawRecord(name(), type(), dnsClass(), timeToLive(), content().duplicate()); + } + + @Override + public int refCnt() { + return content().refCnt(); + } + + @Override + public DnsRawRecord retain() { + content().retain(); + return this; + } + + @Override + public DnsRawRecord retain(int increment) { + content().retain(increment); + return this; + } + + @Override + public boolean release() { + return content().release(); + } + + @Override + public boolean release(int decrement) { + return content().release(decrement); + } + + @Override + public DnsRawRecord touch() { + content().touch(); + return this; + } + + @Override + public DnsRawRecord touch(Object hint) { + content().touch(hint); + return this; + } + + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(64).append(StringUtil.simpleClassName(this)).append('('); + final DnsRecordType type = type(); + if (type != DnsRecordType.OPT) { + buf.append(name().isEmpty()? "" : name()) + .append(' ') + .append(timeToLive()) + .append(' '); + + DnsMessageUtil.appendRecordClass(buf, dnsClass()) + .append(' ') + .append(type.name()); + } else { + buf.append("OPT flags:") + .append(timeToLive()) + .append(" udp:") + .append(dnsClass()); + } + + buf.append(' ') + .append(content().readableBytes()) + .append("B)"); + + return buf.toString(); + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordDecoder.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordDecoder.java new file mode 100644 index 0000000000..2e35aebf5f --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordDecoder.java @@ -0,0 +1,124 @@ +/* + * 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.handler.codec.CorruptedFrameException; +import io.netty.util.CharsetUtil; +import io.netty.util.internal.StringUtil; + +/** + * The default {@link DnsRecordDecoder} implementation. + * + * @see DefaultDnsRecordEncoder + */ +public class DefaultDnsRecordDecoder implements DnsRecordDecoder { + + /** + * Creates a new instance. + */ + protected DefaultDnsRecordDecoder() { } + + @Override + public final DnsQuestion decodeQuestion(ByteBuf in) throws Exception { + String name = decodeName(in); + DnsRecordType type = DnsRecordType.valueOf(in.readUnsignedShort()); + int qClass = in.readUnsignedShort(); + return new DefaultDnsQuestion(name, type, qClass); + } + + @Override + public final T decodeRecord(ByteBuf in) throws Exception { + final String name = decodeName(in); + final DnsRecordType type = DnsRecordType.valueOf(in.readUnsignedShort()); + final int aClass = in.readUnsignedShort(); + final long ttl = in.readUnsignedInt(); + final int length = in.readUnsignedShort(); + final int offset = in.readerIndex(); + + @SuppressWarnings("unchecked") + T record = (T) decodeRecord(name, type, aClass, ttl, in, offset, length); + in.readerIndex(offset + length); + return record; + } + + /** + * Decodes a record from the information decoded so far by {@link #decodeRecord(ByteBuf)}. + * + * @param name the domain name of the record + * @param type the type of the record + * @param dnsClass the class of the record + * @param timeToLive the TTL of the record + * @param in the {@link ByteBuf} that contains the RDATA + * @param offset the start offset of the RDATA in {@code in} + * @param length the length of the RDATA + * + * @return a {@link DnsRawRecord}. Override this method to decode RDATA and return other record implementation. + */ + protected DnsRecord decodeRecord( + String name, DnsRecordType type, int dnsClass, long timeToLive, + ByteBuf in, int offset, int length) throws Exception { + + return new DefaultDnsRawRecord( + name, type, dnsClass, timeToLive, in.duplicate().setIndex(offset, offset + length).retain()); + } + + /** + * Retrieves a domain name given a buffer containing a DNS packet. If the + * name contains a pointer, the position of the buffer will be set to + * directly after the pointer's index after the name has been read. + * + * @param in the byte buffer containing the DNS packet + * @return the domain name for an entry + */ + protected String decodeName(ByteBuf in) { + int position = -1; + int checked = 0; + final int end = in.writerIndex(); + final StringBuilder name = new StringBuilder(in.readableBytes() << 1); + for (int len = in.readUnsignedByte(); in.isReadable() && len != 0; len = in.readUnsignedByte()) { + boolean pointer = (len & 0xc0) == 0xc0; + if (pointer) { + if (position == -1) { + position = in.readerIndex() + 1; + } + + final int next = (len & 0x3f) << 8 | in.readUnsignedByte(); + if (next >= end) { + throw new CorruptedFrameException("name has an out-of-range pointer"); + } + in.readerIndex(next); + + // check for loops + checked += 2; + if (checked >= end) { + throw new CorruptedFrameException("name contains a loop."); + } + } else { + name.append(in.toString(in.readerIndex(), len, CharsetUtil.UTF_8)).append('.'); + in.skipBytes(len); + } + } + if (position != -1) { + in.readerIndex(position); + } + if (name.length() == 0) { + return StringUtil.EMPTY_STRING; + } + + return name.substring(0, name.length() - 1); + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordEncoder.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordEncoder.java new file mode 100644 index 0000000000..055ac2f34f --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsRecordEncoder.java @@ -0,0 +1,79 @@ +/* + * 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.buffer.ByteBufUtil; +import io.netty.handler.codec.UnsupportedMessageTypeException; +import io.netty.util.internal.StringUtil; + +/** + * The default {@link DnsRecordEncoder} implementation. + * + * @see DefaultDnsRecordDecoder + */ +public class DefaultDnsRecordEncoder implements DnsRecordEncoder { + + /** + * Creates a new instance. + */ + protected DefaultDnsRecordEncoder() { } + + @Override + public final void encodeQuestion(DnsQuestion question, ByteBuf out) throws Exception { + encodeName(question.name(), out); + out.writeShort(question.type().intValue()); + out.writeShort(question.dnsClass()); + } + + @Override + public void encodeRecord(DnsRecord record, ByteBuf out) throws Exception { + if (record instanceof DnsQuestion) { + encodeQuestion((DnsQuestion) record, out); + } else if (record instanceof DnsRawRecord) { + encodeRawRecord((DnsRawRecord) record, out); + } else { + throw new UnsupportedMessageTypeException(StringUtil.simpleClassName(record)); + } + } + + private void encodeRawRecord(DnsRawRecord record, ByteBuf out) throws Exception { + encodeName(record.name(), out); + + out.writeShort(record.type().intValue()); + out.writeShort(record.dnsClass()); + out.writeInt((int) record.timeToLive()); + + ByteBuf content = record.content(); + int contentLen = content.readableBytes(); + + out.writeShort(contentLen); + out.writeBytes(content, content.readerIndex(), contentLen); + } + + protected void encodeName(String name, ByteBuf buf) throws Exception { + String[] parts = StringUtil.split(name, '.'); + for (String part: parts) { + final int partLen = part.length(); + if (partLen == 0) { + continue; + } + buf.writeByte(partLen); + ByteBufUtil.writeAscii(buf, part); + } + buf.writeByte(0); // marks end of name field + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsResponse.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsResponse.java new file mode 100644 index 0000000000..c0a6bc22ae --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DefaultDnsResponse.java @@ -0,0 +1,175 @@ +/* + * 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 static io.netty.util.internal.ObjectUtil.checkNotNull; + +/** + * The default {@link DnsResponse} implementation. + */ +public class DefaultDnsResponse extends AbstractDnsMessage implements DnsResponse { + + private boolean authoritativeAnswer; + private boolean truncated; + private boolean recursionAvailable; + private DnsResponseCode code; + + /** + * Creates a new instance with the {@link DnsOpCode#QUERY} {@code opCode} and + * the {@link DnsResponseCode#NOERROR} {@code RCODE}. + * + * @param id the {@code ID} of the DNS response + */ + public DefaultDnsResponse(int id) { + this(id, DnsOpCode.QUERY, DnsResponseCode.NOERROR); + } + + /** + * Creates a new instance with the {@link DnsResponseCode#NOERROR} {@code RCODE}. + * + * @param id the {@code ID} of the DNS response + * @param opCode the {@code opCode} of the DNS response + */ + public DefaultDnsResponse(int id, DnsOpCode opCode) { + this(id, opCode, DnsResponseCode.NOERROR); + } + + /** + * Creates a new instance. + * + * @param id the {@code ID} of the DNS response + * @param opCode the {@code opCode} of the DNS response + * @param code the {@code RCODE} of the DNS response + */ + public DefaultDnsResponse(int id, DnsOpCode opCode, DnsResponseCode code) { + super(id, opCode); + setCode(code); + } + + @Override + public boolean isAuthoritativeAnswer() { + return authoritativeAnswer; + } + + @Override + public DnsResponse setAuthoritativeAnswer(boolean authoritativeAnswer) { + this.authoritativeAnswer = authoritativeAnswer; + return this; + } + + @Override + public boolean isTruncated() { + return truncated; + } + + @Override + public DnsResponse setTruncated(boolean truncated) { + this.truncated = truncated; + return this; + } + + @Override + public boolean isRecursionAvailable() { + return recursionAvailable; + } + + @Override + public DnsResponse setRecursionAvailable(boolean recursionAvailable) { + this.recursionAvailable = recursionAvailable; + return this; + } + + @Override + public DnsResponseCode code() { + return code; + } + + @Override + public DnsResponse setCode(DnsResponseCode code) { + this.code = checkNotNull(code, "code"); + return this; + } + + @Override + public DnsResponse setId(int id) { + return (DnsResponse) super.setId(id); + } + + @Override + public DnsResponse setOpCode(DnsOpCode opCode) { + return (DnsResponse) super.setOpCode(opCode); + } + + @Override + public DnsResponse setRecursionDesired(boolean recursionDesired) { + return (DnsResponse) super.setRecursionDesired(recursionDesired); + } + + @Override + public DnsResponse setZ(int z) { + return (DnsResponse) super.setZ(z); + } + + @Override + public DnsResponse setRecord(DnsSection section, DnsRecord record) { + return (DnsResponse) super.setRecord(section, record); + } + + @Override + public DnsResponse addRecord(DnsSection section, DnsRecord record) { + return (DnsResponse) super.addRecord(section, record); + } + + @Override + public DnsResponse addRecord(DnsSection section, int index, DnsRecord record) { + return (DnsResponse) super.addRecord(section, index, record); + } + + @Override + public DnsResponse clear(DnsSection section) { + return (DnsResponse) super.clear(section); + } + + @Override + public DnsResponse clear() { + return (DnsResponse) super.clear(); + } + + @Override + public DnsResponse touch() { + return (DnsResponse) super.touch(); + } + + @Override + public DnsResponse touch(Object hint) { + return (DnsResponse) super.touch(hint); + } + + @Override + public DnsResponse retain() { + return (DnsResponse) super.retain(); + } + + @Override + public DnsResponse retain(int increment) { + return (DnsResponse) super.retain(increment); + } + + @Override + public String toString() { + return DnsMessageUtil.appendResponse(new StringBuilder(128), this).toString(); + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsClass.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsClass.java deleted file mode 100644 index 296c9ee1f6..0000000000 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsClass.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2014 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; - -/** - * Represents a class field in DNS protocol - */ -public final class DnsClass implements Comparable { - - /** - * Default class for DNS entries. - */ - public static final DnsClass IN = new DnsClass(0x0001, "IN"); - public static final DnsClass CSNET = new DnsClass(0x0002, "CSNET"); - public static final DnsClass CHAOS = new DnsClass(0x0003, "CHAOS"); - public static final DnsClass HESIOD = new DnsClass(0x0004, "HESIOD"); - public static final DnsClass NONE = new DnsClass(0x00fe, "NONE"); - public static final DnsClass ANY = new DnsClass(0x00ff, "ANY"); - - private static final String EXPECTED = - " (expected: " + - IN + '(' + IN.intValue() + "), " + - CSNET + '(' + CSNET.intValue() + "), " + - CHAOS + '(' + CHAOS.intValue() + "), " + - HESIOD + '(' + HESIOD.intValue() + "), " + - NONE + '(' + NONE.intValue() + "), " + - ANY + '(' + ANY.intValue() + "))"; - - public static DnsClass valueOf(String name) { - if (IN.name().equals(name)) { - return IN; - } - if (NONE.name().equals(name)) { - return NONE; - } - if (ANY.name().equals(name)) { - return ANY; - } - if (CSNET.name().equals(name)) { - return CSNET; - } - if (CHAOS.name().equals(name)) { - return CHAOS; - } - if (HESIOD.name().equals(name)) { - return HESIOD; - } - - throw new IllegalArgumentException("name: " + name + EXPECTED); - } - - public static DnsClass valueOf(int intValue) { - switch (intValue) { - case 0x0001: - return IN; - case 0x0002: - return CSNET; - case 0x0003: - return CHAOS; - case 0x0004: - return HESIOD; - case 0x00fe: - return NONE; - case 0x00ff: - return ANY; - default: - return new DnsClass(intValue, "UNKNOWN"); - } - } - - /** - * Returns an instance of DnsClass for a custom type. - * - * @param clazz The class - * @param name The name - */ - public static DnsClass valueOf(int clazz, String name) { - return new DnsClass(clazz, name); - } - - /** - * The protocol value of this DNS class - */ - private final int intValue; - - /** - * The name of this DNS class - */ - private final String name; - - private DnsClass(int intValue, String name) { - if ((intValue & 0xffff) != intValue) { - throw new IllegalArgumentException("intValue: " + intValue + " (expected: 0 ~ 65535)"); - } - - this.intValue = intValue; - this.name = name; - } - - /** - * Returns the name of this class as used in bind config files - */ - public String name() { - return name; - } - - /** - * Returns the protocol value represented by this class - */ - public int intValue() { - return intValue; - } - - @Override - public int hashCode() { - return intValue; - } - - @Override - public boolean equals(Object o) { - return o instanceof DnsClass && ((DnsClass) o).intValue == intValue; - } - - @Override - public int compareTo(DnsClass o) { - return intValue() - o.intValue(); - } - - @Override - public String toString() { - return name; - } -} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsEntry.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsEntry.java deleted file mode 100644 index 7be10af1d4..0000000000 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsEntry.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2013 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.util.internal.StringUtil; - -/** - * A class representing entries in a DNS packet (questions, and all resource - * records). Contains data shared by entries such as name, type, and class. - */ -public class DnsEntry { - - private final String name; - private final DnsType type; - private final DnsClass dnsClass; - - // only allow to extend from same package - DnsEntry(String name, DnsType type, DnsClass dnsClass) { - if (name == null) { - throw new NullPointerException("name"); - } - if (type == null) { - throw new NullPointerException("type"); - } - if (dnsClass == null) { - throw new NullPointerException("dnsClass"); - } - - this.name = name; - this.type = type; - this.dnsClass = dnsClass; - } - - /** - * Returns the name of this entry (the domain). - */ - public String name() { - return name; - } - - /** - * Returns the type of resource record to be received. - */ - public DnsType type() { - return type; - } - - /** - * Returns the class for this entry. Default is IN (Internet). - */ - public DnsClass dnsClass() { - return dnsClass; - } - - @Override - public int hashCode() { - return (name.hashCode() * 31 + type.hashCode()) * 31 + dnsClass.hashCode(); - } - - @Override - public String toString() { - return new StringBuilder(128).append(StringUtil.simpleClassName(this)) - .append("(name: ").append(name) - .append(", type: ").append(type) - .append(", class: ").append(dnsClass) - .append(')').toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof DnsEntry)) { - return false; - } - - DnsEntry that = (DnsEntry) o; - return type().intValue() == that.type().intValue() && - dnsClass().intValue() == that.dnsClass().intValue() && - name().equals(that.name()); - } -} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsHeader.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsHeader.java deleted file mode 100644 index b824e79f52..0000000000 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsHeader.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2013 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; - -/** - * The header super-class which includes information shared by DNS query and - * response packet headers such as the ID, opcode, and type. The only flag - * shared by both classes is the flag for desiring recursion. - */ -public class DnsHeader { - - /** - * Message type is query. - */ - public static final int TYPE_QUERY = 0; - - /** - * Message type is response. - */ - public static final int TYPE_RESPONSE = 1; - - /** - * Message is for a standard query. - */ - public static final int OPCODE_QUERY = 0; - - /** - * Message is for an inverse query. Note: inverse queries have been - * obsoleted since RFC 3425, and are not necessarily supported. - */ - @Deprecated - public static final int OPCODE_IQUERY = 1; - - private final DnsMessage parent; - - private boolean recursionDesired; - private int opcode; - private int id; - private int type; - private int z; - - // only allow to extend from within the same package - DnsHeader(DnsMessage parent) { - if (parent == null) { - throw new NullPointerException("parent"); - } - this.parent = parent; - } - - /** - * Returns the number of questions in the {@link DnsMessage}. - */ - public int questionCount() { - return parent.questions().size(); - } - - /** - * Returns the number of answer resource records in the {@link DnsMessage}. - */ - public int answerCount() { - return parent.answers().size(); - } - - /** - * Returns the number of authority resource records in the - * {@link DnsMessage}. - */ - public int authorityResourceCount() { - return parent.authorityResources().size(); - } - - /** - * Returns the number of additional resource records in the - * {@link DnsMessage}. - */ - public int additionalResourceCount() { - return parent.additionalResources().size(); - } - - /** - * Returns {@code true} if a query is to be pursued recursively. - */ - public boolean isRecursionDesired() { - return recursionDesired; - } - - /** - * Returns the 4 bit opcode used for the {@link DnsMessage}. - * - * @see #OPCODE_QUERY - * @see #OPCODE_IQUERY - */ - public int opcode() { - return opcode; - } - - /** - * Returns the type of {@link DnsMessage}. - * - * @see #TYPE_QUERY - */ - public int type() { - return type; - } - - /** - * Returns the 2 byte unsigned identifier number used for the - * {@link DnsMessage}. - */ - public int id() { - return id; - } - - /** - * Sets the opcode for this {@link DnsMessage}. - * - * @param opcode - * opcode to set - * @return the header to allow method chaining - */ - public DnsHeader setOpcode(int opcode) { - this.opcode = opcode; - return this; - } - - /** - * Sets whether a name server is directed to pursue a query recursively or - * not. - * - * @param recursionDesired - * if set to {@code true}, pursues query recursively - * @return the header to allow method chaining - */ - public DnsHeader setRecursionDesired(boolean recursionDesired) { - this.recursionDesired = recursionDesired; - return this; - } - - /** - * Sets the {@link DnsMessage} type. - * - * @param type - * message type - * @return the header to allow method chaining - */ - public DnsHeader setType(int type) { - this.type = type; - return this; - } - - /** - * Sets the id for this {@link DnsMessage}. - * - * @param id - * a unique 2 byte unsigned identifier - * @return the header to allow method chaining - */ - public DnsHeader setId(int id) { - this.id = id; - return this; - } - - /** - * Returns the 3 bit reserved field 'Z'. - */ - public int z() { - return z; - } - - /** - * Sets the field Z. This field is reserved and should remain as 0 if the - * DNS server does not make usage of this field. - * - * @param z - * the value for the reserved field Z - */ - public DnsHeader setZ(int z) { - this.z = z; - return this; - } -} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsMessage.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsMessage.java index c30722230b..a4b9902b05 100644 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsMessage.java +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 The Netty Project + * 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 @@ -15,225 +15,142 @@ */ package io.netty.handler.codec.dns; -import io.netty.util.AbstractReferenceCounted; -import io.netty.util.ReferenceCountUtil; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import io.netty.util.ReferenceCounted; /** - * The message super-class which contains core information concerning DNS - * packets, both outgoing and incoming. + * The superclass which contains core information concerning a {@link DnsQuery} and a {@link DnsResponse}. */ -public abstract class DnsMessage extends AbstractReferenceCounted { - - private List questions; - private List answers; - private List authority; - private List additional; - - private final DnsHeader header; - - // Only allow to extend from same package - DnsMessage(int id) { - header = newHeader(id); - } +public interface DnsMessage extends ReferenceCounted { /** - * Returns the header belonging to this message. + * Returns the {@code ID} of this DNS message. */ - public DnsHeader header() { - return header; - } + int id(); /** - * Returns a list of all the questions in this message. + * Sets the {@code ID} of this DNS message. */ - public List questions() { - if (questions == null) { - return Collections.emptyList(); - } - return Collections.unmodifiableList(questions); - } + DnsMessage setId(int id); /** - * Returns a list of all the answer resource records in this message. + * Returns the {@code opCode} of this DNS message. */ - public List answers() { - if (answers == null) { - return Collections.emptyList(); - } - return Collections.unmodifiableList(answers); - } + DnsOpCode opCode(); /** - * Returns a list of all the authority resource records in this message. + * Sets the {@code opCode} of this DNS message. */ - public List authorityResources() { - if (authority == null) { - return Collections.emptyList(); - } - return Collections.unmodifiableList(authority); - } + DnsMessage setOpCode(DnsOpCode opCode); /** - * Returns a list of all the additional resource records in this message. + * Returns the {@code RD} (recursion desired} field of this DNS message. */ - public List additionalResources() { - if (additional == null) { - return Collections.emptyList(); - } - return Collections.unmodifiableList(additional); - } + boolean isRecursionDesired(); /** - * Adds an answer resource record to this message. + * Sets the {@code RD} (recursion desired} field of this DNS message. + */ + DnsMessage setRecursionDesired(boolean recursionDesired); + + /** + * Returns the {@code Z} (reserved for future use) field of this DNS message. + */ + int z(); + + /** + * Sets the {@code Z} (reserved for future use) field of this DNS message. + */ + DnsMessage setZ(int z); + + /** + * Returns the number of records in the specified {@code section} of this DNS message. + */ + int count(DnsSection section); + + /** + * Returns the number of records in this DNS message. + */ + int count(); + + /** + * Returns the first record in the specified {@code section} of this DNS message. + * When the specified {@code section} is {@link DnsSection#QUESTION}, the type of the returned record is + * always {@link DnsQuestion}. * - * @param answer - * the answer resource record to be added - * @return the message to allow method chaining + * @return {@code null} if this message doesn't have any records in the specified {@code section} */ - public DnsMessage addAnswer(DnsResource answer) { - if (answers == null) { - answers = new LinkedList(); - } - answers.add(answer); - return this; - } + T recordAt(DnsSection section); /** - * Adds a question to this message. + * Returns the record at the specified {@code index} of the specified {@code section} of this DNS message. + * When the specified {@code section} is {@link DnsSection#QUESTION}, the type of the returned record is + * always {@link DnsQuestion}. * - * @param question - * the question to be added - * @return the message to allow method chaining + * @throws IndexOutOfBoundsException if the specified {@code index} is out of bounds */ - public DnsMessage addQuestion(DnsQuestion question) { - if (questions == null) { - questions = new LinkedList(); - } - questions.add(question); - return this; - } + T recordAt(DnsSection section, int index); /** - * Adds an authority resource record to this message. - * - * @param resource - * the authority resource record to be added - * @return the message to allow method chaining + * Sets the specified {@code section} of this DNS message to the specified {@code record}, + * making it a single-record section. When the specified {@code section} is {@link DnsSection#QUESTION}, + * the specified {@code record} must be a {@link DnsQuestion}. */ - public DnsMessage addAuthorityResource(DnsResource resource) { - if (authority == null) { - authority = new LinkedList(); - } - authority.add(resource); - return this; - } + DnsMessage setRecord(DnsSection section, DnsRecord record); /** - * Adds an additional resource record to this message. + * Sets the specified {@code record} at the specified {@code index} of the specified {@code section} + * of this DNS message. When the specified {@code section} is {@link DnsSection#QUESTION}, + * the specified {@code record} must be a {@link DnsQuestion}. * - * @param resource - * the additional resource record to be added - * @return the message to allow method chaining + * @return the old record + * @throws IndexOutOfBoundsException if the specified {@code index} is out of bounds */ - public DnsMessage addAdditionalResource(DnsResource resource) { - if (additional == null) { - additional = new LinkedList(); - } - additional.add(resource); - return this; - } + T setRecord(DnsSection section, int index, DnsRecord record); + + /** + * Adds the specified {@code record} at the end of the specified {@code section} of this DNS message. + * When the specified {@code section} is {@link DnsSection#QUESTION}, the specified {@code record} + * must be a {@link DnsQuestion}. + */ + DnsMessage addRecord(DnsSection section, DnsRecord record); + + /** + * Adds the specified {@code record} at the specified {@code index} of the specified {@code section} + * of this DNS message. When the specified {@code section} is {@link DnsSection#QUESTION}, the specified + * {@code record} must be a {@link DnsQuestion}. + * + * @throws IndexOutOfBoundsException if the specified {@code index} is out of bounds + */ + DnsMessage addRecord(DnsSection section, int index, DnsRecord record); + + /** + * Removes the record at the specified {@code index} of the specified {@code section} from this DNS message. + * When the specified {@code section} is {@link DnsSection#QUESTION}, the type of the returned record is + * always {@link DnsQuestion}. + * + * @return the removed record + */ + T removeRecord(DnsSection section, int index); + + /** + * Removes all the records in the specified {@code section} of this DNS message. + */ + DnsMessage clear(DnsSection section); + + /** + * Removes all the records in this DNS message. + */ + DnsMessage clear(); @Override - protected void deallocate() { - // NOOP - } + DnsMessage touch(); @Override - public boolean release() { - release(questions()); - release(answers()); - release(additionalResources()); - release(authorityResources()); - return super.release(); - } - - private static void release(List resources) { - for (Object resource: resources) { - ReferenceCountUtil.release(resource); - } - } + DnsMessage touch(Object hint); @Override - public boolean release(int decrement) { - release(questions(), decrement); - release(answers(), decrement); - release(additionalResources(), decrement); - release(authorityResources(), decrement); - return super.release(decrement); - } - - private static void release(List resources, int decrement) { - for (Object resource: resources) { - ReferenceCountUtil.release(resource, decrement); - } - } + DnsMessage retain(); @Override - public DnsMessage touch(Object hint) { - touch(questions(), hint); - touch(answers(), hint); - touch(additionalResources(), hint); - touch(authorityResources(), hint); - return this; - } - - private static void touch(List resources, Object hint) { - for (Object resource: resources) { - ReferenceCountUtil.touch(resource, hint); - } - } - - @Override - public DnsMessage retain() { - retain(questions()); - retain(answers()); - retain(additionalResources()); - retain(authorityResources()); - super.retain(); - return this; - } - - private static void retain(List resources) { - for (Object resource: resources) { - ReferenceCountUtil.retain(resource); - } - } - - @Override - public DnsMessage retain(int increment) { - retain(questions(), increment); - retain(answers(), increment); - retain(additionalResources(), increment); - retain(authorityResources(), increment); - super.retain(increment); - return this; - } - - private static void retain(List resources, int increment) { - for (Object resource: resources) { - ReferenceCountUtil.retain(resource, increment); - } - } - - @Override - public DnsMessage touch() { - super.touch(); - return this; - } - - protected abstract DnsHeader newHeader(int id); + DnsMessage retain(int increment); } diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsMessageUtil.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsMessageUtil.java new file mode 100644 index 0000000000..4fe11759d3 --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsMessageUtil.java @@ -0,0 +1,181 @@ +/* + * 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.channel.AddressedEnvelope; +import io.netty.util.internal.StringUtil; + +import java.net.SocketAddress; + +/** + * Provides some utility methods for DNS message implementations. + */ +final class DnsMessageUtil { + + static StringBuilder appendQuery(StringBuilder buf, DnsQuery query) { + appendQueryHeader(buf, query); + appendAllRecords(buf, query); + return buf; + } + + static StringBuilder appendResponse(StringBuilder buf, DnsResponse response) { + appendResponseHeader(buf, response); + appendAllRecords(buf, response); + return buf; + } + + static StringBuilder appendRecordClass(StringBuilder buf, int dnsClass) { + final String name; + switch (dnsClass &= 0xFFFF) { + case DnsRecord.CLASS_IN: + name = "IN"; + break; + case DnsRecord.CLASS_CSNET: + name = "CSNET"; + break; + case DnsRecord.CLASS_CHAOS: + name = "CHAOS"; + break; + case DnsRecord.CLASS_HESIOD: + name = "HESIOD"; + break; + case DnsRecord.CLASS_NONE: + name = "NONE"; + break; + case DnsRecord.CLASS_ANY: + name = "ANY"; + break; + default: + name = null; + break; + } + + if (name != null) { + buf.append(name); + } else { + buf.append("UNKNOWN(").append(dnsClass).append(')'); + } + + return buf; + } + + private static void appendQueryHeader(StringBuilder buf, DnsQuery msg) { + buf.append(StringUtil.simpleClassName(msg)) + .append('('); + + appendAddresses(buf, msg) + .append(msg.id()) + .append(", ") + .append(msg.opCode()); + + if (msg.isRecursionDesired()) { + buf.append(", RD"); + } + if (msg.z() != 0) { + buf.append(", Z: ") + .append(msg.z()); + } + buf.append(')'); + } + + private static void appendResponseHeader(StringBuilder buf, DnsResponse msg) { + buf.append(StringUtil.simpleClassName(msg)) + .append('('); + + appendAddresses(buf, msg) + .append(msg.id()) + .append(", ") + .append(msg.opCode()) + .append(", ") + .append(msg.code()) + .append(','); + + boolean hasComma = true; + if (msg.isRecursionDesired()) { + hasComma = false; + buf.append(" RD"); + } + if (msg.isAuthoritativeAnswer()) { + hasComma = false; + buf.append(" AA"); + } + if (msg.isTruncated()) { + hasComma = false; + buf.append(" TC"); + } + if (msg.isRecursionAvailable()) { + hasComma = false; + buf.append(" RA"); + } + if (msg.z() != 0) { + if (!hasComma) { + buf.append(','); + } + buf.append(" Z: ") + .append(msg.z()); + } + + if (hasComma) { + buf.setCharAt(buf.length() - 1, ')'); + } else { + buf.append(')'); + } + } + + private static StringBuilder appendAddresses(StringBuilder buf, DnsMessage msg) { + + if (!(msg instanceof AddressedEnvelope)) { + return buf; + } + + @SuppressWarnings("unchecked") + AddressedEnvelope envelope = (AddressedEnvelope) msg; + + SocketAddress addr = envelope.sender(); + if (addr != null) { + buf.append("from: ") + .append(addr) + .append(", "); + } + + addr = envelope.recipient(); + if (addr != null) { + buf.append("to: ") + .append(addr) + .append(", "); + } + + return buf; + } + + private static void appendAllRecords(StringBuilder buf, DnsMessage msg) { + appendRecords(buf, msg, DnsSection.QUESTION); + appendRecords(buf, msg, DnsSection.ANSWER); + appendRecords(buf, msg, DnsSection.AUTHORITY); + appendRecords(buf, msg, DnsSection.ADDITIONAL); + } + + private static void appendRecords(StringBuilder buf, DnsMessage message, DnsSection section) { + final int count = message.count(section); + for (int i = 0; i < count; i ++) { + buf.append(StringUtil.NEWLINE) + .append(StringUtil.TAB) + .append(message.recordAt(section, i)); + } + } + + private DnsMessageUtil() { } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsOpCode.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsOpCode.java new file mode 100644 index 0000000000..4140b2e3ff --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsOpCode.java @@ -0,0 +1,118 @@ +/* + * 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 static io.netty.util.internal.ObjectUtil.checkNotNull; + +/** + * The DNS {@code OpCode} as defined in RFC2929. + */ +public class DnsOpCode implements Comparable { + + /** + * The 'Query' DNS OpCode, as defined in RFC1035. + */ + public static final DnsOpCode QUERY = new DnsOpCode(0x00, "QUERY"); + + /** + * The 'IQuery' DNS OpCode, as defined in RFC1035. + */ + public static final DnsOpCode IQUERY = new DnsOpCode(0x01, "IQUERY"); + + /** + * The 'Status' DNS OpCode, as defined in RFC1035. + */ + public static final DnsOpCode STATUS = new DnsOpCode(0x02, "STATUS"); + + /** + * The 'Notify' DNS OpCode, as defined in RFC1996. + */ + public static final DnsOpCode NOTIFY = new DnsOpCode(0x04, "NOTIFY"); + + /** + * The 'Update' DNS OpCode, as defined in RFC2136. + */ + public static final DnsOpCode UPDATE = new DnsOpCode(0x05, "UPDATE"); + + /** + * Returns the {@link DnsOpCode} instance of the specified byte value. + */ + public static DnsOpCode valueOf(int b) { + switch (b) { + case 0x00: + return QUERY; + case 0x01: + return IQUERY; + case 0x02: + return STATUS; + case 0x04: + return NOTIFY; + case 0x05: + return UPDATE; + } + + return new DnsOpCode(b); + } + + private final byte byteValue; + private final String name; + private String text; + + private DnsOpCode(int byteValue) { + this(byteValue, "UNKNOWN"); + } + + public DnsOpCode(int byteValue, String name) { + this.byteValue = (byte) byteValue; + this.name = checkNotNull(name, "name"); + } + + public byte byteValue() { + return byteValue; + } + + @Override + public int hashCode() { + return byteValue; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof DnsOpCode)) { + return false; + } + + return byteValue == ((DnsOpCode) obj).byteValue; + } + + @Override + public int compareTo(DnsOpCode o) { + return byteValue - o.byteValue; + } + + @Override + public String toString() { + String text = this.text; + if (text == null) { + this.text = text = name + '(' + (byteValue & 0xFF) + ')'; + } + return text; + } +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQuery.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQuery.java index c212c0394e..a30e51de0b 100644 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQuery.java +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 The Netty Project + * 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 @@ -15,89 +15,46 @@ */ package io.netty.handler.codec.dns; -import java.net.InetSocketAddress; - /** - * A DNS query packet which is sent to a server to receive a DNS response packet - * with information answering a DnsQuery's questions. + * A DNS query message. */ -public class DnsQuery extends DnsMessage { - - private final InetSocketAddress recipient; - - /** - * Constructs a DNS query. By default recursion will be toggled on. - */ - public DnsQuery(int id, InetSocketAddress recipient) { - super(id); - if (recipient == null) { - throw new NullPointerException("recipient"); - } - this.recipient = recipient; - } - - /** - * Return the {@link InetSocketAddress} of the recipient of the {@link DnsQuery} - */ - public InetSocketAddress recipient() { - return recipient; - } +public interface DnsQuery extends DnsMessage { + @Override + DnsQuery setId(int id); @Override - public DnsQuery addAnswer(DnsResource answer) { - super.addAnswer(answer); - return this; - } + DnsQuery setOpCode(DnsOpCode opCode); @Override - public DnsQuery addQuestion(DnsQuestion question) { - super.addQuestion(question); - return this; - } + DnsQuery setRecursionDesired(boolean recursionDesired); @Override - public DnsQuery addAuthorityResource(DnsResource resource) { - super.addAuthorityResource(resource); - return this; - } + DnsQuery setZ(int z); @Override - public DnsQuery addAdditionalResource(DnsResource resource) { - super.addAdditionalResource(resource); - return this; - } + DnsQuery setRecord(DnsSection section, DnsRecord record); @Override - public DnsQuery touch(Object hint) { - super.touch(hint); - return this; - } + DnsQuery addRecord(DnsSection section, DnsRecord record); @Override - public DnsQuery retain() { - super.retain(); - return this; - } + DnsQuery addRecord(DnsSection section, int index, DnsRecord record); @Override - public DnsQuery retain(int increment) { - super.retain(increment); - return this; - } + DnsQuery clear(DnsSection section); @Override - public DnsQuery touch() { - super.touch(); - return this; - } + DnsQuery clear(); @Override - public DnsQueryHeader header() { - return (DnsQueryHeader) super.header(); - } + DnsQuery touch(); @Override - protected DnsQueryHeader newHeader(int id) { - return new DnsQueryHeader(this, id); - } + DnsQuery touch(Object hint); + + @Override + DnsQuery retain(); + + @Override + DnsQuery retain(int increment); } diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQueryEncoder.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQueryEncoder.java deleted file mode 100644 index 2cdc562f85..0000000000 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQueryEncoder.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2013 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.MessageToMessageEncoder; -import io.netty.util.CharsetUtil; -import io.netty.util.internal.StringUtil; - -import java.nio.charset.Charset; -import java.util.List; - -/** - * DnsQueryEncoder accepts {@link DnsQuery} and encodes to {@link ByteBuf}. This - * class also contains methods for encoding parts of DnsQuery's such as the - * header and questions. - */ -@ChannelHandler.Sharable -public class DnsQueryEncoder extends MessageToMessageEncoder { - - @Override - protected void encode(ChannelHandlerContext ctx, DnsQuery query, List out) throws Exception { - ByteBuf buf = ctx.alloc().buffer(); - encodeHeader(query.header(), buf); - List questions = query.questions(); - for (DnsQuestion question : questions) { - encodeQuestion(question, CharsetUtil.US_ASCII, buf); - } - for (DnsResource resource: query.additionalResources()) { - encodeResource(resource, CharsetUtil.US_ASCII, buf); - } - out.add(new DatagramPacket(buf, query.recipient(), null)); - } - - /** - * Encodes the information in a {@link DnsHeader} and writes it to the - * specified {@link ByteBuf}. The header is always 12 bytes long. - * - * @param header - * the query header being encoded - * @param buf - * the buffer the encoded data should be written to - */ - private static void encodeHeader(DnsHeader header, ByteBuf buf) { - buf.writeShort(header.id()); - int flags = 0; - flags |= header.type() << 15; - flags |= header.opcode() << 14; - flags |= header.isRecursionDesired() ? 1 << 8 : 0; - buf.writeShort(flags); - buf.writeShort(header.questionCount()); - buf.writeShort(0); // answerCount - buf.writeShort(0); // authorityResourceCount - buf.writeShort(header.additionalResourceCount()); - } - - /** - * Encodes the information in a {@link DnsQuestion} and writes it to the - * specified {@link ByteBuf}. - * - * @param question - * the question being encoded - * @param charset - * charset names are encoded in - * @param buf - * the buffer the encoded data should be written to - */ - private static void encodeQuestion(DnsQuestion question, Charset charset, ByteBuf buf) { - encodeName(question.name(), charset, buf); - buf.writeShort(question.type().intValue()); - buf.writeShort(question.dnsClass().intValue()); - } - - private static void encodeResource(DnsResource resource, Charset charset, ByteBuf buf) { - encodeName(resource.name(), charset, buf); - - buf.writeShort(resource.type().intValue()); - buf.writeShort(resource.dnsClass().intValue()); - buf.writeInt((int) resource.timeToLive()); - - ByteBuf content = resource.content(); - int contentLen = content.readableBytes(); - - buf.writeShort(contentLen); - buf.writeBytes(content, content.readerIndex(), contentLen); - } - - private static void encodeName(String name, Charset charset, ByteBuf buf) { - String[] parts = StringUtil.split(name, '.'); - for (String part: parts) { - final int partLen = part.length(); - if (partLen == 0) { - continue; - } - buf.writeByte(partLen); - buf.writeBytes(part.getBytes(charset)); - } - buf.writeByte(0); // marks end of name field - } -} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQueryHeader.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQueryHeader.java deleted file mode 100644 index 65d60e94c1..0000000000 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQueryHeader.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2013 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; - -/** - * The DNS query header class which is used to represent the 12 byte header in a - * {@link DnsQuery}. - */ -public final class DnsQueryHeader extends DnsHeader { - - /** - * Constructor for a DNS packet query header. The id is user generated and - * will be replicated in the response packet by the server. - * - * @param parent the {@link DnsMessage} this header belongs to - * @param id a 2 bit unsigned identification number for this query - */ - public DnsQueryHeader(DnsMessage parent, int id) { - super(parent); - setId(id); - setRecursionDesired(true); - } - - /** - * Returns the {@link DnsMessage} type. This will always return - * {@code TYPE_QUERY}. - */ - @Override - public int type() { - return TYPE_QUERY; - } - - /** - * Sets the {@link DnsHeader} type. Must be {@code TYPE_RESPONSE}. - * - * @param type message type - * @return the header to allow method chaining - */ - @Override - public DnsQueryHeader setType(int type) { - if (type != TYPE_QUERY) { - throw new IllegalArgumentException("type cannot be anything but TYPE_QUERY (0) for a query header."); - } - super.setType(type); - return this; - } - - @Override - public DnsQueryHeader setId(int id) { - super.setId(id); - return this; - } - - @Override - public DnsQueryHeader setRecursionDesired(boolean recursionDesired) { - super.setRecursionDesired(recursionDesired); - return this; - } - - @Override - public DnsQueryHeader setOpcode(int opcode) { - super.setOpcode(opcode); - return this; - } - - @Override - public DnsQueryHeader setZ(int z) { - super.setZ(z); - return this; - } -} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQuestion.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQuestion.java index 4b7df6be6b..082ca3a11b 100644 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQuestion.java +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsQuestion.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 The Netty Project + * 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 @@ -15,58 +15,13 @@ */ package io.netty.handler.codec.dns; -import static io.netty.handler.codec.dns.DnsClass.IN; - /** - * The DNS question class which represents a question being sent to a server via - * a query, or the question being duplicated and sent back in a response. - * Usually a message contains a single question, and DNS servers often don't - * support multiple questions in a single query. + * A DNS question. */ -public final class DnsQuestion extends DnsEntry { - +public interface DnsQuestion extends DnsRecord { /** - * Constructs a question with the default class IN (Internet). - * - * @param name - * the domain name being queried i.e. "www.example.com" - * @param type - * the question type, which represents the type of - * {@link DnsResource} record that should be returned + * An unused property. This method will always return {@code 0}. */ - public DnsQuestion(String name, DnsType type) { - this(name, type, IN); - } - - /** - * Constructs a question with the given class. - * - * @param name - * the domain name being queried i.e. "www.example.com" - * @param type - * the question type, which represents the type of - * {@link DnsResource} record that should be returned - * @param qClass - * the class of a DNS record - */ - public DnsQuestion(String name, DnsType type, DnsClass qClass) { - super(name, type, qClass); - - if (name.isEmpty()) { - throw new IllegalArgumentException("name must not be left blank."); - } - } - @Override - public boolean equals(Object other) { - if (!(other instanceof DnsQuestion)) { - return false; - } - return super.equals(other); - } - - @Override - public int hashCode() { - return super.hashCode(); - } + long timeToLive(); } diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRawRecord.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRawRecord.java new file mode 100644 index 0000000000..524f8da335 --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRawRecord.java @@ -0,0 +1,41 @@ +/* + * 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.ByteBufHolder; + +/** + * A generic {@link DnsRecord} that contains an undecoded {@code RDATA}. + */ +public interface DnsRawRecord extends DnsRecord, ByteBufHolder { + @Override + DnsRawRecord copy(); + + @Override + DnsRawRecord duplicate(); + + @Override + DnsRawRecord retain(); + + @Override + DnsRawRecord retain(int increment); + + @Override + DnsRawRecord touch(); + + @Override + DnsRawRecord touch(Object hint); +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRecord.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRecord.java new file mode 100644 index 0000000000..eb1c060498 --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRecord.java @@ -0,0 +1,82 @@ +/* + * 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; + +/** + * A DNS resource record. + */ +public interface DnsRecord { + + /** + * DNS resource record class: {@code IN} + */ + int CLASS_IN = 0x0001; + + /** + * DNS resource record class: {@code CSNET} + */ + int CLASS_CSNET = 0x0002; + + /** + * DNS resource record class: {@code CHAOS} + */ + int CLASS_CHAOS = 0x0003; + + /** + * DNS resource record class: {@code HESIOD} + */ + int CLASS_HESIOD = 0x0004; + + /** + * DNS resource record class: {@code NONE} + */ + int CLASS_NONE = 0x00fe; + + /** + * DNS resource record class: {@code ANY} + */ + int CLASS_ANY = 0x00ff; + + /** + * Returns the name of this resource record. + */ + String name(); + + /** + * Returns the type of this resource record. + */ + DnsRecordType type(); + + /** + * Returns the class of this resource record. + * + * @return the class value, usually one of the following: + *
    + *
  • {@link #CLASS_IN}
  • + *
  • {@link #CLASS_CSNET}
  • + *
  • {@link #CLASS_CHAOS}
  • + *
  • {@link #CLASS_HESIOD}
  • + *
  • {@link #CLASS_NONE}
  • + *
  • {@link #CLASS_ANY}
  • + *
+ */ + int dnsClass(); + + /** + * Returns the time to live after reading for this resource record. + */ + long timeToLive(); +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRecordDecoder.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRecordDecoder.java new file mode 100644 index 0000000000..23886487ee --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRecordDecoder.java @@ -0,0 +1,42 @@ +/* + * 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; + +/** + * Decodes a DNS record into its object representation. + * + * @see DatagramDnsResponseDecoder + */ +public interface DnsRecordDecoder { + + DnsRecordDecoder DEFAULT = new DefaultDnsRecordDecoder(); + + /** + * Decodes a DNS question into its object representation. + * + * @param in the input buffer which contains a DNS question at its reader index + */ + DnsQuestion decodeQuestion(ByteBuf in) throws Exception; + + /** + * Decodes a DNS record into its object representation. + * + * @param in the input buffer which contains a DNS record at its reader index + */ + T decodeRecord(ByteBuf in) throws Exception; +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRecordEncoder.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRecordEncoder.java new file mode 100644 index 0000000000..56b7fa1a05 --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRecordEncoder.java @@ -0,0 +1,42 @@ +/* + * 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; + +/** + * Encodes a {@link DnsRecord} into binary representation. + * + * @see DatagramDnsQueryEncoder + */ +public interface DnsRecordEncoder { + + DnsRecordEncoder DEFAULT = new DefaultDnsRecordEncoder(); + + /** + * Encodes a {@link DnsQuestion}. + * + * @param out the output buffer where the encoded question will be written to + */ + void encodeQuestion(DnsQuestion question, ByteBuf out) throws Exception; + + /** + * Encodes a {@link DnsRecord}. + * + * @param out the output buffer where the encoded record will be written to + */ + void encodeRecord(DnsRecord record, ByteBuf out) throws Exception; +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsType.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRecordType.java similarity index 70% rename from codec-dns/src/main/java/io/netty/handler/codec/dns/DnsType.java rename to codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRecordType.java index a0059fff67..77f3d3c5a6 100644 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsType.java +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsRecordType.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 The Netty Project + * 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 @@ -16,32 +16,33 @@ package io.netty.handler.codec.dns; import io.netty.util.collection.IntObjectHashMap; + import java.util.HashMap; import java.util.Map; /** * Represents a DNS record type. */ -public final class DnsType implements Comparable { +public class DnsRecordType implements Comparable { /** * Address record RFC 1035 Returns a 32-bit IPv4 address, most commonly used * to map hostnames to an IP address of the host, but also used for DNSBLs, * storing subnet masks in RFC 1101, etc. */ - public static final DnsType A = new DnsType(0x0001, "A"); + public static final DnsRecordType A = new DnsRecordType(0x0001, "A"); /** * Name server record RFC 1035 Delegates a DNS zone to use the given * authoritative name servers */ - public static final DnsType NS = new DnsType(0x0002, "NS"); + public static final DnsRecordType NS = new DnsRecordType(0x0002, "NS"); /** * Canonical name record RFC 1035 Alias of one name to another: the DNS * lookup will continue by retrying the lookup with the new name. */ - public static final DnsType CNAME = new DnsType(0x0005, "CNAME"); + public static final DnsRecordType CNAME = new DnsRecordType(0x0005, "CNAME"); /** * Start of [a zone of] authority record RFC 1035 and RFC 2308 Specifies @@ -49,7 +50,7 @@ public final class DnsType implements Comparable { * server, the email of the domain administrator, the domain serial number, * and several timers relating to refreshing the zone. */ - public static final DnsType SOA = new DnsType(0x0006, "SOA"); + public static final DnsRecordType SOA = new DnsRecordType(0x0006, "SOA"); /** * Pointer record RFC 1035 Pointer to a canonical name. Unlike a CNAME, DNS @@ -57,13 +58,13 @@ public final class DnsType implements Comparable { * use is for implementing reverse DNS lookups, but other uses include such * things as DNS-SD. */ - public static final DnsType PTR = new DnsType(0x000c, "PTR"); + public static final DnsRecordType PTR = new DnsRecordType(0x000c, "PTR"); /** * Mail exchange record RFC 1035 Maps a domain name to a list of message * transfer agents for that domain. */ - public static final DnsType MX = new DnsType(0x000f, "MX"); + public static final DnsRecordType MX = new DnsRecordType(0x000f, "MX"); /** * Text record RFC 1035 Originally for arbitrary human-readable text in a @@ -72,14 +73,14 @@ public final class DnsType implements Comparable { * opportunistic encryption, Sender Policy Framework, DKIM, DMARC DNS-SD, * etc. */ - public static final DnsType TXT = new DnsType(0x0010, "TXT"); + public static final DnsRecordType TXT = new DnsRecordType(0x0010, "TXT"); /** * Responsible person record RFC 1183 Information about the responsible * person(s) for the domain. Usually an email address with the @ replaced by * a . */ - public static final DnsType RP = new DnsType(0x0011, "RP"); + public static final DnsRecordType RP = new DnsRecordType(0x0011, "RP"); /** * AFS database record RFC 1183 Location of database servers of an AFS cell. @@ -87,14 +88,14 @@ public final class DnsType implements Comparable { * their local domain. A subtype of this record is used by the obsolete * DCE/DFS file system. */ - public static final DnsType AFSDB = new DnsType(0x0012, "AFSDB"); + public static final DnsRecordType AFSDB = new DnsRecordType(0x0012, "AFSDB"); /** * Signature record RFC 2535 Signature record used in SIG(0) (RFC 2931) and * TKEY (RFC 2930). RFC 3755 designated RRSIG as the replacement for SIG for * use within DNSSEC. */ - public static final DnsType SIG = new DnsType(0x0018, "SIG"); + public static final DnsRecordType SIG = new DnsRecordType(0x0018, "SIG"); /** * key record RFC 2535 and RFC 2930 Used only for SIG(0) (RFC 2931) and TKEY @@ -103,32 +104,32 @@ public final class DnsType implements Comparable { * replacement within DNSSEC. RFC 4025 designates IPSECKEY as the * replacement for use with IPsec. */ - public static final DnsType KEY = new DnsType(0x0019, "KEY"); + public static final DnsRecordType KEY = new DnsRecordType(0x0019, "KEY"); /** * IPv6 address record RFC 3596 Returns a 128-bit IPv6 address, most * commonly used to map hostnames to an IP address of the host. */ - public static final DnsType AAAA = new DnsType(0x001c, "AAAA"); + public static final DnsRecordType AAAA = new DnsRecordType(0x001c, "AAAA"); /** * Location record RFC 1876 Specifies a geographical location associated * with a domain name. */ - public static final DnsType LOC = new DnsType(0x001d, "LOC"); + public static final DnsRecordType LOC = new DnsRecordType(0x001d, "LOC"); /** * Service locator RFC 2782 Generalized service location record, used for * newer protocols instead of creating protocol-specific records such as MX. */ - public static final DnsType SRV = new DnsType(0x0021, "SRV"); + public static final DnsRecordType SRV = new DnsRecordType(0x0021, "SRV"); /** * Naming Authority Pointer record RFC 3403 Allows regular expression based * rewriting of domain names which can then be used as URIs, further domain * names to lookups, etc. */ - public static final DnsType NAPTR = new DnsType(0x0023, "NAPTR"); + public static final DnsRecordType NAPTR = new DnsRecordType(0x0023, "NAPTR"); /** * Key eXchanger record RFC 2230 Used with some cryptographic systems (not @@ -137,12 +138,12 @@ public final class DnsType implements Comparable { * Informational status, rather than being on the IETF standards-track. It * has always had limited deployment, but is still in use. */ - public static final DnsType KX = new DnsType(0x0024, "KX"); + public static final DnsRecordType KX = new DnsRecordType(0x0024, "KX"); /** * Certificate record RFC 4398 Stores PKIX, SPKI, PGP, etc. */ - public static final DnsType CERT = new DnsType(0x0025, "CERT"); + public static final DnsRecordType CERT = new DnsRecordType(0x0025, "CERT"); /** * Delegation name record RFC 2672 DNAME creates an alias for a name and all @@ -150,25 +151,25 @@ public final class DnsType implements Comparable { * label. Like the CNAME record, the DNS lookup will continue by retrying * the lookup with the new name. */ - public static final DnsType DNAME = new DnsType(0x0027, "DNAME"); + public static final DnsRecordType DNAME = new DnsRecordType(0x0027, "DNAME"); /** * Option record RFC 2671 This is a pseudo DNS record type needed to support * EDNS. */ - public static final DnsType OPT = new DnsType(0x0029, "OPT"); + public static final DnsRecordType OPT = new DnsRecordType(0x0029, "OPT"); /** * Address Prefix List record RFC 3123 Specify lists of address ranges, e.g. * in CIDR format, for various address families. Experimental. */ - public static final DnsType APL = new DnsType(0x002a, "APL"); + public static final DnsRecordType APL = new DnsRecordType(0x002a, "APL"); /** * Delegation signer record RFC 4034 The record used to identify the DNSSEC * signing key of a delegated zone. */ - public static final DnsType DS = new DnsType(0x002b, "DS"); + public static final DnsRecordType DS = new DnsRecordType(0x002b, "DS"); /** * SSH Public Key Fingerprint record RFC 4255 Resource record for publishing @@ -176,47 +177,47 @@ public final class DnsType implements Comparable { * verifying the authenticity of the host. RFC 6594 defines ECC SSH keys and * SHA-256 hashes. See the IANA SSHFP RR parameters registry for details. */ - public static final DnsType SSHFP = new DnsType(0x002c, "SSHFP"); + public static final DnsRecordType SSHFP = new DnsRecordType(0x002c, "SSHFP"); /** * IPsec Key record RFC 4025 Key record that can be used with IPsec. */ - public static final DnsType IPSECKEY = new DnsType(0x002d, "IPSECKEY"); + public static final DnsRecordType IPSECKEY = new DnsRecordType(0x002d, "IPSECKEY"); /** * DNSSEC signature record RFC 4034 Signature for a DNSSEC-secured record * set. Uses the same format as the SIG record. */ - public static final DnsType RRSIG = new DnsType(0x002e, "RRSIG"); + public static final DnsRecordType RRSIG = new DnsRecordType(0x002e, "RRSIG"); /** * Next-Secure record RFC 4034 Part of DNSSEC, used to prove a name does not * exist. Uses the same format as the (obsolete) NXT record. */ - public static final DnsType NSEC = new DnsType(0x002f, "NSEC"); + public static final DnsRecordType NSEC = new DnsRecordType(0x002f, "NSEC"); /** * DNS Key record RFC 4034 The key record used in DNSSEC. Uses the same * format as the KEY record. */ - public static final DnsType DNSKEY = new DnsType(0x0030, "DNSKEY"); + public static final DnsRecordType DNSKEY = new DnsRecordType(0x0030, "DNSKEY"); /** * DHCP identifier record RFC 4701 Used in conjunction with the FQDN option * to DHCP. */ - public static final DnsType DHCID = new DnsType(0x0031, "DHCID"); + public static final DnsRecordType DHCID = new DnsRecordType(0x0031, "DHCID"); /** * NSEC record version 3 RFC 5155 An extension to DNSSEC that allows proof * of nonexistence for a name without permitting zonewalking. */ - public static final DnsType NSEC3 = new DnsType(0x0032, "NSEC3"); + public static final DnsRecordType NSEC3 = new DnsRecordType(0x0032, "NSEC3"); /** * NSEC3 parameters record RFC 5155 Parameter record for use with NSEC3. */ - public static final DnsType NSEC3PARAM = new DnsType(0x0033, "NSEC3PARAM"); + public static final DnsRecordType NSEC3PARAM = new DnsRecordType(0x0033, "NSEC3PARAM"); /** * TLSA certificate association record RFC 6698 A record for DNS-based @@ -225,34 +226,34 @@ public final class DnsType implements Comparable { * key with the domain name where the record is found, thus forming a 'TLSA * certificate association'. */ - public static final DnsType TLSA = new DnsType(0x0034, "TLSA"); + public static final DnsRecordType TLSA = new DnsRecordType(0x0034, "TLSA"); /** * Host Identity Protocol record RFC 5205 Method of separating the end-point * identifier and locator roles of IP addresses. */ - public static final DnsType HIP = new DnsType(0x0037, "HIP"); + public static final DnsRecordType HIP = new DnsRecordType(0x0037, "HIP"); /** * Sender Policy Framework record RFC 4408 Specified as part of the SPF * protocol as an alternative to of storing SPF data in TXT records. Uses * the same format as the earlier TXT record. */ - public static final DnsType SPF = new DnsType(0x0063, "SPF"); + public static final DnsRecordType SPF = new DnsRecordType(0x0063, "SPF"); /** * Secret key record RFC 2930 A method of providing keying material to be * used with TSIG that is encrypted under the public key in an accompanying * KEY RR.. */ - public static final DnsType TKEY = new DnsType(0x00f9, "TKEY"); + public static final DnsRecordType TKEY = new DnsRecordType(0x00f9, "TKEY"); /** * Transaction Signature record RFC 2845 Can be used to authenticate dynamic * updates as coming from an approved client, or to authenticate responses * as coming from an approved recursive name server similar to DNSSEC. */ - public static final DnsType TSIG = new DnsType(0x00fa, "TSIG"); + public static final DnsRecordType TSIG = new DnsRecordType(0x00fa, "TSIG"); /** * Incremental Zone Transfer record RFC 1996 Requests a zone transfer of the @@ -261,13 +262,13 @@ public final class DnsType implements Comparable { * authoritative server is unable to fulfill the request due to * configuration or lack of required deltas. */ - public static final DnsType IXFR = new DnsType(0x00fb, "IXFR"); + public static final DnsRecordType IXFR = new DnsRecordType(0x00fb, "IXFR"); /** * Authoritative Zone Transfer record RFC 1035 Transfer entire zone file * from the master name server to secondary name servers. */ - public static final DnsType AXFR = new DnsType(0x00fc, "AXFR"); + public static final DnsRecordType AXFR = new DnsRecordType(0x00fc, "AXFR"); /** * All cached records RFC 1035 Returns all records of all types known to the @@ -278,49 +279,50 @@ public final class DnsType implements Comparable { * returned. Sometimes referred to as ANY, for example in Windows nslookup * and Wireshark. */ - public static final DnsType ANY = new DnsType(0x00ff, "ANY"); + public static final DnsRecordType ANY = new DnsRecordType(0x00ff, "ANY"); /** * Certification Authority Authorization record RFC 6844 CA pinning, * constraining acceptable CAs for a host/domain. */ - public static final DnsType CAA = new DnsType(0x0101, "CAA"); + public static final DnsRecordType CAA = new DnsRecordType(0x0101, "CAA"); /** * DNSSEC Trust Authorities record N/A Part of a deployment proposal for * DNSSEC without a signed DNS root. See the IANA database and Weiler Spec * for details. Uses the same format as the DS record. */ - public static final DnsType TA = new DnsType(0x8000, "TA"); + public static final DnsRecordType TA = new DnsRecordType(0x8000, "TA"); /** * DNSSEC Lookaside Validation record RFC 4431 For publishing DNSSEC trust * anchors outside of the DNS delegation chain. Uses the same format as the * DS record. RFC 5074 describes a way of using these records. */ - public static final DnsType DLV = new DnsType(0x8001, "DLV"); + public static final DnsRecordType DLV = new DnsRecordType(0x8001, "DLV"); - private static final Map BY_NAME = new HashMap(); - private static final IntObjectHashMap BY_TYPE = new IntObjectHashMap(); + private static final Map BY_NAME = new HashMap(); + private static final IntObjectHashMap BY_TYPE = new IntObjectHashMap(); private static final String EXPECTED; static { - DnsType[] all = { + DnsRecordType[] all = { A, NS, CNAME, SOA, PTR, MX, TXT, RP, AFSDB, SIG, KEY, AAAA, LOC, SRV, NAPTR, KX, CERT, DNAME, OPT, APL, DS, SSHFP, IPSECKEY, RRSIG, NSEC, DNSKEY, DHCID, NSEC3, NSEC3PARAM, TLSA, HIP, SPF, TKEY, TSIG, IXFR, AXFR, ANY, CAA, TA, DLV }; - StringBuilder expected = new StringBuilder(512); - expected.append(" (expected: "); + final StringBuilder expected = new StringBuilder(512); - for (DnsType type: all) { + expected.append(" (expected: "); + for (DnsRecordType type: all) { BY_NAME.put(type.name(), type); BY_TYPE.put(type.intValue(), type); - expected.append(type.name()); - expected.append('('); - expected.append(type.intValue()); - expected.append("), "); + + expected.append(type.name()) + .append('(') + .append(type.intValue()) + .append("), "); } expected.setLength(expected.length() - 2); @@ -328,33 +330,31 @@ public final class DnsType implements Comparable { EXPECTED = expected.toString(); } - public static DnsType valueOf(int intValue) { - DnsType result = BY_TYPE.get(intValue); + public static DnsRecordType valueOf(int intValue) { + DnsRecordType result = BY_TYPE.get(intValue); if (result == null) { - return new DnsType(intValue, "UNKNOWN"); + return new DnsRecordType(intValue); } return result; } - public static DnsType valueOf(String name) { - DnsType result = BY_NAME.get(name); + public static DnsRecordType valueOf(String name) { + DnsRecordType result = BY_NAME.get(name); if (result == null) { throw new IllegalArgumentException("name: " + name + EXPECTED); } return result; } - /** - * Returns a new instance. - */ - public static DnsType valueOf(int intValue, String name) { - return new DnsType(intValue, name); - } - private final int intValue; private final String name; + private String text; - private DnsType(int intValue, String name) { + private DnsRecordType(int intValue) { + this(intValue, "UNKNOWN"); + } + + public DnsRecordType(int intValue, String name) { if ((intValue & 0xffff) != intValue) { throw new IllegalArgumentException("intValue: " + intValue + " (expected: 0 ~ 65535)"); } @@ -383,16 +383,20 @@ public final class DnsType implements Comparable { @Override public boolean equals(Object o) { - return o instanceof DnsType && ((DnsType) o).intValue == intValue; + return o instanceof DnsRecordType && ((DnsRecordType) o).intValue == intValue; } @Override - public int compareTo(DnsType o) { + public int compareTo(DnsRecordType o) { return intValue() - o.intValue(); } @Override public String toString() { - return name; + String text = this.text; + if (text == null) { + this.text = text = name + '(' + intValue() + ')'; + } + return text; } } diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResource.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResource.java deleted file mode 100644 index 968a149158..0000000000 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResource.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2013 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.buffer.ByteBufHolder; - -/** - * Represents any resource record (answer, authority, or additional resource - * records). - */ -public final class DnsResource extends DnsEntry implements ByteBufHolder { - - private final long ttl; - private final ByteBuf content; - - /** - * Constructs a resource record. - * - * @param name - * the domain name - * @param type - * the type of record being returned - * @param aClass - * the class for this resource record - * @param ttl - * the time to live after reading - * @param content - * the data contained in this record - */ - public DnsResource(String name, DnsType type, DnsClass aClass, long ttl, ByteBuf content) { - super(name, type, aClass); - this.ttl = ttl; - this.content = content; - } - - /** - * Returns the time to live after reading for this resource record. - */ - public long timeToLive() { - return ttl; - } - - /** - * Returns the data contained in this resource record. - */ - @Override - public ByteBuf content() { - return content; - } - - /** - * Returns a deep copy of this resource record. - */ - @Override - public DnsResource copy() { - return new DnsResource(name(), type(), dnsClass(), ttl, content.copy()); - } - - /** - * Returns a duplicate of this resource record. - */ - @Override - public DnsResource duplicate() { - return new DnsResource(name(), type(), dnsClass(), ttl, content.duplicate()); - } - - @Override - public int refCnt() { - return content.refCnt(); - } - - @Override - public DnsResource retain() { - content.retain(); - return this; - } - - @Override - public DnsResource retain(int increment) { - content.retain(increment); - return this; - } - - @Override - public boolean release() { - return content.release(); - } - - @Override - public boolean release(int decrement) { - return content.release(decrement); - } - - @Override - public DnsResource touch() { - content.touch(); - return this; - } - - @Override - public DnsResource touch(Object hint) { - content.touch(hint); - return this; - } -} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponse.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponse.java index b733614d8f..ef1370c597 100644 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponse.java +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 The Netty Project + * 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 @@ -15,86 +15,99 @@ */ package io.netty.handler.codec.dns; -import java.net.InetSocketAddress; - /** - * A DNS response packet which is sent to a client after a server receives a - * query. + * A DNS response message. */ -public final class DnsResponse extends DnsMessage { - - private final InetSocketAddress sender; - - public DnsResponse(int id, InetSocketAddress sender) { - super(id); - if (sender == null) { - throw new NullPointerException("sender"); - } - this.sender = sender; - } +public interface DnsResponse extends DnsMessage { /** - * The {@link InetSocketAddress} of the sender of this {@link DnsResponse} + * Returns {@code true} if responding server is authoritative for the domain + * name in the query message. */ - public InetSocketAddress sender() { - return sender; - } + boolean isAuthoritativeAnswer(); + + /** + * Set to {@code true} if responding server is authoritative for the domain + * name in the query message. + * + * @param authoritativeAnswer flag for authoritative answer + */ + DnsResponse setAuthoritativeAnswer(boolean authoritativeAnswer); + + /** + * Returns {@code true} if response has been truncated, usually if it is + * over 512 bytes. + */ + boolean isTruncated(); + + /** + * Set to {@code true} if response has been truncated (usually happens for + * responses over 512 bytes). + * + * @param truncated flag for truncation + */ + DnsResponse setTruncated(boolean truncated); + + /** + * Returns {@code true} if DNS server can handle recursive queries. + */ + boolean isRecursionAvailable(); + + /** + * Set to {@code true} if DNS server can handle recursive queries. + * + * @param recursionAvailable flag for recursion availability + */ + DnsResponse setRecursionAvailable(boolean recursionAvailable); + + /** + * Returns the 4 bit return code. + */ + DnsResponseCode code(); + + /** + * Sets the response code for this message. + * + * @param code the response code + */ + DnsResponse setCode(DnsResponseCode code); @Override - public DnsResponse addAnswer(DnsResource answer) { - super.addAnswer(answer); - return this; - } + DnsResponse setId(int id); @Override - public DnsResponse addQuestion(DnsQuestion question) { - super.addQuestion(question); - return this; - } + DnsResponse setOpCode(DnsOpCode opCode); @Override - public DnsResponse addAuthorityResource(DnsResource resource) { - super.addAuthorityResource(resource); - return this; - } + DnsResponse setRecursionDesired(boolean recursionDesired); @Override - public DnsResponse addAdditionalResource(DnsResource resource) { - super.addAdditionalResource(resource); - return this; - } + DnsResponse setZ(int z); @Override - public DnsResponse touch(Object hint) { - super.touch(hint); - return this; - } + DnsResponse setRecord(DnsSection section, DnsRecord record); @Override - public DnsResponse retain() { - super.retain(); - return this; - } + DnsResponse addRecord(DnsSection section, DnsRecord record); @Override - public DnsResponse retain(int increment) { - super.retain(increment); - return this; - } + DnsResponse addRecord(DnsSection section, int index, DnsRecord record); @Override - public DnsResponse touch() { - super.touch(); - return this; - } + DnsResponse clear(DnsSection section); @Override - public DnsResponseHeader header() { - return (DnsResponseHeader) super.header(); - } + DnsResponse clear(); @Override - protected DnsResponseHeader newHeader(int id) { - return new DnsResponseHeader(this, id); - } + DnsResponse touch(); + + @Override + DnsResponse touch(Object hint); + + @Override + DnsResponse retain(); + + @Override + DnsResponse retain(int increment); } diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponseCode.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponseCode.java index c2a7b4b986..dcd4c945aa 100644 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponseCode.java +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponseCode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 The Netty Project + * 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 @@ -15,159 +15,183 @@ */ package io.netty.handler.codec.dns; +import static io.netty.util.internal.ObjectUtil.checkNotNull; + /** - * Represents the possible response codes a server may send after receiving a - * query. A response code of 0 indicates no error. + * The DNS {@code RCODE}, as defined in RFC2929. */ -public final class DnsResponseCode implements Comparable { +public class DnsResponseCode implements Comparable { /** - * ID 0, no error + * The 'NoError' DNS RCODE (0), as defined in RFC1035. */ - public static final DnsResponseCode NOERROR = new DnsResponseCode(0, "no error"); + public static final DnsResponseCode NOERROR = new DnsResponseCode(0, "NoError"); /** - * ID 1, format error + * The 'FormErr' DNS RCODE (1), as defined in RFC1035. */ - public static final DnsResponseCode FORMERROR = new DnsResponseCode(1, "format error"); + public static final DnsResponseCode FORMERR = new DnsResponseCode(1, "FormErr"); /** - * ID 2, server failure + * The 'ServFail' DNS RCODE (2), as defined in RFC1035. */ - public static final DnsResponseCode SERVFAIL = new DnsResponseCode(2, "server failure"); + public static final DnsResponseCode SERVFAIL = new DnsResponseCode(2, "ServFail"); /** - * ID 3, name error + * The 'NXDomain' DNS RCODE (3), as defined in RFC1035. */ - public static final DnsResponseCode NXDOMAIN = new DnsResponseCode(3, "name error"); + public static final DnsResponseCode NXDOMAIN = new DnsResponseCode(3, "NXDomain"); /** - * ID 4, not implemented + * The 'NotImp' DNS RCODE (4), as defined in RFC1035. */ - public static final DnsResponseCode NOTIMPL = new DnsResponseCode(4, "not implemented"); + public static final DnsResponseCode NOTIMP = new DnsResponseCode(4, "NotImp"); /** - * ID 5, operation refused + * The 'Refused' DNS RCODE (5), as defined in RFC1035. */ - public static final DnsResponseCode REFUSED = new DnsResponseCode(5, "operation refused"); + public static final DnsResponseCode REFUSED = new DnsResponseCode(5, "Refused"); /** - * ID 6, domain name should not exist + * The 'YXDomain' DNS RCODE (6), as defined in RFC2136. */ - public static final DnsResponseCode YXDOMAIN = new DnsResponseCode(6, "domain name should not exist"); + public static final DnsResponseCode YXDOMAIN = new DnsResponseCode(6, "YXDomain"); /** - * ID 7, resource record set should not exist + * The 'YXRRSet' DNS RCODE (7), as defined in RFC2136. */ - public static final DnsResponseCode YXRRSET = new DnsResponseCode(7, "resource record set should not exist"); + public static final DnsResponseCode YXRRSET = new DnsResponseCode(7, "YXRRSet"); /** - * ID 8, rrset does not exist + * The 'NXRRSet' DNS RCODE (8), as defined in RFC2136. */ - public static final DnsResponseCode NXRRSET = new DnsResponseCode(8, "rrset does not exist"); + public static final DnsResponseCode NXRRSET = new DnsResponseCode(8, "NXRRSet"); /** - * ID 9, not authoritative for zone + * The 'NotAuth' DNS RCODE (9), as defined in RFC2136. */ - public static final DnsResponseCode NOTAUTH = new DnsResponseCode(9, "not authoritative for zone"); + public static final DnsResponseCode NOTAUTH = new DnsResponseCode(9, "NotAuth"); /** - * ID 10, name not in zone + * The 'NotZone' DNS RCODE (10), as defined in RFC2136. */ - public static final DnsResponseCode NOTZONE = new DnsResponseCode(10, "name not in zone"); + public static final DnsResponseCode NOTZONE = new DnsResponseCode(10, "NotZone"); /** - * ID 11, bad extension mechanism for version + * The 'BADVERS' or 'BADSIG' DNS RCODE (16), as defined in RFC2671 + * and RFC2845. */ - public static final DnsResponseCode BADVERS = new DnsResponseCode(11, "bad extension mechanism for version"); + public static final DnsResponseCode BADVERS_OR_BADSIG = new DnsResponseCode(16, "BADVERS_OR_BADSIG"); /** - * ID 12, bad signature + * The 'BADKEY' DNS RCODE (17), as defined in RFC2845. */ - public static final DnsResponseCode BADSIG = new DnsResponseCode(12, "bad signature"); + public static final DnsResponseCode BADKEY = new DnsResponseCode(17, "BADKEY"); /** - * ID 13, bad key + * The 'BADTIME' DNS RCODE (18), as defined in RFC2845. */ - public static final DnsResponseCode BADKEY = new DnsResponseCode(13, "bad key"); + public static final DnsResponseCode BADTIME = new DnsResponseCode(18, "BADTIME"); /** - * ID 14, bad timestamp + * The 'BADMODE' DNS RCODE (19), as defined in RFC2930. */ - public static final DnsResponseCode BADTIME = new DnsResponseCode(14, "bad timestamp"); - - private final int errorCode; - private final String message; + public static final DnsResponseCode BADMODE = new DnsResponseCode(19, "BADMODE"); /** - * Returns the {@link DnsResponseCode} that corresponds with the given - * {@code responseCode}. + * The 'BADNAME' DNS RCODE (20), as defined in RFC2930. + */ + public static final DnsResponseCode BADNAME = new DnsResponseCode(20, "BADNAME"); + + /** + * The 'BADALG' DNS RCODE (21), as defined in RFC2930. + */ + public static final DnsResponseCode BADALG = new DnsResponseCode(21, "BADALG"); + + /** + * Returns the {@link DnsResponseCode} that corresponds with the given {@code responseCode}. * - * @param responseCode - * the error code's id - * @return corresponding {@link DnsResponseCode} or {@code null} if none can be found. + * @param responseCode the DNS RCODE + * + * @return the corresponding {@link DnsResponseCode} */ public static DnsResponseCode valueOf(int responseCode) { switch (responseCode) { - case 0: - return NOERROR; - case 1: - return FORMERROR; - case 2: - return SERVFAIL; - case 3: - return NXDOMAIN; - case 4: - return NOTIMPL; - case 5: - return REFUSED; - case 6: - return YXDOMAIN; - case 7: - return YXRRSET; - case 8: - return NXRRSET; - case 9: - return NOTAUTH; - case 10: - return NOTZONE; - case 11: - return BADVERS; - case 12: - return BADSIG; - case 13: - return BADKEY; - case 14: - return BADTIME; - default: - return new DnsResponseCode(responseCode, null); + case 0: + return NOERROR; + case 1: + return FORMERR; + case 2: + return SERVFAIL; + case 3: + return NXDOMAIN; + case 4: + return NOTIMP; + case 5: + return REFUSED; + case 6: + return YXDOMAIN; + case 7: + return YXRRSET; + case 8: + return NXRRSET; + case 9: + return NOTAUTH; + case 10: + return NOTZONE; + case 16: + return BADVERS_OR_BADSIG; + case 17: + return BADKEY; + case 18: + return BADTIME; + case 19: + return BADMODE; + case 20: + return BADNAME; + case 21: + return BADALG; + default: + return new DnsResponseCode(responseCode); } } - public DnsResponseCode(int errorCode, String message) { - this.errorCode = errorCode; - this.message = message; + private final int code; + private final String name; + private String text; + + private DnsResponseCode(int code) { + this(code, "UNKNOWN"); + } + + public DnsResponseCode(int code, String name) { + if (code < 0 || code > 65535) { + throw new IllegalArgumentException("code: " + code + " (expected: 0 ~ 65535)"); + } + + this.code = code; + this.name = checkNotNull(name, "name"); } /** * Returns the error code for this {@link DnsResponseCode}. */ - public int code() { - return errorCode; + public int intValue() { + return code; } @Override public int compareTo(DnsResponseCode o) { - return code() - o.code(); + return intValue() - o.intValue(); } @Override public int hashCode() { - return code(); + return intValue(); } /** - * Equality of {@link DnsResponseCode} only depends on {@link #code()}. + * Equality of {@link DnsResponseCode} only depends on {@link #intValue()}. */ @Override public boolean equals(Object o) { @@ -175,7 +199,7 @@ public final class DnsResponseCode implements Comparable { return false; } - return code() == ((DnsResponseCode) o).code(); + return intValue() == ((DnsResponseCode) o).intValue(); } /** @@ -183,9 +207,10 @@ public final class DnsResponseCode implements Comparable { */ @Override public String toString() { - if (message == null) { - return "DnsResponseCode(" + errorCode + ')'; + String text = this.text; + if (text == null) { + this.text = text = name + '(' + intValue() + ')'; } - return "DnsResponseCode(" + errorCode + ", " + message + ')'; + return text; } } diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponseDecoder.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponseDecoder.java deleted file mode 100644 index 957055adb9..0000000000 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponseDecoder.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2013 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 io.netty.util.CharsetUtil; - -import java.util.List; - -/** - * DnsResponseDecoder accepts {@link io.netty.channel.socket.DatagramPacket} and encodes to - * {@link DnsResponse}. This class also contains methods for decoding parts of - * DnsResponses such as questions and resource records. - */ -@ChannelHandler.Sharable -public class DnsResponseDecoder extends MessageToMessageDecoder { - - @Override - protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List out) throws Exception { - ByteBuf buf = packet.content(); - - int id = buf.readUnsignedShort(); - - DnsResponse response = new DnsResponse(id, packet.sender()); - DnsResponseHeader header = response.header(); - int flags = buf.readUnsignedShort(); - header.setType(flags >> 15); - header.setOpcode(flags >> 11 & 0xf); - header.setRecursionDesired((flags >> 8 & 1) == 1); - header.setAuthoritativeAnswer((flags >> 10 & 1) == 1); - header.setTruncated((flags >> 9 & 1) == 1); - header.setRecursionAvailable((flags >> 7 & 1) == 1); - header.setZ(flags >> 4 & 0x7); - header.setResponseCode(DnsResponseCode.valueOf(flags & 0xf)); - - int questions = buf.readUnsignedShort(); - int answers = buf.readUnsignedShort(); - int authorities = buf.readUnsignedShort(); - int additionals = buf.readUnsignedShort(); - - for (int i = 0; i < questions; i++) { - response.addQuestion(decodeQuestion(buf)); - } - if (header.responseCode() != DnsResponseCode.NOERROR) { - // response code for error - out.add(response); - return; - } - boolean release = true; - try { - for (int i = 0; i < answers; i++) { - response.addAnswer(decodeResource(buf)); - } - for (int i = 0; i < authorities; i++) { - response.addAuthorityResource(decodeResource(buf)); - } - for (int i = 0; i < additionals; i++) { - response.addAdditionalResource(decodeResource(buf)); - } - out.add(response); - release = false; - } finally { - if (release) { - // We need to release te DnsResources in case of an Exception as we called retain() on the buffer. - releaseDnsResources(response.answers()); - releaseDnsResources(response.authorityResources()); - releaseDnsResources(response.additionalResources()); - } - } - } - - private static void releaseDnsResources(List resources) { - int size = resources.size(); - for (int i = 0; i < size; i++) { - DnsResource resource = resources.get(i); - resource.release(); - } - } - - /** - * Retrieves a domain name given a buffer containing a DNS packet. If the - * name contains a pointer, the position of the buffer will be set to - * directly after the pointer's index after the name has been read. - * - * @param buf - * the byte buffer containing the DNS packet - * @return the domain name for an entry - */ - private static String readName(ByteBuf buf) { - int position = -1; - int checked = 0; - int length = buf.writerIndex(); - StringBuilder name = new StringBuilder(); - for (int len = buf.readUnsignedByte(); buf.isReadable() && len != 0; len = buf.readUnsignedByte()) { - boolean pointer = (len & 0xc0) == 0xc0; - if (pointer) { - if (position == -1) { - position = buf.readerIndex() + 1; - } - buf.readerIndex((len & 0x3f) << 8 | buf.readUnsignedByte()); - // check for loops - checked += 2; - if (checked >= length) { - throw new CorruptedFrameException("name contains a loop."); - } - } else { - name.append(buf.toString(buf.readerIndex(), len, CharsetUtil.UTF_8)).append('.'); - buf.skipBytes(len); - } - } - if (position != -1) { - buf.readerIndex(position); - } - if (name.length() == 0) { - return ""; - } - - return name.substring(0, name.length() - 1); - } - - /** - * Decodes a question, given a DNS packet in a byte buffer. - * - * @param buf - * the byte buffer containing the DNS packet - * @return a decoded {@link DnsQuestion} - */ - private static DnsQuestion decodeQuestion(ByteBuf buf) { - String name = readName(buf); - DnsType type = DnsType.valueOf(buf.readUnsignedShort()); - DnsClass qClass = DnsClass.valueOf(buf.readUnsignedShort()); - return new DnsQuestion(name, type, qClass); - } - - /** - * Decodes a resource record, given a DNS packet in a byte buffer. - * - * @param buf - * the byte buffer containing the DNS packet - * @return a {@link DnsResource} record containing response data - */ - private static DnsResource decodeResource(ByteBuf buf) { - String name = readName(buf); - DnsType type = DnsType.valueOf(buf.readUnsignedShort()); - DnsClass aClass = DnsClass.valueOf(buf.readUnsignedShort()); - long ttl = buf.readUnsignedInt(); - int len = buf.readUnsignedShort(); - - int readerIndex = buf.readerIndex(); - ByteBuf payload = buf.duplicate().setIndex(readerIndex, readerIndex + len).retain(); - buf.readerIndex(readerIndex + len); - return new DnsResource(name, type, aClass, ttl, payload); - } -} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponseHeader.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponseHeader.java deleted file mode 100644 index 7d828ef9af..0000000000 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsResponseHeader.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2013 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; - -/** - * The DNS response header class which is used when receiving data from a DNS - * server. Contains information contained in a DNS response header, such as - * recursion availability, and response codes. - */ -public final class DnsResponseHeader extends DnsHeader { - - private boolean authoritativeAnswer; - private boolean truncated; - private boolean recursionAvailable; - - private DnsResponseCode responseCode; - - /** - * Constructor for a DNS packet response header. The id is received by - * reading a {@link DnsQuery} and is sent back to the client. - * - * @param parent - * the {@link DnsMessage} this header belongs to - * @param id - * a 2 bit unsigned identification number received from client - */ - public DnsResponseHeader(DnsMessage parent, int id) { - super(parent); - setId(id); - } - - /** - * Returns {@code true} if responding server is authoritative for the domain - * name in the query message. - */ - public boolean isAuthoritativeAnswer() { - return authoritativeAnswer; - } - - /** - * Returns {@code true} if response has been truncated, usually if it is - * over 512 bytes. - */ - public boolean isTruncated() { - return truncated; - } - - /** - * Returns {@code true} if DNS server can handle recursive queries. - */ - public boolean isRecursionAvailable() { - return recursionAvailable; - } - - /** - * Returns the 4 bit return code. - */ - public DnsResponseCode responseCode() { - return responseCode; - } - - /** - * Returns the {@link DnsMessage} type. This will always return - * {@code TYPE_RESPONSE}. - */ - @Override - public int type() { - return TYPE_RESPONSE; - } - - /** - * Set to {@code true} if responding server is authoritative for the domain - * name in the query message. - * - * @param authoritativeAnswer - * flag for authoritative answer - */ - public DnsResponseHeader setAuthoritativeAnswer(boolean authoritativeAnswer) { - this.authoritativeAnswer = authoritativeAnswer; - return this; - } - - /** - * Set to {@code true} if response has been truncated (usually happens for - * responses over 512 bytes). - * - * @param truncated - * flag for truncation - */ - public DnsResponseHeader setTruncated(boolean truncated) { - this.truncated = truncated; - return this; - } - - /** - * Set to {@code true} if DNS server can handle recursive queries. - * - * @param recursionAvailable - * flag for recursion availability - */ - public DnsResponseHeader setRecursionAvailable(boolean recursionAvailable) { - this.recursionAvailable = recursionAvailable; - return this; - } - - /** - * Sets the response code for this message. - * - * @param responseCode - * the response code - */ - public DnsResponseHeader setResponseCode(DnsResponseCode responseCode) { - this.responseCode = responseCode; - return this; - } - - /** - * Sets the {@link DnsHeader} type. Must be {@code TYPE_RESPONSE}. - * - * @param type - * message type - * @return the header to allow method chaining - */ - @Override - public DnsResponseHeader setType(int type) { - if (type != TYPE_RESPONSE) { - throw new IllegalArgumentException("type cannot be anything but TYPE_RESPONSE (1) for a response header."); - } - super.setType(type); - return this; - } - - @Override - public DnsResponseHeader setId(int id) { - super.setId(id); - return this; - } - - @Override - public DnsHeader setRecursionDesired(boolean recursionDesired) { - return super.setRecursionDesired(recursionDesired); - } - - @Override - public DnsResponseHeader setOpcode(int opcode) { - super.setOpcode(opcode); - return this; - } - - @Override - public DnsResponseHeader setZ(int z) { - super.setZ(z); - return this; - } -} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsSection.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsSection.java new file mode 100644 index 0000000000..1d0c842d1b --- /dev/null +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/DnsSection.java @@ -0,0 +1,38 @@ +/* + * 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; + +/** + * Represents a section of a {@link DnsMessage}. + */ +public enum DnsSection { + /** + * The section that contains {@link DnsQuestion}s. + */ + QUESTION, + /** + * The section that contains the answer {@link DnsRecord}s. + */ + ANSWER, + /** + * The section that contains the authority {@link DnsRecord}s. + */ + AUTHORITY, + /** + * The section that contains the additional {@link DnsRecord}s. + */ + ADDITIONAL +} diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/package-info.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/package-info.java index 7dab11b7dd..e45c7dfcac 100644 --- a/codec-dns/src/main/java/io/netty/handler/codec/dns/package-info.java +++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 The Netty Project + * 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 @@ -15,7 +15,6 @@ */ /** - * DNS codec information for writing to and reading from a DNS server. - * Includes decoders and classes for representing messages and resources. + * DNS codec. */ package io.netty.handler.codec.dns; diff --git a/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsQueryTest.java b/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsQueryTest.java index fd9be0dfd8..35cfd0f5ac 100644 --- a/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsQueryTest.java +++ b/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsQueryTest.java @@ -25,29 +25,40 @@ import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + public class DnsQueryTest { @Test public void writeQueryTest() throws Exception { InetSocketAddress addr = new InetSocketAddress(0); - EmbeddedChannel embedder = new EmbeddedChannel(new DnsQueryEncoder()); + EmbeddedChannel embedder = new EmbeddedChannel(new DatagramDnsQueryEncoder()); List queries = new ArrayList(5); - queries.add(new DnsQuery(1, addr).addQuestion(new DnsQuestion("1.0.0.127.in-addr.arpa", DnsType.PTR))); - queries.add(new DnsQuery(1, addr).addQuestion(new DnsQuestion("www.example.com", DnsType.A))); - queries.add(new DnsQuery(1, addr).addQuestion(new DnsQuestion("example.com", DnsType.AAAA))); - queries.add(new DnsQuery(1, addr).addQuestion(new DnsQuestion("example.com", DnsType.MX))); - queries.add(new DnsQuery(1, addr).addQuestion(new DnsQuestion("example.com", DnsType.CNAME))); + queries.add(new DatagramDnsQuery(addr, null, 1).setRecord( + DnsSection.QUESTION, + new DefaultDnsQuestion("1.0.0.127.in-addr.arpa", DnsRecordType.PTR))); + queries.add(new DatagramDnsQuery(addr, null, 1).setRecord( + DnsSection.QUESTION, + new DefaultDnsQuestion("www.example.com", DnsRecordType.A))); + queries.add(new DatagramDnsQuery(addr, null, 1).setRecord( + DnsSection.QUESTION, + new DefaultDnsQuestion("example.com", DnsRecordType.AAAA))); + queries.add(new DatagramDnsQuery(addr, null, 1).setRecord( + DnsSection.QUESTION, + new DefaultDnsQuestion("example.com", DnsRecordType.MX))); + queries.add(new DatagramDnsQuery(addr, null, 1).setRecord( + DnsSection.QUESTION, + new DefaultDnsQuestion("example.com", DnsRecordType.CNAME))); for (DnsQuery query: queries) { - Assert.assertEquals("Invalid question count, expected 1.", 1, query.header().questionCount()); - Assert.assertEquals("Invalid answer count, expected 0.", 0, query.header().answerCount()); - Assert.assertEquals("Invalid authority resource record count, expected 0.", 0, query.header() - .authorityResourceCount()); - Assert.assertEquals("Invalid additional resource record count, expected 0.", 0, query.header() - .additionalResourceCount()); - Assert.assertEquals("Invalid type, should be TYPE_QUERY (0)", DnsHeader.TYPE_QUERY, query.header() - .type()); + assertThat(query.count(DnsSection.QUESTION), is(1)); + assertThat(query.count(DnsSection.ANSWER), is(0)); + assertThat(query.count(DnsSection.AUTHORITY), is(0)); + assertThat(query.count(DnsSection.ADDITIONAL), is(0)); + embedder.writeOutbound(query); + DatagramPacket packet = embedder.readOutbound(); Assert.assertTrue(packet.content().isReadable()); packet.release(); diff --git a/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsClassTest.java b/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsRecordTypeTest.java similarity index 63% rename from codec-dns/src/test/java/io/netty/handler/codec/dns/DnsClassTest.java rename to codec-dns/src/test/java/io/netty/handler/codec/dns/DnsRecordTypeTest.java index fc03b8a6d1..aeeab95b04 100644 --- a/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsClassTest.java +++ b/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsRecordTypeTest.java @@ -25,13 +25,13 @@ import java.util.List; import static org.junit.Assert.*; -public class DnsClassTest { +public class DnsRecordTypeTest { - private static List allTypes() throws Exception { - List result = new ArrayList(); - for (Field field : DnsClass.class.getDeclaredFields()) { - if ((field.getModifiers() & Modifier.STATIC) != 0 && field.getType() == DnsClass.class) { - result.add((DnsClass) field.get(null)); + private static List allTypes() throws Exception { + List result = new ArrayList(); + for (Field field : DnsRecordType.class.getFields()) { + if ((field.getModifiers() & Modifier.STATIC) != 0 && field.getType() == DnsRecordType.class) { + result.add((DnsRecordType) field.get(null)); } } assertFalse(result.isEmpty()); @@ -41,26 +41,26 @@ public class DnsClassTest { @Test public void testSanity() throws Exception { assertEquals("More than one type has the same int value", - allTypes().size(), new HashSet(allTypes()).size()); + allTypes().size(), new HashSet(allTypes()).size()); } /** - * Test of hashCode method, of class DnsClass. + * Test of hashCode method, of class DnsRecordType. */ @Test public void testHashCode() throws Exception { - for (DnsClass t : allTypes()) { + for (DnsRecordType t : allTypes()) { assertEquals(t.intValue(), t.hashCode()); } } /** - * Test of equals method, of class DnsClass. + * Test of equals method, of class DnsRecordType. */ @Test public void testEquals() throws Exception { - for (DnsClass t1 : allTypes()) { - for (DnsClass t2 : allTypes()) { + for (DnsRecordType t1 : allTypes()) { + for (DnsRecordType t2 : allTypes()) { if (t1 != t2) { assertNotEquals(t1, t2); } @@ -69,15 +69,15 @@ public class DnsClassTest { } /** - * Test of find method, of class DnsClass. + * Test of find method, of class DnsRecordType. */ @Test public void testFind() throws Exception { - for (DnsClass t : allTypes()) { - DnsClass found = DnsClass.valueOf(t.intValue()); + for (DnsRecordType t : allTypes()) { + DnsRecordType found = DnsRecordType.valueOf(t.intValue()); assertSame(t, found); - found = DnsClass.valueOf(t.toString()); - assertSame(t.toString(), t, found); + found = DnsRecordType.valueOf(t.name()); + assertSame(t.name(), t, found); } } } diff --git a/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsResponseTest.java b/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsResponseTest.java index 7d860fed37..573544e218 100644 --- a/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsResponseTest.java +++ b/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsResponseTest.java @@ -17,16 +17,19 @@ package io.netty.handler.codec.dns; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.AddressedEnvelope; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.socket.DatagramPacket; import io.netty.handler.codec.CorruptedFrameException; -import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import java.net.InetSocketAddress; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + public class DnsResponseTest { private static final byte[][] packets = { @@ -69,25 +72,23 @@ public class DnsResponseTest { @Test public void readResponseTest() throws Exception { - EmbeddedChannel embedder = new EmbeddedChannel(new DnsResponseDecoder()); + EmbeddedChannel embedder = new EmbeddedChannel(new DatagramDnsResponseDecoder()); for (byte[] p: packets) { ByteBuf packet = embedder.alloc().buffer(512).writeBytes(p); embedder.writeInbound(new DatagramPacket(packet, null, new InetSocketAddress(0))); - DnsResponse decoded = embedder.readInbound(); + AddressedEnvelope envelope = embedder.readInbound(); + assertThat(envelope, is(instanceOf(DatagramDnsResponse.class))); + DnsResponse response = envelope.content(); + assertThat(response, is(sameInstance((Object) envelope))); + ByteBuf raw = Unpooled.wrappedBuffer(p); - Assert.assertEquals("Invalid id, expected: " + raw.getUnsignedShort(0) + ", actual: " - + decoded.header().id(), raw.getUnsignedShort(0), decoded.header().id()); - Assert.assertEquals("Invalid resource count, expected: " + raw.getUnsignedShort(4) + ", actual: " - + decoded.questions().size(), raw.getUnsignedShort(4), decoded.questions().size()); - Assert.assertEquals("Invalid resource count, expected: " + raw.getUnsignedShort(6) + ", actual: " - + decoded.answers().size(), raw.getUnsignedShort(6), decoded.answers().size()); - Assert.assertEquals("Invalid resource count, expected: " + raw.getUnsignedShort(8) + ", actual: " - + decoded.authorityResources().size(), raw.getUnsignedShort(8), decoded.authorityResources() - .size()); - Assert.assertEquals("Invalid resource count, expected: " + raw.getUnsignedShort(10) + ", actual: " - + decoded.additionalResources().size(), raw.getUnsignedShort(10), - decoded.additionalResources().size()); - decoded.release(); + assertThat(response.id(), is(raw.getUnsignedShort(0))); + assertThat(response.count(DnsSection.QUESTION), is(raw.getUnsignedShort(4))); + assertThat(response.count(DnsSection.ANSWER), is(raw.getUnsignedShort(6))); + assertThat(response.count(DnsSection.AUTHORITY), is(raw.getUnsignedShort(8))); + assertThat(response.count(DnsSection.ADDITIONAL), is(raw.getUnsignedShort(10))); + + envelope.release(); } } @@ -96,7 +97,7 @@ public class DnsResponseTest { @Test public void readMalormedResponseTest() throws Exception { - EmbeddedChannel embedder = new EmbeddedChannel(new DnsResponseDecoder()); + EmbeddedChannel embedder = new EmbeddedChannel(new DatagramDnsResponseDecoder()); ByteBuf packet = embedder.alloc().buffer(512).writeBytes(malformedLoopPacket); exception.expect(CorruptedFrameException.class); embedder.writeInbound(new DatagramPacket(packet, null, new InetSocketAddress(0))); diff --git a/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsTypeTest.java b/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsTypeTest.java deleted file mode 100644 index 7cce75b7f2..0000000000 --- a/codec-dns/src/test/java/io/netty/handler/codec/dns/DnsTypeTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2014 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 org.junit.Test; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; - -import static org.junit.Assert.*; - -public class DnsTypeTest { - - private static List allTypes() throws Exception { - List result = new ArrayList(); - for (Field field : DnsType.class.getFields()) { - if ((field.getModifiers() & Modifier.STATIC) != 0 && field.getType() == DnsType.class) { - result.add((DnsType) field.get(null)); - } - } - assertFalse(result.isEmpty()); - return result; - } - - @Test - public void testSanity() throws Exception { - assertEquals("More than one type has the same int value", - allTypes().size(), new HashSet(allTypes()).size()); - } - - /** - * Test of hashCode method, of class DnsType. - */ - @Test - public void testHashCode() throws Exception { - for (DnsType t : allTypes()) { - assertEquals(t.intValue(), t.hashCode()); - } - } - - /** - * Test of equals method, of class DnsType. - */ - @Test - public void testEquals() throws Exception { - for (DnsType t1 : allTypes()) { - for (DnsType t2 : allTypes()) { - if (t1 != t2) { - assertNotEquals(t1, t2); - } - } - } - } - - /** - * Test of find method, of class DnsType. - */ - @Test - public void testFind() throws Exception { - for (DnsType t : allTypes()) { - DnsType found = DnsType.valueOf(t.intValue()); - assertSame(t, found); - found = DnsType.valueOf(t.toString()); - assertSame(t.toString(), t, found); - } - } -} diff --git a/common/src/main/java/io/netty/util/internal/StringUtil.java b/common/src/main/java/io/netty/util/internal/StringUtil.java index adbfecaf90..a9bc1d6ac5 100644 --- a/common/src/main/java/io/netty/util/internal/StringUtil.java +++ b/common/src/main/java/io/netty/util/internal/StringUtil.java @@ -28,15 +28,19 @@ import static io.netty.util.internal.ObjectUtil.checkNotNull; */ public final class StringUtil { + public static final String EMPTY_STRING = ""; public static final String NEWLINE; + public static final char DOUBLE_QUOTE = '\"'; public static final char COMMA = ','; public static final char LINE_FEED = '\n'; public static final char CARRIAGE_RETURN = '\r'; - public static final String EMPTY_STRING = ""; - public static final byte UPPER_CASE_TO_LOWER_CASE_ASCII_OFFSET = (int) 'a' - (int) 'A'; + public static final char TAB = '\t'; + + public static final byte UPPER_CASE_TO_LOWER_CASE_ASCII_OFFSET = 'a' - 'A'; private static final String[] BYTE2HEX_PAD = new String[256]; private static final String[] BYTE2HEX_NOPAD = new String[256]; + /** * 2 - Quote character at beginning and end. * 5 - Extra allowance for anticipated escape characters that may be added. diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java index 1bf048e7b6..e49a4f0339 100644 --- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java +++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java @@ -16,6 +16,7 @@ package io.netty.resolver.dns; import io.netty.bootstrap.Bootstrap; +import io.netty.channel.AddressedEnvelope; import io.netty.channel.ChannelFactory; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; @@ -27,13 +28,14 @@ import io.netty.channel.FixedRecvByteBufAllocator; import io.netty.channel.ReflectiveChannelFactory; import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.InternetProtocolFamily; -import io.netty.handler.codec.dns.DnsClass; -import io.netty.handler.codec.dns.DnsQueryEncoder; +import io.netty.handler.codec.dns.DatagramDnsQueryEncoder; +import io.netty.handler.codec.dns.DatagramDnsResponse; +import io.netty.handler.codec.dns.DatagramDnsResponseDecoder; +import io.netty.handler.codec.dns.DnsSection; import io.netty.handler.codec.dns.DnsQuestion; -import io.netty.handler.codec.dns.DnsResource; +import io.netty.handler.codec.dns.DnsRecord; import io.netty.handler.codec.dns.DnsResponse; import io.netty.handler.codec.dns.DnsResponseCode; -import io.netty.handler.codec.dns.DnsResponseDecoder; import io.netty.resolver.NameResolver; import io.netty.resolver.SimpleNameResolver; import io.netty.util.ReferenceCountUtil; @@ -83,8 +85,8 @@ public class DnsNameResolver extends SimpleNameResolver { } } - private static final DnsResponseDecoder DECODER = new DnsResponseDecoder(); - private static final DnsQueryEncoder ENCODER = new DnsQueryEncoder(); + private static final DatagramDnsResponseDecoder DECODER = new DatagramDnsResponseDecoder(); + private static final DatagramDnsQueryEncoder ENCODER = new DatagramDnsQueryEncoder(); final Iterable nameServerAddresses; final ChannelFuture bindFuture; @@ -117,7 +119,6 @@ public class DnsNameResolver extends SimpleNameResolver { private volatile int maxQueriesPerResolve = 8; private volatile int maxPayloadSize; - private volatile DnsClass maxPayloadSizeClass; // EDNS uses the CLASS field as the payload size field. /** * Creates a new DNS-based name resolver that communicates with a single DNS server. @@ -580,16 +581,11 @@ public class DnsNameResolver extends SimpleNameResolver { } this.maxPayloadSize = maxPayloadSize; - maxPayloadSizeClass = DnsClass.valueOf(maxPayloadSize); ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize)); return this; } - DnsClass maxPayloadSizeClass() { - return maxPayloadSizeClass; - } - /** * Clears all the DNS resource records cached by this resolver. * @@ -662,21 +658,23 @@ public class DnsNameResolver extends SimpleNameResolver { /** * Sends a DNS query with the specified question. */ - public Future query(DnsQuestion question) { + public Future> query(DnsQuestion question) { return query(nameServerAddresses, question); } /** * Sends a DNS query with the specified question. */ - public Future query(DnsQuestion question, Promise promise) { + public Future> query( + DnsQuestion question, Promise> promise) { return query(nameServerAddresses, question, promise); } /** * Sends a DNS query with the specified question using the specified name server list. */ - public Future query(Iterable nameServerAddresses, DnsQuestion question) { + public Future> query( + Iterable nameServerAddresses, DnsQuestion question) { if (nameServerAddresses == null) { throw new NullPointerException("nameServerAddresses"); } @@ -693,15 +691,18 @@ public class DnsNameResolver extends SimpleNameResolver { return eventLoop.newFailedFuture(cachedResult.cause); } } else { - return query0(nameServerAddresses, question, eventLoop.newPromise()); + return query0( + nameServerAddresses, question, + eventLoop.>newPromise()); } } /** * Sends a DNS query with the specified question using the specified name server list. */ - public Future query( - Iterable nameServerAddresses, DnsQuestion question, Promise promise) { + public Future> query( + Iterable nameServerAddresses, DnsQuestion question, + Promise> promise) { if (nameServerAddresses == null) { throw new NullPointerException("nameServerAddresses"); @@ -716,23 +717,25 @@ public class DnsNameResolver extends SimpleNameResolver { final DnsCacheEntry cachedResult = queryCache.get(question); if (cachedResult != null) { if (cachedResult.response != null) { - return promise.setSuccess(cachedResult.response.retain()); + return cast(promise).setSuccess(cachedResult.response.retain()); } else { - return promise.setFailure(cachedResult.cause); + return cast(promise).setFailure(cachedResult.cause); } } else { return query0(nameServerAddresses, question, promise); } } - private Future query0( - Iterable nameServerAddresses, DnsQuestion question, Promise promise) { + private Future> query0( + Iterable nameServerAddresses, DnsQuestion question, + Promise> promise) { + final Promise> castPromise = cast(promise); try { - new DnsQueryContext(this, nameServerAddresses, question, promise).query(); - return promise; + new DnsQueryContext(this, nameServerAddresses, question, castPromise).query(); + return castPromise; } catch (Exception e) { - return promise.setFailure(e); + return castPromise.setFailure(e); } } @@ -762,12 +765,22 @@ public class DnsNameResolver extends SimpleNameResolver { } } + @SuppressWarnings("unchecked") + private static Promise> cast(Promise promise) { + return (Promise>) promise; + } + + @SuppressWarnings("unchecked") + private static AddressedEnvelope cast(AddressedEnvelope envelope) { + return (AddressedEnvelope) envelope; + } + private final class DnsResponseHandler extends ChannelHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { try { - final DnsResponse res = (DnsResponse) msg; - final int queryId = res.header().id(); + final DatagramDnsResponse res = (DatagramDnsResponse) msg; + final int queryId = res.id(); if (logger.isDebugEnabled()) { logger.debug("{} RECEIVED: [{}: {}], {}", ch, queryId, res.sender(), res); @@ -782,14 +795,13 @@ public class DnsNameResolver extends SimpleNameResolver { return; } - final List questions = res.questions(); - if (questions.size() != 1) { + if (res.count(DnsSection.QUESTION) != 1) { logger.warn("Received a DNS response with invalid number of questions: {}", res); return; } final DnsQuestion q = qCtx.question(); - if (!q.equals(questions.get(0))) { + if (!q.equals(res.recordAt(DnsSection.QUESTION))) { logger.warn("Received a mismatching DNS response: {}", res); return; } @@ -800,26 +812,26 @@ public class DnsNameResolver extends SimpleNameResolver { timeoutFuture.cancel(false); } - if (res.header().responseCode() == DnsResponseCode.NOERROR) { + if (res.code() == DnsResponseCode.NOERROR) { cache(q, res); promises.set(queryId, null); - Promise qPromise = qCtx.promise(); + Promise> qPromise = qCtx.promise(); if (qPromise.setUncancellable()) { - qPromise.setSuccess(res.retain()); + qPromise.setSuccess(cast(res.retain())); } } else { qCtx.retry(res.sender(), - "response code: " + res.header().responseCode() + - " with " + res.answers().size() + " answer(s) and " + - res.authorityResources().size() + " authority resource(s)"); + "response code: " + res.code() + + " with " + res.count(DnsSection.ANSWER) + " answer(s) and " + + res.count(DnsSection.AUTHORITY) + " authority resource(s)"); } } finally { ReferenceCountUtil.safeRelease(msg); } } - private void cache(DnsQuestion question, DnsResponse res) { + private void cache(DnsQuestion question, AddressedEnvelope res) { final int maxTtl = maxTtl(); if (maxTtl == 0) { return; @@ -827,8 +839,11 @@ public class DnsNameResolver extends SimpleNameResolver { long ttl = Long.MAX_VALUE; // Find the smallest TTL value returned by the server. - for (DnsResource r: res.answers()) { - long rTtl = r.timeToLive(); + final DnsResponse resc = res.content(); + final int answerCount = resc.count(DnsSection.ANSWER); + for (int i = 0; i < answerCount; i ++) { + final DnsRecord r = resc.recordAt(DnsSection.ANSWER, i); + final long rTtl = r.timeToLive(); if (ttl > rTtl) { ttl = rTtl; } @@ -847,12 +862,13 @@ public class DnsNameResolver extends SimpleNameResolver { } static final class DnsCacheEntry { - final DnsResponse response; + final AddressedEnvelope response; final Throwable cause; volatile ScheduledFuture expirationFuture; - DnsCacheEntry(DnsResponse response) { - this.response = response.retain(); + @SuppressWarnings("unchecked") + DnsCacheEntry(AddressedEnvelope response) { + this.response = (AddressedEnvelope) response.retain(); cause = null; } @@ -862,7 +878,7 @@ public class DnsNameResolver extends SimpleNameResolver { } void release() { - DnsResponse response = this.response; + AddressedEnvelope response = this.response; if (response != null) { ReferenceCountUtil.safeRelease(response); } diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverContext.java index 538e2c9a3b..5b1b8531ba 100644 --- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverContext.java +++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolverContext.java @@ -17,13 +17,17 @@ package io.netty.resolver.dns; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufHolder; +import io.netty.channel.AddressedEnvelope; import io.netty.channel.socket.InternetProtocolFamily; -import io.netty.handler.codec.dns.DnsClass; +import io.netty.handler.codec.dns.DefaultDnsQuestion; +import io.netty.handler.codec.dns.DefaultDnsRecordDecoder; +import io.netty.handler.codec.dns.DnsSection; import io.netty.handler.codec.dns.DnsQuestion; -import io.netty.handler.codec.dns.DnsResource; +import io.netty.handler.codec.dns.DnsRawRecord; +import io.netty.handler.codec.dns.DnsRecord; +import io.netty.handler.codec.dns.DnsRecordType; import io.netty.handler.codec.dns.DnsResponse; -import io.netty.handler.codec.dns.DnsResponseDecoder; -import io.netty.handler.codec.dns.DnsType; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.Future; @@ -51,14 +55,15 @@ final class DnsNameResolverContext { private static final int INADDRSZ4 = 4; private static final int INADDRSZ6 = 16; - private static final FutureListener RELEASE_RESPONSE = new FutureListener() { - @Override - public void operationComplete(Future future) { - if (future.isSuccess()) { - future.getNow().release(); - } - } - }; + private static final FutureListener> RELEASE_RESPONSE = + new FutureListener>() { + @Override + public void operationComplete(Future> future) { + if (future.isSuccess()) { + future.getNow().release(); + } + } + }; private final DnsNameResolver parent; private final Promise promise; @@ -67,8 +72,10 @@ final class DnsNameResolverContext { private final int maxAllowedQueries; private final InternetProtocolFamily[] resolveAddressTypes; - private final Set> queriesInProgress = - Collections.newSetFromMap(new IdentityHashMap, Boolean>()); + private final Set>> queriesInProgress = + Collections.newSetFromMap( + new IdentityHashMap>, Boolean>()); + private List resolvedAddresses; private StringBuilder trace; private int allowedQueries; @@ -87,19 +94,19 @@ final class DnsNameResolverContext { void resolve() { for (InternetProtocolFamily f: resolveAddressTypes) { - final DnsType type; + final DnsRecordType type; switch (f) { case IPv4: - type = DnsType.A; + type = DnsRecordType.A; break; case IPv6: - type = DnsType.AAAA; + type = DnsRecordType.AAAA; break; default: throw new Error(); } - query(parent.nameServerAddresses, new DnsQuestion(hostname, type)); + query(parent.nameServerAddresses, new DefaultDnsQuestion(hostname, type)); } } @@ -110,12 +117,12 @@ final class DnsNameResolverContext { allowedQueries --; - final Future f = parent.query(nameServerAddresses, question); + final Future> f = parent.query(nameServerAddresses, question); queriesInProgress.add(f); - f.addListener(new FutureListener() { + f.addListener(new FutureListener>() { @Override - public void operationComplete(Future future) throws Exception { + public void operationComplete(Future> future) { queriesInProgress.remove(future); if (promise.isDone()) { @@ -135,12 +142,12 @@ final class DnsNameResolverContext { }); } - void onResponse(final DnsQuestion question, final DnsResponse response) { - final DnsType type = question.type(); + void onResponse(final DnsQuestion question, AddressedEnvelope response) { + final DnsRecordType type = question.type(); try { - if (type == DnsType.A || type == DnsType.AAAA) { + if (type == DnsRecordType.A || type == DnsRecordType.AAAA) { onResponseAorAAAA(type, question, response); - } else if (type == DnsType.CNAME) { + } else if (type == DnsRecordType.CNAME) { onResponseCNAME(question, response); } } finally { @@ -148,14 +155,19 @@ final class DnsNameResolverContext { } } - private void onResponseAorAAAA(DnsType qType, DnsQuestion question, DnsResponse response) { + private void onResponseAorAAAA( + DnsRecordType qType, DnsQuestion question, AddressedEnvelope envelope) { + // We often get a bunch of CNAMES as well when we asked for A/AAAA. + final DnsResponse response = envelope.content(); final Map cnames = buildAliasMap(response); + final int answerCount = response.count(DnsSection.ANSWER); boolean found = false; - for (DnsResource r: response.answers()) { - final DnsType type = r.type(); - if (type != DnsType.A && type != DnsType.AAAA) { + for (int i = 0; i < answerCount; i ++) { + final DnsRecord r = response.recordAt(DnsSection.ANSWER, i); + final DnsRecordType type = r.type(); + if (type != DnsRecordType.A && type != DnsRecordType.AAAA) { continue; } @@ -178,7 +190,11 @@ final class DnsNameResolverContext { } } - final ByteBuf content = r.content(); + if (!(r instanceof DnsRawRecord)) { + continue; + } + + final ByteBuf content = ((ByteBufHolder) r).content(); final int contentLen = content.readableBytes(); if (contentLen != INADDRSZ4 && contentLen != INADDRSZ6) { continue; @@ -204,20 +220,21 @@ final class DnsNameResolverContext { return; } - addTrace(response.sender(), "no matching " + qType + " record found"); + addTrace(envelope.sender(), "no matching " + qType + " record found"); // We aked for A/AAAA but we got only CNAME. if (!cnames.isEmpty()) { - onResponseCNAME(question, response, cnames, false); + onResponseCNAME(question, envelope, cnames, false); } } - private void onResponseCNAME(DnsQuestion question, DnsResponse response) { - onResponseCNAME(question, response, buildAliasMap(response), true); + private void onResponseCNAME(DnsQuestion question, AddressedEnvelope envelope) { + onResponseCNAME(question, envelope, buildAliasMap(envelope.content()), true); } private void onResponseCNAME( - DnsQuestion question, DnsResponse response, Map cnames, boolean trace) { + DnsQuestion question, AddressedEnvelope response, + Map cnames, boolean trace) { // Resolve the host name in the question into the real host name. final String name = question.name().toLowerCase(Locale.US); @@ -241,15 +258,22 @@ final class DnsNameResolverContext { } private static Map buildAliasMap(DnsResponse response) { + final int answerCount = response.count(DnsSection.ANSWER); Map cnames = null; - for (DnsResource r: response.answers()) { - final DnsType type = r.type(); - if (type != DnsType.CNAME) { + for (int i = 0; i < answerCount; i ++) { + final DnsRecord r = response.recordAt(DnsSection.ANSWER, i); + final DnsRecordType type = r.type(); + if (type != DnsRecordType.CNAME) { continue; } - String content = decodeDomainName(r.content()); - if (content == null) { + if (!(r instanceof DnsRawRecord)) { + continue; + } + + final ByteBuf recordContent = ((ByteBufHolder) r).content(); + final String domainName = decodeDomainName(recordContent); + if (domainName == null) { continue; } @@ -257,7 +281,7 @@ final class DnsNameResolverContext { cnames = new HashMap(); } - cnames.put(r.name().toLowerCase(Locale.US), content.toLowerCase(Locale.US)); + cnames.put(r.name().toLowerCase(Locale.US), domainName.toLowerCase(Locale.US)); } return cnames != null? cnames : Collections.emptyMap(); @@ -281,7 +305,7 @@ final class DnsNameResolverContext { if (!triedCNAME) { // As the last resort, try to query CNAME, just in case the name server has it. triedCNAME = true; - query(parent.nameServerAddresses, new DnsQuestion(hostname, DnsType.CNAME, DnsClass.IN)); + query(parent.nameServerAddresses, new DefaultDnsQuestion(hostname, DnsRecordType.CNAME)); return; } } @@ -319,8 +343,9 @@ final class DnsNameResolverContext { private void finishResolve() { if (!queriesInProgress.isEmpty()) { // If there are queries in progress, we should cancel it because we already finished the resolution. - for (Iterator> i = queriesInProgress.iterator(); i.hasNext();) { - Future f = i.next(); + for (Iterator>> i = queriesInProgress.iterator(); + i.hasNext();) { + Future> f = i.next(); i.remove(); if (!f.cancel(false)) { @@ -392,25 +417,32 @@ final class DnsNameResolverContext { } /** - * Adapted from {@link DnsResponseDecoder#readName(ByteBuf)}. + * Adapted from {@link DefaultDnsRecordDecoder#decodeName(ByteBuf)}. */ static String decodeDomainName(ByteBuf buf) { buf.markReaderIndex(); try { int position = -1; int checked = 0; - int length = buf.writerIndex(); - StringBuilder name = new StringBuilder(64); + final int end = buf.writerIndex(); + final StringBuilder name = new StringBuilder(buf.readableBytes() << 1); for (int len = buf.readUnsignedByte(); buf.isReadable() && len != 0; len = buf.readUnsignedByte()) { boolean pointer = (len & 0xc0) == 0xc0; if (pointer) { if (position == -1) { position = buf.readerIndex() + 1; } - buf.readerIndex((len & 0x3f) << 8 | buf.readUnsignedByte()); + + final int next = (len & 0x3f) << 8 | buf.readUnsignedByte(); + if (next >= end) { + // Should not happen. + return null; + } + buf.readerIndex(next); + // check for loops checked += 2; - if (checked >= length) { + if (checked >= end) { // Name contains a loop; give up. return null; } @@ -449,8 +481,8 @@ final class DnsNameResolverContext { trace.append(" CNAME "); trace.append(cname); - query(parent.nameServerAddresses, new DnsQuestion(cname, DnsType.A, DnsClass.IN)); - query(parent.nameServerAddresses, new DnsQuestion(cname, DnsType.AAAA, DnsClass.IN)); + query(parent.nameServerAddresses, new DefaultDnsQuestion(cname, DnsRecordType.A)); + query(parent.nameServerAddresses, new DefaultDnsQuestion(cname, DnsRecordType.AAAA)); } private void addTrace(InetSocketAddress nameServerAddr, String msg) { diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java index 88c04512a9..f3f35cc4ff 100644 --- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java +++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java @@ -17,13 +17,17 @@ package io.netty.resolver.dns; import io.netty.buffer.Unpooled; +import io.netty.channel.AddressedEnvelope; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; +import io.netty.handler.codec.dns.DatagramDnsQuery; +import io.netty.handler.codec.dns.DefaultDnsRawRecord; +import io.netty.handler.codec.dns.DnsSection; import io.netty.handler.codec.dns.DnsQuery; import io.netty.handler.codec.dns.DnsQuestion; -import io.netty.handler.codec.dns.DnsResource; +import io.netty.handler.codec.dns.DnsRecord; +import io.netty.handler.codec.dns.DnsRecordType; import io.netty.handler.codec.dns.DnsResponse; -import io.netty.handler.codec.dns.DnsType; import io.netty.resolver.dns.DnsNameResolver.DnsCacheEntry; import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.ScheduledFuture; @@ -43,10 +47,10 @@ final class DnsQueryContext { private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsQueryContext.class); private final DnsNameResolver parent; - private final Promise promise; + private final Promise> promise; private final int id; private final DnsQuestion question; - private final DnsResource optResource; + private final DnsRecord optResource; private final Iterator nameServerAddresses; private final boolean recursionDesired; @@ -57,7 +61,7 @@ final class DnsQueryContext { DnsQueryContext(DnsNameResolver parent, Iterable nameServerAddresses, - DnsQuestion question, Promise promise) { + DnsQuestion question, Promise> promise) { this.parent = parent; this.promise = promise; @@ -67,7 +71,8 @@ final class DnsQueryContext { recursionDesired = parent.isRecursionDesired(); maxTries = parent.maxTriesPerQuery(); remainingTries = maxTries; - optResource = new DnsResource("", DnsType.OPT, parent.maxPayloadSizeClass(), 0, Unpooled.EMPTY_BUFFER); + optResource = new DefaultDnsRawRecord( + StringUtil.EMPTY_STRING, DnsRecordType.OPT, parent.maxPayloadSize(), 0, Unpooled.EMPTY_BUFFER); this.nameServerAddresses = nameServerAddresses.iterator(); } @@ -89,7 +94,7 @@ final class DnsQueryContext { } } - Promise promise() { + Promise> promise() { return promise; } @@ -125,10 +130,10 @@ final class DnsQueryContext { remainingTries --; final InetSocketAddress nameServerAddr = nameServerAddresses.next(); - final DnsQuery query = new DnsQuery(id, nameServerAddr); - query.addQuestion(question); - query.header().setRecursionDesired(recursionDesired); - query.addAdditionalResource(optResource); + final DatagramDnsQuery query = new DatagramDnsQuery(null, nameServerAddr, id); + query.setRecursionDesired(recursionDesired); + query.setRecord(DnsSection.QUESTION, question); + query.setRecord(DnsSection.ADDITIONAL, optResource); if (logger.isDebugEnabled()) { logger.debug("{} WRITE: [{}: {}], {}", parent.ch, id, nameServerAddr, question); diff --git a/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java b/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java index ac7a1d141a..42a40c57fd 100644 --- a/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java +++ b/resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 The Netty Project + * 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 @@ -13,18 +13,21 @@ * License for the specific language governing permissions and limitations * under the License. */ - package io.netty.resolver.dns; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufHolder; +import io.netty.channel.AddressedEnvelope; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.InternetProtocolFamily; import io.netty.channel.socket.nio.NioDatagramChannel; -import io.netty.handler.codec.dns.DnsQuestion; -import io.netty.handler.codec.dns.DnsResource; +import io.netty.handler.codec.dns.DefaultDnsQuestion; +import io.netty.handler.codec.dns.DnsSection; +import io.netty.handler.codec.dns.DnsRecord; +import io.netty.handler.codec.dns.DnsRecordType; import io.netty.handler.codec.dns.DnsResponse; import io.netty.handler.codec.dns.DnsResponseCode; -import io.netty.handler.codec.dns.DnsType; import io.netty.util.concurrent.Future; import io.netty.util.internal.StringUtil; import io.netty.util.internal.ThreadLocalRandom; @@ -181,7 +184,7 @@ public class DnsNameResolverTest { EXCLUSIONS_RESOLVE_A, "akamaihd.net", "googleusercontent.com", - ""); + StringUtil.EMPTY_STRING); } /** @@ -236,7 +239,7 @@ public class DnsNameResolverTest { "people.com.cn", "googleusercontent.com", "blogspot.in", - ""); + StringUtil.EMPTY_STRING); } private static final EventLoopGroup group = new NioEventLoopGroup(1); @@ -368,8 +371,8 @@ public class DnsNameResolverTest { public void testQueryMx() throws Exception { assertThat(resolver.isRecursionDesired(), is(true)); - Map> futures = - new LinkedHashMap>(); + Map>> futures = + new LinkedHashMap>>(); for (String name: DOMAINS) { if (EXCLUSIONS_QUERY_MX.contains(name)) { continue; @@ -378,30 +381,36 @@ public class DnsNameResolverTest { queryMx(futures, name); } - for (Entry> e: futures.entrySet()) { + for (Entry>> e: futures.entrySet()) { String hostname = e.getKey(); - DnsResponse response = e.getValue().sync().getNow(); + AddressedEnvelope envelope = e.getValue().sync().getNow(); + DnsResponse response = envelope.content(); - assertThat(response.header().responseCode(), is(DnsResponseCode.NOERROR)); - List mxList = new ArrayList(); - for (DnsResource r: response.answers()) { - if (r.type() == DnsType.MX) { + assertThat(response.code(), is(DnsResponseCode.NOERROR)); + + final int answerCount = response.count(DnsSection.ANSWER); + final List mxList = new ArrayList(answerCount); + for (int i = 0; i < answerCount; i ++) { + final DnsRecord r = response.recordAt(DnsSection.ANSWER, i); + if (r.type() == DnsRecordType.MX) { mxList.add(r); } } assertThat(mxList.size(), is(greaterThan(0))); StringBuilder buf = new StringBuilder(); - for (DnsResource r: mxList) { + for (DnsRecord r: mxList) { + ByteBuf recordContent = ((ByteBufHolder) r).content(); + buf.append(StringUtil.NEWLINE); buf.append('\t'); buf.append(r.name()); buf.append(' '); - buf.append(r.type()); + buf.append(r.type().name()); buf.append(' '); - buf.append(r.content().readUnsignedShort()); + buf.append(recordContent.readUnsignedShort()); buf.append(' '); - buf.append(DnsNameResolverContext.decodeDomainName(r.content())); + buf.append(DnsNameResolverContext.decodeDomainName(recordContent)); } logger.info("{} has the following MX records:{}", hostname, buf); @@ -409,14 +418,17 @@ public class DnsNameResolverTest { } } - private static void resolve(Map> futures, String hostname) { + private static void resolve( + Map> futures, String hostname) { InetSocketAddress unresolved = InetSocketAddress.createUnresolved(hostname, ThreadLocalRandom.current().nextInt(65536)); futures.put(unresolved, resolver.resolve(unresolved)); } - private static void queryMx(Map> futures, String hostname) throws Exception { - futures.put(hostname, resolver.query(new DnsQuestion(hostname, DnsType.MX))); + private static void queryMx( + Map>> futures, + String hostname) throws Exception { + futures.put(hostname, resolver.query(new DefaultDnsQuestion(hostname, DnsRecordType.MX))); } } diff --git a/transport/src/main/java/io/netty/channel/DefaultAddressedEnvelope.java b/transport/src/main/java/io/netty/channel/DefaultAddressedEnvelope.java index bc561399f4..12cdabb862 100644 --- a/transport/src/main/java/io/netty/channel/DefaultAddressedEnvelope.java +++ b/transport/src/main/java/io/netty/channel/DefaultAddressedEnvelope.java @@ -43,6 +43,10 @@ public class DefaultAddressedEnvelope implements Add throw new NullPointerException("message"); } + if (recipient == null && sender == null) { + throw new NullPointerException("recipient and sender"); + } + this.message = message; this.sender = sender; this.recipient = recipient;