• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net;
6 
7 import android.os.Build.VERSION_CODES;
8 
9 import androidx.annotation.Nullable;
10 import androidx.annotation.RequiresApi;
11 import androidx.annotation.RequiresOptIn;
12 
13 import java.time.Duration;
14 
15 /**
16  * A class configuring Cronet's host resolution functionality. Note that while we refer to {@code
17  * DNS} as the most common mechanism being used for brevity, settings apply to other means of
18  * resolving hostnames like hosts file resolution.
19  *
20  * <p>Cronet resolve hostnames in two ways - either by using the system resolver (using {@code
21  * getaddrinfo} provided by system libraries), or by using a custom resolver which is built into the
22  * networking stack Cronet uses.
23  *
24  * <p>The built-in stack provides several advantages over using the system resolver:
25  *
26  * <ul>
27  *   <li>It has been tailored to the needs of the networking stack, particularly speed and
28  *       stability.
29  *   <li>{@code getaddrinfo} is a blocking call which requires dedicating worker threads and makes
30  *       cancellation impossible (we need to abandon the thread until the call completes)
31  *   <li>The {@code getaddrinfo} interface gives no insight into the root cause of failures
32  *   <li>{@code struct addrinfo} provides no TTL (Time To Live) of the returned addresses. This
33  *       restricts flexibility of handling caching (e.g. allowing the use of stale DNS records) and
34  *       requires us to either rely on OS DNS caches, or be extremely conservative with the TTL.
35  *   <li>As part of the OS, {@code getaddrinfo} evolves slowly. Using a custom stack enables Cronet
36  *       to introduce features like encrypted DNS faster.
37  * </ul>
38  *
39  * <p>Most configuration in this class is only applicable if the built-in DNS resolver is used.
40  */
41 public final class DnsOptions {
42     @Nullable private final Boolean mUseBuiltInDnsResolver;
43     @Nullable private final Boolean mPersistHostCache;
44     @Nullable private final Boolean mEnableStaleDns;
45     @Nullable private final Long mPersistHostCachePeriodMillis;
46 
47     @Nullable private final Boolean mPreestablishConnectionsToStaleDnsResults;
48     @Nullable private final StaleDnsOptions mStaleDnsOptions;
49 
DnsOptions(Builder builder)50     DnsOptions(Builder builder) {
51         this.mEnableStaleDns = builder.mEnableStaleDns;
52         this.mStaleDnsOptions = builder.mStaleDnsOptions;
53         this.mPersistHostCachePeriodMillis = builder.mPersistHostCachePeriodMillis;
54         this.mPreestablishConnectionsToStaleDnsResults =
55                 builder.mPreestablishConnectionsToStaleDnsResults;
56         this.mUseBuiltInDnsResolver = builder.mUseBuiltInDnsResolver;
57         this.mPersistHostCache = builder.mPersistHostCache;
58     }
59 
60     @Nullable
getUseBuiltInDnsResolver()61     public Boolean getUseBuiltInDnsResolver() {
62         return mUseBuiltInDnsResolver;
63     }
64 
65     @Nullable
getPersistHostCache()66     public Boolean getPersistHostCache() {
67         return mPersistHostCache;
68     }
69 
70     @Nullable
getEnableStaleDns()71     public Boolean getEnableStaleDns() {
72         return mEnableStaleDns;
73     }
74 
75     @Nullable
getPersistHostCachePeriodMillis()76     public Long getPersistHostCachePeriodMillis() {
77         return mPersistHostCachePeriodMillis;
78     }
79 
80     @Nullable
getPreestablishConnectionsToStaleDnsResults()81     public Boolean getPreestablishConnectionsToStaleDnsResults() {
82         return mPreestablishConnectionsToStaleDnsResults;
83     }
84 
85     @Nullable
getStaleDnsOptions()86     public StaleDnsOptions getStaleDnsOptions() {
87         return mStaleDnsOptions;
88     }
89 
builder()90     public static Builder builder() {
91         return new Builder();
92     }
93 
94     /**
95      * A class configuring Cronet's stale DNS functionality.
96      *
97      * <p>DNS resolution is one of the steps on the critical path to making a URL request, but it
98      * can be slow for various reasons (underlying network latency, buffer bloat, packet loss,
99      * etc.).
100      *
101      * <p>Depending on the use case, it might be worthwhile for an app developer to trade off
102      * guaranteed DNS record freshness for better availability of DNS records.
103      *
104      * <p>Stale results can include both:
105      *
106      * <ul>
107      *   <li>results returned from the current network's DNS server, but past their time-to-live,
108      * and <li>results returned from a previous network's DNS server, whether expired or not.
109      * </ul>
110      */
111     public static class StaleDnsOptions {
112         @Nullable
getFreshLookupTimeoutMillis()113         public Long getFreshLookupTimeoutMillis() {
114             return mFreshLookupTimeoutMillis;
115         }
116 
117         @Nullable
getMaxExpiredDelayMillis()118         public Long getMaxExpiredDelayMillis() {
119             return mMaxExpiredDelayMillis;
120         }
121 
122         @Nullable
getAllowCrossNetworkUsage()123         public Boolean getAllowCrossNetworkUsage() {
124             return mAllowCrossNetworkUsage;
125         }
126 
127         @Nullable
getUseStaleOnNameNotResolved()128         public Boolean getUseStaleOnNameNotResolved() {
129             return mUseStaleOnNameNotResolved;
130         }
131 
builder()132         public static Builder builder() {
133             return new Builder();
134         }
135 
136         @Nullable private final Long mFreshLookupTimeoutMillis;
137         @Nullable private final Long mMaxExpiredDelayMillis;
138         @Nullable private final Boolean mAllowCrossNetworkUsage;
139         @Nullable private final Boolean mUseStaleOnNameNotResolved;
140 
StaleDnsOptions(Builder builder)141         StaleDnsOptions(Builder builder) {
142             this.mFreshLookupTimeoutMillis = builder.mFreshLookupTimeoutMillis;
143             this.mMaxExpiredDelayMillis = builder.mMaxExpiredDelayMillis;
144             this.mAllowCrossNetworkUsage = builder.mAllowCrossNetworkUsage;
145             this.mUseStaleOnNameNotResolved = builder.mUseStaleOnNameNotResolved;
146         }
147 
148         /** Builder for {@link StaleDnsOptions}. */
149         public static final class Builder {
150             private Long mFreshLookupTimeoutMillis;
151             private Long mMaxExpiredDelayMillis;
152             private Boolean mAllowCrossNetworkUsage;
153             private Boolean mUseStaleOnNameNotResolved;
154 
Builder()155             Builder() {}
156 
157             /**
158              * Sets how long (in milliseconds) to wait for a DNS request to return before using a
159              * stale result instead. If set to zero, returns stale results instantly but continues
160              * the DNS request in the background to update the cache.
161              *
162              * @return the builder for chaining
163              */
setFreshLookupTimeoutMillis(long freshLookupTimeoutMillis)164             public Builder setFreshLookupTimeoutMillis(long freshLookupTimeoutMillis) {
165                 this.mFreshLookupTimeoutMillis = freshLookupTimeoutMillis;
166                 return this;
167             }
168 
169             /**
170              * Same as {@link #setFreshLookupTimeoutMillis(long)} but using {@link
171              * java.time.Duration}.
172              *
173              * @return the builder for chaining
174              */
175             @RequiresApi(VERSION_CODES.O)
setFreshLookupTimeout(Duration freshLookupTimeout)176             public Builder setFreshLookupTimeout(Duration freshLookupTimeout) {
177                 return setFreshLookupTimeoutMillis(freshLookupTimeout.toMillis());
178             }
179 
180             /**
181              * Sets how long (in milliseconds) past expiration to consider using expired results.
182              * Setting the value to zero means expired records can be used indefinitely.
183              *
184              * @return the builder for chaining
185              */
setMaxExpiredDelayMillis(long maxExpiredDelayMillis)186             public Builder setMaxExpiredDelayMillis(long maxExpiredDelayMillis) {
187                 this.mMaxExpiredDelayMillis = maxExpiredDelayMillis;
188                 return this;
189             }
190 
191             /**
192              * Same as {@link #setMaxExpiredDelayMillis(long)} but using {@link java.time.Duration}.
193              *
194              * @return the builder for chaining
195              */
196             @RequiresApi(VERSION_CODES.O)
setMaxExpiredDelayMillis(Duration maxExpiredDelay)197             public Builder setMaxExpiredDelayMillis(Duration maxExpiredDelay) {
198                 return setMaxExpiredDelayMillis(maxExpiredDelay.toMillis());
199             }
200 
201             /**
202              * Sets whether to return results originating from other networks or not. Normally,
203              * Cronet clears the DNS cache entirely when switching connections, e.g. between two
204              * Wi-Fi networks or from Wi-Fi to 4G.
205              *
206              * @return the builder for chaining
207              */
allowCrossNetworkUsage(boolean allowCrossNetworkUsage)208             public Builder allowCrossNetworkUsage(boolean allowCrossNetworkUsage) {
209                 this.mAllowCrossNetworkUsage = allowCrossNetworkUsage;
210                 return this;
211             }
212 
213             /**
214              * Sets whether to allow use of stale DNS results when network resolver fails to resolve
215              * the hostname.
216              *
217              * <p>Depending on the use case, if Cronet quickly sees a fresh failure, it may be
218              * desirable to use the failure as it is technically the fresher result, and we had such
219              * a fresh result quickly; or, prefer having any result (even if stale) to use over
220              * having a failure.
221              *
222              * @return the builder for chaining
223              */
useStaleOnNameNotResolved(boolean useStaleOnNameNotResolved)224             public Builder useStaleOnNameNotResolved(boolean useStaleOnNameNotResolved) {
225                 this.mUseStaleOnNameNotResolved = useStaleOnNameNotResolved;
226                 return this;
227             }
228 
229             /**
230              * Creates and returns the final {@link StaleDnsOptions} instance, based on the values
231              * in this builder.
232              */
build()233             public StaleDnsOptions build() {
234                 return new StaleDnsOptions(this);
235             }
236         }
237     }
238 
239     /** Builder for {@link DnsOptions}. */
240     public static final class Builder {
241         @Nullable private Boolean mUseBuiltInDnsResolver;
242         @Nullable private Boolean mEnableStaleDns;
243         @Nullable private StaleDnsOptions mStaleDnsOptions;
244         @Nullable private Boolean mPersistHostCache;
245         @Nullable private Long mPersistHostCachePeriodMillis;
246         @Nullable private Boolean mPreestablishConnectionsToStaleDnsResults;
247 
Builder()248         Builder() {}
249 
useBuiltInDnsResolver(boolean enable)250         public Builder useBuiltInDnsResolver(boolean enable) {
251             this.mUseBuiltInDnsResolver = enable;
252             return this;
253         }
254 
255         /**
256          * Sets whether to use stale DNS results at all.
257          *
258          * @return the builder for chaining
259          */
enableStaleDns(boolean enable)260         public Builder enableStaleDns(boolean enable) {
261             this.mEnableStaleDns = enable;
262             return this;
263         }
264 
265         /**
266          * Sets detailed configuration for stale DNS.
267          *
268          * Only relevant if {@link #enableStaleDns(boolean)} is set.
269          *
270          * @return this builder for chaining.
271          */
setStaleDnsOptions(StaleDnsOptions staleDnsOptions)272         public Builder setStaleDnsOptions(StaleDnsOptions staleDnsOptions) {
273             this.mStaleDnsOptions = staleDnsOptions;
274             return this;
275         }
276 
277         /** @see #setStaleDnsOptions(StaleDnsOptions) */
278         @Experimental
setStaleDnsOptions(StaleDnsOptions.Builder staleDnsOptionsBuilder)279         public Builder setStaleDnsOptions(StaleDnsOptions.Builder staleDnsOptionsBuilder) {
280             return setStaleDnsOptions(staleDnsOptionsBuilder.build());
281         }
282 
283         /**
284          * Sets whether Cronet should use stale cached DNS records to pre-establish connections.
285          *
286          * <p>If enabled, Cronet will optimistically pre-establish connections to servers that
287          * matched the hostname at some point in the past and were cached but the cache entry
288          * expired. Such connections won't be used further until a new DNS lookup confirms the
289          * cached record was up to date.
290          *
291          * <p>To use cached DNS records straight away, use {@link #enableStaleDns} and {@link
292          * StaleDnsOptions} configuration options.
293          *
294          * <p>This option may not be available for all networking protocols.
295          *
296          * @return the builder for chaining
297          */
298         @Experimental
preestablishConnectionsToStaleDnsResults(boolean enable)299         public Builder preestablishConnectionsToStaleDnsResults(boolean enable) {
300             this.mPreestablishConnectionsToStaleDnsResults = enable;
301             return this;
302         }
303 
304         /**
305          * Sets whether the DNS cache should be persisted to disk.
306          *
307          * <p>Only relevant if {@link CronetEngine.Builder#setStoragePath(String)} is
308          * set.
309          *
310          * @return the builder for chaining
311          */
persistHostCache(boolean persistHostCache)312         public Builder persistHostCache(boolean persistHostCache) {
313             this.mPersistHostCache = persistHostCache;
314             return this;
315         }
316 
317         /**
318          * Sets the minimum period between subsequent writes to disk for DNS cache persistence.
319          *
320          * <p>Only relevant if {@link #persistHostCache(boolean)} is set to true.
321          *
322          * @return the builder for chaining
323          */
setPersistHostCachePeriodMillis(long persistHostCachePeriodMillis)324         public Builder setPersistHostCachePeriodMillis(long persistHostCachePeriodMillis) {
325             this.mPersistHostCachePeriodMillis = persistHostCachePeriodMillis;
326             return this;
327         }
328 
329         /**
330          * Same as {@link #setPersistHostCachePeriodMillis(long)} but using {@link
331          * java.time.Duration}.
332          *
333          * @return the builder for chaining
334          */
335         @RequiresApi(api = VERSION_CODES.O)
setPersistDelay(Duration persistToDiskPeriod)336         public Builder setPersistDelay(Duration persistToDiskPeriod) {
337             return setPersistHostCachePeriodMillis(persistToDiskPeriod.toMillis());
338         }
339 
340         /**
341          * Creates and returns the final {@link DnsOptions} instance, based on the values in this
342          * builder.
343          */
build()344         public DnsOptions build() {
345             return new DnsOptions(this);
346         }
347     }
348 
349     /**
350      * An annotation for APIs which are not considered stable yet.
351      *
352      * <p>Experimental APIs are subject to change, breakage, or removal at any time and may not be
353      * production ready.
354      *
355      * <p>It's highly recommended to reach out to Cronet maintainers
356      * (<code>net-dev@chromium.org</code>) before using one of the APIs annotated as experimental
357      * outside of debugging and proof-of-concept code.
358      *
359      * <p>By using an Experimental API, applications acknowledge that they are doing so at their own
360      * risk.
361      */
362     @RequiresOptIn
363     public @interface Experimental {}
364 }
365