-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
extensions: add grpc_http1_reverse_bridge filter #5315
Conversation
Adds a filter that allows converting a gRPC request into an HTTP/1.1 request with a custom content-type. Allows a vanilla HTTP/1.1 upstream to handle incoming requests by reading and responding with protobuf messages in binary octet format. For now this shields the upstream from any gRPC association: the filter removes the gRPC specific message prefix and manages the conversion of the HTTP status code into grpc-status. Signed-off-by: Snow Pettersen <[email protected]>
Open for suggesting on naming. We've been running a variation of this filter internally for a few months now, and it's been working just fine. |
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
Should we call this |
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
// was unsuccessful. Since we're guaranteed at this point to have a valid response | ||
// (unless upstream lied in content-type) we attempt to return a well-formed gRPC | ||
// response body. | ||
const auto length = htonl(buffer.length() + buffer_.length()); |
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.
use Grpc::Encoder
// If this is a gRPC request and this is the start of DATA, remove the first 5 | ||
// bytes. These correspond to the gRPC data header. | ||
if (enabled_ && !prefix_stripped_) { | ||
buffer.drain(5); |
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.
This will crash with malicious downstream when buffer is < 5 bytes length.
Btw, aren't this messing up Content-Length in header?
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.
Ah yeah, you're right. Should be easy enough to +/- 5 the content size and to fail the request if the request body size is < 5.
EXPECT_EQ(17, buffer.length()); | ||
EXPECT_THAT(trailers, HeaderValueOf(Http::Headers::get().GrpcStatus, "0")); | ||
|
||
char null; |
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.
use Grpc::Decoder and test against Grpc::Frame?
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.
That's so much better than my manual byte fiddling! I'll switch over that
@@ -31,6 +31,7 @@ EXTENSIONS = { | |||
"envoy.filters.http.grpc_http1_bridge": "//source/extensions/filters/http/grpc_http1_bridge:config", | |||
"envoy.filters.http.grpc_json_transcoder": "//source/extensions/filters/http/grpc_json_transcoder:config", | |||
"envoy.filters.http.grpc_web": "//source/extensions/filters/http/grpc_web:config", | |||
"envoy.filters.http.grpc_shim": "//source/extensions/filters/http/grpc_shim:config", |
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.
Also an entry in CODEOWNERS.
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
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.
As this is basically doing the opposite of grpc_http1_bridge, can we have an integration test against that so make sure they interoperates?
const auto length = htonl(buffer.length() + buffer_.length()); | ||
|
||
std::array<uint8_t, Grpc::GRPC_FRAME_HEADER_SIZE> frame; | ||
Grpc::Encoder().newFrame(Grpc::GRPC_FH_DEFAULT, htonl(length), frame); |
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.
I don't think you need htonl
here and above. newFrame
takes care of that.
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.
Oh boy, I had just length
here and it caused the tests to fail, didn't realize that I was already htonl
ing on L146. At this point I'm just round tripping back to host 🤦♂️
source/common/grpc/codec.h
Outdated
@@ -13,6 +13,8 @@ const uint8_t GRPC_FH_DEFAULT = 0b0u; | |||
// Last bit for a compressed message. | |||
const uint8_t GRPC_FH_COMPRESSED = 0b1u; | |||
|
|||
const uint64_t GRPC_FRAME_HEADER_SIZE = sizeof(uint8_t) + sizeof(uint32_t); |
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.
nit: constexpr
|
||
#include "absl/strings/str_cat.h" | ||
|
||
namespace { |
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 can put this inside namespace GrpcShim so you don't need to prefix everything with Envoy::
|
||
// Buffer the response in a mutable buffer: we need to determine the size of the response | ||
// and modify it later on. | ||
buffer_.move(buffer); |
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.
Seems this is basically buffer everything in the filter, so there will be no flow control here, would it make more sense just use content-length and pass through data without buffering?
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 problem I ran into with using content-type was that large responses wouldn't have the content-type set due to being chunked.
Wondering if it makes sense to make the filter buffer mutable? That way we could use the standard buffering mechanism to get flow control but also be able to inject the frame header at the end when we know the size of it?
I want to make sure they interoperate but they don't at the moment: grpc1_http_bridge doesn't manage the frame header. That said, maybe I should guard the frame header bits behind a config flag so that the default for both filters is the same, then test the interop on that. Later on I intend to make grpc1_http_bridge also optionally manage the frame header, at which point a similar interoperability test can be added. EDIT: Actually seems like this isn't an issue, since we're going through a valid gRPC format between the filters in either case. I've added the option now so I'll just test both cases. |
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
Unfortunately I think the two filters are not interoperable within the same filter chain at the moment, due to how These will still be interoperable between envoy processes (which is how I intend to use them together), since now the trailers will come in through the http2 codec which is able to trigger It might make sense to make this work within a filter chain (maybe |
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
// strip the gRPC frame from the request, and add it back in to the response. This will | ||
// hide the gRPC semantics from the upstream, allowing it to receive and respond with a | ||
// simple binary encoded protobuf. | ||
bool withhold_grpc_frames = 2; |
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.
I like having this defaulted to false because it means that both the http1 bridge and this leaves the frame header alone by default.
I think you can make the interop test using two listener / HCMs within same process of Envoy, no? |
Oh that's a great idea, I'll give that a go. |
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, this looks better modulo a few nits, and test. Are we OK with the grpc_shim name or any other suggestion?
@@ -0,0 +1,449 @@ | |||
#include <netinet/in.h> |
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.
is this needed?
@@ -0,0 +1,182 @@ | |||
#include "extensions/filters/http/grpc_shim/grpc_shim.h" | |||
|
|||
#include <netinet/in.h> |
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.
is this needed?
|
||
#include <netinet/in.h> | ||
|
||
#include "envoy/common/platform.h" |
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.
ditto
|
||
#include "extensions/filters/http/well_known_names.h" | ||
|
||
#include "absl/strings/str_cat.h" |
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.
not using?
@lizan I'm thinking of switching to @ramaraochavali's suggestion of |
Signed-off-by: Snow Pettersen <[email protected]>
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!
/retest
🔨 rebuilding |
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.
Can we get more complete docs for this before merging? We should add an overview page here: https://www.envoyproxy.io/docs/envoy/latest/configuration/http_filters/http_filters and probably also describe the different conversion options here https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/grpc? Thank you!
/wait
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
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 for adding the extra docs. Very cool!
gRPC frame header management | ||
---------------------------- | ||
|
||
By setting the withhold_grpc_frame option, the filer will assume that the upstream does not understand any gRPC semantics |
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.
typo "filer"
super nit: as long as you are in here again can you try to flow doc lines to ~100 col if possible? Same in other files.
@@ -4,6 +4,7 @@ Version history | |||
1.10.0 (pending) | |||
================ | |||
* http: added new grpc_http1_reverse_bridge filter for converting gRPC requests into HTTP/1.1 requests. | |||
* access log: added a new flag for upstream retry count exceeded. |
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.
nit: alpha order
Signed-off-by: Snow Pettersen <[email protected]>
@snowp LGTM, sorry, you lost the merge race again. Can you merge master one more time? /wait |
Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Snow Pettersen <[email protected]>
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.
Thank you!
Signed-off-by: Snow Pettersen <[email protected]>
/retest |
🔨 rebuilding |
Adds a filter that allows converting a gRPC request into an HTTP/1.1 request with a custom content-type. Allows a vanilla HTTP/1.1 upstream to handle incoming requests by reading and responding with protobuf messages in binary octet format. For now this shields the upstream from any gRPC association: the filter removes the gRPC specific message prefix and manages the conversion of the HTTP status code into grpc-status. Signed-off-by: Snow Pettersen <[email protected]> Signed-off-by: Fred Douglas <[email protected]>
Adds a filter that allows converting a gRPC request into an HTTP/1.1
request with a custom content-type. Allows a vanilla HTTP/1.1 upstream
to handle incoming requests by reading and responding with protobuf
messages in binary octet format.
For now this shields the upstream from any gRPC association: the filter
removes the gRPC specific message prefix and manages the conversion of
the HTTP status code into grpc-status.
Signed-off-by: Snow Pettersen [email protected]
Risk Level: Low, new optional filter
Testing: UTs and integration tests
Docs Changes: n/a for now, will add comprehensive docs
Release Notes: n/a
Fixes #5300