v0.25.1
This commit is contained in:
1 parent
87e571c170
commit
fa7abc9e53
308 files changed
+49746
-50237
No files matched your search
@@ -0,0 +1,370 @@
|
||||
async fn serve_inner(
|
||||
listener: TcpListener,
|
||||
router: Router,
|
||||
shutdown: CancellationToken,
|
||||
permits: Arc<Semaphore>,
|
||||
grace: Duration,
|
||||
metrics: Option<Arc<Metrics>>,
|
||||
tls: Option<TlsMode>,
|
||||
h2c: bool,
|
||||
frontend_state: Option<Arc<App>>,
|
||||
http2_settings: Http2Connections,
|
||||
) -> Result<()> {
|
||||
let mut tasks = JoinSet::new();
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.cancelled() => break,
|
||||
_ = tasks.join_next(), if !tasks.is_empty() => {},
|
||||
accepted = listener.accept() => {
|
||||
let (stream, peer) = accepted.context("listener accept failed")?;
|
||||
// Never substitute the wildcard listener address or a forwarded
|
||||
// header for the destination IP of this accepted connection.
|
||||
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); }
|
||||
drop(stream); continue;
|
||||
};
|
||||
let _ = stream.set_nodelay(true);
|
||||
if let Some(m) = &metrics {
|
||||
m.connections.fetch_add(1, Ordering::Relaxed); m.active_connections.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
let lease = Arc::new(ConnectionLease { _permit: permit, metrics: metrics.clone() });
|
||||
let router = router.clone(); let stop = shutdown.clone(); let tls = tls.clone(); let metrics = metrics.clone();
|
||||
let frontend_state = frontend_state.clone();
|
||||
tasks.spawn(async move {
|
||||
match tls {
|
||||
Some(TlsMode::Frontend(state, http2)) => {
|
||||
// Read ClientHello before committing to ALPN so a vhost can opt out
|
||||
// of HTTP/2 or narrow TLS policy while sharing one TLS listener.
|
||||
let deadline = tokio::time::Instant::now() + Duration::from_secs(10);
|
||||
let acceptor = tokio_rustls::LazyConfigAcceptor::new(
|
||||
rustls::server::Acceptor::default(),
|
||||
stream,
|
||||
);
|
||||
let started = tokio::select! {
|
||||
_ = 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 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));
|
||||
let local_host = host
|
||||
.as_deref()
|
||||
.and_then(|host| snapshot.not_found_host_on(host, local, true));
|
||||
if let Some(frontend) = frontend {
|
||||
if http2 && frontend.http2_enabled {
|
||||
frontend.tls.clone()
|
||||
} else {
|
||||
frontend.tls_http1.clone()
|
||||
}
|
||||
} else if http2 && local_host.is_none_or(|item| item.http2_enabled) {
|
||||
snapshot.tls.clone()
|
||||
} else {
|
||||
snapshot.tls_http1.clone()
|
||||
}
|
||||
};
|
||||
let Some(config) = config else { return; };
|
||||
let accepted = tokio::select! {
|
||||
_ = stop.cancelled() => return,
|
||||
accepted = tokio::time::timeout_at(deadline, start.into_stream(config)) => accepted,
|
||||
};
|
||||
match accepted {
|
||||
Ok(Ok(stream)) => {
|
||||
let h2 = stream.get_ref().1.alpn_protocol() == Some(b"h2".as_slice());
|
||||
state.metrics.tls_connections.fetch_add(1, Ordering::Relaxed);
|
||||
if h2 { state.metrics.http2_connections.fetch_add(1, Ordering::Relaxed); }
|
||||
let extended_connect = h2 && state.config.load().websocket_rfc8441_on(local, true);
|
||||
connection(
|
||||
stream,
|
||||
router,
|
||||
peer,
|
||||
local,
|
||||
Transport { tls: true },
|
||||
h2,
|
||||
extended_connect,
|
||||
false,
|
||||
None,
|
||||
lease,
|
||||
stop,
|
||||
grace,
|
||||
http2_settings,
|
||||
)
|
||||
.await;
|
||||
},
|
||||
_ => { state.metrics.tls_handshake_errors.fetch_add(1, Ordering::Relaxed); },
|
||||
}
|
||||
}
|
||||
Some(TlsMode::Fixed(config, plaintext_router)) => {
|
||||
let deadline = tokio::time::Instant::now() + Duration::from_secs(10);
|
||||
if let Some(plain) = plaintext_router {
|
||||
let mut first = [0u8; 1];
|
||||
let peeked = tokio::select! {
|
||||
_ = stop.cancelled() => return,
|
||||
peeked = tokio::time::timeout_at(deadline, stream.peek(&mut first)) => peeked,
|
||||
};
|
||||
match peeked {
|
||||
Ok(Ok(n)) if n > 0 && first[0] != 0x16 => {
|
||||
connection(
|
||||
stream,
|
||||
plain,
|
||||
peer,
|
||||
local,
|
||||
Transport { tls: false },
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
lease,
|
||||
stop,
|
||||
grace,
|
||||
Http2Connections::default(),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
Ok(Ok(n)) if n > 0 => {}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
let acceptor = tokio_rustls::TlsAcceptor::from(config);
|
||||
let accepted = tokio::select! {
|
||||
_ = stop.cancelled() => return,
|
||||
accepted = tokio::time::timeout_at(deadline, acceptor.accept(stream)) => accepted,
|
||||
};
|
||||
if let Ok(Ok(stream)) = accepted {
|
||||
let h2 = stream.get_ref().1.alpn_protocol() == Some(b"h2".as_slice());
|
||||
connection(
|
||||
stream,
|
||||
router,
|
||||
peer,
|
||||
local,
|
||||
Transport { tls: true },
|
||||
h2,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
lease,
|
||||
stop,
|
||||
grace,
|
||||
Http2Connections::default(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let prior_h2 = if h2c {
|
||||
let detected = tokio::time::timeout(Duration::from_secs(10), async {
|
||||
let mut preface = [0u8; HTTP2_PRIOR_KNOWLEDGE_PREFACE.len()];
|
||||
loop {
|
||||
let n = stream.peek(&mut preface).await?;
|
||||
if n == 0 {
|
||||
return Ok::<bool, std::io::Error>(false);
|
||||
}
|
||||
let prefix = &preface[..n.min(HTTP2_PRIOR_KNOWLEDGE_PREFACE.len())];
|
||||
if !h2_prior_knowledge_prefix(prefix) {
|
||||
return Ok(false);
|
||||
}
|
||||
if n >= HTTP2_PRIOR_KNOWLEDGE_PREFACE.len() {
|
||||
return Ok(prefix == HTTP2_PRIOR_KNOWLEDGE_PREFACE);
|
||||
}
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
}).await;
|
||||
matches!(detected, Ok(Ok(true)))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if prior_h2 {
|
||||
if let Some(m) = &metrics { m.http2_connections.fetch_add(1, Ordering::Relaxed); }
|
||||
}
|
||||
let extended_connect = prior_h2
|
||||
&& frontend_state
|
||||
.as_ref()
|
||||
.is_some_and(|state| state.config.load().websocket_rfc8441_on(local, false));
|
||||
connection(
|
||||
stream,
|
||||
router,
|
||||
peer,
|
||||
local,
|
||||
Transport { tls: false },
|
||||
prior_h2,
|
||||
extended_connect,
|
||||
h2c && !prior_h2,
|
||||
frontend_state,
|
||||
lease,
|
||||
stop,
|
||||
grace,
|
||||
http2_settings,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
let _ = tokio::time::timeout(grace + Duration::from_secs(1), async {
|
||||
while tasks.join_next().await.is_some() {}
|
||||
})
|
||||
.await;
|
||||
tasks.abort_all();
|
||||
while tasks.join_next().await.is_some() {}
|
||||
Ok(())
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn connection<I>(
|
||||
stream: I,
|
||||
router: Router,
|
||||
peer: SocketAddr,
|
||||
local: SocketAddr,
|
||||
transport: Transport,
|
||||
h2: bool,
|
||||
extended_connect: bool,
|
||||
h2c_upgrade: bool,
|
||||
h2c_state: Option<Arc<App>>,
|
||||
lease: Arc<ConnectionLease>,
|
||||
stop: CancellationToken,
|
||||
grace: Duration,
|
||||
http2_settings: Http2Connections,
|
||||
) where
|
||||
I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
{
|
||||
if h2 {
|
||||
http2_connection(
|
||||
stream,
|
||||
router,
|
||||
peer,
|
||||
local,
|
||||
transport,
|
||||
extended_connect,
|
||||
lease,
|
||||
stop,
|
||||
grace,
|
||||
http2_settings,
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
let request_stop = stop.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();
|
||||
async move {
|
||||
request.extensions_mut().insert(PeerAddr(peer));
|
||||
request.extensions_mut().insert(LocalAddr(local));
|
||||
request.extensions_mut().insert(transport);
|
||||
request.extensions_mut().insert(request_lease.clone());
|
||||
request
|
||||
.extensions_mut()
|
||||
.insert(ServerShutdown(request_stop.clone()));
|
||||
|
||||
if h2c_attempt(&request) {
|
||||
if !h2c_upgrade {
|
||||
return Ok::<_, Infallible>(
|
||||
Response::builder()
|
||||
.status(http::StatusCode::BAD_REQUEST)
|
||||
.header(http::header::CACHE_CONTROL, "no-store")
|
||||
.body(Body::from("h2c upgrade is disabled on this listener"))
|
||||
.expect("static h2c disabled response"),
|
||||
);
|
||||
}
|
||||
let injected_headers = match h2_headers_frame(&request) {
|
||||
Ok(frame) => frame,
|
||||
Err(message) => {
|
||||
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);
|
||||
let upgrade_router = router.clone();
|
||||
let upgrade_stop = request_stop.clone();
|
||||
let upgrade_lease = request_lease.clone();
|
||||
let upgrade_state_for_task = upgrade_state.clone();
|
||||
let task = async move {
|
||||
match on_upgrade.await {
|
||||
Ok(upgraded) => {
|
||||
if let Some(state) = &upgrade_state_for_task {
|
||||
state.metrics.http2_connections.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
let extended_connect = upgrade_state_for_task.as_ref().is_some_and(|state| {
|
||||
state.config.load().websocket_rfc8441_on(local, false)
|
||||
});
|
||||
let io = H2cUpgradeIo::new(TokioIo::new(upgraded), injected_headers);
|
||||
http2_connection(
|
||||
io,
|
||||
upgrade_router,
|
||||
peer,
|
||||
local,
|
||||
Transport { tls: false },
|
||||
extended_connect,
|
||||
upgrade_lease,
|
||||
upgrade_stop,
|
||||
grace,
|
||||
http2_settings,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Err(error) => tracing::debug!(%error, "h2c upgrade transport failed"),
|
||||
}
|
||||
};
|
||||
if let Some(state) = upgrade_state {
|
||||
state.tunnels.spawn(task);
|
||||
} else {
|
||||
tokio::spawn(task);
|
||||
}
|
||||
return Ok::<_, Infallible>(
|
||||
Response::builder()
|
||||
.status(http::StatusCode::SWITCHING_PROTOCOLS)
|
||||
.header(http::header::CONNECTION, "Upgrade")
|
||||
.header(http::header::UPGRADE, "h2c")
|
||||
.body(Body::empty())
|
||||
.expect("static h2c switching response"),
|
||||
);
|
||||
}
|
||||
|
||||
router.oneshot(request.map(Body::new)).await
|
||||
}
|
||||
});
|
||||
let mut builder = http1::Builder::new();
|
||||
builder
|
||||
.timer(TokioTimer::new())
|
||||
.keep_alive(true)
|
||||
.max_headers(64)
|
||||
.max_buf_size(65_536)
|
||||
.header_read_timeout(Duration::from_secs(10));
|
||||
let conn = builder
|
||||
.serve_connection(TokioIo::new(stream), service)
|
||||
.with_upgrades();
|
||||
tokio::pin!(conn);
|
||||
tokio::select! {
|
||||
result = &mut conn => { if let Err(error) = result { tracing::debug!(%error, "HTTP/1 connection closed"); } },
|
||||
_ = stop.cancelled() => {
|
||||
conn.as_mut().graceful_shutdown(); let _ = tokio::time::timeout(grace, &mut conn).await;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum H2ShutdownCause {
|
||||
Idle,
|
||||
Lifetime,
|
||||
Process,
|
||||
}
|
||||
Reference in new issue
Block a user