add dns resolver code

This commit is contained in:
Jörg Prante 2024-02-03 00:10:02 +01:00
parent ada04aef58
commit fc565512cc
131 changed files with 21681 additions and 0 deletions

View file

@ -0,0 +1,3 @@
dependencies {
api project(':netty-handler')
}

View file

@ -0,0 +1,478 @@
/*
* 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:
*
* https://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.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetectorFactory;
import io.netty.util.ResourceLeakTracker;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.UnstableApi;
import java.util.ArrayList;
import java.util.List;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* A skeletal implementation of {@link DnsMessage}.
*/
@UnstableApi
public abstract class AbstractDnsMessage extends AbstractReferenceCounted implements DnsMessage {
private static final ResourceLeakDetector<DnsMessage> leakDetector =
ResourceLeakDetectorFactory.instance().newResourceLeakDetector(DnsMessage.class);
private static final int SECTION_QUESTION = DnsSection.QUESTION.ordinal();
private static final int SECTION_COUNT = 4;
private final ResourceLeakTracker<DnsMessage> leak = leakDetector.track(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();
final ResourceLeakTracker<DnsMessage> leak = this.leak;
if (leak != null) {
boolean closed = leak.close(this);
assert closed;
}
}
@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;
default:
break;
}
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;
default:
break;
}
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,78 @@
/*
* Copyright 2016 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:
*
* https://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 io.netty.util.internal.UnstableApi;
/**
* An <a href="https://tools.ietf.org/html/rfc6891#section-6.1">OPT RR</a> record.
*
* This is used for <a href="https://tools.ietf.org/html/rfc6891#section-6.1.3">
* Extension Mechanisms for DNS (EDNS(0))</a>.
*/
@UnstableApi
public abstract class AbstractDnsOptPseudoRrRecord extends AbstractDnsRecord implements DnsOptPseudoRecord {
protected AbstractDnsOptPseudoRrRecord(int maxPayloadSize, int extendedRcode, int version) {
super(StringUtil.EMPTY_STRING, DnsRecordType.OPT, maxPayloadSize, packIntoLong(extendedRcode, version));
}
protected AbstractDnsOptPseudoRrRecord(int maxPayloadSize) {
super(StringUtil.EMPTY_STRING, DnsRecordType.OPT, maxPayloadSize, 0);
}
// See https://tools.ietf.org/html/rfc6891#section-6.1.3
private static long packIntoLong(int val, int val2) {
// We are currently not support DO and Z fields, just use 0.
return ((val & 0xffL) << 24 | (val2 & 0xff) << 16) & 0xFFFFFFFFL;
}
@Override
public int extendedRcode() {
return (short) (((int) timeToLive() >> 24) & 0xff);
}
@Override
public int version() {
return (short) (((int) timeToLive() >> 16) & 0xff);
}
@Override
public int flags() {
return (short) ((short) timeToLive() & 0xff);
}
@Override
public String toString() {
return toStringBuilder().toString();
}
final StringBuilder toStringBuilder() {
return new StringBuilder(64)
.append(StringUtil.simpleClassName(this))
.append('(')
.append("OPT flags:")
.append(flags())
.append(" version:")
.append(version())
.append(" extendedRecode:")
.append(extendedRcode())
.append(" udp:")
.append(dnsClass())
.append(')');
}
}

View file

@ -0,0 +1,165 @@
/*
* 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:
*
* https://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.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.UnstableApi;
import java.net.IDN;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
/**
* A skeletal implementation of {@link DnsRecord}.
*/
@UnstableApi
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) {
checkPositiveOrZero(timeToLive, "timeToLive");
// Convert to ASCII which will also check that the length is not too big.
// See:
// - https://github.com/netty/netty/issues/4937
// - https://github.com/netty/netty/issues/4935
this.name = appendTrailingDot(IDNtoASCII(name));
this.type = checkNotNull(type, "type");
this.dnsClass = (short) dnsClass;
this.timeToLive = timeToLive;
}
private static String IDNtoASCII(String name) {
checkNotNull(name, "name");
if (PlatformDependent.isAndroid() && DefaultDnsRecordDecoder.ROOT.equals(name)) {
// Prior Android 10 there was a bug that did not correctly parse ".".
//
// See https://github.com/netty/netty/issues/10034
return name;
}
return IDN.toASCII(name);
}
private static String appendTrailingDot(String name) {
if (name.length() > 0 && name.charAt(name.length() - 1) != '.') {
return name + '.';
}
return name;
}
@Override
public String name() {
return name;
}
@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,192 @@
/*
* 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:
*
* https://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.UnstableApi;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
/**
* A {@link DnsQuery} implementation for UDP/IP.
*/
@UnstableApi
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,62 @@
/*
* 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:
*
* https://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.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.util.internal.UnstableApi;
import java.util.List;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* Decodes a {@link DatagramPacket} into a {@link DatagramDnsQuery}.
*/
@UnstableApi
@ChannelHandler.Sharable
public class DatagramDnsQueryDecoder extends MessageToMessageDecoder<DatagramPacket> {
private final DnsRecordDecoder recordDecoder;
/**
* Creates a new decoder with {@linkplain DnsRecordDecoder#DEFAULT the default record decoder}.
*/
public DatagramDnsQueryDecoder() {
this(DnsRecordDecoder.DEFAULT);
}
/**
* Creates a new decoder with the specified {@code recordDecoder}.
*/
public DatagramDnsQueryDecoder(DnsRecordDecoder recordDecoder) {
this.recordDecoder = checkNotNull(recordDecoder, "recordDecoder");
}
@Override
protected void decode(ChannelHandlerContext ctx, final DatagramPacket packet, List<Object> out) throws Exception {
DnsQuery query = DnsMessageUtil.decodeDnsQuery(recordDecoder, packet.content(),
new DnsMessageUtil.DnsQueryFactory() {
@Override
public DnsQuery newQuery(int id, DnsOpCode dnsOpCode) {
return new DatagramDnsQuery(packet.sender(), packet.recipient(), id, dnsOpCode);
}
});
out.add(query);
}
}

View file

@ -0,0 +1,84 @@
/*
* 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:
*
* https://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 io.netty.util.internal.UnstableApi;
import java.net.InetSocketAddress;
import java.util.List;
/**
* Encodes a {@link DatagramDnsQuery} (or an {@link AddressedEnvelope} of {@link DnsQuery}} into a
* {@link DatagramPacket}.
*/
@UnstableApi
@ChannelHandler.Sharable
public class DatagramDnsQueryEncoder extends MessageToMessageEncoder<AddressedEnvelope<DnsQuery, InetSocketAddress>> {
private final DnsQueryEncoder encoder;
/**
* 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.encoder = new DnsQueryEncoder(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 {
encoder.encode(query, 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);
}
}

View file

@ -0,0 +1,222 @@
/*
* 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:
*
* https://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.UnstableApi;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
/**
* A {@link DnsResponse} implementation for UDP/IP.
*/
@UnstableApi
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) {
return that.recipient() == null;
} else {
return recipient().equals(that.recipient());
}
}
@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,69 @@
/*
* 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:
*
* https://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.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.internal.UnstableApi;
import java.net.InetSocketAddress;
import java.util.List;
/**
* Decodes a {@link DatagramPacket} into a {@link DatagramDnsResponse}.
*/
@UnstableApi
@ChannelHandler.Sharable
public class DatagramDnsResponseDecoder extends MessageToMessageDecoder<DatagramPacket> {
private final DnsResponseDecoder<InetSocketAddress> responseDecoder;
/**
* 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.responseDecoder = new DnsResponseDecoder<InetSocketAddress>(recordDecoder) {
@Override
protected DnsResponse newResponse(InetSocketAddress sender, InetSocketAddress recipient,
int id, DnsOpCode opCode, DnsResponseCode responseCode) {
return new DatagramDnsResponse(sender, recipient, id, opCode, responseCode);
}
};
}
@Override
protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List<Object> out) throws Exception {
try {
out.add(decodeResponse(ctx, packet));
} catch (IndexOutOfBoundsException e) {
throw new CorruptedFrameException("Unable to decode response", e);
}
}
protected DnsResponse decodeResponse(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
return responseDecoder.decode(packet.sender(), packet.recipient(), packet.content());
}
}

View file

@ -0,0 +1,78 @@
/*
* 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:
*
* https://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 io.netty.util.internal.UnstableApi;
import java.net.InetSocketAddress;
import java.util.List;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* Encodes a {@link DatagramDnsResponse} (or an {@link AddressedEnvelope} of {@link DnsResponse}} into a
* {@link DatagramPacket}.
*/
@UnstableApi
@ChannelHandler.Sharable
public class DatagramDnsResponseEncoder
extends MessageToMessageEncoder<AddressedEnvelope<DnsResponse, InetSocketAddress>> {
private final DnsRecordEncoder recordEncoder;
/**
* Creates a new encoder with {@linkplain DnsRecordEncoder#DEFAULT the default record encoder}.
*/
public DatagramDnsResponseEncoder() {
this(DnsRecordEncoder.DEFAULT);
}
/**
* Creates a new encoder with the specified {@code recordEncoder}.
*/
public DatagramDnsResponseEncoder(DnsRecordEncoder recordEncoder) {
this.recordEncoder = checkNotNull(recordEncoder, "recordEncoder");
}
@Override
protected void encode(ChannelHandlerContext ctx,
AddressedEnvelope<DnsResponse, InetSocketAddress> in, List<Object> out) throws Exception {
final InetSocketAddress recipient = in.recipient();
final DnsResponse response = in.content();
final ByteBuf buf = allocateBuffer(ctx, in);
DnsMessageUtil.encodeDnsResponse(recordEncoder, response, buf);
out.add(new DatagramPacket(buf, recipient, null));
}
/**
* Allocate a {@link ByteBuf} which will be used for constructing a datagram packet.
* Sub-classes may override this method to return a {@link ByteBuf} with a perfect matching initial capacity.
*/
protected ByteBuf allocateBuffer(
ChannelHandlerContext ctx,
@SuppressWarnings("unused") AddressedEnvelope<DnsResponse, InetSocketAddress> msg) throws Exception {
return ctx.alloc().ioBuffer(1024);
}
}

View file

@ -0,0 +1,104 @@
/*
* Copyright 2016 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:
*
* https://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.socket.InternetProtocolFamily;
import io.netty.util.internal.UnstableApi;
import java.net.InetAddress;
import java.util.Arrays;
/**
* Default {@link DnsOptEcsRecord} implementation.
*/
@UnstableApi
public final class DefaultDnsOptEcsRecord extends AbstractDnsOptPseudoRrRecord implements DnsOptEcsRecord {
private final int srcPrefixLength;
private final byte[] address;
/**
* Creates a new instance.
*
* @param maxPayloadSize the suggested max payload size in bytes
* @param extendedRcode the extended rcode
* @param version the version
* @param srcPrefixLength the prefix length
* @param address the bytes of the {@link InetAddress} to use
*/
public DefaultDnsOptEcsRecord(int maxPayloadSize, int extendedRcode, int version,
int srcPrefixLength, byte[] address) {
super(maxPayloadSize, extendedRcode, version);
this.srcPrefixLength = srcPrefixLength;
this.address = verifyAddress(address).clone();
}
/**
* Creates a new instance.
*
* @param maxPayloadSize the suggested max payload size in bytes
* @param srcPrefixLength the prefix length
* @param address the bytes of the {@link InetAddress} to use
*/
public DefaultDnsOptEcsRecord(int maxPayloadSize, int srcPrefixLength, byte[] address) {
this(maxPayloadSize, 0, 0, srcPrefixLength, address);
}
/**
* Creates a new instance.
*
* @param maxPayloadSize the suggested max payload size in bytes
* @param protocolFamily the {@link InternetProtocolFamily} to use. This should be the same as the one used to
* send the query.
*/
public DefaultDnsOptEcsRecord(int maxPayloadSize, InternetProtocolFamily protocolFamily) {
this(maxPayloadSize, 0, 0, 0, protocolFamily.localhost().getAddress());
}
private static byte[] verifyAddress(byte[] bytes) {
if (bytes.length == 4 || bytes.length == 16) {
return bytes;
}
throw new IllegalArgumentException("bytes.length must either 4 or 16");
}
@Override
public int sourcePrefixLength() {
return srcPrefixLength;
}
@Override
public int scopePrefixLength() {
return 0;
}
@Override
public byte[] address() {
return address.clone();
}
@Override
public String toString() {
StringBuilder sb = toStringBuilder();
sb.setLength(sb.length() - 1);
return sb.append(" address:")
.append(Arrays.toString(address))
.append(" sourcePrefixLength:")
.append(sourcePrefixLength())
.append(" scopePrefixLength:")
.append(scopePrefixLength())
.append(')').toString();
}
}

View file

@ -0,0 +1,73 @@
/*
* Copyright 2016 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:
*
* https://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;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.UnstableApi;
@UnstableApi
public class DefaultDnsPtrRecord extends AbstractDnsRecord implements DnsPtrRecord {
private final String hostname;
/**
* Creates a new PTR record.
*
* @param name the domain name
* @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
* @param hostname the hostname this PTR record resolves to.
*/
public DefaultDnsPtrRecord(
String name, int dnsClass, long timeToLive, String hostname) {
super(name, DnsRecordType.PTR, dnsClass, timeToLive);
this.hostname = checkNotNull(hostname, "hostname");
}
@Override
public String hostname() {
return hostname;
}
@Override
public String toString() {
final StringBuilder buf = new StringBuilder(64).append(StringUtil.simpleClassName(this)).append('(');
final DnsRecordType type = type();
buf.append(name().isEmpty()? "<root>" : name())
.append(' ')
.append(timeToLive())
.append(' ');
DnsMessageUtil.appendRecordClass(buf, dnsClass())
.append(' ')
.append(type.name());
buf.append(' ')
.append(hostname);
return buf.toString();
}
}

View file

@ -0,0 +1,114 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://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.UnstableApi;
/**
* The default {@link DnsQuery} implementation.
*/
@UnstableApi
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,72 @@
/*
* 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:
*
* https://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 io.netty.util.internal.UnstableApi;
/**
* The default {@link DnsQuestion} implementation.
*/
@UnstableApi
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,155 @@
/*
* 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:
*
* https://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 io.netty.util.internal.UnstableApi;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* The default {@code DnsRawRecord} implementation.
*/
@UnstableApi
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 replace(content().copy());
}
@Override
public DnsRawRecord duplicate() {
return replace(content().duplicate());
}
@Override
public DnsRawRecord retainedDuplicate() {
return replace(content().retainedDuplicate());
}
@Override
public DnsRawRecord replace(ByteBuf content) {
return new DefaultDnsRawRecord(name(), type(), dnsClass(), timeToLive(), content);
}
@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,131 @@
/*
* 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:
*
* https://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.UnstableApi;
/**
* The default {@link DnsRecordDecoder} implementation.
*
* @see DefaultDnsRecordEncoder
*/
@UnstableApi
public class DefaultDnsRecordDecoder implements DnsRecordDecoder {
static final String ROOT = ".";
/**
* 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 int startOffset = in.readerIndex();
final String name = decodeName(in);
final int endOffset = in.writerIndex();
if (endOffset - in.readerIndex() < 10) {
// Not enough data
in.readerIndex(startOffset);
return null;
}
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();
if (endOffset - offset < length) {
// Not enough data
in.readerIndex(startOffset);
return null;
}
@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 {
// DNS message compression means that domain names may contain "pointers" to other positions in the packet
// to build a full message. This means the indexes are meaningful and we need the ability to reference the
// indexes un-obstructed, and thus we cannot use a slice here.
// See https://www.ietf.org/rfc/rfc1035 [4.1.4. Message compression]
if (type == DnsRecordType.PTR) {
return new DefaultDnsPtrRecord(
name, dnsClass, timeToLive, decodeName0(in.duplicate().setIndex(offset, offset + length)));
}
if (type == DnsRecordType.CNAME || type == DnsRecordType.NS) {
return new DefaultDnsRawRecord(name, type, dnsClass, timeToLive,
DnsCodecUtil.decompressDomainName(
in.duplicate().setIndex(offset, offset + length)));
}
return new DefaultDnsRawRecord(
name, type, dnsClass, timeToLive, in.retainedDuplicate().setIndex(offset, offset + length));
}
/**
* 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 decodeName0(ByteBuf in) {
return decodeName(in);
}
/**
* 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
*/
public static String decodeName(ByteBuf in) {
return DnsCodecUtil.decodeDomainName(in);
}
}

View file

@ -0,0 +1,168 @@
/*
* 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:
*
* https://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.socket.InternetProtocolFamily;
import io.netty.handler.codec.UnsupportedMessageTypeException;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.UnstableApi;
/**
* The default {@link DnsRecordEncoder} implementation.
*
* @see DefaultDnsRecordDecoder
*/
@UnstableApi
public class DefaultDnsRecordEncoder implements DnsRecordEncoder {
private static final int PREFIX_MASK = Byte.SIZE - 1;
/**
* 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 DnsPtrRecord) {
encodePtrRecord((DnsPtrRecord) record, out);
} else if (record instanceof DnsOptEcsRecord) {
encodeOptEcsRecord((DnsOptEcsRecord) record, out);
} else if (record instanceof DnsOptPseudoRecord) {
encodeOptPseudoRecord((DnsOptPseudoRecord) record, out);
} else if (record instanceof DnsRawRecord) {
encodeRawRecord((DnsRawRecord) record, out);
} else {
throw new UnsupportedMessageTypeException(StringUtil.simpleClassName(record));
}
}
private void encodeRecord0(DnsRecord record, ByteBuf out) throws Exception {
encodeName(record.name(), out);
out.writeShort(record.type().intValue());
out.writeShort(record.dnsClass());
out.writeInt((int) record.timeToLive());
}
private void encodePtrRecord(DnsPtrRecord record, ByteBuf out) throws Exception {
encodeRecord0(record, out);
encodeName(record.hostname(), out);
}
private void encodeOptPseudoRecord(DnsOptPseudoRecord record, ByteBuf out) throws Exception {
encodeRecord0(record, out);
out.writeShort(0);
}
private void encodeOptEcsRecord(DnsOptEcsRecord record, ByteBuf out) throws Exception {
encodeRecord0(record, out);
int sourcePrefixLength = record.sourcePrefixLength();
int scopePrefixLength = record.scopePrefixLength();
int lowOrderBitsToPreserve = sourcePrefixLength & PREFIX_MASK;
byte[] bytes = record.address();
int addressBits = bytes.length << 3;
if (addressBits < sourcePrefixLength || sourcePrefixLength < 0) {
throw new IllegalArgumentException(sourcePrefixLength + ": " +
sourcePrefixLength + " (expected: 0 >= " + addressBits + ')');
}
// See https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml
final short addressNumber = (short) (bytes.length == 4 ?
InternetProtocolFamily.IPv4.addressNumber() : InternetProtocolFamily.IPv6.addressNumber());
int payloadLength = calculateEcsAddressLength(sourcePrefixLength, lowOrderBitsToPreserve);
int fullPayloadLength = 2 + // OPTION-CODE
2 + // OPTION-LENGTH
2 + // FAMILY
1 + // SOURCE PREFIX-LENGTH
1 + // SCOPE PREFIX-LENGTH
payloadLength; // ADDRESS...
out.writeShort(fullPayloadLength);
out.writeShort(8); // This is the defined type for ECS.
out.writeShort(fullPayloadLength - 4); // Not include OPTION-CODE and OPTION-LENGTH
out.writeShort(addressNumber);
out.writeByte(sourcePrefixLength);
out.writeByte(scopePrefixLength); // Must be 0 in queries.
if (lowOrderBitsToPreserve > 0) {
int bytesLength = payloadLength - 1;
out.writeBytes(bytes, 0, bytesLength);
// Pad the leftover of the last byte with zeros.
out.writeByte(padWithZeros(bytes[bytesLength], lowOrderBitsToPreserve));
} else {
// The sourcePrefixLength align with Byte so just copy in the bytes directly.
out.writeBytes(bytes, 0, payloadLength);
}
}
// Package-Private for testing
static int calculateEcsAddressLength(int sourcePrefixLength, int lowOrderBitsToPreserve) {
return (sourcePrefixLength >>> 3) + (lowOrderBitsToPreserve != 0 ? 1 : 0);
}
private void encodeRawRecord(DnsRawRecord record, ByteBuf out) throws Exception {
encodeRecord0(record, out);
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 {
DnsCodecUtil.encodeDomainName(name, buf);
}
private static byte padWithZeros(byte b, int lowOrderBitsToPreserve) {
switch (lowOrderBitsToPreserve) {
case 0:
return 0;
case 1:
return (byte) (0x80 & b);
case 2:
return (byte) (0xC0 & b);
case 3:
return (byte) (0xE0 & b);
case 4:
return (byte) (0xF0 & b);
case 5:
return (byte) (0xF8 & b);
case 6:
return (byte) (0xFC & b);
case 7:
return (byte) (0xFE & b);
case 8:
return b;
default:
throw new IllegalArgumentException("lowOrderBitsToPreserve: " + lowOrderBitsToPreserve);
}
}
}

View file

@ -0,0 +1,178 @@
/*
* 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:
*
* https://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.UnstableApi;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* The default {@link DnsResponse} implementation.
*/
@UnstableApi
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

@ -0,0 +1,131 @@
/*
* Copyright 2019 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:
*
* https://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.CorruptedFrameException;
import io.netty.util.CharsetUtil;
import static io.netty.handler.codec.dns.DefaultDnsRecordDecoder.*;
final class DnsCodecUtil {
private DnsCodecUtil() {
// Util class
}
static void encodeDomainName(String name, ByteBuf buf) {
if (ROOT.equals(name)) {
// Root domain
buf.writeByte(0);
return;
}
final String[] labels = name.split("\\.");
for (String label : labels) {
final int labelLen = label.length();
if (labelLen == 0) {
// zero-length label means the end of the name.
break;
}
buf.writeByte(labelLen);
ByteBufUtil.writeAscii(buf, label);
}
buf.writeByte(0); // marks end of name field
}
static String decodeDomainName(ByteBuf in) {
int position = -1;
int checked = 0;
final int end = in.writerIndex();
final int readable = in.readableBytes();
// Looking at the spec we should always have at least enough readable bytes to read a byte here but it seems
// some servers do not respect this for empty names. So just workaround this and return an empty name in this
// case.
//
// See:
// - https://github.com/netty/netty/issues/5014
// - https://www.ietf.org/rfc/rfc1035.txt , Section 3.1
if (readable == 0) {
return ROOT;
}
final StringBuilder name = new StringBuilder(readable << 1);
while (in.isReadable()) {
final int len = in.readUnsignedByte();
final boolean pointer = (len & 0xc0) == 0xc0;
if (pointer) {
if (position == -1) {
position = in.readerIndex() + 1;
}
if (!in.isReadable()) {
throw new CorruptedFrameException("truncated pointer in a name");
}
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 if (len != 0) {
if (!in.isReadable(len)) {
throw new CorruptedFrameException("truncated label in a name");
}
name.append(in.toString(in.readerIndex(), len, CharsetUtil.UTF_8)).append('.');
in.skipBytes(len);
} else { // len == 0
break;
}
}
if (position != -1) {
in.readerIndex(position);
}
if (name.length() == 0) {
return ROOT;
}
if (name.charAt(name.length() - 1) != '.') {
name.append('.');
}
return name.toString();
}
/**
* Decompress pointer data.
* @param compression compressed data
* @return decompressed data
*/
static ByteBuf decompressDomainName(ByteBuf compression) {
String domainName = decodeDomainName(compression);
ByteBuf result = compression.alloc().buffer(domainName.length() << 1);
encodeDomainName(domainName, result);
return result;
}
}

View file

@ -0,0 +1,158 @@
/*
* 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:
*
* https://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.ReferenceCounted;
import io.netty.util.internal.UnstableApi;
/**
* The superclass which contains core information concerning a {@link DnsQuery} and a {@link DnsResponse}.
*/
@UnstableApi
public interface DnsMessage extends ReferenceCounted {
/**
* Returns the {@code ID} of this DNS message.
*/
int id();
/**
* Sets the {@code ID} of this DNS message.
*/
DnsMessage setId(int id);
/**
* Returns the {@code opCode} of this DNS message.
*/
DnsOpCode opCode();
/**
* Sets the {@code opCode} of this DNS message.
*/
DnsMessage setOpCode(DnsOpCode opCode);
/**
* Returns the {@code RD} (recursion desired} field of this DNS message.
*/
boolean isRecursionDesired();
/**
* 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}.
*
* @return {@code null} if this message doesn't have any records in the specified {@code section}
*/
<T extends DnsRecord> T recordAt(DnsSection section);
/**
* 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}.
*
* @throws IndexOutOfBoundsException if the specified {@code index} is out of bounds
*/
<T extends DnsRecord> T recordAt(DnsSection section, int index);
/**
* Sets the specified {@code section} of this DNS message to the specified {@code record},
* making it a single-record section. When the specified {@code section} is {@link DnsSection#QUESTION},
* the specified {@code record} must be a {@link DnsQuestion}.
*/
DnsMessage setRecord(DnsSection section, DnsRecord record);
/**
* 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}.
*
* @return the old record
* @throws IndexOutOfBoundsException if the specified {@code index} is out of bounds
*/
<T extends DnsRecord> T setRecord(DnsSection section, int index, DnsRecord record);
/**
* Adds the specified {@code record} at the end of the specified {@code section} of this DNS message.
* When the specified {@code section} is {@link DnsSection#QUESTION}, the specified {@code record}
* must be a {@link DnsQuestion}.
*/
DnsMessage addRecord(DnsSection section, DnsRecord record);
/**
* Adds the specified {@code record} at the specified {@code index} of the specified {@code section}
* of this DNS message. When the specified {@code section} is {@link DnsSection#QUESTION}, the specified
* {@code record} must be a {@link DnsQuestion}.
*
* @throws IndexOutOfBoundsException if the specified {@code index} is out of bounds
*/
DnsMessage addRecord(DnsSection section, int index, DnsRecord record);
/**
* Removes the record at the specified {@code index} of the specified {@code section} from this DNS message.
* When the specified {@code section} is {@link DnsSection#QUESTION}, the type of the returned record is
* always {@link DnsQuestion}.
*
* @return the removed record
*/
<T extends DnsRecord> T removeRecord(DnsSection section, int index);
/**
* Removes all the records in the specified {@code section} of this DNS message.
*/
DnsMessage clear(DnsSection section);
/**
* Removes all the records in this DNS message.
*/
DnsMessage clear();
@Override
DnsMessage touch();
@Override
DnsMessage touch(Object hint);
@Override
DnsMessage retain();
@Override
DnsMessage retain(int increment);
}

View file

@ -0,0 +1,304 @@
/*
* 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:
*
* https://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.handler.codec.CorruptedFrameException;
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("id: ")
.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("id: ")
.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.<DnsRecord>recordAt(section, i));
}
}
static DnsQuery decodeDnsQuery(DnsRecordDecoder decoder, ByteBuf buf, DnsQueryFactory supplier) throws Exception {
DnsQuery query = newQuery(buf, supplier);
boolean success = false;
try {
int questionCount = buf.readUnsignedShort();
int answerCount = buf.readUnsignedShort();
int authorityRecordCount = buf.readUnsignedShort();
int additionalRecordCount = buf.readUnsignedShort();
decodeQuestions(decoder, query, buf, questionCount);
decodeRecords(decoder, query, DnsSection.ANSWER, buf, answerCount);
decodeRecords(decoder, query, DnsSection.AUTHORITY, buf, authorityRecordCount);
decodeRecords(decoder, query, DnsSection.ADDITIONAL, buf, additionalRecordCount);
success = true;
return query;
} finally {
if (!success) {
query.release();
}
}
}
private static DnsQuery newQuery(ByteBuf buf, DnsQueryFactory supplier) {
int id = buf.readUnsignedShort();
int flags = buf.readUnsignedShort();
if (flags >> 15 == 1) {
throw new CorruptedFrameException("not a query");
}
DnsQuery query = supplier.newQuery(id, DnsOpCode.valueOf((byte) (flags >> 11 & 0xf)));
query.setRecursionDesired((flags >> 8 & 1) == 1);
query.setZ(flags >> 4 & 0x7);
return query;
}
private static void decodeQuestions(DnsRecordDecoder decoder,
DnsQuery query, ByteBuf buf, int questionCount) throws Exception {
for (int i = questionCount; i > 0; --i) {
query.addRecord(DnsSection.QUESTION, decoder.decodeQuestion(buf));
}
}
private static void decodeRecords(DnsRecordDecoder decoder,
DnsQuery query, DnsSection section, ByteBuf buf, int count) throws Exception {
for (int i = count; i > 0; --i) {
DnsRecord r = decoder.decodeRecord(buf);
if (r == null) {
break;
}
query.addRecord(section, r);
}
}
static void encodeDnsResponse(DnsRecordEncoder encoder, DnsResponse response, ByteBuf buf) throws Exception {
boolean success = false;
try {
encodeHeader(response, buf);
encodeQuestions(encoder, response, buf);
encodeRecords(encoder, response, DnsSection.ANSWER, buf);
encodeRecords(encoder, response, DnsSection.AUTHORITY, buf);
encodeRecords(encoder, response, DnsSection.ADDITIONAL, buf);
success = true;
} finally {
if (!success) {
buf.release();
}
}
}
/**
* Encodes the header that is always 12 bytes long.
*
* @param response the response header being encoded
* @param buf the buffer the encoded data should be written to
*/
private static void encodeHeader(DnsResponse response, ByteBuf buf) {
buf.writeShort(response.id());
int flags = 32768;
flags |= (response.opCode().byteValue() & 0xFF) << 11;
if (response.isAuthoritativeAnswer()) {
flags |= 1 << 10;
}
if (response.isTruncated()) {
flags |= 1 << 9;
}
if (response.isRecursionDesired()) {
flags |= 1 << 8;
}
if (response.isRecursionAvailable()) {
flags |= 1 << 7;
}
flags |= response.z() << 4;
flags |= response.code().intValue();
buf.writeShort(flags);
buf.writeShort(response.count(DnsSection.QUESTION));
buf.writeShort(response.count(DnsSection.ANSWER));
buf.writeShort(response.count(DnsSection.AUTHORITY));
buf.writeShort(response.count(DnsSection.ADDITIONAL));
}
private static void encodeQuestions(DnsRecordEncoder encoder, DnsResponse response, ByteBuf buf) throws Exception {
int count = response.count(DnsSection.QUESTION);
for (int i = 0; i < count; ++i) {
encoder.encodeQuestion(response.<DnsQuestion>recordAt(DnsSection.QUESTION, i), buf);
}
}
private static void encodeRecords(DnsRecordEncoder encoder,
DnsResponse response, DnsSection section, ByteBuf buf) throws Exception {
int count = response.count(section);
for (int i = 0; i < count; ++i) {
encoder.encodeRecord(response.recordAt(section, i), buf);
}
}
interface DnsQueryFactory {
DnsQuery newQuery(int id, DnsOpCode dnsOpCode);
}
private DnsMessageUtil() {
}
}

