Added netty based DNS resolver. This is VERY similar to the one released

officially, but with changes to accept/work with xbill DNS records
 (which are more complete than netty's)
This commit is contained in:
nathan 2017-11-06 23:20:34 +01:00
parent 084a85df5c
commit 762215304d
39 changed files with 5136 additions and 631 deletions

View File

@ -0,0 +1,120 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import java.net.InetSocketAddress;
import java.util.List;
import dorkbox.network.dns.records.DnsMessage;
import io.netty.channel.ChannelFuture;
import io.netty.util.internal.UnstableApi;
/**
* Combines two {@link DnsQueryLifecycleObserver} into a single {@link DnsQueryLifecycleObserver}.
*/
@UnstableApi
public final
class BiDnsQueryLifecycleObserver implements DnsQueryLifecycleObserver {
private final DnsQueryLifecycleObserver a;
private final DnsQueryLifecycleObserver b;
/**
* Create a new instance.
*
* @param a The {@link DnsQueryLifecycleObserver} that will receive events first.
* @param b The {@link DnsQueryLifecycleObserver} that will receive events second.
*/
public
BiDnsQueryLifecycleObserver(DnsQueryLifecycleObserver a, DnsQueryLifecycleObserver b) {
this.a = checkNotNull(a, "a");
this.b = checkNotNull(b, "b");
}
@Override
public
void queryWritten(InetSocketAddress dnsServerAddress, ChannelFuture future) {
try {
a.queryWritten(dnsServerAddress, future);
} finally {
b.queryWritten(dnsServerAddress, future);
}
}
@Override
public
void queryCancelled(int queriesRemaining) {
try {
a.queryCancelled(queriesRemaining);
} finally {
b.queryCancelled(queriesRemaining);
}
}
@Override
public
DnsQueryLifecycleObserver queryRedirected(List<InetSocketAddress> nameServers) {
try {
a.queryRedirected(nameServers);
} finally {
b.queryRedirected(nameServers);
}
return this;
}
@Override
public
DnsQueryLifecycleObserver queryCNAMEd(DnsMessage cnameQuestion) {
try {
a.queryCNAMEd(cnameQuestion);
} finally {
b.queryCNAMEd(cnameQuestion);
}
return this;
}
@Override
public
DnsQueryLifecycleObserver queryNoAnswer(int code) {
try {
a.queryNoAnswer(code);
} finally {
b.queryNoAnswer(code);
}
return this;
}
@Override
public
void queryFailed(Throwable cause) {
try {
a.queryFailed(cause);
} finally {
b.queryFailed(cause);
}
}
@Override
public
void querySucceed() {
try {
a.querySucceed();
} finally {
b.querySucceed();
}
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import dorkbox.network.dns.records.DnsMessage;
import io.netty.util.internal.UnstableApi;
/**
* Combines two {@link DnsQueryLifecycleObserverFactory} into a single {@link DnsQueryLifecycleObserverFactory}.
*/
@UnstableApi
public final
class BiDnsQueryLifecycleObserverFactory implements DnsQueryLifecycleObserverFactory {
private final DnsQueryLifecycleObserverFactory a;
private final DnsQueryLifecycleObserverFactory b;
/**
* Create a new instance.
*
* @param a The {@link DnsQueryLifecycleObserverFactory} that will receive events first.
* @param b The {@link DnsQueryLifecycleObserverFactory} that will receive events second.
*/
public
BiDnsQueryLifecycleObserverFactory(DnsQueryLifecycleObserverFactory a, DnsQueryLifecycleObserverFactory b) {
this.a = checkNotNull(a, "a");
this.b = checkNotNull(b, "b");
}
@Override
public
DnsQueryLifecycleObserver newDnsQueryLifecycleObserver(DnsMessage question) {
return new BiDnsQueryLifecycleObserver(a.newDnsQueryLifecycleObserver(question), b.newDnsQueryLifecycleObserver(question));
}
}

View File

@ -0,0 +1,406 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package dorkbox.network.dns.resolver;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.intValue;
import java.util.ArrayList;
import java.util.List;
import dorkbox.network.dns.resolver.addressProvider.DnsServerAddressStreamProvider;
import dorkbox.network.dns.resolver.addressProvider.DnsServerAddressStreamProviders;
import dorkbox.network.dns.resolver.cache.DefaultDnsCache;
import dorkbox.network.dns.resolver.cache.DnsCache;
import io.netty.channel.ChannelFactory;
import io.netty.channel.EventLoop;
import io.netty.channel.ReflectiveChannelFactory;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.resolver.HostsFileEntriesResolver;
import io.netty.resolver.ResolvedAddressTypes;
import io.netty.util.internal.UnstableApi;
/**
* A {@link DnsNameResolver} builder.
*/
@UnstableApi
public final
class DnsNameResolverBuilder {
private final EventLoop eventLoop;
private ChannelFactory<? extends DatagramChannel> channelFactory;
private DnsCache resolveCache;
private DnsCache authoritativeDnsServerCache;
private Integer minTtl;
private Integer maxTtl;
private Integer negativeTtl;
private long queryTimeoutMillis = 5000;
private ResolvedAddressTypes resolvedAddressTypes = DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
private boolean recursionDesired = true;
private int maxQueriesPerResolve = 16;
private boolean traceEnabled;
private int maxPayloadSize = 4096;
private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
private DnsServerAddressStreamProvider dnsServerAddressStreamProvider = DnsServerAddressStreamProviders.platformDefault();
private DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory = NoopDnsQueryLifecycleObserverFactory.INSTANCE;
private String[] searchDomains;
private int ndots = -1;
private boolean decodeIdn = true;
/**
* Creates a new builder.
*
* @param eventLoop the {@link EventLoop} the {@link EventLoop} which will perform the communication with the DNS
* servers.
*/
public
DnsNameResolverBuilder(EventLoop eventLoop) {
this.eventLoop = eventLoop;
}
/**
* Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type.
* Use as an alternative to {@link #channelFactory(ChannelFactory)}.
*
* @param channelType the type
*
* @return {@code this}
*/
public
DnsNameResolverBuilder channelType(Class<? extends DatagramChannel> channelType) {
return channelFactory(new ReflectiveChannelFactory<DatagramChannel>(channelType));
}
/**
* Sets the {@link ChannelFactory} that will create a {@link DatagramChannel}.
*
* @param channelFactory the {@link ChannelFactory}
*
* @return {@code this}
*/
public
DnsNameResolverBuilder channelFactory(ChannelFactory<? extends DatagramChannel> channelFactory) {
this.channelFactory = channelFactory;
return this;
}
/**
* Sets the cache for resolution results.
*
* @param resolveCache the DNS resolution results cache
*
* @return {@code this}
*/
public
DnsNameResolverBuilder resolveCache(DnsCache resolveCache) {
this.resolveCache = resolveCache;
return this;
}
/**
* Set the factory used to generate objects which can observe individual DNS queries.
*
* @param lifecycleObserverFactory the factory used to generate objects which can observe individual DNS queries.
*
* @return {@code this}
*/
public
DnsNameResolverBuilder dnsQueryLifecycleObserverFactory(DnsQueryLifecycleObserverFactory lifecycleObserverFactory) {
this.dnsQueryLifecycleObserverFactory = checkNotNull(lifecycleObserverFactory, "lifecycleObserverFactory");
return this;
}
/**
* Sets the cache for authoritative NS servers
*
* @param authoritativeDnsServerCache the authoritative NS servers cache
*
* @return {@code this}
*/
public
DnsNameResolverBuilder authoritativeDnsServerCache(DnsCache authoritativeDnsServerCache) {
this.authoritativeDnsServerCache = authoritativeDnsServerCache;
return this;
}
/**
* Sets the minimum and maximum TTL of the cached DNS resource records (in seconds). If the TTL of the DNS
* resource record returned by the DNS server is less than the minimum TTL or greater than the maximum TTL,
* this resolver will ignore the TTL from the DNS server and use the minimum TTL or the maximum TTL instead
* respectively.
* The default value is {@code 0} and {@link Integer#MAX_VALUE}, which practically tells this resolver to
* respect the TTL from the DNS server.
*
* @param minTtl the minimum TTL
* @param maxTtl the maximum TTL
*
* @return {@code this}
*/
public
DnsNameResolverBuilder ttl(int minTtl, int maxTtl) {
this.maxTtl = maxTtl;
this.minTtl = minTtl;
return this;
}
/**
* Sets the TTL of the cache for the failed DNS queries (in seconds).
*
* @param negativeTtl the TTL for failed cached queries
*
* @return {@code this}
*/
public
DnsNameResolverBuilder negativeTtl(int negativeTtl) {
this.negativeTtl = negativeTtl;
return this;
}
/**
* Sets the timeout of each DNS query performed by this resolver (in milliseconds).
*
* @param queryTimeoutMillis the query timeout
*
* @return {@code this}
*/
public
DnsNameResolverBuilder queryTimeoutMillis(long queryTimeoutMillis) {
this.queryTimeoutMillis = queryTimeoutMillis;
return this;
}
/**
* Compute a {@link ResolvedAddressTypes} from some {@link InternetProtocolFamily}s.
* An empty input will return the default value, based on "java.net" System properties.
* Valid inputs are (), (IPv4), (IPv6), (Ipv4, IPv6) and (IPv6, IPv4).
*
* @param internetProtocolFamilies a valid sequence of {@link InternetProtocolFamily}s
*
* @return a {@link ResolvedAddressTypes}
*/
public static
ResolvedAddressTypes computeResolvedAddressTypes(InternetProtocolFamily... internetProtocolFamilies) {
if (internetProtocolFamilies == null || internetProtocolFamilies.length == 0) {
return DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
}
if (internetProtocolFamilies.length > 2) {
throw new IllegalArgumentException("No more than 2 InternetProtocolFamilies");
}
switch (internetProtocolFamilies[0]) {
case IPv4:
return (internetProtocolFamilies.length >= 2 && internetProtocolFamilies[1] == InternetProtocolFamily.IPv6)
? ResolvedAddressTypes.IPV4_PREFERRED
: ResolvedAddressTypes.IPV4_ONLY;
case IPv6:
return (internetProtocolFamilies.length >= 2 && internetProtocolFamilies[1] == InternetProtocolFamily.IPv4)
? ResolvedAddressTypes.IPV6_PREFERRED
: ResolvedAddressTypes.IPV6_ONLY;
default:
throw new IllegalArgumentException("Couldn't resolve ResolvedAddressTypes from InternetProtocolFamily array");
}
}
/**
* Sets the list of the protocol families of the address resolved.
* You can use {@link DnsNameResolverBuilder#computeResolvedAddressTypes(InternetProtocolFamily...)}
* to get a {@link ResolvedAddressTypes} out of some {@link InternetProtocolFamily}s.
*
* @param resolvedAddressTypes the address types
*
* @return {@code this}
*/
public
DnsNameResolverBuilder resolvedAddressTypes(ResolvedAddressTypes resolvedAddressTypes) {
this.resolvedAddressTypes = resolvedAddressTypes;
return this;
}
/**
* Sets if this resolver has to send a DNS query with the RD (recursion desired) flag set.
*
* @param recursionDesired true if recursion is desired
*
* @return {@code this}
*/
public
DnsNameResolverBuilder recursionDesired(boolean recursionDesired) {
this.recursionDesired = recursionDesired;
return this;
}
/**
* Sets the maximum allowed number of DNS queries to send when resolving a host name.
*
* @param maxQueriesPerResolve the max number of queries
*
* @return {@code this}
*/
public
DnsNameResolverBuilder maxQueriesPerResolve(int maxQueriesPerResolve) {
this.maxQueriesPerResolve = maxQueriesPerResolve;
return this;
}
/**
* Sets if this resolver should generate the detailed trace information in an exception message so that
* it is easier to understand the cause of resolution failure.
*
* @param traceEnabled true if trace is enabled
*
* @return {@code this}
*/
public
DnsNameResolverBuilder traceEnabled(boolean traceEnabled) {
this.traceEnabled = traceEnabled;
return this;
}
/**
* Sets the capacity of the datagram packet buffer (in bytes). The default value is {@code 4096} bytes.
*
* @param maxPayloadSize the capacity of the datagram packet buffer
*
* @return {@code this}
*/
public
DnsNameResolverBuilder maxPayloadSize(int maxPayloadSize) {
this.maxPayloadSize = maxPayloadSize;
return this;
}
/**
* @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to first check
* if the hostname is locally aliased.
*
* @return {@code this}
*/
public
DnsNameResolverBuilder hostsFileEntriesResolver(HostsFileEntriesResolver hostsFileEntriesResolver) {
this.hostsFileEntriesResolver = hostsFileEntriesResolver;
return this;
}
/**
* Set the {@link DnsServerAddressStreamProvider} which is used to determine which DNS server is used to resolve
* each hostname.
*
* @return {@code this}.
*/
public
DnsNameResolverBuilder nameServerProvider(DnsServerAddressStreamProvider dnsServerAddressStreamProvider) {
this.dnsServerAddressStreamProvider = checkNotNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
return this;
}
/**
* Set the list of search domains of the resolver.
*
* @param searchDomains the search domains
*
* @return {@code this}
*/
public
DnsNameResolverBuilder searchDomains(Iterable<String> searchDomains) {
checkNotNull(searchDomains, "searchDomains");
final List<String> list = new ArrayList<String>(4);
for (String f : searchDomains) {
if (f == null) {
break;
}
// Avoid duplicate entries.
if (list.contains(f)) {
continue;
}
list.add(f);
}
this.searchDomains = list.toArray(new String[list.size()]);
return this;
}
/**
* Set the number of dots which must appear in a name before an initial absolute query is made.
* The default value is {@code 1}.
*
* @param ndots the ndots value
*
* @return {@code this}
*/
public
DnsNameResolverBuilder ndots(int ndots) {
this.ndots = ndots;
return this;
}
/**
* Set if domain / host names should be decoded to unicode when received.
* See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
*
* @param decodeIdn if should get decoded
*
* @return {@code this}
*/
public
DnsNameResolverBuilder decodeIdn(boolean decodeIdn) {
this.decodeIdn = decodeIdn;
return this;
}
/**
* Returns a new {@link DnsNameResolver} instance.
*
* @return a {@link DnsNameResolver}
*/
public
DnsNameResolver build() {
if (resolveCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
throw new IllegalStateException("resolveCache and TTLs are mutually exclusive");
}
if (authoritativeDnsServerCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
throw new IllegalStateException("authoritativeDnsServerCache and TTLs are mutually exclusive");
}
DnsCache resolveCache = this.resolveCache != null ? this.resolveCache : newCache();
DnsCache authoritativeDnsServerCache = this.authoritativeDnsServerCache != null ? this.authoritativeDnsServerCache : newCache();
return new DnsNameResolver(eventLoop,
channelFactory,
resolveCache,
authoritativeDnsServerCache,
dnsQueryLifecycleObserverFactory,
queryTimeoutMillis,
resolvedAddressTypes,
recursionDesired,
maxQueriesPerResolve,
traceEnabled,
maxPayloadSize,
hostsFileEntriesResolver,
dnsServerAddressStreamProvider,
searchDomains,
ndots,
decodeIdn);
}
private
DnsCache newCache() {
return new DefaultDnsCache(intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE), intValue(negativeTtl, 0));
}
}

View File

@ -0,0 +1,964 @@
/*
* 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 dorkbox.network.dns.resolver;
import static java.lang.Math.min;
import static java.util.Collections.unmodifiableList;
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;
import dorkbox.network.dns.DnsQuestion;
import dorkbox.network.dns.DnsResponse;
import dorkbox.network.dns.constants.DnsRecordType;
import dorkbox.network.dns.constants.DnsResponseCode;
import dorkbox.network.dns.constants.DnsSection;
import dorkbox.network.dns.records.AAAARecord;
import dorkbox.network.dns.records.ARecord;
import dorkbox.network.dns.records.CNAMERecord;
import dorkbox.network.dns.records.DnsMessage;
import dorkbox.network.dns.records.DnsRecord;
import dorkbox.network.dns.records.NSRecord;
import dorkbox.network.dns.resolver.addressProvider.DnsServerAddressStream;
import dorkbox.network.dns.resolver.addressProvider.DnsServerAddresses;
import dorkbox.network.dns.resolver.cache.DnsCache;
import dorkbox.network.dns.resolver.cache.DnsCacheEntry;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.ChannelPromise;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.ThrowableUtil;
abstract
class DnsNameResolverContext<T> {
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();
}
}
};
private static final RuntimeException NXDOMAIN_QUERY_FAILED_EXCEPTION =
ThrowableUtil.unknownStackTrace(new RuntimeException("No answer found and NXDOMAIN response code returned"),
DnsNameResolverContext.class,
"onResponse(..)");
private static final RuntimeException CNAME_NOT_FOUND_QUERY_FAILED_EXCEPTION =
ThrowableUtil.unknownStackTrace(new RuntimeException("No matching CNAME record found"),
DnsNameResolverContext.class,
"onResponseCNAME(..)");
private static final RuntimeException NO_MATCHING_RECORD_QUERY_FAILED_EXCEPTION =
ThrowableUtil.unknownStackTrace(new RuntimeException("No matching record type found"),
DnsNameResolverContext.class,
"onResponseAorAAAA(..)");
private static final RuntimeException UNRECOGNIZED_TYPE_QUERY_FAILED_EXCEPTION =
ThrowableUtil.unknownStackTrace(new RuntimeException("Response type was unrecognized"),
DnsNameResolverContext.class,
"onResponse(..)");
private static final RuntimeException NAME_SERVERS_EXHAUSTED_EXCEPTION =
ThrowableUtil.unknownStackTrace(new RuntimeException("No name servers returned an answer"),
DnsNameResolverContext.class,
"tryToFinishResolve(..)");
private final DnsNameResolver parent;
private final DnsServerAddressStream nameServerAddrs;
private final String hostname;
private final DnsCache resolveCache;
private final int maxAllowedQueries;
private final InternetProtocolFamily[] resolvedInternetProtocolFamilies;
private final Set<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> queriesInProgress = Collections.newSetFromMap(new IdentityHashMap<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>, Boolean>());
private List<DnsCacheEntry> resolvedEntries;
private int allowedQueries;
private boolean triedCNAME;
DnsNameResolverContext(DnsNameResolver parent,
String hostname,
DnsCache resolveCache,
DnsServerAddressStream nameServerAddrs) {
this.parent = parent;
this.hostname = hostname;
this.resolveCache = resolveCache;
this.nameServerAddrs = ObjectUtil.checkNotNull(nameServerAddrs, "nameServerAddrs");
maxAllowedQueries = parent.maxQueriesPerResolve();
resolvedInternetProtocolFamilies = parent.resolvedInternetProtocolFamiliesUnsafe();
allowedQueries = maxAllowedQueries;
}
void resolve(final Promise<T> promise) {
if (parent.searchDomains().length == 0 || parent.ndots() == 0 || StringUtil.endsWith(hostname, '.')) {
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;
}
}
doSearchDomainQuery(0, new FutureListener<T>() {
private int count = 1;
@Override
public
void operationComplete(Future<T> future) throws Exception {
if (future.isSuccess()) {
promise.trySuccess(future.getNow());
}
else if (count < parent.searchDomains().length) {
doSearchDomainQuery(count++, this);
}
else {
promise.tryFailure(new SearchDomainUnknownHostException(future.cause(), hostname));
}
}
});
}
}
private static final
class SearchDomainUnknownHostException extends UnknownHostException {
SearchDomainUnknownHostException(Throwable cause, String originalHostname) {
super("Search domain query failed. Original hostname: '" + originalHostname + "' " + cause.getMessage());
setStackTrace(cause.getStackTrace());
}
@Override
public
Throwable fillInStackTrace() {
return this;
}
}
private
void doSearchDomainQuery(int count, FutureListener<T> listener) {
DnsNameResolverContext<T> nextContext = newResolverContext(parent,
hostname + '.' + parent.searchDomains()[count],
resolveCache,
nameServerAddrs);
Promise<T> nextPromise = parent.executor()
.newPromise();
nextContext.internalResolve(nextPromise);
nextPromise.addListener(listener);
}
private
void internalResolve(Promise<T> promise) {
DnsServerAddressStream nameServerAddressStream = getNameServers(hostname);
int[] recordTypes = parent.resolveRecordTypes();
assert recordTypes.length > 0;
final int end = recordTypes.length - 1;
for (int i = 0; i < end; ++i) {
if (!resolveQuery(hostname, recordTypes[i], nameServerAddressStream.duplicate(), promise)) {
return;
}
}
resolveQuery(hostname, recordTypes[end], nameServerAddressStream, promise);
}
/**
* Add an authoritative nameserver to the cache if its not a root server.
*/
private
void addNameServerToCache(AuthoritativeNameServer name, InetAddress resolved, long ttl) {
if (!name.isRootServer()) {
// Cache NS record if not for a root server as we should never cache for root servers.
parent.authoritativeDnsServerCache()
.cache(name.domainName(), resolved, ttl, parent.ch.eventLoop());
}
}
/**
* 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;
List<DnsCacheEntry> entries = parent.authoritativeDnsServerCache().get(hostname);
if (entries != null && !entries.isEmpty()) {
return DnsServerAddresses.sequential(new DnsCacheIterable(entries))
.stream();
}
}
}
private final
class DnsCacheIterable implements Iterable<InetSocketAddress> {
private final List<DnsCacheEntry> entries;
DnsCacheIterable(List<DnsCacheEntry> entries) {
this.entries = entries;
}
@Override
public
Iterator<InetSocketAddress> iterator() {
return new Iterator<InetSocketAddress>() {
Iterator<DnsCacheEntry> entryIterator = entries.iterator();
@Override
public
boolean hasNext() {
return entryIterator.hasNext();
}
@Override
public
InetSocketAddress next() {
InetAddress address = entryIterator.next()
.address();
return new InetSocketAddress(address, parent.dnsRedirectPort(address));
}
@Override
public
void remove() {
entryIterator.remove();
}
};
}
}
private
void resolveQuery(final DnsServerAddressStream nameServerAddrStream,
final int nameServerAddrStreamIndex,
final DnsQuestion question,
final Promise<T> promise) {
resolveQuery(nameServerAddrStream,
nameServerAddrStreamIndex,
question,
parent.dnsQueryLifecycleObserverFactory()
.newDnsQueryLifecycleObserver(question),
promise);
}
private
void resolveQuery(final DnsServerAddressStream nameServerAddrStream,
final int nameServerAddrStreamIndex,
final DnsQuestion question,
final DnsQueryLifecycleObserver queryLifecycleObserver,
final Promise<T> promise) {
// question should have refCnt=2
if (nameServerAddrStreamIndex >= nameServerAddrStream.size() || allowedQueries == 0 || promise.isCancelled()) {
tryToFinishResolve(nameServerAddrStream, nameServerAddrStreamIndex, question, queryLifecycleObserver, promise);
return;
}
--allowedQueries;
final InetSocketAddress nameServerAddr = nameServerAddrStream.next();
final ChannelPromise writePromise = parent.ch.newPromise();
final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f =
parent.query0(nameServerAddr,
question,
writePromise,
parent.ch.eventLoop().<AddressedEnvelope<DnsResponse, InetSocketAddress>>newPromise());
queriesInProgress.add(f);
queryLifecycleObserver.queryWritten(nameServerAddr, writePromise);
f.addListener(new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
@Override
public
void operationComplete(Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> future) {
// future.result() should have refCnt=2
// question should have refCnt=1
queriesInProgress.remove(future);
if (promise.isDone() || future.isCancelled()) {
queryLifecycleObserver.queryCancelled(allowedQueries);
return;
}
AddressedEnvelope<DnsResponse, InetSocketAddress> envelope = future.getNow();
try {
if (future.isSuccess()) {
onResponse(nameServerAddrStream,
nameServerAddrStreamIndex,
question,
envelope,
queryLifecycleObserver,
promise);
}
else {
// Server did not respond or I/O error occurred; try again.
queryLifecycleObserver.queryFailed(future.cause());
// query uses the question again...
question.retain();
resolveQuery(nameServerAddrStream, nameServerAddrStreamIndex + 1, question, promise);
}
} finally {
// future.result() should have refCnt=2
// question should have refCnt=1
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);
}
}
});
}
void onResponse(final DnsServerAddressStream nameServerAddrStream,
final int nameServerAddrStreamIndex,
final DnsQuestion question,
AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
final DnsQueryLifecycleObserver queryLifecycleObserver,
Promise<T> promise) {
final DnsResponse res = envelope.content();
final int code = res.getHeader()
.getRcode();
if (code == DnsResponseCode.NOERROR) {
if (handleRedirect(question, envelope, queryLifecycleObserver, promise)) {
// Was a redirect so return here as everything else is handled in handleRedirect(...)
return;
}
final int type = question.getQuestion()
.getType();
if (type == DnsRecordType.A || type == DnsRecordType.AAAA) {
onResponseAorAAAA(type, question, envelope, queryLifecycleObserver, promise);
}
else if (type == DnsRecordType.CNAME) {
onResponseCNAME(question, envelope, queryLifecycleObserver, promise);
}
else {
queryLifecycleObserver.queryFailed(UNRECOGNIZED_TYPE_QUERY_FAILED_EXCEPTION);
}
return;
}
// Retry with the next server if the server did not tell us that the domain does not exist.
if (code != DnsResponseCode.NXDOMAIN) {
resolveQuery(nameServerAddrStream, nameServerAddrStreamIndex + 1, question, queryLifecycleObserver.queryNoAnswer(code), promise);
}
else {
queryLifecycleObserver.queryFailed(NXDOMAIN_QUERY_FAILED_EXCEPTION);
}
}
/**
* 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<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.
DnsRecord[] answerArray = res.getSectionArray(DnsSection.ANSWER);
if (answerArray.length == 0) {
AuthoritativeNameServerList serverNames = extractAuthoritativeNameServers(question.getQuestion()
.getName()
.toString(), res);
if (serverNames != null) {
List<InetSocketAddress> nameServers = new ArrayList<InetSocketAddress>(serverNames.size());
DnsRecord[] additionalArray = res.getSectionArray(DnsSection.ADDITIONAL);
for (int i = 0; i < additionalArray.length; i++) {
final DnsRecord r = additionalArray[i];
if (r.getType() == DnsRecordType.A && !parent.supportsARecords() ||
r.getType() == DnsRecordType.AAAA && !parent.supportsAAAARecords()) {
continue;
}
final String recordName = r.getName()
.toString();
AuthoritativeNameServer authoritativeNameServer = serverNames.remove(recordName);
if (authoritativeNameServer == null) {
// Not a server we are interested in.
continue;
}
InetAddress resolved = parseAddress(r, recordName);
if (resolved == null) {
// Could not parse it, move to the next.
continue;
}
nameServers.add(new InetSocketAddress(resolved, parent.dnsRedirectPort(resolved)));
addNameServerToCache(authoritativeNameServer, resolved, r.getTTL());
}
if (!nameServers.isEmpty()) {
resolveQuery(parent.uncachedRedirectDnsServerStream(nameServers),
0,
question,
queryLifecycleObserver.queryRedirected(unmodifiableList(nameServers)),
promise);
return true;
}
}
}
return false;
}
/**
* 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) {
DnsRecord[] authority = res.getSectionArray(DnsSection.AUTHORITY);
if (authority.length == 0) {
return null;
}
System.err.println("TYODO");
AuthoritativeNameServerList serverNames = new AuthoritativeNameServerList(questionName);
for (int i = 0; i < authority.length; i++) {
final DnsRecord dnsRecord = authority[i];
serverNames.add(dnsRecord);
}
return serverNames;
}
private
void onResponseAorAAAA(int qType,
DnsMessage question,
AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
final DnsQueryLifecycleObserver queryLifecycleObserver,
Promise<T> promise) {
// We often get a bunch of CNAMES as well when we asked for A/AAAA.
final DnsResponse response = envelope.content();
final Map<String, String> cnames = buildAliasMap(response);
DnsRecord[] answerArray = response.getSectionArray(DnsSection.ANSWER);
boolean found = false;
for (int i = 0; i < answerArray.length; i++) {
final DnsRecord r = answerArray[i];
final int type = r.getType();
if (type != DnsRecordType.A && type != DnsRecordType.AAAA) {
continue;
}
final String questionName = question.getQuestion()
.getName()
.toString();
final String recordName = r.getName()
.toString();
// Make sure the record is for the questioned domain.
if (!recordName.equals(questionName)) {
// 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 = cnames.get(resolved);
if (recordName.equals(resolved)) {
break;
}
} while (resolved != null);
if (resolved == null) {
continue;
}
}
InetAddress resolved = parseAddress(r, hostname);
if (resolved == null) {
continue;
}
if (resolvedEntries == null) {
resolvedEntries = new ArrayList<DnsCacheEntry>(8);
}
final DnsCacheEntry e = new DnsCacheEntry(hostname, resolved);
resolveCache.cache(hostname, resolved, r.getTTL(), parent.ch.eventLoop());
resolvedEntries.add(e);
found = true;
// Note that we do not break from the loop here, so we decode/cache all A/AAAA records.
}
if (found) {
queryLifecycleObserver.querySucceed();
return;
}
if (cnames.isEmpty()) {
queryLifecycleObserver.queryFailed(NO_MATCHING_RECORD_QUERY_FAILED_EXCEPTION);
}
else {
// We asked for A/AAAA but we got only CNAME.
onResponseCNAME(question, envelope, cnames, queryLifecycleObserver, promise);
}
}
private
InetAddress parseAddress(DnsRecord record, String name) {
int type = record.getType();
if (type == DnsRecordType.A) {
ARecord aRecord = (ARecord) record;
return aRecord.getAddress();
}
else if (type == DnsRecordType.AAAA) {
AAAARecord aaaaRecord = (AAAARecord) record;
return aaaaRecord.getAddress();
}
else {
return null;
}
}
private
void onResponseCNAME(DnsMessage question,
AddressedEnvelope<DnsResponse, InetSocketAddress> envelope,
final DnsQueryLifecycleObserver queryLifecycleObserver,
Promise<T> promise) {
onResponseCNAME(question, envelope, buildAliasMap(envelope.content()), queryLifecycleObserver, promise);
}
private
void onResponseCNAME(DnsMessage question,
AddressedEnvelope<DnsResponse, InetSocketAddress> response,
Map<String, String> cnames,
final DnsQueryLifecycleObserver queryLifecycleObserver,
Promise<T> promise) {
// Resolve the host name in the question into the real host name.
String resolved = question.getQuestion()
.getName()
.toString();
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(resolved, queryLifecycleObserver, promise);
}
else {
queryLifecycleObserver.queryFailed(CNAME_NOT_FOUND_QUERY_FAILED_EXCEPTION);
}
}
private static
Map<String, String> buildAliasMap(DnsMessage response) {
DnsRecord[] answerArray = response.getSectionArray(DnsSection.ANSWER);
Map<String, String> cnames = null;
int length = answerArray.length;
for (int i = 0; i < length; i++) {
final DnsRecord record = answerArray[i];
final int type = record.getType();
if (type != DnsRecordType.CNAME) {
continue;
}
System.err.println("CHECK ME ME! we don't have bytebuf content in this fashion anymore");
CNAMERecord re = (CNAMERecord) record;
final String domainName = re.getAlias()
.toString();
if (domainName == null) {
continue;
}
if (cnames == null) {
cnames = new HashMap<String, String>(min(8, length));
}
cnames.put(record.getName()
.toString()
.toLowerCase(Locale.US), domainName.toLowerCase(Locale.US));
}
return cnames != null ? cnames : Collections.<String, String>emptyMap();
}
void tryToFinishResolve(final DnsServerAddressStream nameServerAddrStream,
final int nameServerAddrStreamIndex,
final DnsQuestion question,
final DnsQueryLifecycleObserver queryLifecycleObserver,
final Promise<T> promise) {
// There are no queries left to try.
if (!queriesInProgress.isEmpty()) {
queryLifecycleObserver.queryCancelled(allowedQueries);
// 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.
finishResolve(promise, question);
}
// 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.
if (resolvedEntries == null) {
if (nameServerAddrStreamIndex < nameServerAddrStream.size()) {
// the query is going to use the question again...
question.retain();
if (queryLifecycleObserver == NoopDnsQueryLifecycleObserver.INSTANCE) {
// If the queryLifecycleObserver has already been terminated we should create a new one for this
// fresh query.
resolveQuery(nameServerAddrStream, nameServerAddrStreamIndex + 1, question, promise);
}
else {
resolveQuery(nameServerAddrStream, nameServerAddrStreamIndex + 1, question, queryLifecycleObserver, promise);
}
return;
}
queryLifecycleObserver.queryFailed(NAME_SERVERS_EXHAUSTED_EXCEPTION);
// .. 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;
resolveQuery(hostname, DnsRecordType.CNAME, getNameServers(hostname), promise);
return;
}
}
else {
queryLifecycleObserver.queryCancelled(allowedQueries);
}
// We have at least one resolved address or tried CNAME as the last resort..
finishResolve(promise, question);
}
private
boolean gotPreferredAddress() {
if (resolvedEntries == null) {
return false;
}
final int size = resolvedEntries.size();
final Class<? extends InetAddress> inetAddressType = parent.preferredAddressType()
.addressType();
for (int i = 0; i < size; i++) {
InetAddress address = resolvedEntries.get(i)
.address();
if (inetAddressType.isInstance(address)) {
return true;
}
}
return false;
}
private
void finishResolve(Promise<T> promise, final DnsQuestion question) {
// now we are done with the question.
question.release();
if (!queriesInProgress.isEmpty()) {
// If there are queries in progress, we should cancel it because we already finished the resolution.
for (Iterator<Future<AddressedEnvelope<DnsResponse, InetSocketAddress>>> i = queriesInProgress.iterator(); i.hasNext(); ) {
Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> f = i.next();
i.remove();
if (!f.cancel(false)) {
f.addListener(RELEASE_RESPONSE);
}
}
}
if (resolvedEntries != null) {
// Found at least one resolved address.
for (InternetProtocolFamily f : resolvedInternetProtocolFamilies) {
if (finishResolve(f.addressType(), resolvedEntries, promise)) {
return;
}
}
}
// No resolved address found.
final int tries = maxAllowedQueries - allowedQueries;
final StringBuilder buf = new StringBuilder(64);
buf.append("failed to resolve '")
.append(hostname)
.append('\'');
if (tries > 1) {
if (tries < maxAllowedQueries) {
buf.append(" after ")
.append(tries)
.append(" queries ");
}
else {
buf.append(". Exceeded max queries per resolve ")
.append(maxAllowedQueries)
.append(' ');
}
}
final UnknownHostException cause = new UnknownHostException(buf.toString());
cause.setStackTrace(new StackTraceElement[0]);
resolveCache.cache(hostname, cause, parent.ch.eventLoop());
promise.tryFailure(cause);
}
abstract
boolean finishResolve(Class<? extends InetAddress> addressType, List<DnsCacheEntry> resolvedEntries, Promise<T> promise);
abstract
DnsNameResolverContext<T> newResolverContext(DnsNameResolver parent,
String hostname,
DnsCache resolveCache,
DnsServerAddressStream nameServerAddrs);
private
DnsServerAddressStream getNameServers(String hostname) {
DnsServerAddressStream stream = getNameServersFromCache(hostname);
return stream == null ? nameServerAddrs : stream;
}
private
void followCname(String cname, final DnsQueryLifecycleObserver queryLifecycleObserver, Promise<T> promise) {
// Use the same server for both CNAME queries
DnsServerAddressStream stream = DnsServerAddresses.singleton(getNameServers(cname).next())
.stream();
DnsQuestion cnameQuestion = null;
try {
if (parent.supportsARecords()) {
cnameQuestion = DnsQuestion.newResolveQuestion(hostname, DnsRecordType.A, parent.isRecursionDesired());
}
if (parent.supportsAAAARecords()) {
cnameQuestion = DnsQuestion.newResolveQuestion(hostname, DnsRecordType.AAAA, parent.isRecursionDesired());
}
} catch (Throwable cause) {
queryLifecycleObserver.queryFailed(cause);
PlatformDependent.throwException(cause);
}
if (cnameQuestion != null) {
resolveQuery(stream, 0, cnameQuestion, queryLifecycleObserver.queryCNAMEd(cnameQuestion), promise);
}
}
private
boolean resolveQuery(String hostname, int type, DnsServerAddressStream dnsServerAddressStream, Promise<T> promise) {
DnsQuestion message = DnsQuestion.newResolveQuestion(hostname, type, parent.isRecursionDesired());
if (message == null) {
return false;
}
resolveQuery(dnsServerAddressStream, 0, message, promise);
return true;
}
/**
* 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 count;
AuthoritativeNameServerList(String questionName) {
this.questionName = questionName.toLowerCase(Locale.US);
}
void add(DnsRecord record) {
if (record.getType() != DnsRecordType.NS) {
return;
}
// Only include servers that serve the correct domain.
String recordName = record.getName()
.toString();
if (questionName.length() < recordName.length()) {
return;
}
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;
}
System.err.println("DOUBLE CHECK me! we do things differently now!");
NSRecord re = (NSRecord) record;
final String domainName = re.getAdditionalName()
.toString();
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) {
count = 1;
head = new AuthoritativeNameServer(dots, recordName, domainName);
}
else if (head.dots == dots) {
AuthoritativeNameServer serverName = head;
while (serverName.next != null) {
serverName = serverName.next;
}
serverName.next = new AuthoritativeNameServer(dots, recordName, domainName);
count++;
}
}
// Just walk the linked-list and mark the entry as removed when matched, so next lookup will need to process
// one node less.
AuthoritativeNameServer remove(String nsName) {
AuthoritativeNameServer serverName = head;
while (serverName != null) {
if (!serverName.removed && serverName.nsName.equalsIgnoreCase(nsName)) {
serverName.removed = true;
return serverName;
}
serverName = serverName.next;
}
return null;
}
int size() {
return count;
}
}
static final
class AuthoritativeNameServer {
final int dots;
final String nsName;
final String domainName;
AuthoritativeNameServer next;
boolean removed;
AuthoritativeNameServer(int dots, String domainName, String nsName) {
this.dots = dots;
this.nsName = nsName;
this.domainName = domainName;
}
/**
* Returns {@code true} if its a root server.
*/
boolean isRootServer() {
return dots == 1;
}
/**
* The domain for which the {@link AuthoritativeNameServer} is responsible.
*/
String domainName() {
return domainName;
}
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver;
import java.net.InetSocketAddress;
import dorkbox.network.dns.DnsQuestion;
import dorkbox.network.dns.records.DnsMessage;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.UnstableApi;
/**
* A {@link RuntimeException} raised when {@link DnsResolver} failed to perform a successful query.
*/
@UnstableApi
public final
class DnsNameResolverException extends RuntimeException {
private static final long serialVersionUID = -8826717909627131850L;
private final InetSocketAddress remoteAddress;
private final DnsQuestion question;
public
DnsNameResolverException(InetSocketAddress remoteAddress, DnsQuestion question, String message) {
super(message);
this.remoteAddress = validateRemoteAddress(remoteAddress);
this.question = validateQuestion(question);
}
private static
InetSocketAddress validateRemoteAddress(InetSocketAddress remoteAddress) {
return ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
}
private static
DnsQuestion validateQuestion(DnsQuestion question) {
return ObjectUtil.checkNotNull(question, "question");
}
public
DnsNameResolverException(InetSocketAddress remoteAddress, DnsQuestion question, String message, Throwable cause) {
super(message, cause);
this.remoteAddress = validateRemoteAddress(remoteAddress);
this.question = validateQuestion(question);
}
/**
* Returns the {@link InetSocketAddress} of the DNS query that has failed.
*/
public
InetSocketAddress remoteAddress() {
return remoteAddress;
}
/**
* Returns the {@link DnsQuestion} of the DNS query that has failed.
*/
public
DnsMessage question() {
return question;
}
@Override
public
Throwable fillInStackTrace() {
setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
return this;
}
}

View File

@ -0,0 +1,70 @@
/*
* 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 dorkbox.network.dns.resolver;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import dorkbox.network.dns.resolver.addressProvider.DnsServerAddressStream;
import dorkbox.network.dns.resolver.cache.DnsCache;
import dorkbox.network.dns.resolver.cache.DnsCacheEntry;
import io.netty.util.concurrent.Promise;
/**
*
*/
final
class DnsNameResolverListResolverContext extends DnsNameResolverContext<List<InetAddress>> {
DnsNameResolverListResolverContext(DnsNameResolver parent,
String hostname,
DnsCache resolveCache,
DnsServerAddressStream nameServerAddrs) {
super(parent, hostname, resolveCache, nameServerAddrs);
}
@Override
DnsNameResolverContext<List<InetAddress>> newResolverContext(DnsNameResolver parent,
String hostname,
DnsCache resolveCache,
DnsServerAddressStream nameServerAddrs) {
return new DnsNameResolverListResolverContext(parent, hostname, resolveCache, nameServerAddrs);
}
@Override
boolean finishResolve(Class<? extends InetAddress> addressType,
List<DnsCacheEntry> resolvedEntries,
Promise<List<InetAddress>> promise) {
List<InetAddress> result = null;
final int numEntries = resolvedEntries.size();
for (int i = 0; i < numEntries; i++) {
final InetAddress a = resolvedEntries.get(i).address();
if (addressType.isInstance(a)) {
if (result == null) {
result = new ArrayList<InetAddress>(numEntries);
}
result.add(a);
}
}
if (result != null) {
promise.trySuccess(result);
return true;
}
return false;
}
}

View File

@ -0,0 +1,67 @@
/*
* 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 dorkbox.network.dns.resolver;
import dorkbox.network.dns.DnsResponse;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.concurrent.Promise;
final
class DnsNameResolverResponseHandler extends ChannelInboundHandlerAdapter {
private DnsNameResolver dnsNameResolver;
private final Promise<Channel> channelActivePromise;
DnsNameResolverResponseHandler(final DnsNameResolver dnsNameResolver, Promise<Channel> channelActivePromise) {
this.dnsNameResolver = dnsNameResolver;
this.channelActivePromise = channelActivePromise;
}
@Override
public
void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
channelActivePromise.setSuccess(ctx.channel());
}
@Override
public
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
final DnsResponse response = (DnsResponse) msg;
final int queryId = response.getHeader().getID();
if (DnsNameResolver.logger.isDebugEnabled()) {
DnsNameResolver.logger.debug("{} RECEIVED: [{}: {}], {}", dnsNameResolver.ch, queryId, response.sender(), response);
}
final DnsQueryContext qCtx = dnsNameResolver.queryContextManager.get(response.sender(), queryId);
if (qCtx == null) {
DnsNameResolver.logger.warn("{} Received a DNS response with an unknown ID: {}", dnsNameResolver.ch, queryId);
return;
}
qCtx.finish(response);
}
@Override
public
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
DnsNameResolver.logger.warn("{} Unexpected exception: ", dnsNameResolver.ch, cause);
}
}

View File

@ -0,0 +1,62 @@
/*
* 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 dorkbox.network.dns.resolver;
import java.net.InetAddress;
import java.util.List;
import dorkbox.network.dns.resolver.addressProvider.DnsServerAddressStream;
import dorkbox.network.dns.resolver.cache.DnsCache;
import dorkbox.network.dns.resolver.cache.DnsCacheEntry;
import io.netty.util.concurrent.Promise;
/**
*
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
final
class DnsNameResolverSingleResolverContext extends DnsNameResolverContext<InetAddress> {
DnsNameResolverSingleResolverContext(DnsNameResolver parent,
String hostname,
DnsCache resolveCache,
DnsServerAddressStream nameServerAddrs) {
super(parent, hostname, resolveCache, nameServerAddrs);
}
@Override
boolean finishResolve(Class<? extends InetAddress> addressType, List<DnsCacheEntry> resolvedEntries, Promise<InetAddress> promise) {
final int numEntries = resolvedEntries.size();
for (int i = 0; i < numEntries; i++) {
final InetAddress a = resolvedEntries.get(i).address();
if (addressType.isInstance(a)) {
DnsNameResolver.trySuccess(promise, a);
return true;
}
}
return false;
}
@Override
DnsNameResolverContext<InetAddress> newResolverContext(DnsNameResolver parent,
String hostname,
DnsCache resolveCache,
DnsServerAddressStream nameServerAddrs) {
return new DnsNameResolverSingleResolverContext(parent, hostname, resolveCache, nameServerAddrs);
}
}

View File

@ -0,0 +1,233 @@
/*
* 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 dorkbox.network.dns.resolver;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import java.net.InetSocketAddress;
import dorkbox.network.dns.DnsQuestion;
import dorkbox.network.dns.DnsResponse;
import dorkbox.network.dns.constants.DnsSection;
import dorkbox.network.dns.records.DnsRecord;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
final
class DnsQueryContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsQueryContext.class);
private final DnsNameResolver parent;
private final Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> promise;
private final int id;
private final DnsQuestion question;
private final InetSocketAddress nameServerAddr;
private volatile ScheduledFuture<?> timeoutFuture;
DnsQueryContext(DnsNameResolver parent,
InetSocketAddress nameServerAddr,
DnsQuestion question,
Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> promise) {
this.parent = checkNotNull(parent, "parent");
this.nameServerAddr = checkNotNull(nameServerAddr, "nameServerAddr");
this.question = checkNotNull(question, "question");
this.promise = checkNotNull(promise, "promise");
id = parent.queryContextManager.add(this);
question.init(id, nameServerAddr);
}
void query(ChannelPromise writePromise) {
final DnsQuestion question = question();
final InetSocketAddress nameServerAddr = nameServerAddr();
if (logger.isDebugEnabled()) {
logger.debug("{} WRITE: [{}: {}], {}", parent.ch, id, nameServerAddr, question);
}
sendQuery(question, writePromise);
}
InetSocketAddress nameServerAddr() {
return nameServerAddr;
}
DnsQuestion question() {
return question;
}
private
void sendQuery(final DnsQuestion query, final ChannelPromise writePromise) {
if (parent.channelFuture.isDone()) {
writeQuery(query, writePromise);
}
else {
parent.channelFuture.addListener(new GenericFutureListener<Future<? super Channel>>() {
@Override
public
void operationComplete(Future<? super Channel> future) throws Exception {
if (future.isSuccess()) {
writeQuery(query, writePromise);
}
else {
Throwable cause = future.cause();
promise.tryFailure(cause);
writePromise.setFailure(cause);
}
}
});
}
}
private
void writeQuery(final DnsQuestion query, final ChannelPromise writePromise) {
final ChannelFuture writeFuture = parent.ch.writeAndFlush(query, writePromise);
if (writeFuture.isDone()) {
onQueryWriteCompletion(writeFuture);
}
else {
writeFuture.addListener(new ChannelFutureListener() {
@Override
public
void operationComplete(ChannelFuture future) throws Exception {
onQueryWriteCompletion(writeFuture);
}
});
}
}
private
void onQueryWriteCompletion(ChannelFuture writeFuture) {
if (!writeFuture.isSuccess()) {
writeFuture.cause()
.printStackTrace();
setFailure("failed to send a query", writeFuture.cause());
return;
}
// Schedule a query timeout task if necessary.
final long queryTimeoutMillis = parent.queryTimeoutMillis();
if (queryTimeoutMillis > 0) {
// TODO UNCOMMENT!
// timeoutFuture = parent.ch.eventLoop()
// .schedule(new Runnable() {
// @Override
// public
// void run() {
// if (promise.isDone()) {
// // Received a response before the query times out.
// return;
// }
//
// setFailure("query timed out after " + queryTimeoutMillis + " milliseconds", null);
// }
// }, queryTimeoutMillis, TimeUnit.MILLISECONDS);
}
}
private
void setFailure(String message, Throwable cause) {
final InetSocketAddress nameServerAddr = nameServerAddr();
parent.queryContextManager.remove(nameServerAddr, id);
final StringBuilder buf = new StringBuilder(message.length() + 64);
buf.append('[')
.append(nameServerAddr)
.append("] ")
.append(message)
.append(" (no stack trace available)");
final DnsNameResolverException e;
if (cause != null) {
e = new DnsNameResolverException(nameServerAddr, question(), buf.toString(), cause);
}
else {
e = new DnsNameResolverException(nameServerAddr, question(), buf.toString());
}
promise.tryFailure(e);
}
void finish(AddressedEnvelope<DnsResponse, InetSocketAddress> envelope) {
final DnsResponse response = envelope.content();
try {
DnsRecord[] sectionArray = response.getSectionArray(DnsSection.QUESTION);
if (sectionArray.length != 1) {
logger.warn("Received a DNS response with invalid number of questions: {}", envelope);
return;
}
DnsRecord[] questionArray = question.getSectionArray(DnsSection.QUESTION);
if (questionArray.length != 1) {
logger.warn("Received a DNS response with invalid number of query questions: {}", envelope);
return;
}
if (!questionArray[0].equals(sectionArray[0])) {
logger.warn("Received a mismatching DNS response: {}", envelope);
return;
}
setSuccess(envelope);
} finally {
if (question.isResolveQuestion()) {
// for resolve questions (always A/AAAA), we convert the answer into InetAddress, however with OTHER TYPES, we pass
// back the result to the user, and if we release it, all of the content will be cleared.
response.release();
}
}
}
private
void setSuccess(AddressedEnvelope<DnsResponse, InetSocketAddress> envelope) {
parent.queryContextManager.remove(nameServerAddr(), id);
// Cancel the timeout task.
final ScheduledFuture<?> timeoutFuture = this.timeoutFuture;
if (timeoutFuture != null) {
timeoutFuture.cancel(false);
}
Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> promise = this.promise;
if (promise.setUncancellable()) {
@SuppressWarnings("unchecked")
AddressedEnvelope<DnsResponse, InetSocketAddress> castResponse = envelope.retain();
// envelope now has a refCnt = 2
if (!promise.trySuccess(castResponse)) { // question is used here!
// We failed to notify the promise as it was failed before, thus we need to release the envelope
envelope.release();
}
envelope.release();
}
}
}

View File

@ -0,0 +1,156 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package dorkbox.network.dns.resolver;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import io.netty.util.NetUtil;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap;
import io.netty.util.internal.PlatformDependent;
final
class DnsQueryContextManager {
/**
* A map whose key is the DNS server address and value is the map of the DNS query ID and its corresponding {@link DnsQueryContext}.
*/
final Map<InetSocketAddress, IntObjectMap<DnsQueryContext>> map = new HashMap<InetSocketAddress, IntObjectMap<DnsQueryContext>>();
int add(DnsQueryContext queryContext) {
final IntObjectMap<DnsQueryContext> contexts = getOrCreateContextMap(queryContext.nameServerAddr());
int id = PlatformDependent.threadLocalRandom()
.nextInt(65536 - 1) + 1;
final int maxTries = 65535 << 1;
int tries = 0;
synchronized (contexts) {
for (; ; ) {
if (!contexts.containsKey(id)) {
contexts.put(id, queryContext);
return id;
}
id = id + 1 & 0xFFFF;
if (++tries >= maxTries) {
throw new IllegalStateException("query ID space exhausted: " + queryContext.question());
}
}
}
}
private
IntObjectMap<DnsQueryContext> getOrCreateContextMap(InetSocketAddress nameServerAddr) {
synchronized (map) {
final IntObjectMap<DnsQueryContext> contexts = map.get(nameServerAddr);
if (contexts != null) {
return contexts;
}
final IntObjectMap<DnsQueryContext> newContexts = new IntObjectHashMap<DnsQueryContext>();
final InetAddress a = nameServerAddr.getAddress();
final int port = nameServerAddr.getPort();
map.put(nameServerAddr, newContexts);
if (a instanceof Inet4Address) {
// Also add the mapping for the IPv4-compatible IPv6 address.
final Inet4Address a4 = (Inet4Address) a;
if (a4.isLoopbackAddress()) {
map.put(new InetSocketAddress(NetUtil.LOCALHOST6, port), newContexts);
}
else {
map.put(new InetSocketAddress(toCompactAddress(a4), port), newContexts);
}
}
else if (a instanceof Inet6Address) {
// Also add the mapping for the IPv4 address if this IPv6 address is compatible.
final Inet6Address a6 = (Inet6Address) a;
if (a6.isLoopbackAddress()) {
map.put(new InetSocketAddress(NetUtil.LOCALHOST4, port), newContexts);
}
else if (a6.isIPv4CompatibleAddress()) {
map.put(new InetSocketAddress(toIPv4Address(a6), port), newContexts);
}
}
return newContexts;
}
}
private static
Inet6Address toCompactAddress(Inet4Address a4) {
byte[] b4 = a4.getAddress();
byte[] b6 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, b4[0], b4[1], b4[2], b4[3]};
try {
return (Inet6Address) InetAddress.getByAddress(b6);
} catch (UnknownHostException e) {
throw new Error(e);
}
}
private static
Inet4Address toIPv4Address(Inet6Address a6) {
byte[] b6 = a6.getAddress();
byte[] b4 = {b6[12], b6[13], b6[14], b6[15]};
try {
return (Inet4Address) InetAddress.getByAddress(b4);
} catch (UnknownHostException e) {
throw new Error(e);
}
}
DnsQueryContext get(InetSocketAddress nameServerAddr, int id) {
final IntObjectMap<DnsQueryContext> contexts = getContextMap(nameServerAddr);
final DnsQueryContext qCtx;
if (contexts != null) {
synchronized (contexts) {
qCtx = contexts.get(id);
}
}
else {
qCtx = null;
}
return qCtx;
}
private
IntObjectMap<DnsQueryContext> getContextMap(InetSocketAddress nameServerAddr) {
synchronized (map) {
return map.get(nameServerAddr);
}
}
DnsQueryContext remove(InetSocketAddress nameServerAddr, int id) {
final IntObjectMap<DnsQueryContext> contexts = getContextMap(nameServerAddr);
if (contexts == null) {
return null;
}
synchronized (contexts) {
return contexts.remove(id);
}
}
}

