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.ChannelFactory;
20  import io.netty.channel.ChannelFuture;
21  import io.netty.channel.ChannelFutureListener;
22  import io.netty.channel.ChannelHandlerAdapter;
23  import io.netty.channel.ChannelHandlerContext;
24  import io.netty.channel.ChannelInitializer;
25  import io.netty.channel.EventLoop;
26  import io.netty.channel.FixedRecvByteBufAllocator;
27  import io.netty.channel.ReflectiveChannelFactory;
28  import io.netty.channel.socket.DatagramChannel;
29  import io.netty.channel.socket.InternetProtocolFamily;
30  import io.netty.handler.codec.dns.DnsClass;
31  import io.netty.handler.codec.dns.DnsQueryEncoder;
32  import io.netty.handler.codec.dns.DnsQuestion;
33  import io.netty.handler.codec.dns.DnsResource;
34  import io.netty.handler.codec.dns.DnsResponse;
35  import io.netty.handler.codec.dns.DnsResponseCode;
36  import io.netty.handler.codec.dns.DnsResponseDecoder;
37  import io.netty.resolver.NameResolver;
38  import io.netty.resolver.SimpleNameResolver;
39  import io.netty.util.ReferenceCountUtil;
40  import io.netty.util.collection.IntObjectHashMap;
41  import io.netty.util.concurrent.Future;
42  import io.netty.util.concurrent.Promise;
43  import io.netty.util.concurrent.ScheduledFuture;
44  import io.netty.util.internal.OneTimeTask;
45  import io.netty.util.internal.PlatformDependent;
46  import io.netty.util.internal.SystemPropertyUtil;
47  import io.netty.util.internal.logging.InternalLogger;
48  import io.netty.util.internal.logging.InternalLoggerFactory;
49  
50  import java.net.IDN;
51  import java.net.InetSocketAddress;
52  import java.net.SocketAddress;
53  import java.util.ArrayList;
54  import java.util.Arrays;
55  import java.util.Iterator;
56  import java.util.List;
57  import java.util.Map.Entry;
58  import java.util.concurrent.ConcurrentMap;
59  import java.util.concurrent.TimeUnit;
60  import java.util.concurrent.atomic.AtomicReferenceArray;
61  
62  /**
63   * A DNS-based {@link NameResolver}.
64   */
65  public class DnsNameResolver extends SimpleNameResolver<InetSocketAddress> {
66  
67      private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class);
68  
69      static final InetSocketAddress ANY_LOCAL_ADDR = new InetSocketAddress(0);
70  
71      private static final InternetProtocolFamily[] DEFAULT_RESOLVE_ADDRESS_TYPES = new InternetProtocolFamily[2];
72  
73      static {
74          // Note that we did not use SystemPropertyUtil.getBoolean() here to emulate the behavior of JDK.
75          if ("true".equalsIgnoreCase(SystemPropertyUtil.get("java.net.preferIPv6Addresses"))) {
76              DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv6;
77              DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv4;
78              logger.debug("-Djava.net.preferIPv6Addresses: true");
79          } else {
80              DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv4;
81              DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv6;
82              logger.debug("-Djava.net.preferIPv6Addresses: false");
83          }
84      }
85  
86      private static final DnsResponseDecoder DECODER = new DnsResponseDecoder();
87      private static final DnsQueryEncoder ENCODER = new DnsQueryEncoder();
88  
89      final Iterable<InetSocketAddress> nameServerAddresses;
90      final ChannelFuture bindFuture;
91      final DatagramChannel ch;
92  
93      /**
94       * An array whose index is the ID of a DNS query and whose value is the promise of the corresponsing response. We
95       * don't use {@link IntObjectHashMap} or map-like data structure here because 64k elements are fairly small, which
96       * is only about 512KB.
97       */
98      final AtomicReferenceArray<DnsQueryContext> promises = new AtomicReferenceArray<DnsQueryContext>(65536);
99  
100     /**
101      * The cache for {@link #query(DnsQuestion)}
102      */
103     final ConcurrentMap<DnsQuestion, DnsCacheEntry> queryCache = PlatformDependent.newConcurrentHashMap();
104 
105     private final DnsResponseHandler responseHandler = new DnsResponseHandler();
106 
107     private volatile long queryTimeoutMillis = 5000;
108 
109     // The default TTL values here respect the TTL returned by the DNS server and do not cache the negative response.
110     private volatile int minTtl;
111     private volatile int maxTtl = Integer.MAX_VALUE;
112     private volatile int negativeTtl;
113     private volatile int maxTriesPerQuery = 2;
114 
115     private volatile InternetProtocolFamily[] resolveAddressTypes = DEFAULT_RESOLVE_ADDRESS_TYPES;
116     private volatile boolean recursionDesired = true;
117     private volatile int maxQueriesPerResolve = 8;
118 
119     private volatile int maxPayloadSize;
120     private volatile DnsClass maxPayloadSizeClass; // EDNS uses the CLASS field as the payload size field.
121 
122     /**
123      * Creates a new DNS-based name resolver that communicates with a single DNS server.
124      *
125      * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
126      * @param channelType the type of the {@link DatagramChannel} to create
127      * @param nameServerAddress the address of the DNS server
128      */
129     public DnsNameResolver(
130             EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
131             InetSocketAddress nameServerAddress) {
132         this(eventLoop, channelType, ANY_LOCAL_ADDR, nameServerAddress);
133     }
134 
135     /**
136      * Creates a new DNS-based name resolver that communicates with a single DNS server.
137      *
138      * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
139      * @param channelType the type of the {@link DatagramChannel} to create
140      * @param localAddress the local address of the {@link DatagramChannel}
141      * @param nameServerAddress the address of the DNS server
142      */
143     public DnsNameResolver(
144             EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
145             InetSocketAddress localAddress, InetSocketAddress nameServerAddress) {
146         this(eventLoop, new ReflectiveChannelFactory<DatagramChannel>(channelType), localAddress, nameServerAddress);
147     }
148 
149     /**
150      * Creates a new DNS-based name resolver that communicates with a single DNS server.
151      *
152      * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
153      * @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel}
154      * @param nameServerAddress the address of the DNS server
155      */
156     public DnsNameResolver(
157             EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
158             InetSocketAddress nameServerAddress) {
159         this(eventLoop, channelFactory, ANY_LOCAL_ADDR, nameServerAddress);
160     }
161 
162     /**
163      * Creates a new DNS-based name resolver that communicates with a single DNS server.
164      *
165      * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
166      * @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel}
167      * @param localAddress the local address of the {@link DatagramChannel}
168      * @param nameServerAddress the address of the DNS server
169      */
170     public DnsNameResolver(
171             EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
172             InetSocketAddress localAddress, InetSocketAddress nameServerAddress) {
173         this(eventLoop, channelFactory, localAddress, DnsServerAddresses.singleton(nameServerAddress));
174     }
175 
176     /**
177      * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
178      *
179      * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
180      * @param channelType the type of the {@link DatagramChannel} to create
181      * @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new {@link Iterator} is
182      *                            created from this {@link Iterable} to determine which DNS server should be contacted
183      *                            for the next retry in case of failure.
184      */
185     public DnsNameResolver(
186             EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
187             Iterable<InetSocketAddress> nameServerAddresses) {
188         this(eventLoop, channelType, ANY_LOCAL_ADDR, nameServerAddresses);
189     }
190 
191     /**
192      * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
193      *
194      * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
195      * @param channelType the type of the {@link DatagramChannel} to create
196      * @param localAddress the local address of the {@link DatagramChannel}
197      * @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new {@link Iterator} is
198      *                            created from this {@link Iterable} to determine which DNS server should be contacted
199      *                            for the next retry in case of failure.
200      */
201     public DnsNameResolver(
202             EventLoop eventLoop, Class<? extends DatagramChannel> channelType,
203             InetSocketAddress localAddress, Iterable<InetSocketAddress> nameServerAddresses) {
204         this(eventLoop, new ReflectiveChannelFactory<DatagramChannel>(channelType), localAddress, nameServerAddresses);
205     }
206 
207     /**
208      * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
209      *
210      * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
211      * @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel}
212      * @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new {@link Iterator} is
213      *                            created from this {@link Iterable} to determine which DNS server should be contacted
214      *                            for the next retry in case of failure.
215      */
216     public DnsNameResolver(
217             EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
218             Iterable<InetSocketAddress> nameServerAddresses) {
219         this(eventLoop, channelFactory, ANY_LOCAL_ADDR, nameServerAddresses);
220     }
221 
222     /**
223      * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers.
224      *
225      * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers
226      * @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel}
227      * @param localAddress the local address of the {@link DatagramChannel}
228      * @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new {@link Iterator} is
229      *                            created from this {@link Iterable} to determine which DNS server should be contacted
230      *                            for the next retry in case of failure.
231      */
232     public DnsNameResolver(
233             EventLoop eventLoop, ChannelFactory<? extends DatagramChannel> channelFactory,
234             InetSocketAddress localAddress, Iterable<InetSocketAddress> nameServerAddresses) {
235 
236         super(eventLoop);
237 
238         if (channelFactory == null) {
239             throw new NullPointerException("channelFactory");
240         }
241         if (nameServerAddresses == null) {
242             throw new NullPointerException("nameServerAddresses");
243         }
244         if (!nameServerAddresses.iterator().hasNext()) {
245             throw new NullPointerException("nameServerAddresses is empty");
246         }
247         if (localAddress == null) {
248             throw new NullPointerException("localAddress");
249         }
250 
251         this.nameServerAddresses = nameServerAddresses;
252         bindFuture = newChannel(channelFactory, localAddress);
253         ch = (DatagramChannel) bindFuture.channel();
254 
255         setMaxPayloadSize(4096);
256     }
257 
258     private ChannelFuture newChannel(
259             ChannelFactory<? extends DatagramChannel> channelFactory, InetSocketAddress localAddress) {
260 
261         Bootstrap b = new Bootstrap();
262         b.group(executor());
263         b.channelFactory(channelFactory);
264         b.handler(new ChannelInitializer<DatagramChannel>() {
265             @Override
266             protected void initChannel(DatagramChannel ch) throws Exception {
267                 ch.pipeline().addLast(DECODER, ENCODER, responseHandler);
268             }
269         });
270 
271         ChannelFuture bindFuture = b.bind(localAddress);
272         bindFuture.channel().closeFuture().addListener(new ChannelFutureListener() {
273             @Override
274             public void operationComplete(ChannelFuture future) throws Exception {
275                 clearCache();
276             }
277         });
278 
279         return bindFuture;
280     }
281 
282     /**
283      * Returns the minimum TTL of the cached DNS resource records (in seconds).
284      *
285      * @see #maxTtl()
286      * @see #setTtl(int, int)
287      */
288     public int minTtl() {
289         return minTtl;
290     }
291 
292     /**
293      * Returns the maximum TTL of the cached DNS resource records (in seconds).
294      *
295      * @see #minTtl()
296      * @see #setTtl(int, int)
297      */
298     public int maxTtl() {
299         return maxTtl;
300     }
301 
302     /**
303      * Sets the minimum and maximum TTL of the cached DNS resource records (in seconds). If the TTL of the DNS resource
304      * record returned by the DNS server is less than the minimum TTL or greater than the maximum TTL, this resolver
305      * will ignore the TTL from the DNS server and use the minimum TTL or the maximum TTL instead respectively.
306      * The default value is {@code 0} and {@link Integer#MAX_VALUE}, which practically tells this resolver to respect
307      * the TTL from the DNS server.
308      *
309      * @return {@code this}
310      *
311      * @see #minTtl()
312      * @see #maxTtl()
313      */
314     public DnsNameResolver setTtl(int minTtl, int maxTtl) {
315         if (minTtl < 0) {
316             throw new IllegalArgumentException("minTtl: " + minTtl + " (expected: >= 0)");
317         }
318         if (maxTtl < 0) {
319             throw new IllegalArgumentException("maxTtl: " + maxTtl + " (expected: >= 0)");
320         }
321         if (minTtl > maxTtl) {
322             throw new IllegalArgumentException(
323                     "minTtl: " + minTtl + ", maxTtl: " + maxTtl + " (expected: 0 <= minTtl <= maxTtl)");
324         }
325 
326         this.maxTtl = maxTtl;
327         this.minTtl = minTtl;
328 
329         return this;
330     }
331 
332     /**
333      * Returns the TTL of the cache for the failed DNS queries (in seconds).  The default value is {@code 0}, which
334      * disables the cache for negative results.
335      *
336      * @see #setNegativeTtl(int)
337      */
338     public int negativeTtl() {
339         return negativeTtl;
340     }
341 
342     /**
343      * Sets the TTL of the cache for the failed DNS queries (in seconds).
344      *
345      * @return {@code this}
346      *
347      * @see #negativeTtl()
348      */
349     public DnsNameResolver setNegativeTtl(int negativeTtl) {
350         if (negativeTtl < 0) {
351             throw new IllegalArgumentException("negativeTtl: " + negativeTtl + " (expected: >= 0)");
352         }
353 
354         this.negativeTtl = negativeTtl;
355 
356         return this;
357     }
358 
359     /**
360      * Returns the timeout of each DNS query performed by this resolver (in milliseconds).
361      * The default value is 5 seconds.
362      *
363      * @see #setQueryTimeoutMillis(long)
364      */
365     public long queryTimeoutMillis() {
366         return queryTimeoutMillis;
367     }
368 
369     /**
370      * Sets the timeout of each DNS query performed by this resolver (in milliseconds).
371      *
372      * @return {@code this}
373      *
374      * @see #queryTimeoutMillis()
375      */
376     public DnsNameResolver setQueryTimeoutMillis(long queryTimeoutMillis) {
377         if (queryTimeoutMillis < 0) {
378             throw new IllegalArgumentException("queryTimeoutMillis: " + queryTimeoutMillis + " (expected: >= 0)");
379         }
380 
381         this.queryTimeoutMillis = queryTimeoutMillis;
382 
383         return this;
384     }
385 
386     /**
387      * Returns the maximum number of tries for each query. The default value is 2 times.
388      *
389      * @see #setMaxTriesPerQuery(int)
390      */
391     public int maxTriesPerQuery() {
392         return maxTriesPerQuery;
393     }
394 
395     /**
396      * Sets the maximum number of tries for each query.
397      *
398      * @return {@code this}
399      *
400      * @see #maxTriesPerQuery()
401      */
402     public DnsNameResolver setMaxTriesPerQuery(int maxTriesPerQuery) {
403         if (maxTriesPerQuery < 1) {
404             throw new IllegalArgumentException("maxTries: " + maxTriesPerQuery + " (expected: > 0)");
405         }
406 
407         this.maxTriesPerQuery = maxTriesPerQuery;
408 
409         return this;
410     }
411 
412     /**
413      * Returns the list of the protocol families of the address resolved by {@link #resolve(SocketAddress)}
414      * in the order of preference.
415      * The default value depends on the value of the system property {@code "java.net.preferIPv6Addresses"}.
416      *
417      * @see #setResolveAddressTypes(InternetProtocolFamily...)
418      */
419     public List<InternetProtocolFamily> resolveAddressTypes() {
420         return Arrays.asList(resolveAddressTypes);
421     }
422 
423     InternetProtocolFamily[] resolveAddressTypesUnsafe() {
424         return resolveAddressTypes;
425     }
426 
427     /**
428      * Sets the list of the protocol families of the address resolved by {@link #resolve(SocketAddress)}.
429      * Usually, both {@link InternetProtocolFamily#IPv4} and {@link InternetProtocolFamily#IPv6} are specified in the
430      * order of preference.  To enforce the resolve to retrieve the address of a specific protocol family, specify
431      * only a single {@link InternetProtocolFamily}.
432      *
433      * @return {@code this}
434      *
435      * @see #resolveAddressTypes()
436      */
437     public DnsNameResolver setResolveAddressTypes(InternetProtocolFamily... resolveAddressTypes) {
438         if (resolveAddressTypes == null) {
439             throw new NullPointerException("resolveAddressTypes");
440         }
441 
442         final List<InternetProtocolFamily> list =
443                 new ArrayList<InternetProtocolFamily>(InternetProtocolFamily.values().length);
444 
445         for (InternetProtocolFamily f: resolveAddressTypes) {
446             if (f == null) {
447                 break;
448             }
449 
450             // Avoid duplicate entries.
451             if (list.contains(f)) {
452                 continue;
453             }
454 
455             list.add(f);
456         }
457 
458         if (list.isEmpty()) {
459             throw new IllegalArgumentException("no protocol family specified");
460         }
461 
462         this.resolveAddressTypes = list.toArray(new InternetProtocolFamily[list.size()]);
463 
464         return this;
465     }
466 
467     /**
468      * Sets the list of the protocol families of the address resolved by {@link #resolve(SocketAddress)}.
469      * Usually, both {@link InternetProtocolFamily#IPv4} and {@link InternetProtocolFamily#IPv6} are specified in the
470      * order of preference.  To enforce the resolve to retrieve the address of a specific protocol family, specify
471      * only a single {@link InternetProtocolFamily}.
472      *
473      * @return {@code this}
474      *
475      * @see #resolveAddressTypes()
476      */
477     public DnsNameResolver setResolveAddressTypes(Iterable<InternetProtocolFamily> resolveAddressTypes) {
478         if (resolveAddressTypes == null) {
479             throw new NullPointerException("resolveAddressTypes");
480         }
481 
482         final List<InternetProtocolFamily> list =
483                 new ArrayList<InternetProtocolFamily>(InternetProtocolFamily.values().length);
484 
485         for (InternetProtocolFamily f: resolveAddressTypes) {
486             if (f == null) {
487                 break;
488             }
489 
490             // Avoid duplicate entries.
491             if (list.contains(f)) {
492                 continue;
493             }
494 
495             list.add(f);
496         }
497 
498         if (list.isEmpty()) {
499             throw new IllegalArgumentException("no protocol family specified");
500         }
501 
502         this.resolveAddressTypes = list.toArray(new InternetProtocolFamily[list.size()]);
503 
504         return this;
505     }
506 
507     /**
508      * Returns {@code true} if and only if this resolver sends a DNS query with the RD (recursion desired) flag set.
509      * The default value is {@code true}.
510      *
511      * @see #setRecursionDesired(boolean)
512      */
513     public boolean isRecursionDesired() {
514         return recursionDesired;
515     }
516 
517     /**
518      * Sets if this resolver has to send a DNS query with the RD (recursion desired) flag set.
519      *
520      * @return {@code this}
521      *
522      * @see #isRecursionDesired()
523      */
524     public DnsNameResolver setRecursionDesired(boolean recursionDesired) {
525         this.recursionDesired = recursionDesired;
526         return this;
527     }
528 
529     /**
530      * Returns the maximum allowed number of DNS queries to send when resolving a host name.
531      * The default value is {@code 8}.
532      *
533      * @see #setMaxQueriesPerResolve(int)
534      */
535     public int maxQueriesPerResolve() {
536         return maxQueriesPerResolve;
537     }
538 
539     /**
540      * Sets the maximum allowed number of DNS queries to send when resolving a host name.
541      *
542      * @return {@code this}
543      *
544      * @see #maxQueriesPerResolve()
545      */
546     public DnsNameResolver setMaxQueriesPerResolve(int maxQueriesPerResolve) {
547         if (maxQueriesPerResolve <= 0) {
548             throw new IllegalArgumentException("maxQueriesPerResolve: " + maxQueriesPerResolve + " (expected: > 0)");
549         }
550 
551         this.maxQueriesPerResolve = maxQueriesPerResolve;
552 
553         return this;
554     }
555 
556     /**
557      * Returns the capacity of the datagram packet buffer (in bytes).  The default value is {@code 4096} bytes.
558      *
559      * @see #setMaxPayloadSize(int)
560      */
561     public int maxPayloadSize() {
562         return maxPayloadSize;
563     }
564 
565     /**
566      * Sets the capacity of the datagram packet buffer (in bytes).  The default value is {@code 4096} bytes.
567      *
568      * @return {@code this}
569      *
570      * @see #maxPayloadSize()
571      */
572     public DnsNameResolver setMaxPayloadSize(int maxPayloadSize) {
573         if (maxPayloadSize <= 0) {
574             throw new IllegalArgumentException("maxPayloadSize: " + maxPayloadSize + " (expected: > 0)");
575         }
576 
577         if (this.maxPayloadSize == maxPayloadSize) {
578             // Same value; no need to instantiate DnsClass and RecvByteBufAllocator again.
579             return this;
580         }
581 
582         this.maxPayloadSize = maxPayloadSize;
583         maxPayloadSizeClass = DnsClass.valueOf(maxPayloadSize);
584         ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize));
585 
586         return this;
587     }
588 
589     DnsClass maxPayloadSizeClass() {
590         return maxPayloadSizeClass;
591     }
592 
593     /**
594      * Clears all the DNS resource records cached by this resolver.
595      *
596      * @return {@code this}
597      *
598      * @see #clearCache(DnsQuestion)
599      */
600     public DnsNameResolver clearCache() {
601         for (Iterator<Entry<DnsQuestion, DnsCacheEntry>> i = queryCache.entrySet().iterator(); i.hasNext();) {
602             Entry<DnsQuestion, DnsCacheEntry> e = i.next();
603             i.remove();
604             e.getValue().release();
605         }
606 
607         return this;
608     }
609 
610     /**
611      * Clears the DNS resource record of the specified DNS question from the cache of this resolver.
612      */
613     public boolean clearCache(DnsQuestion question) {
614         DnsCacheEntry e = queryCache.remove(question);
615         if (e != null) {
616             e.release();
617             return true;
618         } else {
619             return false;
620         }
621     }
622 
623     /**
624      * Closes the internal datagram channel used for sending and receiving DNS messages, and clears all DNS resource
625      * records from the cache. Attempting to send a DNS query or to resolve a domain name will fail once this method
626      * has been called.
627      */
628     @Override
629     public void close() {
630         ch.close();
631     }
632 
633     @Override
634     protected EventLoop executor() {
635         return (EventLoop) super.executor();
636     }
637 
638     @Override
639     protected boolean doIsResolved(InetSocketAddress address) {
640         return !address.isUnresolved();
641     }
642 
643     @Override
644     protected void doResolve(InetSocketAddress unresolvedAddress, Promise<InetSocketAddress> promise) throws Exception {
645         final String hostname = IDN.toASCII(hostname(unresolvedAddress));
646         final int port = unresolvedAddress.getPort();
647 
648         final DnsNameResolverContext ctx = new DnsNameResolverContext(this, hostname, port, promise);
649 
650         ctx.resolve();
651     }
652 
653     private static String hostname(InetSocketAddress addr) {
654         // InetSocketAddress.getHostString() is available since Java 7.
655         if (PlatformDependent.javaVersion() < 7) {
656             return addr.getHostName();
657         } else {
658             return addr.getHostString();
659         }
660     }
661 
662     /**
663      * Sends a DNS query with the specified question.
664      */
665     public Future<DnsResponse> query(DnsQuestion question) {
666         return query(nameServerAddresses, question);
667     }
668 
669     /**
670      * Sends a DNS query with the specified question.
671      */
672     public Future<DnsResponse> query(DnsQuestion question, Promise<DnsResponse> promise) {
673         return query(nameServerAddresses, question, promise);
674     }
675 
676     /**
677      * Sends a DNS query with the specified question using the specified name server list.
678      */
679     public Future<DnsResponse> query(Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question) {
680         if (nameServerAddresses == null) {
681             throw new NullPointerException("nameServerAddresses");
682         }
683         if (question == null) {
684             throw new NullPointerException("question");
685         }
686 
687         final EventLoop eventLoop = ch.eventLoop();
688         final DnsCacheEntry cachedResult = queryCache.get(question);
689         if (cachedResult != null) {
690             if (cachedResult.response != null) {
691                 return eventLoop.newSucceededFuture(cachedResult.response.retain());
692             } else {
693                 return eventLoop.newFailedFuture(cachedResult.cause);
694             }
695         } else {
696             return query0(nameServerAddresses, question, eventLoop.<DnsResponse>newPromise());
697         }
698     }
699 
700     /**
701      * Sends a DNS query with the specified question using the specified name server list.
702      */
703     public Future<DnsResponse> query(
704             Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question, Promise<DnsResponse> promise) {
705 
706         if (nameServerAddresses == null) {
707             throw new NullPointerException("nameServerAddresses");
708         }
709         if (question == null) {
710             throw new NullPointerException("question");
711         }
712         if (promise == null) {
713             throw new NullPointerException("promise");
714         }
715 
716         final DnsCacheEntry cachedResult = queryCache.get(question);
717         if (cachedResult != null) {
718             if (cachedResult.response != null) {
719                 return promise.setSuccess(cachedResult.response.retain());
720             } else {
721                 return promise.setFailure(cachedResult.cause);
722             }
723         } else {
724             return query0(nameServerAddresses, question, promise);
725         }
726     }
727 
728     private Future<DnsResponse> query0(
729             Iterable<InetSocketAddress> nameServerAddresses, DnsQuestion question, Promise<DnsResponse> promise) {
730 
731         try {
732             new DnsQueryContext(this, nameServerAddresses, question, promise).query();
733             return promise;
734         } catch (Exception e) {
735             return promise.setFailure(e);
736         }
737     }
738 
739     void cache(final DnsQuestion question, DnsCacheEntry entry, long delaySeconds) {
740         DnsCacheEntry oldEntry = queryCache.put(question, entry);
741         if (oldEntry != null) {
742             oldEntry.release();
743         }
744 
745         boolean scheduled = false;
746         try {
747             entry.expirationFuture = ch.eventLoop().schedule(new OneTimeTask() {
748                 @Override
749                 public void run() {
750                     clearCache(question);
751                 }
752             }, delaySeconds, TimeUnit.SECONDS);
753 
754             scheduled = true;
755         } finally {
756             if (!scheduled) {
757                 // If failed to schedule the expiration task,
758                 // remove the entry from the cache so that it does not leak.
759                 clearCache(question);
760                 entry.release();
761             }
762         }
763     }
764 
765     private final class DnsResponseHandler extends ChannelHandlerAdapter {
766         @Override
767         public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
768             try {
769                 final DnsResponse res = (DnsResponse) msg;
770                 final int queryId = res.header().id();
771 
772                 if (logger.isDebugEnabled()) {
773                     logger.debug("{} RECEIVED: [{}: {}], {}", ch, queryId, res.sender(), res);
774                 }
775 
776                 final DnsQueryContext qCtx = promises.get(queryId);
777 
778                 if (qCtx == null) {
779                     if (logger.isWarnEnabled()) {
780                         logger.warn("Received a DNS response with an unknown ID: {}", queryId);
781                     }
782                     return;
783                 }
784 
785                 final List<DnsQuestion> questions = res.questions();
786                 if (questions.size() != 1) {
787                     logger.warn("Received a DNS response with invalid number of questions: {}", res);
788                     return;
789                 }
790 
791                 final DnsQuestion q = qCtx.question();
792                 if (!q.equals(questions.get(0))) {
793                     logger.warn("Received a mismatching DNS response: {}", res);
794                     return;
795                 }
796 
797                 // Cancel the timeout task.
798                 final ScheduledFuture<?> timeoutFuture = qCtx.timeoutFuture();
799                 if (timeoutFuture != null) {
800                     timeoutFuture.cancel(false);
801                 }
802 
803                 if (res.header().responseCode() == DnsResponseCode.NOERROR) {
804                     cache(q, res);
805                     promises.set(queryId, null);
806 
807                     Promise<DnsResponse> qPromise = qCtx.promise();
808                     if (qPromise.setUncancellable()) {
809                         qPromise.setSuccess(res.retain());
810                     }
811                 } else {
812                     qCtx.retry(res.sender(),
813                                "response code: " + res.header().responseCode() +
814                                " with " + res.answers().size() + " answer(s) and " +
815                                res.authorityResources().size() + " authority resource(s)");
816                 }
817             } finally {
818                 ReferenceCountUtil.safeRelease(msg);
819             }
820         }
821 
822         private void cache(DnsQuestion question, DnsResponse res) {
823             final int maxTtl = maxTtl();
824             if (maxTtl == 0) {
825                 return;
826             }
827 
828             long ttl = Long.MAX_VALUE;
829             // Find the smallest TTL value returned by the server.
830             for (DnsResource r: res.answers()) {
831                 long rTtl = r.timeToLive();
832                 if (ttl > rTtl) {
833                     ttl = rTtl;
834                 }
835             }
836 
837             // Ensure that the found TTL is between minTtl and maxTtl.
838             ttl = Math.max(minTtl(), Math.min(maxTtl, ttl));
839 
840             DnsNameResolver.this.cache(question, new DnsCacheEntry(res), ttl);
841         }
842 
843         @Override
844         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
845             logger.warn("Unexpected exception: ", cause);
846         }
847     }
848 
849     static final class DnsCacheEntry {
850         final DnsResponse response;
851         final Throwable cause;
852         volatile ScheduledFuture<?> expirationFuture;
853 
854         DnsCacheEntry(DnsResponse response) {
855             this.response = response.retain();
856             cause = null;
857         }
858 
859         DnsCacheEntry(Throwable cause) {
860             this.cause = cause;
861             response = null;
862         }
863 
864         void release() {
865             DnsResponse response = this.response;
866             if (response != null) {
867                 ReferenceCountUtil.safeRelease(response);
868             }
869 
870             ScheduledFuture<?> expirationFuture = this.expirationFuture;
871             if (expirationFuture != null) {
872                 expirationFuture.cancel(false);
873             }
874         }
875     }
876 }