v0.25.5
This commit is contained in:
1 parent
1dea7f55c8
commit
c819f0a335
151 files changed
+7610
-4264
No files matched your search
+52
-15
@@ -3,6 +3,7 @@ use anyhow::{Context, Result};
|
||||
use axum::{body::Body, Router};
|
||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use http_body::Body as HttpBody;
|
||||
use hyper::{
|
||||
body::Incoming,
|
||||
server::conn::{http1, http2},
|
||||
@@ -10,7 +11,6 @@ use hyper::{
|
||||
Request, Response,
|
||||
};
|
||||
use hyper_util::rt::{TokioExecutor, TokioIo, TokioTimer};
|
||||
use http_body::Body as HttpBody;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
io,
|
||||
@@ -32,7 +32,6 @@ use tokio::{
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tower::ServiceExt;
|
||||
|
||||
|
||||
const HTTP2_PRIOR_KNOWLEDGE_PREFACE: &[u8; 24] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
|
||||
const H2_FRAME_HEADER_LEN: usize = 9;
|
||||
|
||||
@@ -45,7 +44,10 @@ fn header_has_token(headers: &http::HeaderMap, name: &str, token: &str) -> bool
|
||||
.any(|value| value.trim().eq_ignore_ascii_case(token))
|
||||
}
|
||||
|
||||
fn single_header<'a>(headers: &'a http::HeaderMap, name: &str) -> std::result::Result<&'a str, &'static str> {
|
||||
fn single_header<'a>(
|
||||
headers: &'a http::HeaderMap,
|
||||
name: &str,
|
||||
) -> std::result::Result<&'a str, &'static str> {
|
||||
let mut values = headers.get_all(name).iter();
|
||||
let value = values.next().ok_or("missing required h2c header")?;
|
||||
if values.next().is_some() {
|
||||
@@ -97,9 +99,15 @@ fn h2_headers_frame<B>(request: &Request<B>) -> std::result::Result<Bytes, &'sta
|
||||
if request.version() != http::Version::HTTP_11 {
|
||||
return Err("h2c upgrade requires HTTP/1.1");
|
||||
}
|
||||
if !header_has_token(request.headers(), http::header::CONNECTION.as_str(), "upgrade")
|
||||
|| !header_has_token(request.headers(), http::header::CONNECTION.as_str(), "http2-settings")
|
||||
{
|
||||
if !header_has_token(
|
||||
request.headers(),
|
||||
http::header::CONNECTION.as_str(),
|
||||
"upgrade",
|
||||
) || !header_has_token(
|
||||
request.headers(),
|
||||
http::header::CONNECTION.as_str(),
|
||||
"http2-settings",
|
||||
) {
|
||||
return Err("Connection must nominate Upgrade and HTTP2-Settings");
|
||||
}
|
||||
if !single_header(request.headers(), http::header::UPGRADE.as_str())?
|
||||
@@ -114,10 +122,19 @@ fn h2_headers_frame<B>(request: &Request<B>) -> std::result::Result<Bytes, &'sta
|
||||
if settings.len() > 36 || settings.len() % 6 != 0 {
|
||||
return Err("invalid HTTP2-Settings payload");
|
||||
}
|
||||
if request.headers().contains_key(http::header::TRANSFER_ENCODING) {
|
||||
if request
|
||||
.headers()
|
||||
.contains_key(http::header::TRANSFER_ENCODING)
|
||||
{
|
||||
return Err("h2c upgrade request body is not supported");
|
||||
}
|
||||
if request.headers().get_all(http::header::CONTENT_LENGTH).iter().count() > 1 {
|
||||
if request
|
||||
.headers()
|
||||
.get_all(http::header::CONTENT_LENGTH)
|
||||
.iter()
|
||||
.count()
|
||||
> 1
|
||||
{
|
||||
return Err("duplicate Content-Length on h2c upgrade");
|
||||
}
|
||||
if request
|
||||
@@ -132,7 +149,12 @@ fn h2_headers_frame<B>(request: &Request<B>) -> std::result::Result<Bytes, &'sta
|
||||
.uri()
|
||||
.authority()
|
||||
.map(|value| value.as_str())
|
||||
.or_else(|| request.headers().get(http::header::HOST).and_then(|value| value.to_str().ok()))
|
||||
.or_else(|| {
|
||||
request
|
||||
.headers()
|
||||
.get(http::header::HOST)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
})
|
||||
.ok_or("h2c upgrade requires Host/:authority")?;
|
||||
let path = request
|
||||
.uri()
|
||||
@@ -167,7 +189,9 @@ fn h2_headers_frame<B>(request: &Request<B>) -> std::result::Result<Bytes, &'sta
|
||||
if name == "te" && !value.as_bytes().eq_ignore_ascii_case(b"trailers") {
|
||||
continue;
|
||||
}
|
||||
let value = value.to_str().map_err(|_| "non-text header in h2c bootstrap")?;
|
||||
let value = value
|
||||
.to_str()
|
||||
.map_err(|_| "non-text header in h2c bootstrap")?;
|
||||
hpack_literal(&mut block, name, value);
|
||||
}
|
||||
if block.len() > 32_768 {
|
||||
@@ -215,15 +239,21 @@ impl<I> H2cUpgradeIo<I> {
|
||||
}
|
||||
|
||||
fn try_finish_bootstrap(&mut self) -> io::Result<()> {
|
||||
if self.bootstrap_done || self.bootstrap.len() < HTTP2_PRIOR_KNOWLEDGE_PREFACE.len() + H2_FRAME_HEADER_LEN {
|
||||
if self.bootstrap_done
|
||||
|| self.bootstrap.len() < HTTP2_PRIOR_KNOWLEDGE_PREFACE.len() + H2_FRAME_HEADER_LEN
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
if &self.bootstrap[..HTTP2_PRIOR_KNOWLEDGE_PREFACE.len()] != HTTP2_PRIOR_KNOWLEDGE_PREFACE {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid HTTP/2 client preface after h2c upgrade"));
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"invalid HTTP/2 client preface after h2c upgrade",
|
||||
));
|
||||
}
|
||||
let offset = HTTP2_PRIOR_KNOWLEDGE_PREFACE.len();
|
||||
let header = &self.bootstrap[offset..offset + H2_FRAME_HEADER_LEN];
|
||||
let length = ((header[0] as usize) << 16) | ((header[1] as usize) << 8) | header[2] as usize;
|
||||
let length =
|
||||
((header[0] as usize) << 16) | ((header[1] as usize) << 8) | header[2] as usize;
|
||||
if length > 16_384
|
||||
|| header[3] != 0x4
|
||||
|| header[4] != 0
|
||||
@@ -232,7 +262,10 @@ impl<I> H2cUpgradeIo<I> {
|
||||
|| header[7] != 0
|
||||
|| header[8] != 0
|
||||
{
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "h2c client preface must begin with a non-ACK SETTINGS frame"));
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"h2c client preface must begin with a non-ACK SETTINGS frame",
|
||||
));
|
||||
}
|
||||
let end = offset + H2_FRAME_HEADER_LEN + length;
|
||||
if self.bootstrap.len() < end {
|
||||
@@ -296,7 +329,11 @@ impl<I: AsyncRead + Unpin> AsyncRead for H2cUpgradeIo<I> {
|
||||
}
|
||||
|
||||
impl<I: AsyncWrite + Unpin> AsyncWrite for H2cUpgradeIo<I> {
|
||||
fn poll_write(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut TaskContext<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut self.inner).poll_write(cx, buf)
|
||||
}
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<io::Result<()>> {
|
||||
|
||||
+8
-2
@@ -93,8 +93,14 @@ 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)) => tracing::debug!(?cause, %error, "HTTP/2 graceful shutdown ended with protocol error"),
|
||||
Err(_) => tracing::debug!(?cause, drain_seconds = drain.as_secs_f64(), "HTTP/2 drain timeout reached; closing transport"),
|
||||
Ok(Err(error)) => {
|
||||
tracing::debug!(?cause, %error, "HTTP/2 graceful shutdown ended with protocol error")
|
||||
}
|
||||
Err(_) => tracing::debug!(
|
||||
?cause,
|
||||
drain_seconds = drain.as_secs_f64(),
|
||||
"HTTP/2 drain timeout reached; closing transport"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+50
-18
@@ -55,13 +55,18 @@ mod h2c_tests {
|
||||
) -> io::Result<(u8, u8, u32, Vec<u8>)> {
|
||||
let mut header = [0u8; 9];
|
||||
stream.read_exact(&mut header).await?;
|
||||
let length = ((header[0] as usize) << 16) | ((header[1] as usize) << 8) | header[2] as usize;
|
||||
let length =
|
||||
((header[0] as usize) << 16) | ((header[1] as usize) << 8) | header[2] as usize;
|
||||
if length > 64 * 1024 {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "unexpectedly large HTTP/2 test frame"));
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"unexpectedly large HTTP/2 test frame",
|
||||
));
|
||||
}
|
||||
let mut payload = vec![0u8; length];
|
||||
stream.read_exact(&mut payload).await?;
|
||||
let stream_id = u32::from_be_bytes([header[5], header[6], header[7], header[8]]) & 0x7fff_ffff;
|
||||
let stream_id =
|
||||
u32::from_be_bytes([header[5], header[6], header[7], header[8]]) & 0x7fff_ffff;
|
||||
Ok((header[3], header[4], stream_id, payload))
|
||||
}
|
||||
|
||||
@@ -72,7 +77,11 @@ mod h2c_tests {
|
||||
async fn start_direct_h2(
|
||||
router: Router,
|
||||
settings: Http2Connections,
|
||||
) -> (tokio::net::TcpStream, CancellationToken, tokio::task::JoinHandle<()>) {
|
||||
) -> (
|
||||
tokio::net::TcpStream,
|
||||
CancellationToken,
|
||||
tokio::task::JoinHandle<()>,
|
||||
) {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let address = listener.local_addr().unwrap();
|
||||
let stop = CancellationToken::new();
|
||||
@@ -100,7 +109,10 @@ mod h2c_tests {
|
||||
.await;
|
||||
});
|
||||
let mut client = tokio::net::TcpStream::connect(address).await.unwrap();
|
||||
client.write_all(HTTP2_PRIOR_KNOWLEDGE_PREFACE).await.unwrap();
|
||||
client
|
||||
.write_all(HTTP2_PRIOR_KNOWLEDGE_PREFACE)
|
||||
.await
|
||||
.unwrap();
|
||||
client
|
||||
.write_all(&[0, 0, 0, 0x4, 0, 0, 0, 0, 0])
|
||||
.await
|
||||
@@ -144,7 +156,10 @@ mod h2c_tests {
|
||||
assert!(response.starts_with("HTTP/1.1 101"), "{response}");
|
||||
assert!(response.to_ascii_lowercase().contains("upgrade: h2c"));
|
||||
|
||||
client.write_all(HTTP2_PRIOR_KNOWLEDGE_PREFACE).await.unwrap();
|
||||
client
|
||||
.write_all(HTTP2_PRIOR_KNOWLEDGE_PREFACE)
|
||||
.await
|
||||
.unwrap();
|
||||
// Client connection preface must begin with a non-ACK SETTINGS frame.
|
||||
client
|
||||
.write_all(&[0, 0, 0, 0x4, 0, 0, 0, 0, 0])
|
||||
@@ -154,12 +169,10 @@ mod h2c_tests {
|
||||
let mut saw_stream_one_headers = false;
|
||||
let mut body = Vec::new();
|
||||
for _ in 0..16 {
|
||||
let (kind, flags, stream_id, payload) = tokio::time::timeout(
|
||||
Duration::from_secs(2),
|
||||
read_h2_frame(&mut client),
|
||||
)
|
||||
.await
|
||||
.expect("HTTP/2 response frame timeout");
|
||||
let (kind, flags, stream_id, payload) =
|
||||
tokio::time::timeout(Duration::from_secs(2), read_h2_frame(&mut client))
|
||||
.await
|
||||
.expect("HTTP/2 response frame timeout");
|
||||
if kind == 0x1 && stream_id == 1 {
|
||||
saw_stream_one_headers = true;
|
||||
}
|
||||
@@ -251,7 +264,10 @@ mod h2c_tests {
|
||||
.header("http2-settings", "")
|
||||
.body(())
|
||||
.unwrap();
|
||||
client.write_all(&h2_headers_frame(&upgrade).unwrap()).await.unwrap();
|
||||
client
|
||||
.write_all(&h2_headers_frame(&upgrade).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let saw_goaway_and_close = tokio::time::timeout(Duration::from_secs(4), async {
|
||||
let mut saw_goaway = false;
|
||||
@@ -259,7 +275,12 @@ mod h2c_tests {
|
||||
match try_read_h2_frame(&mut client).await {
|
||||
Ok((0x7, _, 0, _)) => saw_goaway = true,
|
||||
Ok(_) => {}
|
||||
Err(error) if matches!(error.kind(), io::ErrorKind::UnexpectedEof | io::ErrorKind::ConnectionReset) => {
|
||||
Err(error)
|
||||
if matches!(
|
||||
error.kind(),
|
||||
io::ErrorKind::UnexpectedEof | io::ErrorKind::ConnectionReset
|
||||
) =>
|
||||
{
|
||||
return saw_goaway;
|
||||
}
|
||||
Err(_) => return saw_goaway,
|
||||
@@ -268,7 +289,10 @@ mod h2c_tests {
|
||||
})
|
||||
.await
|
||||
.expect("lifetime/drain shutdown exceeded its bound");
|
||||
assert!(saw_goaway_and_close, "lifetime shutdown must send GOAWAY before drain timeout closes TCP");
|
||||
assert!(
|
||||
saw_goaway_and_close,
|
||||
"lifetime shutdown must send GOAWAY before drain timeout closes TCP"
|
||||
);
|
||||
server.await.unwrap();
|
||||
drop(blocker);
|
||||
}
|
||||
@@ -303,7 +327,10 @@ mod h2c_tests {
|
||||
.header("http2-settings", "")
|
||||
.body(())
|
||||
.unwrap();
|
||||
client.write_all(&h2_headers_frame(&request).unwrap()).await.unwrap();
|
||||
client
|
||||
.write_all(&h2_headers_frame(&request).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
let release = blocker.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_millis(1_250)).await;
|
||||
@@ -318,7 +345,9 @@ mod h2c_tests {
|
||||
Ok((0x7, _, 0, _)) => saw_goaway = true,
|
||||
Ok((kind, flags, 1, _)) if (kind == 0x0 || kind == 0x1) && flags & 0x1 != 0 => {
|
||||
saw_end_stream = true;
|
||||
if saw_goaway { return (true, true); }
|
||||
if saw_goaway {
|
||||
return (true, true);
|
||||
}
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(_) => return (saw_goaway, saw_end_stream),
|
||||
@@ -328,7 +357,10 @@ mod h2c_tests {
|
||||
.await
|
||||
.expect("active stream did not drain within the configured bound");
|
||||
assert!(saw_goaway, "lifetime shutdown must send GOAWAY");
|
||||
assert!(saw_end_stream, "an accepted stream must be allowed to finish during drain");
|
||||
assert!(
|
||||
saw_end_stream,
|
||||
"an accepted stream must be allowed to finish during drain"
|
||||
);
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
|
||||
@@ -302,11 +302,15 @@ async fn connection<I>(
|
||||
match on_upgrade.await {
|
||||
Ok(upgraded) => {
|
||||
if let Some(state) = &upgrade_state_for_task {
|
||||
state.metrics.http2_connections.fetch_add(1, Ordering::Relaxed);
|
||||
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 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,
|
||||
|
||||
Reference in new issue
Block a user