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:
parent
084a85df5c
commit
762215304d
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
406
src/dorkbox/network/dns/resolver/DnsNameResolverBuilder.java
Normal file
406
src/dorkbox/network/dns/resolver/DnsNameResolverBuilder.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
964
src/dorkbox/network/dns/resolver/DnsNameResolverContext.java
Normal file
964
src/dorkbox/network/dns/resolver/DnsNameResolverContext.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
233
src/dorkbox/network/dns/resolver/DnsQueryContext.java
Normal file
233
src/dorkbox/network/dns/resolver/DnsQueryContext.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
156
src/dorkbox/network/dns/resolver/DnsQueryContextManager.java
Normal file
156
src/dorkbox/network/dns/resolver/DnsQueryContextManager.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
src/dorkbox/network/dns/resolver/DnsQueryLifecycleObserver.java
Normal file
111
src/dorkbox/network/dns/resolver/DnsQueryLifecycleObserver.java
Normal 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();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
39
src/dorkbox/network/dns/resolver/InetNameGroupResolver.java
Normal file
39
src/dorkbox/network/dns/resolver/InetNameGroupResolver.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
149
src/dorkbox/network/dns/resolver/InflightNameResolver.java
Normal file
149
src/dorkbox/network/dns/resolver/InflightNameResolver.java
Normal 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 + ')';
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,7 @@
|
|||||||
* License for the specific language governing permissions and limitations
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
package io.nettyxbill.resolver.dns;
|
package dorkbox.network.dns.resolver;
|
||||||
|
|
||||||
import dorkbox.network.dns.records.DnsMessage;
|
import dorkbox.network.dns.records.DnsMessage;
|
||||||
import io.netty.util.internal.UnstableApi;
|
import io.netty.util.internal.UnstableApi;
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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 + ")";
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
224
src/dorkbox/network/dns/resolver/cache/DefaultDnsCache.java
vendored
Normal file
224
src/dorkbox/network/dns/resolver/cache/DefaultDnsCache.java
vendored
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
68
src/dorkbox/network/dns/resolver/cache/DnsCache.java
vendored
Normal file
68
src/dorkbox/network/dns/resolver/cache/DnsCache.java
vendored
Normal 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);
|
||||||
|
}
|
83
src/dorkbox/network/dns/resolver/cache/DnsCacheEntry.java
vendored
Normal file
83
src/dorkbox/network/dns/resolver/cache/DnsCacheEntry.java
vendored
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
src/dorkbox/network/dns/resolver/cache/NoopDnsCache.java
vendored
Normal file
65
src/dorkbox/network/dns/resolver/cache/NoopDnsCache.java
vendored
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user