Revamp DNS codec

Motivation:

There are various known issues in netty-codec-dns:

- Message types are not interfaces, which can make it difficult for a
  user to implement his/her own message implementation.
- Some class names and field names do not match with the terms in the
  RFC.
- The support for decoding a DNS record was limited. A user had to
  encode and decode by him/herself.
- The separation of DnsHeader from DnsMessage was unnecessary, although
  it is fine conceptually.
- Buffer leak caused by DnsMessage was difficult to analyze, because the
  leak detector tracks down the underlying ByteBuf rather than the
  DnsMessage itself.
- DnsMessage assumes DNS-over-UDP.
- To send an EDNS message, a user have to create a new DNS record class
  instance unnecessarily.

Modifications:

- Make all message types interfaces and add default implementations
- Rename some classes, properties, and constants to match the RFCs
  - DnsResource -> DnsRecord
  - DnsType -> DnsRecordType
  - and many more
- Remove DnsClass and use an integer to support EDNS better
- Add DnsRecordEncoder/DnsRecordDecoder and their default
  implementations
  - DnsRecord does not require RDATA to be ByteBuf anymore.
  - Add DnsRawRecord as the catch-all record type
- Merge DnsHeader into DnsMessage
- Make ResourceLeakDetector track AbstractDnsMessage
- Remove DnsMessage.sender/recipient properties
  - Wrap DnsMessage with AddressedEnvelope
  - Add DatagramDnsQuest and DatagramDnsResponse for ease of use
  - Rename DnsQueryEncoder to DatagramDnsQueryEncoder
  - Rename DnsResponseDecoder to DatagramDnsResponseDecoder
- Miscellaneous changes
  - Add StringUtil.TAB

Result:

- Cleaner APi
- Can support DNS-over-TCP more easily in the future
- Reduced memory footprint in the default DnsQuery/Response
  implementations
- Better leak tracking for DnsMessages
- Possibility to introduce new DnsRecord types in the future and provide
  full record encoder/decoder implementation.
- No unnecessary instantiation for an EDNS pseudo resource record
This commit is contained in:
Trustin Lee 2015-03-16 15:46:14 +09:00
parent 8271c8afcc
commit 63a02fc04e
44 changed files with 3137 additions and 1865 deletions

View File

@ -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<DnsMessage> leakDetector =
new ResourceLeakDetector<DnsMessage>(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<DnsRecord> recordList = (List<DnsRecord>) 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 extends DnsRecord> T recordAt(DnsSection section) {
return recordAt(sectionOrdinal(section));
}
private <T extends DnsRecord> 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<DnsRecord> recordList = (List<DnsRecord>) records;
if (recordList.isEmpty()) {
return null;
}
return castRecord(recordList.get(0));
}
@Override
public <T extends DnsRecord> T recordAt(DnsSection section, int index) {
return recordAt(sectionOrdinal(section), index);
}
private <T extends DnsRecord> 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<DnsRecord> recordList = (List<DnsRecord>) 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 extends DnsRecord> T setRecord(DnsSection section, int index, DnsRecord record) {
return setRecord(sectionOrdinal(section), index, record);
}
private <T extends DnsRecord> 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<DnsRecord> recordList = (List<DnsRecord>) 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<DnsRecord> recordList = newRecordList();
recordList.add(castRecord(records));
recordList.add(record);
setSection(section, recordList);
return;
}
@SuppressWarnings("unchecked")
final List<DnsRecord> recordList = (List<DnsRecord>) 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<DnsRecord> 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<DnsRecord> recordList = (List<DnsRecord>) records;
recordList.add(index, record);
}
@Override
public <T extends DnsRecord> T removeRecord(DnsSection section, int index) {
return removeRecord(sectionOrdinal(section), index);
}
private <T extends DnsRecord> 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<DnsRecord> recordList = (List<DnsRecord>) 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<DnsRecord> list = (List<DnsRecord>) 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 extends DnsRecord> T castRecord(Object record) {
return (T) record;
}
private static ArrayList<DnsRecord> newRecordList() {
return new ArrayList<DnsRecord>(2);
}
}

View File