View file

@ -0,0 +1,123 @@
/*
* 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:
*
* https://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.UnstableApi;
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>.
*/
@UnstableApi
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;
default:
break;
}
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

@ -0,0 +1,43 @@
/*
* Copyright 2016 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:
*
* https://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.UnstableApi;
import java.net.InetAddress;
/**
* An ECS record as defined in <a href="https://tools.ietf.org/html/rfc7871#section-6">Client Subnet in DNS Queries</a>.
*/
@UnstableApi
public interface DnsOptEcsRecord extends DnsOptPseudoRecord {
/**
* Returns the leftmost number of significant bits of ADDRESS to be used for the lookup.
*/
int sourcePrefixLength();
/**
* Returns the leftmost number of significant bits of ADDRESS that the response covers.
* In queries, it MUST be 0.
*/
int scopePrefixLength();
/**
* Returns the bytes of the {@link InetAddress} to use.
*/
byte[] address();
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2016 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:
*
* https://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.UnstableApi;
/**
* An <a href="https://tools.ietf.org/html/rfc6891#section-6.1">OPT RR</a> record.
* <p>
* This is used for <a href="https://tools.ietf.org/html/rfc6891#section-6.1.3">Extension
* Mechanisms for DNS (EDNS(0))</a>.
*/
@UnstableApi
public interface DnsOptPseudoRecord extends DnsRecord {
/**
* Returns the {@code EXTENDED-RCODE} which is encoded into {@link DnsOptPseudoRecord#timeToLive()}.
*/
int extendedRcode();
/**
* Returns the {@code VERSION} which is encoded into {@link DnsOptPseudoRecord#timeToLive()}.
*/
int version();
/**
* Returns the {@code flags} which includes {@code DO} and {@code Z} which is encoded
* into {@link DnsOptPseudoRecord#timeToLive()}.
*/
int flags();
}

View file

@ -0,0 +1,28 @@
/*
* Copyright 2016 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:
*
* https://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.UnstableApi;
@UnstableApi
public interface DnsPtrRecord extends DnsRecord {
/**
* Returns the hostname this PTR record resolves to.
*/
String hostname();
}

View file

@ -0,0 +1,63 @@
/*
* 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:
*
* https://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.UnstableApi;
/**
* A DNS query message.
*/
@UnstableApi
public interface DnsQuery extends DnsMessage {
@Override
DnsQuery setId(int id);
@Override
DnsQuery setOpCode(DnsOpCode opCode);
@Override
DnsQuery setRecursionDesired(boolean recursionDesired);
@Override
DnsQuery setZ(int z);
@Override
DnsQuery setRecord(DnsSection section, DnsRecord record);
@Override
DnsQuery addRecord(DnsSection section, DnsRecord record);
@Override
DnsQuery addRecord(DnsSection section, int index, DnsRecord record);
@Override
DnsQuery clear(DnsSection section);
@Override
DnsQuery clear();
@Override
DnsQuery touch();
@Override
DnsQuery touch(Object hint);
@Override
DnsQuery retain();
@Override
DnsQuery retain(int increment);
}

View file

@ -0,0 +1,75 @@
/*
* Copyright 2019 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:
*
* https://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 static io.netty.util.internal.ObjectUtil.checkNotNull;
final class DnsQueryEncoder {
private final DnsRecordEncoder recordEncoder;
/**
* Creates a new encoder with the specified {@code recordEncoder}.
*/
DnsQueryEncoder(DnsRecordEncoder recordEncoder) {
this.recordEncoder = checkNotNull(recordEncoder, "recordEncoder");
}
/**
* Encodes the given {@link DnsQuery} into a {@link ByteBuf}.
*/
void encode(DnsQuery query, ByteBuf out) throws Exception {
encodeHeader(query, out);
encodeQuestions(query, out);
encodeRecords(query, DnsSection.ADDITIONAL, out);
}
/**
* 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;
if (query.isRecursionDesired()) {
flags |= 1 << 8;
}
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,30 @@
/*
* 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:
*
* https://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.UnstableApi;
/**
* A DNS question.
*/
@UnstableApi
public interface DnsQuestion extends DnsRecord {
/**
* An unused property. This method will always return {@code 0}.
*/
@Override
long timeToLive();
}

View file

@ -0,0 +1,50 @@
/*
* 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:
*
* https://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;
import io.netty.util.internal.UnstableApi;
/**
* A generic {@link DnsRecord} that contains an undecoded {@code RDATA}.
*/
@UnstableApi
public interface DnsRawRecord extends DnsRecord, ByteBufHolder {
@Override
DnsRawRecord copy();
@Override
DnsRawRecord duplicate();
@Override
DnsRawRecord retainedDuplicate();
@Override
DnsRawRecord replace(ByteBuf content);
@Override
DnsRawRecord retain();
@Override
DnsRawRecord retain(int increment);
@Override
DnsRawRecord touch();
@Override
DnsRawRecord touch(Object hint);
}

View file

@ -0,0 +1,85 @@
/*
* 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:
*
* https://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.UnstableApi;
/**
* A DNS resource record.
*/
@UnstableApi
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,46 @@
/*
* 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:
*
* https://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.UnstableApi;
/**
* Decodes a DNS record into its object representation.
*
* @see DatagramDnsResponseDecoder
*/
@UnstableApi
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
*
* @return the decoded record, or {@code null} if there are not enough data in the input buffer
*/
<T extends DnsRecord> T decodeRecord(ByteBuf in) throws Exception;
}

View file

@ -0,0 +1,44 @@
/*
* 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:
*
* https://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.UnstableApi;
/**
* Encodes a {@link DnsRecord} into binary representation.
*
* @see DatagramDnsQueryEncoder
*/
@UnstableApi
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

@ -0,0 +1,404 @@
/*
* 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:
*
* https://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.collection.IntObjectHashMap;
import io.netty.util.internal.UnstableApi;
import java.util.HashMap;
import java.util.Map;
/**
* Represents a DNS record type.
*/
@UnstableApi
public class DnsRecordType implements Comparable<DnsRecordType> {
/**
* Address record RFC 1035 Returns a 32-bit IPv4 address, most commonly used
* to map hostnames to an IP address of the host, but also used for DNSBLs,
* storing subnet masks in RFC 1101, etc.
*/
public static final DnsRecordType A = new DnsRecordType(0x0001, "A");
/**
* Name server record RFC 1035 Delegates a DNS zone to use the given
* authoritative name servers
*/
public static final DnsRecordType NS = new DnsRecordType(0x0002, "NS");
/**
* Canonical name record RFC 1035 Alias of one name to another: the DNS
* lookup will continue by retrying the lookup with the new name.
*/
public static final DnsRecordType CNAME = new DnsRecordType(0x0005, "CNAME");
/**
* Start of [a zone of] authority record RFC 1035 and RFC 2308 Specifies
* authoritative information about a DNS zone, including the primary name
* server, the email of the domain administrator, the domain serial number,
* and several timers relating to refreshing the zone.
*/
public static final DnsRecordType SOA = new DnsRecordType(0x0006, "SOA");
/**
* Pointer record RFC 1035 Pointer to a canonical name. Unlike a CNAME, DNS
* processing does NOT proceed, just the name is returned. The most common
* use is for implementing reverse DNS lookups, but other uses include such
* things as DNS-SD.
*/
public static final DnsRecordType PTR = new DnsRecordType(0x000c, "PTR");
/**
* Mail exchange record RFC 1035 Maps a domain name to a list of message
* transfer agents for that domain.
*/
public static final DnsRecordType MX = new DnsRecordType(0x000f, "MX");
/**
* Text record RFC 1035 Originally for arbitrary human-readable text in a
* DNS record. Since the early 1990s, however, this record more often
* carries machine-readable data, such as specified by RFC 1464,
* opportunistic encryption, Sender Policy Framework, DKIM, DMARC DNS-SD,
* etc.
*/
public static final DnsRecordType TXT = new DnsRecordType(0x0010, "TXT");
/**
* Responsible person record RFC 1183 Information about the responsible
* person(s) for the domain. Usually an email address with the @ replaced by
* a .
*/
public static final DnsRecordType RP = new DnsRecordType(0x0011, "RP");
/**
* AFS database record RFC 1183 Location of database servers of an AFS cell.
* This record is commonly used by AFS clients to contact AFS cells outside
* their local domain. A subtype of this record is used by the obsolete
* DCE/DFS file system.
*/
public static final DnsRecordType AFSDB = new DnsRecordType(0x0012, "AFSDB");
/**
* Signature record RFC 2535 Signature record used in SIG(0) (RFC 2931) and
* TKEY (RFC 2930). RFC 3755 designated RRSIG as the replacement for SIG for
* use within DNSSEC.
*/
public static final DnsRecordType SIG = new DnsRecordType(0x0018, "SIG");
/**
* key record RFC 2535 and RFC 2930 Used only for SIG(0) (RFC 2931) and TKEY
* (RFC 2930). RFC 3445 eliminated their use for application keys and
* limited their use to DNSSEC. RFC 3755 designates DNSKEY as the
* replacement within DNSSEC. RFC 4025 designates IPSECKEY as the
* replacement for use with IPsec.
*/
public static final DnsRecordType KEY = new DnsRecordType(0x0019, "KEY");
/**
* IPv6 address record RFC 3596 Returns a 128-bit IPv6 address, most
* commonly used to map hostnames to an IP address of the host.
*/
public static final DnsRecordType AAAA = new DnsRecordType(0x001c, "AAAA");
/**
* Location record RFC 1876 Specifies a geographical location associated
* with a domain name.
*/
public static final DnsRecordType LOC = new DnsRecordType(0x001d, "LOC");
/**
* Service locator RFC 2782 Generalized service location record, used for
* newer protocols instead of creating protocol-specific records such as MX.
*/
public static final DnsRecordType SRV = new DnsRecordType(0x0021, "SRV");
/**
* Naming Authority Pointer record RFC 3403 Allows regular expression based
* rewriting of domain names which can then be used as URIs, further domain
* names to lookups, etc.
*/
public static final DnsRecordType NAPTR = new DnsRecordType(0x0023, "NAPTR");
/**
* Key eXchanger record RFC 2230 Used with some cryptographic systems (not
* including DNSSEC) to identify a key management agent for the associated
* domain-name. Note that this has nothing to do with DNS Security. It is
* Informational status, rather than being on the IETF standards-track. It
* has always had limited deployment, but is still in use.
*/
public static final DnsRecordType KX = new DnsRecordType(0x0024, "KX");
/**
* Certificate record RFC 4398 Stores PKIX, SPKI, PGP, etc.
*/
public static final DnsRecordType CERT = new DnsRecordType(0x0025, "CERT");
/**
* Delegation name record RFC 2672 DNAME creates an alias for a name and all
* its subnames, unlike CNAME, which aliases only the exact name in its
* label. Like the CNAME record, the DNS lookup will continue by retrying
* the lookup with the new name.
*/
public static final DnsRecordType DNAME = new DnsRecordType(0x0027, "DNAME");
/**
* Option record RFC 2671 This is a pseudo DNS record type needed to support
* EDNS.
*/
public static final DnsRecordType OPT = new DnsRecordType(0x0029, "OPT");
/**
* Address Prefix List record RFC 3123 Specify lists of address ranges, e.g.
* in CIDR format, for various address families. Experimental.
*/
public static final DnsRecordType APL = new DnsRecordType(0x002a, "APL");
/**
* Delegation signer record RFC 4034 The record used to identify the DNSSEC
* signing key of a delegated zone.
*/
public static final DnsRecordType DS = new DnsRecordType(0x002b, "DS");
/**
* SSH Public Key Fingerprint record RFC 4255 Resource record for publishing
* SSH public host key fingerprints in the DNS System, in order to aid in
* verifying the authenticity of the host. RFC 6594 defines ECC SSH keys and
* SHA-256 hashes. See the IANA SSHFP RR parameters registry for details.
*/
public static final DnsRecordType SSHFP = new DnsRecordType(0x002c, "SSHFP");
/**
* IPsec Key record RFC 4025 Key record that can be used with IPsec.
*/
public static final DnsRecordType IPSECKEY = new DnsRecordType(0x002d, "IPSECKEY");
/**
* DNSSEC signature record RFC 4034 Signature for a DNSSEC-secured record
* set. Uses the same format as the SIG record.
*/
public static final DnsRecordType RRSIG = new DnsRecordType(0x002e, "RRSIG");
/**
* Next-Secure record RFC 4034 Part of DNSSEC, used to prove a name does not
* exist. Uses the same format as the (obsolete) NXT record.
*/
public static final DnsRecordType NSEC = new DnsRecordType(0x002f, "NSEC");
/**
* DNS Key record RFC 4034 The key record used in DNSSEC. Uses the same
* format as the KEY record.
*/
public static final DnsRecordType DNSKEY = new DnsRecordType(0x0030, "DNSKEY");
/**
* DHCP identifier record RFC 4701 Used in conjunction with the FQDN option
* to DHCP.
*/
public static final DnsRecordType DHCID = new DnsRecordType(0x0031, "DHCID");
/**
* NSEC record version 3 RFC 5155 An extension to DNSSEC that allows proof
* of nonexistence for a name without permitting zonewalking.
*/
public static final DnsRecordType NSEC3 = new DnsRecordType(0x0032, "NSEC3");
/**
* NSEC3 parameters record RFC 5155 Parameter record for use with NSEC3.
*/
public static final DnsRecordType NSEC3PARAM = new DnsRecordType(0x0033, "NSEC3PARAM");
/**
* TLSA certificate association record RFC 6698 A record for DNS-based
* Authentication of Named Entities (DANE). RFC 6698 defines The TLSA DNS
* resource record is used to associate a TLS server certificate or public
* key with the domain name where the record is found, thus forming a 'TLSA
* certificate association'.
*/
public static final DnsRecordType TLSA = new DnsRecordType(0x0034, "TLSA");
/**
* Host Identity Protocol record RFC 5205 Method of separating the end-point
* identifier and locator roles of IP addresses.
*/
public static final DnsRecordType HIP = new DnsRecordType(0x0037, "HIP");
/**
* Sender Policy Framework record RFC 4408 Specified as part of the SPF
* protocol as an alternative to of storing SPF data in TXT records. Uses
* the same format as the earlier TXT record.
*/
public static final DnsRecordType SPF = new DnsRecordType(0x0063, "SPF");
/**
* Secret key record RFC 2930 A method of providing keying material to be
* used with TSIG that is encrypted under the public key in an accompanying
* KEY RR..
*/
public static final DnsRecordType TKEY = new DnsRecordType(0x00f9, "TKEY");
/**
* Transaction Signature record RFC 2845 Can be used to authenticate dynamic
* updates as coming from an approved client, or to authenticate responses
* as coming from an approved recursive name server similar to DNSSEC.
*/
public static final DnsRecordType TSIG = new DnsRecordType(0x00fa, "TSIG");
/**
* Incremental Zone Transfer record RFC 1996 Requests a zone transfer of the
* given zone but only differences from a previous serial number. This
* request may be ignored and a full (AXFR) sent in response if the
* authoritative server is unable to fulfill the request due to
* configuration or lack of required deltas.
*/
public static final DnsRecordType IXFR = new DnsRecordType(0x00fb, "IXFR");
/**
* Authoritative Zone Transfer record RFC 1035 Transfer entire zone file
* from the master name server to secondary name servers.
*/
public static final DnsRecordType AXFR = new DnsRecordType(0x00fc, "AXFR");
/**
* All cached records RFC 1035 Returns all records of all types known to the
* name server. If the name server does not have any information on the
* name, the request will be forwarded on. The records returned may not be
* complete. For example, if there is both an A and an MX for a name, but
* the name server has only the A record cached, only the A record will be
* returned. Sometimes referred to as ANY, for example in Windows nslookup
* and Wireshark.
*/
public static final DnsRecordType ANY = new DnsRecordType(0x00ff, "ANY");
/**
* Certification Authority Authorization record RFC 6844 CA pinning,
* constraining acceptable CAs for a host/domain.
*/
public static final DnsRecordType CAA = new DnsRecordType(0x0101, "CAA");
/**
* DNSSEC Trust Authorities record N/A Part of a deployment proposal for
* DNSSEC without a signed DNS root. See the IANA database and Weiler Spec
* for details. Uses the same format as the DS record.
*/
public static final DnsRecordType TA = new DnsRecordType(0x8000, "TA");
/**
* DNSSEC Lookaside Validation record RFC 4431 For publishing DNSSEC trust
* anchors outside of the DNS delegation chain. Uses the same format as the
* DS record. RFC 5074 describes a way of using these records.
*/
public static final DnsRecordType DLV = new DnsRecordType(0x8001, "DLV");
private static final Map<String, DnsRecordType> BY_NAME = new HashMap<String, DnsRecordType>();
private static final IntObjectHashMap<DnsRecordType> BY_TYPE = new IntObjectHashMap<DnsRecordType>();
private static final String EXPECTED;
static {
DnsRecordType[] all = {
A, NS, CNAME, SOA, PTR, MX, TXT, RP, AFSDB, SIG, KEY, AAAA, LOC, SRV, NAPTR, KX, CERT, DNAME, OPT, APL,
DS, SSHFP, IPSECKEY, RRSIG, NSEC, DNSKEY, DHCID, NSEC3, NSEC3PARAM, TLSA, HIP, SPF, TKEY, TSIG, IXFR,
AXFR, ANY, CAA, TA, DLV
};
final StringBuilder expected = new StringBuilder(512);
expected.append(" (expected: ");
for (DnsRecordType type: all) {
BY_NAME.put(type.name(), type);
BY_TYPE.put(type.intValue(), type);
expected.append(type.name())
.append('(')
.append(type.intValue())
.append("), ");
}
expected.setLength(expected.length() - 2);
expected.append(')');
EXPECTED = expected.toString();
}
public static DnsRecordType valueOf(int intValue) {
DnsRecordType result = BY_TYPE.get(intValue);
if (result == null) {
return new DnsRecordType(intValue);
}
return result;
}
public static DnsRecordType valueOf(String name) {
DnsRecordType result = BY_NAME.get(name);
if (result == null) {
throw new IllegalArgumentException("name: " + name + EXPECTED);
}
return result;
}
private final int intValue;
private final String name;
private String text;
private DnsRecordType(int intValue) {
this(intValue, "UNKNOWN");
}
public DnsRecordType(int intValue, String name) {
if ((intValue & 0xffff) != intValue) {
throw new IllegalArgumentException("intValue: " + intValue + " (expected: 0 ~ 65535)");
}
this.intValue = intValue;
this.name = name;
}
/**
* Returns the name of this type, as seen in bind config files
*/
public String name() {
return name;
}
/**
* Returns the value of this DnsType as it appears in DNS protocol
*/
public int intValue() {
return intValue;
}
@Override
public int hashCode() {
return intValue;
}
@Override
public boolean equals(Object o) {
return o instanceof DnsRecordType && ((DnsRecordType) o).intValue == intValue;
}
@Override
public int compareTo(DnsRecordType o) {
return intValue() - o.intValue();
}
@Override
public String toString() {
String text = this.text;
if (text == null) {
this.text = text = name + '(' + intValue() + ')';
}
return text;
}
}

View file

@ -0,0 +1,116 @@
/*
* 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:
*
* https://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.UnstableApi;
/**
* A DNS response message.
*/
@UnstableApi
public interface DnsResponse extends DnsMessage {
/**
* Returns {@code true} if responding server is authoritative for the domain
* name in the query message.
*/
boolean isAuthoritativeAnswer();
/**
* Set to {@code true} if responding server is authoritative for the domain
* name in the query message.
*
* @param authoritativeAnswer flag for authoritative answer
*/
DnsResponse setAuthoritativeAnswer(boolean authoritativeAnswer);
/**
* Returns {@code true} if response has been truncated, usually if it is
* over 512 bytes.
*/
boolean isTruncated();
/**
* Set to {@code true} if response has been truncated (usually happens for
* responses over 512 bytes).
*
* @param truncated flag for truncation
*/
DnsResponse setTruncated(boolean truncated);
/**
* Returns {@code true} if DNS server can handle recursive queries.
*/
boolean isRecursionAvailable();
/**
* Set to {@code true} if DNS server can handle recursive queries.
*
* @param recursionAvailable flag for recursion availability
*/
DnsResponse setRecursionAvailable(boolean recursionAvailable);
/**
* Returns the 4 bit return code.
*/
DnsResponseCode code();
/**
* Sets the response code for this message.
*
* @param code the response code
*/
DnsResponse setCode(DnsResponseCode code);
@Override
DnsResponse setId(int id);
@Override
DnsResponse setOpCode(DnsOpCode opCode);
@Override
DnsResponse setRecursionDesired(boolean recursionDesired);
@Override
DnsResponse setZ(int z);
@Override
DnsResponse setRecord(DnsSection section, DnsRecord record);
@Override
DnsResponse addRecord(DnsSection section, DnsRecord record);
@Override
DnsResponse addRecord(DnsSection section, int index, DnsRecord record);
@Override
DnsResponse clear(DnsSection section);
@Override
DnsResponse clear();
@Override
DnsResponse touch();
@Override
DnsResponse touch(Object hint);
@Override
DnsResponse retain();
@Override
DnsResponse retain(int increment);
}

View file

@ -0,0 +1,219 @@
/*
* 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:
*
* https://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.UnstableApi;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* The DNS {@code RCODE}, as defined in <a href="https://tools.ietf.org/html/rfc2929">RFC2929</a>.
*/
@UnstableApi
public class DnsResponseCode implements Comparable<DnsResponseCode> {
/**
* 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, "NoError");
/**
* The 'FormErr' DNS RCODE (1), as defined in <a href="https://tools.ietf.org/html/rfc1035">RFC1035</a>.
*/
public static final DnsResponseCode FORMERR = new DnsResponseCode(1, "FormErr");
/**
* 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, "ServFail");
/**
* 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, "NXDomain");
/**
* The 'NotImp' DNS RCODE (4), as defined in <a href="https://tools.ietf.org/html/rfc1035">RFC1035</a>.
*/
public static final DnsResponseCode NOTIMP = new DnsResponseCode(4, "NotImp");
/**
* 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, "Refused");
/**
* 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, "YXDomain");
/**
* 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, "YXRRSet");
/**
* 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, "NXRRSet");
/**
* 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, "NotAuth");
/**
* 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, "NotZone");
/**
* 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_OR_BADSIG = new DnsResponseCode(16, "BADVERS_OR_BADSIG");
/**
* The 'BADKEY' DNS RCODE (17), as defined in <a href="https://tools.ietf.org/html/rfc2845">RFC2845</a>.
*/
public static final DnsResponseCode BADKEY = new DnsResponseCode(17, "BADKEY");
/**
* The 'BADTIME' DNS RCODE (18), as defined in <a href="https://tools.ietf.org/html/rfc2845">RFC2845</a>.
*/
public static final DnsResponseCode BADTIME = new DnsResponseCode(18, "BADTIME");
/**
* The 'BADMODE' DNS RCODE (19), as defined in <a href="https://tools.ietf.org/html/rfc2930">RFC2930</a>.
*/
public static final DnsResponseCode BADMODE = new DnsResponseCode(19, "BADMODE");
/**
* The 'BADNAME' DNS RCODE (20), as defined in <a href="https://tools.ietf.org/html/rfc2930">RFC2930</a>.
*/
public static final DnsResponseCode BADNAME = new DnsResponseCode(20, "BADNAME");
/**
* The 'BADALG' DNS RCODE (21), as defined in <a href="https://tools.ietf.org/html/rfc2930">RFC2930</a>.
*/
public static final DnsResponseCode BADALG = new DnsResponseCode(21, "BADALG");
/**
* Returns the {@link DnsResponseCode} that corresponds with the given {@code responseCode}.
*
* @param responseCode the DNS RCODE
*
* @return the corresponding {@link DnsResponseCode}
*/
public static DnsResponseCode valueOf(int responseCode) {
switch (responseCode) {
case 0:
return NOERROR;
case 1:
return FORMERR;
case 2:
return SERVFAIL;
case 3:
return NXDOMAIN;
case 4:
return NOTIMP;
case 5:
return REFUSED;
case 6:
return YXDOMAIN;
case 7:
return YXRRSET;
case 8:
return NXRRSET;
case 9:
return NOTAUTH;
case 10:
return NOTZONE;
case 16:
return BADVERS_OR_BADSIG;
case 17:
return BADKEY;
case 18:
return BADTIME;
case 19:
return BADMODE;
case 20:
return BADNAME;
case 21:
return BADALG;
default:
return new DnsResponseCode(responseCode);
}
}
private final int code;
private final String name;
private String text;
private DnsResponseCode(int code) {
this(code, "UNKNOWN");
}
public DnsResponseCode(int code, String name) {
if (code < 0 || code > 65535) {
throw new IllegalArgumentException("code: " + code + " (expected: 0 ~ 65535)");
}
this.code = code;
this.name = checkNotNull(name, "name");
}
/**
* Returns the error code for this {@link DnsResponseCode}.
*/
public int intValue() {
return code;
}
@Override
public int compareTo(DnsResponseCode o) {
return intValue() - o.intValue();
}
@Override
public int hashCode() {
return intValue();
}
/**
* Equality of {@link DnsResponseCode} only depends on {@link #intValue()}.
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof DnsResponseCode)) {
return false;
}
return intValue() == ((DnsResponseCode) o).intValue();
}
/**
* Returns a formatted error message for this {@link DnsResponseCode}.
*/
@Override
public String toString() {
String text = this.text;
if (text == null) {
this.text = text = name + '(' + intValue() + ')';
}
return text;
}
}

View file

@ -0,0 +1,105 @@
/*
* Copyright 2019 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:
*
* https://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 java.net.SocketAddress;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
abstract class DnsResponseDecoder<A extends SocketAddress> {
private final DnsRecordDecoder recordDecoder;
/**
* Creates a new decoder with the specified {@code recordDecoder}.
*/
DnsResponseDecoder(DnsRecordDecoder recordDecoder) {
this.recordDecoder = checkNotNull(recordDecoder, "recordDecoder");
}
final DnsResponse decode(A sender, A recipient, ByteBuf buffer) throws Exception {
final int id = buffer.readUnsignedShort();
final int flags = buffer.readUnsignedShort();
if (flags >> 15 == 0) {
throw new CorruptedFrameException("not a response");
}
final DnsResponse response = newResponse(
sender,
recipient,
id,
DnsOpCode.valueOf((byte) (flags >> 11 & 0xf)), DnsResponseCode.valueOf((byte) (flags & 0xf)));
response.setRecursionDesired((flags >> 8 & 1) == 1);
response.setAuthoritativeAnswer((flags >> 10 & 1) == 1);
response.setTruncated((flags >> 9 & 1) == 1);
response.setRecursionAvailable((flags >> 7 & 1) == 1);
response.setZ(flags >> 4 & 0x7);
boolean success = false;
try {
final int questionCount = buffer.readUnsignedShort();
final int answerCount = buffer.readUnsignedShort();
final int authorityRecordCount = buffer.readUnsignedShort();
final int additionalRecordCount = buffer.readUnsignedShort();
decodeQuestions(response, buffer, questionCount);
if (!decodeRecords(response, DnsSection.ANSWER, buffer, answerCount)) {
success = true;
return response;
}
if (!decodeRecords(response, DnsSection.AUTHORITY, buffer, authorityRecordCount)) {
success = true;
return response;
}
decodeRecords(response, DnsSection.ADDITIONAL, buffer, additionalRecordCount);
success = true;
return response;
} finally {
if (!success) {
response.release();
}
}
}
protected abstract DnsResponse newResponse(A sender, A recipient, int id,
DnsOpCode opCode, DnsResponseCode responseCode) throws Exception;
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 boolean decodeRecords(
DnsResponse response, DnsSection section, ByteBuf buf, int count) throws Exception {
for (int i = count; i > 0; i --) {
final DnsRecord r = recordDecoder.decodeRecord(buf);
if (r == null) {
// Truncated response
return false;
}
response.addRecord(section, r);
}
return true;
}
}

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:
*
* https://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.UnstableApi;
/**
* Represents a section of a {@link DnsMessage}.
*/
@UnstableApi
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

