mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
net: Optimize initial TLS connection (#44242)
By touching the crypto provider, we can force it to gather entropy. On my linux VM, that moved ~60ms off the critical path (using aws-lc-rs, but probably any other crypto provider would show similar behavior). On my Linux workstation it was around ~30ms. On Linux caching the rustls platform verifier cache optimizes another 50ms. On other platforms this will be cheaper, since only on some systems all certificates are read. It might make sense to explore caching the whole tlsconfig (for websockets), since it looks like we are just cloning the same components and then constructing a new tlsconfig, which would lead to the same effective component. But that needs more investigation. Testing: Doesn't change any visible behavior, covered by existing tests. --------- Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
committed by
GitHub
parent
09ef444d63
commit
97c08a6f95
@@ -74,8 +74,8 @@ servo-default-resources = { workspace = true, optional = true }
|
||||
servo-tracing = { workspace = true }
|
||||
servo-url = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
tracing = { workspace = true, optional = true }
|
||||
time = { workspace = true }
|
||||
tracing = { workspace = true, optional = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync", "fs"] }
|
||||
tokio-rustls = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use std::collections::hash_map::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use std::time::Duration;
|
||||
use std::{fmt, io};
|
||||
|
||||
@@ -377,6 +377,7 @@ pub enum CACertificates<'de> {
|
||||
/// FIXME: The `ignore_certificate_errors` argument ignores all certificate errors. This
|
||||
/// is used when running the WPT tests, because rustls currently rejects the WPT certificiate.
|
||||
/// See <https://github.com/servo/servo/issues/30080>
|
||||
#[servo_tracing::instrument(skip_all)]
|
||||
pub fn create_tls_config(
|
||||
ca_certificates: CACertificates<'static>,
|
||||
ignore_certificate_errors: bool,
|
||||
@@ -387,6 +388,8 @@ pub fn create_tls_config(
|
||||
ignore_certificate_errors,
|
||||
override_manager,
|
||||
);
|
||||
// TODO: After <https://github.com/rustls/rustls-platform-verifier/pull/204> is merged,
|
||||
// `dangerous` can be removed.
|
||||
rustls::ClientConfig::builder()
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(verifier))
|
||||
@@ -405,6 +408,54 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
static CRYPTO_PROVIDER_CACHE: LazyLock<Arc<CryptoProvider>> = LazyLock::new(|| {
|
||||
CryptoProvider::get_default()
|
||||
.cloned()
|
||||
// The embedder should have initialized the default crypto provider before
|
||||
// initializing servo, so this should never fail.
|
||||
.unwrap_or_else(|| {
|
||||
warn!("Default crypto provider not initialized before first access in connector.");
|
||||
Arc::new(aws_lc_rs::default_provider())
|
||||
})
|
||||
});
|
||||
|
||||
/// A cache for the default rustls platform verifier.
|
||||
///
|
||||
/// Instantiating a new verifier can be expensive, since it can read through all certificates:
|
||||
/// <https://github.com/rustls/rustls-platform-verifier/blob/996b1c903491641b17b3c9afb65d1352f6fc6b76/rustls-platform-verifier/src/verification/others.rs#L92>
|
||||
static RUSTLS_PLATFORM_VERIFIER_CACHE: LazyLock<Arc<rustls_platform_verifier::Verifier>> =
|
||||
LazyLock::new(|| {
|
||||
Arc::new(
|
||||
rustls_platform_verifier::Verifier::new(CRYPTO_PROVIDER_CACHE.clone())
|
||||
.expect("Could not initialize platform certificate verifier"),
|
||||
)
|
||||
});
|
||||
|
||||
/// Prewarm the TLS stack to speed up the first connection
|
||||
///
|
||||
/// Currently, this force-seeds the crypto provider (from aws_lc_rs),
|
||||
/// which on my system takes around 30-50ms according to samply, spent in
|
||||
/// `tree_jitter_initialize_once`. If we don't call this function, then
|
||||
/// the initialization will happen much later, on a tokio runtime thread.
|
||||
#[inline]
|
||||
pub fn prewarm_tls() {
|
||||
#[servo_tracing::instrument]
|
||||
fn prewarm_tls_impl() {
|
||||
let mut sink = [0u8; 32];
|
||||
// The first access can be slow, if the provider needs to gather entropy.
|
||||
let _ = CRYPTO_PROVIDER_CACHE.secure_random.fill(&mut sink);
|
||||
// Note: We don't need to explicitly force initialize RUSTLS_PLATFORM_VERIFIER_CACHE,
|
||||
// since the resource manager thread will do that during startup.
|
||||
}
|
||||
|
||||
if let Err(error) = std::thread::Builder::new()
|
||||
.name("Net-TLS-prewarm".into())
|
||||
.spawn(prewarm_tls_impl)
|
||||
{
|
||||
warn!("Failed to spawn thread to prewarm TLS: {error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CertificateVerificationOverrideVerifier {
|
||||
main_verifier: Arc<dyn ServerCertVerifier>,
|
||||
@@ -428,25 +479,25 @@ impl CertificateVerificationOverrideVerifier {
|
||||
// on Android.
|
||||
let use_webpki_roots = cfg!(target_os = "android") || pref!(network_use_webpki_roots);
|
||||
let main_verifier = if !use_webpki_roots {
|
||||
let crypto_provider = CryptoProvider::get_default()
|
||||
.unwrap_or(&Arc::new(aws_lc_rs::default_provider()))
|
||||
.clone();
|
||||
let verifier = match ca_certficates {
|
||||
CACertificates::Default => rustls_platform_verifier::Verifier::new(crypto_provider),
|
||||
CACertificates::Default => RUSTLS_PLATFORM_VERIFIER_CACHE.clone(),
|
||||
// Android doesn't support `Verifier::new_with_extra_roots`, but currently Android
|
||||
// never uses the platform verifier at all.
|
||||
CACertificates::Override(_certificates) => {
|
||||
#[cfg(target_os = "android")]
|
||||
unreachable!("Android should always use the WebPKI verifier.");
|
||||
#[cfg(not(target_os = "android"))]
|
||||
rustls_platform_verifier::Verifier::new_with_extra_roots(
|
||||
_certificates,
|
||||
crypto_provider,
|
||||
)
|
||||
{
|
||||
let verifier = rustls_platform_verifier::Verifier::new_with_extra_roots(
|
||||
_certificates,
|
||||
CRYPTO_PROVIDER_CACHE.clone(),
|
||||
)
|
||||
.expect("Could not initialize platform certificate verifier");
|
||||
Arc::new(verifier)
|
||||
}
|
||||
},
|
||||
}
|
||||
.expect("Could not initialize platform certificate verifier");
|
||||
Arc::new(verifier) as Arc<dyn ServerCertVerifier>
|
||||
};
|
||||
verifier as Arc<dyn ServerCertVerifier>
|
||||
} else {
|
||||
let mut root_store =
|
||||
rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
|
||||
@@ -948,6 +948,8 @@ impl Servo {
|
||||
private_storage_threads.clone(),
|
||||
);
|
||||
|
||||
net::connector::prewarm_tls();
|
||||
|
||||
if opts::get().multiprocess {
|
||||
prefs::add_observer(Box::new(constellation_proxy.clone()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user