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