diff --git a/.github/workflows/ci_rust.yml b/.github/workflows/ci_rust.yml index a726e612962..b53c1408da5 100644 --- a/.github/workflows/ci_rust.yml +++ b/.github/workflows/ci_rust.yml @@ -49,10 +49,10 @@ jobs: working-directory: ${{env.ROOT_PATH}} run: cargo test - - name: "Feature Tests: Fingerprint, kTLS, QUIC, PQ, and http2" + - name: "Feature Tests: Fingerprint, kTLS, QUIC, and PQ" working-directory: ${{env.ROOT_PATH}} # Test all features except for FIPS, which is tested separately. - run: cargo test --features unstable-fingerprint,unstable-ktls,quic,pq,http2 + run: cargo test --features unstable-fingerprint,unstable-ktls,quic,pq - name: "Feature Test: Renegotiate" working-directory: ${{env.ROOT_PATH}} diff --git a/bindings/rust/integration/src/network/https_client.rs b/bindings/rust/integration/src/network/https_client.rs index 84c90e5a10a..d778f0f4ea1 100644 --- a/bindings/rust/integration/src/network/https_client.rs +++ b/bindings/rust/integration/src/network/https_client.rs @@ -51,7 +51,7 @@ const TEST_CASES: &[TestCase] = &[ TestCase::new("https://www.samsung.com", 301), TestCase::new("https://www.twitter.com", 301), TestCase::new("https://www.facebook.com", 302), - TestCase::new("https://www.microsoft.com", 302), + TestCase::new("https://www.microsoft.com", 200), TestCase::new("https://www.ibm.com", 303), TestCase::new("https://www.f5.com", 403), ]; diff --git a/bindings/rust/s2n-tls-hyper/Cargo.toml b/bindings/rust/s2n-tls-hyper/Cargo.toml index 865346a97e7..03c71401ed9 100644 --- a/bindings/rust/s2n-tls-hyper/Cargo.toml +++ b/bindings/rust/s2n-tls-hyper/Cargo.toml @@ -10,22 +10,20 @@ license = "Apache-2.0" publish = false [features] -default = ["http1"] -http1 = ["hyper-util/http1"] -http2 = ["hyper-util/http2", "dep:hashbrown", "dep:tokio-util"] +default = [] [dependencies] s2n-tls = { version = "=0.3.7", path = "../s2n-tls" } s2n-tls-tokio = { version = "=0.3.7", path = "../s2n-tls-tokio" } hyper = { version = "1" } -hyper-util = { version = "0.1", features = ["client-legacy", "tokio"] } +hyper-util = { version = "0.1", features = ["client-legacy", "tokio", "http1", "http2"] } tower-service = { version = "0.3" } http = { version = "1" } # Newer versions require Rust 1.65, see https://github.com/aws/s2n-tls/issues/4242. -hashbrown = { version = "=0.15.0", optional = true } +hashbrown = { version = "=0.15.0" } # Newer versions require Rust 1.70, see https://github.com/aws/s2n-tls/issues/4395. -tokio-util = { version = "=0.7.11", optional = true } +tokio-util = { version = "=0.7.11" } [dev-dependencies] tokio = { version = "1", features = ["macros", "test-util"] } diff --git a/bindings/rust/s2n-tls-hyper/src/connector.rs b/bindings/rust/s2n-tls-hyper/src/connector.rs index a160e605471..d4b8aa79f60 100644 --- a/bindings/rust/s2n-tls-hyper/src/connector.rs +++ b/bindings/rust/s2n-tls-hyper/src/connector.rs @@ -41,6 +41,10 @@ where /// /// This API creates an `HttpsConnector` using the default hyper `HttpConnector`. To use an /// existing HTTP connector, use `HttpsConnector::new_with_http()`. + /// + /// Note that the HttpsConnector will automatically attempt to negotiate HTTP/2 by overriding + /// the ALPN extension. Any ALPN values configured on `conn_builder` with APIs like + /// `s2n_tls::config::Builder::set_application_protocol_preference()` will be ignored. pub fn new(conn_builder: Builder) -> HttpsConnector { let mut http = HttpConnector::new(); @@ -77,6 +81,10 @@ where /// ``` /// /// `HttpsConnector::new()` can be used to create the HTTP connector automatically. + /// + /// Note that the HttpsConnector will automatically attempt to negotiate HTTP/2 by overriding + /// the ALPN extension. Any ALPN values configured on `conn_builder` with APIs like + /// `s2n_tls::config::Builder::set_application_protocol_preference()` will be ignored. pub fn new_with_http(http: Http, conn_builder: Builder) -> HttpsConnector { Self { http, conn_builder } } @@ -118,7 +126,11 @@ where return Box::pin(async move { Err(Error::InvalidScheme) }); } - let builder = self.conn_builder.clone(); + // Attempt to negotiate HTTP/2. + let builder = connection::ModifiedBuilder::new(self.conn_builder.clone(), |conn| { + conn.set_application_protocol_preference([b"h2"]) + }); + let host = req.host().unwrap_or("").to_owned(); let call = self.http.call(req); Box::pin(async move { diff --git a/bindings/rust/s2n-tls-hyper/src/stream.rs b/bindings/rust/s2n-tls-hyper/src/stream.rs index 5af60c5f067..c5440285465 100644 --- a/bindings/rust/s2n-tls-hyper/src/stream.rs +++ b/bindings/rust/s2n-tls-hyper/src/stream.rs @@ -53,7 +53,6 @@ where let conn = stream.inner().as_ref(); match conn.application_protocol() { // Inform hyper that HTTP/2 was negotiated in the ALPN. - #[cfg(feature = "http2")] Some(b"h2") => connected.negotiated_h2(), _ => connected, } diff --git a/bindings/rust/s2n-tls-hyper/tests/http.rs b/bindings/rust/s2n-tls-hyper/tests/http.rs index f49359a99f8..3f22a32952b 100644 --- a/bindings/rust/s2n-tls-hyper/tests/http.rs +++ b/bindings/rust/s2n-tls-hyper/tests/http.rs @@ -218,23 +218,17 @@ async fn error_matching() -> Result<(), Box> { #[tokio::test] async fn http2() -> Result<(), Box> { - let server_config = { - let mut builder = common::config()?; - builder.set_application_protocol_preference(["h2"])?; - builder.build()? - }; - - for send_h2 in [true, false] { - let client_config = { + for expected_http_version in [Version::HTTP_11, Version::HTTP_2] { + let server_config = { let mut builder = common::config()?; - if send_h2 { + if expected_http_version == Version::HTTP_2 { builder.set_application_protocol_preference(["h2"])?; } builder.build()? }; common::echo::make_echo_request(server_config.clone(), |port| async move { - let connector = HttpsConnector::new(client_config); + let connector = HttpsConnector::new(common::config()?.build()?); let client: Client<_, Empty> = Client::builder(TokioExecutor::new()).build(connector); @@ -242,19 +236,8 @@ async fn http2() -> Result<(), Box> { let response = client.get(uri).await?; assert_eq!(response.status(), 200); - // Ensure that HTTP/2 is negotiated when included in the ALPN. - #[cfg(feature = "http2")] - let expected_version = match send_h2 { - true => Version::HTTP_2, - false => Version::HTTP_11, - }; - - // If the http2 feature isn't enabled, then HTTP/1 should be negotiated even if HTTP/2 - // was included in the ALPN. - #[cfg(not(feature = "http2"))] - let expected_version = Version::HTTP_11; - - assert_eq!(response.version(), expected_version); + // Ensure that HTTP/2 is negotiated when supported by the server. + assert_eq!(response.version(), expected_http_version); Ok(()) }) @@ -263,3 +246,38 @@ async fn http2() -> Result<(), Box> { Ok(()) } + +/// Ensure that HTTP/2 is negotiated, regardless of any pre-configured ALPN values. +#[tokio::test] +async fn config_alpn_ignored() -> Result<(), Box> { + let server_config = { + let mut builder = common::config()?; + builder.set_application_protocol_preference(["h2"])?; + builder.build()? + }; + + common::echo::make_echo_request(server_config, |port| async move { + let client_config = { + let mut builder = common::config()?; + // Set an arbitrary non-HTTP/2 ALPN value. + builder.set_application_protocol_preference([b"http/1.1"])?; + builder.build()? + }; + + let connector = HttpsConnector::new(client_config); + let client: Client<_, Empty> = + Client::builder(TokioExecutor::new()).build(connector); + + let uri = Uri::from_str(format!("https://localhost:{}", port).as_str())?; + let response = client.get(uri).await?; + assert_eq!(response.status(), 200); + + // Ensure that HTTP/2 was negotiated. + assert_eq!(response.version(), Version::HTTP_2); + + Ok(()) + }) + .await?; + + Ok(()) +}