netty5/resolver-dns/src/main/java/io/netty/resolver/dns/DnsResolveContext.java

1266 lines
52 KiB
Java
Raw Normal View History

/*
* 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;
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
2015-03-16 07:46:14 +01:00
import io.netty.buffer.ByteBufHolder;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.CorruptedFrameException;
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
2015-03-16 07:46:14 +01:00
import io.netty.handler.codec.dns.DefaultDnsQuestion;
import io.netty.handler.codec.dns.DefaultDnsRecordDecoder;
import io.netty.handler.codec.dns.DnsQuestion;
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
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;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.handler.codec.dns.DnsSection;
import io.netty.util.NetUtil;
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.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.ThrowableUtil;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import static io.netty.resolver.dns.DnsAddressDecoder.decodeAddress;
import static java.lang.Math.min;
import static java.util.Objects.requireNonNull;
abstract class DnsResolveContext<T> {
private static final RuntimeException NXDOMAIN_QUERY_FAILED_EXCEPTION = ThrowableUtil.unknownStackTrace(
DnsResolveContextException.newStatic("No answer found and NXDOMAIN response code returned"),
DnsResolveContext.class,
"onResponse(..)");
private static final RuntimeException CNAME_NOT_FOUND_QUERY_FAILED_EXCEPTION = ThrowableUtil.unknownStackTrace(
DnsResolveContextException.newStatic("No matching CNAME record found"),
DnsResolveContext.class,
"onResponseCNAME(..)");
private static final RuntimeException NO_MATCHING_RECORD_QUERY_FAILED_EXCEPTION = ThrowableUtil.unknownStackTrace(
DnsResolveContextException.newStatic("No matching record type found"),
DnsResolveContext.class,
"onResponseAorAAAA(..)");
private static final RuntimeException UNRECOGNIZED_TYPE_QUERY_FAILED_EXCEPTION = ThrowableUtil.unknownStackTrace(
new RuntimeException("Response type was unrecognized"),
DnsResolveContext.class,
"onResponse(..)");
private static final RuntimeException NAME_SERVERS_EXHAUSTED_EXCEPTION = ThrowableUtil.unknownStackTrace(
DnsResolveContextException.newStatic("No name servers returned an answer"),
DnsResolveContext.class,
"tryToFinishResolve(..)");
final DnsNameResolver parent;
private final DnsServerAddressStream nameServerAddrs;
private final String hostname;
private final int dnsClass;
private final DnsRecordType[] expectedTypes;
private final int maxAllowedQueries;
final DnsRecord[] additionals;
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
2015-03-16 07:46:14 +01:00
private final Set<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> queriesInProgress =
Collections.newSetFromMap(
new IdentityHashMap<>());
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
2015-03-16 07:46:14 +01:00
private List<T> finalResult;
private int allowedQueries;
private boolean triedCNAME;
private boolean completeEarly;
DnsResolveContext(DnsNameResolver parent,
String hostname, int dnsClass, DnsRecordType[] expectedTypes,
DnsRecord[] additionals, DnsServerAddressStream nameServerAddrs) {
assert expectedTypes.length > 0;
this.parent = parent;
this.hostname = hostname;
this.dnsClass = dnsClass;
this.expectedTypes = expectedTypes;
this.additionals = additionals;
this.nameServerAddrs = requireNonNull(nameServerAddrs, "nameServerAddrs");
maxAllowedQueries = parent.maxQueriesPerResolve();
allowedQueries = maxAllowedQueries;
}
static final class DnsResolveContextException extends RuntimeException {
private DnsResolveContextException(String message) {
super(message, null, false, true);
}
static DnsResolveContextException newStatic(String message) {
return new DnsResolveContextException(message);
}
}
/**
* The {@link DnsCache} to use while resolving.
*/
DnsCache resolveCache() {
return parent.resolveCache();
}
/**
* The {@link DnsCnameCache} that is used for resolving.
*/
DnsCnameCache cnameCache() {
return parent.cnameCache();
}
/**
* The {@link AuthoritativeDnsServerCache} to use while resolving.
*/
AuthoritativeDnsServerCache authoritativeDnsServerCache() {
return parent.authoritativeDnsServerCache();
}
/**
* Creates a new context with the given parameters.
*/
abstract DnsResolveContext<T> newResolverContext(DnsNameResolver parent, String hostname,
int dnsClass, DnsRecordType[] expectedTypes,
DnsRecord[] additionals,
DnsServerAddressStream nameServerAddrs);
/**
* Converts the given {@link DnsRecord} into {@code T}.
*/
abstract T convertRecord(DnsRecord record, String hostname, DnsRecord[] additionals, EventLoop eventLoop);
/**
* Returns a filtered list of results which should be the final result of DNS resolution. This must take into
* account JDK semantics such as {@link NetUtil#isIpV6AddressesPreferred()}.
*/
abstract List<T> filterResults(List<T> unfiltered);
abstract boolean isCompleteEarly(T resolved);
/**
* Returns {@code true} if we should allow duplicates in the result or {@code false} if no duplicates should
* be included.
*/
abstract boolean isDuplicateAllowed();
/**
* Caches a successful resolution.
*/
abstract void cache(String hostname, DnsRecord[] additionals,
DnsRecord result, T convertedResult);
/**
* Caches a failed resolution.
*/
abstract void cache(String hostname, DnsRecord[] additionals,
UnknownHostException cause);
void resolve(final Promise<List<T>> promise) {
final String[] searchDomains = parent.searchDomains();
if (searchDomains.length == 0 || parent.ndots() == 0 || StringUtil.endsWith(hostname, '.')) {
internalResolve(hostname, promise);
} else {
final boolean startWithoutSearchDomain = hasNDots();
final String initialHostname = startWithoutSearchDomain ? hostname : hostname + '.' + searchDomains[0];
final int initialSearchDomainIdx = startWithoutSearchDomain ? 0 : 1;
final Promise<List<T>> searchDomainPromise = parent.executor().newPromise();
searchDomainPromise.addListener(new FutureListener<List<T>>() {
private int searchDomainIdx = initialSearchDomainIdx;
@Override
public void operationComplete(Future<List<T>> future) {
Throwable cause = future.cause();
if (cause == null) {
promise.trySuccess(future.getNow());
} else {
if (DnsNameResolver.isTransportOrTimeoutError(cause)) {
promise.tryFailure(new SearchDomainUnknownHostException(cause, hostname));
} else if (searchDomainIdx < searchDomains.length) {
Promise<List<T>> newPromise = parent.executor().newPromise();
newPromise.addListener(this);
doSearchDomainQuery(hostname + '.' + searchDomains[searchDomainIdx++], newPromise);
} else if (!startWithoutSearchDomain) {
internalResolve(hostname, promise);
} else {
promise.tryFailure(new SearchDomainUnknownHostException(cause, hostname));
}
}
}
});
doSearchDomainQuery(initialHostname, searchDomainPromise);
}
Provide more control over DnsNameResolver.query() / Add NameResolver.resolveAll() Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
2015-07-12 12:34:05 +02:00
}
private boolean hasNDots() {
for (int idx = hostname.length() - 1, dots = 0; idx >= 0; idx--) {
if (hostname.charAt(idx) == '.' && ++dots >= parent.ndots()) {
return true;
}
}
return false;
}
private static final class SearchDomainUnknownHostException extends UnknownHostException {
private static final long serialVersionUID = -8573510133644997085L;
SearchDomainUnknownHostException(Throwable cause, String originalHostname) {
super("Search domain query failed. Original hostname: '" + originalHostname + "' " + cause.getMessage());
setStackTrace(cause.getStackTrace());
// Preserve the cause
initCause(cause.getCause());
}
@Override
public Throwable fillInStackTrace() {
return this;
}
}
void doSearchDomainQuery(String hostname, Promise<List<T>> nextPromise) {
DnsResolveContext<T> nextContext = newResolverContext(parent, hostname, dnsClass, expectedTypes,
additionals, nameServerAddrs);
nextContext.internalResolve(hostname, nextPromise);
}
private static String hostnameWithDot(String name) {
if (StringUtil.endsWith(name, '.')) {
return name;
}
return name + '.';
}
private void internalResolve(String name, Promise<List<T>> promise) {
for (;;) {
// Resolve from cnameCache() until there is no more cname entry cached.
String mapping = cnameCache().get(hostnameWithDot(name));
if (mapping == null) {
break;
}
name = mapping;
}
try {
DnsServerAddressStream nameServerAddressStream = getNameServers(name);
final int end = expectedTypes.length - 1;
for (int i = 0; i < end; ++i) {
if (!query(name, expectedTypes[i], nameServerAddressStream.duplicate(), false, promise)) {
return;
}
}
query(name, expectedTypes[end], nameServerAddressStream, false, promise);
} finally {
// Now flush everything we submitted before.
parent.flushQueries();
}
}
/**
* Returns the {@link DnsServerAddressStream} that was cached for the given hostname or {@code null} if non
* could be found.
*/
private DnsServerAddressStream getNameServersFromCache(String hostname) {
int len = hostname.length();
if (len == 0) {
// We never cache for root servers.
return null;
}
// We always store in the cache with a trailing '.'.
if (hostname.charAt(len - 1) != '.') {
hostname += ".";
}
int idx = hostname.indexOf('.');
if (idx == hostname.length() - 1) {
// We are not interested in handling '.' as we should never serve the root servers from cache.
return null;
}
// We start from the closed match and then move down.
for (;;) {
// Skip '.' as well.
hostname = hostname.substring(idx + 1);
int idx2 = hostname.indexOf('.');
if (idx2 <= 0 || idx2 == hostname.length() - 1) {
// We are not interested in handling '.TLD.' as we should never serve the root servers from cache.
return null;
}
idx = idx2;
DnsServerAddressStream entries = authoritativeDnsServerCache().get(hostname);
if (entries != null) {
// The returned List may contain unresolved InetSocketAddress instances that will be
// resolved on the fly in query(....).
return entries;
}
}
}
private void query(final DnsServerAddressStream nameServerAddrStream,
final int nameServerAddrStreamIndex,
final DnsQuestion question,
final DnsQueryLifecycleObserver queryLifecycleObserver,
final boolean flush,
final Promise<List<T>> promise,
final Throwable cause) {
if (completeEarly || nameServerAddrStreamIndex >= nameServerAddrStream.size() ||
allowedQueries == 0 || promise.isCancelled()) {
tryToFinishResolve(nameServerAddrStream, nameServerAddrStreamIndex, question, queryLifecycleObserver,
promise, cause);
return;
}
--allowedQueries;
final InetSocketAddress nameServerAddr = nameServerAddrStream.next();
if (nameServerAddr.isUnresolved()) {
queryUnresolvedNameserver(nameServerAddr, nameServerAddrStream, nameServerAddrStreamIndex, question,
queryLifecycleObserver, promise, cause);
return;
}
final ChannelPromise writePromise = parent.ch.newPromise();
final Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> queryPromise =
parent.ch.eventLoop().newPromise();
final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f =
parent.query0(nameServerAddr, question, additionals, flush, writePromise, queryPromise);
queriesInProgress.add(f);
queryLifecycleObserver.queryWritten(nameServerAddr, writePromise);
f.addListener((FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>) future -> {
queriesInProgress.remove(future);
if (promise.isDone() || future.isCancelled()) {
queryLifecycleObserver.queryCancelled(allowedQueries);
// Check if we need to release the envelope itself. If the query was cancelled the getNow() will
// return null as well as the Future will be failed with a CancellationException.
AddressedEnvelope<DnsResponse, InetSocketAddress> result = future.getNow();
if (result != null) {
result.release();
}
return;
}
final Throwable queryCause = future.cause();
try {
if (queryCause == null) {
onResponse(nameServerAddrStream, nameServerAddrStreamIndex, question, future.getNow(),
queryLifecycleObserver, promise);
} else {
// Server did not respond or I/O error occurred; try again.
queryLifecycleObserver.queryFailed(queryCause);
query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
newDnsQueryLifecycleObserver(question), true, promise, queryCause);
}
} finally {
tryToFinishResolve(nameServerAddrStream, nameServerAddrStreamIndex, question,
// queryLifecycleObserver has already been terminated at this point so we must
// not allow it to be terminated again by tryToFinishResolve.
NoopDnsQueryLifecycleObserver.INSTANCE,
promise, queryCause);
}
});
}
private void queryUnresolvedNameserver(final InetSocketAddress nameServerAddr,
final DnsServerAddressStream nameServerAddrStream,
final int nameServerAddrStreamIndex,
final DnsQuestion question,
final DnsQueryLifecycleObserver queryLifecycleObserver,
final Promise<List<T>> promise,
final Throwable cause) {
final String nameServerName = nameServerAddr.getHostString();
assert nameServerName != null;
// Placeholder so we will not try to finish the original query yet.
final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> resolveFuture = parent.executor()
.newSucceededFuture(null);
queriesInProgress.add(resolveFuture);
Promise<List<InetAddress>> resolverPromise = parent.executor().newPromise();
resolverPromise.addListener((FutureListener<List<InetAddress>>) future -> {
// Remove placeholder.
queriesInProgress.remove(resolveFuture);
if (future.isSuccess()) {
List<InetAddress> resolvedAddresses = future.getNow();
DnsServerAddressStream addressStream = new CombinedDnsServerAddressStream(
nameServerAddr, resolvedAddresses, nameServerAddrStream);
query(addressStream, nameServerAddrStreamIndex, question,
queryLifecycleObserver, true, promise, cause);
} else {
// Ignore the server and try the next one...
query(nameServerAddrStream, nameServerAddrStreamIndex + 1,
question, queryLifecycleObserver, true, promise, cause);
}
});
if (!DnsNameResolver.doResolveAllCached(nameServerName, additionals, resolverPromise, resolveCache(),
parent.resolvedInternetProtocolFamiliesUnsafe())) {
final AuthoritativeDnsServerCache authoritativeDnsServerCache = authoritativeDnsServerCache();
new DnsAddressResolveContext(parent, nameServerName, additionals,
parent.newNameServerAddressStream(nameServerName),
resolveCache(), new AuthoritativeDnsServerCache() {
@Override
public DnsServerAddressStream get(String hostname) {
// To not risk falling into any loop, we will not use the cache while following redirects but only
// on the initial query.
return null;
}
@Override
public void cache(String hostname, InetSocketAddress address, long originalTtl, EventLoop loop) {
authoritativeDnsServerCache.cache(hostname, address, originalTtl, loop);
}
@Override
public void clear() {
authoritativeDnsServerCache.clear();
}
@Override
public boolean clear(String hostname) {
return authoritativeDnsServerCache.clear(hostname);
}
}, false).resolve(resolverPromise);
}
}
private void onResponse(final DnsServerAddressStream nameServerAddrStream, final int nameServerAddrStreamIndex,
final DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
final DnsQueryLifecycleObserver queryLifecycleObserver,
Promise<List<T>> promise) {
try {
Provide more control over DnsNameResolver.query() / Add NameResolver.resolveAll() Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
2015-07-12 12:34:05 +02:00
final DnsResponse res = envelope.content();
final DnsResponseCode code = res.code();
if (code == DnsResponseCode.NOERROR) {
if (handleRedirect(question, envelope, queryLifecycleObserver, promise)) {
// Was a redirect so return here as everything else is handled in handleRedirect(...)
return;
}
Provide more control over DnsNameResolver.query() / Add NameResolver.resolveAll() Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
2015-07-12 12:34:05 +02:00
final DnsRecordType type = question.type();
if (type == DnsRecordType.CNAME) {
onResponseCNAME(question, buildAliasMap(envelope.content(), cnameCache(), parent.executor()),
queryLifecycleObserver, promise);
return;
}
for (DnsRecordType expectedType : expectedTypes) {
if (type == expectedType) {
onExpectedResponse(question, envelope, queryLifecycleObserver, promise);
return;
}
Provide more control over DnsNameResolver.query() / Add NameResolver.resolveAll() Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
2015-07-12 12:34:05 +02:00
}
queryLifecycleObserver.queryFailed(UNRECOGNIZED_TYPE_QUERY_FAILED_EXCEPTION);
Provide more control over DnsNameResolver.query() / Add NameResolver.resolveAll() Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
2015-07-12 12:34:05 +02:00
return;
}
// Retry with the next server if the server did not tell us that the domain does not exist.
if (code != DnsResponseCode.NXDOMAIN) {
query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
queryLifecycleObserver.queryNoAnswer(code), true, promise, null);
} else {
queryLifecycleObserver.queryFailed(NXDOMAIN_QUERY_FAILED_EXCEPTION);
// Try with the next server if is not authoritative for the domain.
//
// From https://tools.ietf.org/html/rfc1035 :
//
// RCODE Response code - this 4 bit field is set as part of
// responses. The values have the following
// interpretation:
//
// ....
// ....
//
// 3 Name Error - Meaningful only for
// responses from an authoritative name
// server, this code signifies that the
// domain name referenced in the query does
// not exist.
// ....
// ....
if (!res.isAuthoritativeAnswer()) {
query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
newDnsQueryLifecycleObserver(question), true, promise, null);
}
}
} finally {
Provide more control over DnsNameResolver.query() / Add NameResolver.resolveAll() Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
2015-07-12 12:34:05 +02:00
ReferenceCountUtil.safeRelease(envelope);
}
}
/**
* Handles a redirect answer if needed and returns {@code true} if a redirect query has been made.
*/
private boolean handleRedirect(
DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
final DnsQueryLifecycleObserver queryLifecycleObserver, Promise<List<T>> promise) {
final DnsResponse res = envelope.content();
// Check if we have answers, if not this may be an non authority NS and so redirects must be handled.
if (res.count(DnsSection.ANSWER) == 0) {
AuthoritativeNameServerList serverNames = extractAuthoritativeNameServers(question.name(), res);
if (serverNames != null) {
int additionalCount = res.count(DnsSection.ADDITIONAL);
AuthoritativeDnsServerCache authoritativeDnsServerCache = authoritativeDnsServerCache();
for (int i = 0; i < additionalCount; i++) {
final DnsRecord r = res.recordAt(DnsSection.ADDITIONAL, i);
if (r.type() == DnsRecordType.A && !parent.supportsARecords() ||
r.type() == DnsRecordType.AAAA && !parent.supportsAAAARecords()) {
continue;
}
// We may have multiple ADDITIONAL entries for the same nameserver name. For example one AAAA and
// one A record.
serverNames.handleWithAdditional(parent, r, authoritativeDnsServerCache);
}
// Process all unresolved nameservers as well.
serverNames.handleWithoutAdditionals(parent, resolveCache(), authoritativeDnsServerCache);
List<InetSocketAddress> addresses = serverNames.addressList();
// Give the user the chance to sort or filter the used servers for the query.
DnsServerAddressStream serverStream = parent.newRedirectDnsServerStream(
question.name(), addresses);
if (serverStream != null) {
query(serverStream, 0, question,
queryLifecycleObserver.queryRedirected(new DnsAddressStreamList(serverStream)),
true, promise, null);
return true;
}
}
}
return false;
}
private static final class DnsAddressStreamList extends AbstractList<InetSocketAddress> {
private final DnsServerAddressStream duplicate;
private List<InetSocketAddress> addresses;
DnsAddressStreamList(DnsServerAddressStream stream) {
duplicate = stream.duplicate();
}
@Override
public InetSocketAddress get(int index) {
if (addresses == null) {
DnsServerAddressStream stream = duplicate.duplicate();
addresses = new ArrayList<>(size());
for (int i = 0; i < stream.size(); i++) {
addresses.add(stream.next());
}
}
return addresses.get(index);
}
@Override
public int size() {
return duplicate.size();
}
@Override
public Iterator<InetSocketAddress> iterator() {
return new Iterator<InetSocketAddress>() {
private final DnsServerAddressStream stream = duplicate.duplicate();
private int i;
@Override
public boolean hasNext() {
return i < stream.size();
}
@Override
public InetSocketAddress next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
i++;
return stream.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Returns the {@code {@link AuthoritativeNameServerList} which were included in {@link DnsSection#AUTHORITY}
* or {@code null} if non are found.
*/
private static AuthoritativeNameServerList extractAuthoritativeNameServers(String questionName, DnsResponse res) {
int authorityCount = res.count(DnsSection.AUTHORITY);
if (authorityCount == 0) {
return null;
}
AuthoritativeNameServerList serverNames = new AuthoritativeNameServerList(questionName);
for (int i = 0; i < authorityCount; i++) {
serverNames.add(res.recordAt(DnsSection.AUTHORITY, i));
}
return serverNames.isEmpty() ? null : serverNames;
}
private void onExpectedResponse(
DnsQuestion question, AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
final DnsQueryLifecycleObserver queryLifecycleObserver, Promise<List<T>> promise) {
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
2015-03-16 07:46:14 +01:00
// We often get a bunch of CNAMES as well when we asked for A/AAAA.
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
2015-03-16 07:46:14 +01:00
final DnsResponse response = envelope.content();
final Map<String, String> cnames = buildAliasMap(response, cnameCache(), parent.executor());
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
2015-03-16 07:46:14 +01:00
final int answerCount = response.count(DnsSection.ANSWER);
boolean found = false;
boolean completeEarly = this.completeEarly;
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
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();
boolean matches = false;
for (DnsRecordType expectedType : expectedTypes) {
if (type == expectedType) {
matches = true;
break;
}
}
if (!matches) {
continue;
}
final String questionName = question.name().toLowerCase(Locale.US);
final String recordName = r.name().toLowerCase(Locale.US);
// Make sure the record is for the questioned domain.
if (!recordName.equals(questionName)) {
Map<String, String> cnamesCopy = new HashMap<>(cnames);
// Even if the record's name is not exactly same, it might be an alias defined in the CNAME records.
String resolved = questionName;
do {
resolved = cnamesCopy.remove(resolved);
if (recordName.equals(resolved)) {
break;
}
} while (resolved != null);
if (resolved == null) {
continue;
}
}
final T converted = convertRecord(r, hostname, additionals, parent.executor());
if (converted == null) {
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
2015-03-16 07:46:14 +01:00
continue;
}
boolean shouldRelease = false;
// Check if we did determine we wanted to complete early before. If this is the case we want to not
// include the result
if (!completeEarly) {
completeEarly = isCompleteEarly(converted);
}
// We want to ensure we do not have duplicates in finalResult as this may be unexpected.
//
// While using a LinkedHashSet or HashSet may sound like the perfect fit for this we will use an
// ArrayList here as duplicates should be found quite unfrequently in the wild and we dont want to pay
// for the extra memory copy and allocations in this cases later on.
if (finalResult == null) {
finalResult = new ArrayList<>(8);
finalResult.add(converted);
} else if (isDuplicateAllowed() || !finalResult.contains(converted)) {
finalResult.add(converted);
} else {
shouldRelease = true;
Provide more control over DnsNameResolver.query() / Add NameResolver.resolveAll() Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
2015-07-12 12:34:05 +02:00
}
cache(hostname, additionals, r, converted);
Provide more control over DnsNameResolver.query() / Add NameResolver.resolveAll() Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
2015-07-12 12:34:05 +02:00
found = true;
if (shouldRelease) {
ReferenceCountUtil.release(converted);
}
Provide more control over DnsNameResolver.query() / Add NameResolver.resolveAll() Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
2015-07-12 12:34:05 +02:00
// Note that we do not break from the loop here, so we decode/cache all A/AAAA records.
}
if (cnames.isEmpty()) {
if (found) {
if (completeEarly) {
this.completeEarly = true;
}
queryLifecycleObserver.querySucceed();
return;
}
queryLifecycleObserver.queryFailed(NO_MATCHING_RECORD_QUERY_FAILED_EXCEPTION);
} else {
queryLifecycleObserver.querySucceed();
// We also got a CNAME so we need to ensure we also query it.
onResponseCNAME(question, cnames, newDnsQueryLifecycleObserver(question), promise);
}
}
private void onResponseCNAME(
DnsQuestion question, Map<String, String> cnames,
final DnsQueryLifecycleObserver queryLifecycleObserver,
Promise<List<T>> promise) {
// Resolve the host name in the question into the real host name.
String resolved = question.name().toLowerCase(Locale.US);
boolean found = false;
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);
if (next != null) {
found = true;
resolved = next;
} else {
break;
}
}
if (found) {
followCname(question, resolved, queryLifecycleObserver, promise);
} else {
queryLifecycleObserver.queryFailed(CNAME_NOT_FOUND_QUERY_FAILED_EXCEPTION);
}
}
private static Map<String, String> buildAliasMap(DnsResponse response, DnsCnameCache cache, EventLoop loop) {
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
2015-03-16 07:46:14 +01:00
final int answerCount = response.count(DnsSection.ANSWER);
Map<String, String> cnames = null;
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
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)) {
continue;
}
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
2015-03-16 07:46:14 +01:00
final ByteBuf recordContent = ((ByteBufHolder) r).content();
final String domainName = decodeDomainName(recordContent);
if (domainName == null) {
continue;
}
if (cnames == null) {
cnames = new HashMap<>(min(8, answerCount));
}
String name = r.name().toLowerCase(Locale.US);
String mapping = domainName.toLowerCase(Locale.US);
// Cache the CNAME as well.
String nameWithDot = hostnameWithDot(name);
String mappingWithDot = hostnameWithDot(mapping);
if (!nameWithDot.equalsIgnoreCase(mappingWithDot)) {
cache.cache(nameWithDot, mappingWithDot, r.timeToLive(), loop);
cnames.put(name, mapping);
}
}
return cnames != null? cnames : Collections.emptyMap();
}
private void tryToFinishResolve(final DnsServerAddressStream nameServerAddrStream,
final int nameServerAddrStreamIndex,
final DnsQuestion question,
final DnsQueryLifecycleObserver queryLifecycleObserver,
final Promise<List<T>> promise,
final Throwable cause) {
// There are no queries left to try.
if (!completeEarly && !queriesInProgress.isEmpty()) {
queryLifecycleObserver.queryCancelled(allowedQueries);
// There are still some queries in process, we will try to notify once the next one finishes until
// all are finished.
return;
}
// There are no queries left to try.
if (finalResult == null) {
if (nameServerAddrStreamIndex < nameServerAddrStream.size()) {
if (queryLifecycleObserver == NoopDnsQueryLifecycleObserver.INSTANCE) {
// If the queryLifecycleObserver has already been terminated we should create a new one for this
// fresh query.
query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
newDnsQueryLifecycleObserver(question), true, promise, cause);
} else {
query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question, queryLifecycleObserver,
true, promise, cause);
}
return;
}
queryLifecycleObserver.queryFailed(NAME_SERVERS_EXHAUSTED_EXCEPTION);
// .. and we could not find any expected records.
// If cause != null we know this was caused by a timeout / cancel / transport exception. In this case we
// won't try to resolve the CNAME as we only should do this if we could not get the expected records
// because they do not exist and the DNS server did probably signal it.
if (cause == null && !triedCNAME) {
// As the last resort, try to query CNAME, just in case the name server has it.
triedCNAME = true;
query(hostname, DnsRecordType.CNAME, getNameServers(hostname), true, promise);
return;
}
} else {
queryLifecycleObserver.queryCancelled(allowedQueries);
}
// We have at least one resolved record or tried CNAME as the last resort..
finishResolve(promise, cause);
}
private void finishResolve(Promise<List<T>> promise, Throwable cause) {
// If completeEarly was true we still want to continue processing the queries to ensure we still put everything
// in the cache eventually.
if (!completeEarly && !queriesInProgress.isEmpty()) {
// If there are queries in progress, we should cancel it because we already finished the resolution.
Revamp DNS codec Motivation: There are various known issues in netty-codec-dns: - Message types are not interfaces, which can make it difficult for a user to implement his/her own message implementation. - Some class names and field names do not match with the terms in the RFC. - The support for decoding a DNS record was limited. A user had to encode and decode by him/herself. - The separation of DnsHeader from DnsMessage was unnecessary, although it is fine conceptually. - Buffer leak caused by DnsMessage was difficult to analyze, because the leak detector tracks down the underlying ByteBuf rather than the DnsMessage itself. - DnsMessage assumes DNS-over-UDP. - To send an EDNS message, a user have to create a new DNS record class instance unnecessarily. Modifications: - Make all message types interfaces and add default implementations - Rename some classes, properties, and constants to match the RFCs - DnsResource -> DnsRecord - DnsType -> DnsRecordType - and many more - Remove DnsClass and use an integer to support EDNS better - Add DnsRecordEncoder/DnsRecordDecoder and their default implementations - DnsRecord does not require RDATA to be ByteBuf anymore. - Add DnsRawRecord as the catch-all record type - Merge DnsHeader into DnsMessage - Make ResourceLeakDetector track AbstractDnsMessage - Remove DnsMessage.sender/recipient properties - Wrap DnsMessage with AddressedEnvelope - Add DatagramDnsQuest and DatagramDnsResponse for ease of use - Rename DnsQueryEncoder to DatagramDnsQueryEncoder - Rename DnsResponseDecoder to DatagramDnsResponseDecoder - Miscellaneous changes - Add StringUtil.TAB Result: - Cleaner APi - Can support DNS-over-TCP more easily in the future - Reduced memory footprint in the default DnsQuery/Response implementations - Better leak tracking for DnsMessages - Possibility to introduce new DnsRecord types in the future and provide full record encoder/decoder implementation. - No unnecessary instantiation for an EDNS pseudo resource record
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();
i.remove();
f.cancel(false);
}
}
if (finalResult != null) {
if (!promise.isDone()) {
// Found at least one resolved record.
DnsNameResolver.trySuccess(promise, filterResults(finalResult));
}
return;
}
// No resolved address found.
Provide more control over DnsNameResolver.query() / Add NameResolver.resolveAll() Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
2015-07-12 12:34:05 +02:00
final int tries = maxAllowedQueries - allowedQueries;
final StringBuilder buf = new StringBuilder(64);
buf.append("failed to resolve '").append(hostname).append('\'');
Provide more control over DnsNameResolver.query() / Add NameResolver.resolveAll() Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
2015-07-12 12:34:05 +02:00
if (tries > 1) {
if (tries < maxAllowedQueries) {
buf.append(" after ")
.append(tries)
.append(" queries ");
Provide more control over DnsNameResolver.query() / Add NameResolver.resolveAll() Related issues: - #3971 - #3973 - #3976 - #4035 Motivation: 1. Previously, DnsNameResolver.query() retried the request query by its own. It prevents a user from deciding when to retry or stop. It is also impossible to get the response object whose code is not NOERROR. 2. NameResolver does not have an operation that resolves a host name into multiple addresses, like InetAddress.getAllByName() Modifications: - Changes related with DnsNameResolver.query() - Make query() not retry - Move the retry logic to DnsNameResolver.resolve() instead. - Make query() fail the promise only when I/O error occurred or it failed to get a response - Add DnsNameResolverException and use it when query() fails so that the resolver can give more information about the failure - query() does not cache anymore. - Changes related with NameResolver.resolveAll() - Add NameResolver.resolveAll() - Add SimpleNameResolver.doResolveAll() - Changes related with DnsNameResolver.resolve() and resolveAll() - Make DnsNameResolveContext abstract so that DnsNameResolver can decide to get single or multiple addresses from it - Re-implement cache so that the cache works for resolve() and resolveAll() - Add 'traceEnabled' property to enable/disable trace information - Miscellaneous changes - Use ObjectUtil.checkNotNull() wherever possible - Add InternetProtocolFamily.addressType() to remove repetitive switch-case blocks in DnsNameResolver(Context) - Do not raise an exception when decoding a truncated DNS response Result: - Full control over query() - A user can now retrieve all addresses via (Dns)NameResolver.resolveAll() - DNS cache works only for resolve() and resolveAll() now.
2015-07-12 12:34:05 +02:00
} else {
buf.append(". Exceeded max queries per resolve ")
.append(maxAllowedQueries)
.append(' ');
}
}
final UnknownHostException unknownHostException = new UnknownHostException(buf.toString());
if (cause == null) {
// Only cache if the failure was not because of an IO error / timeout that was caused by the query
// itself.
cache(hostname, additionals, unknownHostException);
} else {
unknownHostException.initCause(cause);
}
promise.tryFailure(unknownHostException);
}
static String decodeDomainName(ByteBuf in) {
int readerIndex = in.readerIndex();
try {
return DefaultDnsRecordDecoder.decodeName(in);
} catch (CorruptedFrameException e) {
// In this case we just return null.
return null;
} finally {
in.readerIndex(readerIndex);
}
}
2017-04-19 22:37:03 +02:00
private DnsServerAddressStream getNameServers(String hostname) {
DnsServerAddressStream stream = getNameServersFromCache(hostname);
return stream == null ? nameServerAddrs.duplicate() : stream;
}
private void followCname(DnsQuestion question, String cname, DnsQueryLifecycleObserver queryLifecycleObserver,
Promise<List<T>> promise) {
Set<String> cnames = null;
for (;;) {
// Resolve from cnameCache() until there is no more cname entry cached.
String mapping = cnameCache().get(hostnameWithDot(cname));
if (mapping == null) {
break;
}
if (cnames == null) {
// Detect loops.
cnames = new HashSet<>(2);
}
if (!cnames.add(cname)) {
// Follow CNAME from cache would loop. Lets break here.
break;
}
cname = mapping;
}
DnsServerAddressStream stream = getNameServers(cname);
final DnsQuestion cnameQuestion;
try {
cnameQuestion = new DefaultDnsQuestion(cname, question.type(), dnsClass);
} catch (Throwable cause) {
queryLifecycleObserver.queryFailed(cause);
PlatformDependent.throwException(cause);
return;
}
query(stream, 0, cnameQuestion, queryLifecycleObserver.queryCNAMEd(cnameQuestion),
true, promise, null);
}
private boolean query(String hostname, DnsRecordType type, DnsServerAddressStream dnsServerAddressStream,
boolean flush, Promise<List<T>> promise) {
final DnsQuestion question;
try {
question = new DefaultDnsQuestion(hostname, type, dnsClass);
} catch (Throwable cause) {
// Assume a single failure means that queries will succeed. If the hostname is invalid for one type
// there is no case where it is known to be valid for another type.
promise.tryFailure(new IllegalArgumentException("Unable to create DNS Question for: [" + hostname + ", " +
type + ']', cause));
return false;
}
query(dnsServerAddressStream, 0, question, newDnsQueryLifecycleObserver(question), flush, promise, null);
return true;
}
private DnsQueryLifecycleObserver newDnsQueryLifecycleObserver(DnsQuestion question) {
return parent.dnsQueryLifecycleObserverFactory().newDnsQueryLifecycleObserver(question);
}
private final class CombinedDnsServerAddressStream implements DnsServerAddressStream {
private final InetSocketAddress replaced;
private final DnsServerAddressStream originalStream;
private final List<InetAddress> resolvedAddresses;
private Iterator<InetAddress> resolved;
CombinedDnsServerAddressStream(InetSocketAddress replaced, List<InetAddress> resolvedAddresses,
DnsServerAddressStream originalStream) {
this.replaced = replaced;
this.resolvedAddresses = resolvedAddresses;
this.originalStream = originalStream;
resolved = resolvedAddresses.iterator();
}
@Override
public InetSocketAddress next() {
if (resolved.hasNext()) {
return nextResolved0();
}
InetSocketAddress address = originalStream.next();
if (address.equals(replaced)) {
resolved = resolvedAddresses.iterator();
return nextResolved0();
}
return address;
}
private InetSocketAddress nextResolved0() {
return parent.newRedirectServerAddress(resolved.next());
}
@Override
public int size() {
return originalStream.size() + resolvedAddresses.size() - 1;
}
@Override
public DnsServerAddressStream duplicate() {
return new CombinedDnsServerAddressStream(replaced, resolvedAddresses, originalStream.duplicate());
}
}
/**
* Holds the closed DNS Servers for a domain.
*/
private static final class AuthoritativeNameServerList {
private final String questionName;
// We not expect the linked-list to be very long so a double-linked-list is overkill.
private AuthoritativeNameServer head;
private int nameServerCount;
AuthoritativeNameServerList(String questionName) {
this.questionName = questionName.toLowerCase(Locale.US);
}
void add(DnsRecord r) {
if (r.type() != DnsRecordType.NS || !(r instanceof DnsRawRecord)) {
return;
}
// Only include servers that serve the correct domain.
if (questionName.length() < r.name().length()) {
return;
}
String recordName = r.name().toLowerCase(Locale.US);
int dots = 0;
for (int a = recordName.length() - 1, b = questionName.length() - 1; a >= 0; a--, b--) {
char c = recordName.charAt(a);
if (questionName.charAt(b) != c) {
return;
}
if (c == '.') {
dots++;
}
}
if (head != null && head.dots > dots) {
// We already have a closer match so ignore this one, no need to parse the domainName etc.
return;
}
final ByteBuf recordContent = ((ByteBufHolder) r).content();
final String domainName = decodeDomainName(recordContent);
if (domainName == null) {
// Could not be parsed, ignore.
return;
}
// We are only interested in preserving the nameservers which are the closest to our qName, so ensure
// we drop servers that have a smaller dots count.
if (head == null || head.dots < dots) {
nameServerCount = 1;
head = new AuthoritativeNameServer(dots, r.timeToLive(), recordName, domainName);
} else if (head.dots == dots) {
AuthoritativeNameServer serverName = head;
while (serverName.next != null) {
serverName = serverName.next;
}
serverName.next = new AuthoritativeNameServer(dots, r.timeToLive(), recordName, domainName);
nameServerCount++;
}
}
void handleWithAdditional(
DnsNameResolver parent, DnsRecord r, AuthoritativeDnsServerCache authoritativeCache) {
// Just walk the linked-list and mark the entry as handled when matched.
AuthoritativeNameServer serverName = head;
String nsName = r.name();
InetAddress resolved = decodeAddress(r, nsName, parent.isDecodeIdn());
if (resolved == null) {
// Could not parse the address, just ignore.
return;
}
while (serverName != null) {
if (serverName.nsName.equalsIgnoreCase(nsName)) {
if (serverName.address != null) {
// We received multiple ADDITIONAL records for the same name.
// Search for the last we insert before and then append a new one.
while (serverName.next != null && serverName.next.isCopy) {
serverName = serverName.next;
}
AuthoritativeNameServer server = new AuthoritativeNameServer(serverName);
server.next = serverName.next;
serverName.next = server;
serverName = server;
nameServerCount++;
}
// We should replace the TTL if needed with the one of the ADDITIONAL record so we use
// the smallest for caching.
serverName.update(parent.newRedirectServerAddress(resolved), r.timeToLive());
// Cache the server now.
cache(serverName, authoritativeCache, parent.executor());
return;
}
serverName = serverName.next;
}
}
// Now handle all AuthoritativeNameServer for which we had no ADDITIONAL record
void handleWithoutAdditionals(
DnsNameResolver parent, DnsCache cache, AuthoritativeDnsServerCache authoritativeCache) {
AuthoritativeNameServer serverName = head;
while (serverName != null) {
if (serverName.address == null) {
// These will be resolved on the fly if needed.
cacheUnresolved(serverName, authoritativeCache, parent.executor());
// Try to resolve via cache as we had no ADDITIONAL entry for the server.
List<? extends DnsCacheEntry> entries = cache.get(serverName.nsName, null);
if (entries != null && !entries.isEmpty()) {
InetAddress address = entries.get(0).address();
// If address is null we have a resolution failure cached so just use an unresolved address.
if (address != null) {
serverName.update(parent.newRedirectServerAddress(address));
for (int i = 1; i < entries.size(); i++) {
address = entries.get(i).address();
assert address != null :
"Cache returned a cached failure, should never return anything else";
AuthoritativeNameServer server = new AuthoritativeNameServer(serverName);
server.next = serverName.next;
serverName.next = server;
serverName = server;
serverName.update(parent.newRedirectServerAddress(address));
nameServerCount++;
}
}
}
}
serverName = serverName.next;
}
}
private static void cacheUnresolved(
AuthoritativeNameServer server, AuthoritativeDnsServerCache authoritativeCache, EventLoop loop) {
// We still want to cached the unresolved address
server.address = InetSocketAddress.createUnresolved(
server.nsName, DefaultDnsServerAddressStreamProvider.DNS_PORT);
// Cache the server now.
cache(server, authoritativeCache, loop);
}
private static void cache(AuthoritativeNameServer server, AuthoritativeDnsServerCache cache, EventLoop loop) {
// Cache NS record if not for a root server as we should never cache for root servers.
if (!server.isRootServer()) {
cache.cache(server.domainName, server.address, server.ttl, loop);
}
}
/**
* Returns {@code true} if empty, {@code false} otherwise.
*/
boolean isEmpty() {
return nameServerCount == 0;
}
/**
* Creates a new {@link List} which holds the {@link InetSocketAddress}es.
*/
List<InetSocketAddress> addressList() {
List<InetSocketAddress> addressList = new ArrayList<>(nameServerCount);
AuthoritativeNameServer server = head;
while (server != null) {
if (server.address != null) {
addressList.add(server.address);
}
server = server.next;
}
return addressList;
}
}
private static final class AuthoritativeNameServer {
private final int dots;
private final String domainName;
final boolean isCopy;
final String nsName;
private long ttl;
private InetSocketAddress address;
AuthoritativeNameServer next;
AuthoritativeNameServer(int dots, long ttl, String domainName, String nsName) {
this.dots = dots;
this.ttl = ttl;
this.nsName = nsName;
this.domainName = domainName;
isCopy = false;
}
AuthoritativeNameServer(AuthoritativeNameServer server) {
dots = server.dots;
ttl = server.ttl;
nsName = server.nsName;
domainName = server.domainName;
isCopy = true;
}
/**
* Returns {@code true} if its a root server.
*/
boolean isRootServer() {
return dots == 1;
}
/**
* Update the server with the given address and TTL if needed.
*/
void update(InetSocketAddress address, long ttl) {
assert this.address == null || this.address.isUnresolved();
this.address = address;
this.ttl = min(this.ttl, ttl);
}
void update(InetSocketAddress address) {
update(address, Long.MAX_VALUE);
}
}
}