@ -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:
* <ul>
* <li>{@link #CLASS_IN}</li>
* <li>{@link #CLASS_CSNET}</li>
* <li>{@link #CLASS_CHAOS}</li>
* <li>{@link #CLASS_HESIOD}</li>
* <li>{@link #CLASS_NONE}</li>
* <li>{@link #CLASS_ANY}</li>
* </ul>
* @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();
}
}

View File

@ -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<DatagramDnsQuery, InetSocketAddress> {
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<?, SocketAddress> that = (AddressedEnvelope<?, SocketAddress>) 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;
}
}

View File

@ -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<AddressedEnvelope<DnsQuery, InetSocketAddress>> {
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<DnsQuery, InetSocketAddress> in, List<Object> 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<DnsQuery, InetSocketAddress> msg) throws Exception {
return ctx.alloc().ioBuffer(1024);
}
/**
* Encodes the header that is always 12 bytes long.
*
* @param query
* the query header being encoded
* @param buf
* the buffer the encoded data should be written to
*/
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);
}
}
}

View File

@ -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<DatagramDnsResponse, InetSocketAddress> {
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<?, SocketAddress> that = (AddressedEnvelope<?, SocketAddress>) 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;
}
}

View File

@ -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<DatagramPacket> {
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<Object> 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));
}
}
}

View File

@ -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();
}
}

View File

@ -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:
* <ul>
* <li>{@link #CLASS_IN}</li>
* <li>{@link #CLASS_CSNET}</li>
* <li>{@link #CLASS_CHAOS}</li>
* <li>{@link #CLASS_HESIOD}</li>
* <li>{@link #CLASS_NONE}</li>
* <li>{@link #CLASS_ANY}</li>
* </ul>
*/
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();
}
}

View File

@ -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:
* <ul>
* <li>{@link #CLASS_IN}</li>
* <li>{@link #CLASS_CSNET}</li>
* <li>{@link #CLASS_CHAOS}</li>
* <li>{@link #CLASS_HESIOD}</li>
* <li>{@link #CLASS_NONE}</li>
* <li>{@link #CLASS_ANY}</li>
* </ul>
* @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()? "<root>" : 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();
}
}

View File

@ -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 extends DnsRecord> 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);
}
}

View File

@ -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
}
}

View File

@ -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();
}
}

View File

@ -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<DnsClass> {
/**
* 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;
}
}

View File

@ -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());
}
}

View File

@ -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. <strong>Note: inverse queries have been
* obsoleted since RFC 3425, and are not necessarily supported.</strong>
*/
@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;
}
}

View File

@ -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<DnsQuestion> questions;
private List<DnsResource> answers;
private List<DnsResource> authority;
private List<DnsResource> 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<DnsQuestion> 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<DnsResource> 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<DnsResource> 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<DnsResource> 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<DnsResource>();
}
answers.add(answer);
return this;
}
<T extends DnsRecord> 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<DnsQuestion>();
}
questions.add(question);
return this;
}
<T extends DnsRecord> 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<DnsResource>();
}
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<DnsResource>();
}
additional.add(resource);
return this;
}
<T extends DnsRecord> 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 extends DnsRecord> 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);
}

View File

@ -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<?, SocketAddress> envelope = (AddressedEnvelope<?, SocketAddress>) 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() { }
}

View File

@ -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 <a href="https://tools.ietf.org/html/rfc2929">RFC2929</a>.
*/
public class DnsOpCode implements Comparable<DnsOpCode> {
/**
* The 'Query' DNS OpCode, as defined in <a href="https://tools.ietf.org/html/rfc1035">RFC1035</a>.
*/
public static final DnsOpCode QUERY = new DnsOpCode(0x00, "QUERY");
/**
* The 'IQuery' DNS OpCode, as defined in <a href="https://tools.ietf.org/html/rfc1035">RFC1035</a>.
*/
public static final DnsOpCode IQUERY = new DnsOpCode(0x01, "IQUERY");
/**
* The 'Status' DNS OpCode, as defined in <a href="https://tools.ietf.org/html/rfc1035">RFC1035</a>.
*/
public static final DnsOpCode STATUS = new DnsOpCode(0x02, "STATUS");
/**
* The 'Notify' DNS OpCode, as defined in <a href="https://tools.ietf.org/html/rfc1996">RFC1996</a>.
*/
public static final DnsOpCode NOTIFY = new DnsOpCode(0x04, "NOTIFY");
/**
* The 'Update' DNS OpCode, as defined in <a href="https://tools.ietf.org/html/rfc2136">RFC2136</a>.
*/
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;
}
}

View File

@ -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);
}

View File

