// Copyright 2022 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.net; import android.os.Build.VERSION_CODES; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.RequiresOptIn; import java.time.Duration; /** * A class configuring Cronet's host resolution functionality. Note that while we refer to {@code * DNS} as the most common mechanism being used for brevity, settings apply to other means of * resolving hostnames like hosts file resolution. * *

Cronet resolve hostnames in two ways - either by using the system resolver (using {@code * getaddrinfo} provided by system libraries), or by using a custom resolver which is built into the * networking stack Cronet uses. * *

The built-in stack provides several advantages over using the system resolver: * *

* *

Most configuration in this class is only applicable if the built-in DNS resolver is used. */ public final class DnsOptions { @Nullable private final Boolean mUseBuiltInDnsResolver; @Nullable private final Boolean mPersistHostCache; @Nullable private final Boolean mEnableStaleDns; @Nullable private final Long mPersistHostCachePeriodMillis; @Nullable private final Boolean mPreestablishConnectionsToStaleDnsResults; @Nullable private final StaleDnsOptions mStaleDnsOptions; DnsOptions(Builder builder) { this.mEnableStaleDns = builder.mEnableStaleDns; this.mStaleDnsOptions = builder.mStaleDnsOptions; this.mPersistHostCachePeriodMillis = builder.mPersistHostCachePeriodMillis; this.mPreestablishConnectionsToStaleDnsResults = builder.mPreestablishConnectionsToStaleDnsResults; this.mUseBuiltInDnsResolver = builder.mUseBuiltInDnsResolver; this.mPersistHostCache = builder.mPersistHostCache; } @Nullable public Boolean getUseBuiltInDnsResolver() { return mUseBuiltInDnsResolver; } @Nullable public Boolean getPersistHostCache() { return mPersistHostCache; } @Nullable public Boolean getEnableStaleDns() { return mEnableStaleDns; } @Nullable public Long getPersistHostCachePeriodMillis() { return mPersistHostCachePeriodMillis; } @Nullable public Boolean getPreestablishConnectionsToStaleDnsResults() { return mPreestablishConnectionsToStaleDnsResults; } @Nullable public StaleDnsOptions getStaleDnsOptions() { return mStaleDnsOptions; } public static Builder builder() { return new Builder(); } /** * A class configuring Cronet's stale DNS functionality. * *

DNS resolution is one of the steps on the critical path to making a URL request, but it * can be slow for various reasons (underlying network latency, buffer bloat, packet loss, * etc.). * *

Depending on the use case, it might be worthwhile for an app developer to trade off * guaranteed DNS record freshness for better availability of DNS records. * *

Stale results can include both: * *

*/ public static class StaleDnsOptions { @Nullable public Long getFreshLookupTimeoutMillis() { return mFreshLookupTimeoutMillis; } @Nullable public Long getMaxExpiredDelayMillis() { return mMaxExpiredDelayMillis; } @Nullable public Boolean getAllowCrossNetworkUsage() { return mAllowCrossNetworkUsage; } @Nullable public Boolean getUseStaleOnNameNotResolved() { return mUseStaleOnNameNotResolved; } public static Builder builder() { return new Builder(); } @Nullable private final Long mFreshLookupTimeoutMillis; @Nullable private final Long mMaxExpiredDelayMillis; @Nullable private final Boolean mAllowCrossNetworkUsage; @Nullable private final Boolean mUseStaleOnNameNotResolved; StaleDnsOptions(Builder builder) { this.mFreshLookupTimeoutMillis = builder.mFreshLookupTimeoutMillis; this.mMaxExpiredDelayMillis = builder.mMaxExpiredDelayMillis; this.mAllowCrossNetworkUsage = builder.mAllowCrossNetworkUsage; this.mUseStaleOnNameNotResolved = builder.mUseStaleOnNameNotResolved; } /** Builder for {@link StaleDnsOptions}. */ public static final class Builder { private Long mFreshLookupTimeoutMillis; private Long mMaxExpiredDelayMillis; private Boolean mAllowCrossNetworkUsage; private Boolean mUseStaleOnNameNotResolved; Builder() {} /** * Sets how long (in milliseconds) to wait for a DNS request to return before using a * stale result instead. If set to zero, returns stale results instantly but continues * the DNS request in the background to update the cache. * * @return the builder for chaining */ public Builder setFreshLookupTimeoutMillis(long freshLookupTimeoutMillis) { this.mFreshLookupTimeoutMillis = freshLookupTimeoutMillis; return this; } /** * Same as {@link #setFreshLookupTimeoutMillis(long)} but using {@link * java.time.Duration}. * * @return the builder for chaining */ @RequiresApi(VERSION_CODES.O) public Builder setFreshLookupTimeout(Duration freshLookupTimeout) { return setFreshLookupTimeoutMillis(freshLookupTimeout.toMillis()); } /** * Sets how long (in milliseconds) past expiration to consider using expired results. * Setting the value to zero means expired records can be used indefinitely. * * @return the builder for chaining */ public Builder setMaxExpiredDelayMillis(long maxExpiredDelayMillis) { this.mMaxExpiredDelayMillis = maxExpiredDelayMillis; return this; } /** * Same as {@link #setMaxExpiredDelayMillis(long)} but using {@link java.time.Duration}. * * @return the builder for chaining */ @RequiresApi(VERSION_CODES.O) public Builder setMaxExpiredDelayMillis(Duration maxExpiredDelay) { return setMaxExpiredDelayMillis(maxExpiredDelay.toMillis()); } /** * Sets whether to return results originating from other networks or not. Normally, * Cronet clears the DNS cache entirely when switching connections, e.g. between two * Wi-Fi networks or from Wi-Fi to 4G. * * @return the builder for chaining */ public Builder allowCrossNetworkUsage(boolean allowCrossNetworkUsage) { this.mAllowCrossNetworkUsage = allowCrossNetworkUsage; return this; } /** * Sets whether to allow use of stale DNS results when network resolver fails to resolve * the hostname. * *

Depending on the use case, if Cronet quickly sees a fresh failure, it may be * desirable to use the failure as it is technically the fresher result, and we had such * a fresh result quickly; or, prefer having any result (even if stale) to use over * having a failure. * * @return the builder for chaining */ public Builder useStaleOnNameNotResolved(boolean useStaleOnNameNotResolved) { this.mUseStaleOnNameNotResolved = useStaleOnNameNotResolved; return this; } /** * Creates and returns the final {@link StaleDnsOptions} instance, based on the values * in this builder. */ public StaleDnsOptions build() { return new StaleDnsOptions(this); } } } /** Builder for {@link DnsOptions}. */ public static final class Builder { @Nullable private Boolean mUseBuiltInDnsResolver; @Nullable private Boolean mEnableStaleDns; @Nullable private StaleDnsOptions mStaleDnsOptions; @Nullable private Boolean mPersistHostCache; @Nullable private Long mPersistHostCachePeriodMillis; @Nullable private Boolean mPreestablishConnectionsToStaleDnsResults; Builder() {} public Builder useBuiltInDnsResolver(boolean enable) { this.mUseBuiltInDnsResolver = enable; return this; } /** * Sets whether to use stale DNS results at all. * * @return the builder for chaining */ public Builder enableStaleDns(boolean enable) { this.mEnableStaleDns = enable; return this; } /** * Sets detailed configuration for stale DNS. * * Only relevant if {@link #enableStaleDns(boolean)} is set. * * @return this builder for chaining. */ public Builder setStaleDnsOptions(StaleDnsOptions staleDnsOptions) { this.mStaleDnsOptions = staleDnsOptions; return this; } /** @see #setStaleDnsOptions(StaleDnsOptions) */ @Experimental public Builder setStaleDnsOptions(StaleDnsOptions.Builder staleDnsOptionsBuilder) { return setStaleDnsOptions(staleDnsOptionsBuilder.build()); } /** * Sets whether Cronet should use stale cached DNS records to pre-establish connections. * *

If enabled, Cronet will optimistically pre-establish connections to servers that * matched the hostname at some point in the past and were cached but the cache entry * expired. Such connections won't be used further until a new DNS lookup confirms the * cached record was up to date. * *

To use cached DNS records straight away, use {@link #enableStaleDns} and {@link * StaleDnsOptions} configuration options. * *

This option may not be available for all networking protocols. * * @return the builder for chaining */ @Experimental public Builder preestablishConnectionsToStaleDnsResults(boolean enable) { this.mPreestablishConnectionsToStaleDnsResults = enable; return this; } /** * Sets whether the DNS cache should be persisted to disk. * *

Only relevant if {@link CronetEngine.Builder#setStoragePath(String)} is * set. * * @return the builder for chaining */ public Builder persistHostCache(boolean persistHostCache) { this.mPersistHostCache = persistHostCache; return this; } /** * Sets the minimum period between subsequent writes to disk for DNS cache persistence. * *

Only relevant if {@link #persistHostCache(boolean)} is set to true. * * @return the builder for chaining */ public Builder setPersistHostCachePeriodMillis(long persistHostCachePeriodMillis) { this.mPersistHostCachePeriodMillis = persistHostCachePeriodMillis; return this; } /** * Same as {@link #setPersistHostCachePeriodMillis(long)} but using {@link * java.time.Duration}. * * @return the builder for chaining */ @RequiresApi(api = VERSION_CODES.O) public Builder setPersistDelay(Duration persistToDiskPeriod) { return setPersistHostCachePeriodMillis(persistToDiskPeriod.toMillis()); } /** * Creates and returns the final {@link DnsOptions} instance, based on the values in this * builder. */ public DnsOptions build() { return new DnsOptions(this); } } /** * An annotation for APIs which are not considered stable yet. * *

Experimental APIs are subject to change, breakage, or removal at any time and may not be * production ready. * *

It's highly recommended to reach out to Cronet maintainers * (net-dev@chromium.org) before using one of the APIs annotated as experimental * outside of debugging and proof-of-concept code. * *

By using an Experimental API, applications acknowledge that they are doing so at their own * risk. */ @RequiresOptIn public @interface Experimental {} }