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.netty5.resolver.dns;
17  
18  import io.netty5.channel.ChannelFactory;
19  import io.netty5.channel.EventLoop;
20  import io.netty5.channel.ReflectiveChannelFactory;
21  import io.netty5.channel.socket.DatagramChannel;
22  import io.netty5.channel.socket.SocketChannel;
23  import io.netty5.resolver.HostsFileEntriesResolver;
24  import io.netty5.resolver.ResolvedAddressTypes;
25  import io.netty5.util.concurrent.Future;
26  
27  import java.net.Inet4Address;
28  import java.net.Inet6Address;
29  import java.net.ProtocolFamily;
30  import java.net.SocketAddress;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.List;
34  
35  import static io.netty5.util.NetUtil.addressType;
36  import static io.netty5.util.NetUtil.isFamilySupported;
37  import static io.netty5.util.internal.ObjectUtil.intValue;
38  import static java.util.Objects.requireNonNull;
39  
40  /**
41   * A {@link DnsNameResolver} builder.
42   */
43  public final class DnsNameResolverBuilder {
44      volatile EventLoop eventLoop;
45      private ChannelFactory<? extends DatagramChannel> channelFactory;
46      private ChannelFactory<? extends SocketChannel> socketChannelFactory;
47      private DnsCache resolveCache;
48      private DnsCnameCache cnameCache;
49      private AuthoritativeDnsServerCache authoritativeDnsServerCache;
50      private SocketAddress localAddress;
51      private Integer minTtl;
52      private Integer maxTtl;
53      private Integer negativeTtl;
54      private long queryTimeoutMillis = -1;
55      private ResolvedAddressTypes resolvedAddressTypes = DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
56      private boolean completeOncePreferredResolved;
57      private boolean recursionDesired = true;
58      private int maxQueriesPerResolve = -1;
59      private int maxPayloadSize = 4096;
60      private boolean optResourceEnabled = true;
61      private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
62      private DnsServerAddressStreamProvider dnsServerAddressStreamProvider =
63              DnsServerAddressStreamProviders.platformDefault();
64      private DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory =
65              NoopDnsQueryLifecycleObserverFactory.INSTANCE;
66      private String[] searchDomains;
67      private int ndots = -1;
68      private boolean decodeIdn = true;
69  
70      /**
71       * Creates a new builder.
72       */
73      public DnsNameResolverBuilder() {
74      }
75  
76      /**
77       * Creates a new builder.
78       *
79       * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS
80       * servers.
81       */
82      public DnsNameResolverBuilder(EventLoop eventLoop) {
83          eventLoop(eventLoop);
84      }
85  
86      /**
87       * Sets the {@link EventLoop} which will perform the communication with the DNS servers.
88       *
89       * @param eventLoop the {@link EventLoop}
90       * @return {@code this}
91       */
92      public DnsNameResolverBuilder eventLoop(EventLoop eventLoop) {
93          this.eventLoop = eventLoop;
94          return this;
95      }
96  
97      protected ChannelFactory<? extends DatagramChannel> channelFactory() {
98          return this.channelFactory;
99      }
100 
101     /**
102      * Sets the {@link ChannelFactory} that will create a {@link DatagramChannel}.
103      *
104      * @param channelFactory the {@link ChannelFactory}
105      * @return {@code this}
106      */
107     public DnsNameResolverBuilder channelFactory(ChannelFactory<? extends DatagramChannel> channelFactory) {
108         this.channelFactory = channelFactory;
109         return this;
110     }
111 
112     /**
113      * Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type.
114      * Use as an alternative to {@link #channelFactory(ChannelFactory)}.
115      *
116      * @param channelType the type
117      * @return {@code this}
118      */
119     public DnsNameResolverBuilder channelType(Class<? extends DatagramChannel> channelType) {
120         return channelFactory(new ReflectiveChannelFactory<DatagramChannel>(channelType));
121     }
122 
123     /**
124      * Sets the {@link ChannelFactory} that will create a {@link SocketChannel} for
125      * <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
126      *
127      * @param channelFactory the {@link ChannelFactory} or {@code null}
128      *                       if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should not be supported.
129      * @return {@code this}
130      */
131     public DnsNameResolverBuilder socketChannelFactory(ChannelFactory<? extends SocketChannel> channelFactory) {
132         this.socketChannelFactory = channelFactory;
133         return this;
134     }
135 
136     /**
137      * Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type for
138      * <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
139      * Use as an alternative to {@link #socketChannelFactory(ChannelFactory)}.
140      *
141      * @param channelType the type or {@code null} if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a>
142      *                    should not be supported.
143      * @return {@code this}
144      */
145     public DnsNameResolverBuilder socketChannelType(Class<? extends SocketChannel> channelType) {
146         if (channelType == null) {
147             return socketChannelFactory(null);
148         }
149         return socketChannelFactory(new ReflectiveChannelFactory<SocketChannel>(channelType));
150     }
151 
152     /**
153      * Sets the cache for resolution results.
154      *
155      * @param resolveCache the DNS resolution results cache
156      * @return {@code this}
157      */
158     public DnsNameResolverBuilder resolveCache(DnsCache resolveCache) {
159         this.resolveCache  = resolveCache;
160         return this;
161     }
162 
163     /**
164      * Sets the cache for {@code CNAME} mappings.
165      *
166      * @param cnameCache the cache used to cache {@code CNAME} mappings for a domain.
167      * @return {@code this}
168      */
169     public DnsNameResolverBuilder cnameCache(DnsCnameCache cnameCache) {
170         this.cnameCache  = cnameCache;
171         return this;
172     }
173 
174     /**
175      * Set the factory used to generate objects which can observe individual DNS queries.
176      * @param lifecycleObserverFactory the factory used to generate objects which can observe individual DNS queries.
177      * @return {@code this}
178      */
179     public DnsNameResolverBuilder dnsQueryLifecycleObserverFactory(DnsQueryLifecycleObserverFactory
180                                                                            lifecycleObserverFactory) {
181         this.dnsQueryLifecycleObserverFactory = requireNonNull(lifecycleObserverFactory, "lifecycleObserverFactory");
182         return this;
183     }
184 
185     /**
186      * Sets the cache for authoritative NS servers
187      *
188      * @param authoritativeDnsServerCache the authoritative NS servers cache
189      * @return {@code this}
190      * @deprecated Use {@link #authoritativeDnsServerCache(AuthoritativeDnsServerCache)}
191      */
192     @Deprecated
193     public DnsNameResolverBuilder authoritativeDnsServerCache(DnsCache authoritativeDnsServerCache) {
194         this.authoritativeDnsServerCache = new AuthoritativeDnsServerCacheAdapter(authoritativeDnsServerCache);
195         return this;
196     }
197 
198     /**
199      * Sets the cache for authoritative NS servers
200      *
201      * @param authoritativeDnsServerCache the authoritative NS servers cache
202      * @return {@code this}
203      */
204     public DnsNameResolverBuilder authoritativeDnsServerCache(AuthoritativeDnsServerCache authoritativeDnsServerCache) {
205         this.authoritativeDnsServerCache = authoritativeDnsServerCache;
206         return this;
207     }
208 
209     /**
210      * Configure the address that will be used to bind too. If {@code null} the default will be used.
211      * @param localAddress the bind address
212      * @return {@code this}
213      */
214     public DnsNameResolverBuilder localAddress(SocketAddress localAddress) {
215         this.localAddress = localAddress;
216         return this;
217     }
218 
219     /**
220      * Sets the minimum and maximum TTL of the cached DNS resource records (in seconds). If the TTL of the DNS
221      * resource record returned by the DNS server is less than the minimum TTL or greater than the maximum TTL,
222      * this resolver will ignore the TTL from the DNS server and use the minimum TTL or the maximum TTL instead
223      * respectively.
224      * The default value is {@code 0} and {@link Integer#MAX_VALUE}, which practically tells this resolver to
225      * respect the TTL from the DNS server.
226      *
227      * @param minTtl the minimum TTL
228      * @param maxTtl the maximum TTL
229      * @return {@code this}
230      */
231     public DnsNameResolverBuilder ttl(int minTtl, int maxTtl) {
232         this.maxTtl = maxTtl;
233         this.minTtl = minTtl;
234         return this;
235     }
236 
237     /**
238      * Sets the TTL of the cache for the failed DNS queries (in seconds).
239      *
240      * @param negativeTtl the TTL for failed cached queries
241      * @return {@code this}
242      */
243     public DnsNameResolverBuilder negativeTtl(int negativeTtl) {
244         this.negativeTtl = negativeTtl;
245         return this;
246     }
247 
248     /**
249      * Sets the timeout of each DNS query performed by this resolver (in milliseconds).
250      *
251      * @param queryTimeoutMillis the query timeout
252      * @return {@code this}
253      */
254     public DnsNameResolverBuilder queryTimeoutMillis(long queryTimeoutMillis) {
255         this.queryTimeoutMillis = queryTimeoutMillis;
256         return this;
257     }
258 
259     /**
260      * Compute a {@link ResolvedAddressTypes} from some {@link ProtocolFamily}s.
261      * An empty input will return the default value, based on "java.net" System properties.
262      * Valid inputs are (), (IPv4), (IPv6), (Ipv4, IPv6) and (IPv6, IPv4).
263      * @param protocolFamilies a valid sequence of {@link ProtocolFamily}s
264      * @return a {@link ResolvedAddressTypes}
265      */
266     public static ResolvedAddressTypes computeResolvedAddressTypes(ProtocolFamily... protocolFamilies) {
267         if (protocolFamilies == null || protocolFamilies.length == 0) {
268             return DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
269         }
270         if (protocolFamilies.length > 2) {
271             throw new IllegalArgumentException("No more than 2 ProtocolFamilies");
272         }
273 
274         ProtocolFamily first = protocolFamilies[0];
275         if (isFamilySupported(Inet4Address.class, first)) {
276             if (protocolFamilies.length == 1) {
277                 return ResolvedAddressTypes.IPV4_ONLY;
278             } else {
279                 ProtocolFamily second = protocolFamilies[1];
280                 if (isFamilySupported(Inet4Address.class, second)) {
281                     return ResolvedAddressTypes.IPV4_ONLY;
282                 }
283                 if (isFamilySupported(Inet6Address.class, second)) {
284                     return ResolvedAddressTypes.IPV4_PREFERRED;
285                 }
286             }
287         } else if (isFamilySupported(Inet6Address.class, first)) {
288             if (protocolFamilies.length == 1) {
289                 return ResolvedAddressTypes.IPV6_ONLY;
290             } else {
291                 ProtocolFamily second = protocolFamilies[1];
292                 if (isFamilySupported(Inet6Address.class, second)) {
293                     return ResolvedAddressTypes.IPV6_ONLY;
294                 }
295                 if (isFamilySupported(Inet4Address.class, second)) {
296                     return ResolvedAddressTypes.IPV6_PREFERRED;
297                 }
298             }
299         }
300         throw new IllegalArgumentException("Couldn't resolve ResolvedAddressTypes from ProtocolFamily array: " +
301                 Arrays.toString(protocolFamilies));
302     }
303 
304     /**
305      * Sets the list of the protocol families of the address resolved.
306      * You can use {@link DnsNameResolverBuilder#computeResolvedAddressTypes(java.net.ProtocolFamily...)}
307      * to get a {@link ResolvedAddressTypes} out of some {@link ProtocolFamily}s.
308      *
309      * @param resolvedAddressTypes the address types
310      * @return {@code this}
311      */
312     public DnsNameResolverBuilder resolvedAddressTypes(ResolvedAddressTypes resolvedAddressTypes) {
313         this.resolvedAddressTypes = resolvedAddressTypes;
314         return this;
315     }
316 
317     /**
318      * If {@code true} {@link DnsNameResolver#resolveAll(String)} will notify the returned {@link Future} as
319      * soon as all queries for the preferred address-type are complete.
320      *
321      * @param completeOncePreferredResolved {@code true} to enable, {@code false} to disable.
322      * @return {@code this}
323      */
324     public DnsNameResolverBuilder completeOncePreferredResolved(boolean completeOncePreferredResolved) {
325         this.completeOncePreferredResolved = completeOncePreferredResolved;
326         return this;
327     }
328 
329     /**
330      * Sets if this resolver has to send a DNS query with the RD (recursion desired) flag set.
331      *
332      * @param recursionDesired true if recursion is desired
333      * @return {@code this}
334      */
335     public DnsNameResolverBuilder recursionDesired(boolean recursionDesired) {
336         this.recursionDesired = recursionDesired;
337         return this;
338     }
339 
340     /**
341      * Sets the maximum allowed number of DNS queries to send when resolving a host name.
342      *
343      * @param maxQueriesPerResolve the max number of queries
344      * @return {@code this}
345      */
346     public DnsNameResolverBuilder maxQueriesPerResolve(int maxQueriesPerResolve) {
347         this.maxQueriesPerResolve = maxQueriesPerResolve;
348         return this;
349     }
350 
351     /**
352      * Sets the capacity of the datagram packet buffer (in bytes).  The default value is {@code 4096} bytes.
353      *
354      * @param maxPayloadSize the capacity of the datagram packet buffer
355      * @return {@code this}
356      */
357     public DnsNameResolverBuilder maxPayloadSize(int maxPayloadSize) {
358         this.maxPayloadSize = maxPayloadSize;
359         return this;
360     }
361 
362     /**
363      * Enable the automatic inclusion of a optional records that tries to give the remote DNS server a hint about
364      * how much data the resolver can read per response. Some DNSServer may not support this and so fail to answer
365      * queries. If you find problems you may want to disable this.
366      *
367      * @param optResourceEnabled if optional records inclusion is enabled
368      * @return {@code this}
369      */
370     public DnsNameResolverBuilder optResourceEnabled(boolean optResourceEnabled) {
371         this.optResourceEnabled = optResourceEnabled;
372         return this;
373     }
374 
375     /**
376      * @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to first check
377      *                                 if the hostname is locally aliased.
378      * @return {@code this}
379      */
380     public DnsNameResolverBuilder hostsFileEntriesResolver(HostsFileEntriesResolver hostsFileEntriesResolver) {
381         this.hostsFileEntriesResolver = hostsFileEntriesResolver;
382         return this;
383     }
384 
385     protected DnsServerAddressStreamProvider nameServerProvider() {
386         return this.dnsServerAddressStreamProvider;
387     }
388 
389     /**
390      * Set the {@link DnsServerAddressStreamProvider} which is used to determine which DNS server is used to resolve
391      * each hostname.
392      * @return {@code this}.
393      */
394     public DnsNameResolverBuilder nameServerProvider(DnsServerAddressStreamProvider dnsServerAddressStreamProvider) {
395         this.dnsServerAddressStreamProvider =
396                 requireNonNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
397         return this;
398     }
399 
400     /**
401      * Set the list of search domains of the resolver.
402      *
403      * @param searchDomains the search domains
404      * @return {@code this}
405      */
406     public DnsNameResolverBuilder searchDomains(Iterable<String> searchDomains) {
407         requireNonNull(searchDomains, "searchDomains");
408 
409         final List<String> list = new ArrayList<>(4);
410 
411         for (String f : searchDomains) {
412             if (f == null) {
413                 break;
414             }
415 
416             // Avoid duplicate entries.
417             if (list.contains(f)) {
418                 continue;
419             }
420 
421             list.add(f);
422         }
423 
424         this.searchDomains = list.toArray(new String[0]);
425         return this;
426     }
427 
428   /**
429    * Set the number of dots which must appear in a name before an initial absolute query is made.
430    * The default value is {@code 1}.
431    *
432    * @param ndots the ndots value
433    * @return {@code this}
434    */
435     public DnsNameResolverBuilder ndots(int ndots) {
436         this.ndots = ndots;
437         return this;
438     }
439 
440     private DnsCache newCache() {
441         return new DefaultDnsCache(intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE), intValue(negativeTtl, 0));
442     }
443 
444     private AuthoritativeDnsServerCache newAuthoritativeDnsServerCache() {
445         return new DefaultAuthoritativeDnsServerCache(
446                 intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE),
447                 // Let us use the sane ordering as DnsNameResolver will be used when returning
448                 // nameservers from the cache.
449                 new NameServerComparator(addressType(DnsNameResolver.preferredAddressType(resolvedAddressTypes))));
450     }
451 
452     private DnsCnameCache newCnameCache() {
453         return new DefaultDnsCnameCache(
454                 intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE));
455     }
456 
457     /**
458      * Set if domain / host names should be decoded to unicode when received.
459      * See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
460      *
461      * @param decodeIdn if should get decoded
462      * @return {@code this}
463      */
464     public DnsNameResolverBuilder decodeIdn(boolean decodeIdn) {
465         this.decodeIdn = decodeIdn;
466         return this;
467     }
468 
469     /**
470      * Returns a new {@link DnsNameResolver} instance.
471      *
472      * @return a {@link DnsNameResolver}
473      */
474     public DnsNameResolver build() {
475         if (eventLoop == null) {
476             throw new IllegalStateException("eventLoop should be specified to build a DnsNameResolver.");
477         }
478 
479         if (resolveCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
480             throw new IllegalStateException("resolveCache and TTLs are mutually exclusive");
481         }
482 
483         if (authoritativeDnsServerCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
484             throw new IllegalStateException("authoritativeDnsServerCache and TTLs are mutually exclusive");
485         }
486 
487         DnsCache resolveCache = this.resolveCache != null ? this.resolveCache : newCache();
488         DnsCnameCache cnameCache = this.cnameCache != null ? this.cnameCache : newCnameCache();
489         AuthoritativeDnsServerCache authoritativeDnsServerCache = this.authoritativeDnsServerCache != null ?
490                 this.authoritativeDnsServerCache : newAuthoritativeDnsServerCache();
491         return new DnsNameResolver(
492                 eventLoop,
493                 channelFactory,
494                 socketChannelFactory,
495                 resolveCache,
496                 cnameCache,
497                 authoritativeDnsServerCache,
498                 localAddress,
499                 dnsQueryLifecycleObserverFactory,
500                 queryTimeoutMillis,
501                 resolvedAddressTypes,
502                 recursionDesired,
503                 maxQueriesPerResolve,
504                 maxPayloadSize,
505                 optResourceEnabled,
506                 hostsFileEntriesResolver,
507                 dnsServerAddressStreamProvider,
508                 searchDomains,
509                 ndots,
510                 decodeIdn,
511                 completeOncePreferredResolved);
512     }
513 
514     /**
515      * Creates a copy of this {@link DnsNameResolverBuilder}
516      *
517      * @return {@link DnsNameResolverBuilder}
518      */
519     public DnsNameResolverBuilder copy() {
520         DnsNameResolverBuilder copiedBuilder = new DnsNameResolverBuilder();
521 
522         if (eventLoop != null) {
523             copiedBuilder.eventLoop(eventLoop);
524         }
525 
526         if (channelFactory != null) {
527             copiedBuilder.channelFactory(channelFactory);
528         }
529 
530         if (socketChannelFactory != null) {
531             copiedBuilder.socketChannelFactory(socketChannelFactory);
532         }
533 
534         if (resolveCache != null) {
535             copiedBuilder.resolveCache(resolveCache);
536         }
537 
538         if (cnameCache != null) {
539             copiedBuilder.cnameCache(cnameCache);
540         }
541         if (maxTtl != null && minTtl != null) {
542             copiedBuilder.ttl(minTtl, maxTtl);
543         }
544 
545         if (negativeTtl != null) {
546             copiedBuilder.negativeTtl(negativeTtl);
547         }
548 
549         if (authoritativeDnsServerCache != null) {
550             copiedBuilder.authoritativeDnsServerCache(authoritativeDnsServerCache);
551         }
552 
553         if (dnsQueryLifecycleObserverFactory != null) {
554             copiedBuilder.dnsQueryLifecycleObserverFactory(dnsQueryLifecycleObserverFactory);
555         }
556 
557         copiedBuilder.queryTimeoutMillis(queryTimeoutMillis);
558         copiedBuilder.resolvedAddressTypes(resolvedAddressTypes);
559         copiedBuilder.recursionDesired(recursionDesired);
560         copiedBuilder.maxQueriesPerResolve(maxQueriesPerResolve);
561         copiedBuilder.maxPayloadSize(maxPayloadSize);
562         copiedBuilder.optResourceEnabled(optResourceEnabled);
563         copiedBuilder.hostsFileEntriesResolver(hostsFileEntriesResolver);
564 
565         if (dnsServerAddressStreamProvider != null) {
566             copiedBuilder.nameServerProvider(dnsServerAddressStreamProvider);
567         }
568 
569         if (searchDomains != null) {
570             copiedBuilder.searchDomains(Arrays.asList(searchDomains));
571         }
572 
573         copiedBuilder.ndots(ndots);
574         copiedBuilder.decodeIdn(decodeIdn);
575         copiedBuilder.completeOncePreferredResolved(completeOncePreferredResolved);
576         copiedBuilder.localAddress(localAddress);
577         return copiedBuilder;
578     }
579 }