New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Disable HTTP2 while proxying a "Connection: upgrade" request #88781
Conversation
Hi @ibuildthecloud. Thanks for your PR. I'm waiting for a kubernetes member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
Fixes #88782 |
Thanks, can we exercise the bug and fix with a test? |
Doesn't this diable http/2 for all proxied requests? Why not just use:
And pass in a transport with http/2 disabled? |
@mikedanese This disables http2 for only times that we use GetDialer which is only when we are handling a connection upgrade. Correct me if I'm wrong, but connection upgrade is a http/1.1 concept so it is required to disable http2. If the user passes in a transport to disable http2 that will disable http2 for all proxied request. We'd prefer to use http2 on http2 compatible requests. This approach disables http2 only when we know it is explicitly disallowed. |
@liggitt will add test. |
@liggitt Updated with tests. |
Yes, we shouldn't be using http/2 for upgrade requests. I thought we hit it here:
But that function is unused now. This is all pretty messy at this point and is due for some cleanup. |
// going to write HTTP/1.1 request to the wire. http2 should not be allowed in the TLSConfig.NextProtos, | ||
// so we explicitly set that here. We only do this check if the TLSConfig support http/1.1. | ||
if supportsHTTP11(tlsConfig.NextProtos) { | ||
tlsConfig.NextProtos = []string{"http/1.1"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above block tries hard not to modify the tlsConfig that is passed in. Do we need to make a copy here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're correct, I thought it was already copied or new at this point but I see I'm wrong.
Can we either:
Alternatively, we could probably fix this outside of dial url by making sure that DialForUpgrade only uses a transport that has http/2 disabled. |
@mikedanese I added documentation to the DialURL method to describe it's behavior regarding http2 and http/1.1. |
@@ -355,6 +355,25 @@ func TestProxyUpgrade(t *testing.T) { | |||
ServerFunc: httptest.NewServer, | |||
ProxyTransport: nil, | |||
}, | |||
"booth client and server support http2, but force to http/1.1 for upgrade": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"booth client and server support http2, but force to http/1.1 for upgrade": { | |
"both client and server support http2, but force to http/1.1 for upgrade": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
@@ -355,6 +355,25 @@ func TestProxyUpgrade(t *testing.T) { | |||
ServerFunc: httptest.NewServer, | |||
ProxyTransport: nil, | |||
}, | |||
"booth client and server support http2, but force to http/1.1 for upgrade": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"booth client and server support http2, but force to http/1.1 for upgrade": { | |
"both client and server support http2, but force to http/1.1 for upgrade": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
@@ -81,6 +86,15 @@ func DialURL(ctx context.Context, url *url.URL, transport http.RoundTripper) (ne | |||
tlsConfigCopy.ServerName = inferredHost | |||
tlsConfig = tlsConfigCopy | |||
} | |||
|
|||
// Since this method is primary used within a "Connection: Upgrade" call we assume the caller is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does "Connection: Upgrade" mean? Is it a term?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://tools.ietf.org/html/rfc7230#section-6.7
We upgrade http requests to websocket or spdy for streaming request like exec and logs. The headers communicates that you are switching protocols from http1/1 to X so when the upgrade is complete you can send whatever over it like it's just a tcp connection. Sending an upgrade request over http/2 is a connection error because underlying tcp connections are shared between requests. We can eventually migrate off of http1/1 altogether but we are waiting for go support to land. golang/go#27244 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. It's an http header.
nit: should be "Connection: upgrade" according to the RFC.
// RoundTripper. The primary use of this method is to support proxying upgradable connections. | ||
// For this reason this method will prefer to negotiate http/1.1 if the URL scheme is https. | ||
// If you wish to ensure ALPN negotiates http2 then set NextProto=[]string{"http2"} in the | ||
// TLSConfig of the http.Transport | ||
func DialURL(ctx context.Context, url *url.URL, transport http.RoundTripper) (net.Conn, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make it private if it doesn't k/k.
In the PR description (the first comment), can you add "fix #88782"? |
Yup, I was kind of confused since it's the only thing that might dial for no upgrade requests so I deleted it and did a full compile. No usage apparently. |
27268da
to
77d1c7a
Compare
I removed UpgradeAwareHandler.Dial and renamed DialURL to be private. |
When proxying connection upgrade requests, like websockets, we dial the target and then manually write the http.Request to the wire, bypassing the http.Client. In this scenario we are by default using HTTP/1.1 from both the client and to the target server we are proxying. Because of this we must disable HTTP2 in the TLS handshake so that the server does not think we are writing a HTTP2 request. We do this by setting the TLSConfig.NextProtos field to "http/1.1". Signed-off-by: Darren Shepherd <darren@rancher.com>
/lgtm |
Tentatively tagged for 1.18 since it fixes a bug and we haven't blown the final whistle yet. |
/retest |
1 similar comment
/retest |
This lgtm, too. |
/test pull-kubernetes-e2e-gce-network-proxy Run the tests since this is on the path of the apiserver network proxy. Though it won't pass until #88863 gets merged |
@caesarxuchao still needs approval. |
/test pull-kubernetes-e2e-gce-network-proxy |
/approve I want to make sure pull-kubernetes-e2e-gce-network-proxy passes before merging this. This is a bug fix so I think it can merge after code freeze. |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: caesarxuchao, ibuildthecloud The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
@mikedanese @ibuildthecloud feel free to remove the hold if pull-kubernetes-e2e-gce-network-proxy passes. |
When proxying connection upgrade requests, like websockets, we dial
the target and then manually write the http.Request to the wire,
bypassing the http.Client. In this scenario we are by default using
HTTP/1.1 from both the client and to the target server we are proxying.
Because of this we must disable HTTP2 in the TLS handshake so that the
server does not think we are writing a HTTP2 request. We do this by
setting the TLSConfig.NextProtos field to "http/1.1".
fix #88782