@ -0,0 +1,57 @@
/*
* Copyright 2021 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:
*
* https://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.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.UnstableApi;
@UnstableApi
public final class TcpDnsQueryDecoder extends LengthFieldBasedFrameDecoder {
private final DnsRecordDecoder decoder;
/**
* Creates a new decoder with {@linkplain DnsRecordDecoder#DEFAULT the default record decoder}.
*/
public TcpDnsQueryDecoder() {
this(DnsRecordDecoder.DEFAULT, 65535);
}
/**
* Creates a new decoder with the specified {@code decoder}.
*/
public TcpDnsQueryDecoder(DnsRecordDecoder decoder, int maxFrameLength) {
super(maxFrameLength, 0, 2, 0, 2);
this.decoder = ObjectUtil.checkNotNull(decoder, "decoder");
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) {
return null;
}
return DnsMessageUtil.decodeDnsQuery(decoder, frame.slice(), new DnsMessageUtil.DnsQueryFactory() {
@Override
public DnsQuery newQuery(int id, DnsOpCode dnsOpCode) {
return new DefaultDnsQuery(id, dnsOpCode);
}
});
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright 2019 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:
*
* https://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.handler.codec.MessageToByteEncoder;
import io.netty.util.internal.UnstableApi;
@ChannelHandler.Sharable
@UnstableApi
public final class TcpDnsQueryEncoder extends MessageToByteEncoder<DnsQuery> {
private final DnsQueryEncoder encoder;
/**
* Creates a new encoder with {@linkplain DnsRecordEncoder#DEFAULT the default record encoder}.
*/
public TcpDnsQueryEncoder() {
this(DnsRecordEncoder.DEFAULT);
}
/**
* Creates a new encoder with the specified {@code recordEncoder}.
*/
public TcpDnsQueryEncoder(DnsRecordEncoder recordEncoder) {
this.encoder = new DnsQueryEncoder(recordEncoder);
}
@Override
protected void encode(ChannelHandlerContext ctx, DnsQuery msg, ByteBuf out) throws Exception {
// Length is two octets as defined by RFC-7766
// See https://tools.ietf.org/html/rfc7766#section-8
out.writerIndex(out.writerIndex() + 2);
encoder.encode(msg, out);
// Now fill in the correct length based on the amount of data that we wrote the ByteBuf.
out.setShort(0, out.readableBytes() - 2);
}
@Override
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") DnsQuery msg,
boolean preferDirect) {
if (preferDirect) {
return ctx.alloc().ioBuffer(1024);
} else {
return ctx.alloc().heapBuffer(1024);
}
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright 2019 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:
*
* https://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.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.util.internal.UnstableApi;
import java.net.SocketAddress;
@UnstableApi
public final class TcpDnsResponseDecoder extends LengthFieldBasedFrameDecoder {
private final DnsResponseDecoder<SocketAddress> responseDecoder;
/**
* Creates a new decoder with {@linkplain DnsRecordDecoder#DEFAULT the default record decoder}.
*/
public TcpDnsResponseDecoder() {
this(DnsRecordDecoder.DEFAULT, 64 * 1024);
}
/**
* Creates a new decoder with the specified {@code recordDecoder} and {@code maxFrameLength}
*/
public TcpDnsResponseDecoder(DnsRecordDecoder recordDecoder, int maxFrameLength) {
// Length is two octets as defined by RFC-7766
// See https://tools.ietf.org/html/rfc7766#section-8
super(maxFrameLength, 0, 2, 0, 2);
this.responseDecoder = new DnsResponseDecoder<SocketAddress>(recordDecoder) {
@Override
protected DnsResponse newResponse(SocketAddress sender, SocketAddress recipient,
int id, DnsOpCode opCode, DnsResponseCode responseCode) {
return new DefaultDnsResponse(id, opCode, responseCode);
}
};
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) {
return null;
}
try {
return responseDecoder.decode(ctx.channel().remoteAddress(), ctx.channel().localAddress(), frame.slice());
} finally {
frame.release();
}
}
@Override
protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) {
return buffer.copy(index, length);
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright 2021 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:
*
* https://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.handler.codec.MessageToMessageEncoder;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.UnstableApi;
import java.util.List;
@UnstableApi
@ChannelHandler.Sharable
public final class TcpDnsResponseEncoder extends MessageToMessageEncoder<DnsResponse> {
private final DnsRecordEncoder encoder;
/**
* Creates a new encoder with {@linkplain DnsRecordEncoder#DEFAULT the default record encoder}.
*/
public TcpDnsResponseEncoder() {
this(DnsRecordEncoder.DEFAULT);
}
/**
* Creates a new encoder with the specified {@code encoder}.
*/
public TcpDnsResponseEncoder(DnsRecordEncoder encoder) {
this.encoder = ObjectUtil.checkNotNull(encoder, "encoder");
}
@Override
protected void encode(ChannelHandlerContext ctx, DnsResponse response, List<Object> out) throws Exception {
ByteBuf buf = ctx.alloc().ioBuffer(1024);
buf.writerIndex(buf.writerIndex() + 2);
DnsMessageUtil.encodeDnsResponse(encoder, response, buf);
buf.setShort(0, buf.readableBytes() - 2);
out.add(buf);
}
}

View file

@ -0,0 +1,23 @@
/*
* 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:
*
* https://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.
*/
/**
* DNS codec.
*/
@UnstableApi
package io.netty.handler.codec.dns;
import io.netty.util.internal.UnstableApi;

View file

@ -0,0 +1,8 @@
module org.xbib.io.netty.handler.codec.dns {
exports io.netty.handler.codec.dns;
requires org.xbib.io.netty.buffer;
requires org.xbib.io.netty.channel;
requires org.xbib.io.netty.handler;
requires org.xbib.io.netty.handler.codec;
requires org.xbib.io.netty.util;
}

View file

@ -0,0 +1,58 @@
[
{
"name": "io.netty.handler.codec.dns.DatagramDnsQueryDecoder",
"condition": {
"typeReachable": "io.netty.handler.codec.dns.DatagramDnsQueryDecoder"
},
"queryAllPublicMethods": true
},
{
"name": "io.netty.handler.codec.dns.DatagramDnsQueryEncoder",
"condition": {
"typeReachable": "io.netty.handler.codec.dns.DatagramDnsQueryEncoder"
},
"queryAllPublicMethods": true
},
{
"name": "io.netty.handler.codec.dns.DatagramDnsResponseDecoder",
"condition": {
"typeReachable": "io.netty.handler.codec.dns.DatagramDnsResponseDecoder"
},
"queryAllPublicMethods": true
},
{
"name": "io.netty.handler.codec.dns.DatagramDnsResponseEncoder",
"condition": {
"typeReachable": "io.netty.handler.codec.dns.DatagramDnsResponseEncoder"
},
"queryAllPublicMethods": true
},
{
"name": "io.netty.handler.codec.dns.TcpDnsQueryDecoder",
"condition": {
"typeReachable": "io.netty.handler.codec.dns.TcpDnsQueryDecoder"
},
"queryAllPublicMethods": true
},
{
"name": "io.netty.handler.codec.dns.TcpDnsQueryEncoder",
"condition": {
"typeReachable": "io.netty.handler.codec.dns.TcpDnsQueryEncoder"
},
"queryAllPublicMethods": true
},
{
"name": "io.netty.handler.codec.dns.TcpDnsResponseDecoder",
"condition": {
"typeReachable": "io.netty.handler.codec.dns.TcpDnsResponseDecoder"
},
"queryAllPublicMethods": true
},
{
"name": "io.netty.handler.codec.dns.TcpDnsResponseEncoder",
"condition": {
"typeReachable": "io.netty.handler.codec.dns.TcpDnsResponseEncoder"
},
"queryAllPublicMethods": true
}
]

View file

@ -0,0 +1,75 @@
/*
* Copyright 2016 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:
*
* https://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.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class AbstractDnsRecordTest {
@Test
public void testValidDomainName() {
String name = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
AbstractDnsRecord record = new AbstractDnsRecord(name, DnsRecordType.A, 0) { };
assertEquals(name + '.', record.name());
}
@Test
public void testValidDomainNameUmlaut() {
String name = "ä";
AbstractDnsRecord record = new AbstractDnsRecord(name, DnsRecordType.A, 0) { };
assertEquals("xn--4ca.", record.name());
}
@Test
public void testValidDomainNameTrailingDot() {
String name = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.";
AbstractDnsRecord record = new AbstractDnsRecord(name, DnsRecordType.A, 0) { };
assertEquals(name, record.name());
}
@Test
public void testValidDomainNameUmlautTrailingDot() {
String name = "ä.";
AbstractDnsRecord record = new AbstractDnsRecord(name, DnsRecordType.A, 0) { };
assertEquals("xn--4ca.", record.name());
}
@Test
public void testValidDomainNameLength() {
final String name = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
assertThrows(IllegalArgumentException.class, new Executable() {
@Override
public void execute() {
new AbstractDnsRecord(name, DnsRecordType.A, 0) { };
}
});
}
@Test
public void testValidDomainNameUmlautLength() {
final String name = "äaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
assertThrows(IllegalArgumentException.class, new Executable() {
@Override
public void execute() {
new AbstractDnsRecord(name, DnsRecordType.A, 0) { };
}
});
}
}

View file

@ -0,0 +1,256 @@
/*
* Copyright 2016 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:
*
* https://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.buffer.Unpooled;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class DefaultDnsRecordDecoderTest {
@Test
public void testDecodeName() {
testDecodeName("netty.io.", Unpooled.wrappedBuffer(new byte[] {
5, 'n', 'e', 't', 't', 'y', 2, 'i', 'o', 0
}));
}
@Test
public void testDecodeNameWithoutTerminator() {
testDecodeName("netty.io.", Unpooled.wrappedBuffer(new byte[] {
5, 'n', 'e', 't', 't', 'y', 2, 'i', 'o'
}));
}
@Test
public void testDecodeNameWithExtraTerminator() {
// Should not be decoded as 'netty.io..'
testDecodeName("netty.io.", Unpooled.wrappedBuffer(new byte[] {
5, 'n', 'e', 't', 't', 'y', 2, 'i', 'o', 0, 0
}));
}
@Test
public void testDecodeEmptyName() {
testDecodeName(".", Unpooled.buffer().writeByte(0));
}
@Test
public void testDecodeEmptyNameFromEmptyBuffer() {
testDecodeName(".", Unpooled.EMPTY_BUFFER);
}
@Test
public void testDecodeEmptyNameFromExtraZeroes() {
testDecodeName(".", Unpooled.wrappedBuffer(new byte[] { 0, 0 }));
}
private static void testDecodeName(String expected, ByteBuf buffer) {
try {
DefaultDnsRecordDecoder decoder = new DefaultDnsRecordDecoder();
assertEquals(expected, decoder.decodeName0(buffer));
} finally {
buffer.release();
}
}
@Test
public void testDecodePtrRecord() throws Exception {
DefaultDnsRecordDecoder decoder = new DefaultDnsRecordDecoder();
ByteBuf buffer = Unpooled.buffer().writeByte(0);
int readerIndex = buffer.readerIndex();
int writerIndex = buffer.writerIndex();
try {
DnsPtrRecord record = (DnsPtrRecord) decoder.decodeRecord(
"netty.io", DnsRecordType.PTR, DnsRecord.CLASS_IN, 60, buffer, 0, 1);
assertEquals("netty.io.", record.name());
assertEquals(DnsRecord.CLASS_IN, record.dnsClass());
assertEquals(60, record.timeToLive());
assertEquals(DnsRecordType.PTR, record.type());
assertEquals(readerIndex, buffer.readerIndex());
assertEquals(writerIndex, buffer.writerIndex());
} finally {
buffer.release();
}
}
@Test
public void testdecompressCompressPointer() {
byte[] compressionPointer = {
5, 'n', 'e', 't', 't', 'y', 2, 'i', 'o', 0,
(byte) 0xC0, 0
};
ByteBuf buffer = Unpooled.wrappedBuffer(compressionPointer);
ByteBuf uncompressed = null;
try {
uncompressed = DnsCodecUtil.decompressDomainName(buffer.duplicate().setIndex(10, 12));
assertEquals(0, ByteBufUtil.compare(buffer.duplicate().setIndex(0, 10), uncompressed));
} finally {
buffer.release();
if (uncompressed != null) {
uncompressed.release();
}
}
}
@Test
public void testdecompressNestedCompressionPointer() {
byte[] nestedCompressionPointer = {
6, 'g', 'i', 't', 'h', 'u', 'b', 2, 'i', 'o', 0, // github.io
5, 'n', 'e', 't', 't', 'y', (byte) 0xC0, 0, // netty.github.io
(byte) 0xC0, 11, // netty.github.io
};
ByteBuf buffer = Unpooled.wrappedBuffer(nestedCompressionPointer);
ByteBuf uncompressed = null;
try {
uncompressed = DnsCodecUtil.decompressDomainName(buffer.duplicate().setIndex(19, 21));
assertEquals(0, ByteBufUtil.compare(
Unpooled.wrappedBuffer(new byte[] {
5, 'n', 'e', 't', 't', 'y', 6, 'g', 'i', 't', 'h', 'u', 'b', 2, 'i', 'o', 0
}), uncompressed));
} finally {
buffer.release();
if (uncompressed != null) {
uncompressed.release();
}
}
}
@Test
public void testDecodeCompressionRDataPointer() throws Exception {
DefaultDnsRecordDecoder decoder = new DefaultDnsRecordDecoder();
byte[] compressionPointer = {
5, 'n', 'e', 't', 't', 'y', 2, 'i', 'o', 0,
(byte) 0xC0, 0
};
ByteBuf buffer = Unpooled.wrappedBuffer(compressionPointer);
DefaultDnsRawRecord cnameRecord = null;
DefaultDnsRawRecord nsRecord = null;
try {
cnameRecord = (DefaultDnsRawRecord) decoder.decodeRecord(
"netty.github.io", DnsRecordType.CNAME, DnsRecord.CLASS_IN, 60, buffer, 10, 2);
assertEquals(0, ByteBufUtil.compare(buffer.duplicate().setIndex(0, 10), cnameRecord.content()),
"The rdata of CNAME-type record should be decompressed in advance");
assertEquals("netty.io.", DnsCodecUtil.decodeDomainName(cnameRecord.content()));
nsRecord = (DefaultDnsRawRecord) decoder.decodeRecord(
"netty.github.io", DnsRecordType.NS, DnsRecord.CLASS_IN, 60, buffer, 10, 2);
assertEquals(0, ByteBufUtil.compare(buffer.duplicate().setIndex(0, 10), nsRecord.content()),
"The rdata of NS-type record should be decompressed in advance");
assertEquals("netty.io.", DnsCodecUtil.decodeDomainName(nsRecord.content()));
} finally {
buffer.release();
if (cnameRecord != null) {
cnameRecord.release();
}
if (nsRecord != null) {
nsRecord.release();
}
}
}
@Test
public void testDecodeMessageCompression() throws Exception {
// See https://www.ietf.org/rfc/rfc1035 [4.1.4. Message compression]
DefaultDnsRecordDecoder decoder = new DefaultDnsRecordDecoder();
byte[] rfcExample = { 1, 'F', 3, 'I', 'S', 'I', 4, 'A', 'R', 'P', 'A',
0, 3, 'F', 'O', 'O',
(byte) 0xC0, 0, // this is 20 in the example
(byte) 0xC0, 6, // this is 26 in the example
};
DefaultDnsRawRecord rawPlainRecord = null;
DefaultDnsRawRecord rawUncompressedRecord = null;
DefaultDnsRawRecord rawUncompressedIndexedRecord = null;
ByteBuf buffer = Unpooled.wrappedBuffer(rfcExample);
try {
// First lets test that our utility function can correctly handle index references and decompression.
String plainName = DefaultDnsRecordDecoder.decodeName(buffer.duplicate());
assertEquals("F.ISI.ARPA.", plainName);
String uncompressedPlainName = DefaultDnsRecordDecoder.decodeName(buffer.duplicate().setIndex(16, 20));
assertEquals(plainName, uncompressedPlainName);
String uncompressedIndexedName = DefaultDnsRecordDecoder.decodeName(buffer.duplicate().setIndex(12, 20));
assertEquals("FOO." + plainName, uncompressedIndexedName);
// Now lets make sure out object parsing produces the same results for non PTR type (just use CNAME).
rawPlainRecord = (DefaultDnsRawRecord) decoder.decodeRecord(
plainName, DnsRecordType.CNAME, DnsRecord.CLASS_IN, 60, buffer, 0, 11);
assertEquals(plainName, rawPlainRecord.name());
assertEquals(plainName, DefaultDnsRecordDecoder.decodeName(rawPlainRecord.content()));
rawUncompressedRecord = (DefaultDnsRawRecord) decoder.decodeRecord(
uncompressedPlainName, DnsRecordType.CNAME, DnsRecord.CLASS_IN, 60, buffer, 16, 4);
assertEquals(uncompressedPlainName, rawUncompressedRecord.name());
assertEquals(uncompressedPlainName, DefaultDnsRecordDecoder.decodeName(rawUncompressedRecord.content()));
rawUncompressedIndexedRecord = (DefaultDnsRawRecord) decoder.decodeRecord(
uncompressedIndexedName, DnsRecordType.CNAME, DnsRecord.CLASS_IN, 60, buffer, 12, 8);
assertEquals(uncompressedIndexedName, rawUncompressedIndexedRecord.name());
assertEquals(uncompressedIndexedName,
DefaultDnsRecordDecoder.decodeName(rawUncompressedIndexedRecord.content()));
// Now lets make sure out object parsing produces the same results for PTR type.
DnsPtrRecord ptrRecord = (DnsPtrRecord) decoder.decodeRecord(
plainName, DnsRecordType.PTR, DnsRecord.CLASS_IN, 60, buffer, 0, 11);
assertEquals(plainName, ptrRecord.name());
assertEquals(plainName, ptrRecord.hostname());
ptrRecord = (DnsPtrRecord) decoder.decodeRecord(
uncompressedPlainName, DnsRecordType.PTR, DnsRecord.CLASS_IN, 60, buffer, 16, 4);
assertEquals(uncompressedPlainName, ptrRecord.name());
assertEquals(uncompressedPlainName, ptrRecord.hostname());
ptrRecord = (DnsPtrRecord) decoder.decodeRecord(
uncompressedIndexedName, DnsRecordType.PTR, DnsRecord.CLASS_IN, 60, buffer, 12, 8);
assertEquals(uncompressedIndexedName, ptrRecord.name());
assertEquals(uncompressedIndexedName, ptrRecord.hostname());
} finally {
if (rawPlainRecord != null) {
rawPlainRecord.release();
}
if (rawUncompressedRecord != null) {
rawUncompressedRecord.release();
}
if (rawUncompressedIndexedRecord != null) {
rawUncompressedIndexedRecord.release();
}
buffer.release();
}
}
@Test
public void testTruncatedPacket() throws Exception {
ByteBuf buffer = Unpooled.buffer();
buffer.writeByte(0);
buffer.writeShort(DnsRecordType.A.intValue());
buffer.writeShort(1);
buffer.writeInt(32);
// Write a truncated last value.
buffer.writeByte(0);
DefaultDnsRecordDecoder decoder = new DefaultDnsRecordDecoder();
try {
int readerIndex = buffer.readerIndex();
assertNull(decoder.decodeRecord(buffer));
assertEquals(readerIndex, buffer.readerIndex());
} finally {
buffer.release();
}
}
}

View file

@ -0,0 +1,151 @@
/*
* Copyright 2016 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:
*
* https://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.Unpooled;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SocketUtils;
import io.netty.util.internal.StringUtil;
import org.junit.jupiter.api.Test;
import java.net.InetAddress;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class DefaultDnsRecordEncoderTest {
@Test
public void testEncodeName() throws Exception {
testEncodeName(new byte[] { 5, 'n', 'e', 't', 't', 'y', 2, 'i', 'o', 0 }, "netty.io.");
}
@Test
public void testEncodeNameWithoutTerminator() throws Exception {
testEncodeName(new byte[] { 5, 'n', 'e', 't', 't', 'y', 2, 'i', 'o', 0 }, "netty.io");
}
@Test
public void testEncodeNameWithExtraTerminator() throws Exception {
testEncodeName(new byte[] { 5, 'n', 'e', 't', 't', 'y', 2, 'i', 'o', 0 }, "netty.io..");
}
// Test for https://github.com/netty/netty/issues/5014
@Test
public void testEncodeEmptyName() throws Exception {
testEncodeName(new byte[] { 0 }, StringUtil.EMPTY_STRING);
}
@Test
public void testEncodeRootName() throws Exception {
testEncodeName(new byte[] { 0 }, ".");
}
private static void testEncodeName(byte[] expected, String name) throws Exception {
DefaultDnsRecordEncoder encoder = new DefaultDnsRecordEncoder();
ByteBuf out = Unpooled.buffer();
ByteBuf expectedBuf = Unpooled.wrappedBuffer(expected);
try {
encoder.encodeName(name, out);
assertEquals(expectedBuf, out);
} finally {
out.release();
expectedBuf.release();
}
}
@Test
public void testOptEcsRecordIpv4() throws Exception {
testOptEcsRecordIp(SocketUtils.addressByName("1.2.3.4"));
testOptEcsRecordIp(SocketUtils.addressByName("1.2.3.255"));
}
@Test
public void testOptEcsRecordIpv6() throws Exception {
testOptEcsRecordIp(SocketUtils.addressByName("::0"));
testOptEcsRecordIp(SocketUtils.addressByName("::FF"));
}
private static void testOptEcsRecordIp(InetAddress address) throws Exception {
int addressBits = address.getAddress().length * Byte.SIZE;
for (int i = 0; i <= addressBits; ++i) {
testIp(address, i);
}
}
private static void testIp(InetAddress address, int prefix) throws Exception {
int lowOrderBitsToPreserve = prefix % Byte.SIZE;
ByteBuf addressPart = Unpooled.wrappedBuffer(address.getAddress(), 0,
DefaultDnsRecordEncoder.calculateEcsAddressLength(prefix, lowOrderBitsToPreserve));
if (lowOrderBitsToPreserve > 0) {
// Pad the leftover of the last byte with zeros.
int idx = addressPart.writerIndex() - 1;
byte lastByte = addressPart.getByte(idx);
int paddingMask = -1 << 8 - lowOrderBitsToPreserve;
addressPart.setByte(idx, lastByte & paddingMask);
}
int payloadSize = nextInt(Short.MAX_VALUE);
int extendedRcode = nextInt(Byte.MAX_VALUE * 2); // Unsigned
int version = nextInt(Byte.MAX_VALUE * 2); // Unsigned
DefaultDnsRecordEncoder encoder = new DefaultDnsRecordEncoder();
ByteBuf out = Unpooled.buffer();
try {
DnsOptEcsRecord record = new DefaultDnsOptEcsRecord(
payloadSize, extendedRcode, version, prefix, address.getAddress());
encoder.encodeRecord(record, out);
assertEquals(0, out.readByte()); // Name
assertEquals(DnsRecordType.OPT.intValue(), out.readUnsignedShort()); // Opt
assertEquals(payloadSize, out.readUnsignedShort()); // payload
assertEquals(record.timeToLive(), out.getUnsignedInt(out.readerIndex()));
// Read unpacked TTL.
assertEquals(extendedRcode, out.readUnsignedByte());
assertEquals(version, out.readUnsignedByte());
assertEquals(extendedRcode, record.extendedRcode());
assertEquals(version, record.version());
assertEquals(0, record.flags());
assertEquals(0, out.readShort());
int payloadLength = out.readUnsignedShort();
assertEquals(payloadLength, out.readableBytes());
assertEquals(8, out.readShort()); // As defined by RFC.
int rdataLength = out.readUnsignedShort();
assertEquals(rdataLength, out.readableBytes());
assertEquals((short) InternetProtocolFamily.of(address).addressNumber(), out.readShort());
assertEquals(prefix, out.readUnsignedByte());
assertEquals(0, out.readUnsignedByte()); // This must be 0 for requests.
assertEquals(addressPart, out);
} finally {
addressPart.release();
out.release();
}
}
private static int nextInt(int max) {
return PlatformDependent.threadLocalRandom().nextInt(max);
}
}

View file

@ -0,0 +1,83 @@
/*
* 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:
*
* https://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.embedded.EmbeddedChannel;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.internal.SocketUtils;
import org.junit.jupiter.api.Test;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class DnsQueryTest {
@Test
public void testEncodeAndDecodeQuery() {
InetSocketAddress addr = SocketUtils.socketAddress("8.8.8.8", 53);
EmbeddedChannel writeChannel = new EmbeddedChannel(new DatagramDnsQueryEncoder());
EmbeddedChannel readChannel = new EmbeddedChannel(new DatagramDnsQueryDecoder());
List<DnsQuery> queries = new ArrayList<DnsQuery>(5);
queries.add(new DatagramDnsQuery(null, addr, 1).setRecord(
DnsSection.QUESTION,
new DefaultDnsQuestion("1.0.0.127.in-addr.arpa", DnsRecordType.PTR)));
queries.add(new DatagramDnsQuery(null, addr, 1).setRecord(
DnsSection.QUESTION,
new DefaultDnsQuestion("www.example.com", DnsRecordType.A)));
queries.add(new DatagramDnsQuery(null, addr, 1).setRecord(
DnsSection.QUESTION,
new DefaultDnsQuestion("example.com", DnsRecordType.AAAA)));
queries.add(new DatagramDnsQuery(null, addr, 1).setRecord(
DnsSection.QUESTION,
new DefaultDnsQuestion("example.com", DnsRecordType.MX)));
queries.add(new DatagramDnsQuery(null, addr, 1).setRecord(
DnsSection.QUESTION,
new DefaultDnsQuestion("example.com", DnsRecordType.CNAME)));
for (DnsQuery query: queries) {
assertThat(query.count(DnsSection.QUESTION), is(1));
assertThat(query.count(DnsSection.ANSWER), is(0));
assertThat(query.count(DnsSection.AUTHORITY), is(0));
assertThat(query.count(DnsSection.ADDITIONAL), is(0));
assertTrue(writeChannel.writeOutbound(query));
DatagramPacket packet = writeChannel.readOutbound();
assertTrue(packet.content().isReadable());
assertTrue(readChannel.writeInbound(packet));
DnsQuery decodedDnsQuery = readChannel.readInbound();
assertEquals(query, decodedDnsQuery);
assertTrue(decodedDnsQuery.release());
assertNull(writeChannel.readOutbound());
assertNull(readChannel.readInbound());
}
assertFalse(writeChannel.finish());
assertFalse(readChannel.finish());
}
}

View file

@ -0,0 +1,86 @@
/*
* 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:
*
* https://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.jupiter.api.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.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
public class DnsRecordTypeTest {
private static List<DnsRecordType> allTypes() throws Exception {
List<DnsRecordType> result = new ArrayList<DnsRecordType>();
for (Field field : DnsRecordType.class.getFields()) {
if ((field.getModifiers() & Modifier.STATIC) != 0 && field.getType() == DnsRecordType.class) {
result.add((DnsRecordType) field.get(null));
}
}
assertFalse(result.isEmpty());
return result;
}
@Test
public void testSanity() throws Exception {
assertEquals(allTypes().size(), new HashSet<DnsRecordType>(allTypes()).size(),
"More than one type has the same int value");
}
/**
* Test of hashCode method, of class DnsRecordType.
*/
@Test
public void testHashCode() throws Exception {
for (DnsRecordType t : allTypes()) {
assertEquals(t.intValue(), t.hashCode());
}
}
/**
* Test of equals method, of class DnsRecordType.
*/
@Test
public void testEquals() throws Exception {
for (DnsRecordType t1 : allTypes()) {
for (DnsRecordType t2 : allTypes()) {
if (t1 != t2) {
assertNotEquals(t1, t2);
}
}
}
}
/**
* Test of find method, of class DnsRecordType.
*/
@Test
public void testFind() throws Exception {
for (DnsRecordType t : allTypes()) {
DnsRecordType found = DnsRecordType.valueOf(t.intValue());
assertSame(t, found);
found = DnsRecordType.valueOf(t.name());
assertSame(t, found, t.name());
}
}
}

