2014-09-19 15:36:32 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2014 The Netty Project
|
|
|
|
*
|
|
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
|
|
* with the License. You may obtain a copy of the License at:
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
* License for the specific language governing permissions and limitations
|
|
|
|
* under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package io.netty.resolver.dns;
|
|
|
|
|
|
|
|
import io.netty.buffer.ByteBuf;
|
2015-03-16 07:46:14 +01:00
|
|
|
import io.netty.buffer.ByteBufHolder;
|
|
|
|
import io.netty.channel.AddressedEnvelope;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.channel.socket.InternetProtocolFamily;
|
2016-06-13 21:16:22 +02:00
|
|
|
import io.netty.handler.codec.CorruptedFrameException;
|
2015-03-16 07:46:14 +01:00
|
|
|
import io.netty.handler.codec.dns.DefaultDnsQuestion;
|
|
|
|
import io.netty.handler.codec.dns.DefaultDnsRecordDecoder;
|
2015-07-12 12:34:05 +02:00
|
|
|
import io.netty.handler.codec.dns.DnsResponseCode;
|
2015-03-16 07:46:14 +01:00
|
|
|
import io.netty.handler.codec.dns.DnsSection;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.handler.codec.dns.DnsQuestion;
|
2015-03-16 07:46:14 +01:00
|
|
|
import io.netty.handler.codec.dns.DnsRawRecord;
|
|
|
|
import io.netty.handler.codec.dns.DnsRecord;
|
|
|
|
import io.netty.handler.codec.dns.DnsRecordType;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.handler.codec.dns.DnsResponse;
|
|
|
|
import io.netty.util.ReferenceCountUtil;
|
|
|
|
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.net.Inet4Address;
|
|
|
|
import java.net.Inet6Address;
|
|
|
|
import java.net.InetAddress;
|
|
|
|
import java.net.InetSocketAddress;
|
|
|
|
import java.net.UnknownHostException;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.IdentityHashMap;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Locale;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Set;
|
|
|
|
|
2015-07-12 12:34:05 +02:00
|
|
|
abstract class DnsNameResolverContext<T> {
|
2014-09-19 15:36:32 +02:00
|
|
|
|
|
|
|
private static final int INADDRSZ4 = 4;
|
|
|
|
private static final int INADDRSZ6 = 16;
|
|
|
|
|
2015-03-16 07:46:14 +01:00
|
|
|
private static final FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>> RELEASE_RESPONSE =
|
|
|
|
new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
|
|
|
|
@Override
|
|
|
|
public void operationComplete(Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> future) {
|
|
|
|
if (future.isSuccess()) {
|
|
|
|
future.getNow().release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2014-09-19 15:36:32 +02:00
|
|
|
|
|
|
|
private final DnsNameResolver parent;
|
2015-08-19 04:51:15 +02:00
|
|
|
private final DnsServerAddressStream nameServerAddrs;
|
2014-09-19 15:36:32 +02:00
|
|
|
private final String hostname;
|
2016-07-31 03:05:23 +02:00
|
|
|
protected String pristineHostname;
|
2016-01-07 17:49:15 +01:00
|
|
|
private final DnsCache resolveCache;
|
2015-07-12 12:34:05 +02:00
|
|
|
private final boolean traceEnabled;
|
2014-09-19 15:36:32 +02:00
|
|
|
private final int maxAllowedQueries;
|
|
|
|
private final InternetProtocolFamily[] resolveAddressTypes;
|
2016-07-29 10:30:08 +02:00
|
|
|
private final DnsRecord[] additionals;
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-03-16 07:46:14 +01:00
|
|
|
private final Set<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> queriesInProgress =
|
|
|
|
Collections.newSetFromMap(
|
|
|
|
new IdentityHashMap<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>, Boolean>());
|
|
|
|
|
2015-07-12 12:34:05 +02:00
|
|
|
private List<DnsCacheEntry> resolvedEntries;
|
2014-09-19 15:36:32 +02:00
|
|
|
private StringBuilder trace;
|
|
|
|
private int allowedQueries;
|
|
|
|
private boolean triedCNAME;
|
|
|
|
|
2016-01-07 17:49:15 +01:00
|
|
|
protected DnsNameResolverContext(DnsNameResolver parent,
|
|
|
|
String hostname,
|
2016-07-29 10:30:08 +02:00
|
|
|
DnsRecord[] additionals,
|
2016-01-07 17:49:15 +01:00
|
|
|
DnsCache resolveCache) {
|
2014-09-19 15:36:32 +02:00
|
|
|
this.parent = parent;
|
|
|
|
this.hostname = hostname;
|
2016-07-29 10:30:08 +02:00
|
|
|
this.additionals = additionals;
|
2016-01-07 17:49:15 +01:00
|
|
|
this.resolveCache = resolveCache;
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-08-19 04:51:15 +02:00
|
|
|
nameServerAddrs = parent.nameServerAddresses.stream();
|
2014-09-19 15:36:32 +02:00
|
|
|
maxAllowedQueries = parent.maxQueriesPerResolve();
|
|
|
|
resolveAddressTypes = parent.resolveAddressTypesUnsafe();
|
2015-07-12 12:34:05 +02:00
|
|
|
traceEnabled = parent.isTraceEnabled();
|
2014-09-19 15:36:32 +02:00
|
|
|
allowedQueries = maxAllowedQueries;
|
|
|
|
}
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
void resolve(Promise<T> promise) {
|
|
|
|
boolean directSearch = parent.searchDomains().length == 0 || StringUtil.endsWith(hostname, '.');
|
|
|
|
if (directSearch) {
|
|
|
|
internalResolve(promise);
|
|
|
|
} else {
|
|
|
|
final Promise<T> original = promise;
|
|
|
|
promise = parent.executor().newPromise();
|
|
|
|
promise.addListener(new FutureListener<T>() {
|
|
|
|
int count;
|
|
|
|
@Override
|
|
|
|
public void operationComplete(Future<T> future) throws Exception {
|
|
|
|
if (future.isSuccess()) {
|
|
|
|
original.trySuccess(future.getNow());
|
|
|
|
} else if (count < parent.searchDomains().length) {
|
|
|
|
String searchDomain = parent.searchDomains()[count++];
|
|
|
|
Promise<T> nextPromise = parent.executor().newPromise();
|
2016-07-29 10:30:08 +02:00
|
|
|
String nextHostname = hostname + '.' + searchDomain;
|
2016-06-30 23:12:11 +02:00
|
|
|
DnsNameResolverContext<T> nextContext = newResolverContext(parent,
|
2016-07-29 10:30:08 +02:00
|
|
|
nextHostname, additionals, resolveCache);
|
2016-07-31 03:05:23 +02:00
|
|
|
nextContext.pristineHostname = hostname;
|
2016-06-30 23:12:11 +02:00
|
|
|
nextContext.internalResolve(nextPromise);
|
|
|
|
nextPromise.addListener(this);
|
|
|
|
} else {
|
|
|
|
original.tryFailure(future.cause());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2016-07-23 09:47:43 +02:00
|
|
|
if (parent.ndots() == 0) {
|
|
|
|
internalResolve(promise);
|
|
|
|
} else {
|
|
|
|
int dots = 0;
|
|
|
|
for (int idx = hostname.length() - 1; idx >= 0; idx--) {
|
|
|
|
if (hostname.charAt(idx) == '.' && ++dots >= parent.ndots()) {
|
|
|
|
internalResolve(promise);
|
|
|
|
return;
|
|
|
|
}
|
2016-06-30 23:12:11 +02:00
|
|
|
}
|
2016-07-23 09:47:43 +02:00
|
|
|
promise.tryFailure(new UnknownHostException(hostname));
|
2016-06-30 23:12:11 +02:00
|
|
|
}
|
|
|
|
}
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
private void internalResolve(Promise<T> promise) {
|
2015-12-07 09:06:57 +01:00
|
|
|
InetSocketAddress nameServerAddrToTry = nameServerAddrs.next();
|
2014-09-19 15:36:32 +02:00
|
|
|
for (InternetProtocolFamily f: resolveAddressTypes) {
|
2015-03-16 07:46:14 +01:00
|
|
|
final DnsRecordType type;
|
2014-09-19 15:36:32 +02:00
|
|
|
switch (f) {
|
|
|
|
case IPv4:
|
2015-03-16 07:46:14 +01:00
|
|
|
type = DnsRecordType.A;
|
2014-09-19 15:36:32 +02:00
|
|
|
break;
|
|
|
|
case IPv6:
|
2015-03-16 07:46:14 +01:00
|
|
|
type = DnsRecordType.AAAA;
|
2014-09-19 15:36:32 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
query(nameServerAddrToTry, new DefaultDnsQuestion(hostname, type), promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
private void query(InetSocketAddress nameServerAddr, final DnsQuestion question, final Promise<T> promise) {
|
2014-09-19 15:36:32 +02:00
|
|
|
if (allowedQueries == 0 || promise.isCancelled()) {
|
2016-06-30 23:12:11 +02:00
|
|
|
tryToFinishResolve(promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
allowedQueries --;
|
|
|
|
|
2016-07-29 10:30:08 +02:00
|
|
|
final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = parent.query0(
|
|
|
|
nameServerAddr, question, additionals,
|
|
|
|
parent.ch.eventLoop().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise());
|
2014-09-19 15:36:32 +02:00
|
|
|
queriesInProgress.add(f);
|
|
|
|
|
2015-03-16 07:46:14 +01:00
|
|
|
f.addListener(new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
|
2014-09-19 15:36:32 +02:00
|
|
|
@Override
|
2015-03-16 07:46:14 +01:00
|
|
|
public void operationComplete(Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> future) {
|
2014-09-19 15:36:32 +02:00
|
|
|
queriesInProgress.remove(future);
|
|
|
|
|
2015-10-30 04:53:05 +01:00
|
|
|
if (promise.isDone() || future.isCancelled()) {
|
2014-09-19 15:36:32 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (future.isSuccess()) {
|
2016-06-30 23:12:11 +02:00
|
|
|
onResponse(question, future.getNow(), promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
} else {
|
2015-07-12 12:34:05 +02:00
|
|
|
// Server did not respond or I/O error occurred; try again.
|
|
|
|
if (traceEnabled) {
|
|
|
|
addTrace(future.cause());
|
|
|
|
}
|
2016-06-30 23:12:11 +02:00
|
|
|
query(nameServerAddrs.next(), question, promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
} finally {
|
2016-06-30 23:12:11 +02:00
|
|
|
tryToFinishResolve(promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
void onResponse(final DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
|
|
|
|
Promise<T> promise) {
|
2014-09-19 15:36:32 +02:00
|
|
|
try {
|
2015-07-12 12:34:05 +02:00
|
|
|
final DnsResponse res = envelope.content();
|
|
|
|
final DnsResponseCode code = res.code();
|
|
|
|
if (code == DnsResponseCode.NOERROR) {
|
|
|
|
final DnsRecordType type = question.type();
|
|
|
|
if (type == DnsRecordType.A || type == DnsRecordType.AAAA) {
|
2016-06-30 23:12:11 +02:00
|
|
|
onResponseAorAAAA(type, question, envelope, promise);
|
2015-07-12 12:34:05 +02:00
|
|
|
} else if (type == DnsRecordType.CNAME) {
|
2016-06-30 23:12:11 +02:00
|
|
|
onResponseCNAME(question, envelope, promise);
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (traceEnabled) {
|
|
|
|
addTrace(envelope.sender(),
|
|
|
|
"response code: " + code + " with " + res.count(DnsSection.ANSWER) + " answer(s) and " +
|
|
|
|
res.count(DnsSection.AUTHORITY) + " authority resource(s)");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retry with the next server if the server did not tell us that the domain does not exist.
|
|
|
|
if (code != DnsResponseCode.NXDOMAIN) {
|
2016-06-30 23:12:11 +02:00
|
|
|
query(nameServerAddrs.next(), question, promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
} finally {
|
2015-07-12 12:34:05 +02:00
|
|
|
ReferenceCountUtil.safeRelease(envelope);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-16 07:46:14 +01:00
|
|
|
private void onResponseAorAAAA(
|
2016-06-30 23:12:11 +02:00
|
|
|
DnsRecordType qType, DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
|
|
|
|
Promise<T> promise) {
|
2015-03-16 07:46:14 +01:00
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
// We often get a bunch of CNAMES as well when we asked for A/AAAA.
|
2015-03-16 07:46:14 +01:00
|
|
|
final DnsResponse response = envelope.content();
|
2014-09-19 15:36:32 +02:00
|
|
|
final Map<String, String> cnames = buildAliasMap(response);
|
2015-03-16 07:46:14 +01:00
|
|
|
final int answerCount = response.count(DnsSection.ANSWER);
|
2014-09-19 15:36:32 +02:00
|
|
|
|
|
|
|
boolean found = false;
|
2015-03-16 07:46:14 +01:00
|
|
|
for (int i = 0; i < answerCount; i ++) {
|
|
|
|
final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
|
|
|
|
final DnsRecordType type = r.type();
|
|
|
|
if (type != DnsRecordType.A && type != DnsRecordType.AAAA) {
|
2014-09-19 15:36:32 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
final String qName = question.name().toLowerCase(Locale.US);
|
|
|
|
final String rName = r.name().toLowerCase(Locale.US);
|
|
|
|
|
|
|
|
// Make sure the record is for the questioned domain.
|
|
|
|
if (!rName.equals(qName)) {
|
|
|
|
// Even if the record's name is not exactly same, it might be an alias defined in the CNAME records.
|
|
|
|
String resolved = qName;
|
|
|
|
do {
|
|
|
|
resolved = cnames.get(resolved);
|
|
|
|
if (rName.equals(resolved)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while (resolved != null);
|
|
|
|
|
|
|
|
if (resolved == null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-16 07:46:14 +01:00
|
|
|
if (!(r instanceof DnsRawRecord)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
final ByteBuf content = ((ByteBufHolder) r).content();
|
2014-09-19 15:36:32 +02:00
|
|
|
final int contentLen = content.readableBytes();
|
|
|
|
if (contentLen != INADDRSZ4 && contentLen != INADDRSZ6) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
final byte[] addrBytes = new byte[contentLen];
|
|
|
|
content.getBytes(content.readerIndex(), addrBytes);
|
|
|
|
|
2015-07-12 12:34:05 +02:00
|
|
|
final InetAddress resolved;
|
2014-09-19 15:36:32 +02:00
|
|
|
try {
|
2015-07-12 12:34:05 +02:00
|
|
|
resolved = InetAddress.getByAddress(hostname, addrBytes);
|
2014-09-19 15:36:32 +02:00
|
|
|
} catch (UnknownHostException e) {
|
|
|
|
// Should never reach here.
|
|
|
|
throw new Error(e);
|
|
|
|
}
|
2015-07-12 12:34:05 +02:00
|
|
|
|
|
|
|
if (resolvedEntries == null) {
|
|
|
|
resolvedEntries = new ArrayList<DnsCacheEntry>(8);
|
|
|
|
}
|
|
|
|
|
|
|
|
final DnsCacheEntry e = new DnsCacheEntry(hostname, resolved);
|
2016-07-29 10:30:08 +02:00
|
|
|
resolveCache.cache(hostname, additionals, resolved, r.timeToLive(), parent.ch.eventLoop());
|
2015-07-12 12:34:05 +02:00
|
|
|
resolvedEntries.add(e);
|
|
|
|
found = true;
|
|
|
|
|
|
|
|
// Note that we do not break from the loop here, so we decode/cache all A/AAAA records.
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (found) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-07-12 12:34:05 +02:00
|
|
|
if (traceEnabled) {
|
|
|
|
addTrace(envelope.sender(), "no matching " + qType + " record found");
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
|
|
|
|
// We aked for A/AAAA but we got only CNAME.
|
|
|
|
if (!cnames.isEmpty()) {
|
2016-06-30 23:12:11 +02:00
|
|
|
onResponseCNAME(question, envelope, cnames, false, promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
private void onResponseCNAME(DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
|
|
|
|
Promise<T> promise) {
|
|
|
|
onResponseCNAME(question, envelope, buildAliasMap(envelope.content()), true, promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private void onResponseCNAME(
|
2015-03-16 07:46:14 +01:00
|
|
|
DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> response,
|
2016-06-30 23:12:11 +02:00
|
|
|
Map<String, String> cnames, boolean trace, Promise<T> promise) {
|
2014-09-19 15:36:32 +02:00
|
|
|
|
|
|
|
// Resolve the host name in the question into the real host name.
|
|
|
|
final String name = question.name().toLowerCase(Locale.US);
|
|
|
|
String resolved = name;
|
|
|
|
boolean found = false;
|
2016-03-07 03:12:33 +01:00
|
|
|
while (!cnames.isEmpty()) { // Do not attempt to call Map.remove() when the Map is empty
|
|
|
|
// because it can be Collections.emptyMap()
|
|
|
|
// whose remove() throws a UnsupportedOperationException.
|
|
|
|
final String next = cnames.remove(resolved);
|
2014-09-19 15:36:32 +02:00
|
|
|
if (next != null) {
|
|
|
|
found = true;
|
|
|
|
resolved = next;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (found) {
|
2016-06-30 23:12:11 +02:00
|
|
|
followCname(response.sender(), name, resolved, promise);
|
2015-07-12 12:34:05 +02:00
|
|
|
} else if (trace && traceEnabled) {
|
2014-09-19 15:36:32 +02:00
|
|
|
addTrace(response.sender(), "no matching CNAME record found");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Map<String, String> buildAliasMap(DnsResponse response) {
|
2015-03-16 07:46:14 +01:00
|
|
|
final int answerCount = response.count(DnsSection.ANSWER);
|
2014-09-19 15:36:32 +02:00
|
|
|
Map<String, String> cnames = null;
|
2015-03-16 07:46:14 +01:00
|
|
|
for (int i = 0; i < answerCount; i ++) {
|
|
|
|
final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
|
|
|
|
final DnsRecordType type = r.type();
|
|
|
|
if (type != DnsRecordType.CNAME) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(r instanceof DnsRawRecord)) {
|
2014-09-19 15:36:32 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-03-16 07:46:14 +01:00
|
|
|
final ByteBuf recordContent = ((ByteBufHolder) r).content();
|
|
|
|
final String domainName = decodeDomainName(recordContent);
|
|
|
|
if (domainName == null) {
|
2014-09-19 15:36:32 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cnames == null) {
|
|
|
|
cnames = new HashMap<String, String>();
|
|
|
|
}
|
|
|
|
|
2015-03-16 07:46:14 +01:00
|
|
|
cnames.put(r.name().toLowerCase(Locale.US), domainName.toLowerCase(Locale.US));
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return cnames != null? cnames : Collections.<String, String>emptyMap();
|
|
|
|
}
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
void tryToFinishResolve(Promise<T> promise) {
|
2014-09-19 15:36:32 +02:00
|
|
|
if (!queriesInProgress.isEmpty()) {
|
|
|
|
// There are still some queries we did not receive responses for.
|
|
|
|
if (gotPreferredAddress()) {
|
|
|
|
// But it's OK to finish the resolution process if we got a resolved address of the preferred type.
|
2016-06-30 23:12:11 +02:00
|
|
|
finishResolve(promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// We did not get any resolved address of the preferred type, so we can't finish the resolution process.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// There are no queries left to try.
|
2015-07-12 12:34:05 +02:00
|
|
|
if (resolvedEntries == null) {
|
2014-09-19 15:36:32 +02:00
|
|
|
// .. and we could not find any A/AAAA records.
|
|
|
|
if (!triedCNAME) {
|
|
|
|
// As the last resort, try to query CNAME, just in case the name server has it.
|
|
|
|
triedCNAME = true;
|
2016-06-30 23:12:11 +02:00
|
|
|
query(nameServerAddrs.next(), new DefaultDnsQuestion(hostname, DnsRecordType.CNAME), promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have at least one resolved address or tried CNAME as the last resort..
|
2016-06-30 23:12:11 +02:00
|
|
|
finishResolve(promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private boolean gotPreferredAddress() {
|
2015-07-12 12:34:05 +02:00
|
|
|
if (resolvedEntries == null) {
|
2014-09-19 15:36:32 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-07-12 12:34:05 +02:00
|
|
|
final int size = resolvedEntries.size();
|
2014-09-19 15:36:32 +02:00
|
|
|
switch (resolveAddressTypes[0]) {
|
|
|
|
case IPv4:
|
|
|
|
for (int i = 0; i < size; i ++) {
|
2015-07-12 12:34:05 +02:00
|
|
|
if (resolvedEntries.get(i).address() instanceof Inet4Address) {
|
2014-09-19 15:36:32 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case IPv6:
|
|
|
|
for (int i = 0; i < size; i ++) {
|
2015-07-12 12:34:05 +02:00
|
|
|
if (resolvedEntries.get(i).address() instanceof Inet6Address) {
|
2014-09-19 15:36:32 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
private void finishResolve(Promise<T> promise) {
|
2014-09-19 15:36:32 +02:00
|
|
|
if (!queriesInProgress.isEmpty()) {
|
|
|
|
// If there are queries in progress, we should cancel it because we already finished the resolution.
|
2015-03-16 07:46:14 +01:00
|
|
|
for (Iterator<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> i = queriesInProgress.iterator();
|
|
|
|
i.hasNext();) {
|
|
|
|
Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = i.next();
|
2014-09-19 15:36:32 +02:00
|
|
|
i.remove();
|
|
|
|
|
|
|
|
if (!f.cancel(false)) {
|
|
|
|
f.addListener(RELEASE_RESPONSE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-12 12:34:05 +02:00
|
|
|
if (resolvedEntries != null) {
|
2014-09-19 15:36:32 +02:00
|
|
|
// Found at least one resolved address.
|
|
|
|
for (InternetProtocolFamily f: resolveAddressTypes) {
|
2016-06-30 23:12:11 +02:00
|
|
|
if (finishResolve(f.addressType(), resolvedEntries, promise)) {
|
2015-07-12 12:34:05 +02:00
|
|
|
return;
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// No resolved address found.
|
2015-07-12 12:34:05 +02:00
|
|
|
final int tries = maxAllowedQueries - allowedQueries;
|
|
|
|
final StringBuilder buf = new StringBuilder(64);
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2016-07-31 03:05:23 +02:00
|
|
|
buf.append("failed to resolve '");
|
|
|
|
if (pristineHostname != null) {
|
|
|
|
buf.append(pristineHostname);
|
|
|
|
} else {
|
|
|
|
buf.append(hostname);
|
|
|
|
}
|
|
|
|
buf.append('\'');
|
2015-07-12 12:34:05 +02:00
|
|
|
if (tries > 1) {
|
2016-05-27 21:34:36 +02:00
|
|
|
if (tries < maxAllowedQueries) {
|
|
|
|
buf.append(" after ")
|
|
|
|
.append(tries)
|
|
|
|
.append(" queries ");
|
2015-07-12 12:34:05 +02:00
|
|
|
} else {
|
2016-05-27 21:34:36 +02:00
|
|
|
buf.append(". Exceeded max queries per resolve ")
|
|
|
|
.append(maxAllowedQueries)
|
|
|
|
.append(' ');
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
2016-05-27 21:34:36 +02:00
|
|
|
if (trace != null) {
|
|
|
|
buf.append(':')
|
|
|
|
.append(trace);
|
|
|
|
}
|
2015-07-12 12:34:05 +02:00
|
|
|
final UnknownHostException cause = new UnknownHostException(buf.toString());
|
|
|
|
|
2016-07-29 10:30:08 +02:00
|
|
|
resolveCache.cache(hostname, additionals, cause, parent.ch.eventLoop());
|
2015-07-12 12:34:05 +02:00
|
|
|
promise.tryFailure(cause);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
abstract boolean finishResolve(Class<? extends InetAddress> addressType, List<DnsCacheEntry> resolvedEntries,
|
|
|
|
Promise<T> promise);
|
|
|
|
|
|
|
|
abstract DnsNameResolverContext<T> newResolverContext(DnsNameResolver parent, String hostname,
|
2016-07-29 10:30:08 +02:00
|
|
|
DnsRecord[] additionals, DnsCache resolveCache);
|
2015-07-12 12:34:05 +02:00
|
|
|
|
2016-06-13 21:16:22 +02:00
|
|
|
static String decodeDomainName(ByteBuf in) {
|
|
|
|
in.markReaderIndex();
|
2014-09-19 15:36:32 +02:00
|
|
|
try {
|
2016-06-13 21:16:22 +02:00
|
|
|
return DefaultDnsRecordDecoder.decodeName(in);
|
|
|
|
} catch (CorruptedFrameException e) {
|
|
|
|
// In this case we just return null.
|
|
|
|
return null;
|
2014-09-19 15:36:32 +02:00
|
|
|
} finally {
|
2016-06-13 21:16:22 +02:00
|
|
|
in.resetReaderIndex();
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
private void followCname(InetSocketAddress nameServerAddr, String name, String cname, Promise<T> promise) {
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-07-12 12:34:05 +02:00
|
|
|
if (traceEnabled) {
|
|
|
|
if (trace == null) {
|
|
|
|
trace = new StringBuilder(128);
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-07-12 12:34:05 +02:00
|
|
|
trace.append(StringUtil.NEWLINE);
|
|
|
|
trace.append("\tfrom ");
|
|
|
|
trace.append(nameServerAddr);
|
|
|
|
trace.append(": ");
|
|
|
|
trace.append(name);
|
|
|
|
trace.append(" CNAME ");
|
|
|
|
trace.append(cname);
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-07-12 12:34:05 +02:00
|
|
|
final InetSocketAddress nextAddr = nameServerAddrs.next();
|
2016-06-30 23:12:11 +02:00
|
|
|
query(nextAddr, new DefaultDnsQuestion(cname, DnsRecordType.A), promise);
|
|
|
|
query(nextAddr, new DefaultDnsQuestion(cname, DnsRecordType.AAAA), promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private void addTrace(InetSocketAddress nameServerAddr, String msg) {
|
2015-07-12 12:34:05 +02:00
|
|
|
assert traceEnabled;
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
if (trace == null) {
|
|
|
|
trace = new StringBuilder(128);
|
|
|
|
}
|
|
|
|
|
|
|
|
trace.append(StringUtil.NEWLINE);
|
|
|
|
trace.append("\tfrom ");
|
|
|
|
trace.append(nameServerAddr);
|
|
|
|
trace.append(": ");
|
|
|
|
trace.append(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void addTrace(Throwable cause) {
|
2015-07-12 12:34:05 +02:00
|
|
|
assert traceEnabled;
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
if (trace == null) {
|
|
|
|
trace = new StringBuilder(128);
|
|
|
|
}
|
|
|
|
|
|
|
|
trace.append(StringUtil.NEWLINE);
|
|
|
|
trace.append("Caused by: ");
|
|
|
|
trace.append(cause);
|
|
|
|
}
|
|
|
|
}
|