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