View file

@ -0,0 +1,128 @@
/*
* 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:
*
* https://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.Unpooled;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.CorruptedFrameException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import java.net.InetSocketAddress;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class DnsResponseTest {
private static final byte[][] packets = {
{
0, 1, -127, -128, 0, 1, 0, 1, 0, 0, 0, 0, 3, 119, 119, 119, 7, 101, 120, 97, 109, 112, 108, 101, 3,
99, 111, 109, 0, 0, 1, 0, 1, -64, 12, 0, 1, 0, 1, 0, 0, 16, -113, 0, 4, -64, 0, 43, 10
},
{
0, 1, -127, -128, 0, 1, 0, 1, 0, 0, 0, 0, 3, 119, 119, 119, 7, 101, 120, 97, 109, 112, 108, 101, 3,
99, 111, 109, 0, 0, 28, 0, 1, -64, 12, 0, 28, 0, 1, 0, 0, 69, -8, 0, 16, 32, 1, 5, 0, 0, -120, 2,
0, 0, 0, 0, 0, 0, 0, 0, 16
},
{
0, 2, -127, -128, 0, 1, 0, 0, 0, 1, 0, 0, 3, 119, 119, 119, 7, 101, 120, 97, 109, 112, 108, 101, 3,
99, 111, 109, 0, 0, 15, 0, 1, -64, 16, 0, 6, 0, 1, 0, 0, 3, -43, 0, 45, 3, 115, 110, 115, 3, 100,
110, 115, 5, 105, 99, 97, 110, 110, 3, 111, 114, 103, 0, 3, 110, 111, 99, -64, 49, 119, -4, 39,
112, 0, 0, 28, 32, 0, 0, 14, 16, 0, 18, 117, 0, 0, 0, 14, 16
},
{
0, 3, -127, -128, 0, 1, 0, 1, 0, 0, 0, 0, 3, 119, 119, 119, 7, 101, 120, 97, 109, 112, 108, 101, 3,
99, 111, 109, 0, 0, 16, 0, 1, -64, 12, 0, 16, 0, 1, 0, 0, 84, 75, 0, 12, 11, 118, 61, 115, 112,
102, 49, 32, 45, 97, 108, 108
},
{
-105, 19, -127, 0, 0, 1, 0, 0, 0, 13, 0, 0, 2, 104, 112, 11, 116, 105, 109, 98, 111, 117, 100, 114,
101, 97, 117, 3, 111, 114, 103, 0, 0, 1, 0, 1, 0, 0, 2, 0, 1, 0, 7, -23, 0, 0, 20, 1, 68, 12, 82,
79, 79, 84, 45, 83, 69, 82, 86, 69, 82, 83, 3, 78, 69, 84, 0, 0, 0, 2, 0, 1, 0, 7, -23, 0, 0, 4, 1,
70, -64, 49, 0, 0, 2, 0, 1, 0, 7, -23, 0, 0, 4, 1, 69, -64, 49, 0, 0, 2, 0, 1, 0, 7, -23, 0, 0, 4,
1, 75, -64, 49, 0, 0, 2, 0, 1, 0, 7, -23, 0, 0, 4, 1, 67, -64, 49, 0, 0, 2, 0, 1, 0, 7, -23, 0, 0,
4, 1, 76, -64, 49, 0, 0, 2, 0, 1, 0, 7, -23, 0, 0, 4, 1, 71, -64, 49, 0, 0, 2, 0, 1, 0, 7, -23, 0,
0, 4, 1, 73, -64, 49, 0, 0, 2, 0, 1, 0, 7, -23, 0, 0, 4, 1, 66, -64, 49, 0, 0, 2, 0, 1, 0, 7, -23,
0, 0, 4, 1, 77, -64, 49, 0, 0, 2, 0, 1, 0, 7, -23, 0, 0, 4, 1, 65, -64, 49, 0, 0, 2, 0, 1, 0, 7,
-23, 0, 0, 4, 1, 72, -64, 49, 0, 0, 2, 0, 1, 0, 7, -23, 0, 0, 4, 1, 74, -64, 49
}
};
private static final byte[] malformedLoopPacket = {
0, 4, -127, -128, 0, 1, 0, 0, 0, 0, 0, 0, -64, 12, 0, 1, 0, 1
};
@Test
public void readResponseTest() {
EmbeddedChannel embedder = new EmbeddedChannel(new DatagramDnsResponseDecoder());
for (byte[] p: packets) {
ByteBuf packet = embedder.alloc().buffer(512).writeBytes(p);
embedder.writeInbound(new DatagramPacket(packet, null, new InetSocketAddress(0)));
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);
assertThat(response.id(), is(raw.getUnsignedShort(0)));
assertThat(response.count(DnsSection.QUESTION), is(raw.getUnsignedShort(4)));
assertThat(response.count(DnsSection.ANSWER), is(raw.getUnsignedShort(6)));
assertThat(response.count(DnsSection.AUTHORITY), is(raw.getUnsignedShort(8)));
assertThat(response.count(DnsSection.ADDITIONAL), is(raw.getUnsignedShort(10)));
envelope.release();
}
assertFalse(embedder.finish());
}
@Test
public void readMalformedResponseTest() {
final EmbeddedChannel embedder = new EmbeddedChannel(new DatagramDnsResponseDecoder());
final ByteBuf packet = embedder.alloc().buffer(512).writeBytes(malformedLoopPacket);
try {
assertThrows(CorruptedFrameException.class, new Executable() {
@Override
public void execute() {
embedder.writeInbound(new DatagramPacket(packet, null, new InetSocketAddress(0)));
}
});
} finally {
assertFalse(embedder.finish());
}
}
@Test
public void readIncompleteResponseTest() {
final EmbeddedChannel embedder = new EmbeddedChannel(new DatagramDnsResponseDecoder());
final ByteBuf packet = embedder.alloc().buffer(512);
try {
assertThrows(CorruptedFrameException.class, new Executable() {
@Override
public void execute() {
embedder.writeInbound(new DatagramPacket(packet, null, new InetSocketAddress(0)));
}
});
} finally {
assertFalse(embedder.finish());
}
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright 2022 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:
*
* https://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.nativeimage.ChannelHandlerMetadataUtil;
import org.junit.jupiter.api.Test;
public class NativeImageHandlerMetadataTest {
@Test
public void collectAndCompareMetadata() {
ChannelHandlerMetadataUtil.generateMetadata("io.netty.handler.codec.dns");
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright 2021 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:
*
* https://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.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.ReferenceCountUtil;
import org.junit.jupiter.api.Test;
import java.util.Random;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TcpDnsTest {
private static final String QUERY_DOMAIN = "www.example.com";
private static final long TTL = 600;
private static final byte[] QUERY_RESULT = new byte[]{(byte) 192, (byte) 168, 1, 1};
@Test
public void testQueryDecode() {
EmbeddedChannel channel = new EmbeddedChannel(new TcpDnsQueryDecoder());
int randomID = new Random().nextInt(60000 - 1000) + 1000;
DnsQuery query = new DefaultDnsQuery(randomID, DnsOpCode.QUERY)
.setRecord(DnsSection.QUESTION, new DefaultDnsQuestion(QUERY_DOMAIN, DnsRecordType.A));
assertTrue(channel.writeInbound(query));
DnsQuery readQuery = channel.readInbound();
assertThat(readQuery, is(query));
assertThat(readQuery.recordAt(DnsSection.QUESTION).name(), is(query.recordAt(DnsSection.QUESTION).name()));
readQuery.release();
assertFalse(channel.finish());
}
@Test
public void testResponseEncode() {
EmbeddedChannel channel = new EmbeddedChannel(new TcpDnsResponseEncoder());
int randomID = new Random().nextInt(60000 - 1000) + 1000;
DnsQuery query = new DefaultDnsQuery(randomID, DnsOpCode.QUERY)
.setRecord(DnsSection.QUESTION, new DefaultDnsQuestion(QUERY_DOMAIN, DnsRecordType.A));
DnsQuestion question = query.recordAt(DnsSection.QUESTION);
channel.writeInbound(newResponse(query, question, QUERY_RESULT));
DnsResponse readResponse = channel.readInbound();
assertThat(readResponse.recordAt(DnsSection.QUESTION), is((DnsRecord) question));
DnsRawRecord record = new DefaultDnsRawRecord(question.name(),
DnsRecordType.A, TTL, Unpooled.wrappedBuffer(QUERY_RESULT));
assertThat(readResponse.recordAt(DnsSection.ANSWER), is((DnsRecord) record));
assertThat(readResponse.<DnsRawRecord>recordAt(DnsSection.ANSWER).content(), is(record.content()));
ReferenceCountUtil.release(readResponse);
ReferenceCountUtil.release(record);
query.release();
assertFalse(channel.finish());
}
private static DefaultDnsResponse newResponse(DnsQuery query, DnsQuestion question, byte[]... addresses) {
DefaultDnsResponse response = new DefaultDnsResponse(query.id());
response.addRecord(DnsSection.QUESTION, question);
for (byte[] address : addresses) {
DefaultDnsRawRecord queryAnswer = new DefaultDnsRawRecord(question.name(),
DnsRecordType.A, TTL, Unpooled.wrappedBuffer(address));
response.addRecord(DnsSection.ANSWER, queryAnswer);
}
return response;
}
}

View file

@ -0,0 +1,5 @@
dependencies {
api project(':netty-handler-codec-dns')
testImplementation testLibs.apache.ds.dns
testImplementation testLibs.assertj
}

View file

@ -0,0 +1,61 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import io.netty.channel.EventLoop;
import java.net.InetSocketAddress;
/**
* Cache which stores the nameservers that should be used to resolve a specific hostname.
*/
public interface AuthoritativeDnsServerCache {
/**
* Returns the cached nameservers that should be used to resolve the given hostname. The returned
* {@link DnsServerAddressStream} may contain unresolved {@link InetSocketAddress}es that will be resolved
* when needed while resolving other domain names.
*
* @param hostname the hostname
* @return the cached entries or an {@code null} if none.
*/
DnsServerAddressStream get(String hostname);
/**
* Caches a nameserver that should be used to resolve the given hostname.
*
* @param hostname the hostname
* @param address the nameserver address (which may be unresolved).
* @param originalTtl the TTL as returned by the DNS server
* @param loop the {@link EventLoop} used to register the TTL timeout
*/
void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop);
/**
* Clears all cached nameservers.
*
* @see #clear(String)
*/
void clear();
/**
* Clears the cached nameservers for the specified hostname.
*
* @return {@code true} if and only if there was an entry for the specified host name in the cache and
* it has been removed by this method
*/
boolean clear(String hostname);
}

View file

@ -0,0 +1,78 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.dns.DnsRecord;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* {@link AuthoritativeDnsServerCache} implementation which delegates all operations to a wrapped {@link DnsCache}.
* This implementation is only present to preserve a upgrade story.
*/
final class AuthoritativeDnsServerCacheAdapter implements AuthoritativeDnsServerCache {
private static final DnsRecord[] EMPTY = new DnsRecord[0];
private final DnsCache cache;
AuthoritativeDnsServerCacheAdapter(DnsCache cache) {
this.cache = checkNotNull(cache, "cache");
}
@Override
public DnsServerAddressStream get(String hostname) {
List<? extends DnsCacheEntry> entries = cache.get(hostname, EMPTY);
if (entries == null || entries.isEmpty()) {
return null;
}
if (entries.get(0).cause() != null) {
return null;
}
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>(entries.size());
int i = 0;
do {
InetAddress addr = entries.get(i).address();
addresses.add(new InetSocketAddress(addr, DefaultDnsServerAddressStreamProvider.DNS_PORT));
} while (++i < entries.size());
return new SequentialDnsServerAddressStream(addresses, 0);
}
@Override
public void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop) {
// We only cache resolved addresses.
if (!address.isUnresolved()) {
cache.cache(hostname, EMPTY, address.getAddress(), originalTtl, loop);
}
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean clear(String hostname) {
return cache.clear(hostname);
}
}

View file

@ -0,0 +1,109 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsResponseCode;
import java.net.InetSocketAddress;
import java.util.List;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* Combines two {@link DnsQueryLifecycleObserver} into a single {@link DnsQueryLifecycleObserver}.
*/
public final class BiDnsQueryLifecycleObserver implements DnsQueryLifecycleObserver {
private final DnsQueryLifecycleObserver a;
private final DnsQueryLifecycleObserver b;
/**
* Create a new instance.
* @param a The {@link DnsQueryLifecycleObserver} that will receive events first.
* @param b The {@link DnsQueryLifecycleObserver} that will receive events second.
*/
public BiDnsQueryLifecycleObserver(DnsQueryLifecycleObserver a, DnsQueryLifecycleObserver b) {
this.a = checkNotNull(a, "a");
this.b = checkNotNull(b, "b");
}
@Override
public void queryWritten(InetSocketAddress dnsServerAddress, ChannelFuture future) {
try {
a.queryWritten(dnsServerAddress, future);
} finally {
b.queryWritten(dnsServerAddress, future);
}
}
@Override
public void queryCancelled(int queriesRemaining) {
try {
a.queryCancelled(queriesRemaining);
} finally {
b.queryCancelled(queriesRemaining);
}
}
@Override
public DnsQueryLifecycleObserver queryRedirected(List<InetSocketAddress> nameServers) {
try {
a.queryRedirected(nameServers);
} finally {
b.queryRedirected(nameServers);
}
return this;
}
@Override
public DnsQueryLifecycleObserver queryCNAMEd(DnsQuestion cnameQuestion) {
try {
a.queryCNAMEd(cnameQuestion);
} finally {
b.queryCNAMEd(cnameQuestion);
}
return this;
}
@Override
public DnsQueryLifecycleObserver queryNoAnswer(DnsResponseCode code) {
try {
a.queryNoAnswer(code);
} finally {
b.queryNoAnswer(code);
}
return this;
}
@Override
public void queryFailed(Throwable cause) {
try {
a.queryFailed(cause);
} finally {
b.queryFailed(cause);
}
}
@Override
public void querySucceed() {
try {
a.querySucceed();
} finally {
b.querySucceed();
}
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
import io.netty.handler.codec.dns.DnsQuestion;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* Combines two {@link DnsQueryLifecycleObserverFactory} into a single {@link DnsQueryLifecycleObserverFactory}.
*/
public final class BiDnsQueryLifecycleObserverFactory implements DnsQueryLifecycleObserverFactory {
private final DnsQueryLifecycleObserverFactory a;
private final DnsQueryLifecycleObserverFactory b;
/**
* Create a new instance.
* @param a The {@link DnsQueryLifecycleObserverFactory} that will receive events first.
* @param b The {@link DnsQueryLifecycleObserverFactory} that will receive events second.
*/
public BiDnsQueryLifecycleObserverFactory(DnsQueryLifecycleObserverFactory a, DnsQueryLifecycleObserverFactory b) {
this.a = checkNotNull(a, "a");
this.b = checkNotNull(b, "b");
}
@Override
public DnsQueryLifecycleObserver newDnsQueryLifecycleObserver(DnsQuestion question) {
return new BiDnsQueryLifecycleObserver(a.newDnsQueryLifecycleObserver(question),
b.newDnsQueryLifecycleObserver(question));
}
}

View file

@ -0,0 +1,292 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.util.internal.PlatformDependent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Delayed;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import static java.util.Collections.singletonList;
/**
* Abstract cache that automatically removes entries for a hostname once the TTL for an entry is reached.
*
* @param <E>
*/
abstract class Cache<E> {
private static final AtomicReferenceFieldUpdater<Cache.Entries, ScheduledFuture> FUTURE_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(Cache.Entries.class, ScheduledFuture.class, "expirationFuture");
private static final ScheduledFuture<?> CANCELLED = new ScheduledFuture<Object>() {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public long getDelay(TimeUnit unit) {
// We ignore unit and always return the minimum value to ensure the TTL of the cancelled marker is
// the smallest.
return Long.MIN_VALUE;
}
@Override
public int compareTo(Delayed o) {
throw new UnsupportedOperationException();
}
@Override
public boolean isCancelled() {
return true;
}
@Override
public boolean isDone() {
return true;
}
@Override
public Object get() {
throw new UnsupportedOperationException();
}
@Override
public Object get(long timeout, TimeUnit unit) {
throw new UnsupportedOperationException();
}
};
// Two years are supported by all our EventLoop implementations and so safe to use as maximum.
// See also: https://github.com/netty/netty/commit/b47fb817991b42ec8808c7d26538f3f2464e1fa6
static final int MAX_SUPPORTED_TTL_SECS = (int) TimeUnit.DAYS.toSeconds(365 * 2);
private final ConcurrentMap<String, Entries> resolveCache = PlatformDependent.newConcurrentHashMap();
/**
* Remove everything from the cache.
*/
final void clear() {
while (!resolveCache.isEmpty()) {
for (Iterator<Entry<String, Entries>> i = resolveCache.entrySet().iterator(); i.hasNext();) {
Map.Entry<String, Entries> e = i.next();
i.remove();
e.getValue().clearAndCancel();
}
}
}
/**
* Clear all entries (if anything exists) for the given hostname and return {@code true} if anything was removed.
*/
final boolean clear(String hostname) {
Entries entries = resolveCache.remove(hostname);
return entries != null && entries.clearAndCancel();
}
/**
* Returns all caches entries for the given hostname.
*/
final List<? extends E> get(String hostname) {
Entries entries = resolveCache.get(hostname);
return entries == null ? null : entries.get();
}
/**
* Cache a value for the given hostname that will automatically expire once the TTL is reached.
*/
final void cache(String hostname, E value, int ttl, EventLoop loop) {
Entries entries = resolveCache.get(hostname);
if (entries == null) {
entries = new Entries(hostname);
Entries oldEntries = resolveCache.putIfAbsent(hostname, entries);
if (oldEntries != null) {
entries = oldEntries;
}
}
entries.add(value, ttl, loop);
}
/**
* Return the number of hostnames for which we have cached something.
*/
final int size() {
return resolveCache.size();
}
/**
* Returns {@code true} if this entry should replace all other entries that are already cached for the hostname.
*/
protected abstract boolean shouldReplaceAll(E entry);
/**
* Sort the {@link List} for a {@code hostname} before caching these.
*/
protected void sortEntries(
@SuppressWarnings("unused") String hostname, @SuppressWarnings("unused") List<E> entries) {
// NOOP.
}
/**
* Returns {@code true} if both entries are equal.
*/
protected abstract boolean equals(E entry, E otherEntry);
// Directly extend AtomicReference for intrinsics and also to keep memory overhead low.
private final class Entries extends AtomicReference<List<E>> implements Runnable {
private final String hostname;
// Needs to be package-private to be able to access it via the AtomicReferenceFieldUpdater
volatile ScheduledFuture<?> expirationFuture;
Entries(String hostname) {
super(Collections.<E>emptyList());
this.hostname = hostname;
}
void add(E e, int ttl, EventLoop loop) {
if (!shouldReplaceAll(e)) {
for (;;) {
List<E> entries = get();
if (!entries.isEmpty()) {
final E firstEntry = entries.get(0);
if (shouldReplaceAll(firstEntry)) {
assert entries.size() == 1;
if (compareAndSet(entries, singletonList(e))) {
scheduleCacheExpirationIfNeeded(ttl, loop);
return;
} else {
// Need to try again as CAS failed
continue;
}
}
// Create a new List for COW semantics
List<E> newEntries = new ArrayList<E>(entries.size() + 1);
int i = 0;
E replacedEntry = null;
do {
E entry = entries.get(i);
// Only add old entry if the address is not the same as the one we try to add as well.
// In this case we will skip it and just add the new entry as this may have
// more up-to-date data and cancel the old after we were able to update the cache.
if (!Cache.this.equals(e, entry)) {
newEntries.add(entry);
} else {
replacedEntry = entry;
newEntries.add(e);
++i;
for (; i < entries.size(); ++i) {
newEntries.add(entries.get(i));
}
break;
}
} while (++i < entries.size());
if (replacedEntry == null) {
newEntries.add(e);
}
sortEntries(hostname, newEntries);
if (compareAndSet(entries, Collections.unmodifiableList(newEntries))) {
scheduleCacheExpirationIfNeeded(ttl, loop);
return;
}
} else if (compareAndSet(entries, singletonList(e))) {
scheduleCacheExpirationIfNeeded(ttl, loop);
return;
}
}
} else {
set(singletonList(e));
scheduleCacheExpirationIfNeeded(ttl, loop);
}
}
private void scheduleCacheExpirationIfNeeded(int ttl, EventLoop loop) {
for (;;) {
// We currently don't calculate a new TTL when we need to retry the CAS as we don't expect this to
// be invoked very concurrently and also we use SECONDS anyway. If this ever becomes a problem
// we can reconsider.
ScheduledFuture<?> oldFuture = FUTURE_UPDATER.get(this);
if (oldFuture == null || oldFuture.getDelay(TimeUnit.SECONDS) > ttl) {
ScheduledFuture<?> newFuture = loop.schedule(this, ttl, TimeUnit.SECONDS);
// It is possible that
// 1. task will fire in between this line, or
// 2. multiple timers may be set if there is concurrency
// (1) Shouldn't be a problem because we will fail the CAS and then the next loop will see CANCELLED
// so the ttl will not be less, and we will bail out of the loop.
// (2) This is a trade-off to avoid concurrency resulting in contention on a synchronized block.
if (FUTURE_UPDATER.compareAndSet(this, oldFuture, newFuture)) {
if (oldFuture != null) {
oldFuture.cancel(true);
}
break;
} else {
// There was something else scheduled in the meantime... Cancel and try again.
newFuture.cancel(true);
}
} else {
break;
}
}
}
boolean clearAndCancel() {
List<E> entries = getAndSet(Collections.<E>emptyList());
if (entries.isEmpty()) {
return false;
}
ScheduledFuture<?> expirationFuture = FUTURE_UPDATER.getAndSet(this, CANCELLED);
if (expirationFuture != null) {
expirationFuture.cancel(false);
}
return true;
}
@Override
public void run() {
// We always remove all entries for a hostname once one entry expire. This is not the
// most efficient to do but this way we can guarantee that if a DnsResolver
// be configured to prefer one ip family over the other we will not return unexpected
// results to the enduser if one of the A or AAAA records has different TTL settings.
//
// As a TTL is just a hint of the maximum time a cache is allowed to cache stuff it's
// completely fine to remove the entry even if the TTL is not reached yet.
//
// See https://github.com/netty/netty/issues/7329
resolveCache.remove(hostname, this);
clearAndCancel();
}
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2019 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:
*
* https://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.resolver.dns;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.Channel;
import io.netty.handler.codec.dns.DatagramDnsQuery;
import io.netty.handler.codec.dns.DnsQuery;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsRecord;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import java.net.InetSocketAddress;
final class DatagramDnsQueryContext extends DnsQueryContext {
DatagramDnsQueryContext(Channel channel, Future<? extends Channel> channelReadyFuture,
InetSocketAddress nameServerAddr,
DnsQueryContextManager queryContextManager,
int maxPayLoadSize, boolean recursionDesired,
long queryTimeoutMillis,
DnsQuestion question, DnsRecord[] additionals,
Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> promise,
Bootstrap socketBootstrap, boolean retryWithTcpOnTimeout) {
super(channel, channelReadyFuture, nameServerAddr, queryContextManager, maxPayLoadSize, recursionDesired,
queryTimeoutMillis, question, additionals, promise, socketBootstrap, retryWithTcpOnTimeout);
}
@Override
protected DnsQuery newQuery(int id, InetSocketAddress nameServerAddr) {
return new DatagramDnsQuery(null, nameServerAddr, id);
}
@Override
protected String protocol() {
return "UDP";
}
}

View file

@ -0,0 +1,136 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.util.internal.PlatformDependent;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import static io.netty.util.internal.ObjectUtil.*;
/**
* Default implementation of {@link AuthoritativeDnsServerCache}, backed by a {@link ConcurrentMap}.
*/
public class DefaultAuthoritativeDnsServerCache implements AuthoritativeDnsServerCache {
private final int minTtl;
private final int maxTtl;
private final Comparator<InetSocketAddress> comparator;
private final Cache<InetSocketAddress> resolveCache = new Cache<InetSocketAddress>() {
@Override
protected boolean shouldReplaceAll(InetSocketAddress entry) {
return false;
}
@Override
protected boolean equals(InetSocketAddress entry, InetSocketAddress otherEntry) {
if (PlatformDependent.javaVersion() >= 7) {
return entry.getHostString().equalsIgnoreCase(otherEntry.getHostString());
}
return entry.getHostName().equalsIgnoreCase(otherEntry.getHostName());
}
@Override
protected void sortEntries(String hostname, List<InetSocketAddress> entries) {
if (comparator != null) {
Collections.sort(entries, comparator);
}
}
};
/**
* Create a cache that respects the TTL returned by the DNS server.
*/
public DefaultAuthoritativeDnsServerCache() {
this(0, Cache.MAX_SUPPORTED_TTL_SECS, null);
}
/**
* Create a cache.
*
* @param minTtl the minimum TTL
* @param maxTtl the maximum TTL
* @param comparator the {@link Comparator} to order the {@link InetSocketAddress} for a hostname or {@code null}
* if insertion order should be used.
*/
public DefaultAuthoritativeDnsServerCache(int minTtl, int maxTtl, Comparator<InetSocketAddress> comparator) {
this.minTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(minTtl, "minTtl"));
this.maxTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositive(maxTtl, "maxTtl"));
if (minTtl > maxTtl) {
throw new IllegalArgumentException(
"minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)");
}
this.comparator = comparator;
}
@SuppressWarnings("unchecked")
@Override
public DnsServerAddressStream get(String hostname) {
checkNotNull(hostname, "hostname");
List<? extends InetSocketAddress> addresses = resolveCache.get(hostname);
if (addresses == null || addresses.isEmpty()) {
return null;
}
return new SequentialDnsServerAddressStream(addresses, 0);
}
@Override
public void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop) {
checkNotNull(hostname, "hostname");
checkNotNull(address, "address");
checkNotNull(loop, "loop");
if (PlatformDependent.javaVersion() >= 7 && address.getHostString() == null) {
// We only cache addresses that have also a host string as we will need it later when trying to replace
// unresolved entries in the cache.
return;
}
resolveCache.cache(hostname, address, Math.max(minTtl, (int) Math.min(maxTtl, originalTtl)), loop);
}
@Override
public void clear() {
resolveCache.clear();
}
@Override
public boolean clear(String hostname) {
return resolveCache.clear(checkNotNull(hostname, "hostname"));
}
@Override
public String toString() {
return "DefaultAuthoritativeDnsServerCache(minTtl=" + minTtl + ", maxTtl=" + maxTtl + ", cached nameservers=" +
resolveCache.size() + ')';
}
// Package visibility for testing purposes
int minTtl() {
return minTtl;
}
// Package visibility for testing purposes
int maxTtl() {
return maxTtl;
}
}

View file

@ -0,0 +1,346 @@
/*
* Copyright 2016 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:
*
* https://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.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.dns.DnsRecord;
import io.netty.util.internal.StringUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.AbstractList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
/**
* Default implementation of {@link DnsCache}, backed by a {@link ConcurrentMap}.
* If any additional {@link DnsRecord} is used, no caching takes place.
*/
public class DefaultDnsCache implements DnsCache {
private final Cache<DefaultDnsCacheEntry> resolveCache = new Cache<DefaultDnsCacheEntry>() {
@Override
protected boolean shouldReplaceAll(DefaultDnsCacheEntry entry) {
return entry.cause() != null;
}
@Override
protected boolean equals(DefaultDnsCacheEntry entry, DefaultDnsCacheEntry otherEntry) {
if (entry.address() != null) {
return entry.address().equals(otherEntry.address());
}
if (otherEntry.address() != null) {
return false;
}
return entry.cause().equals(otherEntry.cause());
}
};
private final int minTtl;
private final int maxTtl;
private final int negativeTtl;
/**
* Create a cache that respects the TTL returned by the DNS server
* and doesn't cache negative responses.
*/
public DefaultDnsCache() {
this(0, Cache.MAX_SUPPORTED_TTL_SECS, 0);
}
/**
* Create a cache.
* @param minTtl the minimum TTL
* @param maxTtl the maximum TTL
* @param negativeTtl the TTL for failed queries
*/
public DefaultDnsCache(int minTtl, int maxTtl, int negativeTtl) {
this.minTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(minTtl, "minTtl"));
this.maxTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(maxTtl, "maxTtl"));
if (minTtl > maxTtl) {
throw new IllegalArgumentException(
"minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)");
}
this.negativeTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(negativeTtl, "negativeTtl"));
}
/**
* Returns the minimum TTL of the cached DNS resource records (in seconds).
*
* @see #maxTtl()
*/
public int minTtl() {
return minTtl;
}
/**
* Returns the maximum TTL of the cached DNS resource records (in seconds).
*
* @see #minTtl()
*/
public int maxTtl() {
return maxTtl;
}
/**
* Returns the TTL of the cache for the failed DNS queries (in seconds). The default value is {@code 0}, which
* disables the cache for negative results.
*/
public int negativeTtl() {
return negativeTtl;
}
@Override
public void clear() {
resolveCache.clear();
}
@Override
public boolean clear(String hostname) {
checkNotNull(hostname, "hostname");
return resolveCache.clear(appendDot(hostname));
}
private static boolean emptyAdditionals(DnsRecord[] additionals) {
return additionals == null || additionals.length == 0;
}
@Override
public List<? extends DnsCacheEntry> get(String hostname, DnsRecord[] additionals) {
checkNotNull(hostname, "hostname");
if (!emptyAdditionals(additionals)) {
return Collections.<DnsCacheEntry>emptyList();
}
final List<? extends DnsCacheEntry> entries = resolveCache.get(appendDot(hostname));
if (entries == null || entries.isEmpty()) {
return entries;
}
return new DnsCacheEntryList(entries);
}
@Override
public DnsCacheEntry cache(String hostname, DnsRecord[] additionals,
InetAddress address, long originalTtl, EventLoop loop) {
checkNotNull(hostname, "hostname");
checkNotNull(address, "address");
checkNotNull(loop, "loop");
DefaultDnsCacheEntry e = new DefaultDnsCacheEntry(hostname, address);
if (maxTtl == 0 || !emptyAdditionals(additionals)) {
return e;
}
resolveCache.cache(appendDot(hostname), e, Math.max(minTtl, (int) Math.min(maxTtl, originalTtl)), loop);
return e;
}
@Override
public DnsCacheEntry cache(String hostname, DnsRecord[] additionals, Throwable cause, EventLoop loop) {
checkNotNull(hostname, "hostname");
checkNotNull(cause, "cause");
checkNotNull(loop, "loop");
DefaultDnsCacheEntry e = new DefaultDnsCacheEntry(hostname, cause);
if (negativeTtl == 0 || !emptyAdditionals(additionals)) {
return e;
}
resolveCache.cache(appendDot(hostname), e, negativeTtl, loop);
return e;
}
@Override
public String toString() {
return new StringBuilder()
.append("DefaultDnsCache(minTtl=")
.append(minTtl).append(", maxTtl=")
.append(maxTtl).append(", negativeTtl=")
.append(negativeTtl).append(", cached resolved hostname=")
.append(resolveCache.size()).append(')')
.toString();
}
private static final class DefaultDnsCacheEntry implements DnsCacheEntry {
private final String hostname;
private final InetAddress address;
private final Throwable cause;
private final int hash;
DefaultDnsCacheEntry(String hostname, InetAddress address) {
this.hostname = hostname;
this.address = address;
cause = null;
hash = System.identityHashCode(this);
}
DefaultDnsCacheEntry(String hostname, Throwable cause) {
this.hostname = hostname;
this.cause = cause;
address = null;
hash = System.identityHashCode(this);
}
private DefaultDnsCacheEntry(DefaultDnsCacheEntry entry) {
this.hostname = entry.hostname;
if (entry.cause == null) {
this.address = entry.address;
this.cause = null;
} else {
this.address = null;
this.cause = copyThrowable(entry.cause);
}
this.hash = entry.hash;
}
@Override
public InetAddress address() {
return address;
}
@Override
public Throwable cause() {
return cause;
}
String hostname() {
return hostname;
}
@Override
public String toString() {
if (cause != null) {
return hostname + '/' + cause;
} else {
return address.toString();
}
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
return (obj instanceof DefaultDnsCacheEntry) && ((DefaultDnsCacheEntry) obj).hash == hash;
}
DnsCacheEntry copyIfNeeded() {
if (cause == null) {
return this;
}
return new DefaultDnsCacheEntry(this);
}
}
private static String appendDot(String hostname) {
return StringUtil.endsWith(hostname, '.') ? hostname : hostname + '.';
}
private static Throwable copyThrowable(Throwable error) {
if (error.getClass() == UnknownHostException.class) {
// Fast-path as this is the only type of Throwable that our implementation ever add to the cache.
UnknownHostException copy = new UnknownHostException(error.getMessage()) {
@Override
public Throwable fillInStackTrace() {
// noop.
return this;
}
};
copy.initCause(error.getCause());
copy.setStackTrace(error.getStackTrace());
return copy;
}
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
// Throwable is Serializable so lets just do a deep copy.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(error);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
return (Throwable) ois.readObject();
} catch (IOException e) {
throw new IllegalStateException(e);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException ignore) {
// noop
}
}
if (ois != null) {
try {
ois.close();
} catch (IOException ignore) {
// noop
}
}
}
}
private static final class DnsCacheEntryList extends AbstractList<DnsCacheEntry> {
private final List<? extends DnsCacheEntry> entries;
DnsCacheEntryList(List<? extends DnsCacheEntry> entries) {
this.entries = entries;
}
@Override
public DnsCacheEntry get(int index) {
DefaultDnsCacheEntry entry = (DefaultDnsCacheEntry) entries.get(index);
// As we dont know what exactly the user is doing with the returned exception (for example
// using addSuppressed(...) and so hold up a lot of memory until the entry expires) we do
// create a copy.
return entry.copyIfNeeded();
}
@Override
public int size() {
return entries.size();
}
@Override
public int hashCode() {
// Just delegate to super to make checkstyle happy
return super.hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof DnsCacheEntryList) {
// Fast-path.
return entries.equals(((DnsCacheEntryList) o).entries);
}
return super.equals(o);
}
};
}

