Skip to content

Conversation

antoniovicente
Copy link
Contributor

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

…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 mattklein123 self-assigned this Dec 29, 2019
Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@antoniovicente
Copy link
Contributor Author

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.

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>
Copy link
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good find - thanks for tackling it!

@@ -221,6 +221,10 @@ TEST_P(IntegrationTest, RouterUpstreamDisconnectBeforeResponseComplete) {
testRouterUpstreamDisconnectBeforeResponseComplete();
}

TEST_P(IntegrationTest, ResponseFramedByConnectionCloseWithReadLimits) {
testResponseFramedByConnectionCloseWithReadLimits();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

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"}},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for the fixes here. Will defer to @alyssawilk for further review.

Comment on lines +239 to +240
upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}, {":no-chunks", "1"}},
false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I take Antonio's point that :no-chunks doesn't reach the wire - I'm (warily) fine with it.

@lambdai
Copy link
Contributor

lambdai commented Mar 9, 2020

/backport

@PiotrSikora PiotrSikora added backport/approved Approved backports to stable releases and removed backport/review Request to backport to stable releases labels Mar 26, 2020
@PiotrSikora
Copy link
Contributor

@lambdai could you create the PR against release/v1.12? Thanks!

istio-testing pushed a commit to istio/envoy that referenced this pull request Mar 26, 2020
…isable state on a closed connection (#190)

This is a port of envoyproxy#9509

Signed-off-by: Dmitri Dolguikh <ddolguik@redhat.com>
lambdai pushed a commit to lambdai/envoy-dai that referenced this pull request Apr 16, 2020
…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>
lambdai pushed a commit to lambdai/envoy-dai that referenced this pull request Jun 5, 2020
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport/approved Approved backports to stable releases
Projects
None yet
Development

Successfully merging this pull request may close these issues.

HTTP/1: watchdog death when clearing readDisable in onMessageComplete and the upstream connection is closed
6 participants