View File

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

View File

@ -0,0 +1,35 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver;
import dorkbox.network.dns.records.DnsMessage;
import io.netty.util.internal.UnstableApi;
/**
* Used to generate new instances of {@link DnsQueryLifecycleObserver}.
*/
@UnstableApi
public
interface DnsQueryLifecycleObserverFactory {
/**
* Create a new instance of a {@link DnsQueryLifecycleObserver}. This will be called at the start of a new query.
*
* @param question The question being asked.
*
* @return a new instance of a {@link DnsQueryLifecycleObserver}.
*/
DnsQueryLifecycleObserver newDnsQueryLifecycleObserver(DnsMessage question);
}

View File

@ -0,0 +1,39 @@
package dorkbox.network.dns.resolver;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;
import io.netty.resolver.AddressResolver;
import io.netty.resolver.SimpleNameResolver;
import io.netty.util.concurrent.EventExecutor;
public abstract class InetNameGroupResolver extends SimpleNameResolver<List<InetAddress>> {
private volatile AddressResolver<InetSocketAddress> addressResolver;
/**
* @param executor the {@link EventExecutor} which is used to notify the listeners of the {@link Future} returned
* by {@link #resolve(String)}
*/
protected
InetNameGroupResolver(EventExecutor executor) {
super(executor);
}
/**
* Return a {@link AddressResolver} that will use this name resolver underneath.
* It's cached internally, so the same instance is always returned.
*/
public AddressResolver<InetSocketAddress> asAddressResolver() {
AddressResolver<InetSocketAddress> result = addressResolver;
if (result == null) {
synchronized (this) {
result = addressResolver;
if (result == null) {
addressResolver = result = new InetSocketAddressGroupResolver(executor(), this);
}
}
}
return result;
}
}

