fix(net): enable QUIC keep-alive to survive backpressure stalls
This commit is contained in:
parent
f465cfeeaf
commit
20d36624c7
|
|
@ -6,9 +6,10 @@
|
|||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use quinn::crypto::rustls::{QuicClientConfig, QuicServerConfig};
|
||||
use quinn::{ClientConfig, Endpoint, ServerConfig};
|
||||
use quinn::{ClientConfig, Endpoint, IdleTimeout, ServerConfig, TransportConfig, VarInt};
|
||||
use rustls::DigitallySignedStruct;
|
||||
use rustls::SignatureScheme;
|
||||
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
|
||||
|
|
@ -20,6 +21,29 @@ use crate::error::NetError;
|
|||
/// The Application-Layer Protocol Negotiation identifier for the Synvael protocol.
|
||||
pub const ALPN: &[u8] = b"synvael";
|
||||
|
||||
/// Interval between QUIC keep-alive probes, in milliseconds.
|
||||
///
|
||||
/// Kept well below [`MAX_IDLE_TIMEOUT_MS`] so several probes elapse before the idle timeout could fire. Keep-alives are required because chunk delivery deliberately stalls the stream when the client cannot mesh fast enough: during such a flow-control stall no application data flows in either direction, and without a probe the connection would be indistinguishable from a dead peer and closed on the idle timeout.
|
||||
const KEEP_ALIVE_INTERVAL_MS: u32 = 5_000;
|
||||
|
||||
/// Maximum time with no received packets before a connection is considered lost, in milliseconds.
|
||||
const MAX_IDLE_TIMEOUT_MS: u32 = 30_000;
|
||||
|
||||
/// Builds the QUIC transport configuration shared by both endpoints.
|
||||
///
|
||||
/// Enables keep-alive probes and sets an explicit idle timeout; see [`KEEP_ALIVE_INTERVAL_MS`] for why probes are mandatory given the chunk stream's backpressure behaviour. All other transport parameters retain their `quinn` defaults.
|
||||
fn transport_config() -> Arc<TransportConfig> {
|
||||
let mut transport = TransportConfig::default();
|
||||
transport.keep_alive_interval(Some(Duration::from_millis(u64::from(
|
||||
KEEP_ALIVE_INTERVAL_MS,
|
||||
))));
|
||||
// `VarInt::from_u32` is infallible, so no fallible `IdleTimeout::try_from(Duration)` conversion is needed.
|
||||
transport.max_idle_timeout(Some(IdleTimeout::from(VarInt::from_u32(
|
||||
MAX_IDLE_TIMEOUT_MS,
|
||||
))));
|
||||
Arc::new(transport)
|
||||
}
|
||||
|
||||
/// Installs the process-wide default `rustls` `CryptoProvider` if one is not already installed.
|
||||
fn ensure_crypto_provider() {
|
||||
if rustls::crypto::ring::default_provider()
|
||||
|
|
@ -49,7 +73,8 @@ pub fn server_endpoint(bind: SocketAddr) -> Result<Endpoint, NetError> {
|
|||
tls_config.alpn_protocols = vec![ALPN.to_vec()];
|
||||
|
||||
let quic_config = QuicServerConfig::try_from(tls_config)?;
|
||||
let server_config = ServerConfig::with_crypto(Arc::new(quic_config));
|
||||
let mut server_config = ServerConfig::with_crypto(Arc::new(quic_config));
|
||||
server_config.transport_config(transport_config());
|
||||
|
||||
Ok(Endpoint::server(server_config, bind)?)
|
||||
}
|
||||
|
|
@ -69,7 +94,8 @@ pub fn client_endpoint() -> Result<Endpoint, NetError> {
|
|||
tls_config.alpn_protocols = vec![ALPN.to_vec()];
|
||||
|
||||
let quic_config = QuicClientConfig::try_from(tls_config)?;
|
||||
let client_config = ClientConfig::new(Arc::new(quic_config));
|
||||
let mut client_config = ClientConfig::new(Arc::new(quic_config));
|
||||
client_config.transport_config(transport_config());
|
||||
|
||||
let mut endpoint = Endpoint::client("0.0.0.0:0".parse().map_err(std::io::Error::other)?)?;
|
||||
endpoint.set_default_client_config(client_config);
|
||||
|
|
|
|||
Loading…
Reference in a new issue