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 b122faea68
commit c27c487dc9
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, * 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 * version 2.0 (the "License"); you may not use this file except in compliance
@ -15,225 +15,142 @@
*/ */
package io.netty.handler.codec.dns; package io.netty.handler.codec.dns;
import io.netty.util.AbstractReferenceCounted; import io.netty.util.ReferenceCounted;
import io.netty.util.ReferenceCountUtil;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/** /**
* The message super-class which contains core information concerning DNS * The superclass which contains core information concerning a {@link DnsQuery} and a {@link DnsResponse}.
* packets, both outgoing and incoming.
*/ */
public abstract class DnsMessage extends AbstractReferenceCounted { public interface DnsMessage extends ReferenceCounted {
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);
}
/** /**
* Returns the header belonging to this message. * Returns the {@code ID} of this DNS message.
*/ */
public DnsHeader header() { int id();
return header;
}
/** /**
* Returns a list of all the questions in this message. * Sets the {@code ID} of this DNS message.
*/ */
public List<DnsQuestion> questions() { DnsMessage setId(int id);
if (questions == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(questions);
}
/** /**
* Returns a list of all the answer resource records in this message. * Returns the {@code opCode} of this DNS message.
*/ */
public List<DnsResource> answers() { DnsOpCode opCode();
if (answers == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(answers);
}
/** /**
* Returns a list of all the authority resource records in this message. * Sets the {@code opCode} of this DNS message.
*/ */
public List<DnsResource> authorityResources() { DnsMessage setOpCode(DnsOpCode opCode);
if (authority == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(authority);
}
/** /**
* 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() { boolean isRecursionDesired();
if (additional == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(additional);
}
/** /**
* 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 * @return {@code null} if this message doesn't have any records in the specified {@code section}
* the answer resource record to be added
* @return the message to allow method chaining
*/ */
public DnsMessage addAnswer(DnsResource answer) { <T extends DnsRecord> T recordAt(DnsSection section);
if (answers == null) {
answers = new LinkedList<DnsResource>();
}
answers.add(answer);
return this;
}
/** /**
* 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 * @throws IndexOutOfBoundsException if the specified {@code index} is out of bounds
* the question to be added
* @return the message to allow method chaining
*/ */
public DnsMessage addQuestion(DnsQuestion question) { <T extends DnsRecord> T recordAt(DnsSection section, int index);
if (questions == null) {
questions = new LinkedList<DnsQuestion>();
}
questions.add(question);
return this;
}
/** /**
* Adds an authority resource record to this message. * 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},
* @param resource * the specified {@code record} must be a {@link DnsQuestion}.
* the authority resource record to be added
* @return the message to allow method chaining
*/ */
public DnsMessage addAuthorityResource(DnsResource resource) { DnsMessage setRecord(DnsSection section, DnsRecord record);
if (authority == null) {
authority = new LinkedList<DnsResource>();
}
authority.add(resource);
return this;
}
/** /**
* 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 * @return the old record
* the additional resource record to be added * @throws IndexOutOfBoundsException if the specified {@code index} is out of bounds
* @return the message to allow method chaining
*/ */
public DnsMessage addAdditionalResource(DnsResource resource) { <T extends DnsRecord> T setRecord(DnsSection section, int index, DnsRecord record);
if (additional == null) {
additional = new LinkedList<DnsResource>(); /**
} * Adds the specified {@code record} at the end of the specified {@code section} of this DNS message.
additional.add(resource); * When the specified {@code section} is {@link DnsSection#QUESTION}, the specified {@code record}
return this; * 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 @Override
protected void deallocate() { DnsMessage touch();
// NOOP
}
@Override @Override
public boolean release() { DnsMessage touch(Object hint);
release(questions());
release(answers());
release(additionalResources());
release(authorityResources());
return super.release();
}
private static void release(List<?> resources) {
for (Object resource: resources) {
ReferenceCountUtil.release(resource);
}
}
@Override @Override
public boolean release(int decrement) { DnsMessage retain();
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);
}
}
@Override @Override
public DnsMessage touch(Object hint) { DnsMessage retain(int increment);
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);
} }

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, * 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 * version 2.0 (the "License"); you may not use this file except in compliance
@ -15,89 +15,46 @@
*/ */
package io.netty.handler.codec.dns; 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 * A DNS query message.
* with information answering a DnsQuery's questions.
*/ */
public class DnsQuery extends DnsMessage { public interface DnsQuery extends DnsMessage {
@Override
private final InetSocketAddress recipient; DnsQuery setId(int id);
/**
* 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;
}
@Override @Override
public DnsQuery addAnswer(DnsResource answer) { DnsQuery setOpCode(DnsOpCode opCode);
super.addAnswer(answer);
return this;
}
@Override @Override
public DnsQuery addQuestion(DnsQuestion question) { DnsQuery setRecursionDesired(boolean recursionDesired);
super.addQuestion(question);
return this;
}
@Override @Override
public DnsQuery addAuthorityResource(DnsResource resource) { DnsQuery setZ(int z);
super.addAuthorityResource(resource);
return this;
}
@Override @Override
public DnsQuery addAdditionalResource(DnsResource resource) { DnsQuery setRecord(DnsSection section, DnsRecord record);
super.addAdditionalResource(resource);
return this;
}
@Override @Override
public DnsQuery touch(Object hint) { DnsQuery addRecord(DnsSection section, DnsRecord record);
super.touch(hint);
return this;
}
@Override @Override
public DnsQuery retain() { DnsQuery addRecord(DnsSection section, int index, DnsRecord record);
super.retain();
return this;
}
@Override @Override
public DnsQuery retain(int increment) { DnsQuery clear(DnsSection section);
super.retain(increment);
return this;
}
@Override @Override
public DnsQuery touch() { DnsQuery clear();
super.touch();
return this;
}
@Override @Override
public DnsQueryHeader header() { DnsQuery touch();
return (DnsQueryHeader) super.header();
}
@Override @Override
protected DnsQueryHeader newHeader(int id) { DnsQuery touch(Object hint);
return new DnsQueryHeader(this, id);
} @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, * 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 * version 2.0 (the "License"); you may not use this file except in compliance
@ -15,58 +15,13 @@
*/ */
package io.netty.handler.codec.dns; 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 DNS question.
* 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.
*/ */
public final class DnsQuestion extends DnsEntry { public interface DnsQuestion extends DnsRecord {
/** /**
* Constructs a question with the default class IN (Internet). * An unused property. This method will always return {@code 0}.
*
* @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
*/ */
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 @Override
public boolean equals(Object other) { long timeToLive();
if (!(other instanceof DnsQuestion)) {
return false;
}
return super.equals(other);
}
@Override
public int hashCode() {
return super.hashCode();
}
} }

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, * 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 * version 2.0 (the "License"); you may not use this file except in compliance
@ -16,32 +16,33 @@
package io.netty.handler.codec.dns; package io.netty.handler.codec.dns;
import io.netty.util.collection.IntObjectHashMap; import io.netty.util.collection.IntObjectHashMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* Represents a DNS record type. * 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 * 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, * to map hostnames to an IP address of the host, but also used for DNSBLs,
* storing subnet masks in RFC 1101, etc. * 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 * Name server record RFC 1035 Delegates a DNS zone to use the given
* authoritative name servers * 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 * Canonical name record RFC 1035 Alias of one name to another: the DNS
* lookup will continue by retrying the lookup with the new name. * 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 * 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, * server, the email of the domain administrator, the domain serial number,
* and several timers relating to refreshing the zone. * 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 * 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 * use is for implementing reverse DNS lookups, but other uses include such
* things as DNS-SD. * 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 * Mail exchange record RFC 1035 Maps a domain name to a list of message
* transfer agents for that domain. * 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 * 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, * opportunistic encryption, Sender Policy Framework, DKIM, DMARC DNS-SD,
* etc. * 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 * Responsible person record RFC 1183 Information about the responsible
* person(s) for the domain. Usually an email address with the @ replaced by * person(s) for the domain. Usually an email address with the @ replaced by
* a . * 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. * 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 * their local domain. A subtype of this record is used by the obsolete
* DCE/DFS file system. * 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 * 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 * TKEY (RFC 2930). RFC 3755 designated RRSIG as the replacement for SIG for
* use within DNSSEC. * 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 * 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 within DNSSEC. RFC 4025 designates IPSECKEY as the
* replacement for use with IPsec. * 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 * IPv6 address record RFC 3596 Returns a 128-bit IPv6 address, most
* commonly used to map hostnames to an IP address of the host. * 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 * Location record RFC 1876 Specifies a geographical location associated
* with a domain name. * 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 * Service locator RFC 2782 Generalized service location record, used for
* newer protocols instead of creating protocol-specific records such as MX. * 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 * Naming Authority Pointer record RFC 3403 Allows regular expression based
* rewriting of domain names which can then be used as URIs, further domain * rewriting of domain names which can then be used as URIs, further domain
* names to lookups, etc. * 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 * 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 * Informational status, rather than being on the IETF standards-track. It
* has always had limited deployment, but is still in use. * 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. * 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 * 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 * label. Like the CNAME record, the DNS lookup will continue by retrying
* the lookup with the new name. * 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 * Option record RFC 2671 This is a pseudo DNS record type needed to support
* EDNS. * 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. * Address Prefix List record RFC 3123 Specify lists of address ranges, e.g.
* in CIDR format, for various address families. Experimental. * 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 * Delegation signer record RFC 4034 The record used to identify the DNSSEC
* signing key of a delegated zone. * 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 * 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 * 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. * 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. * 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 * DNSSEC signature record RFC 4034 Signature for a DNSSEC-secured record
* set. Uses the same format as the SIG 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 * 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. * 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 * DNS Key record RFC 4034 The key record used in DNSSEC. Uses the same
* format as the KEY record. * 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 * DHCP identifier record RFC 4701 Used in conjunction with the FQDN option
* to DHCP. * 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 * NSEC record version 3 RFC 5155 An extension to DNSSEC that allows proof
* of nonexistence for a name without permitting zonewalking. * 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. * 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 * 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 * key with the domain name where the record is found, thus forming a 'TLSA
* certificate association'. * 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 * Host Identity Protocol record RFC 5205 Method of separating the end-point
* identifier and locator roles of IP addresses. * 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 * 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 * protocol as an alternative to of storing SPF data in TXT records. Uses
* the same format as the earlier TXT record. * 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 * 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 * used with TSIG that is encrypted under the public key in an accompanying
* KEY RR.. * 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 * Transaction Signature record RFC 2845 Can be used to authenticate dynamic
* updates as coming from an approved client, or to authenticate responses * updates as coming from an approved client, or to authenticate responses
* as coming from an approved recursive name server similar to DNSSEC. * 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 * 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 * authoritative server is unable to fulfill the request due to
* configuration or lack of required deltas. * 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 * Authoritative Zone Transfer record RFC 1035 Transfer entire zone file
* from the master name server to secondary name servers. * 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 * 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 * returned. Sometimes referred to as ANY, for example in Windows nslookup
* and Wireshark. * 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, * Certification Authority Authorization record RFC 6844 CA pinning,
* constraining acceptable CAs for a host/domain. * 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 Trust Authorities record N/A Part of a deployment proposal for
* DNSSEC without a signed DNS root. See the IANA database and Weiler Spec * DNSSEC without a signed DNS root. See the IANA database and Weiler Spec
* for details. Uses the same format as the DS record. * 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 * DNSSEC Lookaside Validation record RFC 4431 For publishing DNSSEC trust
* anchors outside of the DNS delegation chain. Uses the same format as the * anchors outside of the DNS delegation chain. Uses the same format as the
* DS record. RFC 5074 describes a way of using these records. * 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 Map<String, DnsRecordType> BY_NAME = new HashMap<String, DnsRecordType>();
private static final IntObjectHashMap<DnsType> BY_TYPE = new IntObjectHashMap<DnsType>(); private static final IntObjectHashMap<DnsRecordType> BY_TYPE = new IntObjectHashMap<DnsRecordType>();
private static final String EXPECTED; private static final String EXPECTED;
static { 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, 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, DS, SSHFP, IPSECKEY, RRSIG, NSEC, DNSKEY, DHCID, NSEC3, NSEC3PARAM, TLSA, HIP, SPF, TKEY, TSIG, IXFR,
AXFR, ANY, CAA, TA, DLV AXFR, ANY, CAA, TA, DLV
}; };
StringBuilder expected = new StringBuilder(512); final StringBuilder expected = new StringBuilder(512);
expected.append(" (expected: ");
for (DnsType type: all) { expected.append(" (expected: ");
for (DnsRecordType type: all) {
BY_NAME.put(type.name(), type); BY_NAME.put(type.name(), type);
BY_TYPE.put(type.intValue(), type); BY_TYPE.put(type.intValue(), type);
expected.append(type.name());
expected.append('('); expected.append(type.name())
expected.append(type.intValue()); .append('(')
expected.append("), "); .append(type.intValue())
.append("), ");
} }
expected.setLength(expected.length() - 2); expected.setLength(expected.length() - 2);
@ -328,33 +330,31 @@ public final class DnsType implements Comparable<DnsType> {
EXPECTED = expected.toString(); EXPECTED = expected.toString();
} }
public static DnsType valueOf(int intValue) { public static DnsRecordType valueOf(int intValue) {
DnsType result = BY_TYPE.get(intValue); DnsRecordType result = BY_TYPE.get(intValue);
if (result == null) { if (result == null) {
return new DnsType(intValue, "UNKNOWN"); return new DnsRecordType(intValue);
} }
return result; return result;
} }
public static DnsType valueOf(String name) { public static DnsRecordType valueOf(String name) {
DnsType result = BY_NAME.get(name); DnsRecordType result = BY_NAME.get(name);
if (result == null) { if (result == null) {
throw new IllegalArgumentException("name: " + name + EXPECTED); throw new IllegalArgumentException("name: " + name + EXPECTED);
} }
return result; 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 int intValue;
private final String name; 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) { if ((intValue & 0xffff) != intValue) {
throw new IllegalArgumentException("intValue: " + intValue + " (expected: 0 ~ 65535)"); throw new IllegalArgumentException("intValue: " + intValue + " (expected: 0 ~ 65535)");
} }
@ -383,16 +383,20 @@ public final class DnsType implements Comparable<DnsType> {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return o instanceof DnsType && ((DnsType) o).intValue == intValue; return o instanceof DnsRecordType && ((DnsRecordType) o).intValue == intValue;
} }
@Override @Override
public int compareTo(DnsType o) { public int compareTo(DnsRecordType o) {
return intValue() - o.intValue(); return intValue() - o.intValue();
} }
@Override @Override
public String toString() { 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, * 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 * version 2.0 (the "License"); you may not use this file except in compliance
@ -15,86 +15,99 @@
*/ */
package io.netty.handler.codec.dns; package io.netty.handler.codec.dns;
import java.net.InetSocketAddress;
/** /**
* A DNS response packet which is sent to a client after a server receives a * A DNS response message.
* query.
*/ */
public final class DnsResponse extends DnsMessage { public interface 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;
}
/** /**
* The {@link InetSocketAddress} of the sender of this {@link DnsResponse} * Returns {@code true} if responding server is authoritative for the domain
* name in the query message.
*/ */
public InetSocketAddress sender() { boolean isAuthoritativeAnswer();
return sender;
} /**
* Set to {@code true} if responding server is authoritative for the domain
* name in the query message.
*
* @param authoritativeAnswer flag for authoritative answer
*/
DnsResponse setAuthoritativeAnswer(boolean authoritativeAnswer);
/**
* Returns {@code true} if response has been truncated, usually if it is
* over 512 bytes.
*/
boolean isTruncated();
/**
* Set to {@code true} if response has been truncated (usually happens for
* responses over 512 bytes).
*
* @param truncated flag for truncation
*/
DnsResponse setTruncated(boolean truncated);
/**
* Returns {@code true} if DNS server can handle recursive queries.
*/
boolean isRecursionAvailable();
/**
* Set to {@code true} if DNS server can handle recursive queries.
*
* @param recursionAvailable flag for recursion availability
*/
DnsResponse setRecursionAvailable(boolean recursionAvailable);
/**
* Returns the 4 bit return code.
*/
DnsResponseCode code();
/**
* Sets the response code for this message.
*
* @param code the response code
*/
DnsResponse setCode(DnsResponseCode code);
@Override @Override
public DnsResponse addAnswer(DnsResource answer) { DnsResponse setId(int id);
super.addAnswer(answer);
return this;
}
@Override @Override
public DnsResponse addQuestion(DnsQuestion question) { DnsResponse setOpCode(DnsOpCode opCode);
super.addQuestion(question);
return this;
}
@Override @Override
public DnsResponse addAuthorityResource(DnsResource resource) { DnsResponse setRecursionDesired(boolean recursionDesired);
super.addAuthorityResource(resource);
return this;
}
@Override @Override
public DnsResponse addAdditionalResource(DnsResource resource) { DnsResponse setZ(int z);
super.addAdditionalResource(resource);
return this;
}
@Override @Override
public DnsResponse touch(Object hint) { DnsResponse setRecord(DnsSection section, DnsRecord record);
super.touch(hint);
return this;
}
@Override @Override
public DnsResponse retain() { DnsResponse addRecord(DnsSection section, DnsRecord record);
super.retain();
return this;
}
@Override @Override
public DnsResponse retain(int increment) { DnsResponse addRecord(DnsSection section, int index, DnsRecord record);
super.retain(increment);
return this;
}
@Override @Override
public DnsResponse touch() { DnsResponse clear(DnsSection section);
super.touch();
return this;
}
@Override @Override
public DnsResponseHeader header() { DnsResponse clear();
return (DnsResponseHeader) super.header();
}
@Override @Override
protected DnsResponseHeader newHeader(int id) { DnsResponse touch();
return new DnsResponseHeader(this, id);
} @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, * 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 * version 2.0 (the "License"); you may not use this file except in compliance
@ -15,159 +15,183 @@
*/ */
package io.netty.handler.codec.dns; 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 * The DNS {@code RCODE}, as defined in <a href="https://tools.ietf.org/html/rfc2929">RFC2929</a>.
* query. A response code of 0 indicates no error.
*/ */
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"); public static final DnsResponseCode BADMODE = new DnsResponseCode(19, "BADMODE");
private final int errorCode;
private final String message;
/** /**
* Returns the {@link DnsResponseCode} that corresponds with the given * The 'BADNAME' DNS RCODE (20), as defined in <a href="https://tools.ietf.org/html/rfc2930">RFC2930</a>.
* {@code responseCode}. */
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 * @param responseCode the DNS RCODE
* the error code's id *
* @return corresponding {@link DnsResponseCode} or {@code null} if none can be found. * @return the corresponding {@link DnsResponseCode}
*/ */
public static DnsResponseCode valueOf(int responseCode) { public static DnsResponseCode valueOf(int responseCode) {
switch (responseCode) { switch (responseCode) {
case 0: case 0:
return NOERROR; return NOERROR;
case 1: case 1:
return FORMERROR; return FORMERR;
case 2: case 2:
return SERVFAIL; return SERVFAIL;
case 3: case 3:
return NXDOMAIN; return NXDOMAIN;
case 4: case 4:
return NOTIMPL; return NOTIMP;
case 5: case 5:
return REFUSED; return REFUSED;
case 6: case 6:
return YXDOMAIN; return YXDOMAIN;
case 7: case 7:
return YXRRSET; return YXRRSET;
case 8: case 8:
return NXRRSET; return NXRRSET;
case 9: case 9:
return NOTAUTH; return NOTAUTH;
case 10: case 10:
return NOTZONE; return NOTZONE;
case 11: case 16:
return BADVERS; return BADVERS_OR_BADSIG;
case 12: case 17:
return BADSIG; return BADKEY;
case 13: case 18:
return BADKEY; return BADTIME;
case 14: case 19:
return BADTIME; return BADMODE;
default: case 20:
return new DnsResponseCode(responseCode, null); return BADNAME;
case 21:
return BADALG;
default:
return new DnsResponseCode(responseCode);
} }
} }
public DnsResponseCode(int errorCode, String message) { private final int code;
this.errorCode = errorCode; private final String name;
this.message = message; 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}. * Returns the error code for this {@link DnsResponseCode}.
*/ */
public int code() { public int intValue() {
return errorCode; return code;
} }
@Override @Override
public int compareTo(DnsResponseCode o) { public int compareTo(DnsResponseCode o) {
return code() - o.code(); return intValue() - o.intValue();
} }
@Override @Override
public int hashCode() { 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 @Override
public boolean equals(Object o) { public boolean equals(Object o) {
@ -175,7 +199,7 @@ public final class DnsResponseCode implements Comparable<DnsResponseCode> {
return false; return false;
} }
return code() == ((DnsResponseCode) o).code(); return intValue() == ((DnsResponseCode) o).intValue();
} }
/** /**
@ -183,9 +207,10 @@ public final class DnsResponseCode implements Comparable<DnsResponseCode> {
*/ */
@Override @Override
public String toString() { public String toString() {
if (message == null) { String text = this.text;
return "DnsResponseCode(" + errorCode + ')'; 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, * 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 * 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. * DNS codec.
* Includes decoders and classes for representing messages and resources.
*/ */
package io.netty.handler.codec.dns; package io.netty.handler.codec.dns;

View File

@ -25,29 +25,40 @@ import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class DnsQueryTest { public class DnsQueryTest {
@Test @Test
public void writeQueryTest() throws Exception { public void writeQueryTest() throws Exception {
InetSocketAddress addr = new InetSocketAddress(0); InetSocketAddress addr = new InetSocketAddress(0);
EmbeddedChannel embedder = new EmbeddedChannel(new DnsQueryEncoder()); EmbeddedChannel embedder = new EmbeddedChannel(new DatagramDnsQueryEncoder());
List<DnsQuery> queries = new ArrayList<DnsQuery>(5); 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 DatagramDnsQuery(addr, null, 1).setRecord(
queries.add(new DnsQuery(1, addr).addQuestion(new DnsQuestion("www.example.com", DnsType.A))); DnsSection.QUESTION,
queries.add(new DnsQuery(1, addr).addQuestion(new DnsQuestion("example.com", DnsType.AAAA))); new DefaultDnsQuestion("1.0.0.127.in-addr.arpa", DnsRecordType.PTR)));
queries.add(new DnsQuery(1, addr).addQuestion(new DnsQuestion("example.com", DnsType.MX))); queries.add(new DatagramDnsQuery(addr, null, 1).setRecord(
queries.add(new DnsQuery(1, addr).addQuestion(new DnsQuestion("example.com", DnsType.CNAME))); 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) { for (DnsQuery query: queries) {
Assert.assertEquals("Invalid question count, expected 1.", 1, query.header().questionCount()); assertThat(query.count(DnsSection.QUESTION), is(1));
Assert.assertEquals("Invalid answer count, expected 0.", 0, query.header().answerCount()); assertThat(query.count(DnsSection.ANSWER), is(0));
Assert.assertEquals("Invalid authority resource record count, expected 0.", 0, query.header() assertThat(query.count(DnsSection.AUTHORITY), is(0));
.authorityResourceCount()); assertThat(query.count(DnsSection.ADDITIONAL), is(0));
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());
embedder.writeOutbound(query); embedder.writeOutbound(query);
DatagramPacket packet = embedder.readOutbound(); DatagramPacket packet = embedder.readOutbound();
Assert.assertTrue(packet.content().isReadable()); Assert.assertTrue(packet.content().isReadable());
packet.release(); packet.release();

View File

@ -25,13 +25,13 @@ import java.util.List;
import static org.junit.Assert.*; import static org.junit.Assert.*;
public class DnsClassTest { public class DnsRecordTypeTest {
private static List<DnsClass> allTypes() throws Exception { private static List<DnsRecordType> allTypes() throws Exception {
List<DnsClass> result = new ArrayList<DnsClass>(); List<DnsRecordType> result = new ArrayList<DnsRecordType>();
for (Field field : DnsClass.class.getDeclaredFields()) { for (Field field : DnsRecordType.class.getFields()) {
if ((field.getModifiers() & Modifier.STATIC) != 0 && field.getType() == DnsClass.class) { if ((field.getModifiers() & Modifier.STATIC) != 0 && field.getType() == DnsRecordType.class) {
result.add((DnsClass) field.get(null)); result.add((DnsRecordType) field.get(null));
} }
} }
assertFalse(result.isEmpty()); assertFalse(result.isEmpty());
@ -41,26 +41,26 @@ public class DnsClassTest {
@Test @Test
public void testSanity() throws Exception { public void testSanity() throws Exception {
assertEquals("More than one type has the same int value", 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 @Test
public void testHashCode() throws Exception { public void testHashCode() throws Exception {
for (DnsClass t : allTypes()) { for (DnsRecordType t : allTypes()) {
assertEquals(t.intValue(), t.hashCode()); assertEquals(t.intValue(), t.hashCode());
} }
} }
/** /**
* Test of equals method, of class DnsClass. * Test of equals method, of class DnsRecordType.
*/ */
@Test @Test
public void testEquals() throws Exception { public void testEquals() throws Exception {
for (DnsClass t1 : allTypes()) { for (DnsRecordType t1 : allTypes()) {
for (DnsClass t2 : allTypes()) { for (DnsRecordType t2 : allTypes()) {
if (t1 != t2) { if (t1 != t2) {
assertNotEquals(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 @Test
public void testFind() throws Exception { public void testFind() throws Exception {
for (DnsClass t : allTypes()) { for (DnsRecordType t : allTypes()) {
DnsClass found = DnsClass.valueOf(t.intValue()); DnsRecordType found = DnsRecordType.valueOf(t.intValue());
assertSame(t, found); assertSame(t, found);
found = DnsClass.valueOf(t.toString()); found = DnsRecordType.valueOf(t.name());
assertSame(t.toString(), t, found); 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.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.CorruptedFrameException; import io.netty.handler.codec.CorruptedFrameException;
import org.junit.Assert;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class DnsResponseTest { public class DnsResponseTest {
private static final byte[][] packets = { private static final byte[][] packets = {
@ -69,25 +72,23 @@ public class DnsResponseTest {
@Test @Test
public void readResponseTest() throws Exception { public void readResponseTest() throws Exception {
EmbeddedChannel embedder = new EmbeddedChannel(new DnsResponseDecoder()); EmbeddedChannel embedder = new EmbeddedChannel(new DatagramDnsResponseDecoder());
for (byte[] p: packets) { for (byte[] p: packets) {
ByteBuf packet = embedder.alloc().buffer(512).writeBytes(p); ByteBuf packet = embedder.alloc().buffer(512).writeBytes(p);
embedder.writeInbound(new DatagramPacket(packet, null, new InetSocketAddress(0))); 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); ByteBuf raw = Unpooled.wrappedBuffer(p);
Assert.assertEquals("Invalid id, expected: " + raw.getUnsignedShort(0) + ", actual: " assertThat(response.id(), is(raw.getUnsignedShort(0)));
+ decoded.header().id(), raw.getUnsignedShort(0), decoded.header().id()); assertThat(response.count(DnsSection.QUESTION), is(raw.getUnsignedShort(4)));
Assert.assertEquals("Invalid resource count, expected: " + raw.getUnsignedShort(4) + ", actual: " assertThat(response.count(DnsSection.ANSWER), is(raw.getUnsignedShort(6)));
+ decoded.questions().size(), raw.getUnsignedShort(4), decoded.questions().size()); assertThat(response.count(DnsSection.AUTHORITY), is(raw.getUnsignedShort(8)));
Assert.assertEquals("Invalid resource count, expected: " + raw.getUnsignedShort(6) + ", actual: " assertThat(response.count(DnsSection.ADDITIONAL), is(raw.getUnsignedShort(10)));
+ decoded.answers().size(), raw.getUnsignedShort(6), decoded.answers().size());
Assert.assertEquals("Invalid resource count, expected: " + raw.getUnsignedShort(8) + ", actual: " envelope.release();
+ 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();
} }
} }
@ -96,7 +97,7 @@ public class DnsResponseTest {
@Test @Test
public void readMalormedResponseTest() throws Exception { 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); ByteBuf packet = embedder.alloc().buffer(512).writeBytes(malformedLoopPacket);
exception.expect(CorruptedFrameException.class); exception.expect(CorruptedFrameException.class);
embedder.writeInbound(new DatagramPacket(packet, null, new InetSocketAddress(0))); 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 final class StringUtil {
public static final String EMPTY_STRING = "";
public static final String NEWLINE; public static final String NEWLINE;
public static final char DOUBLE_QUOTE = '\"'; public static final char DOUBLE_QUOTE = '\"';
public static final char COMMA = ','; public static final char COMMA = ',';
public static final char LINE_FEED = '\n'; public static final char LINE_FEED = '\n';
public static final char CARRIAGE_RETURN = '\r'; public static final char CARRIAGE_RETURN = '\r';
public static final String EMPTY_STRING = ""; public static final char TAB = '\t';
public static final byte UPPER_CASE_TO_LOWER_CASE_ASCII_OFFSET = (int) 'a' - (int) 'A';
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_PAD = new String[256];
private static final String[] BYTE2HEX_NOPAD = new String[256]; private static final String[] BYTE2HEX_NOPAD = new String[256];
/** /**
* 2 - Quote character at beginning and end. * 2 - Quote character at beginning and end.
* 5 - Extra allowance for anticipated escape characters that may be added. * 5 - Extra allowance for anticipated escape characters that may be added.

View File

@ -16,6 +16,7 @@
package io.netty.resolver.dns; package io.netty.resolver.dns;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.ChannelFactory; import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
@ -27,13 +28,14 @@ import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.ReflectiveChannelFactory; import io.netty.channel.ReflectiveChannelFactory;
import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.InternetProtocolFamily; import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.handler.codec.dns.DnsClass; import io.netty.handler.codec.dns.DatagramDnsQueryEncoder;
import io.netty.handler.codec.dns.DnsQueryEncoder; 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.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.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseCode; import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.handler.codec.dns.DnsResponseDecoder;
import io.netty.resolver.NameResolver; import io.netty.resolver.NameResolver;
import io.netty.resolver.SimpleNameResolver; import io.netty.resolver.SimpleNameResolver;
import io.netty.util.ReferenceCountUtil; 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 DatagramDnsResponseDecoder DECODER = new DatagramDnsResponseDecoder();
private static final DnsQueryEncoder ENCODER = new DnsQueryEncoder(); private static final DatagramDnsQueryEncoder ENCODER = new DatagramDnsQueryEncoder();
final Iterable<InetSocketAddress> nameServerAddresses; final Iterable<InetSocketAddress> nameServerAddresses;
final ChannelFuture bindFuture; final ChannelFuture bindFuture;
@ -117,7 +119,6 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
private volatile int maxQueriesPerResolve = 8; private volatile int maxQueriesPerResolve = 8;
private volatile int maxPayloadSize; 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. * 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; this.maxPayloadSize = maxPayloadSize;
maxPayloadSizeClass = DnsClass.valueOf(maxPayloadSize);
ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize)); ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize));
return this; return this;
} }
DnsClass maxPayloadSizeClass() {
return maxPayloadSizeClass;
}
/** /**
* Clears all the DNS resource records cached by this resolver. * 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. * 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); return query(nameServerAddresses, question);
} }
/** /**
* Sends a DNS query with the specified 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); return query(nameServerAddresses, question, promise);
} }
/** /**
* Sends a DNS query with the specified question using the specified name server list. * 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) { if (nameServerAddresses == null) {
throw new NullPointerException("nameServerAddresses"); throw new NullPointerException("nameServerAddresses");
} }
@ -693,15 +691,18 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
return eventLoop.newFailedFuture(cachedResult.cause); return eventLoop.newFailedFuture(cachedResult.cause);
} }
} else { } 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. * Sends a DNS query with the specified question using the specified name server list.
*/ */
public Future<DnsResponse> query( public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question, Promise<DnsResponse> promise) { Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question,
Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
if (nameServerAddresses == null) { if (nameServerAddresses == null) {
throw new NullPointerException("nameServerAddresses"); throw new NullPointerException("nameServerAddresses");
@ -716,23 +717,25 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
final DnsCacheEntry cachedResult = queryCache.get(question); final DnsCacheEntry cachedResult = queryCache.get(question);
if (cachedResult != null) { if (cachedResult != null) {
if (cachedResult.response != null) { if (cachedResult.response != null) {
return promise.setSuccess(cachedResult.response.retain()); return cast(promise).setSuccess(cachedResult.response.retain());
} else { } else {
return promise.setFailure(cachedResult.cause); return cast(promise).setFailure(cachedResult.cause);
} }
} else { } else {
return query0(nameServerAddresses, question, promise); return query0(nameServerAddresses, question, promise);
} }
} }
private Future<DnsResponse> query0( private Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query0(
Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question, Promise<DnsResponse> promise) { Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question,
Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
final Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> castPromise = cast(promise);
try { try {
new DnsQueryContext(this, nameServerAddresses, question, promise).query(); new DnsQueryContext(this, nameServerAddresses, question, castPromise).query();
return promise; return castPromise;
} catch (Exception e) { } 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 ChannelHandlerAdapter { private final class DnsResponseHandler extends ChannelHandlerAdapter {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try { try {
final DnsResponse res = (DnsResponse) msg; final DatagramDnsResponse res = (DatagramDnsResponse) msg;
final int queryId = res.header().id(); final int queryId = res.id();
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("{} RECEIVED: [{}: {}], {}", ch, queryId, res.sender(), res); logger.debug("{} RECEIVED: [{}: {}], {}", ch, queryId, res.sender(), res);
@ -782,14 +795,13 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
return; return;
} }
final List<DnsQuestion> questions = res.questions(); if (res.count(DnsSection.QUESTION) != 1) {
if (questions.size() != 1) {
logger.warn("Received a DNS response with invalid number of questions: {}", res); logger.warn("Received a DNS response with invalid number of questions: {}", res);
return; return;
} }
final DnsQuestion q = qCtx.question(); 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); logger.warn("Received a mismatching DNS response: {}", res);
return; return;
} }
@ -800,26 +812,26 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
timeoutFuture.cancel(false); timeoutFuture.cancel(false);
} }
if (res.header().responseCode() == DnsResponseCode.NOERROR) { if (res.code() == DnsResponseCode.NOERROR) {
cache(q, res); cache(q, res);
promises.set(queryId, null); promises.set(queryId, null);
Promise<DnsResponse> qPromise = qCtx.promise(); Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> qPromise = qCtx.promise();
if (qPromise.setUncancellable()) { if (qPromise.setUncancellable()) {
qPromise.setSuccess(res.retain()); qPromise.setSuccess(cast(res.retain()));
} }
} else { } else {
qCtx.retry(res.sender(), qCtx.retry(res.sender(),
"response code: " + res.header().responseCode() + "response code: " + res.code() +
" with " + res.answers().size() + " answer(s) and " + " with " + res.count(DnsSection.ANSWER) + " answer(s) and " +
res.authorityResources().size() + " authority resource(s)"); res.count(DnsSection.AUTHORITY) + " authority resource(s)");
} }
} finally { } finally {
ReferenceCountUtil.safeRelease(msg); ReferenceCountUtil.safeRelease(msg);
} }
} }
private void cache(DnsQuestion question, DnsResponse res) { private void cache(DnsQuestion question, AddressedEnvelope<? extends DnsResponse, InetSocketAddress> res) {
final int maxTtl = maxTtl(); final int maxTtl = maxTtl();
if (maxTtl == 0) { if (maxTtl == 0) {
return; return;
@ -827,8 +839,11 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
long ttl = Long.MAX_VALUE; long ttl = Long.MAX_VALUE;
// Find the smallest TTL value returned by the server. // Find the smallest TTL value returned by the server.
for (DnsResource r: res.answers()) { final DnsResponse resc = res.content();
long rTtl = r.timeToLive(); 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) { if (ttl > rTtl) {
ttl = rTtl; ttl = rTtl;
} }
@ -847,12 +862,13 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
} }
static final class DnsCacheEntry { static final class DnsCacheEntry {
final DnsResponse response; final AddressedEnvelope<DnsResponse, InetSocketAddress> response;
final Throwable cause; final Throwable cause;
volatile ScheduledFuture<?> expirationFuture; volatile ScheduledFuture<?> expirationFuture;
DnsCacheEntry(DnsResponse response) { @SuppressWarnings("unchecked")
this.response = response.retain(); DnsCacheEntry(AddressedEnvelope<? extends DnsResponse, InetSocketAddress> response) {
this.response = (AddressedEnvelope<DnsResponse, InetSocketAddress>) response.retain();
cause = null; cause = null;
} }
@ -862,7 +878,7 @@ public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
} }
void release() { void release() {
DnsResponse response = this.response; AddressedEnvelope<DnsResponse, InetSocketAddress> response = this.response;
if (response != null) { if (response != null) {
ReferenceCountUtil.safeRelease(response); ReferenceCountUtil.safeRelease(response);
} }

View File

@ -17,13 +17,17 @@
package io.netty.resolver.dns; package io.netty.resolver.dns;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.socket.InternetProtocolFamily; 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.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.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseDecoder;
import io.netty.handler.codec.dns.DnsType;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
@ -51,14 +55,15 @@ final class DnsNameResolverContext {
private static final int INADDRSZ4 = 4; private static final int INADDRSZ4 = 4;
private static final int INADDRSZ6 = 16; 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 =
@Override new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
public void operationComplete(Future<DnsResponse> future) { @Override
if (future.isSuccess()) { public void operationComplete(Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> future) {
future.getNow().release(); if (future.isSuccess()) {
} future.getNow().release();
} }
}; }
};
private final DnsNameResolver parent; private final DnsNameResolver parent;
private final Promise<InetSocketAddress> promise; private final Promise<InetSocketAddress> promise;
@ -67,8 +72,10 @@ final class DnsNameResolverContext {
private final int maxAllowedQueries; private final int maxAllowedQueries;
private final InternetProtocolFamily[] resolveAddressTypes; private final InternetProtocolFamily[] resolveAddressTypes;
private final Set<Future<DnsResponse>> queriesInProgress = private final Set<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> queriesInProgress =
Collections.newSetFromMap(new IdentityHashMap<Future<DnsResponse>, Boolean>()); Collections.newSetFromMap(
new IdentityHashMap<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>, Boolean>());
private List<InetAddress> resolvedAddresses; private List<InetAddress> resolvedAddresses;
private StringBuilder trace; private StringBuilder trace;
private int allowedQueries; private int allowedQueries;
@ -87,19 +94,19 @@ final class DnsNameResolverContext {
void resolve() { void resolve() {
for (InternetProtocolFamily f: resolveAddressTypes) { for (InternetProtocolFamily f: resolveAddressTypes) {
final DnsType type; final DnsRecordType type;
switch (f) { switch (f) {
case IPv4: case IPv4:
type = DnsType.A; type = DnsRecordType.A;
break; break;
case IPv6: case IPv6:
type = DnsType.AAAA; type = DnsRecordType.AAAA;
break; break;
default: default:
throw new Error(); 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 --; allowedQueries --;
final Future<DnsResponse> f = parent.query(nameServerAddresses, question); final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = parent.query(nameServerAddresses, question);
queriesInProgress.add(f); queriesInProgress.add(f);
f.addListener(new FutureListener<DnsResponse>() { f.addListener(new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
@Override @Override
public void operationComplete(Future<DnsResponse> future) throws Exception { public void operationComplete(Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> future) {
queriesInProgress.remove(future); queriesInProgress.remove(future);
if (promise.isDone()) { if (promise.isDone()) {
@ -135,12 +142,12 @@ final class DnsNameResolverContext {
}); });
} }
void onResponse(final DnsQuestion question, final DnsResponse response) { void onResponse(final DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> response) {
final DnsType type = question.type(); final DnsRecordType type = question.type();
try { try {
if (type == DnsType.A || type == DnsType.AAAA) { if (type == DnsRecordType.A || type == DnsRecordType.AAAA) {
onResponseAorAAAA(type, question, response); onResponseAorAAAA(type, question, response);
} else if (type == DnsType.CNAME) { } else if (type == DnsRecordType.CNAME) {
onResponseCNAME(question, response); onResponseCNAME(question, response);
} }
} finally { } 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. // 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 Map<String, String> cnames = buildAliasMap(response);
final int answerCount = response.count(DnsSection.ANSWER);
boolean found = false; boolean found = false;
for (DnsResource r: response.answers()) { for (int i = 0; i < answerCount; i ++) {
final DnsType type = r.type(); final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
if (type != DnsType.A && type != DnsType.AAAA) { final DnsRecordType type = r.type();
if (type != DnsRecordType.A && type != DnsRecordType.AAAA) {
continue; 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(); final int contentLen = content.readableBytes();
if (contentLen != INADDRSZ4 && contentLen != INADDRSZ6) { if (contentLen != INADDRSZ4 && contentLen != INADDRSZ6) {
continue; continue;
@ -204,20 +220,21 @@ final class DnsNameResolverContext {
return; 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. // We aked for A/AAAA but we got only CNAME.
if (!cnames.isEmpty()) { if (!cnames.isEmpty()) {
onResponseCNAME(question, response, cnames, false); onResponseCNAME(question, envelope, cnames, false);
} }
} }
private void onResponseCNAME(DnsQuestion question, DnsResponse response) { private void onResponseCNAME(DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope) {
onResponseCNAME(question, response, buildAliasMap(response), true); onResponseCNAME(question, envelope, buildAliasMap(envelope.content()), true);
} }
private void onResponseCNAME( 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. // Resolve the host name in the question into the real host name.
final String name = question.name().toLowerCase(Locale.US); final String name = question.name().toLowerCase(Locale.US);
@ -241,15 +258,22 @@ final class DnsNameResolverContext {
} }
private static Map<String, String> buildAliasMap(DnsResponse response) { private static Map<String, String> buildAliasMap(DnsResponse response) {
final int answerCount = response.count(DnsSection.ANSWER);
Map<String, String> cnames = null; Map<String, String> cnames = null;
for (DnsResource r: response.answers()) { for (int i = 0; i < answerCount; i ++) {
final DnsType type = r.type(); final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
if (type != DnsType.CNAME) { final DnsRecordType type = r.type();
if (type != DnsRecordType.CNAME) {
continue; continue;
} }
String content = decodeDomainName(r.content()); if (!(r instanceof DnsRawRecord)) {
if (content == null) { continue;
}
final ByteBuf recordContent = ((ByteBufHolder) r).content();
final String domainName = decodeDomainName(recordContent);
if (domainName == null) {
continue; continue;
} }
@ -257,7 +281,7 @@ final class DnsNameResolverContext {
cnames = new HashMap<String, String>(); 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(); return cnames != null? cnames : Collections.<String, String>emptyMap();
@ -281,7 +305,7 @@ final class DnsNameResolverContext {
if (!triedCNAME) { if (!triedCNAME) {
// As the last resort, try to query CNAME, just in case the name server has it. // As the last resort, try to query CNAME, just in case the name server has it.
triedCNAME = true; triedCNAME = true;
query(parent.nameServerAddresses, new DnsQuestion(hostname, DnsType.CNAME, DnsClass.IN)); query(parent.nameServerAddresses, new DefaultDnsQuestion(hostname, DnsRecordType.CNAME));
return; return;
} }
} }
@ -319,8 +343,9 @@ final class DnsNameResolverContext {
private void finishResolve() { private void finishResolve() {
if (!queriesInProgress.isEmpty()) { if (!queriesInProgress.isEmpty()) {
// If there are queries in progress, we should cancel it because we already finished the resolution. // 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();) { for (Iterator<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> i = queriesInProgress.iterator();
Future<DnsResponse> f = i.next(); i.hasNext();) {
Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = i.next();
i.remove(); i.remove();
if (!f.cancel(false)) { 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) { static String decodeDomainName(ByteBuf buf) {
buf.markReaderIndex(); buf.markReaderIndex();
try { try {
int position = -1; int position = -1;
int checked = 0; int checked = 0;
int length = buf.writerIndex(); final int end = buf.writerIndex();
StringBuilder name = new StringBuilder(64); final StringBuilder name = new StringBuilder(buf.readableBytes() << 1);
for (int len = buf.readUnsignedByte(); buf.isReadable() && len != 0; len = buf.readUnsignedByte()) { for (int len = buf.readUnsignedByte(); buf.isReadable() && len != 0; len = buf.readUnsignedByte()) {
boolean pointer = (len & 0xc0) == 0xc0; boolean pointer = (len & 0xc0) == 0xc0;
if (pointer) { if (pointer) {
if (position == -1) { if (position == -1) {
position = buf.readerIndex() + 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 // check for loops
checked += 2; checked += 2;
if (checked >= length) { if (checked >= end) {
// Name contains a loop; give up. // Name contains a loop; give up.
return null; return null;
} }
@ -449,8 +481,8 @@ final class DnsNameResolverContext {
trace.append(" CNAME "); trace.append(" CNAME ");
trace.append(cname); trace.append(cname);
query(parent.nameServerAddresses, new DnsQuestion(cname, DnsType.A, DnsClass.IN)); query(parent.nameServerAddresses, new DefaultDnsQuestion(cname, DnsRecordType.A));
query(parent.nameServerAddresses, new DnsQuestion(cname, DnsType.AAAA, DnsClass.IN)); query(parent.nameServerAddresses, new DefaultDnsQuestion(cname, DnsRecordType.AAAA));
} }
private void addTrace(InetSocketAddress nameServerAddr, String msg) { private void addTrace(InetSocketAddress nameServerAddr, String msg) {

View File

@ -17,13 +17,17 @@
package io.netty.resolver.dns; package io.netty.resolver.dns;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; 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.DnsQuery;
import io.netty.handler.codec.dns.DnsQuestion; 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.DnsResponse;
import io.netty.handler.codec.dns.DnsType;
import io.netty.resolver.dns.DnsNameResolver.DnsCacheEntry; import io.netty.resolver.dns.DnsNameResolver.DnsCacheEntry;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture; import io.netty.util.concurrent.ScheduledFuture;
@ -43,10 +47,10 @@ final class DnsQueryContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsQueryContext.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsQueryContext.class);
private final DnsNameResolver parent; private final DnsNameResolver parent;
private final Promise<DnsResponse> promise; private final Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> promise;
private final int id; private final int id;
private final DnsQuestion question; private final DnsQuestion question;
private final DnsResource optResource; private final DnsRecord optResource;
private final Iterator<InetSocketAddress> nameServerAddresses; private final Iterator<InetSocketAddress> nameServerAddresses;
private final boolean recursionDesired; private final boolean recursionDesired;
@ -57,7 +61,7 @@ final class DnsQueryContext {
DnsQueryContext(DnsNameResolver parent, DnsQueryContext(DnsNameResolver parent,
Iterable<InetSocketAddress> nameServerAddresses, Iterable<InetSocketAddress> nameServerAddresses,
DnsQuestion question, Promise<DnsResponse> promise) { DnsQuestion question, Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> promise) {
this.parent = parent; this.parent = parent;
this.promise = promise; this.promise = promise;
@ -67,7 +71,8 @@ final class DnsQueryContext {
recursionDesired = parent.isRecursionDesired(); recursionDesired = parent.isRecursionDesired();
maxTries = parent.maxTriesPerQuery(); maxTries = parent.maxTriesPerQuery();
remainingTries = maxTries; 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(); this.nameServerAddresses = nameServerAddresses.iterator();
} }
@ -89,7 +94,7 @@ final class DnsQueryContext {
} }
} }
Promise<DnsResponse> promise() { Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> promise() {
return promise; return promise;
} }
@ -125,10 +130,10 @@ final class DnsQueryContext {
remainingTries --; remainingTries --;
final InetSocketAddress nameServerAddr = nameServerAddresses.next(); final InetSocketAddress nameServerAddr = nameServerAddresses.next();
final DnsQuery query = new DnsQuery(id, nameServerAddr); final DatagramDnsQuery query = new DatagramDnsQuery(null, nameServerAddr, id);
query.addQuestion(question); query.setRecursionDesired(recursionDesired);
query.header().setRecursionDesired(recursionDesired); query.setRecord(DnsSection.QUESTION, question);
query.addAdditionalResource(optResource); query.setRecord(DnsSection.ADDITIONAL, optResource);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("{} WRITE: [{}: {}], {}", parent.ch, id, nameServerAddr, question); 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, * 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 * 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 * License for the specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package io.netty.resolver.dns; 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.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.InternetProtocolFamily; import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.handler.codec.dns.DnsQuestion; import io.netty.handler.codec.dns.DefaultDnsQuestion;
import io.netty.handler.codec.dns.DnsResource; 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.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseCode; import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.handler.codec.dns.DnsType;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
import io.netty.util.internal.ThreadLocalRandom; import io.netty.util.internal.ThreadLocalRandom;
@ -181,7 +184,7 @@ public class DnsNameResolverTest {
EXCLUSIONS_RESOLVE_A, EXCLUSIONS_RESOLVE_A,
"akamaihd.net", "akamaihd.net",
"googleusercontent.com", "googleusercontent.com",
""); StringUtil.EMPTY_STRING);
} }
/** /**
@ -236,7 +239,7 @@ public class DnsNameResolverTest {
"people.com.cn", "people.com.cn",
"googleusercontent.com", "googleusercontent.com",
"blogspot.in", "blogspot.in",
""); StringUtil.EMPTY_STRING);
} }
private static final EventLoopGroup group = new NioEventLoopGroup(1); private static final EventLoopGroup group = new NioEventLoopGroup(1);
@ -368,8 +371,8 @@ public class DnsNameResolverTest {
public void testQueryMx() throws Exception { public void testQueryMx() throws Exception {
assertThat(resolver.isRecursionDesired(), is(true)); assertThat(resolver.isRecursionDesired(), is(true));
Map<String, Future<DnsResponse>> futures = Map<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> futures =
new LinkedHashMap<String, Future<DnsResponse>>(); new LinkedHashMap<String, Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>>();
for (String name: DOMAINS) { for (String name: DOMAINS) {
if (EXCLUSIONS_QUERY_MX.contains(name)) { if (EXCLUSIONS_QUERY_MX.contains(name)) {
continue; continue;
@ -378,30 +381,36 @@ public class DnsNameResolverTest {
queryMx(futures, name); 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(); 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)); assertThat(response.code(), is(DnsResponseCode.NOERROR));
List<DnsResource> mxList = new ArrayList<DnsResource>();
for (DnsResource r: response.answers()) { final int answerCount = response.count(DnsSection.ANSWER);
if (r.type() == DnsType.MX) { 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); mxList.add(r);
} }
} }
assertThat(mxList.size(), is(greaterThan(0))); assertThat(mxList.size(), is(greaterThan(0)));
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
for (DnsResource r: mxList) { for (DnsRecord r: mxList) {
ByteBuf recordContent = ((ByteBufHolder) r).content();
buf.append(StringUtil.NEWLINE); buf.append(StringUtil.NEWLINE);
buf.append('\t'); buf.append('\t');
buf.append(r.name()); buf.append(r.name());
buf.append(' '); buf.append(' ');
buf.append(r.type()); buf.append(r.type().name());
buf.append(' '); buf.append(' ');
buf.append(r.content().readUnsignedShort()); buf.append(recordContent.readUnsignedShort());
buf.append(' '); buf.append(' ');
buf.append(DnsNameResolverContext.decodeDomainName(r.content())); buf.append(DnsNameResolverContext.decodeDomainName(recordContent));
} }
logger.info("{} has the following MX records:{}", hostname, buf); 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 unresolved =
InetSocketAddress.createUnresolved(hostname, ThreadLocalRandom.current().nextInt(65536)); InetSocketAddress.createUnresolved(hostname, ThreadLocalRandom.current().nextInt(65536));
futures.put(unresolved, resolver.resolve(unresolved)); futures.put(unresolved, resolver.resolve(unresolved));
} }
private static void queryMx(Map<String, Future<DnsResponse>> futures, String hostname) throws Exception { private static void queryMx(
futures.put(hostname, resolver.query(new DnsQuestion(hostname, DnsType.MX))); 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"); throw new NullPointerException("message");
} }
if (recipient == null && sender == null) {
throw new NullPointerException("recipient and sender");
}
this.message = message; this.message = message;
this.sender = sender; this.sender = sender;
this.recipient = recipient; this.recipient = recipient;