Added more options to the DnsClient
This commit is contained in:
parent
cf27224fec
commit
124ef3d42c
@ -15,6 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network;
|
package dorkbox.network;
|
||||||
|
|
||||||
|
import static io.netty.resolver.dns.DnsServerAddressStreamProviders.platformDefault;
|
||||||
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
import static io.netty.util.internal.ObjectUtil.intValue;
|
||||||
|
|
||||||
import java.net.IDN;
|
import java.net.IDN;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
@ -46,6 +50,7 @@ import dorkbox.util.OS;
|
|||||||
import dorkbox.util.Property;
|
import dorkbox.util.Property;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.AddressedEnvelope;
|
import io.netty.channel.AddressedEnvelope;
|
||||||
|
import io.netty.channel.ChannelFactory;
|
||||||
import io.netty.channel.EventLoopGroup;
|
import io.netty.channel.EventLoopGroup;
|
||||||
import io.netty.channel.ReflectiveChannelFactory;
|
import io.netty.channel.ReflectiveChannelFactory;
|
||||||
import io.netty.channel.epoll.EpollDatagramChannel;
|
import io.netty.channel.epoll.EpollDatagramChannel;
|
||||||
@ -53,6 +58,7 @@ import io.netty.channel.epoll.EpollEventLoopGroup;
|
|||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
import io.netty.channel.oio.OioEventLoopGroup;
|
import io.netty.channel.oio.OioEventLoopGroup;
|
||||||
import io.netty.channel.socket.DatagramChannel;
|
import io.netty.channel.socket.DatagramChannel;
|
||||||
|
import io.netty.channel.socket.InternetProtocolFamily;
|
||||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||||
import io.netty.channel.socket.oio.OioDatagramChannel;
|
import io.netty.channel.socket.oio.OioDatagramChannel;
|
||||||
import io.netty.handler.codec.dns.DefaultDnsQuestion;
|
import io.netty.handler.codec.dns.DefaultDnsQuestion;
|
||||||
@ -63,19 +69,24 @@ import io.netty.handler.codec.dns.DnsRecordType;
|
|||||||
import io.netty.handler.codec.dns.DnsResponse;
|
import io.netty.handler.codec.dns.DnsResponse;
|
||||||
import io.netty.handler.codec.dns.DnsResponseCode;
|
import io.netty.handler.codec.dns.DnsResponseCode;
|
||||||
import io.netty.handler.codec.dns.DnsSection;
|
import io.netty.handler.codec.dns.DnsSection;
|
||||||
|
import io.netty.resolver.HostsFileEntriesResolver;
|
||||||
import io.netty.resolver.ResolvedAddressTypes;
|
import io.netty.resolver.ResolvedAddressTypes;
|
||||||
|
import io.netty.resolver.dns.DefaultDnsCache;
|
||||||
import io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider;
|
import io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider;
|
||||||
|
import io.netty.resolver.dns.DnsCache;
|
||||||
import io.netty.resolver.dns.DnsNameResolver;
|
import io.netty.resolver.dns.DnsNameResolver;
|
||||||
import io.netty.resolver.dns.DnsNameResolverBuilder;
|
import io.netty.resolver.dns.DnsNameResolverAccess;
|
||||||
|
import io.netty.resolver.dns.DnsQueryLifecycleObserverFactory;
|
||||||
import io.netty.resolver.dns.DnsServerAddressStreamProvider;
|
import io.netty.resolver.dns.DnsServerAddressStreamProvider;
|
||||||
|
import io.netty.resolver.dns.NoopDnsQueryLifecycleObserverFactory;
|
||||||
import io.netty.resolver.dns.SequentialDnsServerAddressStreamProvider;
|
import io.netty.resolver.dns.SequentialDnsServerAddressStreamProvider;
|
||||||
import io.netty.util.concurrent.Future;
|
import io.netty.util.concurrent.Future;
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* for now, we only support ipv4
|
* A DnsClient for resolving DNS name, with reasonably good defaults.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||||
public
|
public
|
||||||
class DnsClient {
|
class DnsClient {
|
||||||
|
|
||||||
@ -83,15 +94,18 @@ class DnsClient {
|
|||||||
static {
|
static {
|
||||||
//noinspection Duplicates
|
//noinspection Duplicates
|
||||||
try {
|
try {
|
||||||
// doesn't work when running from inside eclipse.
|
if (PlatformDependent.isAndroid()) {
|
||||||
// Needed for NIO selectors on Android 2.2, and to force IPv4.
|
// doesn't work when running from inside eclipse.
|
||||||
System.setProperty("java.net.preferIPv4Stack", Boolean.TRUE.toString());
|
// Needed for NIO selectors on Android 2.2, and to force IPv4.
|
||||||
System.setProperty("java.net.preferIPv6Addresses", Boolean.FALSE.toString());
|
System.setProperty("java.net.preferIPv4Stack", Boolean.TRUE.toString());
|
||||||
|
System.setProperty("java.net.preferIPv6Addresses", Boolean.FALSE.toString());
|
||||||
|
}
|
||||||
|
|
||||||
// java6 has stack overflow problems when loading certain classes in it's classloader. The result is a StackOverflow when
|
// java6 has stack overflow problems when loading certain classes in it's classloader. The result is a StackOverflow when
|
||||||
// loading them normally
|
// loading them normally. This calls AND FIXES this issue.
|
||||||
if (OS.javaVersion == 6) {
|
if (OS.javaVersion == 6) {
|
||||||
if (PlatformDependent.hasUnsafe()) {
|
if (PlatformDependent.hasUnsafe()) {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
PlatformDependent.newFixedMpscQueue(8);
|
PlatformDependent.newFixedMpscQueue(8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,7 +118,8 @@ class DnsClient {
|
|||||||
*/
|
*/
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
@Property
|
@Property
|
||||||
public static List<InetSocketAddress> DNS_SERVER_LIST = Arrays.asList(
|
public static
|
||||||
|
List<InetSocketAddress> DNS_SERVER_LIST = Arrays.asList(
|
||||||
new InetSocketAddress("8.8.8.8", 53), // Google Public DNS
|
new InetSocketAddress("8.8.8.8", 53), // Google Public DNS
|
||||||
new InetSocketAddress("8.8.4.4", 53),
|
new InetSocketAddress("8.8.4.4", 53),
|
||||||
new InetSocketAddress("208.67.222.222", 53), // OpenDNS
|
new InetSocketAddress("208.67.222.222", 53), // OpenDNS
|
||||||
@ -151,13 +166,51 @@ class DnsClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
|
private final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
|
||||||
private final DnsNameResolver resolver;
|
private Class<? extends DatagramChannel> channelType;
|
||||||
|
|
||||||
|
private DnsNameResolver resolver;
|
||||||
private final Map<DnsRecordType, RecordDecoder<?>> customDecoders = new HashMap<DnsRecordType, RecordDecoder<?>>();
|
private final Map<DnsRecordType, RecordDecoder<?>> customDecoders = new HashMap<DnsRecordType, RecordDecoder<?>>();
|
||||||
|
|
||||||
private ThreadGroup threadGroup;
|
private ThreadGroup threadGroup;
|
||||||
public static final String THREAD_NAME = "DnsClient";
|
private static final String THREAD_NAME = "DnsClient";
|
||||||
|
|
||||||
private EventLoopGroup eventLoopGroup;
|
private EventLoopGroup eventLoopGroup;
|
||||||
|
|
||||||
|
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 = DnsNameResolverAccess.getDefaultResolvedAddressTypes();
|
||||||
|
private boolean recursionDesired = true;
|
||||||
|
private int maxQueriesPerResolve = 16;
|
||||||
|
|
||||||
|
private boolean traceEnabled;
|
||||||
|
private int maxPayloadSize = 4096;
|
||||||
|
|
||||||
|
private boolean optResourceEnabled = true;
|
||||||
|
|
||||||
|
private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
|
||||||
|
private DnsServerAddressStreamProvider dnsServerAddressStreamProvider = platformDefault();
|
||||||
|
private DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory = NoopDnsQueryLifecycleObserverFactory.INSTANCE;
|
||||||
|
|
||||||
|
private String[] searchDomains;
|
||||||
|
private int ndots = -1;
|
||||||
|
private boolean decodeIdn = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new DNS client, with default name server addresses.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsClient() {
|
||||||
|
this(DnsClient.DNS_SERVER_LIST);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new DNS client, using the provided server (default port 53) for DNS query resolution, with a cache that will obey the TTL of the response
|
* Creates a new DNS client, using the provided server (default port 53) for DNS query resolution, with a cache that will obey the TTL of the response
|
||||||
*
|
*
|
||||||
@ -178,16 +231,6 @@ class DnsClient {
|
|||||||
this(Collections.singletonList(nameServerAddresses));
|
this(Collections.singletonList(nameServerAddresses));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new DNS client, with a cache that will obey the TTL of the response
|
|
||||||
*
|
|
||||||
* @param nameServerAddresses the list of servers to receive your DNS questions, until it succeeds
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
DnsClient(Collection<InetSocketAddress> nameServerAddresses) {
|
|
||||||
this(nameServerAddresses, 0, Integer.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new DNS client.
|
* Creates a new DNS client.
|
||||||
*
|
*
|
||||||
@ -195,13 +238,9 @@ class DnsClient {
|
|||||||
* respect the TTL from the DNS server.
|
* respect the TTL from the DNS server.
|
||||||
*
|
*
|
||||||
* @param nameServerAddresses the list of servers to receive your DNS questions, until it succeeds
|
* @param nameServerAddresses the list of servers to receive your DNS questions, until it succeeds
|
||||||
* @param minTtl the minimum TTL
|
|
||||||
* @param maxTtl the maximum TTL
|
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
DnsClient(Collection<InetSocketAddress> nameServerAddresses, int minTtl, int maxTtl) {
|
DnsClient(Collection<InetSocketAddress> nameServerAddresses) {
|
||||||
Class<? extends DatagramChannel> channelType;
|
|
||||||
|
|
||||||
// setup the thread group to easily ID what the following threads belong to (and their spawned threads...)
|
// setup the thread group to easily ID what the following threads belong to (and their spawned threads...)
|
||||||
SecurityManager s = System.getSecurityManager();
|
SecurityManager s = System.getSecurityManager();
|
||||||
threadGroup = new ThreadGroup(s != null
|
threadGroup = new ThreadGroup(s != null
|
||||||
@ -225,20 +264,7 @@ class DnsClient {
|
|||||||
channelType = NioDatagramChannel.class;
|
channelType = NioDatagramChannel.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: A/AAAA use the built-in decoder
|
||||||
List<DnsServerAddressStreamProvider> list = new ArrayList<DnsServerAddressStreamProvider>(nameServerAddresses.size());
|
|
||||||
|
|
||||||
DnsNameResolverBuilder builder = new DnsNameResolverBuilder(eventLoopGroup.next());
|
|
||||||
builder.channelFactory(new ReflectiveChannelFactory<DatagramChannel>(channelType))
|
|
||||||
.channelType(channelType)
|
|
||||||
.ttl(minTtl, maxTtl)
|
|
||||||
.resolvedAddressTypes(ResolvedAddressTypes.IPV4_ONLY) // for now, we only support ipv4
|
|
||||||
.nameServerProvider(new SequentialDnsServerAddressStreamProvider(nameServerAddresses));
|
|
||||||
|
|
||||||
resolver = builder.build();
|
|
||||||
|
|
||||||
|
|
||||||
// A/AAAA use the built-in decoder
|
|
||||||
|
|
||||||
customDecoders.put(DnsRecordType.MX, new MailExchangerDecoder());
|
customDecoders.put(DnsRecordType.MX, new MailExchangerDecoder());
|
||||||
customDecoders.put(DnsRecordType.TXT, new TextDecoder());
|
customDecoders.put(DnsRecordType.TXT, new TextDecoder());
|
||||||
@ -249,6 +275,330 @@ class DnsClient {
|
|||||||
customDecoders.put(DnsRecordType.CNAME, decoder);
|
customDecoders.put(DnsRecordType.CNAME, decoder);
|
||||||
customDecoders.put(DnsRecordType.PTR, decoder);
|
customDecoders.put(DnsRecordType.PTR, decoder);
|
||||||
customDecoders.put(DnsRecordType.SOA, new StartOfAuthorityDecoder());
|
customDecoders.put(DnsRecordType.SOA, new StartOfAuthorityDecoder());
|
||||||
|
|
||||||
|
if (nameServerAddresses != null) {
|
||||||
|
this.dnsServerAddressStreamProvider = new SequentialDnsServerAddressStreamProvider(nameServerAddresses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the cache for resolution results.
|
||||||
|
*
|
||||||
|
* @param resolveCache the DNS resolution results cache
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsClient 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
|
||||||
|
DnsClient 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
|
||||||
|
DnsClient 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
|
||||||
|
DnsClient 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
|
||||||
|
DnsClient 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
|
||||||
|
DnsClient queryTimeoutMillis(long queryTimeoutMillis) {
|
||||||
|
this.queryTimeoutMillis = queryTimeoutMillis;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the list of the protocol families of the address resolved.
|
||||||
|
* You can use {@link DnsClient#computeResolvedAddressTypes(InternetProtocolFamily...)}
|
||||||
|
* to get a {@link ResolvedAddressTypes} out of some {@link InternetProtocolFamily}s.
|
||||||
|
*
|
||||||
|
* @param resolvedAddressTypes the address types
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsClient 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
|
||||||
|
DnsClient 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
|
||||||
|
DnsClient 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
|
||||||
|
DnsClient 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
|
||||||
|
DnsClient maxPayloadSize(int maxPayloadSize) {
|
||||||
|
this.maxPayloadSize = maxPayloadSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the automatic inclusion of a optional records that tries to give the remote DNS server a hint about
|
||||||
|
* how much data the resolver can read per response. Some DNSServer may not support this and so fail to answer
|
||||||
|
* queries. If you find problems you may want to disable this.
|
||||||
|
*
|
||||||
|
* @param optResourceEnabled if optional records inclusion is enabled
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsClient optResourceEnabled(boolean optResourceEnabled) {
|
||||||
|
this.optResourceEnabled = optResourceEnabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to first check
|
||||||
|
* if the hostname is locally aliased.
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsClient 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
|
||||||
|
DnsClient 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
|
||||||
|
DnsClient 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
|
||||||
|
DnsClient ndots(int ndots) {
|
||||||
|
this.ndots = ndots;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
DnsCache newCache() {
|
||||||
|
return new DefaultDnsCache(intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE), intValue(negativeTtl, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
DnsClient decodeToUnicode(boolean decodeIdn) {
|
||||||
|
this.decodeIdn = decodeIdn;
|
||||||
|
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 DnsNameResolverAccess.getDefaultResolvedAddressTypes();
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the DNS Name Resolver for the client, which will resolve DNS queries.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsClient start() {
|
||||||
|
ReflectiveChannelFactory<DatagramChannel> channelFactory = new ReflectiveChannelFactory<DatagramChannel>(channelType);
|
||||||
|
|
||||||
|
// default support is IPV4
|
||||||
|
if (this.resolvedAddressTypes == null) {
|
||||||
|
this.resolvedAddressTypes = ResolvedAddressTypes.IPV4_ONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
resolver = new DnsNameResolver(eventLoopGroup.next(), channelFactory, resolveCache, authoritativeDnsServerCache,
|
||||||
|
dnsQueryLifecycleObserverFactory, queryTimeoutMillis, resolvedAddressTypes, recursionDesired,
|
||||||
|
maxQueriesPerResolve, traceEnabled, maxPayloadSize, optResourceEnabled, hostsFileEntriesResolver,
|
||||||
|
dnsServerAddressStreamProvider, searchDomains, ndots, decodeIdn);
|
||||||
|
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -258,6 +608,10 @@ class DnsClient {
|
|||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
String resolve(String hostname) {
|
String resolve(String hostname) {
|
||||||
|
if (resolver == null) {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
if (resolver.resolvedAddressTypes() == ResolvedAddressTypes.IPV4_ONLY) {
|
if (resolver.resolvedAddressTypes() == ResolvedAddressTypes.IPV4_ONLY) {
|
||||||
return resolve(hostname, DnsRecordType.A);
|
return resolve(hostname, DnsRecordType.A);
|
||||||
}
|
}
|
||||||
@ -279,6 +633,10 @@ class DnsClient {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private
|
private
|
||||||
<T> T resolve(String hostname, DnsRecordType type) {
|
<T> T resolve(String hostname, DnsRecordType type) {
|
||||||
|
if (resolver == null) {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
hostname = IDN.toASCII(hostname);
|
hostname = IDN.toASCII(hostname);
|
||||||
final int value = type.intValue();
|
final int value = type.intValue();
|
||||||
|
|
||||||
@ -368,6 +726,15 @@ class DnsClient {
|
|||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
void reset() {
|
void reset() {
|
||||||
|
if (resolver == null) {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearResolver();
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void clearResolver() {
|
||||||
resolver.resolveCache()
|
resolver.resolveCache()
|
||||||
.clear();
|
.clear();
|
||||||
}
|
}
|
||||||
@ -403,8 +770,11 @@ class DnsClient {
|
|||||||
|
|
||||||
private
|
private
|
||||||
void stopInThread() {
|
void stopInThread() {
|
||||||
reset();
|
clearResolver();
|
||||||
resolver.close(); // also closes the UDP channel that DNS client uses
|
|
||||||
|
if (resolver != null) {
|
||||||
|
resolver.close(); // also closes the UDP channel that DNS client uses
|
||||||
|
}
|
||||||
|
|
||||||
// Sometimes there might be "lingering" connections (ie, halfway though registration) that need to be closed.
|
// Sometimes there might be "lingering" connections (ie, halfway though registration) that need to be closed.
|
||||||
long maxShutdownWaitTimeInMilliSeconds = EndPoint.maxShutdownWaitTimeInMilliSeconds;
|
long maxShutdownWaitTimeInMilliSeconds = EndPoint.maxShutdownWaitTimeInMilliSeconds;
|
||||||
|
981
src/io/netty/resolver/dns/DnsNameResolver.java
Normal file
981
src/io/netty/resolver/dns/DnsNameResolver.java
Normal file
@ -0,0 +1,981 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.resolver.dns;
|
||||||
|
|
||||||
|
import static io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.DNS_PORT;
|
||||||
|
import static io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider.parseEtcResolverFirstNdots;
|
||||||
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
import static io.netty.util.internal.ObjectUtil.checkPositive;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.IDN;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.netty.bootstrap.Bootstrap;
|
||||||
|
import io.netty.channel.AddressedEnvelope;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFactory;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelFutureListener;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.channel.EventLoop;
|
||||||
|
import io.netty.channel.FixedRecvByteBufAllocator;
|
||||||
|
import io.netty.channel.socket.DatagramChannel;
|
||||||
|
import io.netty.channel.socket.InternetProtocolFamily;
|
||||||
|
import io.netty.handler.codec.dns.DatagramDnsQueryEncoder;
|
||||||
|
import io.netty.handler.codec.dns.DatagramDnsResponse;
|
||||||
|
import io.netty.handler.codec.dns.DatagramDnsResponseDecoder;
|
||||||
|
import io.netty.handler.codec.dns.DnsQuestion;
|
||||||
|
import io.netty.handler.codec.dns.DnsRawRecord;
|
||||||
|
import io.netty.handler.codec.dns.DnsRecord;
|
||||||
|
import io.netty.handler.codec.dns.DnsRecordType;
|
||||||
|
import io.netty.handler.codec.dns.DnsResponse;
|
||||||
|
import io.netty.resolver.HostsFileEntriesResolver;
|
||||||
|
import io.netty.resolver.InetNameResolver;
|
||||||
|
import io.netty.resolver.ResolvedAddressTypes;
|
||||||
|
import io.netty.util.NetUtil;
|
||||||
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
import io.netty.util.concurrent.FastThreadLocal;
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
import io.netty.util.concurrent.Promise;
|
||||||
|
import io.netty.util.internal.EmptyArrays;
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
import io.netty.util.internal.StringUtil;
|
||||||
|
import io.netty.util.internal.UnstableApi;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DNS-based {@link InetNameResolver}
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@UnstableApi
|
||||||
|
public class DnsNameResolver extends InetNameResolver {
|
||||||
|
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class);
|
||||||
|
private static final String LOCALHOST = "localhost";
|
||||||
|
private static final InetAddress LOCALHOST_ADDRESS;
|
||||||
|
private static final DnsRecord[] EMPTY_ADDITIONALS = new DnsRecord[0];
|
||||||
|
|
||||||
|
private static final DnsRecordType[] IPV4_ONLY_RESOLVED_RECORD_TYPES = {DnsRecordType.A};
|
||||||
|
private static final InternetProtocolFamily[] IPV4_ONLY_RESOLVED_PROTOCOL_FAMILIES = {InternetProtocolFamily.IPv4};
|
||||||
|
private static final DnsRecordType[] IPV4_PREFERRED_RESOLVED_RECORD_TYPES = {DnsRecordType.A, DnsRecordType.AAAA};
|
||||||
|
private static final InternetProtocolFamily[] IPV4_PREFERRED_RESOLVED_PROTOCOL_FAMILIES = {InternetProtocolFamily.IPv4,
|
||||||
|
InternetProtocolFamily.IPv6};
|
||||||
|
|
||||||
|
private static final DnsRecordType[] IPV6_ONLY_RESOLVED_RECORD_TYPES = {DnsRecordType.AAAA};
|
||||||
|
private static final InternetProtocolFamily[] IPV6_ONLY_RESOLVED_PROTOCOL_FAMILIES = {InternetProtocolFamily.IPv6};
|
||||||
|
private static final DnsRecordType[] IPV6_PREFERRED_RESOLVED_RECORD_TYPES = {DnsRecordType.AAAA, DnsRecordType.A};
|
||||||
|
private static final InternetProtocolFamily[] IPV6_PREFERRED_RESOLVED_PROTOCOL_FAMILIES = {InternetProtocolFamily.IPv6,
|
||||||
|
InternetProtocolFamily.IPv4};
|
||||||
|
|
||||||
|
static final ResolvedAddressTypes DEFAULT_RESOLVE_ADDRESS_TYPES;
|
||||||
|
static final String[] DEFAULT_SEARCH_DOMAINS;
|
||||||
|
private static final int DEFAULT_NDOTS;
|
||||||
|
|
||||||
|
static {
|
||||||
|
if (NetUtil.isIpV4StackPreferred()) {
|
||||||
|
DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_ONLY;
|
||||||
|
LOCALHOST_ADDRESS = NetUtil.LOCALHOST4;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (NetUtil.isIpV6AddressesPreferred()) {
|
||||||
|
DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV6_PREFERRED;
|
||||||
|
LOCALHOST_ADDRESS = NetUtil.LOCALHOST6;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_PREFERRED;
|
||||||
|
LOCALHOST_ADDRESS = NetUtil.LOCALHOST4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
String[] searchDomains;
|
||||||
|
try {
|
||||||
|
Class<?> configClass = Class.forName("sun.net.dns.ResolverConfiguration");
|
||||||
|
Method open = configClass.getMethod("open");
|
||||||
|
Method nameservers = configClass.getMethod("searchlist");
|
||||||
|
Object instance = open.invoke(null);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<String> list = (List<String>) nameservers.invoke(instance);
|
||||||
|
searchDomains = list.toArray(new String[list.size()]);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// Failed to get the system name search domain list.
|
||||||
|
searchDomains = EmptyArrays.EMPTY_STRINGS;
|
||||||
|
}
|
||||||
|
DEFAULT_SEARCH_DOMAINS = searchDomains;
|
||||||
|
|
||||||
|
int ndots;
|
||||||
|
try {
|
||||||
|
ndots = parseEtcResolverFirstNdots();
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
ndots = UnixResolverDnsServerAddressStreamProvider.DEFAULT_NDOTS;
|
||||||
|
}
|
||||||
|
DEFAULT_NDOTS = ndots;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final DatagramDnsResponseDecoder DECODER = new DatagramDnsResponseDecoder();
|
||||||
|
private static final DatagramDnsQueryEncoder ENCODER = new DatagramDnsQueryEncoder();
|
||||||
|
|
||||||
|
final Future<Channel> channelFuture;
|
||||||
|
final DatagramChannel ch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the {@link DnsQueryContext}s in progress and their query IDs.
|
||||||
|
*/
|
||||||
|
final DnsQueryContextManager queryContextManager = new DnsQueryContextManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for {@link #doResolve(String, Promise)} and {@link #doResolveAll(String, Promise)}.
|
||||||
|
*/
|
||||||
|
private final DnsCache resolveCache;
|
||||||
|
private final DnsCache authoritativeDnsServerCache;
|
||||||
|
|
||||||
|
private final FastThreadLocal<DnsServerAddressStream> nameServerAddrStream = new FastThreadLocal<DnsServerAddressStream>() {
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
DnsServerAddressStream initialValue() throws Exception {
|
||||||
|
return dnsServerAddressStreamProvider.nameServerAddressStream("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private final long queryTimeoutMillis;
|
||||||
|
private final int maxQueriesPerResolve;
|
||||||
|
|
||||||
|
private final ResolvedAddressTypes resolvedAddressTypes;
|
||||||
|
private final InternetProtocolFamily[] resolvedInternetProtocolFamilies;
|
||||||
|
|
||||||
|
private final boolean recursionDesired;
|
||||||
|
private final int maxPayloadSize;
|
||||||
|
private final boolean optResourceEnabled;
|
||||||
|
|
||||||
|
private final HostsFileEntriesResolver hostsFileEntriesResolver;
|
||||||
|
private final DnsServerAddressStreamProvider dnsServerAddressStreamProvider;
|
||||||
|
private final String[] searchDomains;
|
||||||
|
private final int ndots;
|
||||||
|
|
||||||
|
private final boolean supportsAAAARecords;
|
||||||
|
|
||||||
|
private final boolean supportsARecords;
|
||||||
|
private final InternetProtocolFamily preferredAddressType;
|
||||||
|
private final DnsRecordType[] resolveRecordTypes;
|
||||||
|
|
||||||
|
private final boolean decodeIdn;
|
||||||
|
private final DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
|
||||||
|
*
|
||||||
|
* @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
|
||||||
|
* @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel}
|
||||||
|
* @param resolveCache the DNS resolved entries cache
|
||||||
|
* @param authoritativeDnsServerCache the cache used to find the authoritative DNS server for a domain
|
||||||
|
* @param dnsQueryLifecycleObserverFactory used to generate new instances of {@link DnsQueryLifecycleObserver} which
|
||||||
|
* can be used to track metrics for DNS servers.
|
||||||
|
* @param queryTimeoutMillis timeout of each DNS query in millis
|
||||||
|
* @param resolvedAddressTypes the preferred address types
|
||||||
|
* @param recursionDesired if recursion desired flag must be set
|
||||||
|
* @param maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution
|
||||||
|
* @param traceEnabled if trace is enabled
|
||||||
|
* @param maxPayloadSize the capacity of the datagram packet buffer
|
||||||
|
* @param optResourceEnabled if automatic inclusion of a optional records is enabled
|
||||||
|
* @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to check for local aliases
|
||||||
|
* @param dnsServerAddressStreamProvider The {@link DnsServerAddressStreamProvider} used to determine the name
|
||||||
|
* servers for each hostname lookup.
|
||||||
|
* @param searchDomains the list of search domain
|
||||||
|
* (can be null, if so, will try to default to the underlying platform ones)
|
||||||
|
* @param ndots the ndots value
|
||||||
|
* @param decodeIdn {@code true} if domain / host names should be decoded to unicode when received.
|
||||||
|
* See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsNameResolver(EventLoop eventLoop,
|
||||||
|
ChannelFactory<? extends DatagramChannel> channelFactory,
|
||||||
|
final DnsCache resolveCache,
|
||||||
|
DnsCache authoritativeDnsServerCache,
|
||||||
|
DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory,
|
||||||
|
long queryTimeoutMillis,
|
||||||
|
ResolvedAddressTypes resolvedAddressTypes,
|
||||||
|
boolean recursionDesired,
|
||||||
|
int maxQueriesPerResolve,
|
||||||
|
boolean traceEnabled,
|
||||||
|
int maxPayloadSize,
|
||||||
|
boolean optResourceEnabled,
|
||||||
|
HostsFileEntriesResolver hostsFileEntriesResolver,
|
||||||
|
DnsServerAddressStreamProvider dnsServerAddressStreamProvider,
|
||||||
|
String[] searchDomains,
|
||||||
|
int ndots,
|
||||||
|
boolean decodeIdn) {
|
||||||
|
super(eventLoop);
|
||||||
|
|
||||||
|
this.queryTimeoutMillis = checkPositive(queryTimeoutMillis, "queryTimeoutMillis");
|
||||||
|
this.resolvedAddressTypes = resolvedAddressTypes != null ? resolvedAddressTypes : DEFAULT_RESOLVE_ADDRESS_TYPES;
|
||||||
|
this.recursionDesired = recursionDesired;
|
||||||
|
this.maxQueriesPerResolve = checkPositive(maxQueriesPerResolve, "maxQueriesPerResolve");
|
||||||
|
this.maxPayloadSize = checkPositive(maxPayloadSize, "maxPayloadSize");
|
||||||
|
this.optResourceEnabled = optResourceEnabled;
|
||||||
|
this.hostsFileEntriesResolver = checkNotNull(hostsFileEntriesResolver, "hostsFileEntriesResolver");
|
||||||
|
this.dnsServerAddressStreamProvider = checkNotNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
|
||||||
|
this.resolveCache = checkNotNull(resolveCache, "resolveCache");
|
||||||
|
this.authoritativeDnsServerCache = checkNotNull(authoritativeDnsServerCache, "authoritativeDnsServerCache");
|
||||||
|
|
||||||
|
if (traceEnabled) {
|
||||||
|
if (dnsQueryLifecycleObserverFactory instanceof NoopDnsQueryLifecycleObserverFactory) {
|
||||||
|
this.dnsQueryLifecycleObserverFactory = new TraceDnsQueryLifeCycleObserverFactory();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.dnsQueryLifecycleObserverFactory = new BiDnsQueryLifecycleObserverFactory(new TraceDnsQueryLifeCycleObserverFactory(),
|
||||||
|
dnsQueryLifecycleObserverFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.dnsQueryLifecycleObserverFactory = checkNotNull(dnsQueryLifecycleObserverFactory, "dnsQueryLifecycleObserverFactory");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.searchDomains = searchDomains != null ? searchDomains.clone() : DEFAULT_SEARCH_DOMAINS;
|
||||||
|
this.ndots = ndots >= 0 ? ndots : DEFAULT_NDOTS;
|
||||||
|
this.decodeIdn = decodeIdn;
|
||||||
|
|
||||||
|
switch (this.resolvedAddressTypes) {
|
||||||
|
case IPV4_ONLY:
|
||||||
|
supportsAAAARecords = false;
|
||||||
|
supportsARecords = true;
|
||||||
|
resolveRecordTypes = IPV4_ONLY_RESOLVED_RECORD_TYPES;
|
||||||
|
resolvedInternetProtocolFamilies = IPV4_ONLY_RESOLVED_PROTOCOL_FAMILIES;
|
||||||
|
preferredAddressType = InternetProtocolFamily.IPv4;
|
||||||
|
break;
|
||||||
|
case IPV4_PREFERRED:
|
||||||
|
supportsAAAARecords = true;
|
||||||
|
supportsARecords = true;
|
||||||
|
resolveRecordTypes = IPV4_PREFERRED_RESOLVED_RECORD_TYPES;
|
||||||
|
resolvedInternetProtocolFamilies = IPV4_PREFERRED_RESOLVED_PROTOCOL_FAMILIES;
|
||||||
|
preferredAddressType = InternetProtocolFamily.IPv4;
|
||||||
|
break;
|
||||||
|
case IPV6_ONLY:
|
||||||
|
supportsAAAARecords = true;
|
||||||
|
supportsARecords = false;
|
||||||
|
resolveRecordTypes = IPV6_ONLY_RESOLVED_RECORD_TYPES;
|
||||||
|
resolvedInternetProtocolFamilies = IPV6_ONLY_RESOLVED_PROTOCOL_FAMILIES;
|
||||||
|
preferredAddressType = InternetProtocolFamily.IPv6;
|
||||||
|
break;
|
||||||
|
case IPV6_PREFERRED:
|
||||||
|
supportsAAAARecords = true;
|
||||||
|
supportsARecords = true;
|
||||||
|
resolveRecordTypes = IPV6_PREFERRED_RESOLVED_RECORD_TYPES;
|
||||||
|
resolvedInternetProtocolFamilies = IPV6_PREFERRED_RESOLVED_PROTOCOL_FAMILIES;
|
||||||
|
preferredAddressType = InternetProtocolFamily.IPv6;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bootstrap b = new Bootstrap();
|
||||||
|
b.group(executor());
|
||||||
|
|
||||||
|
b.channelFactory(channelFactory);
|
||||||
|
b.option(ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION, true);
|
||||||
|
|
||||||
|
final DnsResponseHandler responseHandler = new DnsResponseHandler(executor().<Channel>newPromise());
|
||||||
|
b.handler(new ChannelInitializer<DatagramChannel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(DatagramChannel ch) throws Exception {
|
||||||
|
ch.pipeline().addLast(DECODER, ENCODER, responseHandler);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
channelFuture = responseHandler.channelActivePromise;
|
||||||
|
ch = (DatagramChannel) b.register().channel();
|
||||||
|
ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize));
|
||||||
|
|
||||||
|
ch.closeFuture().addListener(new ChannelFutureListener() {
|
||||||
|
@Override
|
||||||
|
public void operationComplete(ChannelFuture future) throws Exception {
|
||||||
|
resolveCache.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only here to override in unit tests.
|
||||||
|
int dnsRedirectPort(@SuppressWarnings("unused") InetAddress server) {
|
||||||
|
return DNS_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
final DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory() {
|
||||||
|
return dnsQueryLifecycleObserverFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the opportunity to sort the name servers before following a redirected DNS query.
|
||||||
|
* @param nameServers The addresses of the DNS servers which are used in the event of a redirect.
|
||||||
|
* @return A {@link DnsServerAddressStream} which will be used to follow the DNS redirect.
|
||||||
|
*/
|
||||||
|
protected DnsServerAddressStream uncachedRedirectDnsServerStream(List<InetSocketAddress> nameServers) {
|
||||||
|
return DnsServerAddresses.sequential(nameServers).stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the resolution cache.
|
||||||
|
*/
|
||||||
|
public DnsCache resolveCache() {
|
||||||
|
return resolveCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cache used for authoritative DNS servers for a domain.
|
||||||
|
*/
|
||||||
|
public DnsCache authoritativeDnsServerCache() {
|
||||||
|
return authoritativeDnsServerCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the timeout of each DNS query performed by this resolver (in milliseconds).
|
||||||
|
* The default value is 5 seconds.
|
||||||
|
*/
|
||||||
|
public long queryTimeoutMillis() {
|
||||||
|
return queryTimeoutMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link ResolvedAddressTypes} resolved by {@link #resolve(String)}.
|
||||||
|
* The default value depends on the value of the system property {@code "java.net.preferIPv6Addresses"}.
|
||||||
|
*/
|
||||||
|
public ResolvedAddressTypes resolvedAddressTypes() {
|
||||||
|
return resolvedAddressTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
InternetProtocolFamily[] resolvedInternetProtocolFamiliesUnsafe() {
|
||||||
|
return resolvedInternetProtocolFamilies;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String[] searchDomains() {
|
||||||
|
return searchDomains;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int ndots() {
|
||||||
|
return ndots;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean supportsAAAARecords() {
|
||||||
|
return supportsAAAARecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean supportsARecords() {
|
||||||
|
return supportsARecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
final InternetProtocolFamily preferredAddressType() {
|
||||||
|
return preferredAddressType;
|
||||||
|
}
|
||||||
|
|
||||||
|
final DnsRecordType[] resolveRecordTypes() {
|
||||||
|
return resolveRecordTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean isDecodeIdn() {
|
||||||
|
return decodeIdn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if and only if this resolver sends a DNS query with the RD (recursion desired) flag set.
|
||||||
|
* The default value is {@code true}.
|
||||||
|
*/
|
||||||
|
public boolean isRecursionDesired() {
|
||||||
|
return recursionDesired;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum allowed number of DNS queries to send when resolving a host name.
|
||||||
|
* The default value is {@code 8}.
|
||||||
|
*/
|
||||||
|
public int maxQueriesPerResolve() {
|
||||||
|
return maxQueriesPerResolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the capacity of the datagram packet buffer (in bytes). The default value is {@code 4096} bytes.
|
||||||
|
*/
|
||||||
|
public int maxPayloadSize() {
|
||||||
|
return maxPayloadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the automatic inclusion of a optional records that tries to give the remote DNS server a hint about how
|
||||||
|
* much data the resolver can read per response is enabled.
|
||||||
|
*/
|
||||||
|
public boolean isOptResourceEnabled() {
|
||||||
|
return optResourceEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the component that tries to resolve hostnames against the hosts file prior to asking to
|
||||||
|
* remotes DNS servers.
|
||||||
|
*/
|
||||||
|
public HostsFileEntriesResolver hostsFileEntriesResolver() {
|
||||||
|
return hostsFileEntriesResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the internal datagram channel used for sending and receiving DNS messages, and clears all DNS resource
|
||||||
|
* records from the cache. Attempting to send a DNS query or to resolve a domain name will fail once this method
|
||||||
|
* has been called.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (ch.isOpen()) {
|
||||||
|
ch.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected EventLoop executor() {
|
||||||
|
return (EventLoop) super.executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private InetAddress resolveHostsFileEntry(String hostname) {
|
||||||
|
if (hostsFileEntriesResolver == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
|
||||||
|
if (address == null && PlatformDependent.isWindows() && LOCALHOST.equalsIgnoreCase(hostname)) {
|
||||||
|
// If we tried to resolve localhost we need workaround that windows removed localhost from its
|
||||||
|
// hostfile in later versions.
|
||||||
|
// See https://github.com/netty/netty/issues/5386
|
||||||
|
return LOCALHOST_ADDRESS;
|
||||||
|
}
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the specified name into an address.
|
||||||
|
*
|
||||||
|
* @param inetHost the name to resolve
|
||||||
|
* @param additionals additional records ({@code OPT})
|
||||||
|
*
|
||||||
|
* @return the address as the result of the resolution
|
||||||
|
*/
|
||||||
|
public final Future<InetAddress> resolve(String inetHost, Iterable<DnsRecord> additionals) {
|
||||||
|
return resolve(inetHost, additionals, executor().<InetAddress>newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the specified name into an address.
|
||||||
|
*
|
||||||
|
* @param inetHost the name to resolve
|
||||||
|
* @param additionals additional records ({@code OPT})
|
||||||
|
* @param promise the {@link Promise} which will be fulfilled when the name resolution is finished
|
||||||
|
*
|
||||||
|
* @return the address as the result of the resolution
|
||||||
|
*/
|
||||||
|
public final Future<InetAddress> resolve(String inetHost, Iterable<DnsRecord> additionals,
|
||||||
|
Promise<InetAddress> promise) {
|
||||||
|
checkNotNull(promise, "promise");
|
||||||
|
DnsRecord[] additionalsArray = toArray(additionals, true);
|
||||||
|
try {
|
||||||
|
doResolve(inetHost, additionalsArray, promise, resolveCache);
|
||||||
|
return promise;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return promise.setFailure(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the specified host name and port into a list of address.
|
||||||
|
*
|
||||||
|
* @param inetHost the name to resolve
|
||||||
|
* @param additionals additional records ({@code OPT})
|
||||||
|
*
|
||||||
|
* @return the list of the address as the result of the resolution
|
||||||
|
*/
|
||||||
|
public final Future<List<InetAddress>> resolveAll(String inetHost, Iterable<DnsRecord> additionals) {
|
||||||
|
return resolveAll(inetHost, additionals, executor().<List<InetAddress>>newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the specified host name and port into a list of address.
|
||||||
|
*
|
||||||
|
* @param inetHost the name to resolve
|
||||||
|
* @param additionals additional records ({@code OPT})
|
||||||
|
* @param promise the {@link Promise} which will be fulfilled when the name resolution is finished
|
||||||
|
*
|
||||||
|
* @return the list of the address as the result of the resolution
|
||||||
|
*/
|
||||||
|
public final Future<List<InetAddress>> resolveAll(String inetHost, Iterable<DnsRecord> additionals,
|
||||||
|
Promise<List<InetAddress>> promise) {
|
||||||
|
checkNotNull(promise, "promise");
|
||||||
|
DnsRecord[] additionalsArray = toArray(additionals, true);
|
||||||
|
try {
|
||||||
|
doResolveAll(inetHost, additionalsArray, promise, resolveCache);
|
||||||
|
return promise;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return promise.setFailure(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
|
||||||
|
doResolve(inetHost, EMPTY_ADDITIONALS, promise, resolveCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DnsRecord[] toArray(Iterable<DnsRecord> additionals, boolean validateType) {
|
||||||
|
checkNotNull(additionals, "additionals");
|
||||||
|
if (additionals instanceof Collection) {
|
||||||
|
Collection<DnsRecord> records = (Collection<DnsRecord>) additionals;
|
||||||
|
for (DnsRecord r: additionals) {
|
||||||
|
validateAdditional(r, validateType);
|
||||||
|
}
|
||||||
|
return records.toArray(new DnsRecord[records.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<DnsRecord> additionalsIt = additionals.iterator();
|
||||||
|
if (!additionalsIt.hasNext()) {
|
||||||
|
return EMPTY_ADDITIONALS;
|
||||||
|
}
|
||||||
|
List<DnsRecord> records = new ArrayList<DnsRecord>();
|
||||||
|
do {
|
||||||
|
DnsRecord r = additionalsIt.next();
|
||||||
|
validateAdditional(r, validateType);
|
||||||
|
records.add(r);
|
||||||
|
} while (additionalsIt.hasNext());
|
||||||
|
|
||||||
|
return records.toArray(new DnsRecord[records.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateAdditional(DnsRecord record, boolean validateType) {
|
||||||
|
checkNotNull(record, "record");
|
||||||
|
if (validateType && record instanceof DnsRawRecord) {
|
||||||
|
throw new IllegalArgumentException("DnsRawRecord implementations not allowed: " + record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private InetAddress loopbackAddress() {
|
||||||
|
return preferredAddressType().localhost();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook designed for extensibility so one can pass a different cache on each resolution attempt
|
||||||
|
* instead of using the global one.
|
||||||
|
*/
|
||||||
|
protected void doResolve(String inetHost,
|
||||||
|
DnsRecord[] additionals,
|
||||||
|
Promise<InetAddress> promise,
|
||||||
|
DnsCache resolveCache) throws Exception {
|
||||||
|
if (inetHost == null || inetHost.isEmpty()) {
|
||||||
|
// If an empty hostname is used we should use "localhost", just like InetAddress.getByName(...) does.
|
||||||
|
promise.setSuccess(loopbackAddress());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final byte[] bytes = NetUtil.createByteArrayFromIpAddressString(inetHost);
|
||||||
|
if (bytes != null) {
|
||||||
|
// The inetHost is actually an ipaddress.
|
||||||
|
promise.setSuccess(InetAddress.getByAddress(bytes));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String hostname = hostname(inetHost);
|
||||||
|
|
||||||
|
InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
|
||||||
|
if (hostsFileEntry != null) {
|
||||||
|
promise.setSuccess(hostsFileEntry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doResolveCached(hostname, additionals, promise, resolveCache)) {
|
||||||
|
doResolveUncached(hostname, additionals, promise, resolveCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean doResolveCached(String hostname,
|
||||||
|
DnsRecord[] additionals,
|
||||||
|
Promise<InetAddress> promise,
|
||||||
|
DnsCache resolveCache) {
|
||||||
|
final List<DnsCacheEntry> cachedEntries = resolveCache.get(hostname, additionals);
|
||||||
|
if (cachedEntries == null || cachedEntries.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
InetAddress address = null;
|
||||||
|
Throwable cause = null;
|
||||||
|
synchronized (cachedEntries) {
|
||||||
|
final int numEntries = cachedEntries.size();
|
||||||
|
assert numEntries > 0;
|
||||||
|
|
||||||
|
if (cachedEntries.get(0).cause() != null) {
|
||||||
|
cause = cachedEntries.get(0).cause();
|
||||||
|
} else {
|
||||||
|
// Find the first entry with the preferred address type.
|
||||||
|
for (InternetProtocolFamily f : resolvedInternetProtocolFamilies) {
|
||||||
|
for (int i = 0; i < numEntries; i++) {
|
||||||
|
final DnsCacheEntry e = cachedEntries.get(i);
|
||||||
|
if (f.addressType().isInstance(e.address())) {
|
||||||
|
address = e.address();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address != null) {
|
||||||
|
trySuccess(promise, address);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (cause != null) {
|
||||||
|
tryFailure(promise, cause);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> void trySuccess(Promise<T> promise, T result) {
|
||||||
|
if (!promise.trySuccess(result)) {
|
||||||
|
logger.warn("Failed to notify success ({}) to a promise: {}", result, promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void tryFailure(Promise<?> promise, Throwable cause) {
|
||||||
|
if (!promise.tryFailure(cause)) {
|
||||||
|
logger.warn("Failed to notify failure to a promise: {}", promise, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doResolveUncached(String hostname,
|
||||||
|
DnsRecord[] additionals,
|
||||||
|
Promise<InetAddress> promise,
|
||||||
|
DnsCache resolveCache) {
|
||||||
|
new SingleResolverContext(this, hostname, additionals, resolveCache,
|
||||||
|
dnsServerAddressStreamProvider.nameServerAddressStream(hostname)).resolve(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class SingleResolverContext extends DnsNameResolverContext<InetAddress> {
|
||||||
|
SingleResolverContext(DnsNameResolver parent, String hostname,
|
||||||
|
DnsRecord[] additionals, DnsCache resolveCache, DnsServerAddressStream nameServerAddrs) {
|
||||||
|
super(parent, hostname, additionals, resolveCache, nameServerAddrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
DnsNameResolverContext<InetAddress> newResolverContext(DnsNameResolver parent, String hostname,
|
||||||
|
DnsRecord[] additionals, DnsCache resolveCache,
|
||||||
|
DnsServerAddressStream nameServerAddrs) {
|
||||||
|
return new SingleResolverContext(parent, hostname, additionals, 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)) {
|
||||||
|
trySuccess(promise, a);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise) throws Exception {
|
||||||
|
doResolveAll(inetHost, EMPTY_ADDITIONALS, promise, resolveCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook designed for extensibility so one can pass a different cache on each resolution attempt
|
||||||
|
* instead of using the global one.
|
||||||
|
*/
|
||||||
|
protected void doResolveAll(String inetHost,
|
||||||
|
DnsRecord[] additionals,
|
||||||
|
Promise<List<InetAddress>> promise,
|
||||||
|
DnsCache resolveCache) throws Exception {
|
||||||
|
if (inetHost == null || inetHost.isEmpty()) {
|
||||||
|
// If an empty hostname is used we should use "localhost", just like InetAddress.getAllByName(...) does.
|
||||||
|
promise.setSuccess(Collections.singletonList(loopbackAddress()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final byte[] bytes = NetUtil.createByteArrayFromIpAddressString(inetHost);
|
||||||
|
if (bytes != null) {
|
||||||
|
// The unresolvedAddress was created via a String that contains an ipaddress.
|
||||||
|
promise.setSuccess(Collections.singletonList(InetAddress.getByAddress(bytes)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String hostname = hostname(inetHost);
|
||||||
|
|
||||||
|
InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
|
||||||
|
if (hostsFileEntry != null) {
|
||||||
|
promise.setSuccess(Collections.singletonList(hostsFileEntry));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doResolveAllCached(hostname, additionals, promise, resolveCache)) {
|
||||||
|
doResolveAllUncached(hostname, additionals, promise, resolveCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean doResolveAllCached(String hostname,
|
||||||
|
DnsRecord[] additionals,
|
||||||
|
Promise<List<InetAddress>> promise,
|
||||||
|
DnsCache resolveCache) {
|
||||||
|
final List<DnsCacheEntry> cachedEntries = resolveCache.get(hostname, additionals);
|
||||||
|
if (cachedEntries == null || cachedEntries.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<InetAddress> result = null;
|
||||||
|
Throwable cause = null;
|
||||||
|
synchronized (cachedEntries) {
|
||||||
|
final int numEntries = cachedEntries.size();
|
||||||
|
assert numEntries > 0;
|
||||||
|
|
||||||
|
if (cachedEntries.get(0).cause() != null) {
|
||||||
|
cause = cachedEntries.get(0).cause();
|
||||||
|
} else {
|
||||||
|
for (InternetProtocolFamily f : resolvedInternetProtocolFamilies) {
|
||||||
|
for (int i = 0; i < numEntries; i++) {
|
||||||
|
final DnsCacheEntry e = cachedEntries.get(i);
|
||||||
|
if (f.addressType().isInstance(e.address())) {
|
||||||
|
if (result == null) {
|
||||||
|
result = new ArrayList<InetAddress>(numEntries);
|
||||||
|
}
|
||||||
|
result.add(e.address());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
trySuccess(promise, result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (cause != null) {
|
||||||
|
tryFailure(promise, cause);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class ListResolverContext extends DnsNameResolverContext<List<InetAddress>> {
|
||||||
|
ListResolverContext(DnsNameResolver parent, String hostname,
|
||||||
|
DnsRecord[] additionals, DnsCache resolveCache, DnsServerAddressStream nameServerAddrs) {
|
||||||
|
super(parent, hostname, additionals, resolveCache, nameServerAddrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
DnsNameResolverContext<List<InetAddress>> newResolverContext(
|
||||||
|
DnsNameResolver parent, String hostname, DnsRecord[] additionals, DnsCache resolveCache,
|
||||||
|
DnsServerAddressStream nameServerAddrs) {
|
||||||
|
return new ListResolverContext(parent, hostname, additionals, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doResolveAllUncached(String hostname,
|
||||||
|
DnsRecord[] additionals,
|
||||||
|
Promise<List<InetAddress>> promise,
|
||||||
|
DnsCache resolveCache) {
|
||||||
|
new ListResolverContext(this, hostname, additionals, resolveCache,
|
||||||
|
dnsServerAddressStreamProvider.nameServerAddressStream(hostname)).resolve(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String hostname(String inetHost) {
|
||||||
|
String hostname = IDN.toASCII(inetHost);
|
||||||
|
// Check for http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6894622
|
||||||
|
if (StringUtil.endsWith(inetHost, '.') && !StringUtil.endsWith(hostname, '.')) {
|
||||||
|
hostname += ".";
|
||||||
|
}
|
||||||
|
return hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a DNS query with the specified question.
|
||||||
|
*/
|
||||||
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(DnsQuestion question) {
|
||||||
|
return query(nextNameServerAddress(), question);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a DNS query with the specified question with additional records.
|
||||||
|
*/
|
||||||
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
|
||||||
|
DnsQuestion question, Iterable<DnsRecord> additionals) {
|
||||||
|
return query(nextNameServerAddress(), question, additionals);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a DNS query with the specified question.
|
||||||
|
*/
|
||||||
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
|
||||||
|
DnsQuestion question, Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
|
||||||
|
return query(nextNameServerAddress(), question, Collections.<DnsRecord>emptyList(), promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InetSocketAddress nextNameServerAddress() {
|
||||||
|
return nameServerAddrStream.get().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a DNS query with the specified question using the specified name server list.
|
||||||
|
*/
|
||||||
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
|
||||||
|
InetSocketAddress nameServerAddr, DnsQuestion question) {
|
||||||
|
|
||||||
|
return query0(nameServerAddr, question, EMPTY_ADDITIONALS,
|
||||||
|
ch.eventLoop().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a DNS query with the specified question with additional records using the specified name server list.
|
||||||
|
*/
|
||||||
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
|
||||||
|
InetSocketAddress nameServerAddr, DnsQuestion question, Iterable<DnsRecord> additionals) {
|
||||||
|
|
||||||
|
return query0(nameServerAddr, question, toArray(additionals, false),
|
||||||
|
ch.eventLoop().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a DNS query with the specified question using the specified name server list.
|
||||||
|
*/
|
||||||
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
|
||||||
|
InetSocketAddress nameServerAddr, DnsQuestion question,
|
||||||
|
Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
|
||||||
|
|
||||||
|
return query0(nameServerAddr, question, EMPTY_ADDITIONALS, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a DNS query with the specified question with additional records using the specified name server list.
|
||||||
|
*/
|
||||||
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
|
||||||
|
InetSocketAddress nameServerAddr, DnsQuestion question,
|
||||||
|
Iterable<DnsRecord> additionals,
|
||||||
|
Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
|
||||||
|
|
||||||
|
return query0(nameServerAddr, question, toArray(additionals, false), promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query0(
|
||||||
|
InetSocketAddress nameServerAddr, DnsQuestion question,
|
||||||
|
DnsRecord[] additionals,
|
||||||
|
Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
|
||||||
|
return query0(nameServerAddr, question, additionals, ch.newPromise(), promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query0(
|
||||||
|
InetSocketAddress nameServerAddr, DnsQuestion question,
|
||||||
|
DnsRecord[] additionals,
|
||||||
|
ChannelPromise writePromise,
|
||||||
|
Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
|
||||||
|
assert !writePromise.isVoid();
|
||||||
|
|
||||||
|
final Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> castPromise = cast(
|
||||||
|
checkNotNull(promise, "promise"));
|
||||||
|
try {
|
||||||
|
new DnsQueryContext(this, nameServerAddr, question, additionals, castPromise).query(writePromise);
|
||||||
|
return castPromise;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return castPromise.setFailure(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> cast(Promise<?> promise) {
|
||||||
|
return (Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>>) promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class DnsResponseHandler extends ChannelInboundHandlerAdapter {
|
||||||
|
|
||||||
|
private final Promise<Channel> channelActivePromise;
|
||||||
|
|
||||||
|
DnsResponseHandler(Promise<Channel> channelActivePromise) {
|
||||||
|
this.channelActivePromise = channelActivePromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
try {
|
||||||
|
final DatagramDnsResponse res = (DatagramDnsResponse) msg;
|
||||||
|
final int queryId = res.id();
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("{} RECEIVED: [{}: {}], {}", ch, queryId, res.sender(), res);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DnsQueryContext qCtx = queryContextManager.get(res.sender(), queryId);
|
||||||
|
if (qCtx == null) {
|
||||||
|
logger.warn("{} Received a DNS response with an unknown ID: {}", ch, queryId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCtx.finish(res);
|
||||||
|
} finally {
|
||||||
|
ReferenceCountUtil.safeRelease(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
super.channelActive(ctx);
|
||||||
|
channelActivePromise.setSuccess(ctx.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
|
logger.warn("{} Unexpected exception: ", ch, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@
|
|||||||
package io.netty.resolver.dns;
|
package io.netty.resolver.dns;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.resolver.ResolvedAddressTypes;
|
||||||
|
|
||||||
public final
|
public final
|
||||||
class DnsNameResolverAccess {
|
class DnsNameResolverAccess {
|
||||||
@ -25,6 +26,11 @@ class DnsNameResolverAccess {
|
|||||||
return DnsNameResolverContext.decodeDomainName(byteBuff);
|
return DnsNameResolverContext.decodeDomainName(byteBuff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
ResolvedAddressTypes getDefaultResolvedAddressTypes() {
|
||||||
|
return DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
private
|
private
|
||||||
DnsNameResolverAccess() {
|
DnsNameResolverAccess() {
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,16 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network;
|
package dorkbox.network;
|
||||||
|
|
||||||
import dorkbox.network.dns.record.MailExchangerRecord;
|
import static org.junit.Assert.fail;
|
||||||
import dorkbox.network.dns.record.ServiceRecord;
|
|
||||||
import dorkbox.network.dns.record.StartOfAuthorityRecord;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import dorkbox.network.dns.record.MailExchangerRecord;
|
||||||
|
import dorkbox.network.dns.record.ServiceRecord;
|
||||||
|
import dorkbox.network.dns.record.StartOfAuthorityRecord;
|
||||||
|
|
||||||
public class DnsTests {
|
public class DnsTests {
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ public class DnsTests {
|
|||||||
public
|
public
|
||||||
void decode_A_Record() throws UnknownHostException {
|
void decode_A_Record() throws UnknownHostException {
|
||||||
//DnsClient dnsClient = new DnsClient("127.0.1.1");
|
//DnsClient dnsClient = new DnsClient("127.0.1.1");
|
||||||
DnsClient dnsClient = new DnsClient(DnsClient.DNS_SERVER_LIST);
|
DnsClient dnsClient = new DnsClient();
|
||||||
String answer = dnsClient.resolveA("google.com");
|
String answer = dnsClient.resolveA("google.com");
|
||||||
dnsClient.stop();
|
dnsClient.stop();
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ public class DnsTests {
|
|||||||
// PTR absolutely MUST end in '.in-addr.arpa' in order for the DNS server to understand it.
|
// PTR absolutely MUST end in '.in-addr.arpa' in order for the DNS server to understand it.
|
||||||
// our DNS client will FIX THIS, so that end-users do NOT have to know this!
|
// our DNS client will FIX THIS, so that end-users do NOT have to know this!
|
||||||
|
|
||||||
DnsClient dnsClient = new DnsClient(DnsClient.DNS_SERVER_LIST);
|
DnsClient dnsClient = new DnsClient();
|
||||||
String answer = dnsClient.resolvePTR("204.228.150.3");
|
String answer = dnsClient.resolvePTR("204.228.150.3");
|
||||||
dnsClient.stop();
|
dnsClient.stop();
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ public class DnsTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decode_CNAME_Record() {
|
public void decode_CNAME_Record() {
|
||||||
DnsClient dnsClient = new DnsClient(DnsClient.DNS_SERVER_LIST);
|
DnsClient dnsClient = new DnsClient();
|
||||||
String answer = dnsClient.resolveCNAME("www.atmos.org");
|
String answer = dnsClient.resolveCNAME("www.atmos.org");
|
||||||
dnsClient.stop();
|
dnsClient.stop();
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ public class DnsTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decode_MX_Record() {
|
public void decode_MX_Record() {
|
||||||
DnsClient dnsClient = new DnsClient(DnsClient.DNS_SERVER_LIST);
|
DnsClient dnsClient = new DnsClient();
|
||||||
MailExchangerRecord answer = dnsClient.resolveMX("bbc.co.uk");
|
MailExchangerRecord answer = dnsClient.resolveMX("bbc.co.uk");
|
||||||
final String name = answer.name();
|
final String name = answer.name();
|
||||||
dnsClient.stop();
|
dnsClient.stop();
|
||||||
@ -79,7 +80,7 @@ public class DnsTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decode_SRV_Record() {
|
public void decode_SRV_Record() {
|
||||||
DnsClient dnsClient = new DnsClient(DnsClient.DNS_SERVER_LIST);
|
DnsClient dnsClient = new DnsClient();
|
||||||
ServiceRecord answer = dnsClient.resolveSRV("_pop3._tcp.fudo.org");
|
ServiceRecord answer = dnsClient.resolveSRV("_pop3._tcp.fudo.org");
|
||||||
final String name = answer.name();
|
final String name = answer.name();
|
||||||
dnsClient.stop();
|
dnsClient.stop();
|
||||||
@ -91,7 +92,7 @@ public class DnsTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decode_SOA_Record() {
|
public void decode_SOA_Record() {
|
||||||
DnsClient dnsClient = new DnsClient(DnsClient.DNS_SERVER_LIST);
|
DnsClient dnsClient = new DnsClient();
|
||||||
StartOfAuthorityRecord answer = dnsClient.resolveSOA("google.com");
|
StartOfAuthorityRecord answer = dnsClient.resolveSOA("google.com");
|
||||||
final String nameServer = answer.primaryNameServer();
|
final String nameServer = answer.primaryNameServer();
|
||||||
dnsClient.stop();
|
dnsClient.stop();
|
||||||
@ -104,7 +105,7 @@ public class DnsTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decode_TXT_Record() {
|
public void decode_TXT_Record() {
|
||||||
DnsClient dnsClient = new DnsClient(DnsClient.DNS_SERVER_LIST);
|
DnsClient dnsClient = new DnsClient();
|
||||||
List<String> answer = dnsClient.resolveTXT("real-world-systems.com");
|
List<String> answer = dnsClient.resolveTXT("real-world-systems.com");
|
||||||
final String name = answer.get(0);
|
final String name = answer.get(0);
|
||||||
dnsClient.stop();
|
dnsClient.stop();
|
||||||
|
Loading…
Reference in New Issue
Block a user