View Javadoc
1   /*
2    * Copyright 2015 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.resolver.dns;
17  
18  import io.netty.channel.ChannelFactory;
19  import io.netty.channel.EventLoop;
20  import io.netty.channel.ReflectiveChannelFactory;
21  import io.netty.channel.socket.DatagramChannel;
22  import io.netty.channel.socket.InternetProtocolFamily;
23  import io.netty.channel.socket.SocketChannel;
24  import io.netty.channel.socket.SocketProtocolFamily;
25  import io.netty.resolver.HostsFileEntriesResolver;
26  import io.netty.resolver.ResolvedAddressTypes;
27  import io.netty.util.concurrent.Future;
28  import io.netty.util.internal.EmptyArrays;
29  import io.netty.util.internal.ObjectUtil;
30  import io.netty.util.internal.logging.InternalLogger;
31  import io.netty.util.internal.logging.InternalLoggerFactory;
32  
33  import java.net.SocketAddress;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.List;
37  
38  import static io.netty.util.internal.ObjectUtil.checkNotNull;
39  import static io.netty.util.internal.ObjectUtil.intValue;
40  
41  /**
42   * A {@link DnsNameResolver} builder.
43   */
44  public final class DnsNameResolverBuilder {
45  
46      private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolverBuilder.class);
47  
48      volatile EventLoop eventLoop;
49      private ChannelFactory<? extends DatagramChannel> channelFactory;
50      private ChannelFactory<? extends SocketChannel> socketChannelFactory;
51      private boolean retryOnTimeout;
52  
53      private DnsCache resolveCache;
54      private DnsCnameCache cnameCache;
55      private AuthoritativeDnsServerCache authoritativeDnsServerCache;
56      private SocketAddress localAddress;
57      private Integer minTtl;
58      private Integer maxTtl;
59      private Integer negativeTtl;
60      private long queryTimeoutMillis = -1;
61      private ResolvedAddressTypes resolvedAddressTypes = DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
62      private boolean completeOncePreferredResolved;
63      private boolean recursionDesired = true;
64      private int maxQueriesPerResolve = -1;
65      private boolean traceEnabled;
66      private int maxPayloadSize = 4096;
67      private boolean optResourceEnabled = true;
68      private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
69      private DnsServerAddressStreamProvider dnsServerAddressStreamProvider =
70              DnsServerAddressStreamProviders.platformDefault();
71      private DnsServerAddressStream queryDnsServerAddressStream;
72      private DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory =
73              NoopDnsQueryLifecycleObserverFactory.INSTANCE;
74      private String[] searchDomains;
75      private int ndots = -1;
76      private boolean decodeIdn = true;
77  
78      private int maxNumConsolidation;
79  
80      /**
81       * Creates a new builder.
82       */
83      public DnsNameResolverBuilder() {
84      }
85  
86      /**
87       * Creates a new builder.
88       *
89       * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS
90       * servers.
91       */
92      public DnsNameResolverBuilder(EventLoop eventLoop) {
93          eventLoop(eventLoop);
94      }
95  
96      /**
97       * Sets the {@link EventLoop} which will perform the communication with the DNS servers.
98       *
99       * @param eventLoop the {@link EventLoop}
100      * @return {@code this}
101      */
102     public DnsNameResolverBuilder eventLoop(EventLoop eventLoop) {
103         this.eventLoop = eventLoop;
104         return this;
105     }
106 
107     protected ChannelFactory<? extends DatagramChannel> channelFactory() {
108         return this.channelFactory;
109     }
110 
111     /**
112      * Sets the {@link ChannelFactory} that will create a {@link DatagramChannel}.
113      * If <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should be supported as well it is required
114      * to call the {@link #socketChannelFactory(ChannelFactory) or {@link #socketChannelType(Class)}} method.
115      *
116      * @param channelFactory the {@link ChannelFactory}
117      * @return {@code this}
118      */
119     public DnsNameResolverBuilder channelFactory(ChannelFactory<? extends DatagramChannel> channelFactory) {
120         this.channelFactory = channelFactory;
121         return this;
122     }
123 
124     /**
125      * Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type.
126      * Use as an alternative to {@link #channelFactory(ChannelFactory)}.
127      * If <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should be supported as well it is required
128      * to call the {@link #socketChannelFactory(ChannelFactory) or {@link #socketChannelType(Class)}} method.
129      *
130      * @param channelType the type
131      * @return {@code this}
132      */
133     public DnsNameResolverBuilder channelType(Class<? extends DatagramChannel> channelType) {
134         return channelFactory(new ReflectiveChannelFactory<DatagramChannel>(channelType));
135     }
136 
137     /**
138      * Sets the {@link ChannelFactory} that will create a {@link SocketChannel} for
139      * <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
140      *
141      * TCP fallback is <strong>not</strong> enabled by default and must be enabled by providing a non-null
142      * {@link ChannelFactory} for this method.
143      *
144      * @param channelFactory the {@link ChannelFactory} or {@code null}
145      *                       if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should not be supported.
146      *                       By default, TCP fallback is not enabled.
147      * @return {@code this}
148      */
149     public DnsNameResolverBuilder socketChannelFactory(ChannelFactory<? extends SocketChannel> channelFactory) {
150         return socketChannelFactory(channelFactory, false);
151     }
152 
153     /**
154      * Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type for
155      * <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
156      * Use as an alternative to {@link #socketChannelFactory(ChannelFactory)}.
157      *
158      * TCP fallback is <strong>not</strong> enabled by default and must be enabled by providing a non-null
159      * {@code channelType} for this method.
160      *
161      * @param channelType the type or {@code null} if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a>
162      *                    should not be supported. By default, TCP fallback is not enabled.
163      * @return {@code this}
164      */
165     public DnsNameResolverBuilder socketChannelType(Class<? extends SocketChannel> channelType) {
166         return socketChannelType(channelType, false);
167     }
168 
169     /**
170      * Sets the {@link ChannelFactory} that will create a {@link SocketChannel} for
171      * <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
172      *
173      * TCP fallback is <strong>not</strong> enabled by default and must be enabled by providing a non-null
174      * {@link ChannelFactory} for this method.
175      *
176      * @param channelFactory the {@link ChannelFactory} or {@code null}
177      *                       if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should not be supported.
178      *                       By default, TCP fallback is not enabled.
179      * @param retryOnTimeout if {@code true} the {@link DnsNameResolver} will also fallback to TCP if a timeout
180      *                       was detected, if {@code false} it will only try to use TCP if the response was marked
181      *                       as truncated.
182      * @return {@code this}
183      */
184     public DnsNameResolverBuilder socketChannelFactory(
185             ChannelFactory<? extends SocketChannel> channelFactory, boolean retryOnTimeout) {
186         this.socketChannelFactory = channelFactory;
187         this.retryOnTimeout = retryOnTimeout;
188         return this;
189     }
190 
191     /**
192      * Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type for
193      * <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
194      * Use as an alternative to {@link #socketChannelFactory(ChannelFactory)}.
195      *
196      * TCP fallback is <strong>not</strong> enabled by default and must be enabled by providing a non-null
197      * {@code channelType} for this method.
198      *
199      * @param channelType the type or {@code null} if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a>
200      *                    should not be supported. By default, TCP fallback is not enabled.
201      * @param retryOnTimeout if {@code true} the {@link DnsNameResolver} will also fallback to TCP if a timeout
202      *                       was detected, if {@code false} it will only try to use TCP if the response was marked
203      *                       as truncated.
204      * @return {@code this}
205      */
206     public DnsNameResolverBuilder socketChannelType(
207             Class<? extends SocketChannel> channelType, boolean retryOnTimeout) {
208         if (channelType == null) {
209             return socketChannelFactory(null, retryOnTimeout);
210         }
211         return socketChannelFactory(new ReflectiveChannelFactory<SocketChannel>(channelType), retryOnTimeout);
212     }
213 
214     /**
215      * Sets the cache for resolution results.
216      *
217      * @param resolveCache the DNS resolution results cache
218      * @return {@code this}
219      */
220     public DnsNameResolverBuilder resolveCache(DnsCache resolveCache) {
221         this.resolveCache  = resolveCache;
222         return this;
223     }
224 
225     /**
226      * Sets the cache for {@code CNAME} mappings.
227      *
228      * @param cnameCache the cache used to cache {@code CNAME} mappings for a domain.
229      * @return {@code this}
230      */
231     public DnsNameResolverBuilder cnameCache(DnsCnameCache cnameCache) {
232         this.cnameCache  = cnameCache;
233         return this;
234     }
235 
236     /**
237      * Set the factory used to generate objects which can observe individual DNS queries.
238      * @param lifecycleObserverFactory the factory used to generate objects which can observe individual DNS queries.
239      * @return {@code this}
240      */
241     public DnsNameResolverBuilder dnsQueryLifecycleObserverFactory(DnsQueryLifecycleObserverFactory
242                                                                            lifecycleObserverFactory) {
243         this.dnsQueryLifecycleObserverFactory = checkNotNull(lifecycleObserverFactory, "lifecycleObserverFactory");
244         return this;
245     }
246 
247     /**
248      * Sets the cache for authoritative NS servers
249      *
250      * @param authoritativeDnsServerCache the authoritative NS servers cache
251      * @return {@code this}
252      * @deprecated Use {@link #authoritativeDnsServerCache(AuthoritativeDnsServerCache)}
253      */
254     @Deprecated
255     public DnsNameResolverBuilder authoritativeDnsServerCache(DnsCache authoritativeDnsServerCache) {
256         this.authoritativeDnsServerCache = new AuthoritativeDnsServerCacheAdapter(authoritativeDnsServerCache);
257         return this;
258     }
259 
260     /**
261      * Sets the cache for authoritative NS servers
262      *
263      * @param authoritativeDnsServerCache the authoritative NS servers cache
264      * @return {@code this}
265      */
266     public DnsNameResolverBuilder authoritativeDnsServerCache(AuthoritativeDnsServerCache authoritativeDnsServerCache) {
267         this.authoritativeDnsServerCache = authoritativeDnsServerCache;
268         return this;
269     }
270 
271     /**
272      * Configure the address that will be used to bind too. If {@code null} the default will be used.
273      * @param localAddress the bind address
274      * @return {@code this}
275      */
276     public DnsNameResolverBuilder localAddress(SocketAddress localAddress) {
277         this.localAddress = localAddress;
278         return this;
279     }
280 
281     /**
282      * Sets the minimum and maximum TTL of the cached DNS resource records (in seconds). If the TTL of the DNS
283      * resource record returned by the DNS server is less than the minimum TTL or greater than the maximum TTL,
284      * this resolver will ignore the TTL from the DNS server and use the minimum TTL or the maximum TTL instead
285      * respectively.
286      * The default value is {@code 0} and {@link Integer#MAX_VALUE}, which practically tells this resolver to
287      * respect the TTL from the DNS server.
288      *
289      * @param minTtl the minimum TTL
290      * @param maxTtl the maximum TTL
291      * @return {@code this}
292      */
293     public DnsNameResolverBuilder ttl(int minTtl, int maxTtl) {
294         this.maxTtl = maxTtl;
295         this.minTtl = minTtl;
296         return this;
297     }
298 
299     /**
300      * Sets the TTL of the cache for the failed DNS queries (in seconds).
301      *
302      * @param negativeTtl the TTL for failed cached queries
303      * @return {@code this}
304      */
305     public DnsNameResolverBuilder negativeTtl(int negativeTtl) {
306         this.negativeTtl = negativeTtl;
307         return this;
308     }
309 
310     /**
311      * Sets the timeout of each DNS query performed by this resolver (in milliseconds).
312      * {@code 0} disables the timeout. If not set or a negative number is set, the default timeout is used.
313      *
314      * @param queryTimeoutMillis the query timeout
315      * @return {@code this}
316      */
317     public DnsNameResolverBuilder queryTimeoutMillis(long queryTimeoutMillis) {
318         this.queryTimeoutMillis = queryTimeoutMillis;
319         return this;
320     }
321 
322     /**
323      * Compute a {@link ResolvedAddressTypes} from some {@link InternetProtocolFamily}s.
324      * An empty input will return the default value, based on "java.net" System properties.
325      * Valid inputs are (), (IPv4), (IPv6), (Ipv4, IPv6) and (IPv6, IPv4).
326      *
327      * @param internetProtocolFamilies a valid sequence of {@link InternetProtocolFamily}s
328      * @return a {@link ResolvedAddressTypes}
329      * @deprecated use {@link #toResolvedAddressTypes(SocketProtocolFamily...)}
330      */
331     @Deprecated
332     public static ResolvedAddressTypes computeResolvedAddressTypes(InternetProtocolFamily... internetProtocolFamilies) {
333         if (internetProtocolFamilies == null || internetProtocolFamilies.length == 0) {
334             return DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
335         }
336         if (internetProtocolFamilies.length > 2) {
337             throw new IllegalArgumentException("No more than 2 InternetProtocolFamilies");
338         }
339         return toResolvedAddressTypes(toSocketProtocolFamilies(internetProtocolFamilies));
340     }
341 
342     private static SocketProtocolFamily[] toSocketProtocolFamilies(InternetProtocolFamily... internetProtocolFamilies) {
343         if (internetProtocolFamilies == null || internetProtocolFamilies.length == 0) {
344             return null;
345         }
346         SocketProtocolFamily[] socketProtocolFamilies = new SocketProtocolFamily[internetProtocolFamilies.length];
347         for (int i = 0; i < internetProtocolFamilies.length; i++) {
348             socketProtocolFamilies[i] = internetProtocolFamilies[i].toSocketProtocolFamily();
349         }
350         return socketProtocolFamilies;
351     }
352 
353     /**
354      * Compute a {@link ResolvedAddressTypes} from some {@link SocketProtocolFamily}s.
355      * An empty input will return the default value, based on "java.net" System properties.
356      * Valid inputs are (), (IPv4), (IPv6), (Ipv4, IPv6) and (IPv6, IPv4).
357      * @param socketProtocolFamilies a valid sequence of {@link SocketProtocolFamily}s
358      * @return a {@link ResolvedAddressTypes}
359      */
360     public static ResolvedAddressTypes toResolvedAddressTypes(SocketProtocolFamily... socketProtocolFamilies) {
361         if (socketProtocolFamilies == null || socketProtocolFamilies.length == 0) {
362             return DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
363         }
364         if (socketProtocolFamilies.length > 2) {
365             throw new IllegalArgumentException("No more than 2 socketProtocolFamilies");
366         }
367 
368         switch(socketProtocolFamilies[0]) {
369             case INET:
370                 return (socketProtocolFamilies.length >= 2
371                         && socketProtocolFamilies[1] == SocketProtocolFamily.INET6) ?
372                         ResolvedAddressTypes.IPV4_PREFERRED: ResolvedAddressTypes.IPV4_ONLY;
373             case INET6:
374                 return (socketProtocolFamilies.length >= 2
375                         && socketProtocolFamilies[1] == SocketProtocolFamily.INET) ?
376                         ResolvedAddressTypes.IPV6_PREFERRED: ResolvedAddressTypes.IPV6_ONLY;
377             default:
378                 throw new IllegalArgumentException(
379                         "Couldn't resolve ResolvedAddressTypes from InternetProtocolFamily array");
380         }
381     }
382 
383     /**
384      * Sets the list of the protocol families of the address resolved.
385      * You can use {@link DnsNameResolverBuilder#computeResolvedAddressTypes(InternetProtocolFamily...)}
386      * to get a {@link ResolvedAddressTypes} out of some {@link InternetProtocolFamily}s.
387      *
388      * @param resolvedAddressTypes the address types
389      * @return {@code this}
390      */
391     public DnsNameResolverBuilder resolvedAddressTypes(ResolvedAddressTypes resolvedAddressTypes) {
392         this.resolvedAddressTypes = resolvedAddressTypes;
393         return this;
394     }
395 
396     /**
397      * If {@code true} {@link DnsNameResolver#resolveAll(String)} will notify the returned {@link Future} as
398      * soon as all queries for the preferred address-type are complete.
399      *
400      * @param completeOncePreferredResolved {@code true} to enable, {@code false} to disable.
401      * @return {@code this}
402      */
403     public DnsNameResolverBuilder completeOncePreferredResolved(boolean completeOncePreferredResolved) {
404         this.completeOncePreferredResolved = completeOncePreferredResolved;
405         return this;
406     }
407 
408     /**
409      * Sets if this resolver has to send a DNS query with the RD (recursion desired) flag set.
410      *
411      * @param recursionDesired true if recursion is desired
412      * @return {@code this}
413      */
414     public DnsNameResolverBuilder recursionDesired(boolean recursionDesired) {
415         this.recursionDesired = recursionDesired;
416         return this;
417     }
418 
419     /**
420      * Sets the maximum allowed number of DNS queries to send when resolving a host name.
421      *
422      * @param maxQueriesPerResolve the max number of queries
423      * @return {@code this}
424      */
425     public DnsNameResolverBuilder maxQueriesPerResolve(int maxQueriesPerResolve) {
426         this.maxQueriesPerResolve = maxQueriesPerResolve;
427         return this;
428     }
429 
430     /**
431      * Sets if this resolver should generate the detailed trace information in an exception message so that
432      * it is easier to understand the cause of resolution failure.
433      *
434      * @param traceEnabled true if trace is enabled
435      * @return {@code this}
436      * @deprecated Prefer to {@linkplain #dnsQueryLifecycleObserverFactory(DnsQueryLifecycleObserverFactory) configure}
437      * a {@link LoggingDnsQueryLifeCycleObserverFactory} instead.
438      */
439     @Deprecated
440     public DnsNameResolverBuilder traceEnabled(boolean traceEnabled) {
441         this.traceEnabled = traceEnabled;
442         return this;
443     }
444 
445     /**
446      * Sets the capacity of the datagram packet buffer (in bytes).  The default value is {@code 4096} bytes.
447      *
448      * @param maxPayloadSize the capacity of the datagram packet buffer
449      * @return {@code this}
450      */
451     public DnsNameResolverBuilder maxPayloadSize(int maxPayloadSize) {
452         this.maxPayloadSize = maxPayloadSize;
453         return this;
454     }
455 
456     /**
457      * Enable the automatic inclusion of a optional records that tries to give the remote DNS server a hint about
458      * how much data the resolver can read per response. Some DNSServer may not support this and so fail to answer
459      * queries. If you find problems you may want to disable this.
460      *
461      * @param optResourceEnabled if optional records inclusion is enabled
462      * @return {@code this}
463      */
464     public DnsNameResolverBuilder optResourceEnabled(boolean optResourceEnabled) {
465         this.optResourceEnabled = optResourceEnabled;
466         return this;
467     }
468 
469     /**
470      * @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to first check
471      *                                 if the hostname is locally aliased.
472      * @return {@code this}
473      */
474     public DnsNameResolverBuilder hostsFileEntriesResolver(HostsFileEntriesResolver hostsFileEntriesResolver) {
475         this.hostsFileEntriesResolver = hostsFileEntriesResolver;
476         return this;
477     }
478 
479     protected DnsServerAddressStreamProvider nameServerProvider() {
480         return this.dnsServerAddressStreamProvider;
481     }
482 
483     /**
484      * Set the {@link DnsServerAddressStreamProvider} which is used to determine which DNS server is used to resolve
485      * each hostname.
486      * @return {@code this}.
487      */
488     public DnsNameResolverBuilder nameServerProvider(DnsServerAddressStreamProvider dnsServerAddressStreamProvider) {
489         this.dnsServerAddressStreamProvider =
490                 checkNotNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
491         return this;
492     }
493 
494     protected DnsServerAddressStream queryServerAddressStream() {
495         return this.queryDnsServerAddressStream;
496     }
497 
498     /**
499      * Set the {@link DnsServerAddressStream} which provides the server address for DNS queries.
500      * @return {@code this}.
501      */
502     public DnsNameResolverBuilder queryServerAddressStream(DnsServerAddressStream queryServerAddressStream) {
503         this.queryDnsServerAddressStream =
504                 checkNotNull(queryServerAddressStream, "queryServerAddressStream");
505         return this;
506     }
507 
508     /**
509      * Set the list of search domains of the resolver.
510      *
511      * @param searchDomains the search domains
512      * @return {@code this}
513      */
514     public DnsNameResolverBuilder searchDomains(Iterable<String> searchDomains) {
515         checkNotNull(searchDomains, "searchDomains");
516 
517         final List<String> list = new ArrayList<String>(4);
518 
519         for (String f : searchDomains) {
520             if (f == null) {
521                 break;
522             }
523 
524             // Avoid duplicate entries.
525             if (list.contains(f)) {
526                 continue;
527             }
528 
529             list.add(f);
530         }
531 
532         this.searchDomains = list.toArray(EmptyArrays.EMPTY_STRINGS);
533         return this;
534     }
535 
536   /**
537    * Set the number of dots which must appear in a name before an initial absolute query is made.
538    * The default value is {@code 1}.
539    *
540    * @param ndots the ndots value
541    * @return {@code this}
542    */
543     public DnsNameResolverBuilder ndots(int ndots) {
544         this.ndots = ndots;
545         return this;
546     }
547 
548     private DnsCache newCache() {
549         return new DefaultDnsCache(intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE), intValue(negativeTtl, 0));
550     }
551 
552     private AuthoritativeDnsServerCache newAuthoritativeDnsServerCache() {
553         return new DefaultAuthoritativeDnsServerCache(
554                 intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE),
555                 // Let us use the sane ordering as DnsNameResolver will be used when returning
556                 // nameservers from the cache.
557                 new NameServerComparator(DnsNameResolver.addressType(
558                         DnsNameResolver.preferredAddressType(resolvedAddressTypes))));
559     }
560 
561     private DnsServerAddressStream newQueryServerAddressStream(
562             DnsServerAddressStreamProvider dnsServerAddressStreamProvider) {
563         return new ThreadLocalNameServerAddressStream(dnsServerAddressStreamProvider);
564     }
565 
566     private DnsCnameCache newCnameCache() {
567         return new DefaultDnsCnameCache(
568                 intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE));
569     }
570 
571     /**
572      * Set if domain / host names should be decoded to unicode when received.
573      * See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
574      *
575      * @param decodeIdn if should get decoded
576      * @return {@code this}
577      */
578     public DnsNameResolverBuilder decodeIdn(boolean decodeIdn) {
579         this.decodeIdn = decodeIdn;
580         return this;
581     }
582 
583     /**
584      * Set the maximum size of the cache that is used to consolidate lookups for different hostnames when in-flight.
585      * This means if multiple lookups are done for the same hostname and still in-flight only one actual query will
586      * be made and the result will be cascaded to the others.
587      *
588      * @param maxNumConsolidation the maximum lookups to consolidate (different hostnames), or {@code 0} if
589      *                            no consolidation should be performed.
590      * @return {@code this}
591      */
592     public DnsNameResolverBuilder consolidateCacheSize(int maxNumConsolidation) {
593         this.maxNumConsolidation = ObjectUtil.checkPositiveOrZero(maxNumConsolidation, "maxNumConsolidation");
594         return this;
595     }
596 
597     /**
598      * Returns a new {@link DnsNameResolver} instance.
599      *
600      * @return a {@link DnsNameResolver}
601      */
602     public DnsNameResolver build() {
603         if (eventLoop == null) {
604             throw new IllegalStateException("eventLoop should be specified to build a DnsNameResolver.");
605         }
606 
607         if (resolveCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
608             logger.debug("resolveCache and TTLs are mutually exclusive. TTLs are ignored.");
609         }
610 
611         if (cnameCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
612             logger.debug("cnameCache and TTLs are mutually exclusive. TTLs are ignored.");
613         }
614 
615         if (authoritativeDnsServerCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
616             logger.debug("authoritativeDnsServerCache and TTLs are mutually exclusive. TTLs are ignored.");
617         }
618 
619         DnsCache resolveCache = this.resolveCache != null ? this.resolveCache : newCache();
620         DnsCnameCache cnameCache = this.cnameCache != null ? this.cnameCache : newCnameCache();
621         AuthoritativeDnsServerCache authoritativeDnsServerCache = this.authoritativeDnsServerCache != null ?
622                 this.authoritativeDnsServerCache : newAuthoritativeDnsServerCache();
623 
624         DnsServerAddressStream queryDnsServerAddressStream = this.queryDnsServerAddressStream != null ?
625                 this.queryDnsServerAddressStream : newQueryServerAddressStream(dnsServerAddressStreamProvider);
626 
627         return new DnsNameResolver(
628                 eventLoop,
629                 channelFactory,
630                 socketChannelFactory,
631                 retryOnTimeout,
632                 resolveCache,
633                 cnameCache,
634                 authoritativeDnsServerCache,
635                 localAddress,
636                 dnsQueryLifecycleObserverFactory,
637                 queryTimeoutMillis,
638                 resolvedAddressTypes,
639                 recursionDesired,
640                 maxQueriesPerResolve,
641                 traceEnabled,
642                 maxPayloadSize,
643                 optResourceEnabled,
644                 hostsFileEntriesResolver,
645                 dnsServerAddressStreamProvider,
646                 queryDnsServerAddressStream,
647                 searchDomains,
648                 ndots,
649                 decodeIdn,
650                 completeOncePreferredResolved,
651                 maxNumConsolidation);
652     }
653 
654     /**
655      * Creates a copy of this {@link DnsNameResolverBuilder}
656      *
657      * @return {@link DnsNameResolverBuilder}
658      */
659     public DnsNameResolverBuilder copy() {
660         DnsNameResolverBuilder copiedBuilder = new DnsNameResolverBuilder();
661 
662         if (eventLoop != null) {
663             copiedBuilder.eventLoop(eventLoop);
664         }
665 
666         if (channelFactory != null) {
667             copiedBuilder.channelFactory(channelFactory);
668         }
669 
670         copiedBuilder.socketChannelFactory(socketChannelFactory, retryOnTimeout);
671 
672         if (resolveCache != null) {
673             copiedBuilder.resolveCache(resolveCache);
674         }
675 
676         if (cnameCache != null) {
677             copiedBuilder.cnameCache(cnameCache);
678         }
679         if (maxTtl != null && minTtl != null) {
680             copiedBuilder.ttl(minTtl, maxTtl);
681         }
682 
683         if (negativeTtl != null) {
684             copiedBuilder.negativeTtl(negativeTtl);
685         }
686 
687         if (authoritativeDnsServerCache != null) {
688             copiedBuilder.authoritativeDnsServerCache(authoritativeDnsServerCache);
689         }
690 
691         if (dnsQueryLifecycleObserverFactory != null) {
692             copiedBuilder.dnsQueryLifecycleObserverFactory(dnsQueryLifecycleObserverFactory);
693         }
694 
695         copiedBuilder.queryTimeoutMillis(queryTimeoutMillis);
696         copiedBuilder.resolvedAddressTypes(resolvedAddressTypes);
697         copiedBuilder.recursionDesired(recursionDesired);
698         copiedBuilder.maxQueriesPerResolve(maxQueriesPerResolve);
699         copiedBuilder.traceEnabled(traceEnabled);
700         copiedBuilder.maxPayloadSize(maxPayloadSize);
701         copiedBuilder.optResourceEnabled(optResourceEnabled);
702         copiedBuilder.hostsFileEntriesResolver(hostsFileEntriesResolver);
703 
704         if (dnsServerAddressStreamProvider != null) {
705             copiedBuilder.nameServerProvider(dnsServerAddressStreamProvider);
706         }
707 
708         if (queryDnsServerAddressStream != null) {
709             copiedBuilder.queryServerAddressStream(queryDnsServerAddressStream);
710         }
711 
712         if (searchDomains != null) {
713             copiedBuilder.searchDomains(Arrays.asList(searchDomains));
714         }
715 
716         copiedBuilder.ndots(ndots);
717         copiedBuilder.decodeIdn(decodeIdn);
718         copiedBuilder.completeOncePreferredResolved(completeOncePreferredResolved);
719         copiedBuilder.localAddress(localAddress);
720         copiedBuilder.consolidateCacheSize(maxNumConsolidation);
721         return copiedBuilder;
722     }
723 }