Skip to content

transport socket: refactor to capture the filter state#19809

Merged
ggreenway merged 12 commits intoenvoyproxy:mainfrom
kyessenov:socket_filter_state
Feb 23, 2022
Merged

transport socket: refactor to capture the filter state#19809
ggreenway merged 12 commits intoenvoyproxy:mainfrom
kyessenov:socket_filter_state

Conversation

@kyessenov
Copy link
Copy Markdown
Contributor

@kyessenov kyessenov commented Feb 3, 2022

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

Signed-off-by: Kuat Yessenov <kuat@google.com>
@kyessenov kyessenov force-pushed the socket_filter_state branch from c7584d6 to feca4fc Compare February 3, 2022 21:15
@kyessenov
Copy link
Copy Markdown
Contributor Author

@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.

Copy link
Copy Markdown
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.

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

Choose a reason for hiding this comment

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

What in the implementation makes this downstream-specific?
and aren't transport sockets per-connection?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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
alyssawilk previously approved these changes Feb 9, 2022
Copy link
Copy Markdown
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.

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());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shouldn't this be swapped for testing the hash key method?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Signed-off-by: Kuat Yessenov <kuat@google.com>
Signed-off-by: Kuat Yessenov <kuat@google.com>
Signed-off-by: Kuat Yessenov <kuat@google.com>
@alyssawilk
Copy link
Copy Markdown
Contributor

@ggreenway ping?

@ggreenway
Copy link
Copy Markdown
Member

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

@kyessenov
Copy link
Copy Markdown
Contributor Author

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.

@kyessenov
Copy link
Copy Markdown
Contributor Author

@ggreenway Thoughts?

Copy link
Copy Markdown
Member

@ggreenway ggreenway left a comment

Choose a reason for hiding this comment

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

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this implementation go into a base class, since it is used in almost every transport socket?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea. Thanks for the feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Implemented a common class.

Signed-off-by: Kuat Yessenov <kuat@google.com>
Signed-off-by: Kuat Yessenov <kuat@google.com>
Signed-off-by: Kuat Yessenov <kuat@google.com>
@kyessenov
Copy link
Copy Markdown
Contributor Author

/retest

@repokitteh-read-only
Copy link
Copy Markdown

Retrying Azure Pipelines:
Check envoy-presubmit didn't fail.

🐱

Caused by: a #19809 (comment) was created by @kyessenov.

see: more, trace.

Copy link
Copy Markdown
Member

@ggreenway ggreenway left a comment

Choose a reason for hiding this comment

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

LGTM

@ggreenway ggreenway merged commit 3e6aee6 into envoyproxy:main Feb 23, 2022
kyessenov added a commit that referenced this pull request Jul 6, 2022
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants