This commit is contained in:
Mateusz Gruszczyński committed 2026-10-05 17:05:06 +02:00
1 parent d168ee2afa
commit ddc5c6593f
70 files changed
+2293 -594

No files matched your search

+25
View File
@@ -87,6 +87,31 @@ pub struct LocalAddr(pub SocketAddr);
pub struct Transport {
pub tls: bool,
}
fn log_protocol_error(
state: Option<&Arc<App>>,
kind: &'static str,
detail: impl std::fmt::Display,
peer: SocketAddr,
local: SocketAddr,
transport: &'static str,
http_version: &'static str,
status: u16,
tls_sni: Option<String>,
) {
if let Some(state) = state {
state.logs.push_protocol_error(
kind,
detail,
peer,
local,
transport,
http_version,
status,
tls_sni,
);
}
}
#[derive(Clone)]
pub struct ServerShutdown(pub CancellationToken);
#[derive(Clone)]
+16
View File
@@ -6,6 +6,7 @@ async fn http2_connection<I>(
local: SocketAddr,
transport: Transport,
extended_connect: bool,
frontend_state: Option<Arc<App>>,
lease: Arc<ConnectionLease>,
stop: CancellationToken,
grace: Duration,
@@ -66,6 +67,10 @@ async fn http2_connection<I>(
tokio::select! {
result = &mut conn => {
if let Err(error) = result {
log_protocol_error(
frontend_state.as_ref(), "http2_connection_error", &error, peer, local,
if transport.tls { "tls" } else { "tcp" }, "2", 0, None,
);
tracing::debug!(%error, "HTTP/2 connection closed");
}
return;
@@ -94,6 +99,17 @@ async fn http2_connection<I>(
match tokio::time::timeout(drain, &mut conn).await {
Ok(Ok(())) => tracing::debug!(?cause, "HTTP/2 graceful shutdown drained"),
Ok(Err(error)) => {
log_protocol_error(
frontend_state.as_ref(),
"http2_drain_protocol_error",
&error,
peer,
local,
if transport.tls { "tls" } else { "tcp" },
"2",
0,
None,
);
tracing::debug!(?cause, %error, "HTTP/2 graceful shutdown ended with protocol error")
}
Err(_) => tracing::debug!(
+108 -15
View File
@@ -23,6 +23,18 @@ async fn serve_inner(
let local = stream.local_addr().context("accepted local address unavailable")?;
let Ok(permit) = permits.clone().try_acquire_owned() else {
if let Some(m) = &metrics { m.rejected_connections.fetch_add(1, Ordering::Relaxed); }
let log_state = match &tls {
Some(TlsMode::Frontend(state, _)) => Some(state),
_ => frontend_state.as_ref(),
};
log_protocol_error(
log_state,
"connection_limit_rejected",
"connection concurrency limit reached",
peer, local,
if tls.is_some() { "tls" } else { "tcp" },
"", 0, None,
);
drop(stream); continue;
};
let _ = stream.set_nodelay(true);
@@ -46,16 +58,31 @@ async fn serve_inner(
_ = stop.cancelled() => return,
started = tokio::time::timeout_at(deadline, acceptor) => started,
};
let Ok(Ok(start)) = started else {
state.metrics.tls_handshake_errors.fetch_add(1, Ordering::Relaxed);
return;
let start = match started {
Ok(Ok(start)) => start,
Ok(Err(error)) => {
state.metrics.tls_handshake_errors.fetch_add(1, Ordering::Relaxed);
log_protocol_error(
Some(&state), "tls_client_hello_error", error, peer, local,
"tls", "", 0, None,
);
return;
}
Err(_) => {
state.metrics.tls_handshake_errors.fetch_add(1, Ordering::Relaxed);
log_protocol_error(
Some(&state), "tls_client_hello_timeout",
"TLS ClientHello timeout", peer, local, "tls", "", 0, None,
);
return;
}
};
let host = start
.client_hello()
.server_name()
.and_then(|name| crate::config::request_host(name).ok());
let config = {
let snapshot = state.config.load();
let host = start
.client_hello()
.server_name()
.and_then(|name| crate::config::request_host(name).ok());
let frontend = host
.as_deref()
.and_then(|host| snapshot.frontend(host, local, true));
@@ -74,7 +101,14 @@ async fn serve_inner(
snapshot.tls_http1.clone()
}
};
let Some(config) = config else { return; };
let Some(config) = config else {
log_protocol_error(
Some(&state), "tls_configuration_unavailable",
"no TLS configuration is available for this connection",
peer, local, "tls", "", 0, host.clone(),
);
return;
};
let accepted = tokio::select! {
_ = stop.cancelled() => return,
accepted = tokio::time::timeout_at(deadline, start.into_stream(config)) => accepted,
@@ -94,7 +128,7 @@ async fn serve_inner(
h2,
extended_connect,
false,
None,
Some(state.clone()),
lease,
stop,
grace,
@@ -102,7 +136,20 @@ async fn serve_inner(
)
.await;
},
_ => { state.metrics.tls_handshake_errors.fetch_add(1, Ordering::Relaxed); },
Ok(Err(error)) => {
state.metrics.tls_handshake_errors.fetch_add(1, Ordering::Relaxed);
log_protocol_error(
Some(&state), "tls_handshake_error", error, peer, local,
"tls", "", 0, host.clone(),
);
},
Err(_) => {
state.metrics.tls_handshake_errors.fetch_add(1, Ordering::Relaxed);
log_protocol_error(
Some(&state), "tls_handshake_timeout", "TLS handshake timeout",
peer, local, "tls", "", 0, host.clone(),
);
},
}
}
Some(TlsMode::Fixed(config, plaintext_router)) => {
@@ -232,7 +279,7 @@ async fn connection<I>(
h2: bool,
extended_connect: bool,
h2c_upgrade: bool,
h2c_state: Option<Arc<App>>,
frontend_state: Option<Arc<App>>,
lease: Arc<ConnectionLease>,
stop: CancellationToken,
grace: Duration,
@@ -248,6 +295,7 @@ async fn connection<I>(
local,
transport,
extended_connect,
frontend_state.clone(),
lease,
stop,
grace,
@@ -257,11 +305,12 @@ async fn connection<I>(
return;
}
let request_stop = stop.clone();
let service_frontend_state = frontend_state.clone();
let service = service_fn(move |mut request: Request<Incoming>| {
let router = router.clone();
let request_stop = request_stop.clone();
let request_lease = lease.clone();
let upgrade_state = h2c_state.clone();
let upgrade_state = service_frontend_state.clone();
async move {
request.extensions_mut().insert(PeerAddr(peer));
request.extensions_mut().insert(LocalAddr(local));
@@ -273,6 +322,17 @@ async fn connection<I>(
if h2c_attempt(&request) {
if !h2c_upgrade {
log_protocol_error(
upgrade_state.as_ref(),
"h2c_upgrade_rejected",
"h2c upgrade is disabled on this listener",
peer,
local,
"tcp",
"1.1",
400,
None,
);
return Ok::<_, Infallible>(
Response::builder()
.status(http::StatusCode::BAD_REQUEST)
@@ -284,13 +344,24 @@ async fn connection<I>(
let injected_headers = match h2_headers_frame(&request) {
Ok(frame) => frame,
Err(message) => {
log_protocol_error(
upgrade_state.as_ref(),
"h2c_upgrade_invalid",
message,
peer,
local,
"tcp",
"1.1",
400,
None,
);
return Ok::<_, Infallible>(
Response::builder()
.status(http::StatusCode::BAD_REQUEST)
.header(http::header::CACHE_CONTROL, "no-store")
.body(Body::from(message))
.expect("static h2c validation response"),
)
);
}
};
let on_upgrade = hyper::upgrade::on(&mut request);
@@ -319,6 +390,7 @@ async fn connection<I>(
local,
Transport { tls: false },
extended_connect,
upgrade_state_for_task.clone(),
upgrade_lease,
upgrade_stop,
grace,
@@ -326,7 +398,20 @@ async fn connection<I>(
)
.await;
}
Err(error) => tracing::debug!(%error, "h2c upgrade transport failed"),
Err(error) => {
log_protocol_error(
upgrade_state_for_task.as_ref(),
"h2c_transport_error",
&error,
peer,
local,
"tcp",
"2",
0,
None,
);
tracing::debug!(%error, "h2c upgrade transport failed");
}
}
};
if let Some(state) = upgrade_state {
@@ -359,7 +444,15 @@ async fn connection<I>(
.with_upgrades();
tokio::pin!(conn);
tokio::select! {
result = &mut conn => { if let Err(error) = result { tracing::debug!(%error, "HTTP/1 connection closed"); } },
result = &mut conn => {
if let Err(error) = result {
log_protocol_error(
frontend_state.as_ref(), "http1_connection_error", &error, peer, local,
if transport.tls { "tls" } else { "tcp" }, "1.1", 400, None,
);
tracing::debug!(%error, "HTTP/1 connection closed");
}
},
_ = stop.cancelled() => {
conn.as_mut().graceful_shutdown(); let _ = tokio::time::timeout(grace, &mut conn).await;
},