0.34.6_repo_cleanup
This commit is contained in:
1 parent
ce1a90397c
commit
133d2f0f91
273 files changed
+5969
-6926
No files matched your search
@@ -1,6 +1,3 @@
|
||||
//! RFC 9114 transport adapter: Quinn (QUIC/TLS 1.3) + h3 (HTTP/3/QPACK).
|
||||
//! Every request uses the SAME router, ACLs, auth, cache and response policies.
|
||||
//! No full-body buffering or 0-RTT replay. RFC 9220 WebSocket Extended CONNECT is supported; WebTransport is not.
|
||||
use crate::{
|
||||
metrics::Metrics,
|
||||
runtime::App,
|
||||
@@ -63,9 +60,6 @@ pub struct Bound {
|
||||
address: SocketAddr,
|
||||
settings: Settings,
|
||||
}
|
||||
// Set the actual QUIC UDP socket buffers, not only sysctl maxima. Linux
|
||||
// doubles SO_RCVBUF/SO_SNDBUF values returned by getsockopt(2), which `ss`
|
||||
// reports as rb/tb. Target the effective buffer size shown by `ss`.
|
||||
fn requested_socket_buffer_size(effective_bytes: u32) -> usize {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
@@ -159,9 +153,6 @@ pub fn bind(listener: Listener, state: &App) -> Result<Bound> {
|
||||
settings,
|
||||
})
|
||||
}
|
||||
/// An established UDP socket is immutable until restart. Use the committed
|
||||
/// *desired* transport policy for new QUIC connections on that same socket;
|
||||
/// never apply settings from a listener planned on a different IP/port.
|
||||
fn desired_for_bound(address: SocketAddr, tls: bool, desired: &[Listener]) -> Option<Settings> {
|
||||
desired
|
||||
.iter()
|
||||
@@ -169,9 +160,6 @@ fn desired_for_bound(address: SocketAddr, tls: bool, desired: &[Listener]) -> Op
|
||||
.and_then(|listener| listener.http3)
|
||||
}
|
||||
|
||||
/// Compare immutable listener properties. H3 limits other than an increase in
|
||||
/// the startup connection capacity are hot-applied for *new* connections.
|
||||
/// UDP socket creation/removal (and H1/H2 TLS/ALPN settings) still need restart.
|
||||
pub fn listeners_require_restart(active: &[Listener], desired: &[Listener]) -> bool {
|
||||
if active.len() != desired.len() {
|
||||
return true;
|
||||
@@ -199,9 +187,6 @@ pub fn listeners_require_restart(active: &[Listener], desired: &[Listener]) -> b
|
||||
})
|
||||
}
|
||||
|
||||
/// Origin-supplied Alt-Svc is never trusted. An HTTP/3 opt-out emits `clear`
|
||||
/// on TLS responses while the existing QUIC socket is still bound, so clients
|
||||
/// are told to discard their cached alternative service immediately.
|
||||
pub fn withdraw_advertisement(
|
||||
headers: &mut HeaderMap,
|
||||
local: Option<SocketAddr>,
|
||||
@@ -220,9 +205,6 @@ pub fn withdraw_advertisement(
|
||||
}
|
||||
}
|
||||
|
||||
/// Advertise only an actually active H3 UDP listener, using the newest
|
||||
/// committed Alt-Svc lifetime. Disabling H3 for the listener or vhost publishes
|
||||
/// `Alt-Svc: clear` without waiting for a restart.
|
||||
pub fn advertise(
|
||||
headers: &mut HeaderMap,
|
||||
local: Option<SocketAddr>,
|
||||
@@ -241,7 +223,6 @@ pub fn advertise(
|
||||
let Some(bound) = active.iter().find(|binding| binding.matches(local, true)) else {
|
||||
return;
|
||||
};
|
||||
// A desired H3 enable on a socket with no UDP listener is pending restart.
|
||||
if bound.http3.is_none() {
|
||||
return;
|
||||
}
|
||||
@@ -271,17 +252,11 @@ pub async fn serve(bound: Bound, router: Router, state: Arc<App>, grace: Duratio
|
||||
let Some(incoming)=incoming else {break;};
|
||||
let peer=incoming.remote_address();
|
||||
if !incoming.remote_address_validated() {let _=incoming.retry();continue;}
|
||||
// Listener setting saves are already published through ArcSwap.
|
||||
// Apply new limits to fresh QUIC handshakes on this bound UDP
|
||||
// address, without mutating established connections.
|
||||
let desired = {
|
||||
let snapshot = state.config.load();
|
||||
desired_for_bound(bound.address, true, &snapshot.listeners)
|
||||
};
|
||||
let Some(settings) = desired else { incoming.refuse(); continue; };
|
||||
// Startup permits and Quinn's incoming capacity cannot be grown
|
||||
// without restarting the UDP endpoint. A lower limit can be
|
||||
// enforced dynamically without releasing live connection leases.
|
||||
let open = bound.settings.max_concurrent_connections as usize - slots.available_permits();
|
||||
let capacity = (settings.max_concurrent_connections as usize)
|
||||
.min(bound.settings.max_concurrent_connections as usize);
|
||||
@@ -316,9 +291,6 @@ pub async fn serve(bound: Bound, router: Router, state: Arc<App>, grace: Duratio
|
||||
};
|
||||
config.transport = Arc::new(transport);
|
||||
let Some(crypto)=state.config.load().quic_tls.clone() else {incoming.refuse();continue;};
|
||||
// Existing QUIC sessions keep their negotiated transport limits.
|
||||
// New sessions get current limits and certificate without
|
||||
// regenerating the Retry token key.
|
||||
config.crypto=crypto;
|
||||
let connecting=match incoming.accept_with(Arc::new(config)) {
|
||||
Ok(c)=>c,
|
||||
@@ -353,7 +325,6 @@ pub async fn serve(bound: Bound, router: Router, state: Arc<App>, grace: Duratio
|
||||
},
|
||||
}
|
||||
};
|
||||
// A wildcard without destination metadata must fail closed.
|
||||
let local=match connection.local_ip() {
|
||||
Some(ip)=>SocketAddr::new(ip,address.port()),
|
||||
None if !address.ip().is_unspecified()=>address,
|
||||
@@ -376,8 +347,6 @@ pub async fn serve(bound: Bound, router: Router, state: Arc<App>, grace: Duratio
|
||||
}
|
||||
}
|
||||
}
|
||||
// Connection tasks send GOAWAY and drain; only close the endpoint after the
|
||||
// grace period, not immediately after a response FIN (which can lose data).
|
||||
let _ = tokio::time::timeout(grace, async { while tasks.join_next().await.is_some() {} }).await;
|
||||
tasks.abort_all();
|
||||
while tasks.join_next().await.is_some() {}
|
||||
@@ -396,8 +365,6 @@ async fn connection_loop(
|
||||
) -> Result<()> {
|
||||
let mut builder = h3::server::builder();
|
||||
builder.max_field_section_size(32 * 1024);
|
||||
// RFC 9220 remains an explicit experimental opt-in. Avoid advertising
|
||||
// Extended CONNECT on listeners that only use classic HTTP/1.1 WebSockets.
|
||||
builder.enable_extended_connect(state.config.load().websocket_rfc9220_on(local));
|
||||
let mut h3 = builder
|
||||
.build(h3::quinn::Connection::new(connection.clone()))
|
||||
@@ -417,7 +384,6 @@ async fn connection_loop(
|
||||
_=requests.join_next(),if !requests.is_empty()=>{},
|
||||
next=h3.accept()=>match next {
|
||||
Ok(Some(resolver))=>{
|
||||
// Completed JoinSet entries are drained before accepting more.
|
||||
if requests.len()>=limit {
|
||||
log_protocol_error(
|
||||
&state, "http3_stream_limit_rejected", "HTTP/3 concurrent request stream limit reached",
|
||||
@@ -433,8 +399,6 @@ async fn connection_loop(
|
||||
Ok(Ok(request))=>request,
|
||||
Ok(Err(error))=>{
|
||||
if client_cancelled_request(&error) {
|
||||
// Record the cancellation with status=0 (no HTTP response)
|
||||
// for iOS navigation investigations, never synthetic 400.
|
||||
log_protocol_error(
|
||||
&state, "http3_peer_cancelled_before_headers",
|
||||
format!("phase=request_headers stream_status=cancelled error={error}"),
|
||||
@@ -462,11 +426,8 @@ async fn connection_loop(
|
||||
let (mut send,receive)=stream.split();
|
||||
let stream_id=format!("{:?}",send.send_id());
|
||||
let diagnostic_method=request.method().as_str().to_owned();
|
||||
// Do not record query strings or cookies in transport diagnostics.
|
||||
let diagnostic_path=request.uri().path().chars().take(160).collect::<String>();
|
||||
let (diagnostic_authority, diagnostic_host)=request_identity(&request);
|
||||
// GET / is a full document navigation (the original logo).
|
||||
// Emit only query-free, bounded navigation metadata.
|
||||
let root_navigation=diagnostic_method=="GET" && diagnostic_path=="/";
|
||||
if root_navigation { log_root_navigation(
|
||||
"started", peer, local, &tls_sni, &stream_id, "request_headers", 0, 0, false,
|
||||
@@ -487,14 +448,9 @@ async fn connection_loop(
|
||||
let expected=request.headers().get("content-length").and_then(|v|v.to_str().ok()).and_then(|v|v.parse::<u64>().ok());
|
||||
let mut tunnel_receive=None;
|
||||
let mut request=if websocket {
|
||||
// RFC 9220 DATA belongs to the WebSocket byte stream, not
|
||||
// to an HTTP request body. Keep the receive half for the
|
||||
// transport bridge after the router accepts the tunnel.
|
||||
tunnel_receive=Some(receive);
|
||||
request.map(|_|Body::empty())
|
||||
} else if extended_connect {
|
||||
// Unsupported Extended CONNECT protocols are answered by
|
||||
// the router (501). They are not interpreted as HTTP bodies.
|
||||
drop(receive);
|
||||
request.map(|_|Body::empty())
|
||||
} else {
|
||||
@@ -522,17 +478,11 @@ async fn connection_loop(
|
||||
};
|
||||
drop(body);
|
||||
if let Err(error)=crate::proxy::websocket::run_h3(state.clone(),&mut send,&mut receive,tunnel).await {
|
||||
// run_h3 owns RFC 9220 tunnel shutdown/reset semantics.
|
||||
// Do not overwrite its H3_REQUEST_CANCELLED with a
|
||||
// second generic H3_INTERNAL_ERROR here.
|
||||
tracing::debug!(%error,"HTTP/3 WebSocket tunnel closed");
|
||||
}
|
||||
return Ok::<(),anyhow::Error>(());
|
||||
}
|
||||
if let Some(mut receive)=tunnel_receive.take() {
|
||||
// The WebSocket bootstrap was rejected before becoming
|
||||
// a tunnel. Stop accepting request DATA after sending the
|
||||
// ordinary HTTP error response.
|
||||
receive.stop_sending(Code::H3_REQUEST_CANCELLED);
|
||||
}
|
||||
while let Some(frame)=body.frame().await {
|
||||
@@ -569,8 +519,6 @@ async fn connection_loop(
|
||||
response_status, sent_body_bytes, cancelled,
|
||||
); }
|
||||
if !cancelled {
|
||||
// Never mark a truncated upstream response as success.
|
||||
// Reset only this response stream, not sibling requests.
|
||||
send.stop_stream(Code::H3_INTERNAL_ERROR);
|
||||
}
|
||||
} else if root_navigation {
|
||||
@@ -579,9 +527,6 @@ async fn connection_loop(
|
||||
} else {
|
||||
log_h3_response_complete(peer, &stream_id, response_status, sent_body_bytes);
|
||||
}
|
||||
// Connection-level cumulative counters (not per-stream
|
||||
// deltas). Useful for distinguishing QUIC loss/cwnd/MTU
|
||||
// limitations from application CPU when benchmarking.
|
||||
if sent_body_bytes >= 8 * 1024 * 1024 || expected.is_some_and(|n| n >= 8 * 1024 * 1024) {
|
||||
let stats = quic_diagnostics.stats();
|
||||
tracing::debug!(
|
||||
@@ -604,10 +549,6 @@ async fn connection_loop(
|
||||
},
|
||||
Ok(None)=>break,
|
||||
Err(error)=>{
|
||||
// The parser/QUIC transport can stop accepting after a peer
|
||||
// refresh while unrelated response streams are still alive.
|
||||
// Abort_all here reset those streams (including a new GET /).
|
||||
// Stop accepting and allow the bounded drain below instead.
|
||||
log_protocol_error(
|
||||
&state, "http3_accept_error", &error,
|
||||
connection.remote_address(), local, 0, None, None, tls_sni.clone(),
|
||||
@@ -624,7 +565,6 @@ async fn connection_loop(
|
||||
.await;
|
||||
requests.abort_all();
|
||||
while requests.join_next().await.is_some() {}
|
||||
// Retain the transport until the client closes or the bounded drain expires.
|
||||
if draining {
|
||||
let _ = tokio::time::timeout(grace, connection.closed()).await;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user