View file

@ -0,0 +1,105 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.util.AsciiString;
import java.util.List;
import static io.netty.util.internal.ObjectUtil.*;
/**
* Default implementation of a {@link DnsCnameCache}.
*/
public final class DefaultDnsCnameCache implements DnsCnameCache {
private final int minTtl;
private final int maxTtl;
private final Cache<String> cache = new Cache<String>() {
@Override
protected boolean shouldReplaceAll(String entry) {
// Only one 1:1 mapping is supported as specified in the RFC.
return true;
}
@Override
protected boolean equals(String entry, String otherEntry) {
return AsciiString.contentEqualsIgnoreCase(entry, otherEntry);
}
};
/**
* Create a cache that respects the TTL returned by the DNS server.
*/
public DefaultDnsCnameCache() {
this(0, Cache.MAX_SUPPORTED_TTL_SECS);
}
/**
* Create a cache.
*
* @param minTtl the minimum TTL
* @param maxTtl the maximum TTL
*/
public DefaultDnsCnameCache(int minTtl, int maxTtl) {
this.minTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositiveOrZero(minTtl, "minTtl"));
this.maxTtl = Math.min(Cache.MAX_SUPPORTED_TTL_SECS, checkPositive(maxTtl, "maxTtl"));
if (minTtl > maxTtl) {
throw new IllegalArgumentException(
"minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)");
}
}
@SuppressWarnings("unchecked")
@Override
public String get(String hostname) {
List<? extends String> cached = cache.get(checkNotNull(hostname, "hostname"));
if (cached == null || cached.isEmpty()) {
return null;
}
// We can never have more then one record.
return cached.get(0);
}
@Override
public void cache(String hostname, String cname, long originalTtl, EventLoop loop) {
checkNotNull(hostname, "hostname");
checkNotNull(cname, "cname");
checkNotNull(loop, "loop");
cache.cache(hostname, cname, Math.max(minTtl, (int) Math.min(maxTtl, originalTtl)), loop);
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean clear(String hostname) {
return cache.clear(checkNotNull(hostname, "hostname"));
}
// Package visibility for testing purposes
int minTtl() {
return minTtl;
}
// Package visibility for testing purposes
int maxTtl() {
return maxTtl;
}
}

View file

@ -0,0 +1,165 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
import io.netty.util.NetUtil;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SocketUtils;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.lang.reflect.Method;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static io.netty.resolver.dns.DnsServerAddresses.sequential;
/**
* A {@link DnsServerAddressStreamProvider} which will use predefined default DNS servers to use for DNS resolution.
* These defaults do not respect your host's machines defaults.
* <p>
* This may use the JDK's blocking DNS resolution to bootstrap the default DNS server addresses.
*/
public final class DefaultDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider {
private static final InternalLogger logger =
InternalLoggerFactory.getInstance(DefaultDnsServerAddressStreamProvider.class);
private static final String DEFAULT_FALLBACK_SERVER_PROPERTY = "io.netty.resolver.dns.defaultNameServerFallback";
public static final DefaultDnsServerAddressStreamProvider INSTANCE = new DefaultDnsServerAddressStreamProvider();
private static final List<InetSocketAddress> DEFAULT_NAME_SERVER_LIST;
private static final DnsServerAddresses DEFAULT_NAME_SERVERS;
static final int DNS_PORT = 53;
static {
final List<InetSocketAddress> defaultNameServers = new ArrayList<InetSocketAddress>(2);
if (!PlatformDependent.isAndroid()) {
// Only try to use when not on Android as the classes not exists there:
// See https://github.com/netty/netty/issues/8654
DirContextUtils.addNameServers(defaultNameServers, DNS_PORT);
}
// Only try when using on Java8 and lower as otherwise it will produce:
// WARNING: Illegal reflective access by io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider
if (PlatformDependent.javaVersion() < 9 && defaultNameServers.isEmpty()) {
try {
Class<?> configClass = Class.forName("sun.net.dns.ResolverConfiguration");
Method open = configClass.getMethod("open");
Method nameservers = configClass.getMethod("nameservers");
Object instance = open.invoke(null);
@SuppressWarnings("unchecked")
final List<String> list = (List<String>) nameservers.invoke(instance);
for (String a: list) {
if (a != null) {
defaultNameServers.add(new InetSocketAddress(SocketUtils.addressByName(a), DNS_PORT));
}
}
} catch (Exception ignore) {
// Failed to get the system name server list via reflection.
// Will add the default name servers afterwards.
}
}
if (!defaultNameServers.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug(
"Default DNS servers: {} (sun.net.dns.ResolverConfiguration)", defaultNameServers);
}
} else {
String defaultNameserverString = SystemPropertyUtil.get(DEFAULT_FALLBACK_SERVER_PROPERTY, null);
if (defaultNameserverString != null) {
for (String server : defaultNameserverString.split(",")) {
String dns = server.trim();
if (!NetUtil.isValidIpV4Address(dns) && !NetUtil.isValidIpV6Address(dns)) {
throw new ExceptionInInitializerError(DEFAULT_FALLBACK_SERVER_PROPERTY + " doesn't" +
" contain a valid list of NameServers: " + defaultNameserverString);
}
defaultNameServers.add(SocketUtils.socketAddress(server.trim(), DNS_PORT));
}
if (defaultNameServers.isEmpty()) {
throw new ExceptionInInitializerError(DEFAULT_FALLBACK_SERVER_PROPERTY + " doesn't" +
" contain a valid list of NameServers: " + defaultNameserverString);
}
if (logger.isWarnEnabled()) {
logger.warn(
"Default DNS servers: {} (Configured by {} system property)",
defaultNameServers, DEFAULT_FALLBACK_SERVER_PROPERTY);
}
} else {
// Depending if IPv6 or IPv4 is used choose the correct DNS servers provided by google:
// https://developers.google.com/speed/public-dns/docs/using
// https://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html
if (NetUtil.isIpV6AddressesPreferred() ||
(NetUtil.LOCALHOST instanceof Inet6Address && !NetUtil.isIpV4StackPreferred())) {
Collections.addAll(
defaultNameServers,
SocketUtils.socketAddress("2001:4860:4860::8888", DNS_PORT),
SocketUtils.socketAddress("2001:4860:4860::8844", DNS_PORT));
} else {
Collections.addAll(
defaultNameServers,
SocketUtils.socketAddress("8.8.8.8", DNS_PORT),
SocketUtils.socketAddress("8.8.4.4", DNS_PORT));
}
if (logger.isWarnEnabled()) {
logger.warn(
"Default DNS servers: {} (Google Public DNS as a fallback)", defaultNameServers);
}
}
}
DEFAULT_NAME_SERVER_LIST = Collections.unmodifiableList(defaultNameServers);
DEFAULT_NAME_SERVERS = sequential(DEFAULT_NAME_SERVER_LIST);
}
private DefaultDnsServerAddressStreamProvider() {
}
@Override
public DnsServerAddressStream nameServerAddressStream(String hostname) {
return DEFAULT_NAME_SERVERS.stream();
}
/**
* Returns the list of the system DNS server addresses. If it failed to retrieve the list of the system DNS server
* addresses from the environment, it will return {@code "8.8.8.8"} and {@code "8.8.4.4"}, the addresses of the
* Google public DNS servers.
*/
public static List<InetSocketAddress> defaultAddressList() {
return DEFAULT_NAME_SERVER_LIST;
}
/**
* Returns the {@link DnsServerAddresses} that yields the system DNS server addresses sequentially. If it failed to
* retrieve the list of the system DNS server addresses from the environment, it will use {@code "8.8.8.8"} and
* {@code "8.8.4.4"}, the addresses of the Google public DNS servers.
* <p>
* This method has the same effect with the following code:
* <pre>
* DnsServerAddresses.sequential(DnsServerAddresses.defaultAddressList());
* </pre>
* </p>
*/
public static DnsServerAddresses defaultAddresses() {
return DEFAULT_NAME_SERVERS;
}
}

View file

@ -0,0 +1,47 @@
/*
* 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:
*
* https://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.resolver.dns;
import java.net.InetSocketAddress;
import java.util.List;
abstract class DefaultDnsServerAddresses extends DnsServerAddresses {
protected final List<InetSocketAddress> addresses;
private final String strVal;
DefaultDnsServerAddresses(String type, List<InetSocketAddress> addresses) {
this.addresses = addresses;
final StringBuilder buf = new StringBuilder(type.length() + 2 + addresses.size() * 16);
buf.append(type).append('(');
for (InetSocketAddress a: addresses) {
buf.append(a).append(", ");
}
buf.setLength(buf.length() - 2);
buf.append(')');
strVal = buf.toString();
}
@Override
public String toString() {
return strVal;
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import io.netty.util.internal.SocketUtils;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Hashtable;
import java.util.List;
final class DirContextUtils {
private static final InternalLogger logger =
InternalLoggerFactory.getInstance(DirContextUtils.class);
private DirContextUtils() { }
static void addNameServers(List<InetSocketAddress> defaultNameServers, int defaultPort) {
// Using jndi-dns to obtain the default name servers.
//
// See:
// - https://docs.oracle.com/javase/8/docs/technotes/guides/jndi/jndi-dns.html
// - https://mail.openjdk.java.net/pipermail/net-dev/2017-March/010695.html
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
env.put("java.naming.provider.url", "dns://");
try {
DirContext ctx = new InitialDirContext(env);
String dnsUrls = (String) ctx.getEnvironment().get("java.naming.provider.url");
// Only try if not empty as otherwise we will produce an exception
if (dnsUrls != null && !dnsUrls.isEmpty()) {
String[] servers = dnsUrls.split(" ");
for (String server : servers) {
try {
URI uri = new URI(server);
String host = new URI(server).getHost();
if (host == null || host.isEmpty()) {
logger.debug(
"Skipping a nameserver URI as host portion could not be extracted: {}", server);
// If the host portion can not be parsed we should just skip this entry.
continue;
}
int port = uri.getPort();
defaultNameServers.add(SocketUtils.socketAddress(uri.getHost(), port == -1 ?
defaultPort : port));
} catch (URISyntaxException e) {
logger.debug("Skipping a malformed nameserver URI: {}", server, e);
}
}
}
} catch (NamingException ignore) {
// Will try reflection if this fails.
}
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import java.net.IDN;
import java.net.InetAddress;
import java.net.UnknownHostException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.handler.codec.dns.DnsRawRecord;
import io.netty.handler.codec.dns.DnsRecord;
/**
* Decodes an {@link InetAddress} from an A or AAAA {@link DnsRawRecord}.
*/
final class DnsAddressDecoder {
private static final int INADDRSZ4 = 4;
private static final int INADDRSZ6 = 16;
/**
* Decodes an {@link InetAddress} from an A or AAAA {@link DnsRawRecord}.
*
* @param record the {@link DnsRecord}, most likely a {@link DnsRawRecord}
* @param name the host name of the decoded address
* @param decodeIdn whether to convert {@code name} to a unicode host name
*
* @return the {@link InetAddress}, or {@code null} if {@code record} is not a {@link DnsRawRecord} or
* its content is malformed
*/
static InetAddress decodeAddress(DnsRecord record, String name, boolean decodeIdn) {
if (!(record instanceof DnsRawRecord)) {
return null;
}
final ByteBuf content = ((ByteBufHolder) record).content();
final int contentLen = content.readableBytes();
if (contentLen != INADDRSZ4 && contentLen != INADDRSZ6) {
return null;
}
final byte[] addrBytes = new byte[contentLen];
content.getBytes(content.readerIndex(), addrBytes);
try {
return InetAddress.getByAddress(decodeIdn ? IDN.toUnicode(name) : name, addrBytes);
} catch (UnknownHostException e) {
// Should never reach here.
throw new Error(e);
}
}
private DnsAddressDecoder() { }
}

View file

@ -0,0 +1,111 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import static io.netty.resolver.dns.DnsAddressDecoder.decodeAddress;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import io.netty.channel.Channel;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.dns.DnsRecord;
import io.netty.handler.codec.dns.DnsRecordType;
import io.netty.util.concurrent.Promise;
final class DnsAddressResolveContext extends DnsResolveContext<InetAddress> {
private final DnsCache resolveCache;
private final AuthoritativeDnsServerCache authoritativeDnsServerCache;
private final boolean completeEarlyIfPossible;
DnsAddressResolveContext(DnsNameResolver parent, Channel channel, Promise<?> originalPromise,
String hostname, DnsRecord[] additionals,
DnsServerAddressStream nameServerAddrs, int allowedQueries, DnsCache resolveCache,
AuthoritativeDnsServerCache authoritativeDnsServerCache,
boolean completeEarlyIfPossible) {
super(parent, channel, originalPromise, hostname, DnsRecord.CLASS_IN,
parent.resolveRecordTypes(), additionals, nameServerAddrs, allowedQueries);
this.resolveCache = resolveCache;
this.authoritativeDnsServerCache = authoritativeDnsServerCache;
this.completeEarlyIfPossible = completeEarlyIfPossible;
}
@Override
DnsResolveContext<InetAddress> newResolverContext(DnsNameResolver parent, Channel channel,
Promise<?> originalPromise,
String hostname,
int dnsClass, DnsRecordType[] expectedTypes,
DnsRecord[] additionals,
DnsServerAddressStream nameServerAddrs, int allowedQueries) {
return new DnsAddressResolveContext(parent, channel, originalPromise, hostname, additionals, nameServerAddrs,
allowedQueries, resolveCache, authoritativeDnsServerCache, completeEarlyIfPossible);
}
@Override
InetAddress convertRecord(DnsRecord record, String hostname, DnsRecord[] additionals, EventLoop eventLoop) {
return decodeAddress(record, hostname, parent.isDecodeIdn());
}
@Override
List<InetAddress> filterResults(List<InetAddress> unfiltered) {
Collections.sort(unfiltered, PreferredAddressTypeComparator.comparator(parent.preferredAddressType()));
return unfiltered;
}
@Override
boolean isCompleteEarly(InetAddress resolved) {
return completeEarlyIfPossible && parent.preferredAddressType().addressType() == resolved.getClass();
}
@Override
boolean isDuplicateAllowed() {
// We don't want include duplicates to mimic JDK behaviour.
return false;
}
@Override
void cache(String hostname, DnsRecord[] additionals,
DnsRecord result, InetAddress convertedResult) {
resolveCache.cache(hostname, additionals, convertedResult, result.timeToLive(), channel().eventLoop());
}
@Override
void cache(String hostname, DnsRecord[] additionals, UnknownHostException cause) {
resolveCache.cache(hostname, additionals, cause, channel().eventLoop());
}
@Override
void doSearchDomainQuery(String hostname, Promise<List<InetAddress>> nextPromise) {
// Query the cache for the hostname first and only do a query if we could not find it in the cache.
if (!DnsNameResolver.doResolveAllCached(
hostname, additionals, nextPromise, resolveCache, parent.resolvedInternetProtocolFamiliesUnsafe())) {
super.doSearchDomainQuery(hostname, nextPromise);
}
}
@Override
DnsCache resolveCache() {
return resolveCache;
}
@Override
AuthoritativeDnsServerCache authoritativeDnsServerCache() {
return authoritativeDnsServerCache;
}
}

View file

@ -0,0 +1,126 @@
/*
* 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:
*
* https://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.resolver.dns;
import io.netty.channel.ChannelFactory;
import io.netty.channel.EventLoop;
import io.netty.channel.socket.DatagramChannel;
import io.netty.resolver.AddressResolver;
import io.netty.resolver.AddressResolverGroup;
import io.netty.resolver.InetSocketAddressResolver;
import io.netty.resolver.NameResolver;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.StringUtil;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import static io.netty.util.internal.PlatformDependent.newConcurrentHashMap;
/**
* A {@link AddressResolverGroup} of {@link DnsNameResolver}s.
*/
public class DnsAddressResolverGroup extends AddressResolverGroup<InetSocketAddress> {
private final DnsNameResolverBuilder dnsResolverBuilder;
private final ConcurrentMap<String, Promise<InetAddress>> resolvesInProgress = newConcurrentHashMap();
private final ConcurrentMap<String, Promise<List<InetAddress>>> resolveAllsInProgress = newConcurrentHashMap();
public DnsAddressResolverGroup(DnsNameResolverBuilder dnsResolverBuilder) {
this.dnsResolverBuilder = dnsResolverBuilder.copy();
}
public DnsAddressResolverGroup(
Class<? extends DatagramChannel> channelType,
DnsServerAddressStreamProvider nameServerProvider) {
this.dnsResolverBuilder = new DnsNameResolverBuilder();
dnsResolverBuilder.channelType(channelType).nameServerProvider(nameServerProvider);
}
public DnsAddressResolverGroup(
ChannelFactory<? extends DatagramChannel> channelFactory,
DnsServerAddressStreamProvider nameServerProvider) {
this.dnsResolverBuilder = new DnsNameResolverBuilder();
dnsResolverBuilder.channelFactory(channelFactory).nameServerProvider(nameServerProvider);
}
@SuppressWarnings("deprecation")
@Override
protected final AddressResolver<InetSocketAddress> newResolver(EventExecutor executor) throws Exception {
if (!(executor instanceof EventLoop)) {
throw new IllegalStateException(
"unsupported executor type: " + StringUtil.simpleClassName(executor) +
" (expected: " + StringUtil.simpleClassName(EventLoop.class));
}
// we don't really need to pass channelFactory and nameServerProvider separately,
// but still keep this to ensure backward compatibility with (potentially) override methods
EventLoop loop = dnsResolverBuilder.eventLoop;
return newResolver(loop == null ? (EventLoop) executor : loop,
dnsResolverBuilder.channelFactory(),
dnsResolverBuilder.nameServerProvider());
}
/**
* @deprecated Override {@link #newNameResolver(EventLoop, ChannelFactory, DnsServerAddressStreamProvider)}.
*/
@Deprecated
protected AddressResolver<InetSocketAddress> newResolver(
EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
DnsServerAddressStreamProvider nameServerProvider) throws Exception {
final NameResolver<InetAddress> resolver = new InflightNameResolver<InetAddress>(
eventLoop,
newNameResolver(eventLoop, channelFactory, nameServerProvider),
resolvesInProgress,
resolveAllsInProgress);
return newAddressResolver(eventLoop, resolver);
}
/**
* Creates a new {@link NameResolver}. Override this method to create an alternative {@link NameResolver}
* implementation or override the default configuration.
*/
protected NameResolver<InetAddress> newNameResolver(EventLoop eventLoop,
ChannelFactory<? extends DatagramChannel> channelFactory,
DnsServerAddressStreamProvider nameServerProvider)
throws Exception {
DnsNameResolverBuilder builder = dnsResolverBuilder.copy();
// once again, channelFactory and nameServerProvider are most probably set in builder already,
// but I do reassign them again to avoid corner cases with override methods
return builder.eventLoop(eventLoop)
.channelFactory(channelFactory)
.nameServerProvider(nameServerProvider)
.build();
}
/**
* Creates a new {@link AddressResolver}. Override this method to create an alternative {@link AddressResolver}
* implementation or override the default configuration.
*/
protected AddressResolver<InetSocketAddress> newAddressResolver(EventLoop eventLoop,
NameResolver<InetAddress> resolver)
throws Exception {
return new InetSocketAddressResolver(eventLoop, resolver);
}
}

View file

