add dns resolver code
This commit is contained in:
parent
ada04aef58
commit
fc565512cc
131 changed files with 21681 additions and 0 deletions
3
netty-handler-codec-dns/build.gradle
Normal file
3
netty-handler-codec-dns/build.gradle
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
dependencies {
|
||||||
|
api project(':netty-handler')
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(')');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
8
netty-handler-codec-dns/src/main/java/module-info.java
Normal file
8
netty-handler-codec-dns/src/main/java/module-info.java
Normal 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;
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
|
@ -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) { };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
5
netty-resolver-dns/build.gradle
Normal file
5
netty-resolver-dns/build.gradle
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
dependencies {
|
||||||
|
api project(':netty-handler-codec-dns')
|
||||||
|
testImplementation testLibs.apache.ds.dns
|
||||||
|
testImplementation testLibs.assertj
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() { }
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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 + ')';
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
Loading…
Reference in a new issue