2014-09-19 15:36:32 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2014 The Netty Project
|
|
|
|
*
|
|
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
|
|
* with the License. You may obtain a copy of the License at:
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
* License for the specific language governing permissions and limitations
|
|
|
|
* under the License.
|
|
|
|
*/
|
|
|
|
package io.netty.resolver.dns;
|
|
|
|
|
|
|
|
import io.netty.bootstrap.Bootstrap;
|
|
|
|
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.EventLoop;
|
|
|
|
import io.netty.channel.FixedRecvByteBufAllocator;
|
|
|
|
import io.netty.channel.ReflectiveChannelFactory;
|
|
|
|
import io.netty.channel.socket.DatagramChannel;
|
|
|
|
import io.netty.channel.socket.InternetProtocolFamily;
|
|
|
|
import io.netty.handler.codec.dns.DnsClass;
|
|
|
|
import io.netty.handler.codec.dns.DnsQueryEncoder;
|
|
|
|
import io.netty.handler.codec.dns.DnsQuestion;
|
|
|
|
import io.netty.handler.codec.dns.DnsResource;
|
|
|
|
import io.netty.handler.codec.dns.DnsResponse;
|
|
|
|
import io.netty.handler.codec.dns.DnsResponseCode;
|
|
|
|
import io.netty.handler.codec.dns.DnsResponseDecoder;
|
|
|
|
import io.netty.resolver.NameResolver;
|
|
|
|
import io.netty.resolver.SimpleNameResolver;
|
|
|
|
import io.netty.util.ReferenceCountUtil;
|
|
|
|
import io.netty.util.collection.IntObjectHashMap;
|
|
|
|
import io.netty.util.concurrent.Future;
|
|
|
|
import io.netty.util.concurrent.Promise;
|
|
|
|
import io.netty.util.concurrent.ScheduledFuture;
|
|
|
|
import io.netty.util.internal.OneTimeTask;
|
|
|
|
import io.netty.util.internal.PlatformDependent;
|
|
|
|
import io.netty.util.internal.SystemPropertyUtil;
|
|
|
|
import io.netty.util.internal.logging.InternalLogger;
|
|
|
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
|
|
|
|
|
|
|
import java.net.IDN;
|
|
|
|
import java.net.InetSocketAddress;
|
|
|
|
import java.net.SocketAddress;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map.Entry;
|
|
|
|
import java.util.concurrent.ConcurrentMap;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import java.util.concurrent.atomic.AtomicReferenceArray;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A DNS-based {@link NameResolver}.
|
|
|
|
*/
|
|
|
|
public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
|
|
|
|
|
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class);
|
|
|
|
|
|
|
|
static final InetSocketAddress ANY_LOCAL_ADDR = new InetSocketAddress(0);
|
|
|
|
|
|
|
|
private static final InternetProtocolFamily[] DEFAULT_RESOLVE_ADDRESS_TYPES = new InternetProtocolFamily[2];
|
|
|
|
|
|
|
|
static {
|
|
|
|
// Note that we did not use SystemPropertyUtil.getBoolean() here to emulate the behavior of JDK.
|
|
|
|
if ("true".equalsIgnoreCase(SystemPropertyUtil.get("java.net.preferIPv6Addresses"))) {
|
|
|
|
DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv6;
|
|
|
|
DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv4;
|
|
|
|
logger.debug("-Djava.net.preferIPv6Addresses: true");
|
|
|
|
} else {
|
|
|
|
DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv4;
|
|
|
|
DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv6;
|
|
|
|
logger.debug("-Djava.net.preferIPv6Addresses: false");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final DnsResponseDecoder DECODER = new DnsResponseDecoder();
|
|
|
|
private static final DnsQueryEncoder ENCODER = new DnsQueryEncoder();
|
|
|
|
|
|
|
|
final Iterable<InetSocketAddress> nameServerAddresses;
|
|
|
|
final DatagramChannel ch;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* An array whose index is the ID of a DNS query and whose value is the promise of the corresponsing response. We
|
|
|
|
* don't use {@link IntObjectHashMap} or map-like data structure here because 64k elements are fairly small, which
|
|
|
|
* is only about 512KB.
|
|
|
|
*/
|
|
|
|
final AtomicReferenceArray<DnsQueryContext> promises = new AtomicReferenceArray<DnsQueryContext>(65536);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The cache for {@link #query(DnsQuestion)}
|
|
|
|
*/
|
|
|
|
final ConcurrentMap<DnsQuestion, DnsCacheEntry> queryCache = PlatformDependent.newConcurrentHashMap();
|
|
|
|
|
|
|
|
private final DnsResponseHandler responseHandler = new DnsResponseHandler();
|
|
|
|
|
|
|
|
private volatile long queryTimeoutMillis = 5000;
|
|
|
|
|
|
|
|
// The default TTL values here respect the TTL returned by the DNS server and do not cache the negative response.
|
|
|
|
private volatile int minTtl;
|
|
|
|
private volatile int maxTtl = Integer.MAX_VALUE;
|
|
|
|
private volatile int negativeTtl;
|
|
|
|
private volatile int maxTriesPerQuery = 2;
|
|
|
|
|
|
|
|
private volatile InternetProtocolFamily[] resolveAddressTypes = DEFAULT_RESOLVE_ADDRESS_TYPES;
|
|
|
|
private volatile boolean recursionDesired = true;
|
|
|
|
private volatile int maxQueriesPerResolve = 8;
|
|
|
|
|
|
|
|
private volatile int maxPayloadSize;
|
|
|
|
private volatile DnsClass maxPayloadSizeClass; // EDNS uses the CLASS field as the payload size field.
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new DNS-based name resolver that communicates with a single DNS server.
|
|
|
|
*
|
|
|
|
* @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
|
|
|
|
* @param channelType the type of the {@link DatagramChannel} to create
|
|
|
|
* @param nameServerAddress the address of the DNS server
|
|
|
|
*/
|
|
|
|
public DnsNameResolver(
|
|
|
|
EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
|
|
|
|
InetSocketAddress nameServerAddress) {
|
|
|
|
this(eventLoop, channelType, ANY_LOCAL_ADDR, nameServerAddress);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new DNS-based name resolver that communicates with a single DNS server.
|
|
|
|
*
|
|
|
|
* @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
|
|
|
|
* @param channelType the type of the {@link DatagramChannel} to create
|
|
|
|
* @param localAddress the local address of the {@link DatagramChannel}
|
|
|
|
* @param nameServerAddress the address of the DNS server
|
|
|
|
*/
|
|
|
|
public DnsNameResolver(
|
|
|
|
EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
|
|
|
|
InetSocketAddress localAddress, InetSocketAddress nameServerAddress) {
|
|
|
|
this(eventLoop, new ReflectiveChannelFactory<DatagramChannel>(channelType), localAddress, nameServerAddress);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new DNS-based name resolver that communicates with a single DNS server.
|
|
|
|
*
|
|
|
|
* @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 nameServerAddress the address of the DNS server
|
|
|
|
*/
|
|
|
|
public DnsNameResolver(
|
|
|
|
EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
|
|
|
|
InetSocketAddress nameServerAddress) {
|
|
|
|
this(eventLoop, channelFactory, ANY_LOCAL_ADDR, nameServerAddress);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new DNS-based name resolver that communicates with a single DNS server.
|
|
|
|
*
|
|
|
|
* @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 localAddress the local address of the {@link DatagramChannel}
|
|
|
|
* @param nameServerAddress the address of the DNS server
|
|
|
|
*/
|
|
|
|
public DnsNameResolver(
|
|
|
|
EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
|
|
|
|
InetSocketAddress localAddress, InetSocketAddress nameServerAddress) {
|
|
|
|
this(eventLoop, channelFactory, localAddress, DnsServerAddresses.singleton(nameServerAddress));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 channelType the type of the {@link DatagramChannel} to create
|
|
|
|
* @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new {@link Iterator} is
|
|
|
|
* created from this {@link Iterable} to determine which DNS server should be contacted
|
|
|
|
* for the next retry in case of failure.
|
|
|
|
*/
|
|
|
|
public DnsNameResolver(
|
|
|
|
EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
|
|
|
|
Iterable<InetSocketAddress> nameServerAddresses) {
|
|
|
|
this(eventLoop, channelType, ANY_LOCAL_ADDR, nameServerAddresses);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 channelType the type of the {@link DatagramChannel} to create
|
|
|
|
* @param localAddress the local address of the {@link DatagramChannel}
|
|
|
|
* @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new {@link Iterator} is
|
|
|
|
* created from this {@link Iterable} to determine which DNS server should be contacted
|
|
|
|
* for the next retry in case of failure.
|
|
|
|
*/
|
|
|
|
public DnsNameResolver(
|
|
|
|
EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
|
|
|
|
InetSocketAddress localAddress, Iterable<InetSocketAddress> nameServerAddresses) {
|
|
|
|
this(eventLoop, new ReflectiveChannelFactory<DatagramChannel>(channelType), localAddress, nameServerAddresses);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 nameServerAddresses the addresses of the DNS server. For each DNS query, a new {@link Iterator} is
|
|
|
|
* created from this {@link Iterable} to determine which DNS server should be contacted
|
|
|
|
* for the next retry in case of failure.
|
|
|
|
*/
|
|
|
|
public DnsNameResolver(
|
|
|
|
EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
|
|
|
|
Iterable<InetSocketAddress> nameServerAddresses) {
|
|
|
|
this(eventLoop, channelFactory, ANY_LOCAL_ADDR, nameServerAddresses);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 localAddress the local address of the {@link DatagramChannel}
|
|
|
|
* @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new {@link Iterator} is
|
|
|
|
* created from this {@link Iterable} to determine which DNS server should be contacted
|
|
|
|
* for the next retry in case of failure.
|
|
|
|
*/
|
|
|
|
public DnsNameResolver(
|
|
|
|
EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
|
|
|
|
InetSocketAddress localAddress, Iterable<InetSocketAddress> nameServerAddresses) {
|
|
|
|
|
|
|
|
super(eventLoop);
|
|
|
|
|
|
|
|
if (channelFactory == null) {
|
|
|
|
throw new NullPointerException("channelFactory");
|
|
|
|
}
|
|
|
|
if (nameServerAddresses == null) {
|
|
|
|
throw new NullPointerException("nameServerAddresses");
|
|
|
|
}
|
|
|
|
if (!nameServerAddresses.iterator().hasNext()) {
|
|
|
|
throw new NullPointerException("nameServerAddresses is empty");
|
|
|
|
}
|
|
|
|
if (localAddress == null) {
|
|
|
|
throw new NullPointerException("localAddress");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.nameServerAddresses = nameServerAddresses;
|
|
|
|
ch = newChannel(channelFactory, localAddress);
|
|
|
|
|
|
|
|
setMaxPayloadSize(4096);
|
|
|
|
}
|
|
|
|
|
|
|
|
private DatagramChannel newChannel(
|
|
|
|
ChannelFactory<? extends DatagramChannel> channelFactory, InetSocketAddress localAddress) {
|
|
|
|
|
|
|
|
Bootstrap b = new Bootstrap();
|
|
|
|
b.group(executor());
|
|
|
|
b.channelFactory(channelFactory);
|
|
|
|
b.handler(new ChannelInitializer<DatagramChannel>() {
|
|
|
|
@Override
|
|
|
|
protected void initChannel(DatagramChannel ch) throws Exception {
|
|
|
|
ch.pipeline().addLast(DECODER, ENCODER, responseHandler);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
DatagramChannel ch = (DatagramChannel) b.bind(localAddress).channel();
|
|
|
|
ch.closeFuture().addListener(new ChannelFutureListener() {
|
|
|
|
@Override
|
|
|
|
public void operationComplete(ChannelFuture future) throws Exception {
|
|
|
|
clearCache();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the minimum TTL of the cached DNS resource records (in seconds).
|
|
|
|
*
|
|
|
|
* @see #maxTtl()
|
|
|
|
* @see #setTtl(int, int)
|
|
|
|
*/
|
|
|
|
public int minTtl() {
|
|
|
|
return minTtl;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the maximum TTL of the cached DNS resource records (in seconds).
|
|
|
|
*
|
|
|
|
* @see #minTtl()
|
|
|
|
* @see #setTtl(int, int)
|
|
|
|
*/
|
|
|
|
public int maxTtl() {
|
|
|
|
return maxTtl;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* @return {@code this}
|
|
|
|
*
|
|
|
|
* @see #minTtl()
|
|
|
|
* @see #maxTtl()
|
|
|
|
*/
|
|
|
|
public DnsNameResolver setTtl(int minTtl, int maxTtl) {
|
|
|
|
if (minTtl < 0) {
|
|
|
|
throw new IllegalArgumentException("minTtl: " + minTtl + " (expected: >= 0)");
|
|
|
|
}
|
|
|
|
if (maxTtl < 0) {
|
|
|
|
throw new IllegalArgumentException("maxTtl: " + maxTtl + " (expected: >= 0)");
|
|
|
|
}
|
|
|
|
if (minTtl > maxTtl) {
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
"minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.maxTtl = maxTtl;
|
|
|
|
this.minTtl = minTtl;
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* @see #setNegativeTtl(int)
|
|
|
|
*/
|
|
|
|
public int negativeTtl() {
|
|
|
|
return negativeTtl;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the TTL of the cache for the failed DNS queries (in seconds).
|
|
|
|
*
|
|
|
|
* @return {@code this}
|
|
|
|
*
|
|
|
|
* @see #negativeTtl()
|
|
|
|
*/
|
|
|
|
public DnsNameResolver setNegativeTtl(int negativeTtl) {
|
|
|
|
if (negativeTtl < 0) {
|
|
|
|
throw new IllegalArgumentException("negativeTtl: " + negativeTtl + " (expected: >= 0)");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.negativeTtl = negativeTtl;
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the timeout of each DNS query performed by this resolver (in milliseconds).
|
|
|
|
* The default value is 5 seconds.
|
|
|
|
*
|
|
|
|
* @see #setQueryTimeoutMillis(long)
|
|
|
|
*/
|
|
|
|
public long queryTimeoutMillis() {
|
|
|
|
return queryTimeoutMillis;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the timeout of each DNS query performed by this resolver (in milliseconds).
|
|
|
|
*
|
|
|
|
* @return {@code this}
|
|
|
|
*
|
|
|
|
* @see #queryTimeoutMillis()
|
|
|
|
*/
|
|
|
|
public DnsNameResolver setQueryTimeoutMillis(long queryTimeoutMillis) {
|
|
|
|
if (queryTimeoutMillis < 0) {
|
|
|
|
throw new IllegalArgumentException("queryTimeoutMillis: " + queryTimeoutMillis + " (expected: >= 0)");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.queryTimeoutMillis = queryTimeoutMillis;
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the maximum number of tries for each query. The default value is 2 times.
|
|
|
|
*
|
|
|
|
* @see #setMaxTriesPerQuery(int)
|
|
|
|
*/
|
|
|
|
public int maxTriesPerQuery() {
|
|
|
|
return maxTriesPerQuery;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the maximum number of tries for each query.
|
|
|
|
*
|
|
|
|
* @return {@code this}
|
|
|
|
*
|
|
|
|
* @see #maxTriesPerQuery()
|
|
|
|
*/
|
|
|
|
public DnsNameResolver setMaxTriesPerQuery(int maxTriesPerQuery) {
|
|
|
|
if (maxTriesPerQuery < 1) {
|
|
|
|
throw new IllegalArgumentException("maxTries: " + maxTriesPerQuery + " (expected: > 0)");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.maxTriesPerQuery = maxTriesPerQuery;
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the list of the protocol families of the address resolved by {@link #resolve(SocketAddress)}
|
|
|
|
* in the order of preference.
|
|
|
|
* The default value depends on the value of the system property {@code "java.net.preferIPv6Addresses"}.
|
|
|
|
*
|
|
|
|
* @see #setResolveAddressTypes(InternetProtocolFamily...)
|
|
|
|
*/
|
|
|
|
public List<InternetProtocolFamily> resolveAddressTypes() {
|
|
|
|
return Arrays.asList(resolveAddressTypes);
|
|
|
|
}
|
|
|
|
|
|
|
|
InternetProtocolFamily[] resolveAddressTypesUnsafe() {
|
|
|
|
return resolveAddressTypes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the list of the protocol families of the address resolved by {@link #resolve(SocketAddress)}.
|
|
|
|
* Usually, both {@link InternetProtocolFamily#IPv4} and {@link InternetProtocolFamily#IPv6} are specified in the
|
|
|
|
* order of preference. To enforce the resolve to retrieve the address of a specific protocol family, specify
|
|
|
|
* only a single {@link InternetProtocolFamily}.
|
|
|
|
*
|
|
|
|
* @return {@code this}
|
|
|
|
*
|
|
|
|
* @see #resolveAddressTypes()
|
|
|
|
*/
|
|
|
|
public DnsNameResolver setResolveAddressTypes(InternetProtocolFamily... resolveAddressTypes) {
|
|
|
|
if (resolveAddressTypes == null) {
|
|
|
|
throw new NullPointerException("resolveAddressTypes");
|
|
|
|
}
|
|
|
|
|
|
|
|
final List<InternetProtocolFamily> list =
|
|
|
|
new ArrayList<InternetProtocolFamily>(InternetProtocolFamily.values().length);
|
|
|
|
|
|
|
|
for (InternetProtocolFamily f: resolveAddressTypes) {
|
|
|
|
if (f == null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Avoid duplicate entries.
|
|
|
|
if (list.contains(f)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
list.add(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (list.isEmpty()) {
|
|
|
|
throw new IllegalArgumentException("no protocol family specified");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.resolveAddressTypes = list.toArray(new InternetProtocolFamily[list.size()]);
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the list of the protocol families of the address resolved by {@link #resolve(SocketAddress)}.
|
|
|
|
* Usually, both {@link InternetProtocolFamily#IPv4} and {@link InternetProtocolFamily#IPv6} are specified in the
|
|
|
|
* order of preference. To enforce the resolve to retrieve the address of a specific protocol family, specify
|
|
|
|
* only a single {@link InternetProtocolFamily}.
|
|
|
|
*
|
|
|
|
* @return {@code this}
|
|
|
|
*
|
|
|
|
* @see #resolveAddressTypes()
|
|
|
|
*/
|
|
|
|
public DnsNameResolver setResolveAddressTypes(Iterable<InternetProtocolFamily> resolveAddressTypes) {
|
|
|
|
if (resolveAddressTypes == null) {
|
|
|
|
throw new NullPointerException("resolveAddressTypes");
|
|
|
|
}
|
|
|
|
|
|
|
|
final List<InternetProtocolFamily> list =
|
|
|
|
new ArrayList<InternetProtocolFamily>(InternetProtocolFamily.values().length);
|
|
|
|
|
|
|
|
for (InternetProtocolFamily f: resolveAddressTypes) {
|
|
|
|
if (f == null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Avoid duplicate entries.
|
|
|
|
if (list.contains(f)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
list.add(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (list.isEmpty()) {
|
|
|
|
throw new IllegalArgumentException("no protocol family specified");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.resolveAddressTypes = list.toArray(new InternetProtocolFamily[list.size()]);
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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}.
|
|
|
|
*
|
|
|
|
* @see #setRecursionDesired(boolean)
|
|
|
|
*/
|
|
|
|
public boolean isRecursionDesired() {
|
|
|
|
return recursionDesired;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets if this resolver has to send a DNS query with the RD (recursion desired) flag set.
|
|
|
|
*
|
|
|
|
* @return {@code this}
|
|
|
|
*
|
|
|
|
* @see #isRecursionDesired()
|
|
|
|
*/
|
|
|
|
public DnsNameResolver setRecursionDesired(boolean recursionDesired) {
|
|
|
|
this.recursionDesired = recursionDesired;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the maximum allowed number of DNS queries to send when resolving a host name.
|
|
|
|
* The default value is {@code 8}.
|
|
|
|
*
|
|
|
|
* @see #setMaxQueriesPerResolve(int)
|
|
|
|
*/
|
|
|
|
public int maxQueriesPerResolve() {
|
|
|
|
return maxQueriesPerResolve;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the maximum allowed number of DNS queries to send when resolving a host name.
|
|
|
|
*
|
|
|
|
* @return {@code this}
|
|
|
|
*
|
|
|
|
* @see #maxQueriesPerResolve()
|
|
|
|
*/
|
|
|
|
public DnsNameResolver setMaxQueriesPerResolve(int maxQueriesPerResolve) {
|
|
|
|
if (maxQueriesPerResolve <= 0) {
|
|
|
|
throw new IllegalArgumentException("maxQueriesPerResolve: " + maxQueriesPerResolve + " (expected: > 0)");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.maxQueriesPerResolve = maxQueriesPerResolve;
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the capacity of the datagram packet buffer (in bytes). The default value is {@code 4096} bytes.
|
|
|
|
*
|
|
|
|
* @see #setMaxPayloadSize(int)
|
|
|
|
*/
|
|
|
|
public int maxPayloadSize() {
|
|
|
|
return maxPayloadSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the capacity of the datagram packet buffer (in bytes). The default value is {@code 4096} bytes.
|
|
|
|
*
|
|
|
|
* @return {@code this}
|
|
|
|
*
|
|
|
|
* @see #maxPayloadSize()
|
|
|
|
*/
|
|
|
|
public DnsNameResolver setMaxPayloadSize(int maxPayloadSize) {
|
|
|
|
if (maxPayloadSize <= 0) {
|
|
|
|
throw new IllegalArgumentException("maxPayloadSize: " + maxPayloadSize + " (expected: > 0)");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.maxPayloadSize == maxPayloadSize) {
|
|
|
|
// Same value; no need to instantiate DnsClass and RecvByteBufAllocator again.
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.maxPayloadSize = maxPayloadSize;
|
|
|
|
maxPayloadSizeClass = DnsClass.valueOf(maxPayloadSize);
|
|
|
|
ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize));
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
DnsClass maxPayloadSizeClass() {
|
|
|
|
return maxPayloadSizeClass;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears all the DNS resource records cached by this resolver.
|
|
|
|
*
|
|
|
|
* @return {@code this}
|
|
|
|
*
|
|
|
|
* @see #clearCache(DnsQuestion)
|
|
|
|
*/
|
|
|
|
public DnsNameResolver clearCache() {
|
|
|
|
for (Iterator<Entry<DnsQuestion, DnsCacheEntry>> i = queryCache.entrySet().iterator(); i.hasNext();) {
|
|
|
|
Entry<DnsQuestion, DnsCacheEntry> e = i.next();
|
|
|
|
i.remove();
|
|
|
|
e.getValue().release();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears the DNS resource record of the specified DNS question from the cache of this resolver.
|
|
|
|
*/
|
|
|
|
public boolean clearCache(DnsQuestion question) {
|
|
|
|
DnsCacheEntry e = queryCache.remove(question);
|
|
|
|
if (e != null) {
|
|
|
|
e.release();
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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() {
|
|
|
|
ch.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected EventLoop executor() {
|
|
|
|
return (EventLoop) super.executor();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected boolean doIsResolved(InetSocketAddress address) {
|
|
|
|
return !address.isUnresolved();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void doResolve(InetSocketAddress unresolvedAddress, Promise<InetSocketAddress> promise) throws Exception {
|
|
|
|
final String hostname = IDN.toASCII(unresolvedAddress.getHostString());
|
|
|
|
final int port = unresolvedAddress.getPort();
|
|
|
|
|
|
|
|
final DnsNameResolverContext ctx = new DnsNameResolverContext(this, hostname, port, promise);
|
|
|
|
|
|
|
|
ctx.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a DNS query with the specified question.
|
|
|
|
*/
|
|
|
|
public Future<DnsResponse> query(DnsQuestion question) {
|
|
|
|
return query(nameServerAddresses, question);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a DNS query with the specified question.
|
|
|
|
*/
|
|
|
|
public Future<DnsResponse> query(DnsQuestion question, Promise<DnsResponse> promise) {
|
|
|
|
return query(nameServerAddresses, question, promise);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a DNS query with the specified question using the specified name server list.
|
|
|
|
*/
|
|
|
|
public Future<DnsResponse> query(Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question) {
|
|
|
|
if (nameServerAddresses == null) {
|
|
|
|
throw new NullPointerException("nameServerAddresses");
|
|
|
|
}
|
|
|
|
if (question == null) {
|
|
|
|
throw new NullPointerException("question");
|
|
|
|
}
|
|
|
|
|
|
|
|
final EventLoop eventLoop = ch.eventLoop();
|
|
|
|
final DnsCacheEntry cachedResult = queryCache.get(question);
|
|
|
|
if (cachedResult != null) {
|
|
|
|
if (cachedResult.response != null) {
|
|
|
|
return eventLoop.newSucceededFuture(cachedResult.response.retain());
|
|
|
|
} else {
|
|
|
|
return eventLoop.newFailedFuture(cachedResult.cause);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return query0(nameServerAddresses, question, eventLoop.<DnsResponse>newPromise());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a DNS query with the specified question using the specified name server list.
|
|
|
|
*/
|
|
|
|
public Future<DnsResponse> query(
|
|
|
|
Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question, Promise<DnsResponse> promise) {
|
|
|
|
|
|
|
|
if (nameServerAddresses == null) {
|
|
|
|
throw new NullPointerException("nameServerAddresses");
|
|
|
|
}
|
|
|
|
if (question == null) {
|
|
|
|
throw new NullPointerException("question");
|
|
|
|
}
|
|
|
|
if (promise == null) {
|
|
|
|
throw new NullPointerException("promise");
|
|
|
|
}
|
|
|
|
|
|
|
|
final DnsCacheEntry cachedResult = queryCache.get(question);
|
|
|
|
if (cachedResult != null) {
|
|
|
|
if (cachedResult.response != null) {
|
|
|
|
return promise.setSuccess(cachedResult.response.retain());
|
|
|
|
} else {
|
|
|
|
return promise.setFailure(cachedResult.cause);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return query0(nameServerAddresses, question, promise);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private Future<DnsResponse> query0(
|
|
|
|
Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question, Promise<DnsResponse> promise) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
new DnsQueryContext(this, nameServerAddresses, question, promise).query();
|
|
|
|
return promise;
|
|
|
|
} catch (Exception e) {
|
|
|
|
return promise.setFailure(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void cache(final DnsQuestion question, DnsCacheEntry entry, long delaySeconds) {
|
2014-10-17 04:38:43 +02:00
|
|
|
DnsCacheEntry oldEntry = queryCache.put(question, entry);
|
|
|
|
if (oldEntry != null) {
|
|
|
|
oldEntry.release();
|
|
|
|
}
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
boolean scheduled = false;
|
|
|
|
try {
|
|
|
|
entry.expirationFuture = ch.eventLoop().schedule(new OneTimeTask() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
2014-10-17 04:38:43 +02:00
|
|
|
clearCache(question);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}, delaySeconds, TimeUnit.SECONDS);
|
|
|
|
|
|
|
|
scheduled = true;
|
|
|
|
} finally {
|
|
|
|
if (!scheduled) {
|
|
|
|
// If failed to schedule the expiration task,
|
|
|
|
// remove the entry from the cache so that it does not leak.
|
2014-10-17 04:38:43 +02:00
|
|
|
clearCache(question);
|
2014-09-19 15:36:32 +02:00
|
|
|
entry.release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private final class DnsResponseHandler extends ChannelInboundHandlerAdapter {
|
|
|
|
@Override
|
|
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
|
|
try {
|
|
|
|
final DnsResponse res = (DnsResponse) msg;
|
|
|
|
final int queryId = res.header().id();
|
|
|
|
|
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
|
logger.debug("{} RECEIVED: [{}: {}], {}", ch, queryId, res.sender(), res);
|
|
|
|
}
|
|
|
|
|
|
|
|
final DnsQueryContext qCtx = promises.get(queryId);
|
|
|
|
|
|
|
|
if (qCtx == null) {
|
|
|
|
if (logger.isWarnEnabled()) {
|
|
|
|
logger.warn("Received a DNS response with an unknown ID: {}", queryId);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
final List<DnsQuestion> questions = res.questions();
|
|
|
|
if (questions.size() != 1) {
|
|
|
|
logger.warn("Received a DNS response with invalid number of questions: {}", res);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
final DnsQuestion q = qCtx.question();
|
|
|
|
if (!q.equals(questions.get(0))) {
|
|
|
|
logger.warn("Received a mismatching DNS response: {}", res);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cancel the timeout task.
|
|
|
|
final ScheduledFuture<?> timeoutFuture = qCtx.timeoutFuture();
|
|
|
|
if (timeoutFuture != null) {
|
|
|
|
timeoutFuture.cancel(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (res.header().responseCode() == DnsResponseCode.NOERROR) {
|
|
|
|
cache(q, res);
|
|
|
|
promises.set(queryId, null);
|
2014-10-17 04:38:43 +02:00
|
|
|
|
|
|
|
Promise<DnsResponse> qPromise = qCtx.promise();
|
|
|
|
if (qPromise.setUncancellable()) {
|
|
|
|
qPromise.setSuccess(res.retain());
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
} else {
|
|
|
|
qCtx.retry(res.sender(),
|
2014-10-17 04:38:43 +02:00
|
|
|
"response code: " + res.header().responseCode() +
|
|
|
|
" with " + res.answers().size() + " answer(s) and " +
|
|
|
|
res.authorityResources().size() + " authority resource(s)");
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
} finally {
|
2014-10-16 10:56:12 +02:00
|
|
|
ReferenceCountUtil.safeRelease(msg);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void cache(DnsQuestion question, DnsResponse res) {
|
|
|
|
final int maxTtl = maxTtl();
|
|
|
|
if (maxTtl == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
long ttl = Long.MAX_VALUE;
|
|
|
|
// Find the smallest TTL value returned by the server.
|
|
|
|
for (DnsResource r: res.answers()) {
|
|
|
|
long rTtl = r.timeToLive();
|
|
|
|
if (ttl > rTtl) {
|
|
|
|
ttl = rTtl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that the found TTL is between minTtl and maxTtl.
|
|
|
|
ttl = Math.max(minTtl(), Math.min(maxTtl, ttl));
|
|
|
|
|
|
|
|
DnsNameResolver.this.cache(question, new DnsCacheEntry(res), ttl);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
|
|
logger.warn("Unexpected exception: ", cause);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static final class DnsCacheEntry {
|
|
|
|
final DnsResponse response;
|
|
|
|
final Throwable cause;
|
|
|
|
volatile ScheduledFuture<?> expirationFuture;
|
|
|
|
|
|
|
|
DnsCacheEntry(DnsResponse response) {
|
2014-10-16 10:56:12 +02:00
|
|
|
this.response = response.retain();
|
2014-09-19 15:36:32 +02:00
|
|
|
cause = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
DnsCacheEntry(Throwable cause) {
|
|
|
|
this.cause = cause;
|
|
|
|
response = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
void release() {
|
|
|
|
DnsResponse response = this.response;
|
|
|
|
if (response != null) {
|
|
|
|
ReferenceCountUtil.safeRelease(response);
|
|
|
|
}
|
|
|
|
|
|
|
|
ScheduledFuture<?> expirationFuture = this.expirationFuture;
|
|
|
|
if (expirationFuture != null) {
|
|
|
|
expirationFuture.cancel(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|