@ -0,0 +1,76 @@
/*
* Copyright 2016 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:
*
* https://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.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.dns.DnsRecord;
import java.net.InetAddress;
import java.util.List;
/**
* A cache for DNS resolution entries.
*/
public interface DnsCache {
/**
* Clears all the resolved addresses cached by this resolver.
*
* @see #clear(String)
*/
void clear();
/**
* Clears the resolved addresses of the specified host name from the cache of this resolver.
*
* @return {@code true} if and only if there was an entry for the specified host name in the cache and
* it has been removed by this method
*/
boolean clear(String hostname);
/**
* Return the cached entries for the given hostname.
* @param hostname the hostname
* @param additionals the additional records
* @return the cached entries
*/
List<? extends DnsCacheEntry> get(String hostname, DnsRecord[] additionals);
/**
* Create a new {@link DnsCacheEntry} and cache a resolved address for a given hostname.
* @param hostname the hostname
* @param additionals the additional records
* @param address the resolved address
* @param originalTtl the TTL as returned by the DNS server
* @param loop the {@link EventLoop} used to register the TTL timeout
* @return The {@link DnsCacheEntry} corresponding to this cache entry.
*/
DnsCacheEntry cache(String hostname, DnsRecord[] additionals, InetAddress address, long originalTtl,
EventLoop loop);
/**
* Cache the resolution failure for a given hostname.
* Be aware this <strong>won't</strong> be called with timeout / cancel / transport exceptions.
*
* @param hostname the hostname
* @param additionals the additional records
* @param cause the resolution failure
* @param loop the {@link EventLoop} used to register the TTL timeout
* @return The {@link DnsCacheEntry} corresponding to this cache entry, or {@code null} if this cache doesn't
* support caching failed responses.
*/
DnsCacheEntry cache(String hostname, DnsRecord[] additionals, Throwable cause, EventLoop loop);
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
import java.net.InetAddress;
/**
* Represents the results from a previous DNS query which can be cached.
*/
public interface DnsCacheEntry {
/**
* Get the resolved address.
* <p>
* This may be null if the resolution failed, and in that case {@link #cause()} will describe the failure.
* @return the resolved address.
*/
InetAddress address();
/**
* If the DNS query failed this will provide the rational.
* @return the rational for why the DNS query failed, or {@code null} if the query hasn't failed.
*/
Throwable cause();
}

View file

@ -0,0 +1,57 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import io.netty.channel.EventLoop;
/**
* A cache for {@code CNAME}s.
*/
public interface DnsCnameCache {
/**
* Returns the cached cname for the given hostname.
*
* @param hostname the hostname
* @return the cached entries or an {@code null} if none.
*/
String get(String hostname);
/**
* Caches a cname entry that should be used for the given hostname.
*
* @param hostname the hostname
* @param cname the cname mapping.
* @param originalTtl the TTL as returned by the DNS server
* @param loop the {@link EventLoop} used to register the TTL timeout
*/
void cache(String hostname, String cname, long originalTtl, EventLoop loop);
/**
* Clears all cached nameservers.
*
* @see #clear(String)
*/
void clear();
/**
* Clears the cached nameservers for the specified hostname.
*
* @return {@code true} if and only if there was an entry for the specified host name in the cache and
* it has been removed by this method
*/
boolean clear(String hostname);
}

View file

@ -0,0 +1,74 @@
/*
* Copyright 2023 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:
*
* https://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.resolver.dns;
import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SuppressJava6Requirement;
import io.netty.util.internal.ThrowableUtil;
import java.net.UnknownHostException;
/**
* A metadata carrier exception, to propagate {@link DnsResponseCode} information as an enrichment
* within the {@link UnknownHostException} cause.
*/
public final class DnsErrorCauseException extends RuntimeException {
private static final long serialVersionUID = 7485145036717494533L;
private final DnsResponseCode code;
private DnsErrorCauseException(String message, DnsResponseCode code) {
super(message);
this.code = code;
}
@SuppressJava6Requirement(reason = "uses Java 7+ Exception.<init>(String, Throwable, boolean, boolean)" +
" but is guarded by version checks")
private DnsErrorCauseException(String message, DnsResponseCode code, boolean shared) {
super(message, null, false, true);
this.code = code;
assert shared;
}
// Override fillInStackTrace() so we not populate the backtrace via a native call and so leak the
// Classloader.
@Override
public Throwable fillInStackTrace() {
return this;
}
/**
* Returns the DNS error-code that caused the {@link UnknownHostException}.
*
* @return the DNS error-code that caused the {@link UnknownHostException}.
*/
public DnsResponseCode getCode() {
return code;
}
static DnsErrorCauseException newStatic(String message, DnsResponseCode code, Class<?> clazz, String method) {
final DnsErrorCauseException exception;
if (PlatformDependent.javaVersion() >= 7) {
exception = new DnsErrorCauseException(message, code, true);
} else {
exception = new DnsErrorCauseException(message, code);
}
return ThrowableUtil.unknownStackTrace(exception, clazz, method);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,661 @@
/*
* 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:
*
* https://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.resolver.dns;
import io.netty.channel.ChannelFactory;
import io.netty.channel.EventLoop;
import io.netty.channel.ReflectiveChannelFactory;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.channel.socket.SocketChannel;
import io.netty.resolver.HostsFileEntriesResolver;
import io.netty.resolver.ResolvedAddressTypes;
import io.netty.util.concurrent.Future;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.intValue;
/**
* A {@link DnsNameResolver} builder.
*/
public final class DnsNameResolverBuilder {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolverBuilder.class);
volatile EventLoop eventLoop;
private ChannelFactory<? extends DatagramChannel> channelFactory;
private ChannelFactory<? extends SocketChannel> socketChannelFactory;
private boolean retryOnTimeout;
private DnsCache resolveCache;
private DnsCnameCache cnameCache;
private AuthoritativeDnsServerCache authoritativeDnsServerCache;
private SocketAddress localAddress;
private Integer minTtl;
private Integer maxTtl;
private Integer negativeTtl;
private long queryTimeoutMillis = -1;
private ResolvedAddressTypes resolvedAddressTypes = DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
private boolean completeOncePreferredResolved;
private boolean recursionDesired = true;
private int maxQueriesPerResolve = -1;
private boolean traceEnabled;
private int maxPayloadSize = 4096;
private boolean optResourceEnabled = true;
private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
private DnsServerAddressStreamProvider dnsServerAddressStreamProvider =
DnsServerAddressStreamProviders.platformDefault();
private DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory =
NoopDnsQueryLifecycleObserverFactory.INSTANCE;
private String[] searchDomains;
private int ndots = -1;
private boolean decodeIdn = true;
private int maxNumConsolidation;
/**
* Creates a new builder.
*/
public DnsNameResolverBuilder() {
}
/**
* Creates a new builder.
*
* @param eventLoop the {@link EventLoop} which will perform the communication with the DNS
* servers.
*/
public DnsNameResolverBuilder(EventLoop eventLoop) {
eventLoop(eventLoop);
}
/**
* Sets the {@link EventLoop} which will perform the communication with the DNS servers.
*
* @param eventLoop the {@link EventLoop}
* @return {@code this}
*/
public DnsNameResolverBuilder eventLoop(EventLoop eventLoop) {
this.eventLoop = eventLoop;
return this;
}
protected ChannelFactory<? extends DatagramChannel> channelFactory() {
return this.channelFactory;
}
/**
* Sets the {@link ChannelFactory} that will create a {@link DatagramChannel}.
* If <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should be supported as well it is required
* to call the {@link #socketChannelFactory(ChannelFactory)} or {@link #socketChannelType(Class)} method.
*
* @param channelFactory the {@link ChannelFactory}
* @return {@code this}
*/
public DnsNameResolverBuilder channelFactory(ChannelFactory<? extends DatagramChannel> channelFactory) {
this.channelFactory = channelFactory;
return this;
}
/**
* Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type.
* Use as an alternative to {@link #channelFactory(ChannelFactory)}.
* If <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should be supported as well it is required
* to call the {@link #socketChannelFactory(ChannelFactory)} or {@link #socketChannelType(Class)} method.
*
* @param channelType the type
* @return {@code this}
*/
public DnsNameResolverBuilder channelType(Class<? extends DatagramChannel> channelType) {
return channelFactory(new ReflectiveChannelFactory<DatagramChannel>(channelType));
}
/**
* Sets the {@link ChannelFactory} that will create a {@link SocketChannel} for
* <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
*
* TCP fallback is <strong>not</strong> enabled by default and must be enabled by providing a non-null
* {@link ChannelFactory} for this method.
*
* @param channelFactory the {@link ChannelFactory} or {@code null}
* if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should not be supported.
* By default, TCP fallback is not enabled.
* @return {@code this}
*/
public DnsNameResolverBuilder socketChannelFactory(ChannelFactory<? extends SocketChannel> channelFactory) {
return socketChannelFactory(channelFactory, false);
}
/**
* Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type for
* <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
* Use as an alternative to {@link #socketChannelFactory(ChannelFactory)}.
*
* TCP fallback is <strong>not</strong> enabled by default and must be enabled by providing a non-null
* {@code channelType} for this method.
*
* @param channelType the type or {@code null} if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a>
* should not be supported. By default, TCP fallback is not enabled.
* @return {@code this}
*/
public DnsNameResolverBuilder socketChannelType(Class<? extends SocketChannel> channelType) {
return socketChannelType(channelType, false);
}
/**
* Sets the {@link ChannelFactory} that will create a {@link SocketChannel} for
* <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
*
* TCP fallback is <strong>not</strong> enabled by default and must be enabled by providing a non-null
* {@link ChannelFactory} for this method.
*
* @param channelFactory the {@link ChannelFactory} or {@code null}
* if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should not be supported.
* By default, TCP fallback is not enabled.
* @param retryOnTimeout if {@code true} the {@link DnsNameResolver} will also fallback to TCP if a timeout
* was detected, if {@code false} it will only try to use TCP if the response was marked
* as truncated.
* @return {@code this}
*/
public DnsNameResolverBuilder socketChannelFactory(
ChannelFactory<? extends SocketChannel> channelFactory, boolean retryOnTimeout) {
this.socketChannelFactory = channelFactory;
this.retryOnTimeout = retryOnTimeout;
return this;
}
/**
* Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type for
* <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
* Use as an alternative to {@link #socketChannelFactory(ChannelFactory)}.
*
* TCP fallback is <strong>not</strong> enabled by default and must be enabled by providing a non-null
* {@code channelType} for this method.
*
* @param channelType the type or {@code null} if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a>
* should not be supported. By default, TCP fallback is not enabled.
* @param retryOnTimeout if {@code true} the {@link DnsNameResolver} will also fallback to TCP if a timeout
* was detected, if {@code false} it will only try to use TCP if the response was marked
* as truncated.
* @return {@code this}
*/
public DnsNameResolverBuilder socketChannelType(
Class<? extends SocketChannel> channelType, boolean retryOnTimeout) {
if (channelType == null) {
return socketChannelFactory(null, retryOnTimeout);
}
return socketChannelFactory(new ReflectiveChannelFactory<SocketChannel>(channelType), retryOnTimeout);
}
/**
* Sets the cache for resolution results.
*
* @param resolveCache the DNS resolution results cache
* @return {@code this}
*/
public DnsNameResolverBuilder resolveCache(DnsCache resolveCache) {
this.resolveCache = resolveCache;
return this;
}
/**
* Sets the cache for {@code CNAME} mappings.
*
* @param cnameCache the cache used to cache {@code CNAME} mappings for a domain.
* @return {@code this}
*/
public DnsNameResolverBuilder cnameCache(DnsCnameCache cnameCache) {
this.cnameCache = cnameCache;
return this;
}
/**
* Set the factory used to generate objects which can observe individual DNS queries.
* @param lifecycleObserverFactory the factory used to generate objects which can observe individual DNS queries.
* @return {@code this}
*/
public DnsNameResolverBuilder dnsQueryLifecycleObserverFactory(DnsQueryLifecycleObserverFactory
lifecycleObserverFactory) {
this.dnsQueryLifecycleObserverFactory = checkNotNull(lifecycleObserverFactory, "lifecycleObserverFactory");
return this;
}
/**
* Sets the cache for authoritative NS servers
*
* @param authoritativeDnsServerCache the authoritative NS servers cache
* @return {@code this}
* @deprecated Use {@link #authoritativeDnsServerCache(AuthoritativeDnsServerCache)}
*/
@Deprecated
public DnsNameResolverBuilder authoritativeDnsServerCache(DnsCache authoritativeDnsServerCache) {
this.authoritativeDnsServerCache = new AuthoritativeDnsServerCacheAdapter(authoritativeDnsServerCache);
return this;
}
/**
* Sets the cache for authoritative NS servers
*
* @param authoritativeDnsServerCache the authoritative NS servers cache
* @return {@code this}
*/
public DnsNameResolverBuilder authoritativeDnsServerCache(AuthoritativeDnsServerCache authoritativeDnsServerCache) {
this.authoritativeDnsServerCache = authoritativeDnsServerCache;
return this;
}
/**
* Configure the address that will be used to bind too. If {@code null} the default will be used.
* @param localAddress the bind address
* @return {@code this}
*/
public DnsNameResolverBuilder localAddress(SocketAddress localAddress) {
this.localAddress = localAddress;
return this;
}
/**
* Sets the minimum and maximum TTL of the cached DNS resource records (in seconds). If the TTL of the DNS
* resource record returned by the DNS server is less than the minimum TTL or greater than the maximum TTL,
* this resolver will ignore the TTL from the DNS server and use the minimum TTL or the maximum TTL instead
* respectively.
* The default value is {@code 0} and {@link Integer#MAX_VALUE}, which practically tells this resolver to
* respect the TTL from the DNS server.
*
* @param minTtl the minimum TTL
* @param maxTtl the maximum TTL
* @return {@code this}
*/
public DnsNameResolverBuilder ttl(int minTtl, int maxTtl) {
this.maxTtl = maxTtl;
this.minTtl = minTtl;
return this;
}
/**
* Sets the TTL of the cache for the failed DNS queries (in seconds).
*
* @param negativeTtl the TTL for failed cached queries
* @return {@code this}
*/
public DnsNameResolverBuilder negativeTtl(int negativeTtl) {
this.negativeTtl = negativeTtl;
return this;
}
/**
* Sets the timeout of each DNS query performed by this resolver (in milliseconds).
* {@code 0} disables the timeout. If not set or a negative number is set, the default timeout is used.
*
* @param queryTimeoutMillis the query timeout
* @return {@code this}
*/
public DnsNameResolverBuilder queryTimeoutMillis(long queryTimeoutMillis) {
this.queryTimeoutMillis = queryTimeoutMillis;
return this;
}
/**
* Compute a {@link ResolvedAddressTypes} from some {@link InternetProtocolFamily}s.
* An empty input will return the default value, based on "java.net" System properties.
* Valid inputs are (), (IPv4), (IPv6), (Ipv4, IPv6) and (IPv6, IPv4).
* @param internetProtocolFamilies a valid sequence of {@link InternetProtocolFamily}s
* @return a {@link ResolvedAddressTypes}
*/
public static ResolvedAddressTypes computeResolvedAddressTypes(InternetProtocolFamily... internetProtocolFamilies) {
if (internetProtocolFamilies == null || internetProtocolFamilies.length == 0) {
return DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
}
if (internetProtocolFamilies.length > 2) {
throw new IllegalArgumentException("No more than 2 InternetProtocolFamilies");
}
switch(internetProtocolFamilies[0]) {
case IPv4:
return (internetProtocolFamilies.length >= 2
&& internetProtocolFamilies[1] == InternetProtocolFamily.IPv6) ?
ResolvedAddressTypes.IPV4_PREFERRED: ResolvedAddressTypes.IPV4_ONLY;
case IPv6:
return (internetProtocolFamilies.length >= 2
&& internetProtocolFamilies[1] == InternetProtocolFamily.IPv4) ?
ResolvedAddressTypes.IPV6_PREFERRED: ResolvedAddressTypes.IPV6_ONLY;
default:
throw new IllegalArgumentException(
"Couldn't resolve ResolvedAddressTypes from InternetProtocolFamily array");
}
}
/**
* Sets the list of the protocol families of the address resolved.
* You can use {@link DnsNameResolverBuilder#computeResolvedAddressTypes(InternetProtocolFamily...)}
* to get a {@link ResolvedAddressTypes} out of some {@link InternetProtocolFamily}s.
*
* @param resolvedAddressTypes the address types
* @return {@code this}
*/
public DnsNameResolverBuilder resolvedAddressTypes(ResolvedAddressTypes resolvedAddressTypes) {
this.resolvedAddressTypes = resolvedAddressTypes;
return this;
}
/**
* If {@code true} {@link DnsNameResolver#resolveAll(String)} will notify the returned {@link Future} as
* soon as all queries for the preferred address-type are complete.
*
* @param completeOncePreferredResolved {@code true} to enable, {@code false} to disable.
* @return {@code this}
*/
public DnsNameResolverBuilder completeOncePreferredResolved(boolean completeOncePreferredResolved) {
this.completeOncePreferredResolved = completeOncePreferredResolved;
return this;
}
/**
* Sets if this resolver has to send a DNS query with the RD (recursion desired) flag set.
*
* @param recursionDesired true if recursion is desired
* @return {@code this}
*/
public DnsNameResolverBuilder recursionDesired(boolean recursionDesired) {
this.recursionDesired = recursionDesired;
return this;
}
/**
* Sets the maximum allowed number of DNS queries to send when resolving a host name.
*
* @param maxQueriesPerResolve the max number of queries
* @return {@code this}
*/
public DnsNameResolverBuilder maxQueriesPerResolve(int maxQueriesPerResolve) {
this.maxQueriesPerResolve = maxQueriesPerResolve;
return this;
}
/**
* Sets if this resolver should generate the detailed trace information in an exception message so that
* it is easier to understand the cause of resolution failure.
*
* @param traceEnabled true if trace is enabled
* @return {@code this}
* @deprecated Prefer to {@linkplain #dnsQueryLifecycleObserverFactory(DnsQueryLifecycleObserverFactory) configure}
* a {@link LoggingDnsQueryLifeCycleObserverFactory} instead.
*/
@Deprecated
public DnsNameResolverBuilder traceEnabled(boolean traceEnabled) {
this.traceEnabled = traceEnabled;
return this;
}
/**
* Sets the capacity of the datagram packet buffer (in bytes). The default value is {@code 4096} bytes.
*
* @param maxPayloadSize the capacity of the datagram packet buffer
* @return {@code this}
*/
public DnsNameResolverBuilder maxPayloadSize(int maxPayloadSize) {
this.maxPayloadSize = maxPayloadSize;
return this;
}
/**
* Enable the automatic inclusion of a optional records that tries to give the remote DNS server a hint about
* how much data the resolver can read per response. Some DNSServer may not support this and so fail to answer
* queries. If you find problems you may want to disable this.
*
* @param optResourceEnabled if optional records inclusion is enabled
* @return {@code this}
*/
public DnsNameResolverBuilder optResourceEnabled(boolean optResourceEnabled) {
this.optResourceEnabled = optResourceEnabled;
return this;
}
/**
* @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to first check
* if the hostname is locally aliased.
* @return {@code this}
*/
public DnsNameResolverBuilder hostsFileEntriesResolver(HostsFileEntriesResolver hostsFileEntriesResolver) {
this.hostsFileEntriesResolver = hostsFileEntriesResolver;
return this;
}
protected DnsServerAddressStreamProvider nameServerProvider() {
return this.dnsServerAddressStreamProvider;
}
/**
* Set the {@link DnsServerAddressStreamProvider} which is used to determine which DNS server is used to resolve
* each hostname.
* @return {@code this}.
*/
public DnsNameResolverBuilder nameServerProvider(DnsServerAddressStreamProvider dnsServerAddressStreamProvider) {
this.dnsServerAddressStreamProvider =
checkNotNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
return this;
}
/**
* Set the list of search domains of the resolver.
*
* @param searchDomains the search domains
* @return {@code this}
*/
public DnsNameResolverBuilder searchDomains(Iterable<String> searchDomains) {
checkNotNull(searchDomains, "searchDomains");
final List<String> list = new ArrayList<String>(4);
for (String f : searchDomains) {
if (f == null) {
break;
}
// Avoid duplicate entries.
if (list.contains(f)) {
continue;
}
list.add(f);
}
this.searchDomains = list.toArray(EmptyArrays.EMPTY_STRINGS);
return this;
}
/**
* Set the number of dots which must appear in a name before an initial absolute query is made.
* The default value is {@code 1}.
*
* @param ndots the ndots value
* @return {@code this}
*/
public DnsNameResolverBuilder ndots(int ndots) {
this.ndots = ndots;
return this;
}
private DnsCache newCache() {
return new DefaultDnsCache(intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE), intValue(negativeTtl, 0));
}
private AuthoritativeDnsServerCache newAuthoritativeDnsServerCache() {
return new DefaultAuthoritativeDnsServerCache(
intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE),
// Let us use the sane ordering as DnsNameResolver will be used when returning
// nameservers from the cache.
new NameServerComparator(DnsNameResolver.preferredAddressType(resolvedAddressTypes).addressType()));
}
private DnsCnameCache newCnameCache() {
return new DefaultDnsCnameCache(
intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE));
}
/**
* Set if domain / host names should be decoded to unicode when received.
* See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
*
* @param decodeIdn if should get decoded
* @return {@code this}
*/
public DnsNameResolverBuilder decodeIdn(boolean decodeIdn) {
this.decodeIdn = decodeIdn;
return this;
}
/**
* Set the maximum size of the cache that is used to consolidate lookups for different hostnames when in-flight.
* This means if multiple lookups are done for the same hostname and still in-flight only one actual query will
* be made and the result will be cascaded to the others.
*
* @param maxNumConsolidation the maximum lookups to consolidate (different hostnames), or {@code 0} if
* no consolidation should be performed.
* @return {@code this}
*/
public DnsNameResolverBuilder consolidateCacheSize(int maxNumConsolidation) {
this.maxNumConsolidation = ObjectUtil.checkPositiveOrZero(maxNumConsolidation, "maxNumConsolidation");
return this;
}
/**
* Returns a new {@link DnsNameResolver} instance.
*
* @return a {@link DnsNameResolver}
*/
public DnsNameResolver build() {
if (eventLoop == null) {
throw new IllegalStateException("eventLoop should be specified to build a DnsNameResolver.");
}
if (resolveCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
logger.debug("resolveCache and TTLs are mutually exclusive. TTLs are ignored.");
}
if (cnameCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
logger.debug("cnameCache and TTLs are mutually exclusive. TTLs are ignored.");
}
if (authoritativeDnsServerCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
logger.debug("authoritativeDnsServerCache and TTLs are mutually exclusive. TTLs are ignored.");
}
DnsCache resolveCache = this.resolveCache != null ? this.resolveCache : newCache();
DnsCnameCache cnameCache = this.cnameCache != null ? this.cnameCache : newCnameCache();
AuthoritativeDnsServerCache authoritativeDnsServerCache = this.authoritativeDnsServerCache != null ?
this.authoritativeDnsServerCache : newAuthoritativeDnsServerCache();
return new DnsNameResolver(
eventLoop,
channelFactory,
socketChannelFactory,
retryOnTimeout,
resolveCache,
cnameCache,
authoritativeDnsServerCache,
localAddress,
dnsQueryLifecycleObserverFactory,
queryTimeoutMillis,
resolvedAddressTypes,
recursionDesired,
maxQueriesPerResolve,
traceEnabled,
maxPayloadSize,
optResourceEnabled,
hostsFileEntriesResolver,
dnsServerAddressStreamProvider,
searchDomains,
ndots,
decodeIdn,
completeOncePreferredResolved,
maxNumConsolidation);
}
/**
* Creates a copy of this {@link DnsNameResolverBuilder}
*
* @return {@link DnsNameResolverBuilder}
*/
public DnsNameResolverBuilder copy() {
DnsNameResolverBuilder copiedBuilder = new DnsNameResolverBuilder();
if (eventLoop != null) {
copiedBuilder.eventLoop(eventLoop);
}
if (channelFactory != null) {
copiedBuilder.channelFactory(channelFactory);
}
copiedBuilder.socketChannelFactory(socketChannelFactory, retryOnTimeout);
if (resolveCache != null) {
copiedBuilder.resolveCache(resolveCache);
}
if (cnameCache != null) {
copiedBuilder.cnameCache(cnameCache);
}
if (maxTtl != null && minTtl != null) {
copiedBuilder.ttl(minTtl, maxTtl);
}
if (negativeTtl != null) {
copiedBuilder.negativeTtl(negativeTtl);
}
if (authoritativeDnsServerCache != null) {
copiedBuilder.authoritativeDnsServerCache(authoritativeDnsServerCache);
}
if (dnsQueryLifecycleObserverFactory != null) {
copiedBuilder.dnsQueryLifecycleObserverFactory(dnsQueryLifecycleObserverFactory);
}
copiedBuilder.queryTimeoutMillis(queryTimeoutMillis);
copiedBuilder.resolvedAddressTypes(resolvedAddressTypes);
copiedBuilder.recursionDesired(recursionDesired);
copiedBuilder.maxQueriesPerResolve(maxQueriesPerResolve);
copiedBuilder.traceEnabled(traceEnabled);
copiedBuilder.maxPayloadSize(maxPayloadSize);
copiedBuilder.optResourceEnabled(optResourceEnabled);
copiedBuilder.hostsFileEntriesResolver(hostsFileEntriesResolver);
if (dnsServerAddressStreamProvider != null) {
copiedBuilder.nameServerProvider(dnsServerAddressStreamProvider);
}
if (searchDomains != null) {
copiedBuilder.searchDomains(Arrays.asList(searchDomains));
}
copiedBuilder.ndots(ndots);
copiedBuilder.decodeIdn(decodeIdn);
copiedBuilder.completeOncePreferredResolved(completeOncePreferredResolved);
copiedBuilder.localAddress(localAddress);
copiedBuilder.consolidateCacheSize(maxNumConsolidation);
return copiedBuilder;
}
}

View file