View File

@ -0,0 +1,86 @@
package dorkbox.network.dns.resolver;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import io.netty.resolver.AbstractAddressResolver;
import io.netty.resolver.NameResolver;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
public class InetSocketAddressGroupResolver extends AbstractAddressResolver<InetSocketAddress> {
final NameResolver<List<InetAddress>> nameResolver;
/**
* @param executor the {@link EventExecutor} which is used to notify the listeners of the {@link Future} returned
* by {@link #resolve(java.net.SocketAddress)}
* @param nameResolver the {@link NameResolver} used for name resolution
*/
public
InetSocketAddressGroupResolver(EventExecutor executor, NameResolver<List<InetAddress>> nameResolver) {
super(executor, InetSocketAddress.class);
this.nameResolver = nameResolver;
}
@Override
protected boolean doIsResolved(InetSocketAddress address) {
return !address.isUnresolved();
}
@Override
protected void doResolve(final InetSocketAddress unresolvedAddress, final Promise<InetSocketAddress> promise) throws Exception {
// Note that InetSocketAddress.getHostName() will never incur a reverse lookup here,
// because an unresolved address always has a host name.
nameResolver.resolve(unresolvedAddress.getHostName())
.addListener(new FutureListener<List<InetAddress>>() {
@Override
public void operationComplete(Future<List<InetAddress>> future) throws Exception {
if (future.isSuccess()) {
ArrayList<InetSocketAddress> arrayList = new ArrayList<InetSocketAddress>();
List<InetAddress> now = future.getNow();
for (InetAddress inetAddress : now) {
arrayList.add(new InetSocketAddress(inetAddress, unresolvedAddress.getPort()));
}
// promise.setSuccess(arrayList);
} else {
promise.setFailure(future.cause());
}
}
});
}
@Override
protected void doResolveAll(final InetSocketAddress unresolvedAddress, final Promise<List<InetSocketAddress>> promise) throws Exception {
// Note that InetSocketAddress.getHostName() will never incur a reverse lookup here,
// because an unresolved address always has a host name.
nameResolver.resolveAll(unresolvedAddress.getHostName())
.addListener(new FutureListener<List<List<InetAddress>>>() {
@Override
public void operationComplete(Future<List<List<InetAddress>>> future) throws Exception {
if (future.isSuccess()) {
List<List<InetAddress>> inetAddresseses = future.getNow();
List<InetSocketAddress> socketAddresses = new ArrayList<InetSocketAddress>(inetAddresseses.size());
for (List<InetAddress> inetAddresses : inetAddresseses) {
for (InetAddress inetAddress : inetAddresses) {
socketAddresses.add(new InetSocketAddress(inetAddress, unresolvedAddress.getPort()));
}
}
promise.setSuccess(socketAddresses);
} else {
promise.setFailure(future.cause());
}
}
});
}
@Override
public void close() {
nameResolver.close();
}
}

