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