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