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