@ -0,0 +1,75 @@
/*
* 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:
*
* https://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.resolver.dns;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.ObjectUtil;
import java.net.InetSocketAddress;
/**
* A {@link RuntimeException} raised when {@link DnsNameResolver} failed to perform a successful query.
*/
public class DnsNameResolverException extends RuntimeException {
private static final long serialVersionUID = -8826717909627131850L;
private final InetSocketAddress remoteAddress;
private final DnsQuestion question;
public DnsNameResolverException(InetSocketAddress remoteAddress, DnsQuestion question, String message) {
super(message);
this.remoteAddress = validateRemoteAddress(remoteAddress);
this.question = validateQuestion(question);
}
public DnsNameResolverException(
InetSocketAddress remoteAddress, DnsQuestion question, String message, Throwable cause) {
super(message, cause);
this.remoteAddress = validateRemoteAddress(remoteAddress);
this.question = validateQuestion(question);
}
private static InetSocketAddress validateRemoteAddress(InetSocketAddress remoteAddress) {
return ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
}
private static DnsQuestion validateQuestion(DnsQuestion question) {
return ObjectUtil.checkNotNull(question, "question");
}
/**
* Returns the {@link InetSocketAddress} of the DNS query that has failed.
*/
public InetSocketAddress remoteAddress() {
return remoteAddress;
}
/**
* Returns the {@link DnsQuestion} of the DNS query that has failed.
*/
public DnsQuestion question() {
return question;
}
// Suppress a warning since the method doesn't need synchronization
@Override
public Throwable fillInStackTrace() {
setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
return this;
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
import io.netty.handler.codec.dns.DnsQuestion;
import java.net.InetSocketAddress;
/**
* A {@link DnsNameResolverException} raised when {@link DnsNameResolver} failed to perform a successful query because
* of an timeout. In this case you may want to retry the operation.
*/
public final class DnsNameResolverTimeoutException extends DnsNameResolverException {
private static final long serialVersionUID = -8826717969627131854L;
public DnsNameResolverTimeoutException(
InetSocketAddress remoteAddress, DnsQuestion question, String message) {
super(remoteAddress, question, message);
}
}

View file

@ -0,0 +1,598 @@
/*
* 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:
*
* https://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.resolver.dns;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.dns.AbstractDnsOptPseudoRrRecord;
import io.netty.handler.codec.dns.DnsQuery;
import io.netty.handler.codec.dns.DnsQuestion;
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.DnsSection;
import io.netty.handler.codec.dns.TcpDnsQueryEncoder;
import io.netty.handler.codec.dns.TcpDnsResponseDecoder;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.ThrowableUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
abstract class DnsQueryContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsQueryContext.class);
private static final long ID_REUSE_ON_TIMEOUT_DELAY_MILLIS;
static {
ID_REUSE_ON_TIMEOUT_DELAY_MILLIS =
SystemPropertyUtil.getLong("io.netty.resolver.dns.idReuseOnTimeoutDelayMillis", 10000);
logger.debug("-Dio.netty.resolver.dns.idReuseOnTimeoutDelayMillis: {}", ID_REUSE_ON_TIMEOUT_DELAY_MILLIS);
}
private static final TcpDnsQueryEncoder TCP_ENCODER = new TcpDnsQueryEncoder();
private final Future<? extends Channel> channelReadyFuture;
private final Channel channel;
private final InetSocketAddress nameServerAddr;
private final DnsQueryContextManager queryContextManager;
private final Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> promise;
private final DnsQuestion question;
private final DnsRecord[] additionals;
private final DnsRecord optResource;
private final boolean recursionDesired;
private final Bootstrap socketBootstrap;
private final boolean retryWithTcpOnTimeout;
private final long queryTimeoutMillis;
private volatile Future<?> timeoutFuture;
private int id = Integer.MIN_VALUE;
DnsQueryContext(Channel channel,
Future<? extends Channel> channelReadyFuture,
InetSocketAddress nameServerAddr,
DnsQueryContextManager queryContextManager,
int maxPayLoadSize,
boolean recursionDesired,
long queryTimeoutMillis,
DnsQuestion question,
DnsRecord[] additionals,
Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> promise,
Bootstrap socketBootstrap,
boolean retryWithTcpOnTimeout) {
this.channel = checkNotNull(channel, "channel");
this.queryContextManager = checkNotNull(queryContextManager, "queryContextManager");
this.channelReadyFuture = checkNotNull(channelReadyFuture, "channelReadyFuture");
this.nameServerAddr = checkNotNull(nameServerAddr, "nameServerAddr");
this.question = checkNotNull(question, "question");
this.additionals = checkNotNull(additionals, "additionals");
this.promise = checkNotNull(promise, "promise");
this.recursionDesired = recursionDesired;
this.queryTimeoutMillis = queryTimeoutMillis;
this.socketBootstrap = socketBootstrap;
this.retryWithTcpOnTimeout = retryWithTcpOnTimeout;
if (maxPayLoadSize > 0 &&
// Only add the extra OPT record if there is not already one. This is required as only one is allowed
// as per RFC:
// - https://datatracker.ietf.org/doc/html/rfc6891#section-6.1.1
!hasOptRecord(additionals)) {
optResource = new AbstractDnsOptPseudoRrRecord(maxPayLoadSize, 0, 0) {
// We may want to remove this in the future and let the user just specify the opt record in the query.
};
} else {
optResource = null;
}
}
private static boolean hasOptRecord(DnsRecord[] additionals) {
if (additionals != null && additionals.length > 0) {
for (DnsRecord additional: additionals) {
if (additional.type() == DnsRecordType.OPT) {
return true;
}
}
}
return false;
}
/**
* Returns {@code true} if the query was completed already.
*
* @return {@code true} if done.
*/
final boolean isDone() {
return promise.isDone();
}
/**
* Returns the {@link DnsQuestion} that will be written as part of the {@link DnsQuery}.
*
* @return the question.
*/
final DnsQuestion question() {
return question;
}
/**
* Creates and returns a new {@link DnsQuery}.
*
* @param id the transaction id to use.
* @param nameServerAddr the nameserver to which the query will be send.
* @return the new query.
*/
protected abstract DnsQuery newQuery(int id, InetSocketAddress nameServerAddr);
/**
* Returns the protocol that is used for the query.
*
* @return the protocol.
*/
protected abstract String protocol();
/**
* Write the query and return the {@link ChannelFuture} that is completed once the write completes.
*
* @param flush {@code true} if {@link Channel#flush()} should be called as well.
* @return the {@link ChannelFuture} that is notified once once the write completes.
*/
final ChannelFuture writeQuery(boolean flush) {
assert id == Integer.MIN_VALUE : this.getClass().getSimpleName() +
".writeQuery(...) can only be executed once.";
if ((id = queryContextManager.add(nameServerAddr, this)) == -1) {
// We did exhaust the id space, fail the query
IllegalStateException e = new IllegalStateException("query ID space exhausted: " + question());
finishFailure("failed to send a query via " + protocol(), e, false);
return channel.newFailedFuture(e);
}
// Ensure we remove the id from the QueryContextManager once the query completes.
promise.addListener(new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
@Override
public void operationComplete(Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> future) {
// Cancel the timeout task.
Future<?> timeoutFuture = DnsQueryContext.this.timeoutFuture;
if (timeoutFuture != null) {
DnsQueryContext.this.timeoutFuture = null;
timeoutFuture.cancel(false);
}
Throwable cause = future.cause();
if (cause instanceof DnsNameResolverTimeoutException || cause instanceof CancellationException) {
// This query was failed due a timeout or cancellation. Let's delay the removal of the id to reduce
// the risk of reusing the same id again while the remote nameserver might send the response after
// the timeout.
channel.eventLoop().schedule(new Runnable() {
@Override
public void run() {
removeFromContextManager(nameServerAddr);
}
}, ID_REUSE_ON_TIMEOUT_DELAY_MILLIS, TimeUnit.MILLISECONDS);
} else {
// Remove the id from the manager as soon as the query completes. This may be because of success,
// failure or cancellation
removeFromContextManager(nameServerAddr);
}
}
});
final DnsQuestion question = question();
final DnsQuery query = newQuery(id, nameServerAddr);
query.setRecursionDesired(recursionDesired);
query.addRecord(DnsSection.QUESTION, question);
for (DnsRecord record: additionals) {
query.addRecord(DnsSection.ADDITIONAL, record);
}
if (optResource != null) {
query.addRecord(DnsSection.ADDITIONAL, optResource);
}
if (logger.isDebugEnabled()) {
logger.debug("{} WRITE: {}, [{}: {}], {}",
channel, protocol(), id, nameServerAddr, question);
}
return sendQuery(query, flush);
}
private void removeFromContextManager(InetSocketAddress nameServerAddr) {
DnsQueryContext self = queryContextManager.remove(nameServerAddr, id);
assert self == this : "Removed DnsQueryContext is not the correct instance";
}
private ChannelFuture sendQuery(final DnsQuery query, final boolean flush) {
final ChannelPromise writePromise = channel.newPromise();
if (channelReadyFuture.isSuccess()) {
writeQuery(query, flush, writePromise);
} else {
Throwable cause = channelReadyFuture.cause();
if (cause != null) {
// the promise failed before so we should also fail this query.
failQuery(query, cause, writePromise);
} else {
// The promise is not complete yet, let's delay the query.
channelReadyFuture.addListener(new GenericFutureListener<Future<? super Channel>>() {
@Override
public void operationComplete(Future<? super Channel> future) {
if (future.isSuccess()) {
// If the query is done in a late fashion (as the channel was not ready yet) we always flush
// to ensure we did not race with a previous flush() that was done when the Channel was not
// ready yet.
writeQuery(query, true, writePromise);
} else {
Throwable cause = future.cause();
failQuery(query, cause, writePromise);
}
}
});
}
}
return writePromise;
}
private void failQuery(DnsQuery query, Throwable cause, ChannelPromise writePromise) {
try {
promise.tryFailure(cause);
writePromise.tryFailure(cause);
} finally {
ReferenceCountUtil.release(query);
}
}
private void writeQuery(final DnsQuery query,
final boolean flush, ChannelPromise promise) {
final ChannelFuture writeFuture = flush ? channel.writeAndFlush(query, promise) :
channel.write(query, promise);
if (writeFuture.isDone()) {
onQueryWriteCompletion(queryTimeoutMillis, writeFuture);
} else {
writeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
onQueryWriteCompletion(queryTimeoutMillis, writeFuture);
}
});
}
}
private void onQueryWriteCompletion(final long queryTimeoutMillis,
ChannelFuture writeFuture) {
if (!writeFuture.isSuccess()) {
finishFailure("failed to send a query '" + id + "' via " + protocol(), writeFuture.cause(), false);
return;
}
// Schedule a query timeout task if necessary.
if (queryTimeoutMillis > 0) {
timeoutFuture = channel.eventLoop().schedule(new Runnable() {
@Override
public void run() {
if (promise.isDone()) {
// Received a response before the query times out.
return;
}
finishFailure("query '" + id + "' via " + protocol() + " timed out after " +
queryTimeoutMillis + " milliseconds", null, true);
}
}, queryTimeoutMillis, TimeUnit.MILLISECONDS);
}
}
/**
* Notifies the original {@link Promise} that the response for the query was received.
* This method takes ownership of passed {@link AddressedEnvelope}.
*/
void finishSuccess(AddressedEnvelope<? extends DnsResponse, InetSocketAddress> envelope, boolean truncated) {
// Check if the response was not truncated or if a fallback to TCP is possible.
if (!truncated || !retryWithTcp(envelope)) {
final DnsResponse res = envelope.content();
if (res.count(DnsSection.QUESTION) != 1) {
logger.warn("{} Received a DNS response with invalid number of questions. Expected: 1, found: {}",
channel, envelope);
} else if (!question().equals(res.recordAt(DnsSection.QUESTION))) {
logger.warn("{} Received a mismatching DNS response. Expected: [{}], found: {}",
channel, question(), envelope);
} else if (trySuccess(envelope)) {
return; // Ownership transferred, don't release
}
envelope.release();
}
}
@SuppressWarnings("unchecked")
private boolean trySuccess(AddressedEnvelope<? extends DnsResponse, InetSocketAddress> envelope) {
return promise.trySuccess((AddressedEnvelope<DnsResponse, InetSocketAddress>) envelope);
}
/**
* Notifies the original {@link Promise} that the query completes because of an failure.
*/
final boolean finishFailure(String message, Throwable cause, boolean timeout) {
if (promise.isDone()) {
return false;
}
final DnsQuestion question = question();
final StringBuilder buf = new StringBuilder(message.length() + 128);
buf.append('[')
.append(id)
.append(": ")
.append(nameServerAddr)
.append("] ")
.append(question)
.append(' ')
.append(message)
.append(" (no stack trace available)");
final DnsNameResolverException e;
if (timeout) {
// This was caused by a timeout so use DnsNameResolverTimeoutException to allow the user to
// handle it special (like retry the query).
e = new DnsNameResolverTimeoutException(nameServerAddr, question, buf.toString());
if (retryWithTcpOnTimeout && retryWithTcp(e)) {
// We did successfully retry with TCP.
return false;
}
} else {
e = new DnsNameResolverException(nameServerAddr, question, buf.toString(), cause);
}
return promise.tryFailure(e);
}
/**
* Retry the original query with TCP if possible.
*
* @param originalResult the result of the original {@link DnsQueryContext}.
* @return {@code true} if retry via TCP is supported and so the ownership of
* {@code originalResult} was transferred, {@code false} otherwise.
*/
private boolean retryWithTcp(final Object originalResult) {
if (socketBootstrap == null) {
return false;
}
socketBootstrap.connect(nameServerAddr).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
if (!future.isSuccess()) {
logger.debug("{} Unable to fallback to TCP [{}: {}]",
future.channel(), id, nameServerAddr, future.cause());
// TCP fallback failed, just use the truncated response or error.
finishOriginal(originalResult, future);
return;
}
final Channel tcpCh = future.channel();
Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> promise =
tcpCh.eventLoop().newPromise();
final TcpDnsQueryContext tcpCtx = new TcpDnsQueryContext(tcpCh, channelReadyFuture,
(InetSocketAddress) tcpCh.remoteAddress(), queryContextManager, 0,
recursionDesired, queryTimeoutMillis, question(), additionals, promise);
tcpCh.pipeline().addLast(TCP_ENCODER);
tcpCh.pipeline().addLast(new TcpDnsResponseDecoder());
tcpCh.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
Channel tcpCh = ctx.channel();
DnsResponse response = (DnsResponse) msg;
int queryId = response.id();
if (logger.isDebugEnabled()) {
logger.debug("{} RECEIVED: TCP [{}: {}], {}", tcpCh, queryId,
tcpCh.remoteAddress(), response);
}
DnsQueryContext foundCtx = queryContextManager.get(nameServerAddr, queryId);
if (foundCtx != null && foundCtx.isDone()) {
logger.debug("{} Received a DNS response for a query that was timed out or cancelled " +
": TCP [{}: {}]", tcpCh, queryId, nameServerAddr);
response.release();
} else if (foundCtx == tcpCtx) {
tcpCtx.finishSuccess(new AddressedEnvelopeAdapter(
(InetSocketAddress) ctx.channel().remoteAddress(),
(InetSocketAddress) ctx.channel().localAddress(),
response), false);
} else {
response.release();
tcpCtx.finishFailure("Received TCP DNS response with unexpected ID", null, false);
if (logger.isDebugEnabled()) {
logger.debug("{} Received a DNS response with an unexpected ID: TCP [{}: {}]",
tcpCh, queryId, tcpCh.remoteAddress());
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (tcpCtx.finishFailure(
"TCP fallback error", cause, false) && logger.isDebugEnabled()) {
logger.debug("{} Error during processing response: TCP [{}: {}]",
ctx.channel(), id,
ctx.channel().remoteAddress(), cause);
}
}
});
promise.addListener(
new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
@Override
public void operationComplete(
Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> future) {
if (future.isSuccess()) {
finishSuccess(future.getNow(), false);
// Release the original result.
ReferenceCountUtil.release(originalResult);
} else {
// TCP fallback failed, just use the truncated response or error.
finishOriginal(originalResult, future);
}
tcpCh.close();
}
});
tcpCtx.writeQuery(true);
}
});
return true;
}
@SuppressWarnings("unchecked")
private void finishOriginal(Object originalResult, Future<?> future) {
if (originalResult instanceof Throwable) {
Throwable error = (Throwable) originalResult;
ThrowableUtil.addSuppressed(error, future.cause());
promise.tryFailure(error);
} else {
finishSuccess((AddressedEnvelope<? extends DnsResponse, InetSocketAddress>) originalResult, false);
}
}
private static final class AddressedEnvelopeAdapter implements AddressedEnvelope<DnsResponse, InetSocketAddress> {
private final InetSocketAddress sender;
private final InetSocketAddress recipient;
private final DnsResponse response;
AddressedEnvelopeAdapter(InetSocketAddress sender, InetSocketAddress recipient, DnsResponse response) {
this.sender = sender;
this.recipient = recipient;
this.response = response;
}
@Override
public DnsResponse content() {
return response;
}
@Override
public InetSocketAddress sender() {
return sender;
}
@Override
public InetSocketAddress recipient() {
return recipient;
}
@Override
public AddressedEnvelope<DnsResponse, InetSocketAddress> retain() {
response.retain();
return this;
}
@Override
public AddressedEnvelope<DnsResponse, InetSocketAddress> retain(int increment) {
response.retain(increment);
return this;
}
@Override
public AddressedEnvelope<DnsResponse, InetSocketAddress> touch() {
response.touch();
return this;
}
@Override
public AddressedEnvelope<DnsResponse, InetSocketAddress> touch(Object hint) {
response.touch(hint);
return this;
}
@Override
public int refCnt() {
return response.refCnt();
}
@Override
public boolean release() {
return response.release();
}
@Override
public boolean release(int decrement) {
return response.release(decrement);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
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 response.equals(obj);
}
@Override
public int hashCode() {
int hashCode = response.hashCode();
if (sender() != null) {
hashCode = hashCode * 31 + sender().hashCode();
}
if (recipient() != null) {
hashCode = hashCode * 31 + recipient().hashCode();
}
return hashCode;
}
}
}

View file

@ -0,0 +1,178 @@
/*
* 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:
*
* https://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.resolver.dns;
import io.netty.util.NetUtil;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
final class DnsQueryContextManager {
/**
* A map whose key is the DNS server address and value is the map of the DNS query ID and its corresponding
* {@link DnsQueryContext}.
*/
private final Map<InetSocketAddress, DnsQueryContextMap> map =
new HashMap<InetSocketAddress, DnsQueryContextMap>();
/**
* Add {@link DnsQueryContext} to the context manager and return the ID that should be used for the query.
* This method will return {@code -1} if an ID could not be generated and the context was not stored.
*
* @param nameServerAddr The {@link InetSocketAddress} of the nameserver to query.
* @param qCtx The {@link {@link DnsQueryContext} to store.
* @return the ID that should be used or {@code -1} if none could be generated.
*/
int add(InetSocketAddress nameServerAddr, DnsQueryContext qCtx) {
final DnsQueryContextMap contexts = getOrCreateContextMap(nameServerAddr);
return contexts.add(qCtx);
}
/**
* Return the {@link DnsQueryContext} for the given {@link InetSocketAddress} and id or {@code null} if
* none could be found.
*
* @param nameServerAddr The {@link InetSocketAddress} of the nameserver.
* @param id The id that identifies the {@link DnsQueryContext} and was used for the query.
* @return The context or {@code null} if none could be found.
*/
DnsQueryContext get(InetSocketAddress nameServerAddr, int id) {
final DnsQueryContextMap contexts = getContextMap(nameServerAddr);
if (contexts == null) {
return null;
}
return contexts.get(id);
}
/**
* Remove the {@link DnsQueryContext} for the given {@link InetSocketAddress} and id or {@code null} if
* none could be found.
*
* @param nameServerAddr The {@link InetSocketAddress} of the nameserver.
* @param id The id that identifies the {@link DnsQueryContext} and was used for the query.
* @return The context or {@code null} if none could be removed.
*/
DnsQueryContext remove(InetSocketAddress nameServerAddr, int id) {
final DnsQueryContextMap contexts = getContextMap(nameServerAddr);
if (contexts == null) {
return null;
}
return contexts.remove(id);
}
private DnsQueryContextMap getContextMap(InetSocketAddress nameServerAddr) {
synchronized (map) {
return map.get(nameServerAddr);
}
}
private DnsQueryContextMap getOrCreateContextMap(InetSocketAddress nameServerAddr) {
synchronized (map) {
final DnsQueryContextMap contexts = map.get(nameServerAddr);
if (contexts != null) {
return contexts;
}
final DnsQueryContextMap newContexts = new DnsQueryContextMap();
final InetAddress a = nameServerAddr.getAddress();
final int port = nameServerAddr.getPort();
DnsQueryContextMap old = map.put(nameServerAddr, newContexts);
// Assert that we didn't replace an existing mapping.
assert old == null : "DnsQueryContextMap already exists for " + nameServerAddr;
InetSocketAddress extraAddress = null;
if (a instanceof Inet4Address) {
// Also add the mapping for the IPv4-compatible IPv6 address.
final Inet4Address a4 = (Inet4Address) a;
if (a4.isLoopbackAddress()) {
extraAddress = new InetSocketAddress(NetUtil.LOCALHOST6, port);
} else {
extraAddress = new InetSocketAddress(toCompactAddress(a4), port);
}
} else if (a instanceof Inet6Address) {
// Also add the mapping for the IPv4 address if this IPv6 address is compatible.
final Inet6Address a6 = (Inet6Address) a;
if (a6.isLoopbackAddress()) {
extraAddress = new InetSocketAddress(NetUtil.LOCALHOST4, port);
} else if (a6.isIPv4CompatibleAddress()) {
extraAddress = new InetSocketAddress(toIPv4Address(a6), port);
}
}
if (extraAddress != null) {
old = map.put(extraAddress, newContexts);
// Assert that we didn't replace an existing mapping.
assert old == null : "DnsQueryContextMap already exists for " + extraAddress;
}
return newContexts;
}
}
private static Inet6Address toCompactAddress(Inet4Address a4) {
byte[] b4 = a4.getAddress();
byte[] b6 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, b4[0], b4[1], b4[2], b4[3] };
try {
return (Inet6Address) InetAddress.getByAddress(b6);
} catch (UnknownHostException e) {
throw new Error(e);
}
}
private static Inet4Address toIPv4Address(Inet6Address a6) {
assert a6.isIPv4CompatibleAddress();
byte[] b6 = a6.getAddress();
byte[] b4 = { b6[12], b6[13], b6[14], b6[15] };
try {
return (Inet4Address) InetAddress.getByAddress(b4);
} catch (UnknownHostException e) {
throw new Error(e);
}
}
private static final class DnsQueryContextMap {
private final DnsQueryIdSpace idSpace = new DnsQueryIdSpace();
// We increment on every usage so start with -1, this will ensure we start with 0 as first id.
private final IntObjectMap<DnsQueryContext> map = new IntObjectHashMap<DnsQueryContext>();
synchronized int add(DnsQueryContext ctx) {
int id = idSpace.nextId();
DnsQueryContext oldCtx = map.put(id, ctx);
assert oldCtx == null;
return id;
}
synchronized DnsQueryContext get(int id) {
return map.get(id);
}
synchronized DnsQueryContext remove(int id) {
idSpace.pushId(id);
return map.remove(id);
}
}
}

View file

@ -0,0 +1,207 @@
/*
* Copyright 2024 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:
*
* https://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.resolver.dns;
import io.netty.util.internal.MathUtil;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.ThreadLocalRandom;
import java.util.Random;
/**
* Special data-structure that will allow to retrieve the next query id to use, while still guarantee some sort
* of randomness.
* The query id will be between 0 (inclusive) and 65535 (inclusive) as defined by the RFC.
*/
final class DnsQueryIdSpace {
private static final int MAX_ID = 65535;
private static final int BUCKETS = 4;
// Each bucket is 16kb of size.
private static final int BUCKET_SIZE = (MAX_ID + 1) / BUCKETS;
// If there are other buckets left that have at least 500 usable ids we will drop an unused bucket.
private static final int BUCKET_DROP_THRESHOLD = 500;
private final DnsQueryIdRange[] idBuckets = new DnsQueryIdRange[BUCKETS];
DnsQueryIdSpace() {
assert idBuckets.length == MathUtil.findNextPositivePowerOfTwo(idBuckets.length);
// We start with 1 bucket.
idBuckets[0] = newBucket(0);
}
private static DnsQueryIdRange newBucket(int idBucketsIdx) {
return new DnsQueryIdRange(BUCKET_SIZE, idBucketsIdx * BUCKET_SIZE);
}
/**
* Returns the next ID to use for a query or {@code -1} if there is none left to use.
*
* @return next id to use.
*/
int nextId() {
int freeIdx = -1;
for (int bucketIdx = 0; bucketIdx < idBuckets.length; bucketIdx++) {
DnsQueryIdRange bucket = idBuckets[bucketIdx];
if (bucket != null) {
int id = bucket.nextId();
if (id != -1) {
return id;
}
} else if (freeIdx == -1 ||
// Let's make it somehow random which free slot is used.
ThreadLocalRandom.current().nextBoolean()) {
// We have a slot that we can use to create a new bucket if we need to.
freeIdx = bucketIdx;
}
}
if (freeIdx == -1) {
// No ids left and no slot left to create a new bucket.
return -1;
}
// We still have some slots free to store a new bucket. Let's do this now and use it to generate the next id.
DnsQueryIdRange bucket = newBucket(freeIdx);
idBuckets[freeIdx] = bucket;
int id = bucket.nextId();
assert id >= 0;
return id;
}
/**
* Push back the id, so it can be used again for the next query.
*
* @param id the id.
*/
void pushId(int id) {
int bucketIdx = id / BUCKET_SIZE;
if (bucketIdx >= idBuckets.length) {
throw new IllegalArgumentException("id too large: " + id);
}
DnsQueryIdRange bucket = idBuckets[bucketIdx];
assert bucket != null;
bucket.pushId(id);
if (bucket.usableIds() == bucket.maxUsableIds()) {
// All ids are usable in this bucket. Let's check if there are other buckets left that have still
// some space left and if so drop this bucket.
for (int idx = 0; idx < idBuckets.length; idx++) {
if (idx != bucketIdx) {
DnsQueryIdRange otherBucket = idBuckets[idx];
if (otherBucket != null && otherBucket.usableIds() > BUCKET_DROP_THRESHOLD) {
// Drop bucket on the floor to reduce memory usage, there is another bucket left we can
// use that still has enough ids to use.
idBuckets[bucketIdx] = null;
return;
}
}
}
}
}
/**
* Return how much more usable ids are left.
*
* @return the number of ids that are left for usage.
*/
int usableIds() {
int usableIds = 0;
for (DnsQueryIdRange bucket: idBuckets) {
// If there is nothing stored in the index yet we can assume the whole bucket is usable
usableIds += bucket == null ? BUCKET_SIZE : bucket.usableIds();
}
return usableIds;
}
/**
* Return the maximum number of ids that are supported.
*
* @return the maximum number of ids.
*/
int maxUsableIds() {
return BUCKET_SIZE * idBuckets.length;
}
/**
* Provides a query if from a range of possible ids.
*/
private static final class DnsQueryIdRange {
// Holds all possible ids which are stored as unsigned shorts
private final short[] ids;
private final int startId;
private int count;
DnsQueryIdRange(int bucketSize, int startId) {
this.ids = new short[bucketSize];
this.startId = startId;
for (int v = startId; v < bucketSize + startId; v++) {
pushId(v);
}
}
/**
* Returns the next ID to use for a query or {@code -1} if there is none left to use.
*
* @return next id to use.
*/
int nextId() {
assert count >= 0;
if (count == 0) {
return -1;
}
short id = ids[count - 1];
count--;
return id & 0xFFFF;
}
/**
* Push back the id, so it can be used again for the next query.
*
* @param id the id.
*/
void pushId(int id) {
if (count == ids.length) {
throw new IllegalStateException("overflow");
}
assert id <= startId + ids.length && id >= startId;
// pick a slot for our index, and whatever was in that slot before will get moved to the tail.
Random random = PlatformDependent.threadLocalRandom();
int insertionPosition = random.nextInt(count + 1);
ids[count] = ids[insertionPosition];
ids[insertionPosition] = (short) id;
count++;
}
/**
* Return how much more usable ids are left.
*
* @return the number of ids that are left for usage.
*/
int usableIds() {
return count;
}
/**
* Return the maximum number of ids that are supported.
*
* @return the maximum number of ids.
*/
int maxUsableIds() {
return ids.length;
}
}
}

View file

@ -0,0 +1,99 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsRecordType;
import io.netty.handler.codec.dns.DnsResponseCode;
import java.net.InetSocketAddress;
import java.util.List;
/**
* This interface provides visibility into individual DNS queries. The lifecycle of an objects is as follows:
* <ol>
* <li>Object creation</li>
* <li>{@link #queryCancelled(int)}</li>
* </ol>
* OR
* <ol>
* <li>Object creation</li>
* <li>{@link #queryWritten(InetSocketAddress, ChannelFuture)}</li>
* <li>{@link #queryRedirected(List)} or {@link #queryCNAMEd(DnsQuestion)} or
* {@link #queryNoAnswer(DnsResponseCode)} or {@link #queryCancelled(int)} or
* {@link #queryFailed(Throwable)} or {@link #querySucceed()}</li>
* </ol>
* <p>
* This interface can be used to track metrics for individual DNS servers. Methods which may lead to another DNS query
* return an object of type {@link DnsQueryLifecycleObserver}. Implementations may use this to build a query tree to
* understand the "sub queries" generated by a single query.
*/
public interface DnsQueryLifecycleObserver {
/**
* The query has been written.
* @param dnsServerAddress The DNS server address which the query was sent to.
* @param future The future which represents the status of the write operation for the DNS query.
*/
void queryWritten(InetSocketAddress dnsServerAddress, ChannelFuture future);
/**
* The query may have been written but it was cancelled at some point.
* @param queriesRemaining The number of queries remaining.
*/
void queryCancelled(int queriesRemaining);
/**
* The query has been redirected to another list of DNS servers.
* @param nameServers The name servers the query has been redirected to.
* @return An observer for the new query which we may issue.
*/
DnsQueryLifecycleObserver queryRedirected(List<InetSocketAddress> nameServers);
/**
* The query returned a CNAME which we may attempt to follow with a new query.
* <p>
* Note that multiple queries may be encountering a CNAME. For example a if both {@link DnsRecordType#AAAA} and
* {@link DnsRecordType#A} are supported we may query for both.
* @param cnameQuestion the question we would use if we issue a new query.
* @return An observer for the new query which we may issue.
*/
DnsQueryLifecycleObserver queryCNAMEd(DnsQuestion cnameQuestion);
/**
* The response to the query didn't provide the expected response code, but it didn't return
* {@link DnsResponseCode#NXDOMAIN} so we may try to query again.
* @param code the unexpected response code.
* @return An observer for the new query which we may issue.
*/
DnsQueryLifecycleObserver queryNoAnswer(DnsResponseCode code);
/**
* The following criteria are possible:
* <ul>
* <li>IO Error</li>
* <li>Server responded with an invalid DNS response</li>
* <li>Server responded with a valid DNS response, but it didn't progress the resolution</li>
* </ul>
* @param cause The cause which for the failure.
*/
void queryFailed(Throwable cause);
/**
* The query received the expected results.
*/
void querySucceed();
}

View file

@ -0,0 +1,30 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
import io.netty.handler.codec.dns.DnsQuestion;
/**
* Used to generate new instances of {@link DnsQueryLifecycleObserver}.
*/
public interface DnsQueryLifecycleObserverFactory {
/**
* Create a new instance of a {@link DnsQueryLifecycleObserver}. This will be called at the start of a new query.
* @param question The question being asked.
* @return a new instance of a {@link DnsQueryLifecycleObserver}.
*/
DnsQueryLifecycleObserver newDnsQueryLifecycleObserver(DnsQuestion question);
}

View file

