View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.resolver.dns;
17  
18  import io.netty.bootstrap.Bootstrap;
19  import io.netty.buffer.ByteBuf;
20  import io.netty.buffer.Unpooled;
21  import io.netty.channel.AddressedEnvelope;
22  import io.netty.channel.Channel;
23  import io.netty.channel.ChannelFactory;
24  import io.netty.channel.ChannelFuture;
25  import io.netty.channel.ChannelFutureListener;
26  import io.netty.channel.ChannelHandler;
27  import io.netty.channel.ChannelHandlerAdapter;
28  import io.netty.channel.ChannelHandlerContext;
29  import io.netty.channel.ChannelInboundHandlerAdapter;
30  import io.netty.channel.ChannelInitializer;
31  import io.netty.channel.ChannelOption;
32  import io.netty.channel.EventLoop;
33  import io.netty.channel.FixedRecvByteBufAllocator;
34  import io.netty.channel.socket.DatagramChannel;
35  import io.netty.channel.socket.DatagramPacket;
36  import io.netty.channel.socket.InternetProtocolFamily;
37  import io.netty.channel.socket.SocketChannel;
38  import io.netty.handler.codec.CorruptedFrameException;
39  import io.netty.handler.codec.dns.DatagramDnsQueryEncoder;
40  import io.netty.handler.codec.dns.DatagramDnsResponse;
41  import io.netty.handler.codec.dns.DatagramDnsResponseDecoder;
42  import io.netty.handler.codec.dns.DefaultDnsRawRecord;
43  import io.netty.handler.codec.dns.DnsQuestion;
44  import io.netty.handler.codec.dns.DnsRawRecord;
45  import io.netty.handler.codec.dns.DnsRecord;
46  import io.netty.handler.codec.dns.DnsRecordType;
47  import io.netty.handler.codec.dns.DnsResponse;
48  import io.netty.resolver.DefaultHostsFileEntriesResolver;
49  import io.netty.resolver.HostsFileEntries;
50  import io.netty.resolver.HostsFileEntriesResolver;
51  import io.netty.resolver.InetNameResolver;
52  import io.netty.resolver.ResolvedAddressTypes;
53  import io.netty.util.AttributeKey;
54  import io.netty.util.NetUtil;
55  import io.netty.util.ReferenceCountUtil;
56  import io.netty.util.concurrent.EventExecutor;
57  import io.netty.util.concurrent.Future;
58  import io.netty.util.concurrent.FutureListener;
59  import io.netty.util.concurrent.GenericFutureListener;
60  import io.netty.util.concurrent.Promise;
61  import io.netty.util.concurrent.PromiseNotifier;
62  import io.netty.util.internal.EmptyArrays;
63  import io.netty.util.internal.PlatformDependent;
64  import io.netty.util.internal.StringUtil;
65  import io.netty.util.internal.logging.InternalLogger;
66  import io.netty.util.internal.logging.InternalLoggerFactory;
67  
68  import java.lang.reflect.Method;
69  import java.net.IDN;
70  import java.net.Inet4Address;
71  import java.net.Inet6Address;
72  import java.net.InetAddress;
73  import java.net.InetSocketAddress;
74  import java.net.NetworkInterface;
75  import java.net.SocketAddress;
76  import java.net.UnknownHostException;
77  import java.util.ArrayList;
78  import java.util.Arrays;
79  import java.util.Collection;
80  import java.util.Collections;
81  import java.util.Comparator;
82  import java.util.Enumeration;
83  import java.util.HashMap;
84  import java.util.Iterator;
85  import java.util.List;
86  import java.util.Locale;
87  import java.util.Map;
88  import java.util.concurrent.TimeUnit;
89  
90  import static io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.DNS_PORT;
91  import static io.netty.util.internal.ObjectUtil.checkNotNull;
92  import static io.netty.util.internal.ObjectUtil.checkPositive;
93  
94  /**
95   * A DNS-based {@link InetNameResolver}.
96   */
97  public class DnsNameResolver extends InetNameResolver {
98      /**
99       * An attribute used to mark all channels created by the {@link DnsNameResolver}.
100      */
101     public static final AttributeKey<Boolean> DNS_PIPELINE_ATTRIBUTE =
102             AttributeKey.newInstance("io.netty.resolver.dns.pipeline");
103 
104     private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class);
105     private static final String LOCALHOST = "localhost";
106     private static final String DOT_LOCALHOST = '.' + LOCALHOST;
107     private static final String WINDOWS_HOST_NAME;
108     private static final DnsRecord[] EMPTY_ADDITIONALS = new DnsRecord[0];
109     private static final DnsRecordType[] IPV4_ONLY_RESOLVED_RECORD_TYPES =
110             {DnsRecordType.A};
111     private static final InternetProtocolFamily[] IPV4_ONLY_RESOLVED_PROTOCOL_FAMILIES =
112             {InternetProtocolFamily.IPv4};
113     private static final DnsRecordType[] IPV4_PREFERRED_RESOLVED_RECORD_TYPES =
114             {DnsRecordType.A, DnsRecordType.AAAA};
115     private static final InternetProtocolFamily[] IPV4_PREFERRED_RESOLVED_PROTOCOL_FAMILIES =
116             {InternetProtocolFamily.IPv4, InternetProtocolFamily.IPv6};
117     private static final DnsRecordType[] IPV6_ONLY_RESOLVED_RECORD_TYPES =
118             {DnsRecordType.AAAA};
119     private static final InternetProtocolFamily[] IPV6_ONLY_RESOLVED_PROTOCOL_FAMILIES =
120             {InternetProtocolFamily.IPv6};
121     private static final DnsRecordType[] IPV6_PREFERRED_RESOLVED_RECORD_TYPES =
122             {DnsRecordType.AAAA, DnsRecordType.A};
123     private static final InternetProtocolFamily[] IPV6_PREFERRED_RESOLVED_PROTOCOL_FAMILIES =
124             {InternetProtocolFamily.IPv6, InternetProtocolFamily.IPv4};
125 
126     private static final ChannelHandler NOOP_HANDLER = new ChannelHandlerAdapter() {
127         @Override
128         public boolean isSharable() {
129             return true;
130         }
131     };
132 
133     static final ResolvedAddressTypes DEFAULT_RESOLVE_ADDRESS_TYPES;
134     static final String[] DEFAULT_SEARCH_DOMAINS;
135     private static final UnixResolverOptions DEFAULT_OPTIONS;
136 
137     static {
138         if (NetUtil.isIpV4StackPreferred() || !anyInterfaceSupportsIpV6()) {
139             DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_ONLY;
140         } else {
141             if (NetUtil.isIpV6AddressesPreferred()) {
142                 DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV6_PREFERRED;
143             } else {
144                 DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_PREFERRED;
145             }
146         }
147         logger.debug("Default ResolvedAddressTypes: {}", DEFAULT_RESOLVE_ADDRESS_TYPES);
148 
149         String hostName;
150         try {
151             hostName = PlatformDependent.isWindows() ? InetAddress.getLocalHost().getHostName() : null;
152         } catch (Exception ignore) {
153             hostName = null;
154         }
155         WINDOWS_HOST_NAME = hostName;
156         logger.debug("Windows hostname: {}", WINDOWS_HOST_NAME);
157 
158         String[] searchDomains;
159         try {
160             List<String> list = PlatformDependent.isWindows()
161                     ? getSearchDomainsHack()
162                     : UnixResolverDnsServerAddressStreamProvider.parseEtcResolverSearchDomains();
163             searchDomains = list.toArray(EmptyArrays.EMPTY_STRINGS);
164         } catch (Exception ignore) {
165             // Failed to get the system name search domain list.
166             searchDomains = EmptyArrays.EMPTY_STRINGS;
167         }
168         DEFAULT_SEARCH_DOMAINS = searchDomains;
169         logger.debug("Default search domains: {}", Arrays.toString(DEFAULT_SEARCH_DOMAINS));
170 
171         UnixResolverOptions options;
172         try {
173             options = UnixResolverDnsServerAddressStreamProvider.parseEtcResolverOptions();
174         } catch (Exception ignore) {
175             options = UnixResolverOptions.newBuilder().build();
176         }
177         DEFAULT_OPTIONS = options;
178         logger.debug("Default {}", DEFAULT_OPTIONS);
179     }
180 
181     /**
182      * Returns {@code true} if any {@link NetworkInterface} supports {@code IPv6}, {@code false} otherwise.
183      */
184     private static boolean anyInterfaceSupportsIpV6() {
185         for (NetworkInterface iface : NetUtil.NETWORK_INTERFACES) {
186             Enumeration<InetAddress> addresses = iface.getInetAddresses();
187             while (addresses.hasMoreElements()) {
188                 InetAddress inetAddress = addresses.nextElement();
189                 if (inetAddress instanceof Inet6Address && !inetAddress.isAnyLocalAddress() &&
190                         !inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress()) {
191                     return true;
192                 }
193             }
194         }
195         return false;
196     }
197 
198     @SuppressWarnings("unchecked")
199     private static List<String> getSearchDomainsHack() throws Exception {
200         // Only try if not using Java9 and later
201         // See https://github.com/netty/netty/issues/9500
202         if (PlatformDependent.javaVersion() < 9) {
203             // This code on Java 9+ yields a warning about illegal reflective access that will be denied in
204             // a future release. There doesn't seem to be a better way to get search domains for Windows yet.
205             Class<?> configClass = Class.forName("sun.net.dns.ResolverConfiguration");
206             Method open = configClass.getMethod("open");
207             Method nameservers = configClass.getMethod("searchlist");
208             Object instance = open.invoke(null);
209 
210             return (List<String>) nameservers.invoke(instance);
211         }
212         return Collections.emptyList();
213     }
214 
215     private static final DatagramDnsResponseDecoder DATAGRAM_DECODER = new DatagramDnsResponseDecoder() {
216         @Override
217         protected DnsResponse decodeResponse(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
218             DnsResponse response = super.decodeResponse(ctx, packet);
219             if (packet.content().isReadable()) {
220                 // If there is still something to read we did stop parsing because of a truncated message.
221                 // This can happen if we enabled EDNS0 but our MTU is not big enough to handle all the
222                 // data.
223                 response.setTruncated(true);
224 
225                 if (logger.isDebugEnabled()) {
226                     logger.debug("{} RECEIVED: UDP [{}: {}] truncated packet received, consider adjusting "
227                                     + "maxPayloadSize for the {}.", ctx.channel(), response.id(), packet.sender(),
228                             StringUtil.simpleClassName(DnsNameResolver.class));
229                 }
230             }
231             return response;
232         }
233     };
234     private static final DatagramDnsQueryEncoder DATAGRAM_ENCODER = new DatagramDnsQueryEncoder();
235 
236     // Comparator that ensures we will try first to use the nameservers that use our preferred address type.
237     private final Comparator<InetSocketAddress> nameServerComparator;
238     /**
239      * Manages the {@link DnsQueryContext}s in progress and their query IDs.
240      */
241     private final DnsQueryContextManager queryContextManager = new DnsQueryContextManager();
242 
243     /**
244      * Cache for {@link #doResolve(String, Promise)} and {@link #doResolveAll(String, Promise)}.
245      */
246     private final DnsCache resolveCache;
247     private final AuthoritativeDnsServerCache authoritativeDnsServerCache;
248     private final DnsCnameCache cnameCache;
249     private final DnsServerAddressStream queryDnsServerAddressStream;
250 
251     private final long queryTimeoutMillis;
252     private final int maxQueriesPerResolve;
253     private final ResolvedAddressTypes resolvedAddressTypes;
254     private final InternetProtocolFamily[] resolvedInternetProtocolFamilies;
255     private final boolean recursionDesired;
256     private final int maxPayloadSize;
257     private final boolean optResourceEnabled;
258     private final HostsFileEntriesResolver hostsFileEntriesResolver;
259     private final DnsServerAddressStreamProvider dnsServerAddressStreamProvider;
260     private final String[] searchDomains;
261     private final int ndots;
262     private final boolean supportsAAAARecords;
263     private final boolean supportsARecords;
264     private final InternetProtocolFamily preferredAddressType;
265     private final DnsRecordType[] resolveRecordTypes;
266     private final boolean decodeIdn;
267     private final DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory;
268     private final boolean completeOncePreferredResolved;
269     private final DnsResolveChannelProvider resolveChannelProvider;
270     private final Bootstrap socketBootstrap;
271     private final boolean retryWithTcpOnTimeout;
272 
273     private final int maxNumConsolidation;
274     private final Map<String, Future<List<InetAddress>>> inflightLookups;
275 
276     /**
277      * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
278      *
279      * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
280      * @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel}
281      * @param resolveCache the DNS resolved entries cache
282      * @param authoritativeDnsServerCache the cache used to find the authoritative DNS server for a domain
283      * @param dnsQueryLifecycleObserverFactory used to generate new instances of {@link DnsQueryLifecycleObserver} which
284      *                                         can be used to track metrics for DNS servers.
285      * @param queryTimeoutMillis timeout of each DNS query in millis. {@code 0} disables the timeout. If not set or a
286      *                           negative number is set, the default timeout is used.
287      * @param resolvedAddressTypes the preferred address types
288      * @param recursionDesired if recursion desired flag must be set
289      * @param maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution
290      * @param traceEnabled if trace is enabled
291      * @param maxPayloadSize the capacity of the datagram packet buffer
292      * @param optResourceEnabled if automatic inclusion of a optional records is enabled
293      * @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to check for local aliases
294      * @param dnsServerAddressStreamProvider The {@link DnsServerAddressStreamProvider} used to determine the name
295      *                                       servers for each hostname lookup.
296      * @param searchDomains the list of search domain
297      *                      (can be null, if so, will try to default to the underlying platform ones)
298      * @param ndots the ndots value
299      * @param decodeIdn {@code true} if domain / host names should be decoded to unicode when received.
300      *                        See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
301      * @deprecated Use {@link DnsNameResolverBuilder}.
302      */
303     @Deprecated
304     public DnsNameResolver(
305             EventLoop eventLoop,
306             ChannelFactory<? extends DatagramChannel> channelFactory,
307             final DnsCache resolveCache,
308             final DnsCache authoritativeDnsServerCache,
309             DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory,
310             long queryTimeoutMillis,
311             ResolvedAddressTypes resolvedAddressTypes,
312             boolean recursionDesired,
313             int maxQueriesPerResolve,
314             boolean traceEnabled,
315             int maxPayloadSize,
316             boolean optResourceEnabled,
317             HostsFileEntriesResolver hostsFileEntriesResolver,
318             DnsServerAddressStreamProvider dnsServerAddressStreamProvider,
319             String[] searchDomains,
320             int ndots,
321             boolean decodeIdn) {
322         this(eventLoop, channelFactory, resolveCache,
323              new AuthoritativeDnsServerCacheAdapter(authoritativeDnsServerCache), dnsQueryLifecycleObserverFactory,
324              queryTimeoutMillis, resolvedAddressTypes, recursionDesired, maxQueriesPerResolve, traceEnabled,
325              maxPayloadSize, optResourceEnabled, hostsFileEntriesResolver, dnsServerAddressStreamProvider,
326              searchDomains, ndots, decodeIdn);
327     }
328 
329     /**
330      * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
331      *
332      * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
333      * @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel}
334      * @param resolveCache the DNS resolved entries cache
335      * @param authoritativeDnsServerCache the cache used to find the authoritative DNS server for a domain
336      * @param dnsQueryLifecycleObserverFactory used to generate new instances of {@link DnsQueryLifecycleObserver} which
337      *                                         can be used to track metrics for DNS servers.
338      * @param queryTimeoutMillis timeout of each DNS query in millis. {@code 0} disables the timeout. If not set or a
339      *                           negative number is set, the default timeout is used.
340      * @param resolvedAddressTypes the preferred address types
341      * @param recursionDesired if recursion desired flag must be set
342      * @param maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution
343      * @param traceEnabled if trace is enabled
344      * @param maxPayloadSize the capacity of the datagram packet buffer
345      * @param optResourceEnabled if automatic inclusion of a optional records is enabled
346      * @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to check for local aliases
347      * @param dnsServerAddressStreamProvider The {@link DnsServerAddressStreamProvider} used to determine the name
348      *                                       servers for each hostname lookup.
349      * @param searchDomains the list of search domain
350      *                      (can be null, if so, will try to default to the underlying platform ones)
351      * @param ndots the ndots value
352      * @param decodeIdn {@code true} if domain / host names should be decoded to unicode when received.
353      *                        See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
354      * @deprecated Use {@link DnsNameResolverBuilder}.
355      */
356     @Deprecated
357     public DnsNameResolver(
358             EventLoop eventLoop,
359             ChannelFactory<? extends DatagramChannel> channelFactory,
360             final DnsCache resolveCache,
361             final AuthoritativeDnsServerCache authoritativeDnsServerCache,
362             DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory,
363             long queryTimeoutMillis,
364             ResolvedAddressTypes resolvedAddressTypes,
365             boolean recursionDesired,
366             int maxQueriesPerResolve,
367             boolean traceEnabled,
368             int maxPayloadSize,
369             boolean optResourceEnabled,
370             HostsFileEntriesResolver hostsFileEntriesResolver,
371             DnsServerAddressStreamProvider dnsServerAddressStreamProvider,
372             String[] searchDomains,
373             int ndots,
374             boolean decodeIdn) {
375         this(eventLoop, channelFactory, null, false, resolveCache,
376                 NoopDnsCnameCache.INSTANCE, authoritativeDnsServerCache, null,
377              dnsQueryLifecycleObserverFactory, queryTimeoutMillis, resolvedAddressTypes, recursionDesired,
378              maxQueriesPerResolve, traceEnabled, maxPayloadSize, optResourceEnabled, hostsFileEntriesResolver,
379              dnsServerAddressStreamProvider, new ThreadLocalNameServerAddressStream(dnsServerAddressStreamProvider),
380              searchDomains, ndots, decodeIdn, false, 0, DnsNameResolverChannelStrategy.ChannelPerResolver);
381     }
382 
383     @SuppressWarnings("deprecation")
384     DnsNameResolver(
385             EventLoop eventLoop,
386             ChannelFactory<? extends DatagramChannel> channelFactory,
387             ChannelFactory<? extends SocketChannel> socketChannelFactory,
388             boolean retryWithTcpOnTimeout,
389             final DnsCache resolveCache,
390             final DnsCnameCache cnameCache,
391             final AuthoritativeDnsServerCache authoritativeDnsServerCache,
392             SocketAddress localAddress,
393             DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory,
394             long queryTimeoutMillis,
395             ResolvedAddressTypes resolvedAddressTypes,
396             boolean recursionDesired,
397             int maxQueriesPerResolve,
398             boolean traceEnabled,
399             final int maxPayloadSize,
400             boolean optResourceEnabled,
401             HostsFileEntriesResolver hostsFileEntriesResolver,
402             DnsServerAddressStreamProvider dnsServerAddressStreamProvider,
403             DnsServerAddressStream queryDnsServerAddressStream,
404             String[] searchDomains,
405             int ndots,
406             boolean decodeIdn,
407             boolean completeOncePreferredResolved,
408             int maxNumConsolidation, DnsNameResolverChannelStrategy datagramChannelStrategy) {
409         super(eventLoop);
410         this.queryTimeoutMillis = queryTimeoutMillis >= 0
411             ? queryTimeoutMillis
412             : TimeUnit.SECONDS.toMillis(DEFAULT_OPTIONS.timeout());
413         this.resolvedAddressTypes = resolvedAddressTypes != null ? resolvedAddressTypes : DEFAULT_RESOLVE_ADDRESS_TYPES;
414         this.recursionDesired = recursionDesired;
415         this.maxQueriesPerResolve = maxQueriesPerResolve > 0 ? maxQueriesPerResolve : DEFAULT_OPTIONS.attempts();
416         this.maxPayloadSize = checkPositive(maxPayloadSize, "maxPayloadSize");
417         this.optResourceEnabled = optResourceEnabled;
418         this.hostsFileEntriesResolver = checkNotNull(hostsFileEntriesResolver, "hostsFileEntriesResolver");
419         this.dnsServerAddressStreamProvider =
420                 checkNotNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
421         this.queryDnsServerAddressStream = checkNotNull(queryDnsServerAddressStream, "queryDnsServerAddressStream");
422         this.resolveCache = checkNotNull(resolveCache, "resolveCache");
423         this.cnameCache = checkNotNull(cnameCache, "cnameCache");
424         this.dnsQueryLifecycleObserverFactory = traceEnabled ?
425                 dnsQueryLifecycleObserverFactory instanceof NoopDnsQueryLifecycleObserverFactory ?
426                         new LoggingDnsQueryLifeCycleObserverFactory() :
427                         new BiDnsQueryLifecycleObserverFactory(new LoggingDnsQueryLifeCycleObserverFactory(),
428                                                                dnsQueryLifecycleObserverFactory) :
429                 checkNotNull(dnsQueryLifecycleObserverFactory, "dnsQueryLifecycleObserverFactory");
430         this.searchDomains = searchDomains != null ? searchDomains.clone() : DEFAULT_SEARCH_DOMAINS;
431         this.ndots = ndots >= 0 ? ndots : DEFAULT_OPTIONS.ndots();
432         this.decodeIdn = decodeIdn;
433         this.completeOncePreferredResolved = completeOncePreferredResolved;
434         this.retryWithTcpOnTimeout = retryWithTcpOnTimeout;
435         if (socketChannelFactory == null) {
436             socketBootstrap = null;
437         } else {
438             socketBootstrap = new Bootstrap();
439             socketBootstrap.option(ChannelOption.SO_REUSEADDR, true)
440                     .group(executor())
441                     .channelFactory(socketChannelFactory)
442                     .attr(DNS_PIPELINE_ATTRIBUTE, Boolean.TRUE)
443                     .handler(NOOP_HANDLER);
444             if (queryTimeoutMillis > 0 && queryTimeoutMillis <= Integer.MAX_VALUE) {
445                 // Set the connect timeout to the same as queryTimeout as otherwise it might take a long
446                 // time for the query to fail in case of a connection timeout.
447                 socketBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) queryTimeoutMillis);
448             }
449         }
450         switch (this.resolvedAddressTypes) {
451             case IPV4_ONLY:
452                 supportsAAAARecords = false;
453                 supportsARecords = true;
454                 resolveRecordTypes = IPV4_ONLY_RESOLVED_RECORD_TYPES;
455                 resolvedInternetProtocolFamilies = IPV4_ONLY_RESOLVED_PROTOCOL_FAMILIES;
456                 break;
457             case IPV4_PREFERRED:
458                 supportsAAAARecords = true;
459                 supportsARecords = true;
460                 resolveRecordTypes = IPV4_PREFERRED_RESOLVED_RECORD_TYPES;
461                 resolvedInternetProtocolFamilies = IPV4_PREFERRED_RESOLVED_PROTOCOL_FAMILIES;
462                 break;
463             case IPV6_ONLY:
464                 supportsAAAARecords = true;
465                 supportsARecords = false;
466                 resolveRecordTypes = IPV6_ONLY_RESOLVED_RECORD_TYPES;
467                 resolvedInternetProtocolFamilies = IPV6_ONLY_RESOLVED_PROTOCOL_FAMILIES;
468                 break;
469             case IPV6_PREFERRED:
470                 supportsAAAARecords = true;
471                 supportsARecords = true;
472                 resolveRecordTypes = IPV6_PREFERRED_RESOLVED_RECORD_TYPES;
473                 resolvedInternetProtocolFamilies = IPV6_PREFERRED_RESOLVED_PROTOCOL_FAMILIES;
474                 break;
475             default:
476                 throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
477         }
478         preferredAddressType = preferredAddressType(this.resolvedAddressTypes);
479         this.authoritativeDnsServerCache = checkNotNull(authoritativeDnsServerCache, "authoritativeDnsServerCache");
480         nameServerComparator = new NameServerComparator(preferredAddressType.addressType());
481         this.maxNumConsolidation = maxNumConsolidation;
482         if (maxNumConsolidation > 0) {
483             inflightLookups = new HashMap<String, Future<List<InetAddress>>>();
484         } else {
485             inflightLookups = null;
486         }
487 
488         final DnsResponseHandler responseHandler = new DnsResponseHandler(queryContextManager);
489         Bootstrap bootstrap = new Bootstrap()
490                 .channelFactory(channelFactory)
491                 .group(eventLoop)
492                 .attr(DNS_PIPELINE_ATTRIBUTE, Boolean.TRUE)
493                 .handler(new ChannelInitializer<DatagramChannel>() {
494                     @Override
495                     protected void initChannel(DatagramChannel ch) {
496                         ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize));
497                         ch.pipeline().addLast(DATAGRAM_ENCODER, DATAGRAM_DECODER, responseHandler);
498                     }
499                 });
500         if (localAddress == null) {
501             bootstrap.option(ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION, true);
502         }
503         this.resolveChannelProvider = newProvider(datagramChannelStrategy, bootstrap, localAddress);
504     }
505 
506     private static DnsResolveChannelProvider newProvider(DnsNameResolverChannelStrategy channelStrategy,
507                                                          Bootstrap bootstrap, SocketAddress localAddress) {
508         switch (channelStrategy) {
509             case ChannelPerResolver:
510                 return new DnsResolveChannelPerResolverProvider(bootstrap, localAddress);
511             case ChannelPerResolution:
512                 return new DnsResolveChannelPerResolutionProvider(bootstrap, localAddress);
513             default:
514                 throw new IllegalArgumentException("Unknown DnsNameResolverChannelStrategy: " + channelStrategy);
515         }
516     }
517 
518     static InternetProtocolFamily preferredAddressType(ResolvedAddressTypes resolvedAddressTypes) {
519         switch (resolvedAddressTypes) {
520         case IPV4_ONLY:
521         case IPV4_PREFERRED:
522             return InternetProtocolFamily.IPv4;
523         case IPV6_ONLY:
524         case IPV6_PREFERRED:
525             return InternetProtocolFamily.IPv6;
526         default:
527             throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
528         }
529     }
530 
531     // Only here to override in unit tests.
532     InetSocketAddress newRedirectServerAddress(InetAddress server) {
533         return new InetSocketAddress(server, DNS_PORT);
534     }
535 
536     final DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory() {
537         return dnsQueryLifecycleObserverFactory;
538     }
539 
540     /**
541      * Creates a new {@link DnsServerAddressStream} to following a redirected DNS query. By overriding this
542      * it provides the opportunity to sort the name servers before following a redirected DNS query.
543      *
544      * @param hostname the hostname.
545      * @param nameservers The addresses of the DNS servers which are used in the event of a redirect. This may
546      *                    contain resolved and unresolved addresses so the used {@link DnsServerAddressStream} must
547      *                    allow unresolved addresses if you want to include these as well.
548      * @return A {@link DnsServerAddressStream} which will be used to follow the DNS redirect or {@code null} if
549      *         none should be followed.
550      */
551     protected DnsServerAddressStream newRedirectDnsServerStream(
552             @SuppressWarnings("unused") String hostname, List<InetSocketAddress> nameservers) {
553         DnsServerAddressStream cached = authoritativeDnsServerCache().get(hostname);
554         if (cached == null || cached.size() == 0) {
555             // If there is no cache hit (which may be the case for example when a NoopAuthoritativeDnsServerCache
556             // is used), we will just directly use the provided nameservers.
557             Collections.sort(nameservers, nameServerComparator);
558             return new SequentialDnsServerAddressStream(nameservers, 0);
559         }
560         return cached;
561     }
562 
563     /**
564      * Returns the resolution cache.
565      */
566     public DnsCache resolveCache() {
567         return resolveCache;
568     }
569 
570     /**
571      * Returns the {@link DnsCnameCache}.
572      */
573     public DnsCnameCache cnameCache() {
574         return cnameCache;
575     }
576 
577     /**
578      * Returns the cache used for authoritative DNS servers for a domain.
579      */
580     public AuthoritativeDnsServerCache authoritativeDnsServerCache() {
581         return authoritativeDnsServerCache;
582     }
583 
584     /**
585      * Returns the timeout of each DNS query performed by this resolver (in milliseconds).
586      * The default value is 5 seconds.
587      */
588     public long queryTimeoutMillis() {
589         return queryTimeoutMillis;
590     }
591 
592     /**
593      * Returns the dns server address stream used for DNS queries (not resolve).
594      */
595     public DnsServerAddressStream queryDnsServerAddressStream() {
596         return queryDnsServerAddressStream;
597     }
598 
599     /**
600      * Returns the {@link ResolvedAddressTypes} resolved by {@link #resolve(String)}.
601      * The default value depends on the value of the system property {@code "java.net.preferIPv6Addresses"}.
602      */
603     public ResolvedAddressTypes resolvedAddressTypes() {
604         return resolvedAddressTypes;
605     }
606 
607     InternetProtocolFamily[] resolvedInternetProtocolFamiliesUnsafe() {
608         return resolvedInternetProtocolFamilies;
609     }
610 
611     final String[] searchDomains() {
612         return searchDomains;
613     }
614 
615     final int ndots() {
616         return ndots;
617     }
618 
619     final boolean supportsAAAARecords() {
620         return supportsAAAARecords;
621     }
622 
623     final boolean supportsARecords() {
624         return supportsARecords;
625     }
626 
627     final InternetProtocolFamily preferredAddressType() {
628         return preferredAddressType;
629     }
630 
631     final DnsRecordType[] resolveRecordTypes() {
632         return resolveRecordTypes;
633     }
634 
635     final boolean isDecodeIdn() {
636         return decodeIdn;
637     }
638 
639     /**
640      * Returns {@code true} if and only if this resolver sends a DNS query with the RD (recursion desired) flag set.
641      * The default value is {@code true}.
642      */
643     public boolean isRecursionDesired() {
644         return recursionDesired;
645     }
646 
647     /**
648      * Returns the maximum allowed number of DNS queries to send when resolving a host name.
649      * The default value is {@code 8}.
650      */
651     public int maxQueriesPerResolve() {
652         return maxQueriesPerResolve;
653     }
654 
655     /**
656      * Returns the capacity of the datagram packet buffer (in bytes).  The default value is {@code 4096} bytes.
657      */
658     public int maxPayloadSize() {
659         return maxPayloadSize;
660     }
661 
662     /**
663      * Returns the automatic inclusion of a optional records that tries to give the remote DNS server a hint about how
664      * much data the resolver can read per response is enabled.
665      */
666     public boolean isOptResourceEnabled() {
667         return optResourceEnabled;
668     }
669 
670     /**
671      * Returns the component that tries to resolve hostnames against the hosts file prior to asking to
672      * remotes DNS servers.
673      */
674     public HostsFileEntriesResolver hostsFileEntriesResolver() {
675         return hostsFileEntriesResolver;
676     }
677 
678     /**
679      * Closes the internal datagram channel used for sending and receiving DNS messages, and clears all DNS resource
680      * records from the cache. Attempting to send a DNS query or to resolve a domain name will fail once this method
681      * has been called.
682      */
683     @Override
684     public void close() {
685         resolveChannelProvider.close();
686         resolveCache.clear();
687         cnameCache.clear();
688         authoritativeDnsServerCache.clear();
689     }
690 
691     @Override
692     protected EventLoop executor() {
693         return (EventLoop) super.executor();
694     }
695 
696     private InetAddress resolveHostsFileEntry(String hostname) {
697         if (hostsFileEntriesResolver == null) {
698             return null;
699         }
700         InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
701         return address == null && isLocalHostAddress(hostname)? getLocalHostAddress() : address;
702     }
703 
704     private List<InetAddress> resolveHostsFileEntries(String hostname) {
705         if (hostsFileEntriesResolver == null) {
706             return null;
707         }
708         List<InetAddress> addresses;
709         if (hostsFileEntriesResolver instanceof DefaultHostsFileEntriesResolver) {
710             addresses = ((DefaultHostsFileEntriesResolver) hostsFileEntriesResolver)
711                     .addresses(hostname, resolvedAddressTypes);
712         } else {
713             InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
714             addresses = address != null? Collections.singletonList(address) : null;
715         }
716         return addresses == null && isLocalHostAddress(hostname)?
717                 Collections.singletonList(getLocalHostAddress()) : addresses;
718     }
719 
720     /**
721      * Checks whether the given hostname refers to the current computer. This is the case for:
722      * <ul>
723      *     <li>localhost.</li>
724      *     <li>any domain within .localhost.</li>
725      *     <li>the hostname of the local computer on Windows</li>
726      * </ul>
727      * <p>
728      * According to RFC 6761 Section 6.3, localhost and subdomains of localhost should be resolved to the loopback
729      * address by name resolution libraries without querying DNS servers. The hostname of the local machine can usually
730      * be resolved from the hosts file, but on Windows, this is no longer possible.
731      *
732      * @param hostname the hostname that's being looked up
733      * @return true if the hostname should point to the loopback adress. False otherwise.
734      * @see <a href="https://github.com/netty/netty/issues/5386">Issue 5386</a>
735      * @see <a href="https://github.com/netty/netty/issues/11142">Issue 11142</a>
736      * @see <a href="https://github.com/netty/netty/issues/16744">Issue 16744</a>
737      * @see <a href="https://www.rfc-editor.org/rfc/rfc6761.html#section-6.3">RFC 6761</a>
738      */
739     private static boolean isLocalHostAddress(String hostname) {
740         if (PlatformDependent.isWindows() && WINDOWS_HOST_NAME != null &&
741             WINDOWS_HOST_NAME.equalsIgnoreCase(hostname)) {
742             return true;
743         }
744 
745         if (hostname.endsWith(".")) {
746             hostname = hostname.substring(0, hostname.length() - 1);
747         }
748         return hostname.equalsIgnoreCase(LOCALHOST) || hostname.toLowerCase(Locale.US).endsWith(DOT_LOCALHOST);
749     }
750 
751     private InetAddress getLocalHostAddress() {
752         switch (resolvedAddressTypes) {
753         case IPV4_ONLY:
754         case IPV4_PREFERRED:
755             return NetUtil.LOCALHOST4;
756         case IPV6_ONLY:
757         case IPV6_PREFERRED:
758             return NetUtil.LOCALHOST6;
759         default:
760             throw new IllegalStateException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
761         }
762     }
763 
764     /**
765      * Resolves the specified name into an address.
766      *
767      * @param inetHost the name to resolve
768      * @param additionals additional records ({@code OPT})
769      *
770      * @return the address as the result of the resolution
771      */
772     public final Future<InetAddress> resolve(String inetHost, Iterable<DnsRecord> additionals) {
773         return resolve(inetHost, additionals, executor().<InetAddress>newPromise());
774     }
775 
776     /**
777      * Resolves the specified name into an address.
778      *
779      * @param inetHost the name to resolve
780      * @param additionals additional records ({@code OPT})
781      * @param promise the {@link Promise} which will be fulfilled when the name resolution is finished
782      *
783      * @return the address as the result of the resolution
784      */
785     public final Future<InetAddress> resolve(String inetHost, Iterable<DnsRecord> additionals,
786                                              Promise<InetAddress> promise) {
787         checkNotNull(promise, "promise");
788         DnsRecord[] additionalsArray = toArray(additionals, true);
789         try {
790             doResolve(inetHost, additionalsArray, promise, resolveCache);
791             return promise;
792         } catch (Exception e) {
793             return promise.setFailure(e);
794         }
795     }
796 
797     /**
798      * Resolves the specified host name and port into a list of address.
799      *
800      * @param inetHost the name to resolve
801      * @param additionals additional records ({@code OPT})
802      *
803      * @return the list of the address as the result of the resolution
804      */
805     public final Future<List<InetAddress>> resolveAll(String inetHost, Iterable<DnsRecord> additionals) {
806         return resolveAll(inetHost, additionals, executor().<List<InetAddress>>newPromise());
807     }
808 
809     /**
810      * Resolves the specified host name and port into a list of address.
811      *
812      * @param inetHost the name to resolve
813      * @param additionals additional records ({@code OPT})
814      * @param promise the {@link Promise} which will be fulfilled when the name resolution is finished
815      *
816      * @return the list of the address as the result of the resolution
817      */
818     public final Future<List<InetAddress>> resolveAll(String inetHost, Iterable<DnsRecord> additionals,
819                                                       Promise<List<InetAddress>> promise) {
820         checkNotNull(promise, "promise");
821         DnsRecord[] additionalsArray = toArray(additionals, true);
822         try {
823             doResolveAll(inetHost, additionalsArray, promise, resolveCache);
824             return promise;
825         } catch (Exception e) {
826             return promise.setFailure(e);
827         }
828     }
829 
830     @Override
831     protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
832         doResolve(inetHost, EMPTY_ADDITIONALS, promise, resolveCache);
833     }
834 
835     /**
836      * Resolves the {@link DnsRecord}s that are matched by the specified {@link DnsQuestion}. Unlike
837      * {@link #query(DnsQuestion)}, this method handles redirection, CNAMEs and multiple name servers.
838      * If the specified {@link DnsQuestion} is {@code A} or {@code AAAA}, this method looks up the configured
839      * {@link HostsFileEntries} before sending a query to the name servers. If a match is found in the
840      * {@link HostsFileEntries}, a synthetic {@code A} or {@code AAAA} record will be returned.
841      *
842      * @param question the question
843      *
844      * @return the list of the {@link DnsRecord}s as the result of the resolution
845      */
846     public final Future<List<DnsRecord>> resolveAll(DnsQuestion question) {
847         return resolveAll(question, EMPTY_ADDITIONALS, executor().<List<DnsRecord>>newPromise());
848     }
849 
850     /**
851      * Resolves the {@link DnsRecord}s that are matched by the specified {@link DnsQuestion}. Unlike
852      * {@link #query(DnsQuestion)}, this method handles redirection, CNAMEs and multiple name servers.
853      * If the specified {@link DnsQuestion} is {@code A} or {@code AAAA}, this method looks up the configured
854      * {@link HostsFileEntries} before sending a query to the name servers. If a match is found in the
855      * {@link HostsFileEntries}, a synthetic {@code A} or {@code AAAA} record will be returned.
856      *
857      * @param question the question
858      * @param additionals additional records ({@code OPT})
859      *
860      * @return the list of the {@link DnsRecord}s as the result of the resolution
861      */
862     public final Future<List<DnsRecord>> resolveAll(DnsQuestion question, Iterable<DnsRecord> additionals) {
863         return resolveAll(question, additionals, executor().<List<DnsRecord>>newPromise());
864     }
865 
866     /**
867      * Resolves the {@link DnsRecord}s that are matched by the specified {@link DnsQuestion}. Unlike
868      * {@link #query(DnsQuestion)}, this method handles redirection, CNAMEs and multiple name servers.
869      * If the specified {@link DnsQuestion} is {@code A} or {@code AAAA}, this method looks up the configured
870      * {@link HostsFileEntries} before sending a query to the name servers. If a match is found in the
871      * {@link HostsFileEntries}, a synthetic {@code A} or {@code AAAA} record will be returned.
872      *
873      * @param question the question
874      * @param additionals additional records ({@code OPT})
875      * @param promise the {@link Promise} which will be fulfilled when the resolution is finished
876      *
877      * @return the list of the {@link DnsRecord}s as the result of the resolution
878      */
879     public final Future<List<DnsRecord>> resolveAll(DnsQuestion question, Iterable<DnsRecord> additionals,
880                                                     Promise<List<DnsRecord>> promise) {
881         final DnsRecord[] additionalsArray = toArray(additionals, true);
882         return resolveAll(question, additionalsArray, promise);
883     }
884 
885     private Future<List<DnsRecord>> resolveAll(final DnsQuestion question, final DnsRecord[] additionals,
886                                                final Promise<List<DnsRecord>> promise) {
887         checkNotNull(question, "question");
888         checkNotNull(promise, "promise");
889 
890         // Respect /etc/hosts as well if the record type is A or AAAA.
891         final DnsRecordType type = question.type();
892         final String hostname = question.name();
893 
894         if (type == DnsRecordType.A || type == DnsRecordType.AAAA) {
895             final List<InetAddress> hostsFileEntries = resolveHostsFileEntries(hostname);
896             if (hostsFileEntries != null) {
897                 List<DnsRecord> result = new ArrayList<DnsRecord>();
898                 for (InetAddress hostsFileEntry : hostsFileEntries) {
899                     ByteBuf content = null;
900                     if (hostsFileEntry instanceof Inet4Address) {
901                         if (type == DnsRecordType.A) {
902                             content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
903                         }
904                     } else if (hostsFileEntry instanceof Inet6Address) {
905                         if (type == DnsRecordType.AAAA) {
906                             content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
907                         }
908                     }
909                     if (content != null) {
910                         // Our current implementation does not support reloading the hosts file,
911                         // so use a fairly large TTL (1 day, i.e. 86400 seconds).
912                         result.add(new DefaultDnsRawRecord(hostname, type, 86400, content));
913                     }
914                 }
915 
916                 if (!result.isEmpty()) {
917                     if (!trySuccess(promise, result)) {
918                         // We were not able to transfer ownership, release the records to prevent leaks.
919                         for (DnsRecord r: result) {
920                             ReferenceCountUtil.safeRelease(r);
921                         }
922                     }
923                     return promise;
924                 }
925             }
926         }
927 
928         ChannelFuture f = resolveChannelProvider.nextResolveChannel(promise);
929         if (f.isDone()) {
930             resolveAllNow(f, hostname, question, additionals, promise);
931         } else {
932             f.addListener(new ChannelFutureListener() {
933                 @Override
934                 public void operationComplete(ChannelFuture f) {
935                     resolveAllNow(f, hostname, question, additionals, promise);
936                 }
937             });
938         }
939         return promise;
940     }
941 
942     private void resolveAllNow(ChannelFuture f, String hostname, final DnsQuestion question,
943                                final DnsRecord[] additionals, final Promise<List<DnsRecord>> promise) {
944         if (f.isSuccess()) {
945             // It was not A/AAAA question or there was no entry in /etc/hosts.
946             final DnsServerAddressStream nameServerAddrs =
947                     dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
948 
949             new DnsRecordResolveContext(DnsNameResolver.this, f.channel(), promise, question, additionals,
950                     nameServerAddrs, maxQueriesPerResolve).resolve(promise);
951         } else {
952             UnknownHostException e = toException(f, hostname, question, additionals);
953             promise.setFailure(e);
954         }
955     }
956 
957     private static UnknownHostException toException(
958             ChannelFuture f, String hostname, DnsQuestion question, DnsRecord[] additionals) {
959         UnknownHostException e = new UnknownHostException(
960                 "Failed to resolve '" + hostname + "', couldn't setup transport: " + f.channel());
961         e.initCause(f.cause());
962 
963         if (question != null) {
964             ReferenceCountUtil.release(question);
965         }
966         for (DnsRecord record : additionals) {
967             ReferenceCountUtil.release(record);
968         }
969         return e;
970     }
971 
972     private static DnsRecord[] toArray(Iterable<DnsRecord> additionals, boolean validateType) {
973         checkNotNull(additionals, "additionals");
974         if (additionals instanceof Collection) {
975             Collection<DnsRecord> records = (Collection<DnsRecord>) additionals;
976             for (DnsRecord r: additionals) {
977                 validateAdditional(r, validateType);
978             }
979             return records.toArray(new DnsRecord[records.size()]);
980         }
981 
982         Iterator<DnsRecord> additionalsIt = additionals.iterator();
983         if (!additionalsIt.hasNext()) {
984             return EMPTY_ADDITIONALS;
985         }
986         List<DnsRecord> records = new ArrayList<DnsRecord>();
987         do {
988             DnsRecord r = additionalsIt.next();
989             validateAdditional(r, validateType);
990             records.add(r);
991         } while (additionalsIt.hasNext());
992 
993         return records.toArray(new DnsRecord[records.size()]);
994     }
995 
996     private static void validateAdditional(DnsRecord record, boolean validateType) {
997         checkNotNull(record, "record");
998         if (validateType && record instanceof DnsRawRecord) {
999             throw new IllegalArgumentException("DnsRawRecord implementations not allowed: " + record);
1000         }
1001     }
1002 
1003     private InetAddress loopbackAddress() {
1004         return preferredAddressType().localhost();
1005     }
1006 
1007     /**
1008      * Hook designed for extensibility so one can pass a different cache on each resolution attempt
1009      * instead of using the global one.
1010      */
1011     protected void doResolve(String inetHost,
1012                              final DnsRecord[] additionals,
1013                              final Promise<InetAddress> promise,
1014                              final DnsCache resolveCache) throws Exception {
1015         if (inetHost == null || inetHost.isEmpty()) {
1016             // If an empty hostname is used we should use "localhost", just like InetAddress.getByName(...) does.
1017             promise.setSuccess(loopbackAddress());
1018             return;
1019         }
1020         final InetAddress address = NetUtil.createInetAddressFromIpAddressString(inetHost);
1021         if (address != null) {
1022             // The inetHost is actually an ipaddress.
1023             promise.setSuccess(address);
1024             return;
1025         }
1026 
1027         final String hostname = hostname(inetHost);
1028 
1029         InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
1030         if (hostsFileEntry != null) {
1031             promise.setSuccess(hostsFileEntry);
1032             return;
1033         }
1034 
1035         if (!doResolveCached(hostname, additionals, promise, resolveCache)) {
1036             ChannelFuture f = resolveChannelProvider.nextResolveChannel(promise);
1037             if (f.isDone()) {
1038                 doResolveNow(f, hostname, additionals, promise, resolveCache);
1039             } else {
1040                 f.addListener(new ChannelFutureListener() {
1041                     @Override
1042                     public void operationComplete(ChannelFuture f) {
1043                         doResolveNow(f, hostname, additionals, promise, resolveCache);
1044                     }
1045                 });
1046             }
1047         }
1048     }
1049 
1050     private void doResolveNow(ChannelFuture f, final String hostname, final DnsRecord[] additionals,
1051                               final Promise<InetAddress> promise,
1052                               final DnsCache resolveCache) {
1053         if (f.isSuccess()) {
1054             doResolveUncached(f.channel(), hostname, additionals, promise,
1055                     resolveCache, completeOncePreferredResolved);
1056         } else {
1057             UnknownHostException e = toException(f, hostname, null, additionals);
1058             promise.setFailure(e);
1059         }
1060     }
1061 
1062     private boolean doResolveCached(String hostname,
1063                                     DnsRecord[] additionals,
1064                                     Promise<InetAddress> promise,
1065                                     DnsCache resolveCache) {
1066         final List<? extends DnsCacheEntry> cachedEntries = resolveCache.get(hostname, additionals);
1067         if (cachedEntries == null || cachedEntries.isEmpty()) {
1068             return false;
1069         }
1070 
1071         Throwable cause = cachedEntries.get(0).cause();
1072         if (cause == null) {
1073             final int numEntries = cachedEntries.size();
1074             // Find the first entry with the preferred address type.
1075             for (InternetProtocolFamily f : resolvedInternetProtocolFamilies) {
1076                 for (int i = 0; i < numEntries; i++) {
1077                     final DnsCacheEntry e = cachedEntries.get(i);
1078                     if (f.addressType().isInstance(e.address())) {
1079                         trySuccess(promise, e.address());
1080                         return true;
1081                     }
1082                 }
1083             }
1084             return false;
1085         } else {
1086             tryFailure(promise, cause);
1087             return true;
1088         }
1089     }
1090 
1091     static <T> boolean trySuccess(Promise<T> promise, T result) {
1092         final boolean notifiedRecords = promise.trySuccess(result);
1093         if (!notifiedRecords) {
1094             // There is nothing really wrong with not be able to notify the promise as we may have raced here because
1095             // of multiple queries that have been executed. Log it with trace level anyway just in case the user
1096             // wants to better understand what happened.
1097             logger.trace("Failed to notify success ({}) to a promise: {}", result, promise);
1098         }
1099         return notifiedRecords;
1100     }
1101 
1102     private static void tryFailure(Promise<?> promise, Throwable cause) {
1103         if (!promise.tryFailure(cause)) {
1104             // There is nothing really wrong with not be able to notify the promise as we may have raced here because
1105             // of multiple queries that have been executed. Log it with trace level anyway just in case the user
1106             // wants to better understand what happened.
1107             logger.trace("Failed to notify failure to a promise: {}", promise, cause);
1108         }
1109     }
1110 
1111     private void doResolveUncached(Channel channel,
1112                                    String hostname,
1113                                    DnsRecord[] additionals,
1114                                    final Promise<InetAddress> promise,
1115                                    DnsCache resolveCache, boolean completeEarlyIfPossible) {
1116         final Promise<List<InetAddress>> allPromise = executor().newPromise();
1117         doResolveAllUncached(channel, hostname, additionals, promise, allPromise,
1118                 resolveCache, completeEarlyIfPossible);
1119         allPromise.addListener(new FutureListener<List<InetAddress>>() {
1120             @Override
1121             public void operationComplete(Future<List<InetAddress>> future) {
1122                 if (future.isSuccess()) {
1123                     trySuccess(promise, future.getNow().get(0));
1124                 } else {
1125                     tryFailure(promise, future.cause());
1126                 }
1127             }
1128         });
1129     }
1130 
1131     @Override
1132     protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise) throws Exception {
1133         doResolveAll(inetHost, EMPTY_ADDITIONALS, promise, resolveCache);
1134     }
1135 
1136     /**
1137      * Hook designed for extensibility so one can pass a different cache on each resolution attempt
1138      * instead of using the global one.
1139      */
1140     protected void doResolveAll(String inetHost,
1141                                 final DnsRecord[] additionals,
1142                                 final Promise<List<InetAddress>> promise,
1143                                 final DnsCache resolveCache) throws Exception {
1144         if (inetHost == null || inetHost.isEmpty()) {
1145             // If an empty hostname is used we should use "localhost", just like InetAddress.getAllByName(...) does.
1146             promise.setSuccess(Collections.singletonList(loopbackAddress()));
1147             return;
1148         }
1149         final InetAddress address = NetUtil.createInetAddressFromIpAddressString(inetHost);
1150         if (address != null) {
1151             // The unresolvedAddress was created via a String that contains an ipaddress.
1152             promise.setSuccess(Collections.singletonList(address));
1153             return;
1154         }
1155 
1156         final String hostname = hostname(inetHost);
1157 
1158         List<InetAddress> hostsFileEntries = resolveHostsFileEntries(hostname);
1159         if (hostsFileEntries != null) {
1160             promise.setSuccess(hostsFileEntries);
1161             return;
1162         }
1163 
1164         if (!doResolveAllCached(hostname, additionals, promise, resolveCache, this.searchDomains(),
1165                 ndots(), resolvedInternetProtocolFamilies)) {
1166             ChannelFuture f = resolveChannelProvider.nextResolveChannel(promise);
1167             if (f.isDone()) {
1168                 doResolveAllNow(f, hostname, additionals, promise, resolveCache);
1169             } else {
1170                 f.addListener(new ChannelFutureListener() {
1171                     @Override
1172                     public void operationComplete(ChannelFuture f) {
1173                         doResolveAllNow(f, hostname, additionals, promise, resolveCache);
1174                     }
1175                 });
1176             }
1177         }
1178     }
1179 
1180     private void doResolveAllNow(ChannelFuture f, final String hostname, final DnsRecord[] additionals,
1181                               final Promise<List<InetAddress>> promise,
1182                               final DnsCache resolveCache) {
1183         if (f.isSuccess()) {
1184             doResolveAllUncached(f.channel(), hostname, additionals, promise, promise,
1185                     resolveCache, completeOncePreferredResolved);
1186         } else {
1187             UnknownHostException e = toException(f, hostname, null, additionals);
1188             promise.setFailure(e);
1189         }
1190     }
1191 
1192     private static boolean hasEntries(List<? extends DnsCacheEntry> cachedEntries) {
1193         return cachedEntries != null && !cachedEntries.isEmpty();
1194     }
1195 
1196     static boolean doResolveAllCached(String hostname,
1197                                       DnsRecord[] additionals,
1198                                       Promise<List<InetAddress>> promise,
1199                                       DnsCache resolveCache,
1200                                       String[] searchDomains,
1201                                       int ndots,
1202                                       InternetProtocolFamily[] resolvedInternetProtocolFamilies) {
1203         List<? extends DnsCacheEntry> cachedEntries = resolveCache.get(hostname, additionals);
1204         if (!hasEntries(cachedEntries) && searchDomains != null && ndots != 0
1205                 && !StringUtil.endsWith(hostname, '.')) {
1206             for (String searchDomain : searchDomains) {
1207                 final String initialHostname = hostname + '.' + searchDomain;
1208                 cachedEntries = resolveCache.get(initialHostname, additionals);
1209                 if (hasEntries(cachedEntries)) {
1210                     break;
1211                 }
1212             }
1213         }
1214         if (!hasEntries(cachedEntries)) {
1215             return false;
1216         }
1217 
1218         Throwable cause = cachedEntries.get(0).cause();
1219         if (cause == null) {
1220             List<InetAddress> result = null;
1221             final int numEntries = cachedEntries.size();
1222             for (InternetProtocolFamily f : resolvedInternetProtocolFamilies) {
1223                 for (int i = 0; i < numEntries; i++) {
1224                     final DnsCacheEntry e = cachedEntries.get(i);
1225                     if (f.addressType().isInstance(e.address())) {
1226                         if (result == null) {
1227                             result = new ArrayList<InetAddress>(numEntries);
1228                         }
1229                         result.add(e.address());
1230                     }
1231                 }
1232             }
1233             if (result != null) {
1234                 trySuccess(promise, result);
1235                 return true;
1236             }
1237             return false;
1238         } else {
1239             tryFailure(promise, cause);
1240             return true;
1241         }
1242     }
1243 
1244     private void doResolveAllUncached(final Channel channel,
1245                                       final String hostname,
1246                                       final DnsRecord[] additionals,
1247                                       final Promise<?> originalPromise,
1248                                       final Promise<List<InetAddress>> promise,
1249                                       final DnsCache resolveCache,
1250                                       final boolean completeEarlyIfPossible) {
1251         // Call doResolveUncached0(...) in the EventLoop as we may need to submit multiple queries which would need
1252         // to submit multiple Runnable at the end if we are not already on the EventLoop.
1253         EventExecutor executor = executor();
1254         if (executor.inEventLoop()) {
1255             doResolveAllUncached0(channel, hostname, additionals, originalPromise,
1256                                   promise, resolveCache, completeEarlyIfPossible);
1257         } else {
1258             executor.execute(new Runnable() {
1259                 @Override
1260                 public void run() {
1261                     doResolveAllUncached0(channel, hostname, additionals, originalPromise,
1262                                           promise, resolveCache, completeEarlyIfPossible);
1263                 }
1264             });
1265         }
1266     }
1267 
1268     private void doResolveAllUncached0(final Channel channel,
1269                                        final String hostname,
1270                                        final DnsRecord[] additionals,
1271                                        final Promise<?> originalPromise,
1272                                        final Promise<List<InetAddress>> promise,
1273                                        final DnsCache resolveCache,
1274                                        final boolean completeEarlyIfPossible) {
1275 
1276         assert executor().inEventLoop();
1277 
1278         if (inflightLookups != null && (additionals == null || additionals.length == 0)) {
1279             Future<List<InetAddress>> inflightFuture = inflightLookups.get(hostname);
1280             if (inflightFuture != null) {
1281                 inflightFuture.addListener(new GenericFutureListener<Future<? super List<InetAddress>>>() {
1282                     @SuppressWarnings("unchecked")
1283                     @Override
1284                     public void operationComplete(Future<? super List<InetAddress>> future) {
1285                         if (future.isSuccess()) {
1286                             promise.setSuccess((List<InetAddress>) future.getNow());
1287                         } else {
1288                             Throwable cause = future.cause();
1289                             if (isTimeoutError(cause)) {
1290                                 // The failure was caused by a timeout. This might be happening as a result of
1291                                 // the remote server be overloaded for some short amount of time or because
1292                                 // UDP packets were dropped on the floor. In this case lets try to just do the
1293                                 // query explicit and don't cascade this possible temporary failure.
1294                                 resolveNow(channel, hostname, additionals, originalPromise, promise,
1295                                         resolveCache, completeEarlyIfPossible);
1296                             } else {
1297                                 promise.setFailure(cause);
1298                             }
1299                         }
1300                     }
1301                 });
1302                 return;
1303             // Check if we have space left in the map.
1304             } else if (inflightLookups.size() < maxNumConsolidation) {
1305                 inflightLookups.put(hostname, promise);
1306                 promise.addListener(new GenericFutureListener<Future<? super List<InetAddress>>>() {
1307                     @Override
1308                     public void operationComplete(Future<? super List<InetAddress>> future) {
1309                         inflightLookups.remove(hostname);
1310                     }
1311                 });
1312             }
1313         }
1314         resolveNow(channel, hostname, additionals, originalPromise, promise,
1315                 resolveCache, completeEarlyIfPossible);
1316     }
1317 
1318     private void resolveNow(final Channel channel,
1319                             final String hostname,
1320                             final DnsRecord[] additionals,
1321                             final Promise<?> originalPromise,
1322                             final Promise<List<InetAddress>> promise,
1323                             final DnsCache resolveCache,
1324                             final boolean completeEarlyIfPossible) {
1325         final DnsServerAddressStream nameServerAddrs =
1326                 dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
1327         DnsAddressResolveContext ctx = new DnsAddressResolveContext(this, channel,
1328                 originalPromise, hostname, additionals, nameServerAddrs, maxQueriesPerResolve, resolveCache,
1329                 authoritativeDnsServerCache, completeEarlyIfPossible);
1330         ctx.resolve(promise);
1331     }
1332 
1333     private static String hostname(String inetHost) {
1334         String hostname = IDN.toASCII(inetHost);
1335         // Check for https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6894622
1336         if (StringUtil.endsWith(inetHost, '.') && !StringUtil.endsWith(hostname, '.')) {
1337             hostname += ".";
1338         }
1339         return hostname;
1340     }
1341 
1342     /**
1343      * Sends a DNS query with the specified question.
1344      */
1345     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(DnsQuestion question) {
1346         return query(nextNameServerAddress(), question);
1347     }
1348 
1349     /**
1350      * Sends a DNS query with the specified question with additional records.
1351      */
1352     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
1353             DnsQuestion question, Iterable<DnsRecord> additionals) {
1354         return query(nextNameServerAddress(), question, additionals);
1355     }
1356 
1357     /**
1358      * Sends a DNS query with the specified question.
1359      */
1360     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
1361             DnsQuestion question, Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
1362         return query(nextNameServerAddress(), question, Collections.<DnsRecord>emptyList(), promise);
1363     }
1364 
1365     private InetSocketAddress nextNameServerAddress() {
1366         return queryDnsServerAddressStream.next();
1367     }
1368 
1369     /**
1370      * Sends a DNS query with the specified question using the specified name server list.
1371      */
1372     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
1373             final InetSocketAddress nameServerAddr, final DnsQuestion question) {
1374         return query(nameServerAddr, question, Collections.<DnsRecord>emptyList());
1375     }
1376 
1377     /**
1378      * Sends a DNS query with the specified question with additional records using the specified name server list.
1379      */
1380     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
1381             final InetSocketAddress nameServerAddr, final DnsQuestion question, final Iterable<DnsRecord> additionals) {
1382         return query(nameServerAddr, question, additionals,
1383                 executor().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise());
1384     }
1385 
1386     /**
1387      * Sends a DNS query with the specified question using the specified name server list.
1388      */
1389     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
1390             final InetSocketAddress nameServerAddr, final DnsQuestion question,
1391             final Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
1392         return query(nameServerAddr, question, Collections.<DnsRecord>emptyList(), promise);
1393     }
1394 
1395     /**
1396      * Sends a DNS query with the specified question with additional records using the specified name server list.
1397      */
1398     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
1399             final InetSocketAddress nameServerAddr, final DnsQuestion question,
1400             final Iterable<DnsRecord> additionals,
1401             final Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
1402 
1403         ChannelFuture f = resolveChannelProvider.nextResolveChannel(promise);
1404         final DnsRecord[] additionalsArray = toArray(additionals, false);
1405         if (f.isDone()) {
1406             if (f.isSuccess()) {
1407                 return doQuery(f.channel(), nameServerAddr, question,
1408                         NoopDnsQueryLifecycleObserver.INSTANCE, additionalsArray,
1409                         true, promise);
1410             } else {
1411                 UnknownHostException e = toException(f, question.name(), question, additionalsArray);
1412                 promise.setFailure(e);
1413                 return executor().newFailedFuture(e);
1414             }
1415         } else {
1416             final Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> p = executor().newPromise();
1417             f.addListener(new ChannelFutureListener() {
1418                 @Override
1419                 public void operationComplete(ChannelFuture f) {
1420                     if (f.isSuccess()) {
1421                         Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> qf = doQuery(
1422                                 f.channel(), nameServerAddr, question, NoopDnsQueryLifecycleObserver.INSTANCE,
1423                                 additionalsArray, true, promise);
1424                         PromiseNotifier.cascade(qf, p);
1425                     } else {
1426                         UnknownHostException e = toException(f, question.name(), question, additionalsArray);
1427                         promise.setFailure(e);
1428                         p.setFailure(e);
1429                     }
1430                 }
1431             });
1432             return p;
1433         }
1434     }
1435 
1436     /**
1437      * Returns {@code true} if the {@link Throwable} was caused by an timeout or transport error.
1438      * These methods can be used on the {@link Future#cause()} that is returned by the various methods exposed by this
1439      * {@link DnsNameResolver}.
1440      */
1441     public static boolean isTransportOrTimeoutError(Throwable cause) {
1442         return cause != null && cause.getCause() instanceof DnsNameResolverException;
1443     }
1444 
1445     /**
1446      * Returns {@code true} if the {@link Throwable} was caused by an timeout.
1447      * These methods can be used on the {@link Future#cause()} that is returned by the various methods exposed by this
1448      * {@link DnsNameResolver}.
1449      */
1450     public static boolean isTimeoutError(Throwable cause) {
1451         return cause != null && cause.getCause() instanceof DnsNameResolverTimeoutException;
1452     }
1453 
1454     final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> doQuery(
1455             Channel channel,
1456             InetSocketAddress nameServerAddr, DnsQuestion question,
1457             final DnsQueryLifecycleObserver queryLifecycleObserver,
1458             DnsRecord[] additionals, boolean flush,
1459             Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
1460 
1461         final Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> castPromise = cast(
1462                 checkNotNull(promise, "promise"));
1463         final int payloadSize = isOptResourceEnabled() ? maxPayloadSize() : 0;
1464         try {
1465             DnsQueryContext queryContext = new DatagramDnsQueryContext(channel, nameServerAddr,
1466                     queryContextManager, payloadSize, isRecursionDesired(), queryTimeoutMillis(), question, additionals,
1467                     castPromise, socketBootstrap, retryWithTcpOnTimeout);
1468             ChannelFuture future = queryContext.writeQuery(flush);
1469             queryLifecycleObserver.queryWritten(nameServerAddr, future);
1470             return castPromise;
1471         } catch (Exception e) {
1472             return castPromise.setFailure(e);
1473         }
1474     }
1475 
1476     @SuppressWarnings("unchecked")
1477     private static Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> cast(Promise<?> promise) {
1478         return (Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>>) promise;
1479     }
1480 
1481     final DnsServerAddressStream newNameServerAddressStream(String hostname) {
1482         return dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
1483     }
1484 
1485     private static final class DnsResponseHandler extends ChannelInboundHandlerAdapter {
1486 
1487         private final DnsQueryContextManager queryContextManager;
1488 
1489         DnsResponseHandler(DnsQueryContextManager queryContextManager) {
1490             this.queryContextManager = queryContextManager;
1491         }
1492 
1493         @Override
1494         public boolean isSharable() {
1495             return true;
1496         }
1497 
1498         @Override
1499         public void channelRead(ChannelHandlerContext ctx, Object msg) {
1500             final Channel qCh = ctx.channel();
1501             final DatagramDnsResponse res = (DatagramDnsResponse) msg;
1502             final int queryId = res.id();
1503             logger.debug("{} RECEIVED: UDP [{}: {}], {}", qCh, queryId, res.sender(), res);
1504 
1505             final DnsQueryContext qCtx = queryContextManager.get(res.sender(), queryId);
1506             if (qCtx == null) {
1507                 logger.debug("{} Received a DNS response with an unknown ID: UDP [{}: {}]",
1508                         qCh, queryId, res.sender());
1509                 res.release();
1510                 return;
1511             } else if (qCtx.isDone()) {
1512                 logger.debug("{} Received a DNS response for a query that was timed out or cancelled: UDP [{}: {}]",
1513                         qCh, queryId, res.sender());
1514                 res.release();
1515                 return;
1516             }
1517 
1518             // The context will handle truncation itself.
1519             qCtx.finishSuccess(res, res.isTruncated());
1520         }
1521 
1522         @Override
1523         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
1524             if (cause instanceof CorruptedFrameException) {
1525                 logger.debug("{} Unable to decode DNS response: UDP", ctx.channel(), cause);
1526             } else {
1527                 logger.warn("{} Unexpected exception: UDP", ctx.channel(), cause);
1528             }
1529         }
1530     }
1531 
1532     private interface DnsResolveChannelProvider {
1533 
1534         /**
1535          * Return the next {@link ChannelFuture} that contains the {@link Channel} that should be used for resolving
1536          * a chain of queries.
1537          *
1538          * @param resolutionFuture  the {@link Future} that will be notified once th resolution completes.
1539          * @return                  the {@link ChannelFuture}
1540          */
1541         <T> ChannelFuture nextResolveChannel(Future<T> resolutionFuture);
1542 
1543         /**
1544          * Close the {@link DnsResolveChannelProvider} and so cleanup resources if needed.
1545          */
1546         void close();
1547     }
1548 
1549     private static ChannelFuture registerOrBind(Bootstrap bootstrap, SocketAddress localAddress) {
1550         return localAddress == null ? bootstrap.register() : bootstrap.bind(localAddress);
1551     }
1552 
1553     private static final class DnsResolveChannelPerResolverProvider implements DnsResolveChannelProvider {
1554 
1555         private final ChannelFuture resolveChannelFuture;
1556 
1557         DnsResolveChannelPerResolverProvider(Bootstrap bootstrap, SocketAddress localAddress) {
1558             resolveChannelFuture = registerOrBind(bootstrap, localAddress);
1559         }
1560 
1561         @Override
1562         public <T> ChannelFuture nextResolveChannel(Future<T> resolutionFuture) {
1563             return resolveChannelFuture;
1564         }
1565 
1566         @Override
1567         public void close() {
1568             resolveChannelFuture.channel().close();
1569         }
1570     }
1571 
1572     private static final class DnsResolveChannelPerResolutionProvider implements DnsResolveChannelProvider {
1573 
1574         private final Bootstrap bootstrap;
1575         private final SocketAddress localAddress;
1576 
1577         DnsResolveChannelPerResolutionProvider(Bootstrap bootstrap, SocketAddress localAddress) {
1578             this.bootstrap = bootstrap;
1579             this.localAddress = localAddress;
1580         }
1581 
1582         @Override
1583         public <T> ChannelFuture nextResolveChannel(Future<T> resolutionFuture) {
1584             final ChannelFuture f = registerOrBind(bootstrap, localAddress);
1585             resolutionFuture.addListener(new FutureListener<T>() {
1586                 @Override
1587                 public void operationComplete(Future<T> future) {
1588                     // Always just close the Channel once the resolution is considered complete.
1589                     f.channel().close();
1590                 }
1591             });
1592             return f;
1593         }
1594 
1595         @Override
1596         public void close() {
1597             // NOOP
1598         }
1599     }
1600 }