@ -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<DnsQuery> {
@Override
protected void encode(ChannelHandlerContext ctx, DnsQuery query, List<Object> out) throws Exception {
ByteBuf buf = ctx.alloc().buffer();
encodeHeader(query.header(), buf);
List<DnsQuestion> 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
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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:
* <ul>
* <li>{@link #CLASS_IN}</li>
* <li>{@link #CLASS_CSNET}</li>
* <li>{@link #CLASS_CHAOS}</li>
* <li>{@link #CLASS_HESIOD}</li>
* <li>{@link #CLASS_NONE}</li>
* <li>{@link #CLASS_ANY}</li>
* </ul>
*/
int dnsClass();
/**
* Returns the time to live after reading for this resource record.
*/
long timeToLive();
}

View File

@ -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 extends DnsRecord> T decodeRecord(ByteBuf in) throws Exception;
}

View File

@ -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;
}

View File

@ -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<DnsType> {
public class DnsRecordType implements Comparable<DnsRecordType> {
/**
* 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<DnsType> {
* 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<DnsType> {
* 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<DnsType> {
* 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<DnsType> {
* 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<DnsType> {
* 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<DnsType> {
* 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<DnsType> {
* 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<DnsType> {
* 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<DnsType> {
* 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<DnsType> {
* 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<DnsType> {
* 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<String, DnsType> BY_NAME = new HashMap<String, DnsType>();
private static final IntObjectHashMap<DnsType> BY_TYPE = new IntObjectHashMap<DnsType>();
private static final Map<String, DnsRecordType> BY_NAME = new HashMap<String, DnsRecordType>();
private static final IntObjectHashMap<DnsRecordType> BY_TYPE = new IntObjectHashMap<DnsRecordType>();
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<DnsType> {
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<DnsType> {
@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;
}
}

View File

@ -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;
}
}

View File

@ -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 message.
*/
public interface DnsResponse extends DnsMessage {
/**
* A DNS response packet which is sent to a client after a server receives a
* query.
* Returns {@code true} if responding server is authoritative for the domain
* name in the query 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;
}
boolean isAuthoritativeAnswer();
/**
* The {@link InetSocketAddress} of the sender of this {@link DnsResponse}
* Set to {@code true} if responding server is authoritative for the domain
* name in the query message.
*
* @param authoritativeAnswer flag for authoritative answer
*/
public InetSocketAddress sender() {
return sender;
}
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);
}

View File

@ -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,110 +15,118 @@
*/
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 <a href="https://tools.ietf.org/html/rfc2929">RFC2929</a>.
*/
public final class DnsResponseCode implements Comparable<DnsResponseCode> {
public class DnsResponseCode implements Comparable<DnsResponseCode> {
/**
* ID 0, no error
* The 'NoError' DNS RCODE (0), as defined in <a href="https://tools.ietf.org/html/rfc1035">RFC1035</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc1035">RFC1035</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc1035">RFC1035</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc1035">RFC1035</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc1035">RFC1035</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc1035">RFC1035</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc2136">RFC2136</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc2136">RFC2136</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc2136">RFC2136</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc2136">RFC2136</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc2136">RFC2136</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc2671">RFC2671</a>
* and <a href="https://tools.ietf.org/html/rfc2845">RFC2845</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc2845">RFC2845</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc2845">RFC2845</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc2930">RFC2930</a>.
*/
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 <a href="https://tools.ietf.org/html/rfc2930">RFC2930</a>.
*/
public static final DnsResponseCode BADNAME = new DnsResponseCode(20, "BADNAME");
/**
* The 'BADALG' DNS RCODE (21), as defined in <a href="https://tools.ietf.org/html/rfc2930">RFC2930</a>.
*/
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;
return FORMERR;
case 2:
return SERVFAIL;
case 3:
return NXDOMAIN;
case 4:
return NOTIMPL;
return NOTIMP;
case 5:
return REFUSED;
case 6:
@ -131,43 +139,59 @@ public final class DnsResponseCode implements Comparable<DnsResponseCode> {
return NOTAUTH;
case 10:
return NOTZONE;
case 11:
return BADVERS;
case 12:
return BADSIG;
case 13:
case 16:
return BADVERS_OR_BADSIG;
case 17:
return BADKEY;
case 14:
case 18:
return BADTIME;
case 19:
return BADMODE;
case 20:
return BADNAME;
case 21:
return BADALG;
default:
return new DnsResponseCode(responseCode, null);
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<DnsResponseCode> {
return false;
}
return code() == ((DnsResponseCode) o).code();
return intValue() == ((DnsResponseCode) o).intValue();
}
/**
@ -183,9 +207,10 @@ public final class DnsResponseCode implements Comparable<DnsResponseCode> {
*/
@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;
}
}

View File

@ -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<DatagramPacket> {
@Override
protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List<Object> 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<DnsResource> 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);
}
}

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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;

View File

@ -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<DnsQuery> queries = new ArrayList<DnsQuery>(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();

View File

@ -25,13 +25,13 @@ import java.util.List;
import static org.junit.Assert.*;
public class DnsClassTest {
public class DnsRecordTypeTest {
private static List<DnsClass> allTypes() throws Exception {
List<DnsClass> result = new ArrayList<DnsClass>();
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<DnsRecordType> allTypes() throws Exception {
List<DnsRecordType> result = new ArrayList<DnsRecordType>();
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<DnsClass>(allTypes()).size());
allTypes().size(), new HashSet<DnsRecordType>(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);
}
}
}

View File

@ -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<DnsResponse, InetSocketAddress> 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)));

View File

@ -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<DnsType> allTypes() throws Exception {
List<DnsType> result = new ArrayList<DnsType>();
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<DnsType>(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);
}
}
}

