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 android.net.http; 6 7 import android.annotation.IntDef; 8 import android.annotation.SuppressLint; 9 import android.os.Build.VERSION_CODES; 10 11 import androidx.annotation.NonNull; 12 import androidx.annotation.Nullable; 13 import androidx.annotation.RequiresApi; 14 15 import java.lang.annotation.Retention; 16 import java.lang.annotation.RetentionPolicy; 17 import java.time.Duration; 18 19 /** 20 * A class configuring the host resolution functionality. Note that while we refer to {@code 21 * DNS} as the most common mechanism being used for brevity, settings apply to other means of 22 * resolving hostnames like hosts file resolution. 23 * 24 * <p>Hostnames can be resolved in two ways - either by using the system resolver (using {@code 25 * getaddrinfo} provided by system libraries), or by using a custom resolver which is tailored 26 * for the HTTP networking stack. 27 * 28 * <p>The built-in stack provides several advantages over using the global system resolver: 29 * 30 * <ul> 31 * <li>It has been tailored to the needs of the HTTP networking stack, particularly speed and 32 * stability. 33 * <li>{@code getaddrinfo} is a blocking call which requires dedicating worker threads and makes 34 * cancellation impossible (we need to abandon the thread until the call completes) 35 * <li>The {@code getaddrinfo} interface gives no insight into the root cause of failures 36 * <li>{@code struct addrinfo} provides no TTL (Time To Live) of the returned addresses. This 37 * restricts flexibility of handling caching (e.g. allowing the use of stale DNS records) and 38 * requires us to either rely on OS DNS caches, or be extremely conservative with the TTL. 39 * </ul> 40 * 41 * <p>Most configuration in this class is only applicable if the built-in DNS resolver is used. 42 */ 43 // SuppressLint to be consistent with other cronet code 44 @SuppressLint("UserHandleName") 45 public final class DnsOptions { 46 private final @DnsOptionState int mUseHttpStackDnsResolver; 47 private final @DnsOptionState int mPersistHostCache; 48 private final @DnsOptionState int mEnableStaleDns; 49 @Nullable 50 private final Duration mPersistHostCachePeriod; 51 52 private final @DnsOptionState int mPreestablishConnectionsToStaleDnsResults; 53 @Nullable 54 private final StaleDnsOptions mStaleDnsOptions; 55 DnsOptions(Builder builder)56 DnsOptions(Builder builder) { 57 this.mEnableStaleDns = builder.mEnableStaleDns; 58 this.mStaleDnsOptions = builder.mStaleDnsOptions; 59 this.mPersistHostCachePeriod = builder.mPersistHostCachePeriod; 60 this.mPreestablishConnectionsToStaleDnsResults = 61 builder.mPreestablishConnectionsToStaleDnsResults; 62 this.mUseHttpStackDnsResolver = builder.mUseHttpStackDnsResolver; 63 this.mPersistHostCache = builder.mPersistHostCache; 64 } 65 66 /** 67 * Option is unspecified, platform default value will be used. 68 */ 69 public static final int DNS_OPTION_UNSPECIFIED = 0; 70 71 /** 72 * Option is enabled. 73 */ 74 public static final int DNS_OPTION_ENABLED = 1; 75 76 /** 77 * Option is disabled. 78 */ 79 public static final int DNS_OPTION_DISABLED = 2; 80 81 /** @hide */ 82 @Retention(RetentionPolicy.SOURCE) 83 @IntDef(flag = false, prefix = "DNS_OPTION_", value = { 84 DNS_OPTION_UNSPECIFIED, 85 DNS_OPTION_ENABLED, 86 DNS_OPTION_DISABLED, 87 }) 88 public @interface DnsOptionState {} 89 90 /** 91 * See {@link Builder#setUseHttpStackDnsResolver(int)} 92 */ getUseHttpStackDnsResolver()93 public @DnsOptionState int getUseHttpStackDnsResolver() { 94 return mUseHttpStackDnsResolver; 95 } 96 97 /** 98 * See {@link Builder#setPersistHostCache(int)} 99 */ getPersistHostCache()100 public @DnsOptionState int getPersistHostCache() { 101 return mPersistHostCache; 102 } 103 104 /** 105 * See {@link Builder#setStaleDns(int)} 106 */ getStaleDns()107 public @DnsOptionState int getStaleDns() { 108 return mEnableStaleDns; 109 } 110 111 /** 112 * See {@link Builder#setPersistHostCachePeriod} 113 */ 114 @Nullable getPersistHostCachePeriod()115 public Duration getPersistHostCachePeriod() { 116 return mPersistHostCachePeriod; 117 } 118 119 /** 120 * See {@link Builder#setPreestablishConnectionsToStaleDnsResults(int)} 121 */ getPreestablishConnectionsToStaleDnsResults()122 public @DnsOptionState int getPreestablishConnectionsToStaleDnsResults() { 123 return mPreestablishConnectionsToStaleDnsResults; 124 } 125 126 /** 127 * See {@link Builder#setStaleDnsOptions} 128 */ 129 @Nullable getStaleDnsOptions()130 public StaleDnsOptions getStaleDnsOptions() { 131 return mStaleDnsOptions; 132 } 133 134 /** 135 * Returns a new builder for {@link DnsOptions}. 136 * 137 * @hide 138 */ 139 @NonNull builder()140 public static Builder builder() { 141 return new Builder(); 142 } 143 144 /** 145 * A class configuring the stale DNS functionality. 146 * 147 * <p>DNS resolution is one of the steps on the critical path to making a URL request, but it 148 * can be slow for various reasons (underlying network latency, buffer bloat, packet loss, 149 * etc.). Depending on the use case, it might be worthwhile for an app developer to trade off 150 * guaranteed DNS record freshness for better availability of DNS records. 151 * 152 * <p>Stale results can include both: 153 * 154 * <ul> 155 * <li>results returned from the current network's DNS server, but past their time-to-live, 156 * and 157 * <li>results returned from a different network's DNS server, whether expired or not. 158 * </ul> 159 * 160 * <p>For detailed explanation of the configuration options see javadoc on 161 * {@link StaleDnsOptions.Builder} methods. 162 */ 163 // SuppressLint to be consistent with other cronet code 164 @SuppressLint("UserHandleName") 165 public static class StaleDnsOptions { 166 @Nullable getFreshLookupTimeout()167 public Duration getFreshLookupTimeout() { 168 return mFreshLookupTimeout; 169 } 170 171 @Nullable getMaxExpiredDelay()172 public Duration getMaxExpiredDelay() { 173 return mMaxExpiredDelay; 174 } 175 getAllowCrossNetworkUsage()176 public @DnsOptionState int getAllowCrossNetworkUsage() { 177 return mAllowCrossNetworkUsage; 178 } getUseStaleOnNameNotResolved()179 public @DnsOptionState int getUseStaleOnNameNotResolved() { 180 return mUseStaleOnNameNotResolved; 181 } 182 183 /** 184 * @hide 185 */ 186 @NonNull builder()187 public static Builder builder() { 188 return new Builder(); 189 } 190 191 @Nullable 192 private final Duration mFreshLookupTimeout; 193 @Nullable 194 private final Duration mMaxExpiredDelay; 195 private final @DnsOptionState int mAllowCrossNetworkUsage; 196 private final @DnsOptionState int mUseStaleOnNameNotResolved; 197 StaleDnsOptions(@onNull Builder builder)198 StaleDnsOptions(@NonNull Builder builder) { 199 this.mFreshLookupTimeout = builder.mFreshLookupTimeout; 200 this.mMaxExpiredDelay = builder.mMaxExpiredDelay; 201 this.mAllowCrossNetworkUsage = builder.mAllowCrossNetworkUsage; 202 this.mUseStaleOnNameNotResolved = builder.mUseStaleOnNameNotResolved; 203 } 204 205 /** 206 * Builder for {@link StaleDnsOptions}. 207 */ 208 public static final class Builder { 209 private Duration mFreshLookupTimeout; 210 private Duration mMaxExpiredDelay; 211 private @DnsOptionState int mAllowCrossNetworkUsage; 212 private @DnsOptionState int mUseStaleOnNameNotResolved; 213 Builder()214 public Builder() {} 215 216 /** 217 * Sets how long (in milliseconds) to wait for a DNS request to return before using a 218 * stale result instead. If set to zero, returns stale results instantly but continues 219 * the DNS request in the background to update the cache. 220 * 221 * @return the builder for chaining 222 */ 223 @NonNull setFreshLookupTimeout(@onNull Duration freshLookupTimeout)224 public Builder setFreshLookupTimeout(@NonNull Duration freshLookupTimeout) { 225 this.mFreshLookupTimeout = freshLookupTimeout; 226 return this; 227 } 228 229 /** 230 * Sets how long (in milliseconds) past expiration to consider using expired results. 231 * Setting the value to zero means expired records can be used indefinitely. 232 * 233 * @return the builder for chaining 234 */ 235 @NonNull setMaxExpiredDelay(@onNull Duration maxExpiredDelay)236 public Builder setMaxExpiredDelay(@NonNull Duration maxExpiredDelay) { 237 this.mMaxExpiredDelay = maxExpiredDelay; 238 return this; 239 } 240 241 /** 242 * Sets whether to return results originating from other networks or not. Normally, 243 * the HTTP stack clears the DNS cache entirely when switching connections, e.g. between 244 * two Wi-Fi networks or from Wi-Fi to 4G. 245 * 246 * @param state one of the DNS_OPTION_* values 247 * @return the builder for chaining 248 */ 249 @NonNull setAllowCrossNetworkUsage(@nsOptionState int state)250 public Builder setAllowCrossNetworkUsage(@DnsOptionState int state) { 251 this.mAllowCrossNetworkUsage = state; 252 return this; 253 } 254 255 /** 256 * Sets whether to allow use of stale DNS results when network resolver fails to resolve 257 * the hostname. 258 * 259 * <p>Depending on the use case, if the DNS resolver quickly sees a fresh failure, it 260 * may be desirable to use the failure as it is technically the fresher result, and we 261 * had such a fresh result quickly; or, prefer having any result (even if stale) to use 262 * over dealing with a DNS failure. 263 * 264 * @param state one of the DNS_OPTION_* values 265 * @return the builder for chaining 266 */ 267 @NonNull setUseStaleOnNameNotResolved(@nsOptionState int state)268 public Builder setUseStaleOnNameNotResolved(@DnsOptionState int state) { 269 this.mUseStaleOnNameNotResolved = state; 270 return this; 271 } 272 273 /** 274 * Creates and returns the final {@link StaleDnsOptions} instance, based on the values 275 * in this builder. 276 */ 277 @NonNull build()278 public StaleDnsOptions build() { 279 return new StaleDnsOptions(this); 280 } 281 } 282 } 283 284 /** 285 * Builder for {@link DnsOptions}. 286 */ 287 public static final class Builder { 288 private @DnsOptionState int mUseHttpStackDnsResolver; 289 private @DnsOptionState int mEnableStaleDns; 290 @Nullable 291 private StaleDnsOptions mStaleDnsOptions; 292 private @DnsOptionState int mPersistHostCache; 293 @Nullable 294 private Duration mPersistHostCachePeriod; 295 private @DnsOptionState int mPreestablishConnectionsToStaleDnsResults; 296 Builder()297 public Builder() {} 298 299 /** 300 * Enables the use of the HTTP-stack-specific DNS resolver. 301 * 302 * <p>Setting this to {@link #DNS_OPTION_ENABLED} is necessary for other functionality 303 * of {@link DnsOptions} to work, unless specified otherwise. See the {@link DnsOptions} 304 * documentation for more details. 305 * 306 * @param state one of the DNS_OPTION_* values 307 * @return the builder for chaining 308 */ 309 @NonNull setUseHttpStackDnsResolver(@nsOptionState int state)310 public Builder setUseHttpStackDnsResolver(@DnsOptionState int state) { 311 this.mUseHttpStackDnsResolver = state; 312 return this; 313 } 314 315 /** 316 * Sets whether to use stale DNS results at all. 317 * 318 * @param state one of the DNS_OPTION_* values 319 * @return the builder for chaining 320 */ 321 @NonNull setStaleDns(@nsOptionState int state)322 public Builder setStaleDns(@DnsOptionState int state) { 323 this.mEnableStaleDns = state; 324 return this; 325 } 326 327 /** 328 * Sets detailed configuration for stale DNS. 329 * 330 * Only relevant if {@link #setStaleDns(int)} is set. 331 * 332 * @return this builder for chaining. 333 */ 334 @NonNull setStaleDnsOptions(@onNull StaleDnsOptions staleDnsOptions)335 public Builder setStaleDnsOptions(@NonNull StaleDnsOptions staleDnsOptions) { 336 this.mStaleDnsOptions = staleDnsOptions; 337 return this; 338 } 339 340 /** 341 * @see #setStaleDnsOptions(StaleDnsOptions) 342 * 343 * {@hide} 344 */ 345 @Experimental 346 @NonNull setStaleDnsOptions(@onNull StaleDnsOptions.Builder staleDnsOptionsBuilder)347 public Builder setStaleDnsOptions(@NonNull StaleDnsOptions.Builder staleDnsOptionsBuilder) { 348 return setStaleDnsOptions(staleDnsOptionsBuilder.build()); 349 } 350 351 /** 352 * Sets whether Cronet should use stale cached DNS records to pre-establish connections. 353 * 354 * <p>If enabled, Cronet will optimistically pre-establish connections to servers that 355 * matched the hostname at some point in the past and were cached but the cache entry 356 * expired. Such connections won't be used further until a new DNS lookup confirms the 357 * cached record was up to date. 358 * 359 * <p>To use cached DNS records straight away, use {@link #setStaleDns(int)} and {@link 360 * StaleDnsOptions} configuration options. 361 * 362 * <p>This option may not be available for all networking protocols. 363 * 364 * @param state one of the DNS_OPTION_* values 365 * @return the builder for chaining 366 */ 367 @Experimental 368 @NonNull setPreestablishConnectionsToStaleDnsResults( @nsOptionState int state)369 public Builder setPreestablishConnectionsToStaleDnsResults( 370 @DnsOptionState int state) { 371 this.mPreestablishConnectionsToStaleDnsResults = state; 372 return this; 373 } 374 375 /** 376 * Sets whether the DNS cache should be persisted to disk. 377 * 378 * <p>Only relevant if {@link HttpEngine.Builder#setStoragePath(String)} is 379 * set. 380 * 381 * @param state one of the DNS_OPTION_* values 382 * @return the builder for chaining 383 */ 384 @NonNull setPersistHostCache(@nsOptionState int state)385 public Builder setPersistHostCache(@DnsOptionState int state) { 386 this.mPersistHostCache = state; 387 return this; 388 } 389 390 /** 391 * Sets the minimum period between subsequent writes to disk for DNS cache persistence. 392 * 393 * <p>Only relevant if {@link #setPersistHostCache(int)} is set to 394 * {@link #DNS_OPTION_ENABLED}. 395 * 396 * @return the builder for chaining 397 */ 398 @NonNull setPersistHostCachePeriod(@onNull Duration persistHostCachePeriod)399 public Builder setPersistHostCachePeriod(@NonNull Duration persistHostCachePeriod) { 400 this.mPersistHostCachePeriod = persistHostCachePeriod; 401 return this; 402 } 403 404 /** 405 * Creates and returns the final {@link DnsOptions} instance, based on the values in this 406 * builder. 407 */ 408 @NonNull build()409 public DnsOptions build() { 410 return new DnsOptions(this); 411 } 412 } 413 414 /** 415 * An annotation for APIs which are not considered stable yet. 416 * 417 * <p>Applications using experimental APIs must acknowledge that they're aware of using APIs 418 * that are not considered stable. The APIs might change functionality, break or cease to exist 419 * without notice. 420 * 421 * <p>It's highly recommended to reach out to Cronet maintainers ({@code net-dev@chromium.org}) 422 * before using one of the APIs annotated as experimental outside of debugging 423 * and proof-of-concept code. Be ready to help to help polishing the API, or for a "sorry, 424 * really not production ready yet". 425 * 426 * <p>If you still want to use an experimental API in production, you're doing so at your 427 * own risk. You have been warned. 428 * 429 * {@hide} 430 */ 431 public @interface Experimental {} 432 } 433