From e39dace8cb4b1868e811fd13b87f2a81e84b98ce Mon Sep 17 00:00:00 2001 From: Andreu Botella Date: Mon, 25 Oct 2021 09:41:06 -0700 Subject: [PATCH] fix(tls): Make TLS clients support HTTP/2 (#12530) `fetch()` and client-side websocket used to support HTTP/2, but this regressed in #11491. This patch reenables it by explicitly adding `h2` and `http/1.1` to the list of ALPN protocols on the HTTP and websocket clients. --- cli/tests/unit/fetch_test.ts | 36 ++++++++ ext/tls/lib.rs | 2 + test_util/Cargo.toml | 2 +- test_util/src/lib.rs | 156 +++++++++++++++++++++++++++++++---- 4 files changed, 180 insertions(+), 16 deletions(-) diff --git a/cli/tests/unit/fetch_test.ts b/cli/tests/unit/fetch_test.ts index a2bd1741b6..bc61d67b52 100644 --- a/cli/tests/unit/fetch_test.ts +++ b/cli/tests/unit/fetch_test.ts @@ -1324,3 +1324,39 @@ unitTest( }), TypeError); }, ); + +unitTest( + { permissions: { net: true, read: true } }, + async function fetchSupportsHttp1Only() { + const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"); + const client = Deno.createHttpClient({ caCerts: [caCert] }); + const res = await fetch("https://localhost:5546/http_version", { client }); + assert(res.ok); + assertEquals(await res.text(), "HTTP/1.1"); + client.close(); + }, +); + +unitTest( + { permissions: { net: true, read: true } }, + async function fetchSupportsHttp2() { + const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"); + const client = Deno.createHttpClient({ caCerts: [caCert] }); + const res = await fetch("https://localhost:5547/http_version", { client }); + assert(res.ok); + assertEquals(await res.text(), "HTTP/2.0"); + client.close(); + }, +); + +unitTest( + { permissions: { net: true, read: true } }, + async function fetchPrefersHttp2() { + const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"); + const client = Deno.createHttpClient({ caCerts: [caCert] }); + const res = await fetch("https://localhost:5545/http_version", { client }); + assert(res.ok); + assertEquals(await res.text(), "HTTP/2.0"); + client.close(); + }, +); diff --git a/ext/tls/lib.rs b/ext/tls/lib.rs index 076ef59fb7..fb4fac85b6 100644 --- a/ext/tls/lib.rs +++ b/ext/tls/lib.rs @@ -238,6 +238,8 @@ pub fn create_http_client( .expect("invalid client key or certificate"); } + tls_config.alpn_protocols = vec!["h2".into(), "http/1.1".into()]; + let mut headers = HeaderMap::new(); headers.insert(USER_AGENT, user_agent.parse().unwrap()); let mut builder = Client::builder() diff --git a/test_util/Cargo.toml b/test_util/Cargo.toml index 92523ac817..e93793392a 100644 --- a/test_util/Cargo.toml +++ b/test_util/Cargo.toml @@ -17,7 +17,7 @@ async-stream = "0.3.2" atty = "0.2.14" base64 = "0.13.0" futures = "0.3.16" -hyper = { version = "0.14.12", features = ["server", "http1", "runtime"] } +hyper = { version = "0.14.12", features = ["server", "http1", "http2", "runtime"] } lazy_static = "1.4.0" os_pipe = "0.9.2" regex = "1.5.4" diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index f20601340a..52924ac909 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -61,6 +61,8 @@ const TLS_CLIENT_AUTH_PORT: u16 = 4552; const BASIC_AUTH_REDIRECT_PORT: u16 = 4554; const TLS_PORT: u16 = 4557; const HTTPS_PORT: u16 = 5545; +const H1_ONLY_PORT: u16 = 5546; +const H2_ONLY_PORT: u16 = 5547; const HTTPS_CLIENT_AUTH_PORT: u16 = 5552; const WS_PORT: u16 = 4242; const WSS_PORT: u16 = 4243; @@ -290,10 +292,22 @@ async fn run_ws_close_server(addr: &SocketAddr) { } } +enum SupportedHttpVersions { + All, + Http1Only, + Http2Only, +} +impl Default for SupportedHttpVersions { + fn default() -> SupportedHttpVersions { + SupportedHttpVersions::All + } +} + async fn get_tls_config( cert: &str, key: &str, ca: &str, + http_versions: SupportedHttpVersions, ) -> io::Result> { let cert_path = testdata_path().join(cert); let key_path = testdata_path().join(key); @@ -336,6 +350,15 @@ async fn get_tls_config( let allow_client_auth = rustls::AllowAnyAnonymousOrAuthenticatedClient::new(root_cert_store); let mut config = rustls::ServerConfig::new(allow_client_auth); + match http_versions { + SupportedHttpVersions::All => { + config.set_protocols(&["h2".into(), "http/1.1".into()]); + } + SupportedHttpVersions::Http1Only => {} + SupportedHttpVersions::Http2Only => { + config.set_protocols(&["h2".into()]); + } + } config .set_single_cert(cert, key) .map_err(|e| { @@ -354,9 +377,10 @@ async fn run_wss_server(addr: &SocketAddr) { let key_file = "tls/localhost.key"; let ca_cert_file = "tls/RootCA.pem"; - let tls_config = get_tls_config(cert_file, key_file, ca_cert_file) - .await - .unwrap(); + let tls_config = + get_tls_config(cert_file, key_file, ca_cert_file, Default::default()) + .await + .unwrap(); let tls_acceptor = TlsAcceptor::from(tls_config); let listener = TcpListener::bind(addr).await.unwrap(); println!("ready: wss"); // Eye catcher for HttpServerCount @@ -396,9 +420,10 @@ async fn run_tls_client_auth_server() { let cert_file = "tls/localhost.crt"; let key_file = "tls/localhost.key"; let ca_cert_file = "tls/RootCA.pem"; - let tls_config = get_tls_config(cert_file, key_file, ca_cert_file) - .await - .unwrap(); + let tls_config = + get_tls_config(cert_file, key_file, ca_cert_file, Default::default()) + .await + .unwrap(); let tls_acceptor = TlsAcceptor::from(tls_config); // Listen on ALL addresses that localhost can resolves to. @@ -459,9 +484,10 @@ async fn run_tls_server() { let cert_file = "tls/localhost.crt"; let key_file = "tls/localhost.key"; let ca_cert_file = "tls/RootCA.pem"; - let tls_config = get_tls_config(cert_file, key_file, ca_cert_file) - .await - .unwrap(); + let tls_config = + get_tls_config(cert_file, key_file, ca_cert_file, Default::default()) + .await + .unwrap(); let tls_acceptor = TlsAcceptor::from(tls_config); // Listen on ALL addresses that localhost can resolves to. @@ -819,6 +845,10 @@ async fn main_server(req: Request) -> hyper::Result> { Ok(Response::new(Body::empty())) } } + (_, "/http_version") => { + let version = format!("{:?}", req.version()); + Ok(Response::new(version.into())) + } _ => { let mut file_path = testdata_path(); file_path.push(&req.uri().path()[1..]); @@ -959,9 +989,10 @@ async fn wrap_main_https_server() { let cert_file = "tls/localhost.crt"; let key_file = "tls/localhost.key"; let ca_cert_file = "tls/RootCA.pem"; - let tls_config = get_tls_config(cert_file, key_file, ca_cert_file) - .await - .unwrap(); + let tls_config = + get_tls_config(cert_file, key_file, ca_cert_file, Default::default()) + .await + .unwrap(); loop { let tcp = TcpListener::bind(&main_server_https_addr) .await @@ -993,15 +1024,106 @@ async fn wrap_main_https_server() { } } +async fn wrap_https_h1_only_server() { + let main_server_https_addr = SocketAddr::from(([127, 0, 0, 1], H1_ONLY_PORT)); + let cert_file = "tls/localhost.crt"; + let key_file = "tls/localhost.key"; + let ca_cert_file = "tls/RootCA.pem"; + let tls_config = get_tls_config( + cert_file, + key_file, + ca_cert_file, + SupportedHttpVersions::Http1Only, + ) + .await + .unwrap(); + loop { + let tcp = TcpListener::bind(&main_server_https_addr) + .await + .expect("Cannot bind TCP"); + println!("ready: https"); // Eye catcher for HttpServerCount + let tls_acceptor = TlsAcceptor::from(tls_config.clone()); + // Prepare a long-running future stream to accept and serve cients. + let incoming_tls_stream = async_stream::stream! { + loop { + let (socket, _) = tcp.accept().await?; + let stream = tls_acceptor.accept(socket); + yield stream.await; + } + } + .boxed(); + + let main_server_https_svc = make_service_fn(|_| async { + Ok::<_, Infallible>(service_fn(main_server)) + }); + let main_server_https = Server::builder(HyperAcceptor { + acceptor: incoming_tls_stream, + }) + .http1_only(true) + .serve(main_server_https_svc); + + //continue to prevent TLS error stopping the server + if main_server_https.await.is_err() { + continue; + } + } +} + +async fn wrap_https_h2_only_server() { + let main_server_https_addr = SocketAddr::from(([127, 0, 0, 1], H2_ONLY_PORT)); + let cert_file = "tls/localhost.crt"; + let key_file = "tls/localhost.key"; + let ca_cert_file = "tls/RootCA.pem"; + let tls_config = get_tls_config( + cert_file, + key_file, + ca_cert_file, + SupportedHttpVersions::Http2Only, + ) + .await + .unwrap(); + loop { + let tcp = TcpListener::bind(&main_server_https_addr) + .await + .expect("Cannot bind TCP"); + println!("ready: https"); // Eye catcher for HttpServerCount + let tls_acceptor = TlsAcceptor::from(tls_config.clone()); + // Prepare a long-running future stream to accept and serve cients. + let incoming_tls_stream = async_stream::stream! { + loop { + let (socket, _) = tcp.accept().await?; + let stream = tls_acceptor.accept(socket); + yield stream.await; + } + } + .boxed(); + + let main_server_https_svc = make_service_fn(|_| async { + Ok::<_, Infallible>(service_fn(main_server)) + }); + let main_server_https = Server::builder(HyperAcceptor { + acceptor: incoming_tls_stream, + }) + .http2_only(true) + .serve(main_server_https_svc); + + //continue to prevent TLS error stopping the server + if main_server_https.await.is_err() { + continue; + } + } +} + async fn wrap_client_auth_https_server() { let main_server_https_addr = SocketAddr::from(([127, 0, 0, 1], HTTPS_CLIENT_AUTH_PORT)); let cert_file = "tls/localhost.crt"; let key_file = "tls/localhost.key"; let ca_cert_file = "tls/RootCA.pem"; - let tls_config = get_tls_config(cert_file, key_file, ca_cert_file) - .await - .unwrap(); + let tls_config = + get_tls_config(cert_file, key_file, ca_cert_file, Default::default()) + .await + .unwrap(); loop { let tcp = TcpListener::bind(&main_server_https_addr) .await @@ -1078,6 +1200,8 @@ pub async fn run_all_servers() { let client_auth_server_https_fut = wrap_client_auth_https_server(); let main_server_fut = wrap_main_server(); let main_server_https_fut = wrap_main_https_server(); + let h1_only_server_fut = wrap_https_h1_only_server(); + let h2_only_server_fut = wrap_https_h2_only_server(); let mut server_fut = async { futures::join!( @@ -1096,6 +1220,8 @@ pub async fn run_all_servers() { main_server_fut, main_server_https_fut, client_auth_server_https_fut, + h1_only_server_fut, + h2_only_server_fut ) } .boxed();