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