View File

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

View File

@ -0,0 +1,64 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver;
import java.util.List;
import dorkbox.network.dns.resolver.addressProvider.DnsServerAddressStream;
import dorkbox.network.dns.resolver.addressProvider.DnsServerAddressStreamProvider;
import io.netty.util.internal.UnstableApi;
/**
* A {@link DnsServerAddressStreamProvider} which iterates through a collection of
* {@link DnsServerAddressStreamProvider} until the first non-{@code null} result is found.
*/
@UnstableApi
public final
class MultiDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider {
private final DnsServerAddressStreamProvider[] providers;
/**
* Create a new instance.
*
* @param providers The providers to use for DNS resolution. They will be queried in order.
*/
public
MultiDnsServerAddressStreamProvider(List<DnsServerAddressStreamProvider> providers) {
this.providers = providers.toArray(new DnsServerAddressStreamProvider[0]);
}
/**
* Create a new instance.
*
* @param providers The providers to use for DNS resolution. They will be queried in order.
*/
public
MultiDnsServerAddressStreamProvider(DnsServerAddressStreamProvider... providers) {
this.providers = providers.clone();
}
@Override
public
DnsServerAddressStream nameServerAddressStream(String hostname) {
for (DnsServerAddressStreamProvider provider : providers) {
DnsServerAddressStream stream = provider.nameServerAddressStream(hostname);
if (stream != null) {
return stream;
}
}
return null;
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver;
import java.net.InetSocketAddress;
import java.util.List;
import dorkbox.network.dns.records.DnsMessage;
import io.netty.channel.ChannelFuture;
final
class NoopDnsQueryLifecycleObserver implements DnsQueryLifecycleObserver {
static final NoopDnsQueryLifecycleObserver INSTANCE = new NoopDnsQueryLifecycleObserver();
private
NoopDnsQueryLifecycleObserver() {
}
@Override
public
void queryWritten(InetSocketAddress dnsServerAddress, ChannelFuture future) {
}
@Override
public
void queryCancelled(int queriesRemaining) {
}
@Override
public
DnsQueryLifecycleObserver queryRedirected(List<InetSocketAddress> nameServers) {
return this;
}
@Override
public
DnsQueryLifecycleObserver queryCNAMEd(DnsMessage cnameQuestion) {
return this;
}
@Override
public
DnsQueryLifecycleObserver queryNoAnswer(int code) {
return this;
}
@Override
public
void queryFailed(Throwable cause) {
}
@Override
public
void querySucceed() {
}
}

View File

@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.nettyxbill.resolver.dns;
package dorkbox.network.dns.resolver;
import dorkbox.network.dns.records.DnsMessage;
import io.netty.util.internal.UnstableApi;

View File

@ -0,0 +1,46 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import dorkbox.network.dns.records.DnsMessage;
import io.netty.util.internal.logging.InternalLogLevel;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
final
class TraceDnsQueryLifeCycleObserverFactory implements DnsQueryLifecycleObserverFactory {
private static final InternalLogger DEFAULT_LOGGER = InternalLoggerFactory.getInstance(TraceDnsQueryLifeCycleObserverFactory.class);
private static final InternalLogLevel DEFAULT_LEVEL = InternalLogLevel.DEBUG;
private final InternalLogger logger;
private final InternalLogLevel level;
TraceDnsQueryLifeCycleObserverFactory() {
this(DEFAULT_LOGGER, DEFAULT_LEVEL);
}
TraceDnsQueryLifeCycleObserverFactory(InternalLogger logger, InternalLogLevel level) {
this.logger = checkNotNull(logger, "logger");
this.level = checkNotNull(level, "level");
}
@Override
public
DnsQueryLifecycleObserver newDnsQueryLifecycleObserver(DnsMessage question) {
return new TraceDnsQueryLifecycleObserver(question, logger, level);
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import java.net.InetSocketAddress;
import java.util.List;
import dorkbox.network.dns.records.DnsMessage;
import io.netty.channel.ChannelFuture;
import io.netty.util.internal.logging.InternalLogLevel;
import io.netty.util.internal.logging.InternalLogger;
final
class TraceDnsQueryLifecycleObserver implements DnsQueryLifecycleObserver {
private final InternalLogger logger;
private final InternalLogLevel level;
private final DnsMessage question;
private InetSocketAddress dnsServerAddress;
TraceDnsQueryLifecycleObserver(DnsMessage question, InternalLogger logger, InternalLogLevel level) {
this.question = checkNotNull(question, "question");
this.logger = checkNotNull(logger, "logger");
this.level = checkNotNull(level, "level");
}
@Override
public
void queryWritten(InetSocketAddress dnsServerAddress, ChannelFuture future) {
this.dnsServerAddress = dnsServerAddress;
}
@Override
public
void queryCancelled(int queriesRemaining) {
if (dnsServerAddress != null) {
logger.log(level, "from {} : {} cancelled with {} queries remaining", dnsServerAddress, question, queriesRemaining);
}
else {
logger.log(level, "{} query never written and cancelled with {} queries remaining", question, queriesRemaining);
}
}
@Override
public
DnsQueryLifecycleObserver queryRedirected(List<InetSocketAddress> nameServers) {
logger.log(level, "from {} : {} redirected", dnsServerAddress, question);
return this;
}
@Override
public
DnsQueryLifecycleObserver queryCNAMEd(DnsMessage cnameQuestion) {
logger.log(level, "from {} : {} CNAME question {}", dnsServerAddress, question, cnameQuestion);
return this;
}
@Override
public
DnsQueryLifecycleObserver queryNoAnswer(int code) {
logger.log(level, "from {} : {} no answer {}", dnsServerAddress, question, code);
return this;
}
@Override
public
void queryFailed(Throwable cause) {
if (dnsServerAddress != null) {
logger.log(level, "from {} : {} failure", dnsServerAddress, question, cause);
}
else {
logger.log(level, "{} query never written and failed", question, cause);
}
}
@Override
public
void querySucceed() {
}
}

View File

@ -0,0 +1,168 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.addressProvider;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import io.netty.util.internal.SocketUtils;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
/**
* A {@link DnsServerAddressStreamProvider} which will use predefined default DNS servers to use for DNS resolution.
* These defaults do not respect your host's machines defaults.
* <p>
* This may use the JDK's blocking DNS resolution to bootstrap the default DNS server addresses.
*/
@UnstableApi
public final
class DefaultDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultDnsServerAddressStreamProvider.class);
public static final DefaultDnsServerAddressStreamProvider INSTANCE = new DefaultDnsServerAddressStreamProvider();
private static final List<InetSocketAddress> DEFAULT_NAME_SERVER_LIST;
private static final InetSocketAddress[] DEFAULT_NAME_SERVER_ARRAY;
private static final DnsServerAddresses DEFAULT_NAME_SERVERS;
public static final int DNS_PORT = 53;
static {
final List<InetSocketAddress> defaultNameServers = new ArrayList<InetSocketAddress>(2);
// Using jndi-dns to obtain the default name servers.
//
// See:
// - http://docs.oracle.com/javase/8/docs/technotes/guides/jndi/jndi-dns.html
// - http://mail.openjdk.java.net/pipermail/net-dev/2017-March/010695.html
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
env.put("java.naming.provider.url", "dns://");
try {
DirContext ctx = new InitialDirContext(env);
String dnsUrls = (String) ctx.getEnvironment()
.get("java.naming.provider.url");
String[] servers = dnsUrls.split(" ");
for (String server : servers) {
try {
defaultNameServers.add(SocketUtils.socketAddress(new URI(server).getHost(), DNS_PORT));
} catch (URISyntaxException e) {
logger.debug("Skipping a malformed nameserver URI: {}", server, e);
}
}
} catch (NamingException ignore) {
// Will try reflection if this fails.
}
if (defaultNameServers.isEmpty()) {
try {
Class<?> configClass = Class.forName("sun.net.dns.ResolverConfiguration");
Method open = configClass.getMethod("open");
Method nameservers = configClass.getMethod("nameservers");
Object instance = open.invoke(null);
@SuppressWarnings("unchecked")
final List<String> list = (List<String>) nameservers.invoke(instance);
for (String a : list) {
if (a != null) {
defaultNameServers.add(new InetSocketAddress(SocketUtils.addressByName(a), DNS_PORT));
}
}
} catch (Exception ignore) {
// Failed to get the system name server list via reflection.
// Will add the default name servers afterwards.
}
}
if (!defaultNameServers.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug("Default DNS servers: {} (sun.net.dns.ResolverConfiguration)", defaultNameServers);
}
}
else {
Collections.addAll(defaultNameServers,
SocketUtils.socketAddress("8.8.8.8", DNS_PORT),
SocketUtils.socketAddress("8.8.4.4", DNS_PORT));
if (logger.isWarnEnabled()) {
logger.warn("Default DNS servers: {} (Google Public DNS as a fallback)", defaultNameServers);
}
}
DEFAULT_NAME_SERVER_LIST = Collections.unmodifiableList(defaultNameServers);
DEFAULT_NAME_SERVER_ARRAY = defaultNameServers.toArray(new InetSocketAddress[defaultNameServers.size()]);
DEFAULT_NAME_SERVERS = DnsServerAddresses.sequential(DEFAULT_NAME_SERVER_ARRAY);
}
private
DefaultDnsServerAddressStreamProvider() {
}
@Override
public
DnsServerAddressStream nameServerAddressStream(String hostname) {
return DEFAULT_NAME_SERVERS.stream();
}
/**
* Returns the list of the system DNS server addresses. If it failed to retrieve the list of the system DNS server
* addresses from the environment, it will return {@code "8.8.8.8"} and {@code "8.8.4.4"}, the addresses of the
* Google public DNS servers.
*/
public static
List<InetSocketAddress> defaultAddressList() {
return DEFAULT_NAME_SERVER_LIST;
}
/**
* Returns the {@link DnsServerAddresses} that yields the system DNS server addresses sequentially. If it failed to
* retrieve the list of the system DNS server addresses from the environment, it will use {@code "8.8.8.8"} and
* {@code "8.8.4.4"}, the addresses of the Google public DNS servers.
* <p>
* This method has the same effect with the following code:
* <pre>
* DnsServerAddresses.sequential(DnsServerAddresses.defaultAddressList());
* </pre>
* </p>
*/
public static
DnsServerAddresses defaultAddresses() {
return DEFAULT_NAME_SERVERS;
}
/**
* Get the array form of {@link #defaultAddressList()}.
*
* @return The array form of {@link #defaultAddressList()}.
*/
static
InetSocketAddress[] defaultAddressArray() {
return DEFAULT_NAME_SERVER_ARRAY.clone();
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.addressProvider;
import java.net.InetSocketAddress;
abstract
class DefaultDnsServerAddresses extends DnsServerAddresses {
protected final InetSocketAddress[] addresses;
private final String strVal;
DefaultDnsServerAddresses(String type, InetSocketAddress[] addresses) {
this.addresses = addresses;
final StringBuilder buf = new StringBuilder(type.length() + 2 + addresses.length * 16);
buf.append(type)
.append('(');
for (InetSocketAddress a : addresses) {
buf.append(a)
.append(", ");
}
buf.setLength(buf.length() - 2);
buf.append(')');
strVal = buf.toString();
}
@Override
public
String toString() {
return strVal;
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.addressProvider;
import java.net.InetSocketAddress;
import io.netty.util.internal.UnstableApi;
/**
* An infinite stream of DNS server addresses.
*/
@UnstableApi
public
interface DnsServerAddressStream {
/**
* Retrieves the next DNS server address from the stream.
*/
InetSocketAddress next();
/**
* Get the number of times {@link #next()} will return a distinct element before repeating or terminating.
*
* @return the number of times {@link #next()} will return a distinct element before repeating or terminating.
*/
int size();
/**
* Duplicate this object. The result of this should be able to be independently iterated over via {@link #next()}.
* <p>
* Note that {@link #clone()} isn't used because it may make sense for some implementations to have the following
* relationship {@code x.duplicate() == x}.
*
* @return A duplicate of this object.
*/
DnsServerAddressStream duplicate();
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.addressProvider;
import io.netty.util.internal.UnstableApi;
/**
* Provides an opportunity to override which {@link DnsServerAddressStream} is used to resolve a specific hostname.
* <p>
* For example this can be used to represent <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> and
* <a href="https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a>.
*/
@UnstableApi
public
interface DnsServerAddressStreamProvider {
/**
* Ask this provider for the name servers to query for {@code hostname}.
*
* @param hostname The hostname for which to lookup the DNS server addressed to use.
* If this is the final {@link DnsServerAddressStreamProvider} to be queried then generally empty
* string or {@code '.'} correspond to the default {@link DnsServerAddressStream}.
*
* @return The {@link DnsServerAddressStream} which should be used to resolve {@code hostname}.
*/
DnsServerAddressStream nameServerAddressStream(String hostname);
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.addressProvider;
import io.netty.util.internal.UnstableApi;
/**
* Utility methods related to {@link DnsServerAddressStreamProvider}.
*/
@UnstableApi
public final
class DnsServerAddressStreamProviders {
// TODO(scott): how is this done on Windows? This may require a JNI call to GetNetworkParams
// https://msdn.microsoft.com/en-us/library/aa365968(VS.85).aspx.
private static final DnsServerAddressStreamProvider DEFAULT_DNS_SERVER_ADDRESS_STREAM_PROVIDER = UnixResolverDnsServerAddressStreamProvider.parseSilently();
private
DnsServerAddressStreamProviders() {
}
/**
* A {@link DnsServerAddressStreamProvider} which inherits the DNS servers from your local host's configuration.
* <p>
* Note that only macOS and Linux are currently supported.
*
* @return A {@link DnsServerAddressStreamProvider} which inherits the DNS servers from your local host's
* configuration.
*/
public static
DnsServerAddressStreamProvider platformDefault() {
return DEFAULT_DNS_SERVER_ADDRESS_STREAM_PROVIDER;
}
}

View File

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

View File

@ -0,0 +1,50 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.addressProvider;
import java.net.InetSocketAddress;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
final
class RotationalDnsServerAddresses extends DefaultDnsServerAddresses {
private static final AtomicIntegerFieldUpdater<RotationalDnsServerAddresses> startIdxUpdater = AtomicIntegerFieldUpdater.newUpdater(
RotationalDnsServerAddresses.class,
"startIdx");
@SuppressWarnings("UnusedDeclaration")
private volatile int startIdx;
RotationalDnsServerAddresses(InetSocketAddress[] addresses) {
super("rotational", addresses);
}
@Override
public
DnsServerAddressStream stream() {
for (; ; ) {
int curStartIdx = startIdx;
int nextStartIdx = curStartIdx + 1;
if (nextStartIdx >= addresses.length) {
nextStartIdx = 0;
}
if (startIdxUpdater.compareAndSet(this, curStartIdx, nextStartIdx)) {
return new SequentialDnsServerAddressStream(addresses, curStartIdx);
}
}
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package dorkbox.network.dns.resolver.addressProvider;
import java.net.InetSocketAddress;
final
class SequentialDnsServerAddressStream implements DnsServerAddressStream {
private final InetSocketAddress[] addresses;
private int i;
SequentialDnsServerAddressStream(InetSocketAddress[] addresses, int startIdx) {
this.addresses = addresses;
i = startIdx;
}
@Override
public
InetSocketAddress next() {
int i = this.i;
InetSocketAddress next = addresses[i];
if (++i < addresses.length) {
this.i = i;
}
else {
this.i = 0;
}
return next;
}
@Override
public
int size() {
return addresses.length;
}
@Override
public
SequentialDnsServerAddressStream duplicate() {
return new SequentialDnsServerAddressStream(addresses, i);
}
@Override
public
String toString() {
return toString("sequential", i, addresses);
}
static
String toString(String type, int index, InetSocketAddress[] addresses) {
final StringBuilder buf = new StringBuilder(type.length() + 2 + addresses.length * 16);
buf.append(type)
.append("(index: ")
.append(index);
buf.append(", addrs: (");
for (InetSocketAddress a : addresses) {
buf.append(a)
.append(", ");
}
buf.setLength(buf.length() - 2);
buf.append("))");
return buf.toString();
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.addressProvider;
import java.net.InetSocketAddress;
import io.netty.util.internal.UnstableApi;
/**
* A {@link DnsServerAddressStreamProvider} which is backed by a sequential list of DNS servers.
*/
@UnstableApi
public final
class SequentialDnsServerAddressStreamProvider
extends UniSequentialDnsServerAddressStreamProvider {
/**
* Create a new instance.
*
* @param addresses The addresses which will be be returned in sequential order via
* {@link #nameServerAddressStream(String)}
*/
public
SequentialDnsServerAddressStreamProvider(InetSocketAddress... addresses) {
super(DnsServerAddresses.sequential(addresses));
}
/**
* Create a new instance.
*
* @param addresses The addresses which will be be returned in sequential order via
* {@link #nameServerAddressStream(String)}
*/
public
SequentialDnsServerAddressStreamProvider(Iterable<? extends InetSocketAddress> addresses) {
super(DnsServerAddresses.sequential(addresses));
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package dorkbox.network.dns.resolver.addressProvider;
import java.net.InetSocketAddress;
import java.util.Random;
import io.netty.util.internal.PlatformDependent;
final
class ShuffledDnsServerAddressStream implements DnsServerAddressStream {
private final InetSocketAddress[] addresses;
private int i;
/**
* Create a new instance.
*
* @param addresses The addresses are not cloned. It is assumed the caller has cloned this array or otherwise will
* not modify the contents.
*/
ShuffledDnsServerAddressStream(InetSocketAddress[] addresses) {
this.addresses = addresses;
shuffle();
}
private
void shuffle() {
final InetSocketAddress[] addresses = this.addresses;
final Random r = PlatformDependent.threadLocalRandom();
for (int i = addresses.length - 1; i >= 0; i--) {
InetSocketAddress tmp = addresses[i];
int j = r.nextInt(i + 1);
addresses[i] = addresses[j];
addresses[j] = tmp;
}
}
private
ShuffledDnsServerAddressStream(InetSocketAddress[] addresses, int startIdx) {
this.addresses = addresses;
i = startIdx;
}
@Override
public
InetSocketAddress next() {
int i = this.i;
InetSocketAddress next = addresses[i];
if (++i < addresses.length) {
this.i = i;
}
else {
this.i = 0;
shuffle();
}
return next;
}
@Override
public
int size() {
return addresses.length;
}
@Override
public
ShuffledDnsServerAddressStream duplicate() {
return new ShuffledDnsServerAddressStream(addresses, i);
}
@Override
public
String toString() {
return SequentialDnsServerAddressStream.toString("shuffled", i, addresses);
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.addressProvider;
import java.net.InetSocketAddress;
import io.netty.util.internal.UnstableApi;
/**
* A {@link DnsServerAddressStreamProvider} which always uses a single DNS server for resolution.
*/
@UnstableApi
public final
class SingletonDnsServerAddressStreamProvider
extends UniSequentialDnsServerAddressStreamProvider {
/**
* Create a new instance.
*
* @param address The singleton address to use for every DNS resolution.
*/
public
SingletonDnsServerAddressStreamProvider(final InetSocketAddress address) {
super(DnsServerAddresses.singleton(address));
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package dorkbox.network.dns.resolver.addressProvider;
import java.net.InetSocketAddress;
final
class SingletonDnsServerAddresses extends DnsServerAddresses {
private final InetSocketAddress address;
private final DnsServerAddressStream stream = new DnsServerAddressStream() {
@Override
public
InetSocketAddress next() {
return address;
}
@Override
public
int size() {
return 1;
}
@Override
public
DnsServerAddressStream duplicate() {
return this;
}
@Override
public
String toString() {
return SingletonDnsServerAddresses.this.toString();
}
};
SingletonDnsServerAddresses(InetSocketAddress address) {
this.address = address;
}
@Override
public
DnsServerAddressStream stream() {
return stream;
}
@Override
public
String toString() {
return "singleton(" + address + ")";
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.addressProvider;
import io.netty.util.internal.ObjectUtil;
/**
* A {@link DnsServerAddressStreamProvider} which is backed by a single {@link DnsServerAddresses}.
*/
abstract
class UniSequentialDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider {
private final DnsServerAddresses addresses;
UniSequentialDnsServerAddressStreamProvider(DnsServerAddresses addresses) {
this.addresses = ObjectUtil.checkNotNull(addresses, "addresses");
}
@Override
public final
DnsServerAddressStream nameServerAddressStream(String hostname) {
return addresses.stream();
}
}

View File

@ -0,0 +1,312 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.addressProvider;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.StringUtil.indexOfNonWhiteSpace;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.netty.util.NetUtil;
import io.netty.util.internal.SocketUtils;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
/**
* Able to parse files such as <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> and
* <a href="https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a> to respect the system default domain servers.
*/
@UnstableApi
public final
class UnixResolverDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(UnixResolverDnsServerAddressStreamProvider.class);
private static final String ETC_RESOLV_CONF_FILE = "/etc/resolv.conf";
private static final String ETC_RESOLVER_DIR = "/etc/resolver";
private static final String NAMESERVER_ROW_LABEL = "nameserver";
private static final String SORTLIST_ROW_LABEL = "sortlist";
private static final String OPTIONS_ROW_LABEL = "options";
private static final String DOMAIN_ROW_LABEL = "domain";
private static final String PORT_ROW_LABEL = "port";
private static final String NDOTS_LABEL = "ndots:";
public static final int DEFAULT_NDOTS = 1;
private final DnsServerAddresses defaultNameServerAddresses;
private final Map<String, DnsServerAddresses> domainToNameServerStreamMap;
/**
* Attempt to parse {@code /etc/resolv.conf} and files in the {@code /etc/resolver} directory by default.
* A failure to parse will return {@link DefaultDnsServerAddressStreamProvider}.
*/
static
DnsServerAddressStreamProvider parseSilently() {
try {
UnixResolverDnsServerAddressStreamProvider nameServerCache = new UnixResolverDnsServerAddressStreamProvider(ETC_RESOLV_CONF_FILE,
ETC_RESOLVER_DIR);
return nameServerCache.mayOverrideNameServers() ? nameServerCache : DefaultDnsServerAddressStreamProvider.INSTANCE;
} catch (Exception e) {
logger.debug("failed to parse {} and/or {}", ETC_RESOLV_CONF_FILE, ETC_RESOLVER_DIR, e);
return DefaultDnsServerAddressStreamProvider.INSTANCE;
}
}
private
boolean mayOverrideNameServers() {
return !domainToNameServerStreamMap.isEmpty() || defaultNameServerAddresses.stream()
.next() != null;
}
/**
* Parse a file of the format <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> which may contain
* the default DNS server to use, and also overrides for individual domains. Also parse a directory of the format
* <a href="
* https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a> which may contain multiple files to override the name servers used for multimple domains.
*
* @param etcResolvConf <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a>.
* @param etcResolverDir Directory containing files of the format defined in
* <a href="
* https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a>.
*
* @throws IOException If an error occurs while parsing the input files.
*/
public
UnixResolverDnsServerAddressStreamProvider(String etcResolvConf, String etcResolverDir) throws IOException {
this(etcResolvConf == null ? null : new File(etcResolvConf), etcResolverDir == null ? null : new File(etcResolverDir).listFiles());
}
/**
* Parse a file of the format <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> which may contain
* the default DNS server to use, and also overrides for individual domains. Also parse list of files of the format
* <a href="
* https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a> which may contain multiple files to override the name servers used for multimple domains.
*
* @param etcResolvConf <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a>.
* @param etcResolverFiles List of files of the format defined in
* <a href="
* https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/resolver.5.html">
* /etc/resolver</a>.
*
* @throws IOException If an error occurs while parsing the input files.
*/
public
UnixResolverDnsServerAddressStreamProvider(File etcResolvConf, File... etcResolverFiles) throws IOException {
Map<String, DnsServerAddresses> etcResolvConfMap = parse(checkNotNull(etcResolvConf, "etcResolvConf"));
final boolean useEtcResolverFiles = etcResolverFiles != null && etcResolverFiles.length != 0;
domainToNameServerStreamMap = useEtcResolverFiles ? parse(etcResolverFiles) : etcResolvConfMap;
DnsServerAddresses defaultNameServerAddresses = etcResolvConfMap.get(etcResolvConf.getName());
if (defaultNameServerAddresses == null) {
Collection<DnsServerAddresses> values = etcResolvConfMap.values();
if (values.isEmpty()) {
throw new IllegalArgumentException(etcResolvConf + " didn't provide any name servers");
}
this.defaultNameServerAddresses = values.iterator()
.next();
}
else {
this.defaultNameServerAddresses = defaultNameServerAddresses;
}
if (useEtcResolverFiles) {
domainToNameServerStreamMap.putAll(etcResolvConfMap);
}
}
private static
Map<String, DnsServerAddresses> parse(File... etcResolverFiles) throws IOException {
Map<String, DnsServerAddresses> domainToNameServerStreamMap = new HashMap<String, DnsServerAddresses>(etcResolverFiles.length << 1);
for (File etcResolverFile : etcResolverFiles) {
if (!etcResolverFile.isFile()) {
continue;
}
FileReader fr = new FileReader(etcResolverFile);
BufferedReader br = null;
try {
br = new BufferedReader(fr);
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>(2);
String domainName = etcResolverFile.getName();
int port = DefaultDnsServerAddressStreamProvider.DNS_PORT;
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
char c;
if (line.isEmpty() || (c = line.charAt(0)) == '#' || c == ';') {
continue;
}
if (line.startsWith(NAMESERVER_ROW_LABEL)) {
int i = indexOfNonWhiteSpace(line, NAMESERVER_ROW_LABEL.length());
if (i < 0) {
throw new IllegalArgumentException("error parsing label " + NAMESERVER_ROW_LABEL + " in file " +
etcResolverFile + ". value: " + line);
}
String maybeIP = line.substring(i);
// There may be a port appended onto the IP address so we attempt to extract it.
if (!NetUtil.isValidIpV4Address(maybeIP) && !NetUtil.isValidIpV6Address(maybeIP)) {
i = maybeIP.lastIndexOf('.');
if (i + 1 >= maybeIP.length()) {
throw new IllegalArgumentException("error parsing label " + NAMESERVER_ROW_LABEL + " in file " +
etcResolverFile + ". invalid IP value: " + line);
}
port = Integer.parseInt(maybeIP.substring(i + 1));
maybeIP = maybeIP.substring(0, i);
}
addresses.add(SocketUtils.socketAddress(maybeIP, port));
}
else if (line.startsWith(DOMAIN_ROW_LABEL)) {
int i = indexOfNonWhiteSpace(line, DOMAIN_ROW_LABEL.length());
if (i < 0) {
throw new IllegalArgumentException("error parsing label " + DOMAIN_ROW_LABEL + " in file " + etcResolverFile +
" value: " + line);
}
domainName = line.substring(i);
if (!addresses.isEmpty()) {
putIfAbsent(domainToNameServerStreamMap, domainName, addresses);
}
addresses = new ArrayList<InetSocketAddress>(2);
}
else if (line.startsWith(PORT_ROW_LABEL)) {
int i = indexOfNonWhiteSpace(line, PORT_ROW_LABEL.length());
if (i < 0) {
throw new IllegalArgumentException("error parsing label " + PORT_ROW_LABEL + " in file " + etcResolverFile +
" value: " + line);
}
port = Integer.parseInt(line.substring(i));
}
else if (line.startsWith(SORTLIST_ROW_LABEL)) {
logger.info("row type {} not supported. ignoring line: {}", SORTLIST_ROW_LABEL, line);
}
}
if (!addresses.isEmpty()) {
putIfAbsent(domainToNameServerStreamMap, domainName, addresses);
}
} finally {
if (br == null) {
fr.close();
}
else {
br.close();
}
}
}
return domainToNameServerStreamMap;
}
private static
void putIfAbsent(Map<String, DnsServerAddresses> domainToNameServerStreamMap, String domainName, List<InetSocketAddress> addresses) {
// TODO(scott): sortlist is being ignored.
putIfAbsent(domainToNameServerStreamMap, domainName, DnsServerAddresses.sequential(addresses));
}
private static
void putIfAbsent(Map<String, DnsServerAddresses> domainToNameServerStreamMap, String domainName, DnsServerAddresses addresses) {
DnsServerAddresses existingAddresses = domainToNameServerStreamMap.put(domainName, addresses);
if (existingAddresses != null) {
domainToNameServerStreamMap.put(domainName, existingAddresses);
logger.debug("Domain name {} already maps to addresses {} so new addresses {} will be discarded",
domainName,
existingAddresses,
addresses);
}
}
@Override
public
DnsServerAddressStream nameServerAddressStream(String hostname) {
for (; ; ) {
int i = hostname.indexOf('.', 1);
if (i < 0 || i == hostname.length() - 1) {
return defaultNameServerAddresses.stream();
}
DnsServerAddresses addresses = domainToNameServerStreamMap.get(hostname);
if (addresses != null) {
return addresses.stream();
}
hostname = hostname.substring(i + 1);
}
}
/**
* Parse a file of the format <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> and return the
* value corresponding to the first ndots in an options configuration.
*
* @return the value corresponding to the first ndots in an options configuration, or {@link #DEFAULT_NDOTS} if not
* found.
*
* @throws IOException If a failure occurs parsing the file.
*/
public static
int parseEtcResolverFirstNdots() throws IOException {
return parseEtcResolverFirstNdots(new File(ETC_RESOLV_CONF_FILE));
}
/**
* Parse a file of the format <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a> and return the
* value corresponding to the first ndots in an options configuration.
*
* @param etcResolvConf a file of the format <a href="https://linux.die.net/man/5/resolver">/etc/resolv.conf</a>.
*
* @return the value corresponding to the first ndots in an options configuration, or {@link #DEFAULT_NDOTS} if not
* found.
*
* @throws IOException If a failure occurs parsing the file.
*/
static
int parseEtcResolverFirstNdots(File etcResolvConf) throws IOException {
FileReader fr = new FileReader(etcResolvConf);
BufferedReader br = null;
try {
br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {
if (line.startsWith(OPTIONS_ROW_LABEL)) {
int i = line.indexOf(NDOTS_LABEL);
if (i >= 0) {
i += NDOTS_LABEL.length();
final int j = line.indexOf(' ', i);
return Integer.parseInt(line.substring(i, j < 0 ? line.length() : j));
}
break;
}
}
} finally {
if (br == null) {
fr.close();
}
else {
br.close();
}
}
return DEFAULT_NDOTS;
}
}

View File

@ -0,0 +1,224 @@
/*
* Copyright 2016 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.cache;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import dorkbox.network.dns.records.DnsRecord;
import io.netty.channel.EventLoop;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.UnstableApi;
/**
* Default implementation of {@link DnsCache}, backed by a {@link ConcurrentMap}.
* If any additional {@link DnsRecord} is used, no caching takes place.
*/
@UnstableApi
public class DefaultDnsCache implements DnsCache {
private final ConcurrentMap<String, List<DnsCacheEntry>> resolveCache = PlatformDependent.newConcurrentHashMap();
private final int minTtl;
private final int maxTtl;
private final int negativeTtl;
/**
* Create a cache that respects the TTL returned by the DNS server
* and doesn't cache negative responses.
*/
public DefaultDnsCache() {
this(0, Integer.MAX_VALUE, 0);
}
/**
* Create a cache.
* @param minTtl the minimum TTL
* @param maxTtl the maximum TTL
* @param negativeTtl the TTL for failed queries
*/
public DefaultDnsCache(int minTtl, int maxTtl, int negativeTtl) {
this.minTtl = checkPositiveOrZero(minTtl, "minTtl");
this.maxTtl = checkPositiveOrZero(maxTtl, "maxTtl");
if (minTtl > maxTtl) {
throw new IllegalArgumentException(
"minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)");
}
this.negativeTtl = checkPositiveOrZero(negativeTtl, "negativeTtl");
}
/**
* Returns the minimum TTL of the cached DNS resource records (in seconds).
*
* @see #maxTtl()
*/
public int minTtl() {
return minTtl;
}
/**
* Returns the maximum TTL of the cached DNS resource records (in seconds).
*
* @see #minTtl()
*/
public int maxTtl() {
return maxTtl;
}
/**
* Returns the TTL of the cache for the failed DNS queries (in seconds). The default value is {@code 0}, which
* disables the cache for negative results.
*/
public int negativeTtl() {
return negativeTtl;
}
@Override
public void clear() {
for (Iterator<Map.Entry<String, List<DnsCacheEntry>>> i = resolveCache.entrySet().iterator(); i.hasNext();) {
final Map.Entry<String, List<DnsCacheEntry>> e = i.next();
i.remove();
cancelExpiration(e.getValue());
}
}
@Override
public boolean clear(String hostname) {
checkNotNull(hostname, "hostname");
boolean removed = false;
for (Iterator<Map.Entry<String, List<DnsCacheEntry>>> i = resolveCache.entrySet().iterator(); i.hasNext();) {
final Map.Entry<String, List<DnsCacheEntry>> e = i.next();
if (e.getKey().equals(hostname)) {
i.remove();
cancelExpiration(e.getValue());
removed = true;
}
}
return removed;
}
@Override
public List<DnsCacheEntry> get(String hostname) {
checkNotNull(hostname, "hostname");
return resolveCache.get(hostname);
}
private List<DnsCacheEntry> cachedEntries(String hostname) {
List<DnsCacheEntry> oldEntries = resolveCache.get(hostname);
final List<DnsCacheEntry> entries;
if (oldEntries == null) {
List<DnsCacheEntry> newEntries = new ArrayList<DnsCacheEntry>(8);
oldEntries = resolveCache.putIfAbsent(hostname, newEntries);
entries = oldEntries != null? oldEntries : newEntries;
} else {
entries = oldEntries;
}
return entries;
}
@Override
public void cache(String hostname, InetAddress address, long originalTtl, EventLoop loop) {
checkNotNull(hostname, "hostname");
checkNotNull(address, "address");
checkNotNull(loop, "loop");
if (maxTtl == 0) {
return;
}
final int ttl = Math.max(minTtl, (int) Math.min(maxTtl, originalTtl));
final List<DnsCacheEntry> entries = cachedEntries(hostname);
final DnsCacheEntry e = new DnsCacheEntry(hostname, address);
synchronized (entries) {
if (!entries.isEmpty()) {
final DnsCacheEntry firstEntry = entries.get(0);
if (firstEntry.cause() != null) {
assert entries.size() == 1;
firstEntry.cancelExpiration();
entries.clear();
}
}
entries.add(e);
}
scheduleCacheExpiration(entries, e, ttl, loop);
}
@Override
public void cache(String hostname, Throwable cause, EventLoop loop) {
checkNotNull(hostname, "hostname");
checkNotNull(cause, "cause");
checkNotNull(loop, "loop");
if (negativeTtl == 0) {
return;
}
final List<DnsCacheEntry> entries = cachedEntries(hostname);
final DnsCacheEntry e = new DnsCacheEntry(hostname, cause);
synchronized (entries) {
final int numEntries = entries.size();
for (int i = 0; i < numEntries; i ++) {
entries.get(i).cancelExpiration();
}
entries.clear();
entries.add(e);
}
scheduleCacheExpiration(entries, e, negativeTtl, loop);
}
private static void cancelExpiration(List<DnsCacheEntry> entries) {
final int numEntries = entries.size();
for (int i = 0; i < numEntries; i++) {
entries.get(i).cancelExpiration();
}
}
private void scheduleCacheExpiration(final List<DnsCacheEntry> entries,
final DnsCacheEntry e,
int ttl,
EventLoop loop) {
e.scheduleExpiration(loop, new Runnable() {
@Override
public void run() {
synchronized (entries) {
entries.remove(e);
if (entries.isEmpty()) {
resolveCache.remove(e.hostname());
}
}
}
}, ttl, TimeUnit.SECONDS);
}
@Override
public String toString() {
return new StringBuilder()
.append("DefaultDnsCache(minTtl=")
.append(minTtl).append(", maxTtl=")
.append(maxTtl).append(", negativeTtl=")
.append(negativeTtl).append(", cached resolved hostname=")
.append(resolveCache.size()).append(")")
.toString();
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 2016 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.cache;
import java.net.InetAddress;
import java.util.List;
import io.netty.channel.EventLoop;
import io.netty.util.internal.UnstableApi;
/**
* A cache for DNS resolution entries.
*/
@UnstableApi
public interface DnsCache {
/**
* Clears all the resolved addresses cached by this resolver.
*
* @see #clear(String)
*/
void clear();
/**
* Clears the resolved addresses of the specified host name from the cache of this resolver.
*
* @return {@code true} if and only if there was an entry for the specified host name in the cache and
* it has been removed by this method
*/
boolean clear(String hostname);
/**
* Return the cached entries for the given hostname.
* @param hostname the hostname
* @return the cached entries
*/
List<DnsCacheEntry> get(String hostname);
/**
* Cache a resolved address for a given hostname.
* @param hostname the hostname
* @param address the resolved address
* @param originalTtl the TLL as returned by the DNS server
* @param loop the {@link EventLoop} used to register the TTL timeout
*/
void cache(String hostname, InetAddress address, long originalTtl, EventLoop loop);
/**
* Cache the resolution failure for a given hostname.
* @param hostname the hostname
* @param cause the resolution failure
* @param loop the {@link EventLoop} used to register the TTL timeout
*/
void cache(String hostname, Throwable cause, EventLoop loop);
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package dorkbox.network.dns.resolver.cache;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import java.net.InetAddress;
import java.util.concurrent.TimeUnit;
import io.netty.channel.EventLoop;
import io.netty.util.concurrent.ScheduledFuture;
import io.netty.util.internal.UnstableApi;
/**
* Entry in {@link DnsCache}.
*/
@UnstableApi
public final class DnsCacheEntry {
private final String hostname;
private final InetAddress address;
private final Throwable cause;
private volatile ScheduledFuture<?> expirationFuture;
public DnsCacheEntry(String hostname, InetAddress address) {
this.hostname = checkNotNull(hostname, "hostname");
this.address = checkNotNull(address, "address");
cause = null;
}
public DnsCacheEntry(String hostname, Throwable cause) {
this.hostname = checkNotNull(hostname, "hostname");
this.cause = checkNotNull(cause, "cause");
address = null;
}
public String hostname() {
return hostname;
}
public InetAddress address() {
return address;
}
public Throwable cause() {
return cause;
}
void scheduleExpiration(EventLoop loop, Runnable task, long delay, TimeUnit unit) {
assert expirationFuture == null: "expiration task scheduled already";
expirationFuture = loop.schedule(task, delay, unit);
}
void cancelExpiration() {
ScheduledFuture<?> expirationFuture = this.expirationFuture;
if (expirationFuture != null) {
expirationFuture.cancel(false);
}
}
@Override
public String toString() {
if (cause != null) {
return hostname + '/' + cause;
} else {
return address.toString();
}
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2016 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* 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 dorkbox.network.dns.resolver.cache;
import java.net.InetAddress;
import java.util.Collections;
import java.util.List;
import io.netty.channel.EventLoop;
import io.netty.util.internal.UnstableApi;
/**
* A noop DNS cache that actually never caches anything.
*/
@UnstableApi
public final class NoopDnsCache implements DnsCache {
public static final NoopDnsCache INSTANCE = new NoopDnsCache();
/**
* Private singleton constructor.
*/
private NoopDnsCache() {
}
@Override
public void clear() {
}
@Override
public boolean clear(String hostname) {
return false;
}
@Override
public List<DnsCacheEntry> get(String hostname) {
return Collections.emptyList();
}
@Override
public void cache(String hostname, InetAddress address, long originalTtl, EventLoop loop) {
}
@Override
public void cache(String hostname, Throwable cause, EventLoop loop) {
}
@Override
public String toString() {
return NoopDnsCache.class.getSimpleName();
}
}