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