• 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 androidx.annotation.Nullable;
8 import androidx.annotation.RequiresOptIn;
9 
10 import java.util.Collections;
11 import java.util.LinkedHashSet;
12 import java.util.Set;
13 
14 /**
15  * Configuration options for QUIC in Cronet.
16  *
17  * <p>The settings in this class are only relevant if QUIC is enabled. Use
18  * {@link org.chromium.net.CronetEngine.Builder#enableQuic(boolean)} to enable / disable QUIC for
19  * the Cronet engine.
20  */
21 public class QuicOptions {
22     private final Set<String> mQuicHostAllowlist;
23     private final Set<String> mEnabledQuicVersions;
24 
25     private final Set<String> mConnectionOptions;
26     private final Set<String> mClientConnectionOptions;
27     @Nullable private final Integer mInMemoryServerConfigsCacheSize;
28     @Nullable private final String mHandshakeUserAgent;
29     @Nullable private final Boolean mRetryWithoutAltSvcOnQuicErrors;
30     @Nullable private final Boolean mEnableTlsZeroRtt;
31 
32     @Nullable private final Long mPreCryptoHandshakeIdleTimeoutSeconds;
33     @Nullable private final Long mCryptoHandshakeTimeoutSeconds;
34 
35     @Nullable private final Long mIdleConnectionTimeoutSeconds;
36     @Nullable private final Long mRetransmittableOnWireTimeoutMillis;
37 
38     @Nullable private final Boolean mCloseSessionsOnIpChange;
39     @Nullable private final Boolean mGoawaySessionsOnIpChange;
40 
41     @Nullable private final Long mInitialBrokenServicePeriodSeconds;
42     @Nullable private final Boolean mIncreaseBrokenServicePeriodExponentially;
43     @Nullable private final Boolean mDelayJobsWithAvailableSpdySession;
44 
45     private final Set<String> mExtraQuicheFlags;
46 
QuicOptions(Builder builder)47     QuicOptions(Builder builder) {
48         this.mQuicHostAllowlist =
49                 Collections.unmodifiableSet(new LinkedHashSet<>(builder.mQuicHostAllowlist));
50         this.mEnabledQuicVersions =
51                 Collections.unmodifiableSet(new LinkedHashSet<>(builder.mEnabledQuicVersions));
52         this.mConnectionOptions =
53                 Collections.unmodifiableSet(new LinkedHashSet<>(builder.mConnectionOptions));
54         this.mClientConnectionOptions =
55                 Collections.unmodifiableSet(new LinkedHashSet<>(builder.mClientConnectionOptions));
56         this.mInMemoryServerConfigsCacheSize = builder.mInMemoryServerConfigsCacheSize;
57         this.mHandshakeUserAgent = builder.mHandshakeUserAgent;
58         this.mRetryWithoutAltSvcOnQuicErrors = builder.mRetryWithoutAltSvcOnQuicErrors;
59         this.mEnableTlsZeroRtt = builder.mEnableTlsZeroRtt;
60         this.mPreCryptoHandshakeIdleTimeoutSeconds = builder.mPreCryptoHandshakeIdleTimeoutSeconds;
61         this.mCryptoHandshakeTimeoutSeconds = builder.mCryptoHandshakeTimeoutSeconds;
62         this.mIdleConnectionTimeoutSeconds = builder.mIdleConnectionTimeoutSeconds;
63         this.mRetransmittableOnWireTimeoutMillis = builder.mRetransmittableOnWireTimeoutMillis;
64         this.mCloseSessionsOnIpChange = builder.mCloseSessionsOnIpChange;
65         this.mGoawaySessionsOnIpChange = builder.mGoawaySessionsOnIpChange;
66         this.mInitialBrokenServicePeriodSeconds = builder.mInitialBrokenServicePeriodSeconds;
67         this.mIncreaseBrokenServicePeriodExponentially =
68                 builder.mIncreaseBrokenServicePeriodExponentially;
69         this.mDelayJobsWithAvailableSpdySession = builder.mDelayJobsWithAvailableSpdySession;
70         this.mExtraQuicheFlags =
71                 Collections.unmodifiableSet(new LinkedHashSet<>(builder.mExtraQuicheFlags));
72     }
73 
getQuicHostAllowlist()74     public Set<String> getQuicHostAllowlist() {
75         return mQuicHostAllowlist;
76     }
77 
getEnabledQuicVersions()78     public Set<String> getEnabledQuicVersions() {
79         return mEnabledQuicVersions;
80     }
81 
getConnectionOptions()82     public Set<String> getConnectionOptions() {
83         return mConnectionOptions;
84     }
85 
getClientConnectionOptions()86     public Set<String> getClientConnectionOptions() {
87         return mClientConnectionOptions;
88     }
89 
90     @Nullable
getInMemoryServerConfigsCacheSize()91     public Integer getInMemoryServerConfigsCacheSize() {
92         return mInMemoryServerConfigsCacheSize;
93     }
94 
95     @Nullable
getHandshakeUserAgent()96     public String getHandshakeUserAgent() {
97         return mHandshakeUserAgent;
98     }
99 
100     @Nullable
getRetryWithoutAltSvcOnQuicErrors()101     public Boolean getRetryWithoutAltSvcOnQuicErrors() {
102         return mRetryWithoutAltSvcOnQuicErrors;
103     }
104 
105     @Nullable
getEnableTlsZeroRtt()106     public Boolean getEnableTlsZeroRtt() {
107         return mEnableTlsZeroRtt;
108     }
109 
110     @Nullable
getPreCryptoHandshakeIdleTimeoutSeconds()111     public Long getPreCryptoHandshakeIdleTimeoutSeconds() {
112         return mPreCryptoHandshakeIdleTimeoutSeconds;
113     }
114 
115     @Nullable
getCryptoHandshakeTimeoutSeconds()116     public Long getCryptoHandshakeTimeoutSeconds() {
117         return mCryptoHandshakeTimeoutSeconds;
118     }
119 
120     @Nullable
getIdleConnectionTimeoutSeconds()121     public Long getIdleConnectionTimeoutSeconds() {
122         return mIdleConnectionTimeoutSeconds;
123     }
124 
125     @Nullable
getRetransmittableOnWireTimeoutMillis()126     public Long getRetransmittableOnWireTimeoutMillis() {
127         return mRetransmittableOnWireTimeoutMillis;
128     }
129 
130     @Nullable
getCloseSessionsOnIpChange()131     public Boolean getCloseSessionsOnIpChange() {
132         return mCloseSessionsOnIpChange;
133     }
134 
135     @Nullable
getGoawaySessionsOnIpChange()136     public Boolean getGoawaySessionsOnIpChange() {
137         return mGoawaySessionsOnIpChange;
138     }
139 
140     @Nullable
getInitialBrokenServicePeriodSeconds()141     public Long getInitialBrokenServicePeriodSeconds() {
142         return mInitialBrokenServicePeriodSeconds;
143     }
144 
145     @Nullable
getIncreaseBrokenServicePeriodExponentially()146     public Boolean getIncreaseBrokenServicePeriodExponentially() {
147         return mIncreaseBrokenServicePeriodExponentially;
148     }
149 
150     @Nullable
getDelayJobsWithAvailableSpdySession()151     public Boolean getDelayJobsWithAvailableSpdySession() {
152         return mDelayJobsWithAvailableSpdySession;
153     }
154 
getExtraQuicheFlags()155     public Set<String> getExtraQuicheFlags() {
156         return mExtraQuicheFlags;
157     }
158 
builder()159     public static Builder builder() {
160         return new Builder();
161     }
162 
163     /** Builder for {@link QuicOptions}. */
164     public static class Builder {
165         private final Set<String> mQuicHostAllowlist = new LinkedHashSet<>();
166         private final Set<String> mEnabledQuicVersions = new LinkedHashSet<>();
167         private final Set<String> mConnectionOptions = new LinkedHashSet<>();
168         private final Set<String> mClientConnectionOptions = new LinkedHashSet<>();
169         @Nullable private Integer mInMemoryServerConfigsCacheSize;
170         @Nullable private String mHandshakeUserAgent;
171         @Nullable private Boolean mRetryWithoutAltSvcOnQuicErrors;
172         @Nullable private Boolean mEnableTlsZeroRtt;
173         @Nullable private Long mPreCryptoHandshakeIdleTimeoutSeconds;
174         @Nullable private Long mCryptoHandshakeTimeoutSeconds;
175         @Nullable private Long mIdleConnectionTimeoutSeconds;
176         @Nullable private Long mRetransmittableOnWireTimeoutMillis;
177         @Nullable private Boolean mCloseSessionsOnIpChange;
178         @Nullable private Boolean mGoawaySessionsOnIpChange;
179         @Nullable private Long mInitialBrokenServicePeriodSeconds;
180         @Nullable private Boolean mIncreaseBrokenServicePeriodExponentially;
181         @Nullable private Boolean mDelayJobsWithAvailableSpdySession;
182         @Nullable private final Set<String> mExtraQuicheFlags = new LinkedHashSet<>();
183 
Builder()184         Builder() {}
185 
186         /**
187          * Adds a host to the QUIC allowlist.
188          *
189          * <p>If no hosts are specified, the per-host allowlist functionality is disabled.
190          * Otherwise, Cronet will only use QUIC when talking to hosts on the allowlist.
191          *
192          * @return the builder for chaining
193          */
addAllowedQuicHost(String quicHost)194         public Builder addAllowedQuicHost(String quicHost) {
195             mQuicHostAllowlist.add(quicHost);
196             return this;
197         }
198 
199         /**
200          * Adds a QUIC version to the list of QUIC versions to enable.
201          *
202          * <p>If no versions are specified, Cronet will use a list of default QUIC versions.
203          *
204          * <p>The version format is specified by
205          * <a
206          * href="https://github.com/google/quiche/blob/main/quiche/quic/core/quic_versions.cc#L344">QUICHE</a>.
207          * Outside of filtering out values known to be obsolete, Cronet doesn't process the versions
208          * anyhow and simply passes them along to QUICHE.
209          *
210          * @return the builder for chaining
211          */
212         @QuichePassthroughOption
addEnabledQuicVersion(String enabledQuicVersion)213         public Builder addEnabledQuicVersion(String enabledQuicVersion) {
214             mEnabledQuicVersions.add(enabledQuicVersion);
215             return this;
216         }
217 
218         /**
219          * Adds a QUIC tag to send in a QUIC handshake's connection options.
220          *
221          * <p>The QUIC tags should be presented as strings up to four letters long
222          * (for instance, {@code NBHD}).
223          *
224          * <p>As the QUIC tags are under active development and some are only relevant to the
225          * server, Cronet doesn't attempt to maintain a complete list of all supported QUIC flags as
226          * a part of the API. The flags. Flags supported by QUICHE, a QUIC implementation used by
227          * Cronet and Google servers, can be found <a
228          * href=https://github.com/google/quiche/blob/main/quiche/quic/core/crypto/crypto_protocol.h">here</a>.
229          *
230          * @return the builder for chaining
231          */
232         @QuichePassthroughOption
addConnectionOption(String connectionOption)233         public Builder addConnectionOption(String connectionOption) {
234             mConnectionOptions.add(connectionOption);
235             return this;
236         }
237 
238         /**
239          * Adds a QUIC tag to send in a QUIC handshake's connection options that only affects
240          * the client.
241          *
242          * <p>See {@link #addConnectionOption(String)} for more details.
243          */
244         @QuichePassthroughOption
addClientConnectionOption(String clientConnectionOption)245         public Builder addClientConnectionOption(String clientConnectionOption) {
246             mClientConnectionOptions.add(clientConnectionOption);
247             return this;
248         }
249 
250         /**
251          * Sets how many server configurations (metadata like list of alt svc, whether QUIC is
252          * supported, etc.) should be held in memory.
253          *
254          * <p>If the storage path is set ({@link
255          * org.chromium.net.CronetEngine.Builder#setStoragePath(String)}, Cronet will also persist
256          * the server configurations on disk.
257          *
258          * @return the builder for chaining
259          */
setInMemoryServerConfigsCacheSize(int inMemoryServerConfigsCacheSize)260         public Builder setInMemoryServerConfigsCacheSize(int inMemoryServerConfigsCacheSize) {
261             this.mInMemoryServerConfigsCacheSize = inMemoryServerConfigsCacheSize;
262             return this;
263         }
264 
265         /**
266          * Sets the user agent to be used outside of HTTP requests (for example for QUIC
267          * handshakes).
268          *
269          * <p>To set the default user agent for HTTP requests, use
270          * {@link CronetEngine.Builder#setUserAgent(String)} instead.
271          *
272          * @return the builder for chaining
273          */
setHandshakeUserAgent(String handshakeUserAgent)274         public Builder setHandshakeUserAgent(String handshakeUserAgent) {
275             this.mHandshakeUserAgent = handshakeUserAgent;
276             return this;
277         }
278 
279         /**
280          * Sets whether requests that failed with a QUIC protocol errors should be retried without
281          * using any {@code alt-svc} servers.
282          *
283          * @return the builder for chaining
284          */
285         @Experimental
retryWithoutAltSvcOnQuicErrors(boolean retryWithoutAltSvcOnQuicErrors)286         public Builder retryWithoutAltSvcOnQuicErrors(boolean retryWithoutAltSvcOnQuicErrors) {
287             this.mRetryWithoutAltSvcOnQuicErrors = retryWithoutAltSvcOnQuicErrors;
288             return this;
289         }
290 
291         /**
292          * Sets whether TLS with 0-RTT should be enabled.
293          *
294          * <p>0-RTT is a performance optimization avoiding an extra round trip when resuming
295          * connections to a known server.
296          *
297          * @see <a href="https://blog.cloudflare.com/introducing-0-rtt/">Cloudflare's 0-RTT
298          *         blogpost</a>
299          *
300          * @return the builder for chaining
301          */
302         @Experimental
enableTlsZeroRtt(boolean enableTlsZeroRtt)303         public Builder enableTlsZeroRtt(boolean enableTlsZeroRtt) {
304             this.mEnableTlsZeroRtt = enableTlsZeroRtt;
305             return this;
306         }
307 
308         /**
309          * Sets the maximum idle time for a connection which hasn't completed a SSL handshake yet.
310          *
311          * @return the builder for chaining
312          */
313         @Experimental
setPreCryptoHandshakeIdleTimeoutSeconds( long preCryptoHandshakeIdleTimeoutSeconds)314         public Builder setPreCryptoHandshakeIdleTimeoutSeconds(
315                 long preCryptoHandshakeIdleTimeoutSeconds) {
316             this.mPreCryptoHandshakeIdleTimeoutSeconds = preCryptoHandshakeIdleTimeoutSeconds;
317             return this;
318         }
319 
320         /**
321          * Sets the timeout for a connection SSL handshake.
322          *
323          * @return the builder for chaining
324          */
325         @Experimental
setCryptoHandshakeTimeoutSeconds(long cryptoHandshakeTimeoutSeconds)326         public Builder setCryptoHandshakeTimeoutSeconds(long cryptoHandshakeTimeoutSeconds) {
327             this.mCryptoHandshakeTimeoutSeconds = cryptoHandshakeTimeoutSeconds;
328             return this;
329         }
330 
331         /**
332          * Sets the maximum idle time for a connection.
333          *
334          * TODO what happens to connection that are idle for too long?
335          *
336          * @return the builder for chaining
337          */
setIdleConnectionTimeoutSeconds(long idleConnectionTimeoutSeconds)338         public Builder setIdleConnectionTimeoutSeconds(long idleConnectionTimeoutSeconds) {
339             this.mIdleConnectionTimeoutSeconds = idleConnectionTimeoutSeconds;
340             return this;
341         }
342 
343         /**
344          * Sets the maximum desired time between packets on wire.
345          *
346          * <p>When the retransmittable-on-wire time is exceeded Cronet will probe quality of the
347          * network using artificial traffic. Smaller timeouts will typically  result in faster
348          * discovery of a broken or degrading path, but also larger usage of resources (battery,
349          * data).
350          *
351          * @return the builder for chaining
352          */
353         @Experimental
setRetransmittableOnWireTimeoutMillis( long retransmittableOnWireTimeoutMillis)354         public Builder setRetransmittableOnWireTimeoutMillis(
355                 long retransmittableOnWireTimeoutMillis) {
356             this.mRetransmittableOnWireTimeoutMillis = retransmittableOnWireTimeoutMillis;
357             return this;
358         }
359 
360         /**
361          * Sets whether QUIC sessions should be closed on IP address change.
362          *
363          * <p>Don't use in combination with connection migration
364          * (configured using {@link ConnectionMigrationOptions}).
365          *
366          * @return the builder for chaining
367          */
368         @Experimental
closeSessionsOnIpChange(boolean closeSessionsOnIpChange)369         public Builder closeSessionsOnIpChange(boolean closeSessionsOnIpChange) {
370             this.mCloseSessionsOnIpChange = closeSessionsOnIpChange;
371             return this;
372         }
373 
374         /**
375          * Sets whether QUIC sessions should be goaway'd on IP address change.
376          *
377          * <p>Don't use in combination with connection migration
378          * (configured using {@link ConnectionMigrationOptions}).
379          *
380          * @return the builder for chaining
381          */
382         @Experimental
goawaySessionsOnIpChange(boolean goawaySessionsOnIpChange)383         public Builder goawaySessionsOnIpChange(boolean goawaySessionsOnIpChange) {
384             this.mGoawaySessionsOnIpChange = goawaySessionsOnIpChange;
385             return this;
386         }
387 
388         /**
389          * Sets the initial for which Cronet shouldn't attempt to use QUIC for a given server after
390          * the server's QUIC support turned out to be broken.
391          *
392          * <p>Once Cronet detects that a server advertises QUIC but doesn't actually speak it, it
393          * marks the server as broken and doesn't attempt to use QUIC when talking to the server for
394          * an amount of time. Once Cronet is past this point it will try using QUIC again. This is
395          * to balance short term (there's no point wasting resources to try QUIC if the server is
396          * broken) and long term (the breakage might have been temporary, using QUIC is generally
397          * beneficial) interests.
398          *
399          * <p>The delay is increased every unsuccessful consecutive retry. See
400          * {@link #increaseBrokenServicePeriodExponentially(boolean)} for details.
401          *
402          * @return the builder for chaining
403          */
404         @Experimental
setInitialBrokenServicePeriodSeconds( long initialBrokenServicePeriodSeconds)405         public Builder setInitialBrokenServicePeriodSeconds(
406                 long initialBrokenServicePeriodSeconds) {
407             this.mInitialBrokenServicePeriodSeconds = initialBrokenServicePeriodSeconds;
408             return this;
409         }
410 
411         /**
412          * Sets whether the broken server period should scale exponentially.
413          *
414          * <p>If set to true, the initial delay (configurable
415          * by {@link #setInitialBrokenServicePeriodSeconds}) will be scaled exponentially for
416          * subsequent retries ({@code SCALING_FACTOR^NUM_TRIES * delay}). If false, the delay will
417          * scale linearly (SCALING_FACTOR * NUM_TRIES * delay).
418          *
419          * @return the builder for chaining
420          */
421         @Experimental
increaseBrokenServicePeriodExponentially( boolean increaseBrokenServicePeriodExponentially)422         public Builder increaseBrokenServicePeriodExponentially(
423                 boolean increaseBrokenServicePeriodExponentially) {
424             this.mIncreaseBrokenServicePeriodExponentially =
425                     increaseBrokenServicePeriodExponentially;
426             return this;
427         }
428 
429         /**
430          * Sets whether Cronet should wait for the primary path (usually QUIC) to be ready even if
431          * there's a secondary path of reaching the server (SPDY / HTTP2) which is ready
432          * immediately.
433          *
434          * @return the builder for chaining
435          */
436         @Experimental
delayJobsWithAvailableSpdySession( boolean delayJobsWithAvailableSpdySession)437         public Builder delayJobsWithAvailableSpdySession(
438                 boolean delayJobsWithAvailableSpdySession) {
439             this.mDelayJobsWithAvailableSpdySession = delayJobsWithAvailableSpdySession;
440             return this;
441         }
442 
443         /**
444          * Sets an arbitrary QUICHE flag. Flags should be passed in {@code FLAG_NAME=FLAG_VALUE}
445          * format.
446          *
447          * See the <a href="https://github.com/google/quiche/">QUICHE code base</a> for a full list
448          * of flags.
449          *
450          * @return the builder for chaining
451          */
452         @QuichePassthroughOption
addExtraQuicheFlag(String extraQuicheFlag)453         public Builder addExtraQuicheFlag(String extraQuicheFlag) {
454             this.mExtraQuicheFlags.add(extraQuicheFlag);
455             return this;
456         }
457 
458         /**
459          * Creates and returns the final {@link QuicOptions} instance, based on the values
460          * in this builder.
461          */
build()462         public QuicOptions build() {
463             return new QuicOptions(this);
464         }
465     }
466 
467     /**
468      * An annotation for APIs which are not considered stable yet.
469      *
470      * <p>Experimental APIs are subject to change, breakage, or removal at any time and may not be
471      * production ready.
472      *
473      * <p>It's highly recommended to reach out to Cronet maintainers
474      * (<code>net-dev@chromium.org</code>) before using one of the APIs annotated as experimental
475      * outside of debugging and proof-of-concept code.
476      *
477      * <p>By using an Experimental API, applications acknowledge that they are doing so at their own
478      * risk.
479      */
480     @RequiresOptIn
481     public @interface Experimental {}
482 
483     /**
484      * An annotation for APIs which configure QUICHE options not curated by Cronet.
485      *
486      * <p>APIs annotated by this are considered stable from Cronet's perspective. However, they
487      * simply pass the configuration options to QUICHE, a library that provides the HTTP3
488      * implementation. As the dependency is under active development those flags might change
489      * behavior, or get deleted. The application accepts the stability contract as stated by QUICHE.
490      * Cronet is just a mediator passing the messages back and forth.
491      *
492      * <p>Cronet provides the APIs as a compromise between customer development velocity (some
493      * customers value access to bleeding edge QUICHE features ASAP), and Cronet's own interests
494      * (stability and readability of the API, capacity to propagate new QUICHE changes). Most Cronet
495      * customers shouldn't need to use those APIs directly. Mature QUICHE features that are
496      * generally useful will be exposed by Cronet as proper top level APIs or configuration options.
497      */
498     @RequiresOptIn
499     public @interface QuichePassthroughOption {}
500 }
501