transport socket: refactor to capture the filter state#19809
transport socket: refactor to capture the filter state#19809ggreenway merged 12 commits intoenvoyproxy:mainfrom
Conversation
Signed-off-by: Kuat Yessenov <kuat@google.com>
c7584d6 to
feca4fc
Compare
|
@alyssawilk This is a cleaner attempt to wire the filter state across internal loopback. The plan is to add a "hash" method to filter state objects, and then in the internal upstream transport socket, hash all the exported objects. |
alyssawilk
left a comment
There was a problem hiding this comment.
sorry for the lag - somehow it got lost in my inbox!
/wait
| virtual absl::optional<Network::ProxyProtocolData> proxyProtocolOptions() const PURE; | ||
|
|
||
| /** | ||
| * @return filter state from the downstream request or connection. |
There was a problem hiding this comment.
What in the implementation makes this downstream-specific?
and aren't transport sockets per-connection?
There was a problem hiding this comment.
It specifies which filter state to capture when creating transport socket options. That determines whether downstream requests/connections map to the same upstream connection once we use the filter state in hashKey. This is basically a generalization of ProxyProtocolData but for arbitrary filter state objects.
| std::vector<std::string> alpn_fallback; | ||
| absl::optional<Network::ProxyProtocolData> proxy_protocol_options; | ||
|
|
||
| bool needs_transport_socket_options = false; |
There was a problem hiding this comment.
I could easily be misremembering but I thought previously this being null vs non-null meant that connections would or would not be hashed together in connection pools. have you made sure it's safe to always return this?
cc @lizan who wrote this code
There was a problem hiding this comment.
Good point. I assumed this is just some optimization but if nullptr is actually special, then I'll have to track its usage throughout the code base.
There was a problem hiding this comment.
I looked over and I think there is no risk in returning an empty options struct vs nullptr. This is because the common hash iterates over the optionals and vectors to add to hash, and if they are empty, then it is effectively a no-op.
PS. I added some additional refactor to make it more clear the goal of this PR. I want to swap out hashKey definition site to the transport socket factory so that the factory can change the hash (rather that the options checking whether a factory is special).
Signed-off-by: Kuat Yessenov <kuat@google.com>
alyssawilk
left a comment
There was a problem hiding this comment.
LGTM (with minor test question) but I'd definitely appreciate a second pass, throwing over to Greg
| TEST(RawBufferSocketFactory, RawBufferSocketFactory) { | ||
| TransportSocketFactoryPtr factory = Envoy::Network::Test::createRawBufferSocketFactory(); | ||
| EXPECT_FALSE(factory->usesProxyProtocolOptions()); | ||
| EXPECT_FALSE(factory->implementsSecureTransport()); |
There was a problem hiding this comment.
shouldn't this be swapped for testing the hash key method?
There was a problem hiding this comment.
I can add that, too. I added something to recover coverage since the entire file has just one test.
| // Proxy protocol options should only be included in the hash if the upstream | ||
| // socket intends to use them. | ||
| if (options) { | ||
| const auto& proxy_protocol_options = options->proxyProtocolOptions(); |
There was a problem hiding this comment.
Minor comment: As I was writing this, I realized that we don't have to capture proxy data by copy in the options for proxy protocol. We could just look it up in the filter state here.
|
@ggreenway ping? |
|
Given that this is for use between custom upstream/downstream transport sockets for the internal listener, would it make more sense to send the relevant data in-band, eg somehow stick it in the payload that the upstream writes to the internal-listener connection? That would prevent lots of implementation details from leaking out and affecting the rest of the system. If we validate that the special upstream transport-socket is connecting to an internal listener with the special downstream transport-socket, it could even be a pointer still (not a copy/serialization of the data). /wait-any |
|
I understand the need to keep it more like proxy protocol handling. I think the cost of passing data on the wire is not worth it. Serialization seems hard to justify since this all happens within the same process. Passing a raw pointer from a shared pointer that is filter state seems problematic to me since we buffer data in the internal listener. Besides the above, this PR doesn't actually make use of the stored filter state (that's #19435). Whether we pass it on the wire or via a side channel, we have to expose the filter state in the transport socket context. This PR just makes it possible to reference other filter state objects besides the proxy protocol one and some other well-known ones. |
|
@ggreenway Thoughts? |
ggreenway
left a comment
There was a problem hiding this comment.
This makes sense to me. I like that proxy-protocol isn't special-cased in the interface anymore. Access to this data makes sense regardless of how it is passed through the internal-listener implementation.
/wait
|
|
||
| void PassthroughFactory::hashKey(std::vector<uint8_t>& key, | ||
| Network::TransportSocketOptionsConstSharedPtr options) const { | ||
| return Network::TransportSocketOptionsUtility::commonHashKey(key, options); |
There was a problem hiding this comment.
Should this implementation go into a base class, since it is used in almost every transport socket?
There was a problem hiding this comment.
Good idea. Thanks for the feedback.
There was a problem hiding this comment.
Implemented a common class.
|
/retest |
|
Retrying Azure Pipelines: |
Commit Message: Adds a new flag for filter state objects that indicates the intent to share with the upstream. Additional Description: Follow-up to #19809. There have been multiple reports of unexpected lifecycle changes for the filter state objects because they are stored in the transport socket options. This PR addresses this issue by introducing a new mark for filter state that explicitly changes the usage of filter state objects: marked objects always participate in the connection pool hashing (generalizing and simplifying transport sockets: support passthrough state for internal connections #19435); marked objects are copied by reference to the upstream info - this allows sharing state between downstream and upstream (and further down the chain, the internal listeners). Risk Level: medium, revert to the original behavior prior to #19809 Testing: yes Docs Changes: yes Release Notes: yes
Signed-off-by: Kuat Yessenov kuat@google.com
Commit Message: Capture the filter state in the transport socket options to be exposed to the internal listener transport socket. The goal is to enable propagation the filter state from the downstream request/connection to the upstream internal listener. The assumption is that the propagation is consistent with the hash key: if multiple downstream HTTP requests share the internal upstream connection, then their filter state objects are "equivalent".
Risk Level: low, refactor (shared pointer does not seem to prolong the lifetime)
Testing: unit
Docs Changes: none
Release Notes: none
Issue: #19274