@ -0,0 +1,95 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import java.net.UnknownHostException;
import java.util.List;
import io.netty.channel.Channel;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsRecord;
import io.netty.handler.codec.dns.DnsRecordType;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Promise;
final class DnsRecordResolveContext extends DnsResolveContext<DnsRecord> {
DnsRecordResolveContext(DnsNameResolver parent, Channel channel, Promise<?> originalPromise, DnsQuestion question,
DnsRecord[] additionals, DnsServerAddressStream nameServerAddrs, int allowedQueries) {
this(parent, channel, originalPromise, question.name(), question.dnsClass(),
new DnsRecordType[] { question.type() },
additionals, nameServerAddrs, allowedQueries);
}
private DnsRecordResolveContext(DnsNameResolver parent, Channel channel, Promise<?> originalPromise,
String hostname, int dnsClass, DnsRecordType[] expectedTypes,
DnsRecord[] additionals,
DnsServerAddressStream nameServerAddrs,
int allowedQueries) {
super(parent, channel, originalPromise, hostname, dnsClass, expectedTypes,
additionals, nameServerAddrs, allowedQueries);
}
@Override
DnsResolveContext<DnsRecord> newResolverContext(DnsNameResolver parent, Channel channel, Promise<?> originalPromise,
String hostname,
int dnsClass, DnsRecordType[] expectedTypes,
DnsRecord[] additionals,
DnsServerAddressStream nameServerAddrs,
int allowedQueries) {
return new DnsRecordResolveContext(parent, channel, originalPromise, hostname, dnsClass,
expectedTypes, additionals, nameServerAddrs, allowedQueries);
}
@Override
DnsRecord convertRecord(DnsRecord record, String hostname, DnsRecord[] additionals, EventLoop eventLoop) {
return ReferenceCountUtil.retain(record);
}
@Override
List<DnsRecord> filterResults(List<DnsRecord> unfiltered) {
return unfiltered;
}
@Override
boolean isCompleteEarly(DnsRecord resolved) {
return false;
}
@Override
boolean isDuplicateAllowed() {
return true;
}
@Override
void cache(String hostname, DnsRecord[] additionals, DnsRecord result, DnsRecord convertedResult) {
// Do not cache.
// XXX: When we implement cache, we would need to retain the reference count of the result record.
}
@Override
void cache(String hostname, DnsRecord[] additionals, UnknownHostException cause) {
// Do not cache.
// XXX: When we implement cache, we would need to retain the reference count of the result record.
}
@Override
DnsCnameCache cnameCache() {
// We don't use a cache here at all as we also don't cache if we end up using the DnsRecordResolverContext.
return NoopDnsCnameCache.INSTANCE;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,44 @@
/*
* 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:
*
* https://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.resolver.dns;
import java.net.InetSocketAddress;
/**
* An infinite stream of DNS server addresses.
*/
public interface DnsServerAddressStream {
/**
* Retrieves the next DNS server address from the stream.
*/
InetSocketAddress next();
/**
* Get the number of times {@link #next()} will return a distinct element before repeating or terminating.
* @return the number of times {@link #next()} will return a distinct element before repeating or terminating.
*/
int size();
/**
* Duplicate this object. The result of this should be able to be independently iterated over via {@link #next()}.
* <p>
* Note that clone() isn't used because it may make sense for some implementations to have the following
* relationship {@code x.duplicate() == x}.
* @return A duplicate of this object.
*/
DnsServerAddressStream duplicate();
}

View file

@ -0,0 +1,34 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
/**
* Provides an opportunity to override which {@link DnsServerAddressStream} is used to resolve a specific hostname.
* <p>
* For example this can be used to represent <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> and
* <a href="https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a>.
*/
public interface DnsServerAddressStreamProvider {
/**
* Ask this provider for the name servers to query for {@code hostname}.
* @param hostname The hostname for which to lookup the DNS server addressed to use.
* If this is the final {@link DnsServerAddressStreamProvider} to be queried then generally empty
* string or {@code '.'} correspond to the default {@link DnsServerAddressStream}.
* @return The {@link DnsServerAddressStream} which should be used to resolve {@code hostname}.
*/
DnsServerAddressStream nameServerAddressStream(String hostname);
}

View file

@ -0,0 +1,155 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* Utility methods related to {@link DnsServerAddressStreamProvider}.
*/
public final class DnsServerAddressStreamProviders {
private static final InternalLogger LOGGER =
InternalLoggerFactory.getInstance(DnsServerAddressStreamProviders.class);
private static final Constructor<? extends DnsServerAddressStreamProvider> STREAM_PROVIDER_CONSTRUCTOR;
private static final String MACOS_PROVIDER_CLASS_NAME =
"io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider";
static {
Constructor<? extends DnsServerAddressStreamProvider> constructor = null;
if (PlatformDependent.isOsx()) {
try {
// As MacOSDnsServerAddressStreamProvider is contained in another jar which depends on this jar
// we use reflection to use it if its on the classpath.
Object maybeProvider = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
return Class.forName(
MACOS_PROVIDER_CLASS_NAME,
true,
DnsServerAddressStreamProviders.class.getClassLoader());
} catch (Throwable cause) {
return cause;
}
}
});
if (maybeProvider instanceof Class) {
@SuppressWarnings("unchecked")
Class<? extends DnsServerAddressStreamProvider> providerClass =
(Class<? extends DnsServerAddressStreamProvider>) maybeProvider;
constructor = providerClass.getConstructor();
constructor.newInstance(); // ctor ensures availability
LOGGER.debug("{}: available", MACOS_PROVIDER_CLASS_NAME);
} else {
throw (Throwable) maybeProvider;
}
} catch (ClassNotFoundException cause) {
LOGGER.warn("Can not find {} in the classpath, fallback to system defaults. This may result in "
+ "incorrect DNS resolutions on MacOS. Check whether you have a dependency on "
+ "'io.netty:netty-resolver-dns-native-macos'", MACOS_PROVIDER_CLASS_NAME);
} catch (Throwable cause) {
if (LOGGER.isDebugEnabled()) {
LOGGER.error("Unable to load {}, fallback to system defaults. This may result in "
+ "incorrect DNS resolutions on MacOS. Check whether you have a dependency on "
+ "'io.netty:netty-resolver-dns-native-macos'", MACOS_PROVIDER_CLASS_NAME, cause);
} else {
LOGGER.error("Unable to load {}, fallback to system defaults. This may result in "
+ "incorrect DNS resolutions on MacOS. Check whether you have a dependency on "
+ "'io.netty:netty-resolver-dns-native-macos'. Use DEBUG level to see the full stack: {}",
MACOS_PROVIDER_CLASS_NAME,
cause.getCause() != null ? cause.getCause().toString() : cause.toString());
}
constructor = null;
}
}
STREAM_PROVIDER_CONSTRUCTOR = constructor;
}
private DnsServerAddressStreamProviders() {
}
/**
* A {@link DnsServerAddressStreamProvider} which inherits the DNS servers from your local host's configuration.
* <p>
* Note that only macOS and Linux are currently supported.
* @return A {@link DnsServerAddressStreamProvider} which inherits the DNS servers from your local host's
* configuration.
*/
public static DnsServerAddressStreamProvider platformDefault() {
if (STREAM_PROVIDER_CONSTRUCTOR != null) {
try {
return STREAM_PROVIDER_CONSTRUCTOR.newInstance();
} catch (IllegalAccessException e) {
// ignore
} catch (InstantiationException e) {
// ignore
} catch (InvocationTargetException e) {
// ignore
}
}
return unixDefault();
}
public static DnsServerAddressStreamProvider unixDefault() {
return DefaultProviderHolder.DEFAULT_DNS_SERVER_ADDRESS_STREAM_PROVIDER;
}
// We use a Holder class to only initialize DEFAULT_DNS_SERVER_ADDRESS_STREAM_PROVIDER if we really
// need it.
private static final class DefaultProviderHolder {
// We use 5 minutes which is the same as what OpenJDK is using in sun.net.dns.ResolverConfigurationImpl.
private static final long REFRESH_INTERVAL = TimeUnit.MINUTES.toNanos(5);
// TODO(scott): how is this done on Windows? This may require a JNI call to GetNetworkParams
// https://msdn.microsoft.com/en-us/library/aa365968(VS.85).aspx.
static final DnsServerAddressStreamProvider DEFAULT_DNS_SERVER_ADDRESS_STREAM_PROVIDER =
new DnsServerAddressStreamProvider() {
private volatile DnsServerAddressStreamProvider currentProvider = provider();
private final AtomicLong lastRefresh = new AtomicLong(System.nanoTime());
@Override
public DnsServerAddressStream nameServerAddressStream(String hostname) {
long last = lastRefresh.get();
DnsServerAddressStreamProvider current = currentProvider;
if (System.nanoTime() - last > REFRESH_INTERVAL) {
// This is slightly racy which means it will be possible still use the old configuration
// for a small amount of time, but that's ok.
if (lastRefresh.compareAndSet(last, System.nanoTime())) {
current = currentProvider = provider();
}
}
return current.nameServerAddressStream(hostname);
}
private DnsServerAddressStreamProvider provider() {
// If on windows just use the DefaultDnsServerAddressStreamProvider.INSTANCE as otherwise
// we will log some error which may be confusing.
return PlatformDependent.isWindows() ? DefaultDnsServerAddressStreamProvider.INSTANCE :
UnixResolverDnsServerAddressStreamProvider.parseSilently();
}
};
}
}

View file

@ -0,0 +1,209 @@
/*
* 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:
*
* https://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.resolver.dns;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkNonEmpty;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Provides an infinite sequence of DNS server addresses to {@link DnsNameResolver}.
*/
@SuppressWarnings("IteratorNextCanNotThrowNoSuchElementException")
public abstract class DnsServerAddresses {
/**
* @deprecated Use {@link DefaultDnsServerAddressStreamProvider#defaultAddressList()}.
* <p>
* Returns the list of the system DNS server addresses. If it failed to retrieve the list of the system DNS server
* addresses from the environment, it will return {@code "8.8.8.8"} and {@code "8.8.4.4"}, the addresses of the
* Google public DNS servers.
*/
@Deprecated
public static List<InetSocketAddress> defaultAddressList() {
return DefaultDnsServerAddressStreamProvider.defaultAddressList();
}
/**
* @deprecated Use {@link DefaultDnsServerAddressStreamProvider#defaultAddresses()}.
* <p>
* Returns the {@link DnsServerAddresses} that yields the system DNS server addresses sequentially. If it failed to
* retrieve the list of the system DNS server addresses from the environment, it will use {@code "8.8.8.8"} and
* {@code "8.8.4.4"}, the addresses of the Google public DNS servers.
* <p>
* This method has the same effect with the following code:
* <pre>
* DnsServerAddresses.sequential(DnsServerAddresses.defaultAddressList());
* </pre>
* </p>
*/
@Deprecated
public static DnsServerAddresses defaultAddresses() {
return DefaultDnsServerAddressStreamProvider.defaultAddresses();
}
/**
* Returns the {@link DnsServerAddresses} that yields the specified {@code addresses} sequentially. Once the
* last address is yielded, it will start again from the first address.
*/
public static DnsServerAddresses sequential(Iterable<? extends InetSocketAddress> addresses) {
return sequential0(sanitize(addresses));
}
/**
* Returns the {@link DnsServerAddresses} that yields the specified {@code addresses} sequentially. Once the
* last address is yielded, it will start again from the first address.
*/
public static DnsServerAddresses sequential(InetSocketAddress... addresses) {
return sequential0(sanitize(addresses));
}
private static DnsServerAddresses sequential0(final List<InetSocketAddress> addresses) {
if (addresses.size() == 1) {
return singleton(addresses.get(0));
}
return new DefaultDnsServerAddresses("sequential", addresses) {
@Override
public DnsServerAddressStream stream() {
return new SequentialDnsServerAddressStream(addresses, 0);
}
};
}
/**
* Returns the {@link DnsServerAddresses} that yields the specified {@code address} in a shuffled order. Once all
* addresses are yielded, the addresses are shuffled again.
*/
public static DnsServerAddresses shuffled(Iterable<? extends InetSocketAddress> addresses) {
return shuffled0(sanitize(addresses));
}
/**
* Returns the {@link DnsServerAddresses} that yields the specified {@code addresses} in a shuffled order. Once all
* addresses are yielded, the addresses are shuffled again.
*/
public static DnsServerAddresses shuffled(InetSocketAddress... addresses) {
return shuffled0(sanitize(addresses));
}
private static DnsServerAddresses shuffled0(List<InetSocketAddress> addresses) {
if (addresses.size() == 1) {
return singleton(addresses.get(0));
}
return new DefaultDnsServerAddresses("shuffled", addresses) {
@Override
public DnsServerAddressStream stream() {
return new ShuffledDnsServerAddressStream(addresses);
}
};
}
/**
* Returns the {@link DnsServerAddresses} that yields the specified {@code addresses} in a rotational sequential
* order. It is similar to {@link #sequential(Iterable)}, but each {@link DnsServerAddressStream} starts from
* a different starting point. For example, the first {@link #stream()} will start from the first address, the
* second one will start from the second address, and so on.
*/
public static DnsServerAddresses rotational(Iterable<? extends InetSocketAddress> addresses) {
return rotational0(sanitize(addresses));
}
/**
* Returns the {@link DnsServerAddresses} that yields the specified {@code addresses} in a rotational sequential
* order. It is similar to {@link #sequential(Iterable)}, but each {@link DnsServerAddressStream} starts from
* a different starting point. For example, the first {@link #stream()} will start from the first address, the
* second one will start from the second address, and so on.
*/
public static DnsServerAddresses rotational(InetSocketAddress... addresses) {
return rotational0(sanitize(addresses));
}
private static DnsServerAddresses rotational0(List<InetSocketAddress> addresses) {
if (addresses.size() == 1) {
return singleton(addresses.get(0));
}
return new RotationalDnsServerAddresses(addresses);
}
/**
* Returns the {@link DnsServerAddresses} that yields only a single {@code address}.
*/
public static DnsServerAddresses singleton(final InetSocketAddress address) {
checkNotNull(address, "address");
if (address.isUnresolved()) {
throw new IllegalArgumentException("cannot use an unresolved DNS server address: " + address);
}
return new SingletonDnsServerAddresses(address);
}
private static List<InetSocketAddress> sanitize(Iterable<? extends InetSocketAddress> addresses) {
checkNotNull(addresses, "addresses");
final List<InetSocketAddress> list;
if (addresses instanceof Collection) {
list = new ArrayList<InetSocketAddress>(((Collection<?>) addresses).size());
} else {
list = new ArrayList<InetSocketAddress>(4);
}
for (InetSocketAddress a : addresses) {
if (a == null) {
break;
}
if (a.isUnresolved()) {
throw new IllegalArgumentException("cannot use an unresolved DNS server address: " + a);
}
list.add(a);
}
return checkNonEmpty(list, "list");
}
private static List<InetSocketAddress> sanitize(InetSocketAddress[] addresses) {
checkNotNull(addresses, "addresses");
List<InetSocketAddress> list = new ArrayList<InetSocketAddress>(addresses.length);
for (InetSocketAddress a: addresses) {
if (a == null) {
break;
}
if (a.isUnresolved()) {
throw new IllegalArgumentException("cannot use an unresolved DNS server address: " + a);
}
list.add(a);
}
if (list.isEmpty()) {
return DefaultDnsServerAddressStreamProvider.defaultAddressList();
}
return list;
}
/**
* Starts a new infinite stream of DNS server addresses. This method is invoked by {@link DnsNameResolver} on every
* uncached {@link DnsNameResolver#resolve(String)}or {@link DnsNameResolver#resolveAll(String)}.
*/
public abstract DnsServerAddressStream stream();
}

View file

@ -0,0 +1,47 @@
/*
* Copyright 2022 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:
*
* https://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.resolver.dns;
import java.net.InetSocketAddress;
/**
* An infinite stream of DNS server addresses, that requests feedback to be returned to it.
*
* If query is successful timing information is provided, else a failure notification is given.
*/
public interface DnsServerResponseFeedbackAddressStream extends DnsServerAddressStream {
/**
* A way to provide success feedback to {@link DnsServerAddressStream} so that {@link #next()} can be tuned
* to return the best performing DNS server address
*
* NOTE: This is called regardless of the RCode returned by the DNS server
*
* @param address The address returned by {@link #next()} that feedback needs to be applied to
* @param queryResponseTimeNanos The response time of a query against the given DNS server
*/
void feedbackSuccess(InetSocketAddress address, long queryResponseTimeNanos);
/**
* A way to provide failure feedback to {@link DnsServerAddressStream} so that {@link #next()} cab be tuned
* to return the best performing DNS server address
*
* @param address The address returned by {@link #next()} that feedback needs to be applied to
* @param failureCause The reason the DNS query failed, can be used to penalize failures differently
* @param queryResponseTimeNanos The response time of a query against the given DNS server
*/
void feedbackFailure(InetSocketAddress address, Throwable failureCause, long queryResponseTimeNanos);
}

View file

@ -0,0 +1,131 @@
/*
* Copyright 2016 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:
*
* https://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.resolver.dns;
import io.netty.resolver.NameResolver;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.StringUtil;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
// FIXME(trustin): Find a better name and move it to the 'resolver' module.
final class InflightNameResolver<T> implements NameResolver<T> {
private final EventExecutor executor;
private final NameResolver<T> delegate;
private final ConcurrentMap<String, Promise<T>> resolvesInProgress;
private final ConcurrentMap<String, Promise<List<T>>> resolveAllsInProgress;
InflightNameResolver(EventExecutor executor, NameResolver<T> delegate,
ConcurrentMap<String, Promise<T>> resolvesInProgress,
ConcurrentMap<String, Promise<List<T>>> resolveAllsInProgress) {
this.executor = checkNotNull(executor, "executor");
this.delegate = checkNotNull(delegate, "delegate");
this.resolvesInProgress = checkNotNull(resolvesInProgress, "resolvesInProgress");
this.resolveAllsInProgress = checkNotNull(resolveAllsInProgress, "resolveAllsInProgress");
}
@Override
public Future<T> resolve(String inetHost) {
return resolve(inetHost, executor.<T>newPromise());
}
@Override
public Future<List<T>> resolveAll(String inetHost) {
return resolveAll(inetHost, executor.<List<T>>newPromise());
}
@Override
public void close() {
delegate.close();
}
@Override
public Promise<T> resolve(String inetHost, Promise<T> promise) {
return resolve(resolvesInProgress, inetHost, promise, false);
}
@Override
public Promise<List<T>> resolveAll(String inetHost, Promise<List<T>> promise) {
return resolve(resolveAllsInProgress, inetHost, promise, true);
}
private <U> Promise<U> resolve(
final ConcurrentMap<String, Promise<U>> resolveMap,
final String inetHost, final Promise<U> promise, boolean resolveAll) {
final Promise<U> earlyPromise = resolveMap.putIfAbsent(inetHost, promise);
if (earlyPromise != null) {
// Name resolution for the specified inetHost is in progress already.
if (earlyPromise.isDone()) {
transferResult(earlyPromise, promise);
} else {
earlyPromise.addListener(new FutureListener<U>() {
@Override
public void operationComplete(Future<U> f) throws Exception {
transferResult(f, promise);
}
});
}
} else {
try {
if (resolveAll) {
@SuppressWarnings("unchecked")
final Promise<List<T>> castPromise = (Promise<List<T>>) promise; // U is List<T>
delegate.resolveAll(inetHost, castPromise);
} else {
@SuppressWarnings("unchecked")
final Promise<T> castPromise = (Promise<T>) promise; // U is T
delegate.resolve(inetHost, castPromise);
}
} finally {
if (promise.isDone()) {
resolveMap.remove(inetHost);
} else {
promise.addListener(new FutureListener<U>() {
@Override
public void operationComplete(Future<U> f) throws Exception {
resolveMap.remove(inetHost);
}
});
}
}
}
return promise;
}
private static <T> void transferResult(Future<T> src, Promise<T> dst) {
if (src.isSuccess()) {
dst.trySuccess(src.getNow());
} else {
dst.tryFailure(src.cause());
}
}
@Override
public String toString() {
return StringUtil.simpleClassName(this) + '(' + delegate + ')';
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright 2020 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:
*
* https://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.resolver.dns;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.logging.LogLevel;
import io.netty.util.internal.logging.InternalLogLevel;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* A {@link DnsQueryLifecycleObserverFactory} that enables detailed logging in the {@link DnsNameResolver}.
* <p>
* When {@linkplain DnsNameResolverBuilder#dnsQueryLifecycleObserverFactory(DnsQueryLifecycleObserverFactory)
* configured on the resolver}, detailed trace information will be generated so that it is easier to understand the
* cause of resolution failure.
*/
public final class LoggingDnsQueryLifeCycleObserverFactory implements DnsQueryLifecycleObserverFactory {
private static final InternalLogger DEFAULT_LOGGER =
InternalLoggerFactory.getInstance(LoggingDnsQueryLifeCycleObserverFactory.class);
private final InternalLogger logger;
private final InternalLogLevel level;
/**
* Create {@link DnsQueryLifecycleObserver} instances that log events at the default {@link LogLevel#DEBUG} level.
*/
public LoggingDnsQueryLifeCycleObserverFactory() {
this(LogLevel.DEBUG);
}
/**
* Create {@link DnsQueryLifecycleObserver} instances that log events at the given log level.
* @param level The log level to use for logging resolver events.
*/
public LoggingDnsQueryLifeCycleObserverFactory(LogLevel level) {
this.level = checkAndConvertLevel(level);
logger = DEFAULT_LOGGER;
}
/**
* Create {@link DnsQueryLifecycleObserver} instances that log events to a logger with the given class context,
* at the given log level.
* @param classContext The class context for the logger to use.
* @param level The log level to use for logging resolver events.
*/
public LoggingDnsQueryLifeCycleObserverFactory(Class<?> classContext, LogLevel level) {
this.level = checkAndConvertLevel(level);
logger = InternalLoggerFactory.getInstance(checkNotNull(classContext, "classContext"));
}
/**
* Create {@link DnsQueryLifecycleObserver} instances that log events to a logger with the given name context,
* at the given log level.
* @param name The name for the logger to use.
* @param level The log level to use for logging resolver events.
*/
public LoggingDnsQueryLifeCycleObserverFactory(String name, LogLevel level) {
this.level = checkAndConvertLevel(level);
logger = InternalLoggerFactory.getInstance(checkNotNull(name, "name"));
}
private static InternalLogLevel checkAndConvertLevel(LogLevel level) {
return checkNotNull(level, "level").toInternalLevel();
}
@Override
public DnsQueryLifecycleObserver newDnsQueryLifecycleObserver(DnsQuestion question) {
return new LoggingDnsQueryLifecycleObserver(question, logger, level);
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.util.internal.logging.InternalLogLevel;
import io.netty.util.internal.logging.InternalLogger;
import java.net.InetSocketAddress;
import java.util.List;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
final class LoggingDnsQueryLifecycleObserver implements DnsQueryLifecycleObserver {
private final InternalLogger logger;
private final InternalLogLevel level;
private final DnsQuestion question;
private InetSocketAddress dnsServerAddress;
LoggingDnsQueryLifecycleObserver(DnsQuestion question, InternalLogger logger, InternalLogLevel level) {
this.question = checkNotNull(question, "question");
this.logger = checkNotNull(logger, "logger");
this.level = checkNotNull(level, "level");
}
@Override
public void queryWritten(InetSocketAddress dnsServerAddress, ChannelFuture future) {
this.dnsServerAddress = dnsServerAddress;
}
@Override
public void queryCancelled(int queriesRemaining) {
if (dnsServerAddress != null) {
logger.log(level, "from {} : {} cancelled with {} queries remaining", dnsServerAddress, question,
queriesRemaining);
} else {
logger.log(level, "{} query never written and cancelled with {} queries remaining", question,
queriesRemaining);
}
}
@Override
public DnsQueryLifecycleObserver queryRedirected(List<InetSocketAddress> nameServers) {
logger.log(level, "from {} : {} redirected", dnsServerAddress, question);
return this;
}
@Override
public DnsQueryLifecycleObserver queryCNAMEd(DnsQuestion cnameQuestion) {
logger.log(level, "from {} : {} CNAME question {}", dnsServerAddress, question, cnameQuestion);
return this;
}
@Override
public DnsQueryLifecycleObserver queryNoAnswer(DnsResponseCode code) {
logger.log(level, "from {} : {} no answer {}", dnsServerAddress, question, code);
return this;
}
@Override
public void queryFailed(Throwable cause) {
if (dnsServerAddress != null) {
logger.log(level, "from {} : {} failure", dnsServerAddress, question, cause);
} else {
logger.log(level, "{} query never written and failed", question, cause);
}
}
@Override
public void querySucceed() {
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
import java.util.List;
/**
* A {@link DnsServerAddressStreamProvider} which iterates through a collection of
* {@link DnsServerAddressStreamProvider} until the first non-{@code null} result is found.
*/
public final class MultiDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider {
private final DnsServerAddressStreamProvider[] providers;
/**
* Create a new instance.
* @param providers The providers to use for DNS resolution. They will be queried in order.
*/
public MultiDnsServerAddressStreamProvider(List<DnsServerAddressStreamProvider> providers) {
this.providers = providers.toArray(new DnsServerAddressStreamProvider[0]);
}
/**
* Create a new instance.
* @param providers The providers to use for DNS resolution. They will be queried in order.
*/
public MultiDnsServerAddressStreamProvider(DnsServerAddressStreamProvider... providers) {
this.providers = providers.clone();
}
@Override
public DnsServerAddressStream nameServerAddressStream(String hostname) {
for (DnsServerAddressStreamProvider provider : providers) {
DnsServerAddressStream stream = provider.nameServerAddressStream(hostname);
if (stream != null) {
return stream;
}
}
return null;
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import io.netty.util.internal.ObjectUtil;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Comparator;
import java.util.List;
/**
* Special {@link Comparator} implementation to sort the nameservers to use when follow redirects.
*
* This implementation follows all the semantics listed in the
* <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html">Comparator apidocs</a>
* with the limitation that {@link InetSocketAddress#equals(Object)} will not result in the same return value as
* {@link #compare(InetSocketAddress, InetSocketAddress)}. This is completely fine as this should only be used
* to sort {@link List}s.
*/
public final class NameServerComparator implements Comparator<InetSocketAddress>, Serializable {
private static final long serialVersionUID = 8372151874317596185L;
private final Class<? extends InetAddress> preferredAddressType;
public NameServerComparator(Class<? extends InetAddress> preferredAddressType) {
this.preferredAddressType = ObjectUtil.checkNotNull(preferredAddressType, "preferredAddressType");
}
@Override
public int compare(InetSocketAddress addr1, InetSocketAddress addr2) {
if (addr1.equals(addr2)) {
return 0;
}
if (!addr1.isUnresolved() && !addr2.isUnresolved()) {
if (addr1.getAddress().getClass() == addr2.getAddress().getClass()) {
return 0;
}
return preferredAddressType.isAssignableFrom(addr1.getAddress().getClass()) ? -1 : 1;
}
if (addr1.isUnresolved() && addr2.isUnresolved()) {
return 0;
}
return addr1.isUnresolved() ? 1 : -1;
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import io.netty.channel.EventLoop;
import java.net.InetSocketAddress;
/**
* A noop {@link AuthoritativeDnsServerCache} that actually never caches anything.
*/
public final class NoopAuthoritativeDnsServerCache implements AuthoritativeDnsServerCache {
public static final NoopAuthoritativeDnsServerCache INSTANCE = new NoopAuthoritativeDnsServerCache();
private NoopAuthoritativeDnsServerCache() { }
@Override
public DnsServerAddressStream get(String hostname) {
return null;
}
@Override
public void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop) {
// NOOP
}
@Override
public void clear() {
// NOOP
}
@Override
public boolean clear(String hostname) {
return false;
}
}

View file

@ -0,0 +1,90 @@
/*
* Copyright 2016 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:
*
* https://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.resolver.dns;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.dns.DnsRecord;
import java.net.InetAddress;
import java.util.Collections;
import java.util.List;
/**
* A noop DNS cache that actually never caches anything.
*/
public final class NoopDnsCache implements DnsCache {
public static final NoopDnsCache INSTANCE = new NoopDnsCache();
/**
* Private singleton constructor.
*/
private NoopDnsCache() {
}
@Override
public void clear() {
}
@Override
public boolean clear(String hostname) {
return false;
}
@Override
public List<? extends DnsCacheEntry> get(String hostname, DnsRecord[] additionals) {
return Collections.emptyList();
}
@Override
public DnsCacheEntry cache(String hostname, DnsRecord[] additional,
InetAddress address, long originalTtl, EventLoop loop) {
return new NoopDnsCacheEntry(address);
}
@Override
public DnsCacheEntry cache(String hostname, DnsRecord[] additional, Throwable cause, EventLoop loop) {
return null;
}
@Override
public String toString() {
return NoopDnsCache.class.getSimpleName();
}
private static final class NoopDnsCacheEntry implements DnsCacheEntry {
private final InetAddress address;
NoopDnsCacheEntry(InetAddress address) {
this.address = address;
}
@Override
public InetAddress address() {
return address;
}
@Override
public Throwable cause() {
return null;
}
@Override
public String toString() {
return address.toString();
}
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright 2018 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:
*
* https://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.resolver.dns;
import io.netty.channel.EventLoop;
public final class NoopDnsCnameCache implements DnsCnameCache {
public static final NoopDnsCnameCache INSTANCE = new NoopDnsCnameCache();
private NoopDnsCnameCache() { }
@Override
public String get(String hostname) {
return null;
}
@Override
public void cache(String hostname, String cname, long originalTtl, EventLoop loop) {
// NOOP
}
@Override
public void clear() {
// NOOP
}
@Override
public boolean clear(String hostname) {
return false;
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsResponseCode;
import java.net.InetSocketAddress;
import java.util.List;
final class NoopDnsQueryLifecycleObserver implements DnsQueryLifecycleObserver {
static final NoopDnsQueryLifecycleObserver INSTANCE = new NoopDnsQueryLifecycleObserver();
private NoopDnsQueryLifecycleObserver() {
}
@Override
public void queryWritten(InetSocketAddress dnsServerAddress, ChannelFuture future) {
}
@Override
public void queryCancelled(int queriesRemaining) {
}
@Override
public DnsQueryLifecycleObserver queryRedirected(List<InetSocketAddress> nameServers) {
return this;
}
@Override
public DnsQueryLifecycleObserver queryCNAMEd(DnsQuestion cnameQuestion) {
return this;
}
@Override
public DnsQueryLifecycleObserver queryNoAnswer(DnsResponseCode code) {
return this;
}
@Override
public void queryFailed(Throwable cause) {
}
@Override
public void querySucceed() {
}
}

View file

@ -0,0 +1,30 @@
/*
* Copyright 2017 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:
*
* https://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.resolver.dns;
import io.netty.handler.codec.dns.DnsQuestion;
public final class NoopDnsQueryLifecycleObserverFactory implements DnsQueryLifecycleObserverFactory {
public static final NoopDnsQueryLifecycleObserverFactory INSTANCE = new NoopDnsQueryLifecycleObserverFactory();
private NoopDnsQueryLifecycleObserverFactory() {
}
@Override
public DnsQueryLifecycleObserver newDnsQueryLifecycleObserver(DnsQuestion question) {
return NoopDnsQueryLifecycleObserver.INSTANCE;
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2019 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:
*
* https://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.resolver.dns;
import io.netty.channel.socket.InternetProtocolFamily;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Comparator;
final class PreferredAddressTypeComparator implements Comparator<InetAddress> {
private static final PreferredAddressTypeComparator IPv4 = new PreferredAddressTypeComparator(Inet4Address.class);
private static final PreferredAddressTypeComparator IPv6 = new PreferredAddressTypeComparator(Inet6Address.class);
static PreferredAddressTypeComparator comparator(InternetProtocolFamily family) {
switch (family) {
case IPv4:
return IPv4;
case IPv6:
return IPv6;
default:
throw new IllegalArgumentException();
}
}
private final Class<? extends InetAddress> preferredAddressType;
private PreferredAddressTypeComparator(Class<? extends InetAddress> preferredAddressType) {
this.preferredAddressType = preferredAddressType;
}
@Override
public int compare(InetAddress o1, InetAddress o2) {
if (o1.getClass() == o2.getClass()) {
return 0;
}
return preferredAddressType.isAssignableFrom(o1.getClass()) ? -1 : 1;
}
}

View file

@ -0,0 +1,48 @@
/*
* 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:
*
* https://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.resolver.dns;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
final class RotationalDnsServerAddresses extends DefaultDnsServerAddresses {
private static final AtomicIntegerFieldUpdater<RotationalDnsServerAddresses> startIdxUpdater =
AtomicIntegerFieldUpdater.newUpdater(RotationalDnsServerAddresses.class, "startIdx");
@SuppressWarnings("UnusedDeclaration")
private volatile int startIdx;
RotationalDnsServerAddresses(List<InetSocketAddress> addresses) {
super("rotational", addresses);
}
@Override
public DnsServerAddressStream stream() {
for (;;) {
int curStartIdx = startIdx;
int nextStartIdx = curStartIdx + 1;
if (nextStartIdx >= addresses.size()) {
nextStartIdx = 0;
}
if (startIdxUpdater.compareAndSet(this, curStartIdx, nextStartIdx)) {
return new SequentialDnsServerAddressStream(addresses, curStartIdx);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more