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    *   http://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.ChannelHandlerContext;
27  import io.netty.channel.ChannelInboundHandlerAdapter;
28  import io.netty.channel.ChannelInitializer;
29  import io.netty.channel.ChannelOption;
30  import io.netty.channel.ChannelPromise;
31  import io.netty.channel.EventLoop;
32  import io.netty.channel.FixedRecvByteBufAllocator;
33  import io.netty.channel.socket.DatagramChannel;
34  import io.netty.channel.socket.InternetProtocolFamily;
35  import io.netty.handler.codec.dns.DatagramDnsQueryEncoder;
36  import io.netty.handler.codec.dns.DatagramDnsResponse;
37  import io.netty.handler.codec.dns.DatagramDnsResponseDecoder;
38  import io.netty.handler.codec.dns.DefaultDnsRawRecord;
39  import io.netty.handler.codec.dns.DnsQuestion;
40  import io.netty.handler.codec.dns.DnsRawRecord;
41  import io.netty.handler.codec.dns.DnsRecord;
42  import io.netty.handler.codec.dns.DnsRecordType;
43  import io.netty.handler.codec.dns.DnsResponse;
44  import io.netty.resolver.HostsFileEntries;
45  import io.netty.resolver.HostsFileEntriesResolver;
46  import io.netty.resolver.InetNameResolver;
47  import io.netty.resolver.ResolvedAddressTypes;
48  import io.netty.util.NetUtil;
49  import io.netty.util.ReferenceCountUtil;
50  import io.netty.util.concurrent.FastThreadLocal;
51  import io.netty.util.concurrent.Future;
52  import io.netty.util.concurrent.FutureListener;
53  import io.netty.util.concurrent.Promise;
54  import io.netty.util.internal.EmptyArrays;
55  import io.netty.util.internal.PlatformDependent;
56  import io.netty.util.internal.StringUtil;
57  import io.netty.util.internal.UnstableApi;
58  import io.netty.util.internal.logging.InternalLogger;
59  import io.netty.util.internal.logging.InternalLoggerFactory;
60  
61  import java.lang.reflect.Method;
62  import java.net.IDN;
63  import java.net.Inet4Address;
64  import java.net.Inet6Address;
65  import java.net.InetAddress;
66  import java.net.InetSocketAddress;
67  import java.util.ArrayList;
68  import java.util.Collection;
69  import java.util.Collections;
70  import java.util.Comparator;
71  import java.util.Iterator;
72  import java.util.List;
73  
74  import static io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.DNS_PORT;
75  import static io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider.parseEtcResolverFirstNdots;
76  import static io.netty.util.internal.ObjectUtil.checkNotNull;
77  import static io.netty.util.internal.ObjectUtil.checkPositive;
78  
79  /**
80   * A DNS-based {@link InetNameResolver}.
81   */
82  @UnstableApi
83  public class DnsNameResolver extends InetNameResolver {
84  
85      private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class);
86      private static final String LOCALHOST = "localhost";
87      private static final InetAddress LOCALHOST_ADDRESS;
88      private static final DnsRecord[] EMPTY_ADDITIONALS = new DnsRecord[0];
89      private static final DnsRecordType[] IPV4_ONLY_RESOLVED_RECORD_TYPES =
90              {DnsRecordType.A};
91      private static final InternetProtocolFamily[] IPV4_ONLY_RESOLVED_PROTOCOL_FAMILIES =
92              {InternetProtocolFamily.IPv4};
93      private static final DnsRecordType[] IPV4_PREFERRED_RESOLVED_RECORD_TYPES =
94              {DnsRecordType.A, DnsRecordType.AAAA};
95      private static final InternetProtocolFamily[] IPV4_PREFERRED_RESOLVED_PROTOCOL_FAMILIES =
96              {InternetProtocolFamily.IPv4, InternetProtocolFamily.IPv6};
97      private static final DnsRecordType[] IPV6_ONLY_RESOLVED_RECORD_TYPES =
98              {DnsRecordType.AAAA};
99      private static final InternetProtocolFamily[] IPV6_ONLY_RESOLVED_PROTOCOL_FAMILIES =
100             {InternetProtocolFamily.IPv6};
101     private static final DnsRecordType[] IPV6_PREFERRED_RESOLVED_RECORD_TYPES =
102             {DnsRecordType.AAAA, DnsRecordType.A};
103     private static final InternetProtocolFamily[] IPV6_PREFERRED_RESOLVED_PROTOCOL_FAMILIES =
104             {InternetProtocolFamily.IPv6, InternetProtocolFamily.IPv4};
105 
106     static final ResolvedAddressTypes DEFAULT_RESOLVE_ADDRESS_TYPES;
107     static final String[] DEFAULT_SEARCH_DOMAINS;
108     private static final int DEFAULT_NDOTS;
109 
110     static {
111         if (NetUtil.isIpV4StackPreferred()) {
112             DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_ONLY;
113             LOCALHOST_ADDRESS = NetUtil.LOCALHOST4;
114         } else {
115             if (NetUtil.isIpV6AddressesPreferred()) {
116                 DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV6_PREFERRED;
117                 LOCALHOST_ADDRESS = NetUtil.LOCALHOST6;
118             } else {
119                 DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_PREFERRED;
120                 LOCALHOST_ADDRESS = NetUtil.LOCALHOST4;
121             }
122         }
123     }
124 
125     static {
126         String[] searchDomains;
127         try {
128             List<String> list = PlatformDependent.isWindows()
129                     ? getSearchDomainsHack()
130                     : UnixResolverDnsServerAddressStreamProvider.parseEtcResolverSearchDomains();
131             searchDomains = list.toArray(new String[0]);
132         } catch (Exception ignore) {
133             // Failed to get the system name search domain list.
134             searchDomains = EmptyArrays.EMPTY_STRINGS;
135         }
136         DEFAULT_SEARCH_DOMAINS = searchDomains;
137 
138         int ndots;
139         try {
140             ndots = parseEtcResolverFirstNdots();
141         } catch (Exception ignore) {
142             ndots = UnixResolverDnsServerAddressStreamProvider.DEFAULT_NDOTS;
143         }
144         DEFAULT_NDOTS = ndots;
145     }
146 
147     @SuppressWarnings("unchecked")
148     private static List<String> getSearchDomainsHack() throws Exception {
149         // This code on Java 9+ yields a warning about illegal reflective access that will be denied in
150         // a future release. There doesn't seem to be a better way to get search domains for Windows yet.
151         Class<?> configClass = Class.forName("sun.net.dns.ResolverConfiguration");
152         Method open = configClass.getMethod("open");
153         Method nameservers = configClass.getMethod("searchlist");
154         Object instance = open.invoke(null);
155 
156         return (List<String>) nameservers.invoke(instance);
157     }
158 
159     private static final DatagramDnsResponseDecoder DECODER = new DatagramDnsResponseDecoder();
160     private static final DatagramDnsQueryEncoder ENCODER = new DatagramDnsQueryEncoder();
161 
162     final Future<Channel> channelFuture;
163     final Channel ch;
164 
165     // Comparator that ensures we will try first to use the nameservers that use our preferred address type.
166     private final Comparator<InetSocketAddress> nameServerComparator;
167     /**
168      * Manages the {@link DnsQueryContext}s in progress and their query IDs.
169      */
170     final DnsQueryContextManager queryContextManager = new DnsQueryContextManager();
171 
172     /**
173      * Cache for {@link #doResolve(String, Promise)} and {@link #doResolveAll(String, Promise)}.
174      */
175     private final DnsCache resolveCache;
176     private final AuthoritativeDnsServerCache authoritativeDnsServerCache;
177     private final DnsCnameCache cnameCache;
178 
179     private final FastThreadLocal<DnsServerAddressStream> nameServerAddrStream =
180             new FastThreadLocal<DnsServerAddressStream>() {
181                 @Override
182                 protected DnsServerAddressStream initialValue() {
183                     return dnsServerAddressStreamProvider.nameServerAddressStream("");
184                 }
185             };
186 
187     private final long queryTimeoutMillis;
188     private final int maxQueriesPerResolve;
189     private final ResolvedAddressTypes resolvedAddressTypes;
190     private final InternetProtocolFamily[] resolvedInternetProtocolFamilies;
191     private final boolean recursionDesired;
192     private final int maxPayloadSize;
193     private final boolean optResourceEnabled;
194     private final HostsFileEntriesResolver hostsFileEntriesResolver;
195     private final DnsServerAddressStreamProvider dnsServerAddressStreamProvider;
196     private final String[] searchDomains;
197     private final int ndots;
198     private final boolean supportsAAAARecords;
199     private final boolean supportsARecords;
200     private final InternetProtocolFamily preferredAddressType;
201     private final DnsRecordType[] resolveRecordTypes;
202     private final boolean decodeIdn;
203     private final DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory;
204 
205     /**
206      * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
207      *
208      * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
209      * @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel}
210      * @param resolveCache the DNS resolved entries cache
211      * @param authoritativeDnsServerCache the cache used to find the authoritative DNS server for a domain
212      * @param dnsQueryLifecycleObserverFactory used to generate new instances of {@link DnsQueryLifecycleObserver} which
213      *                                         can be used to track metrics for DNS servers.
214      * @param queryTimeoutMillis timeout of each DNS query in millis
215      * @param resolvedAddressTypes the preferred address types
216      * @param recursionDesired if recursion desired flag must be set
217      * @param maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution
218      * @param traceEnabled if trace is enabled
219      * @param maxPayloadSize the capacity of the datagram packet buffer
220      * @param optResourceEnabled if automatic inclusion of a optional records is enabled
221      * @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to check for local aliases
222      * @param dnsServerAddressStreamProvider The {@link DnsServerAddressStreamProvider} used to determine the name
223      *                                       servers for each hostname lookup.
224      * @param searchDomains the list of search domain
225      *                      (can be null, if so, will try to default to the underlying platform ones)
226      * @param ndots the ndots value
227      * @param decodeIdn {@code true} if domain / host names should be decoded to unicode when received.
228      *                        See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
229      * @deprecated Use {@link DnsNameResolverBuilder}.
230      */
231     @Deprecated
232     public DnsNameResolver(
233             EventLoop eventLoop,
234             ChannelFactory<? extends DatagramChannel> channelFactory,
235             final DnsCache resolveCache,
236             final DnsCache authoritativeDnsServerCache,
237             DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory,
238             long queryTimeoutMillis,
239             ResolvedAddressTypes resolvedAddressTypes,
240             boolean recursionDesired,
241             int maxQueriesPerResolve,
242             boolean traceEnabled,
243             int maxPayloadSize,
244             boolean optResourceEnabled,
245             HostsFileEntriesResolver hostsFileEntriesResolver,
246             DnsServerAddressStreamProvider dnsServerAddressStreamProvider,
247             String[] searchDomains,
248             int ndots,
249             boolean decodeIdn) {
250         this(eventLoop, channelFactory, resolveCache,
251              new AuthoritativeDnsServerCacheAdapter(authoritativeDnsServerCache), dnsQueryLifecycleObserverFactory,
252              queryTimeoutMillis, resolvedAddressTypes, recursionDesired, maxQueriesPerResolve, traceEnabled,
253              maxPayloadSize, optResourceEnabled, hostsFileEntriesResolver, dnsServerAddressStreamProvider,
254              searchDomains, ndots, decodeIdn);
255     }
256 
257     /**
258      * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
259      *
260      * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
261      * @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel}
262      * @param resolveCache the DNS resolved entries cache
263      * @param authoritativeDnsServerCache the cache used to find the authoritative DNS server for a domain
264      * @param dnsQueryLifecycleObserverFactory used to generate new instances of {@link DnsQueryLifecycleObserver} which
265      *                                         can be used to track metrics for DNS servers.
266      * @param queryTimeoutMillis timeout of each DNS query in millis
267      * @param resolvedAddressTypes the preferred address types
268      * @param recursionDesired if recursion desired flag must be set
269      * @param maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution
270      * @param traceEnabled if trace is enabled
271      * @param maxPayloadSize the capacity of the datagram packet buffer
272      * @param optResourceEnabled if automatic inclusion of a optional records is enabled
273      * @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to check for local aliases
274      * @param dnsServerAddressStreamProvider The {@link DnsServerAddressStreamProvider} used to determine the name
275      *                                       servers for each hostname lookup.
276      * @param searchDomains the list of search domain
277      *                      (can be null, if so, will try to default to the underlying platform ones)
278      * @param ndots the ndots value
279      * @param decodeIdn {@code true} if domain / host names should be decoded to unicode when received.
280      *                        See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
281      * @deprecated Use {@link DnsNameResolverBuilder}.
282      */
283     @Deprecated
284     public DnsNameResolver(
285             EventLoop eventLoop,
286             ChannelFactory<? extends DatagramChannel> channelFactory,
287             final DnsCache resolveCache,
288             final AuthoritativeDnsServerCache authoritativeDnsServerCache,
289             DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory,
290             long queryTimeoutMillis,
291             ResolvedAddressTypes resolvedAddressTypes,
292             boolean recursionDesired,
293             int maxQueriesPerResolve,
294             boolean traceEnabled,
295             int maxPayloadSize,
296             boolean optResourceEnabled,
297             HostsFileEntriesResolver hostsFileEntriesResolver,
298             DnsServerAddressStreamProvider dnsServerAddressStreamProvider,
299             String[] searchDomains,
300             int ndots,
301             boolean decodeIdn) {
302         this(eventLoop, channelFactory, resolveCache, NoopDnsCnameCache.INSTANCE, authoritativeDnsServerCache,
303              dnsQueryLifecycleObserverFactory, queryTimeoutMillis, resolvedAddressTypes, recursionDesired,
304              maxQueriesPerResolve, traceEnabled, maxPayloadSize, optResourceEnabled, hostsFileEntriesResolver,
305              dnsServerAddressStreamProvider, searchDomains, ndots, decodeIdn);
306     }
307 
308     DnsNameResolver(
309             EventLoop eventLoop,
310             ChannelFactory<? extends DatagramChannel> channelFactory,
311             final DnsCache resolveCache,
312             final DnsCnameCache cnameCache,
313             final AuthoritativeDnsServerCache authoritativeDnsServerCache,
314             DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory,
315             long queryTimeoutMillis,
316             ResolvedAddressTypes resolvedAddressTypes,
317             boolean recursionDesired,
318             int maxQueriesPerResolve,
319             boolean traceEnabled,
320             int maxPayloadSize,
321             boolean optResourceEnabled,
322             HostsFileEntriesResolver hostsFileEntriesResolver,
323             DnsServerAddressStreamProvider dnsServerAddressStreamProvider,
324             String[] searchDomains,
325             int ndots,
326             boolean decodeIdn) {
327         super(eventLoop);
328         this.queryTimeoutMillis = checkPositive(queryTimeoutMillis, "queryTimeoutMillis");
329         this.resolvedAddressTypes = resolvedAddressTypes != null ? resolvedAddressTypes : DEFAULT_RESOLVE_ADDRESS_TYPES;
330         this.recursionDesired = recursionDesired;
331         this.maxQueriesPerResolve = checkPositive(maxQueriesPerResolve, "maxQueriesPerResolve");
332         this.maxPayloadSize = checkPositive(maxPayloadSize, "maxPayloadSize");
333         this.optResourceEnabled = optResourceEnabled;
334         this.hostsFileEntriesResolver = checkNotNull(hostsFileEntriesResolver, "hostsFileEntriesResolver");
335         this.dnsServerAddressStreamProvider =
336                 checkNotNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
337         this.resolveCache = checkNotNull(resolveCache, "resolveCache");
338         this.cnameCache = checkNotNull(cnameCache, "cnameCache");
339         this.dnsQueryLifecycleObserverFactory = traceEnabled ?
340                 dnsQueryLifecycleObserverFactory instanceof NoopDnsQueryLifecycleObserverFactory ?
341                         new TraceDnsQueryLifeCycleObserverFactory() :
342                         new BiDnsQueryLifecycleObserverFactory(new TraceDnsQueryLifeCycleObserverFactory(),
343                                                                dnsQueryLifecycleObserverFactory) :
344                 checkNotNull(dnsQueryLifecycleObserverFactory, "dnsQueryLifecycleObserverFactory");
345         this.searchDomains = searchDomains != null ? searchDomains.clone() : DEFAULT_SEARCH_DOMAINS;
346         this.ndots = ndots >= 0 ? ndots : DEFAULT_NDOTS;
347         this.decodeIdn = decodeIdn;
348 
349         switch (this.resolvedAddressTypes) {
350             case IPV4_ONLY:
351                 supportsAAAARecords = false;
352                 supportsARecords = true;
353                 resolveRecordTypes = IPV4_ONLY_RESOLVED_RECORD_TYPES;
354                 resolvedInternetProtocolFamilies = IPV4_ONLY_RESOLVED_PROTOCOL_FAMILIES;
355                 break;
356             case IPV4_PREFERRED:
357                 supportsAAAARecords = true;
358                 supportsARecords = true;
359                 resolveRecordTypes = IPV4_PREFERRED_RESOLVED_RECORD_TYPES;
360                 resolvedInternetProtocolFamilies = IPV4_PREFERRED_RESOLVED_PROTOCOL_FAMILIES;
361                 break;
362             case IPV6_ONLY:
363                 supportsAAAARecords = true;
364                 supportsARecords = false;
365                 resolveRecordTypes = IPV6_ONLY_RESOLVED_RECORD_TYPES;
366                 resolvedInternetProtocolFamilies = IPV6_ONLY_RESOLVED_PROTOCOL_FAMILIES;
367                 break;
368             case IPV6_PREFERRED:
369                 supportsAAAARecords = true;
370                 supportsARecords = true;
371                 resolveRecordTypes = IPV6_PREFERRED_RESOLVED_RECORD_TYPES;
372                 resolvedInternetProtocolFamilies = IPV6_PREFERRED_RESOLVED_PROTOCOL_FAMILIES;
373                 break;
374             default:
375                 throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
376         }
377         preferredAddressType = preferredAddressType(resolvedAddressTypes);
378         this.authoritativeDnsServerCache = checkNotNull(authoritativeDnsServerCache, "authoritativeDnsServerCache");
379         nameServerComparator = new NameServerComparator(preferredAddressType.addressType());
380 
381         Bootstrap b = new Bootstrap();
382         b.group(executor());
383         b.channelFactory(channelFactory);
384         b.option(ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION, true);
385         final DnsResponseHandler responseHandler = new DnsResponseHandler(executor().<Channel>newPromise());
386         b.handler(new ChannelInitializer<DatagramChannel>() {
387             @Override
388             protected void initChannel(DatagramChannel ch) throws Exception {
389                 ch.pipeline().addLast(DECODER, ENCODER, responseHandler);
390             }
391         });
392 
393         channelFuture = responseHandler.channelActivePromise;
394         ChannelFuture future = b.register();
395         Throwable cause = future.cause();
396         if (cause != null) {
397             if (cause instanceof RuntimeException) {
398                 throw (RuntimeException) cause;
399             }
400             if (cause instanceof Error) {
401                 throw (Error) cause;
402             }
403             throw new IllegalStateException("Unable to create / register Channel", cause);
404         }
405         ch = future.channel();
406         ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize));
407 
408         ch.closeFuture().addListener(new ChannelFutureListener() {
409             @Override
410             public void operationComplete(ChannelFuture future) {
411                 resolveCache.clear();
412                 cnameCache.clear();
413                 authoritativeDnsServerCache.clear();
414             }
415         });
416     }
417 
418     static InternetProtocolFamily preferredAddressType(ResolvedAddressTypes resolvedAddressTypes) {
419         switch (resolvedAddressTypes) {
420         case IPV4_ONLY:
421         case IPV4_PREFERRED:
422             return InternetProtocolFamily.IPv4;
423         case IPV6_ONLY:
424         case IPV6_PREFERRED:
425             return InternetProtocolFamily.IPv6;
426         default:
427             throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
428         }
429     }
430 
431     // Only here to override in unit tests.
432     InetSocketAddress newRedirectServerAddress(InetAddress server) {
433         return new InetSocketAddress(server, DNS_PORT);
434     }
435 
436     final DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory() {
437         return dnsQueryLifecycleObserverFactory;
438     }
439 
440     /**
441      * Creates a new {@link DnsServerAddressStream} to following a redirected DNS query. By overriding this
442      * it provides the opportunity to sort the name servers before following a redirected DNS query.
443      *
444      * @param hostname the hostname.
445      * @param nameservers The addresses of the DNS servers which are used in the event of a redirect. This may
446      *                    contain resolved and unresolved addresses so the used {@link DnsServerAddressStream} must
447      *                    allow unresolved addresses if you want to include these as well.
448      * @return A {@link DnsServerAddressStream} which will be used to follow the DNS redirect or {@code null} if
449      *         none should be followed.
450      */
451     protected DnsServerAddressStream newRedirectDnsServerStream(
452             @SuppressWarnings("unused") String hostname, List<InetSocketAddress> nameservers) {
453         DnsServerAddressStream cached = authoritativeDnsServerCache().get(hostname);
454         if (cached == null || cached.size() == 0) {
455             // If there is no cache hit (which may be the case for example when a NoopAuthoritativeDnsServerCache
456             // is used), we will just directly use the provided nameservers.
457             Collections.sort(nameservers, nameServerComparator);
458             return new SequentialDnsServerAddressStream(nameservers, 0);
459         }
460         return cached;
461     }
462 
463     /**
464      * Returns the resolution cache.
465      */
466     public DnsCache resolveCache() {
467         return resolveCache;
468     }
469 
470     /**
471      * Returns the {@link DnsCnameCache}.
472      */
473     DnsCnameCache cnameCache() {
474         return cnameCache;
475     }
476 
477     /**
478      * Returns the cache used for authoritative DNS servers for a domain.
479      */
480     public AuthoritativeDnsServerCache authoritativeDnsServerCache() {
481         return authoritativeDnsServerCache;
482     }
483 
484     /**
485      * Returns the timeout of each DNS query performed by this resolver (in milliseconds).
486      * The default value is 5 seconds.
487      */
488     public long queryTimeoutMillis() {
489         return queryTimeoutMillis;
490     }
491 
492     /**
493      * Returns the {@link ResolvedAddressTypes} resolved by {@link #resolve(String)}.
494      * The default value depends on the value of the system property {@code "java.net.preferIPv6Addresses"}.
495      */
496     public ResolvedAddressTypes resolvedAddressTypes() {
497         return resolvedAddressTypes;
498     }
499 
500     InternetProtocolFamily[] resolvedInternetProtocolFamiliesUnsafe() {
501         return resolvedInternetProtocolFamilies;
502     }
503 
504     final String[] searchDomains() {
505         return searchDomains;
506     }
507 
508     final int ndots() {
509         return ndots;
510     }
511 
512     final boolean supportsAAAARecords() {
513         return supportsAAAARecords;
514     }
515 
516     final boolean supportsARecords() {
517         return supportsARecords;
518     }
519 
520     final InternetProtocolFamily preferredAddressType() {
521         return preferredAddressType;
522     }
523 
524     final DnsRecordType[] resolveRecordTypes() {
525         return resolveRecordTypes;
526     }
527 
528     final boolean isDecodeIdn() {
529         return decodeIdn;
530     }
531 
532     /**
533      * Returns {@code true} if and only if this resolver sends a DNS query with the RD (recursion desired) flag set.
534      * The default value is {@code true}.
535      */
536     public boolean isRecursionDesired() {
537         return recursionDesired;
538     }
539 
540     /**
541      * Returns the maximum allowed number of DNS queries to send when resolving a host name.
542      * The default value is {@code 8}.
543      */
544     public int maxQueriesPerResolve() {
545         return maxQueriesPerResolve;
546     }
547 
548     /**
549      * Returns the capacity of the datagram packet buffer (in bytes).  The default value is {@code 4096} bytes.
550      */
551     public int maxPayloadSize() {
552         return maxPayloadSize;
553     }
554 
555     /**
556      * Returns the automatic inclusion of a optional records that tries to give the remote DNS server a hint about how
557      * much data the resolver can read per response is enabled.
558      */
559     public boolean isOptResourceEnabled() {
560         return optResourceEnabled;
561     }
562 
563     /**
564      * Returns the component that tries to resolve hostnames against the hosts file prior to asking to
565      * remotes DNS servers.
566      */
567     public HostsFileEntriesResolver hostsFileEntriesResolver() {
568         return hostsFileEntriesResolver;
569     }
570 
571     /**
572      * Closes the internal datagram channel used for sending and receiving DNS messages, and clears all DNS resource
573      * records from the cache. Attempting to send a DNS query or to resolve a domain name will fail once this method
574      * has been called.
575      */
576     @Override
577     public void close() {
578         if (ch.isOpen()) {
579             ch.close();
580         }
581     }
582 
583     @Override
584     protected EventLoop executor() {
585         return (EventLoop) super.executor();
586     }
587 
588     private InetAddress resolveHostsFileEntry(String hostname) {
589         if (hostsFileEntriesResolver == null) {
590             return null;
591         } else {
592             InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
593             if (address == null && PlatformDependent.isWindows() && LOCALHOST.equalsIgnoreCase(hostname)) {
594                 // If we tried to resolve localhost we need workaround that windows removed localhost from its
595                 // hostfile in later versions.
596                 // See https://github.com/netty/netty/issues/5386
597                 return LOCALHOST_ADDRESS;
598             }
599             return address;
600         }
601     }
602 
603     /**
604      * Resolves the specified name into an address.
605      *
606      * @param inetHost the name to resolve
607      * @param additionals additional records ({@code OPT})
608      *
609      * @return the address as the result of the resolution
610      */
611     public final Future<InetAddress> resolve(String inetHost, Iterable<DnsRecord> additionals) {
612         return resolve(inetHost, additionals, executor().<InetAddress>newPromise());
613     }
614 
615     /**
616      * Resolves the specified name into an address.
617      *
618      * @param inetHost the name to resolve
619      * @param additionals additional records ({@code OPT})
620      * @param promise the {@link Promise} which will be fulfilled when the name resolution is finished
621      *
622      * @return the address as the result of the resolution
623      */
624     public final Future<InetAddress> resolve(String inetHost, Iterable<DnsRecord> additionals,
625                                              Promise<InetAddress> promise) {
626         checkNotNull(promise, "promise");
627         DnsRecord[] additionalsArray = toArray(additionals, true);
628         try {
629             doResolve(inetHost, additionalsArray, promise, resolveCache);
630             return promise;
631         } catch (Exception e) {
632             return promise.setFailure(e);
633         }
634     }
635 
636     /**
637      * Resolves the specified host name and port into a list of address.
638      *
639      * @param inetHost the name to resolve
640      * @param additionals additional records ({@code OPT})
641      *
642      * @return the list of the address as the result of the resolution
643      */
644     public final Future<List<InetAddress>> resolveAll(String inetHost, Iterable<DnsRecord> additionals) {
645         return resolveAll(inetHost, additionals, executor().<List<InetAddress>>newPromise());
646     }
647 
648     /**
649      * Resolves the specified host name and port into a list of address.
650      *
651      * @param inetHost the name to resolve
652      * @param additionals additional records ({@code OPT})
653      * @param promise the {@link Promise} which will be fulfilled when the name resolution is finished
654      *
655      * @return the list of the address as the result of the resolution
656      */
657     public final Future<List<InetAddress>> resolveAll(String inetHost, Iterable<DnsRecord> additionals,
658                                                 Promise<List<InetAddress>> promise) {
659         checkNotNull(promise, "promise");
660         DnsRecord[] additionalsArray = toArray(additionals, true);
661         try {
662             doResolveAll(inetHost, additionalsArray, promise, resolveCache);
663             return promise;
664         } catch (Exception e) {
665             return promise.setFailure(e);
666         }
667     }
668 
669     @Override
670     protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
671         doResolve(inetHost, EMPTY_ADDITIONALS, promise, resolveCache);
672     }
673 
674     /**
675      * Resolves the {@link DnsRecord}s that are matched by the specified {@link DnsQuestion}. Unlike
676      * {@link #query(DnsQuestion)}, this method handles redirection, CNAMEs and multiple name servers.
677      * If the specified {@link DnsQuestion} is {@code A} or {@code AAAA}, this method looks up the configured
678      * {@link HostsFileEntries} before sending a query to the name servers. If a match is found in the
679      * {@link HostsFileEntries}, a synthetic {@code A} or {@code AAAA} record will be returned.
680      *
681      * @param question the question
682      *
683      * @return the list of the {@link DnsRecord}s as the result of the resolution
684      */
685     public final Future<List<DnsRecord>> resolveAll(DnsQuestion question) {
686         return resolveAll(question, EMPTY_ADDITIONALS, executor().<List<DnsRecord>>newPromise());
687     }
688 
689     /**
690      * Resolves the {@link DnsRecord}s that are matched by the specified {@link DnsQuestion}. Unlike
691      * {@link #query(DnsQuestion)}, this method handles redirection, CNAMEs and multiple name servers.
692      * If the specified {@link DnsQuestion} is {@code A} or {@code AAAA}, this method looks up the configured
693      * {@link HostsFileEntries} before sending a query to the name servers. If a match is found in the
694      * {@link HostsFileEntries}, a synthetic {@code A} or {@code AAAA} record will be returned.
695      *
696      * @param question the question
697      * @param additionals additional records ({@code OPT})
698      *
699      * @return the list of the {@link DnsRecord}s as the result of the resolution
700      */
701     public final Future<List<DnsRecord>> resolveAll(DnsQuestion question, Iterable<DnsRecord> additionals) {
702         return resolveAll(question, additionals, executor().<List<DnsRecord>>newPromise());
703     }
704 
705     /**
706      * Resolves the {@link DnsRecord}s that are matched by the specified {@link DnsQuestion}. Unlike
707      * {@link #query(DnsQuestion)}, this method handles redirection, CNAMEs and multiple name servers.
708      * If the specified {@link DnsQuestion} is {@code A} or {@code AAAA}, this method looks up the configured
709      * {@link HostsFileEntries} before sending a query to the name servers. If a match is found in the
710      * {@link HostsFileEntries}, a synthetic {@code A} or {@code AAAA} record will be returned.
711      *
712      * @param question the question
713      * @param additionals additional records ({@code OPT})
714      * @param promise the {@link Promise} which will be fulfilled when the resolution is finished
715      *
716      * @return the list of the {@link DnsRecord}s as the result of the resolution
717      */
718     public final Future<List<DnsRecord>> resolveAll(DnsQuestion question, Iterable<DnsRecord> additionals,
719                                                     Promise<List<DnsRecord>> promise) {
720         final DnsRecord[] additionalsArray = toArray(additionals, true);
721         return resolveAll(question, additionalsArray, promise);
722     }
723 
724     private Future<List<DnsRecord>> resolveAll(DnsQuestion question, DnsRecord[] additionals,
725                                                Promise<List<DnsRecord>> promise) {
726         checkNotNull(question, "question");
727         checkNotNull(promise, "promise");
728 
729         // Respect /etc/hosts as well if the record type is A or AAAA.
730         final DnsRecordType type = question.type();
731         final String hostname = question.name();
732 
733         if (type == DnsRecordType.A || type == DnsRecordType.AAAA) {
734             final InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
735             if (hostsFileEntry != null) {
736                 ByteBuf content = null;
737                 if (hostsFileEntry instanceof Inet4Address) {
738                     if (type == DnsRecordType.A) {
739                         content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
740                     }
741                 } else if (hostsFileEntry instanceof Inet6Address) {
742                     if (type == DnsRecordType.AAAA) {
743                         content = Unpooled.wrappedBuffer(hostsFileEntry.getAddress());
744                     }
745                 }
746 
747                 if (content != null) {
748                     // Our current implementation does not support reloading the hosts file,
749                     // so use a fairly large TTL (1 day, i.e. 86400 seconds).
750                     trySuccess(promise, Collections.<DnsRecord>singletonList(
751                             new DefaultDnsRawRecord(hostname, type, 86400, content)));
752                     return promise;
753                 }
754             }
755         }
756 
757         // It was not A/AAAA question or there was no entry in /etc/hosts.
758         final DnsServerAddressStream nameServerAddrs =
759                 dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
760         new DnsRecordResolveContext(this, question, additionals, nameServerAddrs).resolve(promise);
761         return promise;
762     }
763 
764     private static DnsRecord[] toArray(Iterable<DnsRecord> additionals, boolean validateType) {
765         checkNotNull(additionals, "additionals");
766         if (additionals instanceof Collection) {
767             Collection<DnsRecord> records = (Collection<DnsRecord>) additionals;
768             for (DnsRecord r: additionals) {
769                 validateAdditional(r, validateType);
770             }
771             return records.toArray(new DnsRecord[records.size()]);
772         }
773 
774         Iterator<DnsRecord> additionalsIt = additionals.iterator();
775         if (!additionalsIt.hasNext()) {
776             return EMPTY_ADDITIONALS;
777         }
778         List<DnsRecord> records = new ArrayList<DnsRecord>();
779         do {
780             DnsRecord r = additionalsIt.next();
781             validateAdditional(r, validateType);
782             records.add(r);
783         } while (additionalsIt.hasNext());
784 
785         return records.toArray(new DnsRecord[records.size()]);
786     }
787 
788     private static void validateAdditional(DnsRecord record, boolean validateType) {
789         checkNotNull(record, "record");
790         if (validateType && record instanceof DnsRawRecord) {
791             throw new IllegalArgumentException("DnsRawRecord implementations not allowed: " + record);
792         }
793     }
794 
795     private InetAddress loopbackAddress() {
796         return preferredAddressType().localhost();
797     }
798 
799     /**
800      * Hook designed for extensibility so one can pass a different cache on each resolution attempt
801      * instead of using the global one.
802      */
803     protected void doResolve(String inetHost,
804                              DnsRecord[] additionals,
805                              Promise<InetAddress> promise,
806                              DnsCache resolveCache) throws Exception {
807         if (inetHost == null || inetHost.isEmpty()) {
808             // If an empty hostname is used we should use "localhost", just like InetAddress.getByName(...) does.
809             promise.setSuccess(loopbackAddress());
810             return;
811         }
812         final byte[] bytes = NetUtil.createByteArrayFromIpAddressString(inetHost);
813         if (bytes != null) {
814             // The inetHost is actually an ipaddress.
815             promise.setSuccess(InetAddress.getByAddress(bytes));
816             return;
817         }
818 
819         final String hostname = hostname(inetHost);
820 
821         InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
822         if (hostsFileEntry != null) {
823             promise.setSuccess(hostsFileEntry);
824             return;
825         }
826 
827         if (!doResolveCached(hostname, additionals, promise, resolveCache)) {
828             doResolveUncached(hostname, additionals, promise, resolveCache);
829         }
830     }
831 
832     private boolean doResolveCached(String hostname,
833                                     DnsRecord[] additionals,
834                                     Promise<InetAddress> promise,
835                                     DnsCache resolveCache) {
836         final List<? extends DnsCacheEntry> cachedEntries = resolveCache.get(hostname, additionals);
837         if (cachedEntries == null || cachedEntries.isEmpty()) {
838             return false;
839         }
840 
841         Throwable cause = cachedEntries.get(0).cause();
842         if (cause == null) {
843             final int numEntries = cachedEntries.size();
844             // Find the first entry with the preferred address type.
845             for (InternetProtocolFamily f : resolvedInternetProtocolFamilies) {
846                 for (int i = 0; i < numEntries; i++) {
847                     final DnsCacheEntry e = cachedEntries.get(i);
848                     if (f.addressType().isInstance(e.address())) {
849                         trySuccess(promise, e.address());
850                         return true;
851                     }
852                 }
853             }
854             return false;
855         } else {
856             tryFailure(promise, cause);
857             return true;
858         }
859     }
860 
861     static <T> void trySuccess(Promise<T> promise, T result) {
862         if (!promise.trySuccess(result)) {
863             logger.warn("Failed to notify success ({}) to a promise: {}", result, promise);
864         }
865     }
866 
867     private static void tryFailure(Promise<?> promise, Throwable cause) {
868         if (!promise.tryFailure(cause)) {
869             logger.warn("Failed to notify failure to a promise: {}", promise, cause);
870         }
871     }
872 
873     private void doResolveUncached(String hostname,
874                                    DnsRecord[] additionals,
875                                    final Promise<InetAddress> promise,
876                                    DnsCache resolveCache) {
877         final Promise<List<InetAddress>> allPromise = executor().newPromise();
878         doResolveAllUncached(hostname, additionals, allPromise, resolveCache);
879         allPromise.addListener(new FutureListener<List<InetAddress>>() {
880             @Override
881             public void operationComplete(Future<List<InetAddress>> future) {
882                 if (future.isSuccess()) {
883                     trySuccess(promise, future.getNow().get(0));
884                 } else {
885                     tryFailure(promise, future.cause());
886                 }
887             }
888         });
889     }
890 
891     @Override
892     protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise) throws Exception {
893         doResolveAll(inetHost, EMPTY_ADDITIONALS, promise, resolveCache);
894     }
895 
896     /**
897      * Hook designed for extensibility so one can pass a different cache on each resolution attempt
898      * instead of using the global one.
899      */
900     protected void doResolveAll(String inetHost,
901                                 DnsRecord[] additionals,
902                                 Promise<List<InetAddress>> promise,
903                                 DnsCache resolveCache) throws Exception {
904         if (inetHost == null || inetHost.isEmpty()) {
905             // If an empty hostname is used we should use "localhost", just like InetAddress.getAllByName(...) does.
906             promise.setSuccess(Collections.singletonList(loopbackAddress()));
907             return;
908         }
909         final byte[] bytes = NetUtil.createByteArrayFromIpAddressString(inetHost);
910         if (bytes != null) {
911             // The unresolvedAddress was created via a String that contains an ipaddress.
912             promise.setSuccess(Collections.singletonList(InetAddress.getByAddress(bytes)));
913             return;
914         }
915 
916         final String hostname = hostname(inetHost);
917 
918         InetAddress hostsFileEntry = resolveHostsFileEntry(hostname);
919         if (hostsFileEntry != null) {
920             promise.setSuccess(Collections.singletonList(hostsFileEntry));
921             return;
922         }
923 
924         if (!doResolveAllCached(hostname, additionals, promise, resolveCache, resolvedInternetProtocolFamilies)) {
925             doResolveAllUncached(hostname, additionals, promise, resolveCache);
926         }
927     }
928 
929     static boolean doResolveAllCached(String hostname,
930                                       DnsRecord[] additionals,
931                                       Promise<List<InetAddress>> promise,
932                                       DnsCache resolveCache,
933                                       InternetProtocolFamily[] resolvedInternetProtocolFamilies) {
934         final List<? extends DnsCacheEntry> cachedEntries = resolveCache.get(hostname, additionals);
935         if (cachedEntries == null || cachedEntries.isEmpty()) {
936             return false;
937         }
938 
939         Throwable cause = cachedEntries.get(0).cause();
940         if (cause == null) {
941             List<InetAddress> result = null;
942             final int numEntries = cachedEntries.size();
943             for (InternetProtocolFamily f : resolvedInternetProtocolFamilies) {
944                 for (int i = 0; i < numEntries; i++) {
945                     final DnsCacheEntry e = cachedEntries.get(i);
946                     if (f.addressType().isInstance(e.address())) {
947                         if (result == null) {
948                             result = new ArrayList<InetAddress>(numEntries);
949                         }
950                         result.add(e.address());
951                     }
952                 }
953             }
954             if (result != null) {
955                 trySuccess(promise, result);
956                 return true;
957             }
958             return false;
959         } else {
960             tryFailure(promise, cause);
961             return true;
962         }
963     }
964 
965     private void doResolveAllUncached(String hostname,
966                                       DnsRecord[] additionals,
967                                       Promise<List<InetAddress>> promise,
968                                       DnsCache resolveCache) {
969         final DnsServerAddressStream nameServerAddrs =
970                 dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
971         new DnsAddressResolveContext(this, hostname, additionals, nameServerAddrs,
972                                      resolveCache, authoritativeDnsServerCache).resolve(promise);
973     }
974 
975     private static String hostname(String inetHost) {
976         String hostname = IDN.toASCII(inetHost);
977         // Check for http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6894622
978         if (StringUtil.endsWith(inetHost, '.') && !StringUtil.endsWith(hostname, '.')) {
979             hostname += ".";
980         }
981         return hostname;
982     }
983 
984     /**
985      * Sends a DNS query with the specified question.
986      */
987     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(DnsQuestion question) {
988         return query(nextNameServerAddress(), question);
989     }
990 
991     /**
992      * Sends a DNS query with the specified question with additional records.
993      */
994     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
995             DnsQuestion question, Iterable<DnsRecord> additionals) {
996         return query(nextNameServerAddress(), question, additionals);
997     }
998 
999     /**
1000      * Sends a DNS query with the specified question.
1001      */
1002     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
1003             DnsQuestion question, Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
1004         return query(nextNameServerAddress(), question, Collections.<DnsRecord>emptyList(), promise);
1005     }
1006 
1007     private InetSocketAddress nextNameServerAddress() {
1008         return nameServerAddrStream.get().next();
1009     }
1010 
1011     /**
1012      * Sends a DNS query with the specified question using the specified name server list.
1013      */
1014     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
1015             InetSocketAddress nameServerAddr, DnsQuestion question) {
1016 
1017         return query0(nameServerAddr, question, EMPTY_ADDITIONALS,
1018                 ch.eventLoop().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise());
1019     }
1020 
1021     /**
1022      * Sends a DNS query with the specified question with additional records using the specified name server list.
1023      */
1024     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
1025             InetSocketAddress nameServerAddr, DnsQuestion question, Iterable<DnsRecord> additionals) {
1026 
1027         return query0(nameServerAddr, question, toArray(additionals, false),
1028                 ch.eventLoop().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise());
1029     }
1030 
1031     /**
1032      * Sends a DNS query with the specified question using the specified name server list.
1033      */
1034     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
1035             InetSocketAddress nameServerAddr, DnsQuestion question,
1036             Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
1037 
1038         return query0(nameServerAddr, question, EMPTY_ADDITIONALS, promise);
1039     }
1040 
1041     /**
1042      * Sends a DNS query with the specified question with additional records using the specified name server list.
1043      */
1044     public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
1045             InetSocketAddress nameServerAddr, DnsQuestion question,
1046             Iterable<DnsRecord> additionals,
1047             Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
1048 
1049         return query0(nameServerAddr, question, toArray(additionals, false), promise);
1050     }
1051 
1052     /**
1053      * Returns {@code true} if the {@link Throwable} was caused by an timeout or transport error.
1054      * These methods can be used on the {@link Future#cause()} that is returned by the various methods exposed by this
1055      * {@link DnsNameResolver}.
1056      */
1057     public static boolean isTransportOrTimeoutError(Throwable cause) {
1058         return cause != null && cause.getCause() instanceof DnsNameResolverException;
1059     }
1060 
1061     /**
1062      * Returns {@code true} if the {@link Throwable} was caused by an timeout.
1063      * These methods can be used on the {@link Future#cause()} that is returned by the various methods exposed by this
1064      * {@link DnsNameResolver}.
1065      */
1066     public static boolean isTimeoutError(Throwable cause) {
1067         return cause != null && cause.getCause() instanceof DnsNameResolverTimeoutException;
1068     }
1069 
1070     final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query0(
1071             InetSocketAddress nameServerAddr, DnsQuestion question,
1072             DnsRecord[] additionals,
1073             Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
1074         return query0(nameServerAddr, question, additionals, ch.newPromise(), promise);
1075     }
1076 
1077     final Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query0(
1078             InetSocketAddress nameServerAddr, DnsQuestion question,
1079             DnsRecord[] additionals,
1080             ChannelPromise writePromise,
1081             Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) {
1082         assert !writePromise.isVoid();
1083 
1084         final Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> castPromise = cast(
1085                 checkNotNull(promise, "promise"));
1086         try {
1087             new DnsQueryContext(this, nameServerAddr, question, additionals, castPromise).query(writePromise);
1088             return castPromise;
1089         } catch (Exception e) {
1090             return castPromise.setFailure(e);
1091         }
1092     }
1093 
1094     @SuppressWarnings("unchecked")
1095     private static Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> cast(Promise<?> promise) {
1096         return (Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>>) promise;
1097     }
1098 
1099     final DnsServerAddressStream newNameServerAddressStream(String hostname) {
1100         return dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
1101     }
1102 
1103     private final class DnsResponseHandler extends ChannelInboundHandlerAdapter {
1104 
1105         private final Promise<Channel> channelActivePromise;
1106 
1107         DnsResponseHandler(Promise<Channel> channelActivePromise) {
1108             this.channelActivePromise = channelActivePromise;
1109         }
1110 
1111         @Override
1112         public void channelRead(ChannelHandlerContext ctx, Object msg) {
1113             try {
1114                 final DatagramDnsResponse res = (DatagramDnsResponse) msg;
1115                 final int queryId = res.id();
1116 
1117                 if (logger.isDebugEnabled()) {
1118                     logger.debug("{} RECEIVED: [{}: {}], {}", ch, queryId, res.sender(), res);
1119                 }
1120 
1121                 final DnsQueryContext qCtx = queryContextManager.get(res.sender(), queryId);
1122                 if (qCtx == null) {
1123                     logger.warn("{} Received a DNS response with an unknown ID: {}", ch, queryId);
1124                     return;
1125                 }
1126 
1127                 qCtx.finish(res);
1128             } finally {
1129                 ReferenceCountUtil.safeRelease(msg);
1130             }
1131         }
1132 
1133         @Override
1134         public void channelActive(ChannelHandlerContext ctx) throws Exception {
1135             super.channelActive(ctx);
1136             channelActivePromise.setSuccess(ctx.channel());
1137         }
1138 
1139         @Override
1140         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
1141             logger.warn("{} Unexpected exception: ", ch, cause);
1142         }
1143     }
1144 }