Repository navigation
Allow clients to select ALPN protocols instead of forcing HTTP/2 - #161
Conversation
GetTLSConfigurationForClient has hardcoded NextProtos to {"h2"} since google#72,
which was added in 2022 when s2a-go was only used for gRPC. The HTTP entry
points added later, NewTLSClientConfigFactory and NewS2ADialTLSContextFunc,
inherited that default, so an HTTP/1.1 client gets a connection that has
negotiated h2 via ALPN. net/http then writes HTTP/1.1 onto it and the first
response fails to parse:
net/http: HTTP/1.x transport connection broken: malformed HTTP response
"\x00\x00$\x04\x00\x00\x00\x00\x00\x00\x05\x00\x10..."
which is an HTTP/2 SETTINGS frame. There is currently no way to avoid this
other than rebuilding the tls.Config by hand after Build returns.
This adds ClientOptions.NextProtos, applied whenever a client tls.Config is
built, and TLSClientConfigOptions.NextProtos as a per-Build override.
NewS2ADialTLSContextFunc goes through NewTLSClientConfigFactory, so it picks
the option up as well.
Behavior is unchanged when neither is set: nextProtosOrDefault falls back to
{"h2"}. The gRPC ClientHandshake path in internal/v2/s2av2.go passes nil
explicitly, since that transport requires HTTP/2 and must not be overridable.
The returned slice is copied so a later mutation by the caller cannot alter a
live tls.Config.
- Use slices.Clone in nextProtosOrDefault and simplify doc comment - Add unit tests for TLSClientConfigFactory with custom NextProtos in s2a_test.go - Add end-to-end HTTP/1.1 test in s2a_e2e_test.go
|
Can we manually E2E test this? We have an example folder in the top level directory which may contain some useful components for testing |
|
Thanks @rmehta19, addressed the nits. I ran a manual E2E with every component as a separate OS process: the standalone fake S2A , a plain crypto/tls + net/http server offering ALPN [h2, http/1.1], and a small client using only the public s2a package (NewS2ADialTLSContextFunc). Testing details:
gRPC: I couldn't wire example/client , example/server to the fake as-is. None of the three binaries call flag.Parse(), so --port, --s2a_addr are ignored and the defaults don't line up (fake :8008 vs examples 0.0.0.0:61365). The gRPC path is still covered by the existing e2e tests (e.g. TestV2EndToEndUsingFakeS2AOverTCP). |
s2a-go v0.1.11 (google/s2a-go#161) adds ClientOptions.NextProtos, so a client can choose the ALPN protocols offered during the S2A TLS handshake instead of always offering "h2". Upgrade to v0.1.11 and set ClientOptions.NextProtos = []string{"http/1.1"} when client-protocol is http1, replacing the workaround that overwrote tlsConfig.NextProtos after factory.Build. With ALPN configured via ClientOptions, http1 and http2 can share a single S2A dialer helper (newS2ADialTLSContext) that opens its TCP connection with the base net.Dialer. Previously http2 called s2a.NewS2ADialTLSContextFunc, which dials with a zero-value net.Dialer, and http.Transport ignores DialContext for HTTPS once DialTLSContext is set; sharing the helper keeps --experimental-local-socket-address and --enable-http-dns-cache working for both http1 and http2 when S2A is enabled.
s2a-go v0.1.11 (google/s2a-go#161) adds ClientOptions.NextProtos, so a client can choose the ALPN protocols offered during the S2A TLS handshake instead of always offering "h2". Upgrade to v0.1.11 and set ClientOptions.NextProtos = []string{"http/1.1"} when clientProtocol is http1, replacing the workaround that overwrote tlsConfig.NextProtos after factory.Build. With ALPN configured via ClientOptions, http1 and http2 can share a single S2A dialer helper (newS2ADialTLSContext) that opens its TCP connection with the base net.Dialer. Previously http2 called s2a.NewS2ADialTLSContextFunc, which dials with a zero-value net.Dialer, and http.Transport ignores DialContext for HTTPS once DialTLSContext is set; sharing the helper keeps --experimental-local-socket-address and --enable-http-dns-cache working for both http1 and http2 when S2A is enabled.
Description
This PR carries forward the changes from #159 (by @sruthi-talluri) to allow clients to select ALPN protocols instead of forcing HTTP/2, and addresses the review feedback on that PR.
Background
GetTLSConfigurationForClienthas hardcodedNextProtosto{"h2"}since #72, which was added whens2a-gowas only used for gRPC. The HTTP entry points added later (NewTLSClientConfigFactoryandNewS2ADialTLSContextFunc) inherited that default, so an HTTP/1.1 client gets a connection that has negotiatedh2via ALPN.net/httpthen writes HTTP/1.1 onto it and the first response fails to parse:which is an HTTP/2
SETTINGSframe.Changes
ALPN Selection:
ClientOptions.NextProtos(applied when a clienttls.Configis built viaNewS2ADialTLSContextFuncandNewTLSClientConfigFactory).TLSClientConfigOptions.NextProtosas a per-Build()override.{"h2"}when unset, and strictly enforces HTTP/2 for gRPC client handshakes.Addressed Review Feedback from Allow clients to select ALPN protocols instead of forcing HTTP/2 #159:
slices.Clone(nextProtos)ininternal/v2/tlsconfigstore/tlsconfigstore.go.nextProtosOrDefault.NewTLSClientConfigFactorywith customNextProtosins2a_test.go.s2a_e2e_test.go.Co-authored-by: Sruthi Talluri sruthitalluri@google.com