http: Fix ASSERT failure and infinite loop when attempting to unset readDisable state on a closed connection.#9509
Conversation
…able state on a closed connection. Signed-off-by: Antonio Vicente <avd@google.com>
…d_disabled_close Signed-off-by: Antonio Vicente <avd@google.com>
mattklein123
left a comment
There was a problem hiding this comment.
Awesome, thanks for debugging and fixing this. In general this makes sense to me. Optimally I would like @alyssawilk to review, but if this needs to merge urgently we can do that and she can post-review. Also need to check format.
/wait
I think it would be fine to wait until @alyssawilk is back from the holidays |
…d_disabled_close Signed-off-by: Antonio Vicente <avd@google.com>
…rectly. Signed-off-by: Antonio Vicente <avd@google.com>
alyssawilk
left a comment
There was a problem hiding this comment.
Good find - thanks for tackling it!
test/integration/integration_test.cc
Outdated
| } | ||
|
|
||
| TEST_P(IntegrationTest, ResponseFramedByConnectionCloseWithReadLimits) { | ||
| testResponseFramedByConnectionCloseWithReadLimits(); |
There was a problem hiding this comment.
If this is only used in one place please just put the code inline - it's not totally obvious but I'm trying to push for inline code where things are not reused.
test/integration/http_integration.cc
Outdated
| auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); | ||
| waitForNextUpstreamRequest(); | ||
| // Disable chunking to trigger framing by connection close. | ||
| upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}, {":no-chunks", "1"}}, |
There was a problem hiding this comment.
This is just run for HTTP/1.1 right?
ostensibly the no chunk header is never supposed to be sent on the wire - I think if you don't send a content length and send HTTP/1.0 (see existing example tests) you can force frame by close that way. Mind trying it out?
There was a problem hiding this comment.
I'm having some trouble finding examples under test/integration that encode an HTTP/1.0 response. Do you have some links handy?
As far as I can tell ":no-chunks" is the way to tell StreamEncoderImpl::encodeHeaders in source/common/http/http1/codec_impl.cc to not chunk a response without a content-length.
There was a problem hiding this comment.
grep around for config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); ?
They all use sendRawHttpAndWaitForResponse but given this is a header only request and you're waiting for connection close I think that should be fine.
There was a problem hiding this comment.
I'm fairly sure that the :no-chunk does not end up on the wire. One thing that I wanted to verify in some way is that the Envoy does see an upstream response framed by connection close. I'm not sure how to accomplish that, since I think envoy will add chunk encoding to the response it sends downstream.
Let's talk briefly offline.
… readEnabled is called. Fix typo "chunking" -> "chunk encoding" Signed-off-by: Antonio Vicente <avd@google.com>
Signed-off-by: Antonio Vicente <avd@google.com>
mattklein123
left a comment
There was a problem hiding this comment.
LGTM, thanks for the fixes here. Will defer to @alyssawilk for further review.
| upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}, {":no-chunks", "1"}}, | ||
| false); |
There was a problem hiding this comment.
If you wanted to avoid the :no-chunks business, you could potentially just use a raw TCP fake upstream connection and manually send an HTTP/1.0 response (you could I suppose also build HTTP/1.0 handling into the fake upstream but that sounds like a lot more work), which IIRC Envoy should handle correctly with close framing. I don't feel strongly about it but I know @alyssawilk commented on this.
There was a problem hiding this comment.
I take Antonio's point that :no-chunks doesn't reach the wire - I'm (warily) fine with it.
has alyssawilk seal of approval :-P
|
/backport |
|
@lambdai could you create the PR against |
…isable state on a closed connection (#190) This is a port of envoyproxy#9509 Signed-off-by: Dmitri Dolguikh <ddolguik@redhat.com>
…eadDisable state on a closed connection. (envoyproxy#9509) Description: Handle calls to ConnectionImpl::readDisable on Closed connections more gracefully. There are several places under source/common, including ClientConnectionImpl::onMessageComplete, where Connection::readDisable(false) is called on a loop to clear the readDisable state as part of the process of preparing upstream connections for reuse. It is possible to end up in an infinite loop if proxying an HTTP/1 response is framed by connection close, the call to onData triggers readDisable(true), and there is a pending PostIoAction::Close action. The closeSocket call on the upstreamConnection triggers the call to ClientConnectionImpl::onMessageComplete Risk Level: medium Testing: unit and integration tests Docs Changes: n/a Release Notes: n/a Fixes envoyproxy#9508 Signed-off-by: Antonio Vicente <avd@google.com> Signed-off-by: Yuchen Dai <silentdai@gmail.com>
…eadDisable state on a closed connection. (envoyproxy#9509) Description: Handle calls to ConnectionImpl::readDisable on Closed connections more gracefully. There are several places under source/common, including ClientConnectionImpl::onMessageComplete, where Connection::readDisable(false) is called on a loop to clear the readDisable state as part of the process of preparing upstream connections for reuse. It is possible to end up in an infinite loop if proxying an HTTP/1 response is framed by connection close, the call to onData triggers readDisable(true), and there is a pending PostIoAction::Close action. The closeSocket call on the upstreamConnection triggers the call to ClientConnectionImpl::onMessageComplete Risk Level: medium Testing: unit and integration tests Docs Changes: n/a Release Notes: n/a Fixes envoyproxy#9508 Signed-off-by: Antonio Vicente <avd@google.com> Signed-off-by: Yuchen Dai <silentdai@gmail.com>
Description: Handle calls to ConnectionImpl::readDisable on Closed connections more gracefully. There are several places under source/common, including ClientConnectionImpl::onMessageComplete, where Connection::readDisable(false) is called on a loop to clear the readDisable state as part of the process of preparing upstream connections for reuse. It is possible to end up in an infinite loop if proxying an HTTP/1 response is framed by connection close, the call to onData triggers readDisable(true), and there is a pending PostIoAction::Close action. The closeSocket call on the upstreamConnection triggers the call to ClientConnectionImpl::onMessageComplete
Risk Level: Low
Testing: unit and integration tests
Docs Changes: n/a
Release Notes: n/a
Fixes #9508