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