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