View File

@ -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.

View File

@ -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<InetSocketAddress> {
}
}
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<InetSocketAddress> nameServerAddresses;
final ChannelFuture bindFuture;
@ -117,7 +119,6 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
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<InetSocketAddress> {
}
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<InetSocketAddress> {
/**
* Sends a DNS query with the specified question.
*/
public Future<DnsResponse> query(DnsQuestion question) {
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(DnsQuestion question) {
return query(nameServerAddresses, question);
}
/**
* Sends a DNS query with the specified question.
*/
public Future<DnsResponse> query(DnsQuestion question, Promise<DnsResponse> promise) {
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
DnsQuestion question, Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
return query(nameServerAddresses, question, promise);
}
/**
* Sends a DNS query with the specified question using the specified name server list.
*/
public Future<DnsResponse> query(Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question) {
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question) {
if (nameServerAddresses == null) {
throw new NullPointerException("nameServerAddresses");
}
@ -693,15 +691,18 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
return eventLoop.newFailedFuture(cachedResult.cause);
}
} else {
return query0(nameServerAddresses, question, eventLoop.<DnsResponse>newPromise());
return query0(
nameServerAddresses, question,
eventLoop.<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise());
}
}
/**
* Sends a DNS query with the specified question using the specified name server list.
*/
public Future<DnsResponse> query(
Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question, Promise<DnsResponse> promise) {
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question,
Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
if (nameServerAddresses == null) {
throw new NullPointerException("nameServerAddresses");
@ -716,23 +717,25 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
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<DnsResponse> query0(
Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question, Promise<DnsResponse> promise) {
private Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query0(
Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question,
Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
final Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> 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<InetSocketAddress> {
}
}
@SuppressWarnings("unchecked")
private static Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> cast(Promise<?> promise) {
return (Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>>) promise;
}
@SuppressWarnings("unchecked")
private static AddressedEnvelope<DnsResponse, InetSocketAddress> cast(AddressedEnvelope<?, ?> envelope) {
return (AddressedEnvelope<DnsResponse, InetSocketAddress>) envelope;
}
private final class DnsResponseHandler extends ChannelInboundHandlerAdapter {
@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<InetSocketAddress> {
return;
}
final List<DnsQuestion> 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<InetSocketAddress> {
timeoutFuture.cancel(false);
}
if (res.header().responseCode() == DnsResponseCode.NOERROR) {
if (res.code() == DnsResponseCode.NOERROR) {
cache(q, res);
promises.set(queryId, null);
Promise<DnsResponse> qPromise = qCtx.promise();
Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> 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<? extends DnsResponse, InetSocketAddress> res) {
final int maxTtl = maxTtl();
if (maxTtl == 0) {
return;
@ -827,8 +839,11 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
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<InetSocketAddress> {
}
static final class DnsCacheEntry {
final DnsResponse response;
final AddressedEnvelope<DnsResponse, InetSocketAddress> response;
final Throwable cause;
volatile ScheduledFuture<?> expirationFuture;
DnsCacheEntry(DnsResponse response) {
this.response = response.retain();
@SuppressWarnings("unchecked")
DnsCacheEntry(AddressedEnvelope<? extends DnsResponse, InetSocketAddress> response) {
this.response = (AddressedEnvelope<DnsResponse, InetSocketAddress>) response.retain();
cause = null;
}
@ -862,7 +878,7 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
}
void release() {
DnsResponse response = this.response;
AddressedEnvelope<DnsResponse, InetSocketAddress> response = this.response;
if (response != null) {
ReferenceCountUtil.safeRelease(response);
}

View File

@ -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,9 +55,10 @@ final class DnsNameResolverContext {
private static final int INADDRSZ4 = 4;
private static final int INADDRSZ6 = 16;
private static final FutureListener<DnsResponse> RELEASE_RESPONSE = new FutureListener<DnsResponse>() {
private static final FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>> RELEASE_RESPONSE =
new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
@Override
public void operationComplete(Future<DnsResponse> future) {
public void operationComplete(Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> future) {
if (future.isSuccess()) {
future.getNow().release();
}
@ -67,8 +72,10 @@ final class DnsNameResolverContext {
private final int maxAllowedQueries;
private final InternetProtocolFamily[] resolveAddressTypes;
private final Set<Future<DnsResponse>> queriesInProgress =
Collections.newSetFromMap(new IdentityHashMap<Future<DnsResponse>, Boolean>());
private final Set<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> queriesInProgress =
Collections.newSetFromMap(
new IdentityHashMap<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>, Boolean>());
private List<InetAddress> 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<DnsResponse> f = parent.query(nameServerAddresses, question);
final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = parent.query(nameServerAddresses, question);
queriesInProgress.add(f);
f.addListener(new FutureListener<DnsResponse>() {
f.addListener(new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
@Override
public void operationComplete(Future<DnsResponse> future) throws Exception {
public void operationComplete(Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> 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<DnsResponse, InetSocketAddress> 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<DnsResponse, InetSocketAddress> envelope) {
// We often get a bunch of CNAMES as well when we asked for A/AAAA.
final DnsResponse response = envelope.content();
final Map<String, String> 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<DnsResponse, InetSocketAddress> envelope) {
onResponseCNAME(question, envelope, buildAliasMap(envelope.content()), true);
}
private void onResponseCNAME(
DnsQuestion question, DnsResponse response, Map<String, String> cnames, boolean trace) {
DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> response,
Map<String, String> 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<String, String> buildAliasMap(DnsResponse response) {
final int answerCount = response.count(DnsSection.ANSWER);
Map<String, String> 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<String, String>();
}
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.<String, String>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<Future<DnsResponse>> i = queriesInProgress.iterator(); i.hasNext();) {
Future<DnsResponse> f = i.next();
for (Iterator<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> i = queriesInProgress.iterator();
i.hasNext();) {
Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> 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) {

View File

@ -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<DnsResponse> promise;
private final Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> promise;
private final int id;
private final DnsQuestion question;
private final DnsResource optResource;
private final DnsRecord optResource;
private final Iterator<InetSocketAddress> nameServerAddresses;
private final boolean recursionDesired;
@ -57,7 +61,7 @@ final class DnsQueryContext {
DnsQueryContext(DnsNameResolver parent,
Iterable<InetSocketAddress> nameServerAddresses,
DnsQuestion question, Promise<DnsResponse> promise) {
DnsQuestion question, Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> 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<DnsResponse> promise() {
Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> 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);

View File

@ -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<String, Future<DnsResponse>> futures =
new LinkedHashMap<String, Future<DnsResponse>>();
Map<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> futures =
new LinkedHashMap<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>>();
for (String name: DOMAINS) {
if (EXCLUSIONS_QUERY_MX.contains(name)) {
continue;
@ -378,30 +381,36 @@ public class DnsNameResolverTest {
queryMx(futures, name);
}
for (Entry<String, Future<DnsResponse>> e: futures.entrySet()) {
for (Entry<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> e: futures.entrySet()) {
String hostname = e.getKey();
DnsResponse response = e.getValue().sync().getNow();
AddressedEnvelope<DnsResponse, InetSocketAddress> envelope = e.getValue().sync().getNow();
DnsResponse response = envelope.content();
assertThat(response.header().responseCode(), is(DnsResponseCode.NOERROR));
List<DnsResource> mxList = new ArrayList<DnsResource>();
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<DnsRecord> mxList = new ArrayList<DnsRecord>(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<InetSocketAddress, Future<InetSocketAddress>> futures, String hostname) {
private static void resolve(
Map<InetSocketAddress, Future<InetSocketAddress>> futures, String hostname) {
InetSocketAddress unresolved =
InetSocketAddress.createUnresolved(hostname, ThreadLocalRandom.current().nextInt(65536));
futures.put(unresolved, resolver.resolve(unresolved));
}
private static void queryMx(Map<String, Future<DnsResponse>> futures, String hostname) throws Exception {
futures.put(hostname, resolver.query(new DnsQuestion(hostname, DnsType.MX)));
private static void queryMx(
Map<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> futures,
String hostname) throws Exception {
futures.put(hostname, resolver.query(new DefaultDnsQuestion(hostname, DnsRecordType.MX)));
}
}

View File

@ -43,6 +43,10 @@ public class DefaultAddressedEnvelope<M, A extends SocketAddress> 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;