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