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;
|
2018-03-29 22:01:25 +02:00
|
|
|
import io.netty.buffer.ByteBuf;
|
|
|
|
import io.netty.buffer.Unpooled;
|
2015-03-16 07:46:14 +01:00
|
|
|
import io.netty.channel.AddressedEnvelope;
|
2016-06-28 11:44:04 +02:00
|
|
|
import io.netty.channel.Channel;
|
2014-09-19 15:36:32 +02:00
|
|
|
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;
|
2016-06-28 11:44:04 +02:00
|
|
|
import io.netty.channel.ChannelOption;
|
2017-04-07 03:09:28 +02:00
|
|
|
import io.netty.channel.ChannelPromise;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.channel.EventLoop;
|
|
|
|
import io.netty.channel.FixedRecvByteBufAllocator;
|
|
|
|
import io.netty.channel.socket.DatagramChannel;
|
|
|
|
import io.netty.channel.socket.InternetProtocolFamily;
|
2019-05-17 14:37:11 +02:00
|
|
|
import io.netty.channel.socket.SocketChannel;
|
2015-03-16 07:46:14 +01:00
|
|
|
import io.netty.handler.codec.dns.DatagramDnsQueryEncoder;
|
|
|
|
import io.netty.handler.codec.dns.DatagramDnsResponse;
|
|
|
|
import io.netty.handler.codec.dns.DatagramDnsResponseDecoder;
|
2018-03-29 22:01:25 +02:00
|
|
|
import io.netty.handler.codec.dns.DefaultDnsRawRecord;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.handler.codec.dns.DnsQuestion;
|
2017-02-07 23:09:55 +01:00
|
|
|
import io.netty.handler.codec.dns.DnsRawRecord;
|
|
|
|
import io.netty.handler.codec.dns.DnsRecord;
|
2017-01-19 13:18:19 +01:00
|
|
|
import io.netty.handler.codec.dns.DnsRecordType;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.handler.codec.dns.DnsResponse;
|
2019-05-17 14:37:11 +02:00
|
|
|
import io.netty.handler.codec.dns.TcpDnsQueryEncoder;
|
|
|
|
import io.netty.handler.codec.dns.TcpDnsResponseDecoder;
|
2018-03-29 22:01:25 +02:00
|
|
|
import io.netty.resolver.HostsFileEntries;
|
2015-12-14 15:27:49 +01:00
|
|
|
import io.netty.resolver.HostsFileEntriesResolver;
|
2015-12-13 00:11:59 +01:00
|
|
|
import io.netty.resolver.InetNameResolver;
|
2017-02-08 16:43:15 +01:00
|
|
|
import io.netty.resolver.ResolvedAddressTypes;
|
2015-07-10 22:00:30 +02:00
|
|
|
import io.netty.util.NetUtil;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.util.ReferenceCountUtil;
|
2018-11-21 06:42:40 +01:00
|
|
|
import io.netty.util.concurrent.EventExecutor;
|
2015-07-12 12:34:05 +02:00
|
|
|
import io.netty.util.concurrent.FastThreadLocal;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.util.concurrent.Future;
|
2018-03-29 22:01:25 +02:00
|
|
|
import io.netty.util.concurrent.FutureListener;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.util.concurrent.Promise;
|
2016-06-30 23:12:11 +02:00
|
|
|
import io.netty.util.internal.EmptyArrays;
|
2016-06-13 12:44:35 +02:00
|
|
|
import io.netty.util.internal.PlatformDependent;
|
2016-06-30 23:12:11 +02:00
|
|
|
import io.netty.util.internal.StringUtil;
|
2016-04-12 14:22:41 +02:00
|
|
|
import io.netty.util.internal.UnstableApi;
|
2014-09-19 15:36:32 +02:00
|
|
|
import io.netty.util.internal.logging.InternalLogger;
|
|
|
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
import java.lang.reflect.Method;
|
2014-09-19 15:36:32 +02:00
|
|
|
import java.net.IDN;
|
2018-03-29 22:01:25 +02:00
|
|
|
import java.net.Inet4Address;
|
|
|
|
import java.net.Inet6Address;
|
2015-07-10 22:00:30 +02:00
|
|
|
import java.net.InetAddress;
|
2014-09-19 15:36:32 +02:00
|
|
|
import java.net.InetSocketAddress;
|
2019-04-15 13:07:05 +02:00
|
|
|
import java.net.NetworkInterface;
|
2019-05-17 14:37:11 +02:00
|
|
|
import java.net.SocketAddress;
|
2019-04-15 13:07:05 +02:00
|
|
|
import java.net.SocketException;
|
2014-09-19 15:36:32 +02:00
|
|
|
import java.util.ArrayList;
|
2016-07-29 10:30:08 +02:00
|
|
|
import java.util.Collection;
|
2015-07-12 12:34:05 +02:00
|
|
|
import java.util.Collections;
|
2018-08-22 17:49:22 +02:00
|
|
|
import java.util.Comparator;
|
2019-04-15 13:07:05 +02:00
|
|
|
import java.util.Enumeration;
|
2016-07-29 10:30:08 +02:00
|
|
|
import java.util.Iterator;
|
2014-09-19 15:36:32 +02:00
|
|
|
import java.util.List;
|
|
|
|
|
2017-03-31 22:52:46 +02:00
|
|
|
import static io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.DNS_PORT;
|
2017-06-21 22:48:45 +02:00
|
|
|
import static io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider.parseEtcResolverFirstNdots;
|
2017-02-07 23:09:55 +01:00
|
|
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
|
|
|
import static io.netty.util.internal.ObjectUtil.checkPositive;
|
2015-07-12 12:34:05 +02:00
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
/**
|
2015-12-13 00:11:59 +01:00
|
|
|
* A DNS-based {@link InetNameResolver}.
|
2014-09-19 15:36:32 +02:00
|
|
|
*/
|
2016-04-12 14:22:41 +02:00
|
|
|
@UnstableApi
|
2015-12-13 00:11:59 +01:00
|
|
|
public class DnsNameResolver extends InetNameResolver {
|
2014-09-19 15:36:32 +02:00
|
|
|
|
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class);
|
2016-06-13 12:44:35 +02:00
|
|
|
private static final String LOCALHOST = "localhost";
|
|
|
|
private static final InetAddress LOCALHOST_ADDRESS;
|
2017-02-08 16:43:15 +01:00
|
|
|
private static final DnsRecord[] EMPTY_ADDITIONALS = new DnsRecord[0];
|
|
|
|
private static final DnsRecordType[] IPV4_ONLY_RESOLVED_RECORD_TYPES =
|
2017-04-19 20:54:23 +02:00
|
|
|
{DnsRecordType.A};
|
2017-02-08 16:43:15 +01:00
|
|
|
private static final InternetProtocolFamily[] IPV4_ONLY_RESOLVED_PROTOCOL_FAMILIES =
|
2017-04-19 20:54:23 +02:00
|
|
|
{InternetProtocolFamily.IPv4};
|
2017-02-08 16:43:15 +01:00
|
|
|
private static final DnsRecordType[] IPV4_PREFERRED_RESOLVED_RECORD_TYPES =
|
2017-04-19 20:54:23 +02:00
|
|
|
{DnsRecordType.A, DnsRecordType.AAAA};
|
2017-02-08 16:43:15 +01:00
|
|
|
private static final InternetProtocolFamily[] IPV4_PREFERRED_RESOLVED_PROTOCOL_FAMILIES =
|
2017-04-19 20:54:23 +02:00
|
|
|
{InternetProtocolFamily.IPv4, InternetProtocolFamily.IPv6};
|
2017-02-08 16:43:15 +01:00
|
|
|
private static final DnsRecordType[] IPV6_ONLY_RESOLVED_RECORD_TYPES =
|
2017-04-19 20:54:23 +02:00
|
|
|
{DnsRecordType.AAAA};
|
2017-02-08 16:43:15 +01:00
|
|
|
private static final InternetProtocolFamily[] IPV6_ONLY_RESOLVED_PROTOCOL_FAMILIES =
|
2017-04-19 20:54:23 +02:00
|
|
|
{InternetProtocolFamily.IPv6};
|
2017-02-08 16:43:15 +01:00
|
|
|
private static final DnsRecordType[] IPV6_PREFERRED_RESOLVED_RECORD_TYPES =
|
2017-04-19 20:54:23 +02:00
|
|
|
{DnsRecordType.AAAA, DnsRecordType.A};
|
2017-02-08 16:43:15 +01:00
|
|
|
private static final InternetProtocolFamily[] IPV6_PREFERRED_RESOLVED_PROTOCOL_FAMILIES =
|
2017-04-19 20:54:23 +02:00
|
|
|
{InternetProtocolFamily.IPv6, InternetProtocolFamily.IPv4};
|
2017-02-08 16:43:15 +01:00
|
|
|
|
|
|
|
static final ResolvedAddressTypes DEFAULT_RESOLVE_ADDRESS_TYPES;
|
|
|
|
static final String[] DEFAULT_SEARCH_DOMAINS;
|
2017-06-21 22:48:45 +02:00
|
|
|
private static final int DEFAULT_NDOTS;
|
2014-09-19 15:36:32 +02:00
|
|
|
|
|
|
|
static {
|
2019-04-15 13:07:05 +02:00
|
|
|
if (NetUtil.isIpV4StackPreferred() || !anyInterfaceSupportsIpV6()) {
|
2017-02-08 16:43:15 +01:00
|
|
|
DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_ONLY;
|
2016-06-13 12:44:35 +02:00
|
|
|
LOCALHOST_ADDRESS = NetUtil.LOCALHOST4;
|
2016-08-10 04:53:59 +02:00
|
|
|
} else {
|
|
|
|
if (NetUtil.isIpV6AddressesPreferred()) {
|
2017-02-08 16:43:15 +01:00
|
|
|
DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV6_PREFERRED;
|
2016-08-10 04:53:59 +02:00
|
|
|
LOCALHOST_ADDRESS = NetUtil.LOCALHOST6;
|
|
|
|
} else {
|
2017-02-08 16:43:15 +01:00
|
|
|
DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_PREFERRED;
|
2016-08-10 04:53:59 +02:00
|
|
|
LOCALHOST_ADDRESS = NetUtil.LOCALHOST4;
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
static {
|
|
|
|
String[] searchDomains;
|
|
|
|
try {
|
2018-09-26 20:55:46 +02:00
|
|
|
List<String> list = PlatformDependent.isWindows()
|
|
|
|
? getSearchDomainsHack()
|
|
|
|
: UnixResolverDnsServerAddressStreamProvider.parseEtcResolverSearchDomains();
|
2018-06-29 07:56:04 +02:00
|
|
|
searchDomains = list.toArray(new String[0]);
|
2016-06-30 23:12:11 +02:00
|
|
|
} catch (Exception ignore) {
|
|
|
|
// Failed to get the system name search domain list.
|
|
|
|
searchDomains = EmptyArrays.EMPTY_STRINGS;
|
|
|
|
}
|
2017-02-08 16:43:15 +01:00
|
|
|
DEFAULT_SEARCH_DOMAINS = searchDomains;
|
2017-06-21 22:48:45 +02:00
|
|
|
|
|
|
|
int ndots;
|
|
|
|
try {
|
|
|
|
ndots = parseEtcResolverFirstNdots();
|
|
|
|
} catch (Exception ignore) {
|
|
|
|
ndots = UnixResolverDnsServerAddressStreamProvider.DEFAULT_NDOTS;
|
|
|
|
}
|
|
|
|
DEFAULT_NDOTS = ndots;
|
2016-06-30 23:12:11 +02:00
|
|
|
}
|
|
|
|
|
2019-04-15 13:07:05 +02:00
|
|
|
/**
|
|
|
|
* Returns {@code true} if any {@link NetworkInterface} supports {@code IPv6}, {@code false} otherwise.
|
|
|
|
*/
|
|
|
|
private static boolean anyInterfaceSupportsIpV6() {
|
|
|
|
try {
|
|
|
|
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
|
|
|
while (interfaces.hasMoreElements()) {
|
|
|
|
NetworkInterface iface = interfaces.nextElement();
|
|
|
|
Enumeration<InetAddress> addresses = iface.getInetAddresses();
|
|
|
|
while (addresses.hasMoreElements()) {
|
|
|
|
if (addresses.nextElement() instanceof Inet6Address) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (SocketException e) {
|
|
|
|
logger.debug("Unable to detect if any interface supports IPv6, assuming IPv4-only", e);
|
|
|
|
// ignore
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-26 20:55:46 +02:00
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
private static List<String> getSearchDomainsHack() throws Exception {
|
|
|
|
// This code on Java 9+ yields a warning about illegal reflective access that will be denied in
|
|
|
|
// a future release. There doesn't seem to be a better way to get search domains for Windows yet.
|
|
|
|
Class<?> configClass = Class.forName("sun.net.dns.ResolverConfiguration");
|
|
|
|
Method open = configClass.getMethod("open");
|
|
|
|
Method nameservers = configClass.getMethod("searchlist");
|
|
|
|
Object instance = open.invoke(null);
|
|
|
|
|
|
|
|
return (List<String>) nameservers.invoke(instance);
|
|
|
|
}
|
|
|
|
|
2019-05-17 14:37:11 +02:00
|
|
|
private static final DatagramDnsResponseDecoder DATAGRAM_DECODER = new DatagramDnsResponseDecoder();
|
|
|
|
private static final DatagramDnsQueryEncoder DATAGRAM_ENCODER = new DatagramDnsQueryEncoder();
|
|
|
|
private static final TcpDnsQueryEncoder TCP_ENCODER = new TcpDnsQueryEncoder();
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2016-06-28 11:44:04 +02:00
|
|
|
final Future<Channel> channelFuture;
|
2018-09-11 20:34:37 +02:00
|
|
|
final Channel ch;
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2018-08-22 17:49:22 +02:00
|
|
|
// Comparator that ensures we will try first to use the nameservers that use our preferred address type.
|
|
|
|
private final Comparator<InetSocketAddress> nameServerComparator;
|
2014-09-19 15:36:32 +02:00
|
|
|
/**
|
2015-11-08 04:59:01 +01:00
|
|
|
* Manages the {@link DnsQueryContext}s in progress and their query IDs.
|
2014-09-19 15:36:32 +02:00
|
|
|
*/
|
2015-11-08 04:59:01 +01:00
|
|
|
final DnsQueryContextManager queryContextManager = new DnsQueryContextManager();
|
2014-09-19 15:36:32 +02:00
|
|
|
|
|
|
|
/**
|
2015-12-13 00:11:59 +01:00
|
|
|
* Cache for {@link #doResolve(String, Promise)} and {@link #doResolveAll(String, Promise)}.
|
2014-09-19 15:36:32 +02:00
|
|
|
*/
|
2016-01-07 17:49:15 +01:00
|
|
|
private final DnsCache resolveCache;
|
2018-08-22 17:49:22 +02:00
|
|
|
private final AuthoritativeDnsServerCache authoritativeDnsServerCache;
|
2018-09-27 17:05:35 +02:00
|
|
|
private final DnsCnameCache cnameCache;
|
2015-07-12 12:34:05 +02:00
|
|
|
|
2015-08-19 04:51:15 +02:00
|
|
|
private final FastThreadLocal<DnsServerAddressStream> nameServerAddrStream =
|
|
|
|
new FastThreadLocal<DnsServerAddressStream>() {
|
2015-07-12 12:34:05 +02:00
|
|
|
@Override
|
2018-08-22 17:49:22 +02:00
|
|
|
protected DnsServerAddressStream initialValue() {
|
2017-03-31 02:02:16 +02:00
|
|
|
return dnsServerAddressStreamProvider.nameServerAddressStream("");
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
};
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
private final long queryTimeoutMillis;
|
|
|
|
private final int maxQueriesPerResolve;
|
2017-02-08 16:43:15 +01:00
|
|
|
private final ResolvedAddressTypes resolvedAddressTypes;
|
|
|
|
private final InternetProtocolFamily[] resolvedInternetProtocolFamilies;
|
2015-12-14 15:27:49 +01:00
|
|
|
private final boolean recursionDesired;
|
|
|
|
private final int maxPayloadSize;
|
|
|
|
private final boolean optResourceEnabled;
|
|
|
|
private final HostsFileEntriesResolver hostsFileEntriesResolver;
|
2017-02-02 10:32:33 +01:00
|
|
|
private final DnsServerAddressStreamProvider dnsServerAddressStreamProvider;
|
2016-06-30 23:12:11 +02:00
|
|
|
private final String[] searchDomains;
|
|
|
|
private final int ndots;
|
2017-01-25 09:28:22 +01:00
|
|
|
private final boolean supportsAAAARecords;
|
|
|
|
private final boolean supportsARecords;
|
2017-01-19 13:18:19 +01:00
|
|
|
private final InternetProtocolFamily preferredAddressType;
|
|
|
|
private final DnsRecordType[] resolveRecordTypes;
|
2017-01-18 10:00:01 +01:00
|
|
|
private final boolean decodeIdn;
|
2017-04-07 03:09:28 +02:00
|
|
|
private final DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory;
|
2019-05-09 08:06:52 +02:00
|
|
|
private final boolean completeOncePreferredResolved;
|
2019-05-17 14:37:11 +02:00
|
|
|
private final ChannelFactory<? extends SocketChannel> socketChannelFactory;
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2017-01-18 10:00:01 +01:00
|
|
|
/**
|
|
|
|
* 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
|
2017-01-25 09:28:22 +01:00
|
|
|
* @param authoritativeDnsServerCache the cache used to find the authoritative DNS server for a domain
|
2017-04-07 03:09:28 +02:00
|
|
|
* @param dnsQueryLifecycleObserverFactory used to generate new instances of {@link DnsQueryLifecycleObserver} which
|
|
|
|
* can be used to track metrics for DNS servers.
|
2017-01-18 10:00:01 +01:00
|
|
|
* @param queryTimeoutMillis timeout of each DNS query in millis
|
2017-02-08 16:43:15 +01:00
|
|
|
* @param resolvedAddressTypes the preferred address types
|
2017-01-18 10:00:01 +01:00
|
|
|
* @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
|
2017-03-31 02:02:16 +02:00
|
|
|
* @param dnsServerAddressStreamProvider The {@link DnsServerAddressStreamProvider} used to determine the name
|
2017-02-02 10:32:33 +01:00
|
|
|
* servers for each hostname lookup.
|
2017-01-18 10:00:01 +01:00
|
|
|
* @param searchDomains the list of search domain
|
2017-06-13 10:51:48 +02:00
|
|
|
* (can be null, if so, will try to default to the underlying platform ones)
|
2017-01-18 10:00:01 +01:00
|
|
|
* @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>.
|
2018-09-27 17:05:35 +02:00
|
|
|
* @deprecated Use {@link DnsNameResolverBuilder}.
|
2017-01-18 10:00:01 +01:00
|
|
|
*/
|
2018-08-22 17:49:22 +02:00
|
|
|
@Deprecated
|
2017-01-18 10:00:01 +01:00
|
|
|
public DnsNameResolver(
|
|
|
|
EventLoop eventLoop,
|
|
|
|
ChannelFactory<? extends DatagramChannel> channelFactory,
|
|
|
|
final DnsCache resolveCache,
|
2018-08-06 08:31:17 +02:00
|
|
|
final DnsCache authoritativeDnsServerCache,
|
2017-04-07 03:09:28 +02:00
|
|
|
DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory,
|
2017-01-18 10:00:01 +01:00
|
|
|
long queryTimeoutMillis,
|
2017-02-08 16:43:15 +01:00
|
|
|
ResolvedAddressTypes resolvedAddressTypes,
|
2017-01-18 10:00:01 +01:00
|
|
|
boolean recursionDesired,
|
|
|
|
int maxQueriesPerResolve,
|
|
|
|
boolean traceEnabled,
|
|
|
|
int maxPayloadSize,
|
|
|
|
boolean optResourceEnabled,
|
|
|
|
HostsFileEntriesResolver hostsFileEntriesResolver,
|
2017-02-02 10:32:33 +01:00
|
|
|
DnsServerAddressStreamProvider dnsServerAddressStreamProvider,
|
2017-01-18 10:00:01 +01:00
|
|
|
String[] searchDomains,
|
|
|
|
int ndots,
|
|
|
|
boolean decodeIdn) {
|
2018-08-22 17:49:22 +02:00
|
|
|
this(eventLoop, channelFactory, resolveCache,
|
|
|
|
new AuthoritativeDnsServerCacheAdapter(authoritativeDnsServerCache), dnsQueryLifecycleObserverFactory,
|
|
|
|
queryTimeoutMillis, resolvedAddressTypes, recursionDesired, maxQueriesPerResolve, traceEnabled,
|
|
|
|
maxPayloadSize, optResourceEnabled, hostsFileEntriesResolver, dnsServerAddressStreamProvider,
|
|
|
|
searchDomains, ndots, decodeIdn);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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>.
|
2018-09-27 17:05:35 +02:00
|
|
|
* @deprecated Use {@link DnsNameResolverBuilder}.
|
2018-08-22 17:49:22 +02:00
|
|
|
*/
|
2018-09-27 17:05:35 +02:00
|
|
|
@Deprecated
|
2018-08-22 17:49:22 +02:00
|
|
|
public DnsNameResolver(
|
|
|
|
EventLoop eventLoop,
|
|
|
|
ChannelFactory<? extends DatagramChannel> channelFactory,
|
|
|
|
final DnsCache resolveCache,
|
|
|
|
final AuthoritativeDnsServerCache 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) {
|
2019-05-17 14:37:11 +02:00
|
|
|
this(eventLoop, channelFactory, null, resolveCache, NoopDnsCnameCache.INSTANCE, authoritativeDnsServerCache,
|
2018-09-27 17:05:35 +02:00
|
|
|
dnsQueryLifecycleObserverFactory, queryTimeoutMillis, resolvedAddressTypes, recursionDesired,
|
|
|
|
maxQueriesPerResolve, traceEnabled, maxPayloadSize, optResourceEnabled, hostsFileEntriesResolver,
|
2019-05-09 08:06:52 +02:00
|
|
|
dnsServerAddressStreamProvider, searchDomains, ndots, decodeIdn, false);
|
2018-09-27 17:05:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
DnsNameResolver(
|
|
|
|
EventLoop eventLoop,
|
|
|
|
ChannelFactory<? extends DatagramChannel> channelFactory,
|
2019-05-17 14:37:11 +02:00
|
|
|
ChannelFactory<? extends SocketChannel> socketChannelFactory,
|
2018-09-27 17:05:35 +02:00
|
|
|
final DnsCache resolveCache,
|
|
|
|
final DnsCnameCache cnameCache,
|
|
|
|
final AuthoritativeDnsServerCache 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,
|
2019-05-09 08:06:52 +02:00
|
|
|
boolean decodeIdn,
|
|
|
|
boolean completeOncePreferredResolved) {
|
2014-09-19 15:36:32 +02:00
|
|
|
super(eventLoop);
|
2015-12-14 15:27:49 +01:00
|
|
|
this.queryTimeoutMillis = checkPositive(queryTimeoutMillis, "queryTimeoutMillis");
|
2017-02-08 16:43:15 +01:00
|
|
|
this.resolvedAddressTypes = resolvedAddressTypes != null ? resolvedAddressTypes : DEFAULT_RESOLVE_ADDRESS_TYPES;
|
2015-12-14 15:27:49 +01:00
|
|
|
this.recursionDesired = recursionDesired;
|
|
|
|
this.maxQueriesPerResolve = checkPositive(maxQueriesPerResolve, "maxQueriesPerResolve");
|
|
|
|
this.maxPayloadSize = checkPositive(maxPayloadSize, "maxPayloadSize");
|
|
|
|
this.optResourceEnabled = optResourceEnabled;
|
|
|
|
this.hostsFileEntriesResolver = checkNotNull(hostsFileEntriesResolver, "hostsFileEntriesResolver");
|
2017-02-02 10:32:33 +01:00
|
|
|
this.dnsServerAddressStreamProvider =
|
|
|
|
checkNotNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
|
2017-01-16 13:06:13 +01:00
|
|
|
this.resolveCache = checkNotNull(resolveCache, "resolveCache");
|
2018-09-27 17:05:35 +02:00
|
|
|
this.cnameCache = checkNotNull(cnameCache, "cnameCache");
|
2017-06-22 10:20:23 +02:00
|
|
|
this.dnsQueryLifecycleObserverFactory = traceEnabled ?
|
2018-08-22 17:49:22 +02:00
|
|
|
dnsQueryLifecycleObserverFactory instanceof NoopDnsQueryLifecycleObserverFactory ?
|
|
|
|
new TraceDnsQueryLifeCycleObserverFactory() :
|
|
|
|
new BiDnsQueryLifecycleObserverFactory(new TraceDnsQueryLifeCycleObserverFactory(),
|
|
|
|
dnsQueryLifecycleObserverFactory) :
|
2017-04-07 03:09:28 +02:00
|
|
|
checkNotNull(dnsQueryLifecycleObserverFactory, "dnsQueryLifecycleObserverFactory");
|
2017-06-13 10:51:48 +02:00
|
|
|
this.searchDomains = searchDomains != null ? searchDomains.clone() : DEFAULT_SEARCH_DOMAINS;
|
2017-06-21 22:48:45 +02:00
|
|
|
this.ndots = ndots >= 0 ? ndots : DEFAULT_NDOTS;
|
2017-01-18 10:00:01 +01:00
|
|
|
this.decodeIdn = decodeIdn;
|
2019-05-09 08:06:52 +02:00
|
|
|
this.completeOncePreferredResolved = completeOncePreferredResolved;
|
2019-05-17 14:37:11 +02:00
|
|
|
this.socketChannelFactory = socketChannelFactory;
|
2017-02-08 16:43:15 +01:00
|
|
|
switch (this.resolvedAddressTypes) {
|
|
|
|
case IPV4_ONLY:
|
|
|
|
supportsAAAARecords = false;
|
|
|
|
supportsARecords = true;
|
|
|
|
resolveRecordTypes = IPV4_ONLY_RESOLVED_RECORD_TYPES;
|
|
|
|
resolvedInternetProtocolFamilies = IPV4_ONLY_RESOLVED_PROTOCOL_FAMILIES;
|
|
|
|
break;
|
|
|
|
case IPV4_PREFERRED:
|
|
|
|
supportsAAAARecords = true;
|
|
|
|
supportsARecords = true;
|
|
|
|
resolveRecordTypes = IPV4_PREFERRED_RESOLVED_RECORD_TYPES;
|
|
|
|
resolvedInternetProtocolFamilies = IPV4_PREFERRED_RESOLVED_PROTOCOL_FAMILIES;
|
|
|
|
break;
|
|
|
|
case IPV6_ONLY:
|
|
|
|
supportsAAAARecords = true;
|
|
|
|
supportsARecords = false;
|
|
|
|
resolveRecordTypes = IPV6_ONLY_RESOLVED_RECORD_TYPES;
|
|
|
|
resolvedInternetProtocolFamilies = IPV6_ONLY_RESOLVED_PROTOCOL_FAMILIES;
|
|
|
|
break;
|
|
|
|
case IPV6_PREFERRED:
|
|
|
|
supportsAAAARecords = true;
|
|
|
|
supportsARecords = true;
|
|
|
|
resolveRecordTypes = IPV6_PREFERRED_RESOLVED_RECORD_TYPES;
|
|
|
|
resolvedInternetProtocolFamilies = IPV6_PREFERRED_RESOLVED_PROTOCOL_FAMILIES;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
|
2017-01-19 13:18:19 +01:00
|
|
|
}
|
2018-10-30 13:15:16 +01:00
|
|
|
preferredAddressType = preferredAddressType(this.resolvedAddressTypes);
|
2018-08-22 17:49:22 +02:00
|
|
|
this.authoritativeDnsServerCache = checkNotNull(authoritativeDnsServerCache, "authoritativeDnsServerCache");
|
|
|
|
nameServerComparator = new NameServerComparator(preferredAddressType.addressType());
|
2017-01-19 13:18:19 +01:00
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
Bootstrap b = new Bootstrap();
|
|
|
|
b.group(executor());
|
|
|
|
b.channelFactory(channelFactory);
|
2016-06-28 11:44:04 +02:00
|
|
|
b.option(ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION, true);
|
|
|
|
final DnsResponseHandler responseHandler = new DnsResponseHandler(executor().<Channel>newPromise());
|
2014-09-19 15:36:32 +02:00
|
|
|
b.handler(new ChannelInitializer<DatagramChannel>() {
|
|
|
|
@Override
|
2019-05-17 14:37:11 +02:00
|
|
|
protected void initChannel(DatagramChannel ch) {
|
|
|
|
ch.pipeline().addLast(DATAGRAM_ENCODER, DATAGRAM_DECODER, responseHandler);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-06-28 11:44:04 +02:00
|
|
|
channelFuture = responseHandler.channelActivePromise;
|
2018-09-11 20:34:37 +02:00
|
|
|
ChannelFuture future = b.register();
|
|
|
|
Throwable cause = future.cause();
|
|
|
|
if (cause != null) {
|
|
|
|
if (cause instanceof RuntimeException) {
|
|
|
|
throw (RuntimeException) cause;
|
|
|
|
}
|
|
|
|
if (cause instanceof Error) {
|
|
|
|
throw (Error) cause;
|
|
|
|
}
|
|
|
|
throw new IllegalStateException("Unable to create / register Channel", cause);
|
|
|
|
}
|
|
|
|
ch = future.channel();
|
2016-06-28 11:44:04 +02:00
|
|
|
ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize));
|
|
|
|
|
|
|
|
ch.closeFuture().addListener(new ChannelFutureListener() {
|
2014-09-19 15:36:32 +02:00
|
|
|
@Override
|
2018-08-06 08:31:17 +02:00
|
|
|
public void operationComplete(ChannelFuture future) {
|
2016-01-07 17:49:15 +01:00
|
|
|
resolveCache.clear();
|
2018-09-27 17:05:35 +02:00
|
|
|
cnameCache.clear();
|
2018-08-06 08:31:17 +02:00
|
|
|
authoritativeDnsServerCache.clear();
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-08-22 17:49:22 +02:00
|
|
|
static InternetProtocolFamily preferredAddressType(ResolvedAddressTypes resolvedAddressTypes) {
|
|
|
|
switch (resolvedAddressTypes) {
|
|
|
|
case IPV4_ONLY:
|
|
|
|
case IPV4_PREFERRED:
|
|
|
|
return InternetProtocolFamily.IPv4;
|
|
|
|
case IPV6_ONLY:
|
|
|
|
case IPV6_PREFERRED:
|
|
|
|
return InternetProtocolFamily.IPv6;
|
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-25 09:28:22 +01:00
|
|
|
// Only here to override in unit tests.
|
2018-08-22 17:49:22 +02:00
|
|
|
InetSocketAddress newRedirectServerAddress(InetAddress server) {
|
|
|
|
return new InetSocketAddress(server, DNS_PORT);
|
2017-01-25 09:28:22 +01:00
|
|
|
}
|
|
|
|
|
2017-04-07 03:09:28 +02:00
|
|
|
final DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory() {
|
|
|
|
return dnsQueryLifecycleObserverFactory;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-08-22 17:49:22 +02:00
|
|
|
* Creates a new {@link DnsServerAddressStream} to following a redirected DNS query. By overriding this
|
|
|
|
* it provides the opportunity to sort the name servers before following a redirected DNS query.
|
|
|
|
*
|
|
|
|
* @param hostname the hostname.
|
|
|
|
* @param nameservers The addresses of the DNS servers which are used in the event of a redirect. This may
|
|
|
|
* contain resolved and unresolved addresses so the used {@link DnsServerAddressStream} must
|
|
|
|
* allow unresolved addresses if you want to include these as well.
|
|
|
|
* @return A {@link DnsServerAddressStream} which will be used to follow the DNS redirect or {@code null} if
|
|
|
|
* none should be followed.
|
2017-04-07 03:09:28 +02:00
|
|
|
*/
|
2018-08-22 17:49:22 +02:00
|
|
|
protected DnsServerAddressStream newRedirectDnsServerStream(
|
|
|
|
@SuppressWarnings("unused") String hostname, List<InetSocketAddress> nameservers) {
|
2018-09-27 19:45:58 +02:00
|
|
|
DnsServerAddressStream cached = authoritativeDnsServerCache().get(hostname);
|
|
|
|
if (cached == null || cached.size() == 0) {
|
|
|
|
// If there is no cache hit (which may be the case for example when a NoopAuthoritativeDnsServerCache
|
|
|
|
// is used), we will just directly use the provided nameservers.
|
|
|
|
Collections.sort(nameservers, nameServerComparator);
|
|
|
|
return new SequentialDnsServerAddressStream(nameservers, 0);
|
|
|
|
}
|
|
|
|
return cached;
|
2017-04-07 03:09:28 +02:00
|
|
|
}
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
/**
|
2016-01-07 17:49:15 +01:00
|
|
|
* Returns the resolution cache.
|
2014-09-19 15:36:32 +02:00
|
|
|
*/
|
2016-01-07 17:49:15 +01:00
|
|
|
public DnsCache resolveCache() {
|
|
|
|
return resolveCache;
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2018-09-27 17:05:35 +02:00
|
|
|
/**
|
|
|
|
* Returns the {@link DnsCnameCache}.
|
|
|
|
*/
|
|
|
|
DnsCnameCache cnameCache() {
|
|
|
|
return cnameCache;
|
|
|
|
}
|
|
|
|
|
2017-01-25 09:28:22 +01:00
|
|
|
/**
|
|
|
|
* Returns the cache used for authoritative DNS servers for a domain.
|
|
|
|
*/
|
2018-08-22 17:49:22 +02:00
|
|
|
public AuthoritativeDnsServerCache authoritativeDnsServerCache() {
|
2017-01-25 09:28:22 +01:00
|
|
|
return authoritativeDnsServerCache;
|
|
|
|
}
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
/**
|
|
|
|
* Returns the timeout of each DNS query performed by this resolver (in milliseconds).
|
|
|
|
* The default value is 5 seconds.
|
|
|
|
*/
|
|
|
|
public long queryTimeoutMillis() {
|
|
|
|
return queryTimeoutMillis;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-02-08 16:43:15 +01:00
|
|
|
* Returns the {@link ResolvedAddressTypes} resolved by {@link #resolve(String)}.
|
2014-09-19 15:36:32 +02:00
|
|
|
* The default value depends on the value of the system property {@code "java.net.preferIPv6Addresses"}.
|
|
|
|
*/
|
2017-02-08 16:43:15 +01:00
|
|
|
public ResolvedAddressTypes resolvedAddressTypes() {
|
|
|
|
return resolvedAddressTypes;
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2017-02-08 16:43:15 +01:00
|
|
|
InternetProtocolFamily[] resolvedInternetProtocolFamiliesUnsafe() {
|
|
|
|
return resolvedInternetProtocolFamilies;
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2016-06-30 23:12:11 +02:00
|
|
|
final String[] searchDomains() {
|
|
|
|
return searchDomains;
|
|
|
|
}
|
|
|
|
|
|
|
|
final int ndots() {
|
|
|
|
return ndots;
|
|
|
|
}
|
|
|
|
|
2017-01-25 09:28:22 +01:00
|
|
|
final boolean supportsAAAARecords() {
|
|
|
|
return supportsAAAARecords;
|
2017-01-19 13:18:19 +01:00
|
|
|
}
|
|
|
|
|
2017-01-25 09:28:22 +01:00
|
|
|
final boolean supportsARecords() {
|
|
|
|
return supportsARecords;
|
2017-01-19 13:18:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
final InternetProtocolFamily preferredAddressType() {
|
|
|
|
return preferredAddressType;
|
|
|
|
}
|
|
|
|
|
|
|
|
final DnsRecordType[] resolveRecordTypes() {
|
|
|
|
return resolveRecordTypes;
|
|
|
|
}
|
|
|
|
|
2017-01-18 10:00:01 +01:00
|
|
|
final boolean isDecodeIdn() {
|
|
|
|
return decodeIdn;
|
|
|
|
}
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2015-10-02 15:16:16 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
/**
|
|
|
|
* Returns the component that tries to resolve hostnames against the hosts file prior to asking to
|
|
|
|
* remotes DNS servers.
|
|
|
|
*/
|
|
|
|
public HostsFileEntriesResolver hostsFileEntriesResolver() {
|
|
|
|
return hostsFileEntriesResolver;
|
|
|
|
}
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
/**
|
|
|
|
* 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() {
|
2016-08-01 07:28:20 +02:00
|
|
|
if (ch.isOpen()) {
|
|
|
|
ch.close();
|
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected EventLoop executor() {
|
|
|
|
return (EventLoop) super.executor();
|
|
|
|
}
|
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
private InetAddress resolveHostsFileEntry(String hostname) {
|
2016-06-13 12:44:35 +02:00
|
|
|
if (hostsFileEntriesResolver == null) {
|
|
|
|
return null;
|
|
|
|
} else {
|
2017-02-08 16:43:15 +01:00
|
|
|
InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
|
2016-06-13 12:44:35 +02:00
|
|
|
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;
|
|
|
|
}
|
2015-12-14 15:27:49 +01:00
|
|
|
}
|
|
|
|
|
2016-07-29 10:30:08 +02:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
@Override
|
2015-12-13 00:11:59 +01:00
|
|
|
protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
|
2017-02-08 16:43:15 +01:00
|
|
|
doResolve(inetHost, EMPTY_ADDITIONALS, promise, resolveCache);
|
2016-07-29 10:30:08 +02:00
|
|
|
}
|
|
|
|
|
2018-03-29 22:01:25 +02:00
|
|
|
/**
|
|
|
|
* Resolves the {@link DnsRecord}s that are matched by the specified {@link DnsQuestion}. Unlike
|
|
|
|
* {@link #query(DnsQuestion)}, this method handles redirection, CNAMEs and multiple name servers.
|
|
|
|
* If the specified {@link DnsQuestion} is {@code A} or {@code AAAA}, this method looks up the configured
|
|
|
|
* {@link HostsFileEntries} before sending a query to the name servers. If a match is found in the
|
|
|
|
* {@link HostsFileEntries}, a synthetic {@code A} or {@code AAAA} record will be returned.
|
|
|
|
*
|
|
|
|
* @param question the question
|
|
|
|
*
|
|
|
|
* @return the list of the {@link DnsRecord}s as the result of the resolution
|
|
|
|
*/
|
|
|
|
public final Future<List<DnsRecord>> resolveAll(DnsQuestion question) {
|
|
|
|
return resolveAll(question, EMPTY_ADDITIONALS, executor().<List<DnsRecord>>newPromise());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolves the {@link DnsRecord}s that are matched by the specified {@link DnsQuestion}. Unlike
|
|
|
|
* {@link #query(DnsQuestion)}, this method handles redirection, CNAMEs and multiple name servers.
|
|
|
|
* If the specified {@link DnsQuestion} is {@code A} or {@code AAAA}, this method looks up the configured
|
|
|
|
* {@link HostsFileEntries} before sending a query to the name servers. If a match is found in the
|
|
|
|
* {@link HostsFileEntries}, a synthetic {@code A} or {@code AAAA} record will be returned.
|
|
|
|
*
|
|
|
|
* @param question the question
|
|
|
|
* @param additionals additional records ({@code OPT})
|
|
|
|
*
|
|
|
|
* @return the list of the {@link DnsRecord}s as the result of the resolution
|
|
|
|
*/
|
|
|
|
public final Future<List<DnsRecord>> resolveAll(DnsQuestion question, Iterable<DnsRecord> additionals) {
|
|
|
|
return resolveAll(question, additionals, executor().<List<DnsRecord>>newPromise());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolves the {@link DnsRecord}s that are matched by the specified {@link DnsQuestion}. Unlike
|
|
|
|
* {@link #query(DnsQuestion)}, this method handles redirection, CNAMEs and multiple name servers.
|
|
|
|
* If the specified {@link DnsQuestion} is {@code A} or {@code AAAA}, this method looks up the configured
|
|
|
|
* {@link HostsFileEntries} before sending a query to the name servers. If a match is found in the
|
|
|
|
* {@link HostsFileEntries}, a synthetic {@code A} or {@code AAAA} record will be returned.
|
|
|
|
*
|
|
|
|
* @param question the question
|
|
|
|
* @param additionals additional records ({@code OPT})
|
|
|
|
* @param promise the {@link Promise} which will be fulfilled when the resolution is finished
|
|
|
|
*
|
|
|
|
* @return the list of the {@link DnsRecord}s as the result of the resolution
|
|
|
|
*/
|
|
|
|
public final Future<List<DnsRecord>> resolveAll(DnsQuestion question, Iterable<DnsRecord> additionals,
|
|
|
|
Promise<List<DnsRecord>> promise) {
|
|
|
|
final DnsRecord[] additionalsArray = toArray(additionals, true);
|
|
|
|
return resolveAll(question, additionalsArray, promise);
|
|
|
|
}
|
|
|
|
|
|
|
|
private Future<List<DnsRecord>> resolveAll(DnsQuestion question, DnsRecord[] additionals,
|
|
|
|
Promise<List<DnsRecord>> promise) {
|
|
|
|
checkNotNull(question, "question");
|
|
|
|
checkNotNull(promise, "promise");
|
|
|
|
|
|
|
|
// Respect /etc/hosts as well if the record type is A or AAAA.
|
|
|
|
final DnsRecordType type = question.type();
|
|
|
|
final String hostname = question.name();
|
|
|
|
|
|
|
|
if (type == DnsRecordType.A || type == DnsRecordType.AAAA) {
|
|
|
|
final InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
|
|
|
|
if (hostsFileEntry != null) {
|
|
|
|
ByteBuf content = null;
|
|
|
|
if (hostsFileEntry instanceof Inet4Address) {
|
|
|
|
if (type == DnsRecordType.A) {
|
|
|
|
content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
|
|
|
|
}
|
|
|
|
} else if (hostsFileEntry instanceof Inet6Address) {
|
|
|
|
if (type == DnsRecordType.AAAA) {
|
|
|
|
content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (content != null) {
|
|
|
|
// Our current implementation does not support reloading the hosts file,
|
|
|
|
// so use a fairly large TTL (1 day, i.e. 86400 seconds).
|
|
|
|
trySuccess(promise, Collections.<DnsRecord>singletonList(
|
|
|
|
new DefaultDnsRawRecord(hostname, type, 86400, content)));
|
|
|
|
return promise;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// It was not A/AAAA question or there was no entry in /etc/hosts.
|
|
|
|
final DnsServerAddressStream nameServerAddrs =
|
|
|
|
dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
|
|
|
|
new DnsRecordResolveContext(this, question, additionals, nameServerAddrs).resolve(promise);
|
|
|
|
return promise;
|
|
|
|
}
|
|
|
|
|
2016-07-29 10:30:08 +02:00
|
|
|
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()) {
|
2017-02-08 16:43:15 +01:00
|
|
|
return EMPTY_ADDITIONALS;
|
2016-07-29 10:30:08 +02:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
2016-01-07 17:49:15 +01:00
|
|
|
}
|
|
|
|
|
2017-02-07 23:09:55 +01:00
|
|
|
private InetAddress loopbackAddress() {
|
2017-04-19 20:54:23 +02:00
|
|
|
return preferredAddressType().localhost();
|
2017-01-20 15:46:46 +01:00
|
|
|
}
|
|
|
|
|
2016-01-07 17:49:15 +01:00
|
|
|
/**
|
|
|
|
* 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,
|
2016-07-29 10:30:08 +02:00
|
|
|
DnsRecord[] additionals,
|
2016-01-07 17:49:15 +01:00
|
|
|
Promise<InetAddress> promise,
|
|
|
|
DnsCache resolveCache) throws Exception {
|
2017-02-07 23:09:55 +01:00
|
|
|
if (inetHost == null || inetHost.isEmpty()) {
|
|
|
|
// If an empty hostname is used we should use "localhost", just like InetAddress.getByName(...) does.
|
|
|
|
promise.setSuccess(loopbackAddress());
|
|
|
|
return;
|
|
|
|
}
|
2015-12-13 00:11:59 +01:00
|
|
|
final byte[] bytes = NetUtil.createByteArrayFromIpAddressString(inetHost);
|
2015-07-12 12:34:05 +02:00
|
|
|
if (bytes != null) {
|
2015-12-13 00:11:59 +01:00
|
|
|
// The inetHost is actually an ipaddress.
|
|
|
|
promise.setSuccess(InetAddress.getByAddress(bytes));
|
2015-07-12 12:34:05 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-12-13 00:11:59 +01:00
|
|
|
final String hostname = hostname(inetHost);
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
|
|
|
|
if (hostsFileEntry != null) {
|
|
|
|
promise.setSuccess(hostsFileEntry);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-07-29 10:30:08 +02:00
|
|
|
if (!doResolveCached(hostname, additionals, promise, resolveCache)) {
|
2019-04-15 21:42:04 +02:00
|
|
|
doResolveUncached(hostname, additionals, promise, resolveCache, true);
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-07 17:49:15 +01:00
|
|
|
private boolean doResolveCached(String hostname,
|
2016-07-29 10:30:08 +02:00
|
|
|
DnsRecord[] additionals,
|
2016-01-07 17:49:15 +01:00
|
|
|
Promise<InetAddress> promise,
|
|
|
|
DnsCache resolveCache) {
|
2017-08-19 04:16:51 +02:00
|
|
|
final List<? extends DnsCacheEntry> cachedEntries = resolveCache.get(hostname, additionals);
|
2018-01-16 12:35:34 +01:00
|
|
|
if (cachedEntries == null || cachedEntries.isEmpty()) {
|
2015-07-12 12:34:05 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-01-16 12:35:34 +01:00
|
|
|
Throwable cause = cachedEntries.get(0).cause();
|
|
|
|
if (cause == null) {
|
2015-07-12 12:34:05 +02:00
|
|
|
final int numEntries = cachedEntries.size();
|
2018-01-16 12:35:34 +01:00
|
|
|
// 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())) {
|
|
|
|
trySuccess(promise, e.address());
|
|
|
|
return true;
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-01-16 12:35:34 +01:00
|
|
|
return false;
|
|
|
|
} else {
|
2016-07-29 10:30:08 +02:00
|
|
|
tryFailure(promise, cause);
|
|
|
|
return true;
|
|
|
|
}
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
|
2018-03-29 22:01:25 +02:00
|
|
|
static <T> void trySuccess(Promise<T> promise, T result) {
|
2015-07-12 12:34:05 +02:00
|
|
|
if (!promise.trySuccess(result)) {
|
2019-04-10 07:13:53 +02:00
|
|
|
// There is nothing really wrong with not be able to notify the promise as we may have raced here because
|
|
|
|
// of multiple queries that have been executed. Log it with trace level anyway just in case the user
|
|
|
|
// wants to better understand what happened.
|
|
|
|
logger.trace("Failed to notify success ({}) to a promise: {}", result, promise);
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-29 10:30:08 +02:00
|
|
|
private static void tryFailure(Promise<?> promise, Throwable cause) {
|
|
|
|
if (!promise.tryFailure(cause)) {
|
2019-04-10 07:13:53 +02:00
|
|
|
// There is nothing really wrong with not be able to notify the promise as we may have raced here because
|
|
|
|
// of multiple queries that have been executed. Log it with trace level anyway just in case the user
|
|
|
|
// wants to better understand what happened.
|
|
|
|
logger.trace("Failed to notify failure to a promise: {}", promise, cause);
|
2016-07-29 10:30:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-07 17:49:15 +01:00
|
|
|
private void doResolveUncached(String hostname,
|
2016-07-29 10:30:08 +02:00
|
|
|
DnsRecord[] additionals,
|
2018-03-29 22:01:25 +02:00
|
|
|
final Promise<InetAddress> promise,
|
2019-04-15 21:42:04 +02:00
|
|
|
DnsCache resolveCache, boolean completeEarlyIfPossible) {
|
2018-03-29 22:01:25 +02:00
|
|
|
final Promise<List<InetAddress>> allPromise = executor().newPromise();
|
2019-04-15 21:42:04 +02:00
|
|
|
doResolveAllUncached(hostname, additionals, allPromise, resolveCache, true);
|
2018-03-29 22:01:25 +02:00
|
|
|
allPromise.addListener(new FutureListener<List<InetAddress>>() {
|
|
|
|
@Override
|
|
|
|
public void operationComplete(Future<List<InetAddress>> future) {
|
|
|
|
if (future.isSuccess()) {
|
|
|
|
trySuccess(promise, future.getNow().get(0));
|
|
|
|
} else {
|
|
|
|
tryFailure(promise, future.cause());
|
2016-06-30 23:12:11 +02:00
|
|
|
}
|
|
|
|
}
|
2018-03-29 22:01:25 +02:00
|
|
|
});
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-12-13 00:11:59 +01:00
|
|
|
protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise) throws Exception {
|
2017-02-08 16:43:15 +01:00
|
|
|
doResolveAll(inetHost, EMPTY_ADDITIONALS, promise, resolveCache);
|
2016-01-07 17:49:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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,
|
2016-07-29 10:30:08 +02:00
|
|
|
DnsRecord[] additionals,
|
2016-01-07 17:49:15 +01:00
|
|
|
Promise<List<InetAddress>> promise,
|
|
|
|
DnsCache resolveCache) throws Exception {
|
2017-02-07 23:09:55 +01:00
|
|
|
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;
|
|
|
|
}
|
2015-12-13 00:11:59 +01:00
|
|
|
final byte[] bytes = NetUtil.createByteArrayFromIpAddressString(inetHost);
|
2015-07-12 12:34:05 +02:00
|
|
|
if (bytes != null) {
|
2015-07-10 22:00:30 +02:00
|
|
|
// The unresolvedAddress was created via a String that contains an ipaddress.
|
2015-12-13 00:11:59 +01:00
|
|
|
promise.setSuccess(Collections.singletonList(InetAddress.getByAddress(bytes)));
|
2015-07-12 12:34:05 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-12-13 00:11:59 +01:00
|
|
|
final String hostname = hostname(inetHost);
|
2015-07-12 12:34:05 +02:00
|
|
|
|
2015-12-14 15:27:49 +01:00
|
|
|
InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
|
|
|
|
if (hostsFileEntry != null) {
|
|
|
|
promise.setSuccess(Collections.singletonList(hostsFileEntry));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-08-22 17:49:22 +02:00
|
|
|
if (!doResolveAllCached(hostname, additionals, promise, resolveCache, resolvedInternetProtocolFamilies)) {
|
2019-05-09 08:06:52 +02:00
|
|
|
doResolveAllUncached(hostname, additionals, promise, resolveCache, completeOncePreferredResolved);
|
2015-07-10 22:00:30 +02:00
|
|
|
}
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2018-08-22 17:49:22 +02:00
|
|
|
static boolean doResolveAllCached(String hostname,
|
|
|
|
DnsRecord[] additionals,
|
|
|
|
Promise<List<InetAddress>> promise,
|
|
|
|
DnsCache resolveCache,
|
|
|
|
InternetProtocolFamily[] resolvedInternetProtocolFamilies) {
|
2017-08-19 04:16:51 +02:00
|
|
|
final List<? extends DnsCacheEntry> cachedEntries = resolveCache.get(hostname, additionals);
|
2018-01-16 12:35:34 +01:00
|
|
|
if (cachedEntries == null || cachedEntries.isEmpty()) {
|
2015-07-12 12:34:05 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-01-16 12:35:34 +01:00
|
|
|
Throwable cause = cachedEntries.get(0).cause();
|
|
|
|
if (cause == null) {
|
|
|
|
List<InetAddress> result = null;
|
2015-07-12 12:34:05 +02:00
|
|
|
final int numEntries = cachedEntries.size();
|
2018-01-16 12:35:34 +01:00
|
|
|
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);
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
2018-01-16 12:35:34 +01:00
|
|
|
result.add(e.address());
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-01-16 12:35:34 +01:00
|
|
|
if (result != null) {
|
|
|
|
trySuccess(promise, result);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
} else {
|
2016-07-29 10:30:08 +02:00
|
|
|
tryFailure(promise, cause);
|
|
|
|
return true;
|
|
|
|
}
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
|
2018-11-21 06:42:40 +01:00
|
|
|
private void doResolveAllUncached(final String hostname,
|
|
|
|
final DnsRecord[] additionals,
|
|
|
|
final Promise<List<InetAddress>> promise,
|
2019-04-15 21:42:04 +02:00
|
|
|
final DnsCache resolveCache,
|
|
|
|
final boolean completeEarlyIfPossible) {
|
2018-11-21 06:42:40 +01:00
|
|
|
// Call doResolveUncached0(...) in the EventLoop as we may need to submit multiple queries which would need
|
|
|
|
// to submit multiple Runnable at the end if we are not already on the EventLoop.
|
|
|
|
EventExecutor executor = executor();
|
|
|
|
if (executor.inEventLoop()) {
|
2019-04-15 21:42:04 +02:00
|
|
|
doResolveAllUncached0(hostname, additionals, promise, resolveCache, completeEarlyIfPossible);
|
2018-11-21 06:42:40 +01:00
|
|
|
} else {
|
|
|
|
executor.execute(new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
2019-04-15 21:42:04 +02:00
|
|
|
doResolveAllUncached0(hostname, additionals, promise, resolveCache, completeEarlyIfPossible);
|
2018-11-21 06:42:40 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void doResolveAllUncached0(String hostname,
|
2019-04-15 21:42:04 +02:00
|
|
|
DnsRecord[] additionals,
|
|
|
|
Promise<List<InetAddress>> promise,
|
|
|
|
DnsCache resolveCache,
|
|
|
|
boolean completeEarlyIfPossible) {
|
2018-11-21 06:42:40 +01:00
|
|
|
|
|
|
|
assert executor().inEventLoop();
|
|
|
|
|
2018-03-29 22:01:25 +02:00
|
|
|
final DnsServerAddressStream nameServerAddrs =
|
|
|
|
dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
|
2019-04-15 21:42:04 +02:00
|
|
|
new DnsAddressResolveContext(this, hostname, additionals, nameServerAddrs, resolveCache,
|
|
|
|
authoritativeDnsServerCache, completeEarlyIfPossible).resolve(promise);
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
|
2015-12-13 00:11:59 +01:00
|
|
|
private static String hostname(String inetHost) {
|
2016-06-30 23:12:11 +02:00
|
|
|
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;
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
/**
|
|
|
|
* Sends a DNS query with the specified question.
|
|
|
|
*/
|
2015-03-16 07:46:14 +01:00
|
|
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(DnsQuestion question) {
|
2015-07-12 12:34:05 +02:00
|
|
|
return query(nextNameServerAddress(), question);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2016-02-13 14:06:06 +01:00
|
|
|
/**
|
|
|
|
* Sends a DNS query with the specified question with additional records.
|
|
|
|
*/
|
|
|
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
|
2016-07-29 10:30:08 +02:00
|
|
|
DnsQuestion question, Iterable<DnsRecord> additionals) {
|
|
|
|
return query(nextNameServerAddress(), question, additionals);
|
2016-02-13 14:06:06 +01:00
|
|
|
}
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
/**
|
|
|
|
* Sends a DNS query with the specified question.
|
|
|
|
*/
|
2015-03-16 07:46:14 +01:00
|
|
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
|
|
|
|
DnsQuestion question, Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
|
2016-02-13 14:06:06 +01:00
|
|
|
return query(nextNameServerAddress(), question, Collections.<DnsRecord>emptyList(), promise);
|
2015-07-12 12:34:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private InetSocketAddress nextNameServerAddress() {
|
2015-08-19 04:51:15 +02:00
|
|
|
return nameServerAddrStream.get().next();
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a DNS query with the specified question using the specified name server list.
|
|
|
|
*/
|
2015-03-16 07:46:14 +01:00
|
|
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
|
2015-07-12 12:34:05 +02:00
|
|
|
InetSocketAddress nameServerAddr, DnsQuestion question) {
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2018-11-21 06:42:40 +01:00
|
|
|
return query0(nameServerAddr, question, EMPTY_ADDITIONALS, true, ch.newPromise(),
|
|
|
|
ch.eventLoop().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise());
|
2016-02-13 14:06:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a DNS query with the specified question with additional records using the specified name server list.
|
|
|
|
*/
|
|
|
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
|
2016-07-29 10:30:08 +02:00
|
|
|
InetSocketAddress nameServerAddr, DnsQuestion question, Iterable<DnsRecord> additionals) {
|
2016-02-13 14:06:06 +01:00
|
|
|
|
2018-11-21 06:42:40 +01:00
|
|
|
return query0(nameServerAddr, question, toArray(additionals, false), true, ch.newPromise(),
|
|
|
|
ch.eventLoop().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise());
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a DNS query with the specified question using the specified name server list.
|
|
|
|
*/
|
2015-03-16 07:46:14 +01:00
|
|
|
public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
|
2015-07-12 12:34:05 +02:00
|
|
|
InetSocketAddress nameServerAddr, DnsQuestion question,
|
2015-03-16 07:46:14 +01:00
|
|
|
Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2018-11-21 06:42:40 +01:00
|
|
|
return query0(nameServerAddr, question, EMPTY_ADDITIONALS, true, ch.newPromise(), promise);
|
2016-02-13 14:06:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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,
|
2016-07-29 10:30:08 +02:00
|
|
|
Iterable<DnsRecord> additionals,
|
2016-02-13 14:06:06 +01:00
|
|
|
Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
|
|
|
|
|
2018-11-21 06:42:40 +01:00
|
|
|
return query0(nameServerAddr, question, toArray(additionals, false), true, ch.newPromise(), promise);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2017-11-23 08:51:38 +01:00
|
|
|
/**
|
|
|
|
* Returns {@code true} if the {@link Throwable} was caused by an timeout or transport error.
|
|
|
|
* These methods can be used on the {@link Future#cause()} that is returned by the various methods exposed by this
|
|
|
|
* {@link DnsNameResolver}.
|
|
|
|
*/
|
|
|
|
public static boolean isTransportOrTimeoutError(Throwable cause) {
|
|
|
|
return cause != null && cause.getCause() instanceof DnsNameResolverException;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns {@code true} if the {@link Throwable} was caused by an timeout.
|
|
|
|
* These methods can be used on the {@link Future#cause()} that is returned by the various methods exposed by this
|
|
|
|
* {@link DnsNameResolver}.
|
|
|
|
*/
|
|
|
|
public static boolean isTimeoutError(Throwable cause) {
|
|
|
|
return cause != null && cause.getCause() instanceof DnsNameResolverTimeoutException;
|
|
|
|
}
|
|
|
|
|
2018-11-21 06:42:40 +01:00
|
|
|
final void flushQueries() {
|
|
|
|
ch.flush();
|
2017-04-07 03:09:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query0(
|
2015-07-12 12:34:05 +02:00
|
|
|
InetSocketAddress nameServerAddr, DnsQuestion question,
|
2016-07-29 10:30:08 +02:00
|
|
|
DnsRecord[] additionals,
|
2018-11-21 06:42:40 +01:00
|
|
|
boolean flush,
|
2017-04-07 03:09:28 +02:00
|
|
|
ChannelPromise writePromise,
|
2015-03-16 07:46:14 +01:00
|
|
|
Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
|
2017-04-07 03:09:28 +02:00
|
|
|
assert !writePromise.isVoid();
|
2014-09-19 15:36:32 +02:00
|
|
|
|
2016-02-13 14:06:06 +01:00
|
|
|
final Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> castPromise = cast(
|
|
|
|
checkNotNull(promise, "promise"));
|
2014-09-19 15:36:32 +02:00
|
|
|
try {
|
2019-05-17 14:37:11 +02:00
|
|
|
new DatagramDnsQueryContext(this, nameServerAddr, question, additionals, castPromise)
|
2018-11-21 06:42:40 +01:00
|
|
|
.query(flush, writePromise);
|
2015-03-16 07:46:14 +01:00
|
|
|
return castPromise;
|
2014-09-19 15:36:32 +02:00
|
|
|
} catch (Exception e) {
|
2015-03-16 07:46:14 +01:00
|
|
|
return castPromise.setFailure(e);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-16 07:46:14 +01:00
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
private static Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> cast(Promise<?> promise) {
|
|
|
|
return (Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>>) promise;
|
|
|
|
}
|
|
|
|
|
2018-08-22 17:49:22 +02:00
|
|
|
final DnsServerAddressStream newNameServerAddressStream(String hostname) {
|
|
|
|
return dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
|
|
|
|
}
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
private final class DnsResponseHandler extends ChannelInboundHandlerAdapter {
|
2016-06-28 11:44:04 +02:00
|
|
|
|
|
|
|
private final Promise<Channel> channelActivePromise;
|
|
|
|
|
|
|
|
DnsResponseHandler(Promise<Channel> channelActivePromise) {
|
|
|
|
this.channelActivePromise = channelActivePromise;
|
|
|
|
}
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
@Override
|
2018-08-22 17:49:22 +02:00
|
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
2014-09-19 15:36:32 +02:00
|
|
|
try {
|
2015-03-16 07:46:14 +01:00
|
|
|
final DatagramDnsResponse res = (DatagramDnsResponse) msg;
|
|
|
|
final int queryId = res.id();
|
2014-09-19 15:36:32 +02:00
|
|
|
|
|
|
|
if (logger.isDebugEnabled()) {
|
2019-05-17 14:37:11 +02:00
|
|
|
logger.debug("{} RECEIVED: UDP [{}: {}], {}", ch, queryId, res.sender(), res);
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
|
2015-11-08 04:59:01 +01:00
|
|
|
final DnsQueryContext qCtx = queryContextManager.get(res.sender(), queryId);
|
2014-09-19 15:36:32 +02:00
|
|
|
if (qCtx == null) {
|
2016-01-07 17:49:15 +01:00
|
|
|
logger.warn("{} Received a DNS response with an unknown ID: {}", ch, queryId);
|
2014-09-19 15:36:32 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-05-17 14:37:11 +02:00
|
|
|
// Check if the response was truncated and if we can fallback to TCP to retry.
|
|
|
|
if (res.isTruncated() && socketChannelFactory != null) {
|
|
|
|
// Let's retain as we may need it later on.
|
|
|
|
res.retain();
|
|
|
|
|
|
|
|
Bootstrap bs = new Bootstrap();
|
|
|
|
bs.option(ChannelOption.SO_REUSEADDR, true)
|
|
|
|
.group(executor())
|
|
|
|
.channelFactory(socketChannelFactory)
|
|
|
|
.handler(new ChannelInitializer<Channel>() {
|
|
|
|
@Override
|
|
|
|
protected void initChannel(Channel ch) {
|
|
|
|
ch.pipeline().addLast(TCP_ENCODER);
|
|
|
|
ch.pipeline().addLast(new TcpDnsResponseDecoder());
|
|
|
|
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
|
|
|
|
private boolean finish;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
|
|
|
try {
|
|
|
|
Channel channel = ctx.channel();
|
|
|
|
DnsResponse response = (DnsResponse) msg;
|
|
|
|
int queryId = response.id();
|
|
|
|
|
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
|
logger.debug("{} RECEIVED: TCP [{}: {}], {}", channel, queryId,
|
|
|
|
channel.remoteAddress(), response);
|
|
|
|
}
|
|
|
|
|
|
|
|
DnsQueryContext tcpCtx = queryContextManager.get(res.sender(), queryId);
|
|
|
|
if (tcpCtx == null) {
|
|
|
|
logger.warn("{} Received a DNS response with an unknown ID: {}",
|
|
|
|
channel, queryId);
|
|
|
|
qCtx.finish(res);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Release the original response as we will use the response that we
|
|
|
|
// received via TCP fallback.
|
|
|
|
res.release();
|
|
|
|
|
|
|
|
tcpCtx.finish(new AddressedEnvelopeAdapter(
|
|
|
|
(InetSocketAddress) ctx.channel().remoteAddress(),
|
|
|
|
(InetSocketAddress) ctx.channel().localAddress(),
|
|
|
|
response));
|
|
|
|
|
|
|
|
finish = true;
|
|
|
|
} finally {
|
|
|
|
ReferenceCountUtil.release(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
|
|
if (!finish) {
|
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
|
logger.debug("{} Error during processing response: TCP [{}: {}]",
|
|
|
|
ctx.channel(), queryId,
|
|
|
|
ctx.channel().remoteAddress(), cause);
|
|
|
|
}
|
|
|
|
// TCP fallback failed, just use the truncated response as
|
|
|
|
qCtx.finish(res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
bs.connect(res.sender()).addListener(new ChannelFutureListener() {
|
|
|
|
@Override
|
|
|
|
public void operationComplete(ChannelFuture future) {
|
|
|
|
if (future.isSuccess()) {
|
|
|
|
final Channel channel = future.channel();
|
|
|
|
|
|
|
|
Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> promise =
|
|
|
|
channel.eventLoop().newPromise();
|
|
|
|
new TcpDnsQueryContext(DnsNameResolver.this, channel,
|
|
|
|
(InetSocketAddress) channel.remoteAddress(), qCtx.question(),
|
|
|
|
EMPTY_ADDITIONALS, promise).query(true, future.channel().newPromise());
|
|
|
|
promise.addListener(
|
|
|
|
new FutureListener<AddressedEnvelope<DnsResponse, InetSocketAddress>>() {
|
|
|
|
@Override
|
|
|
|
public void operationComplete(
|
|
|
|
Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> future) {
|
|
|
|
channel.close();
|
|
|
|
|
|
|
|
if (future.isSuccess()) {
|
|
|
|
qCtx.finish(future.getNow());
|
|
|
|
} else {
|
|
|
|
// TCP fallback failed, just use the truncated response.
|
|
|
|
qCtx.finish(res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
|
logger.debug("{} Unable to fallback to TCP [{}]", queryId, future.cause());
|
|
|
|
}
|
|
|
|
|
|
|
|
// TCP fallback failed, just use the truncated response.
|
|
|
|
qCtx.finish(res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
qCtx.finish(res);
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-28 11:44:04 +02:00
|
|
|
@Override
|
|
|
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
|
|
|
super.channelActive(ctx);
|
|
|
|
channelActivePromise.setSuccess(ctx.channel());
|
|
|
|
}
|
|
|
|
|
2014-09-19 15:36:32 +02:00
|
|
|
@Override
|
2018-08-22 17:49:22 +02:00
|
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
2019-05-17 14:37:11 +02:00
|
|
|
logger.warn("{} Unexpected exception: ", ctx.channel(), cause);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private final class AddressedEnvelopeAdapter implements AddressedEnvelope<DnsResponse, InetSocketAddress> {
|
|
|
|
private final InetSocketAddress sender;
|
|
|
|
private final InetSocketAddress recipient;
|
|
|
|
private final DnsResponse response;
|
|
|
|
|
|
|
|
AddressedEnvelopeAdapter(InetSocketAddress sender, InetSocketAddress recipient, DnsResponse response) {
|
|
|
|
this.sender = sender;
|
|
|
|
this.recipient = recipient;
|
|
|
|
this.response = response;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public DnsResponse content() {
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public InetSocketAddress sender() {
|
|
|
|
return sender;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public InetSocketAddress recipient() {
|
|
|
|
return recipient;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public AddressedEnvelope<DnsResponse, InetSocketAddress> retain() {
|
|
|
|
response.retain();
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public AddressedEnvelope<DnsResponse, InetSocketAddress> retain(int increment) {
|
|
|
|
response.retain(increment);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public AddressedEnvelope<DnsResponse, InetSocketAddress> touch() {
|
|
|
|
response.touch();
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public AddressedEnvelope<DnsResponse, InetSocketAddress> touch(Object hint) {
|
|
|
|
response.touch(hint);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int refCnt() {
|
|
|
|
return response.refCnt();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean release() {
|
|
|
|
return response.release();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean release(int decrement) {
|
|
|
|
return response.release(decrement);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean equals(Object obj) {
|
|
|
|
if (this == obj) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(obj instanceof AddressedEnvelope)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
final AddressedEnvelope<?, SocketAddress> that = (AddressedEnvelope<?, SocketAddress>) obj;
|
|
|
|
if (sender() == null) {
|
|
|
|
if (that.sender() != null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else if (!sender().equals(that.sender())) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (recipient() == null) {
|
|
|
|
if (that.recipient() != null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else if (!recipient().equals(that.recipient())) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return response.equals(obj);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int hashCode() {
|
|
|
|
int hashCode = response.hashCode();
|
|
|
|
if (sender() != null) {
|
|
|
|
hashCode = hashCode * 31 + sender().hashCode();
|
|
|
|
}
|
|
|
|
if (recipient() != null) {
|
|
|
|
hashCode = hashCode * 31 + recipient().hashCode();
|
|
|
|
}
|
|
|
|
return hashCode;
|
2014-09-19 15:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|