Skip to content

http3: Add support for HTTP/3 METADATA#32568

Merged
alyssawilk merged 24 commits intoenvoyproxy:mainfrom
RyanTheOptimist:h3_metadata
Mar 14, 2024
Merged

http3: Add support for HTTP/3 METADATA#32568
alyssawilk merged 24 commits intoenvoyproxy:mainfrom
RyanTheOptimist:h3_metadata

Conversation

@RyanTheOptimist
Copy link
Copy Markdown
Contributor

http3: Add support for HTTP/3 METADATA

Adds a new allow_metadata option to Http3ProtocolOptions.

Risk Level: Low, protected by new config option
Testing: New integration tests
Docs Changes: N/A
Release Notes: Updated

@repokitteh-read-only
Copy link
Copy Markdown

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to (api/envoy/|docs/root/api-docs/).
envoyproxy/api-shepherds assignee is @abeyad
CC @envoyproxy/api-watchers: FYI only for changes made to (api/envoy/|docs/root/api-docs/).

🐱

Caused by: #32568 was opened by RyanTheOptimist.

see: more, trace.

Comment thread api/envoy/config/core/v3/protocol.proto Outdated
Comment thread api/envoy/config/core/v3/protocol.proto Outdated
@RyanTheOptimist RyanTheOptimist marked this pull request as draft February 26, 2024 01:10
abeyad
abeyad previously approved these changes Feb 26, 2024
Copy link
Copy Markdown
Contributor

@abeyad abeyad left a comment

Choose a reason for hiding this comment

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

/lgtm api

@RyanTheOptimist
Copy link
Copy Markdown
Contributor Author

Sorry! I keep forgetting that Envoy does automatic assignment. I think the code in this PR is basically ready for review, but it depends on some un-commited QUICHE changes that I manually passed in so it won't yet pass CI

Signed-off-by: Ryan Hamilton <rch@google.com>

Cleanup

Signed-off-by: Ryan Hamilton <rch@google.com>

Reduce code duplication

Signed-off-by: Ryan Hamilton <rch@google.com>

Changelog

Signed-off-by: Ryan Hamilton <rch@google.com>

Better release notes

Signed-off-by: Ryan Hamilton <rch@google.com>

More cleanup

Signed-off-by: Ryan Hamilton <rch@google.com>

Even more cleanup

Signed-off-by: Ryan Hamilton <rch@google.com>

Cleanup! Cleanup

Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
@repokitteh-read-only repokitteh-read-only bot added the deps Approval required for changes to Envoy's external dependencies label Feb 29, 2024
@repokitteh-read-only
Copy link
Copy Markdown

CC @envoyproxy/dependency-shepherds: Your approval is needed for changes made to (bazel/.*repos.*\.bzl)|(bazel/dependency_imports\.bzl)|(api/bazel/.*\.bzl)|(.*/requirements\.txt)|(.*\.patch).
envoyproxy/dependency-shepherds assignee is @mattklein123

🐱

Caused by: #32568 was synchronize by RyanTheOptimist.

see: more, trace.

@RyanTheOptimist RyanTheOptimist marked this pull request as ready for review February 29, 2024 23:26
@RyanTheOptimist
Copy link
Copy Markdown
Contributor Author

/assign @danzh2010

Ok, this is ready for review, I hope. There's a QUICHE update in this as well, but I'll land that separately once #32642 lands.

Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
@RyanTheOptimist
Copy link
Copy Markdown
Contributor Author

Now that #32650 has landed, this PR does not change deps, so I'll remove that label.

@RyanTheOptimist RyanTheOptimist removed the deps Approval required for changes to Envoy's external dependencies label Mar 2, 2024
@mattklein123 mattklein123 removed their assignment Mar 3, 2024
#ifdef ENVOY_ENABLE_QUIC
EXPECT_TRUE(GetQuicReloadableFlag(quic_enable_http3_metadata_decoding));
#else
EXCLUDE_DOWNSTREAM_HTTP3; // The HTTP/3 client has no "bad frame" equivalent.
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.

The comment seems irrelevant.

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.

Whoops. Thanks.

result = quic_stream_.WriteMemSlices(metadata_frame, /*end_stream=*/false);
}
// QUIC stream must take all.
if (result.bytes_consumed == 0) {
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.

Can this be true if the metadata passed in is empty?

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.

No, because the METADATA frame header will always be present.

Signed-off-by: Ryan Hamilton <rch@google.com>
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.

Nice addition!
/wait on comments

return dynamic_cast<QuicFilterManagerConnectionImpl*>(session());
}

void EnvoyQuicClientStream::OnMetadataComplete(size_t /*frame_len*/,
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.

in H3, can metadata arrive before headers? I think it's an invariant of Envoy that headers arrive first and I'm not sure if it's an invariant of the quiche libraries.

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.

METADATA is an HTTP/3 frame which is delivered on the QUIC stream, so it's guaranteed to be delivered in the order it was sent. I think this is true for HTTP/2 as well. But if a peer sent a METADATA frame before sending HEADERS I think QUICHE will expose it in that order. I think that's also the same for HTTP/2. But we could make this an error of some here, if you prefer?

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 think we should either make it an error, or add an e2e test it doesn't cause problems in Envoy.

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.

Oh! I think we have a test for this already! ProxyMetadataInResponse does:

  // Sends metadata before response header.                                                                                                                                                 
  const std::string key = "key";
  std::string value = std::string(80 * 1024, '1');
  Http::MetadataMap metadata_map = {{key, value}};
  Http::MetadataMapPtr metadata_map_ptr = std::make_unique<Http::MetadataMap>(metadata_map);
  Http::MetadataMapVector metadata_map_vector;
  metadata_map_vector.push_back(std::move(metadata_map_ptr));
  upstream_request_->encodeMetadata(metadata_map_vector);
  upstream_request_->encodeHeaders(default_response_headers_, false);
  upstream_request_->encodeData(12, true);

Comment thread source/common/quic/envoy_quic_stream.cc Outdated

void EnvoyQuicStream::encodeMetadata(const Http::MetadataMapVector& metadata_map_vector) {
if (!http3_options_.allow_metadata()) {
// Metadata Frame is not supported in QUICHE.
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.

update comment and log: not supported by config

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

Comment thread source/common/quic/envoy_quic_stream.cc Outdated
"bytes in send buffer. Current write was rejected.",
quic_stream_.write_side_closed() ? "closed" : "open",
quic_stream_.BufferedDataBytes()));
quic_stream_.Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD);
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.

is bad application payload the best? feels like an invariant failure but idk if ther's a better 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.

Fair enough. How about QUIC_ERROR_PROCESSING_STREAM which seems to be the closest thing we have.


void EnvoyQuicServerStream::OnMetadataComplete(size_t /*frame_len*/,
const quic::QuicHeaderList& header_list) {
request_decoder_->decodeMetadata(metadataMapFromHeaderList(header_list));
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.

do we need to do any validation steps with or without UHV? I honestly don't recall sorry

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 don't see anything like that for HTTP/2. I think the payload of METADATA is "key/value pairs" not "HTTP headers" so normal header validation does not apply, as I understand it.

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.

Ok I didn't know if we had concerns about character validation, since I think Envoy has some assumptions there, but maybe not for metadata.
cc @yanavlasov

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.

@birenroy might also know

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.

No restrictions on metadata character set, since it is opt-in and the strings are length prefixed.

EXPECT_EQ(count * size + added_decoded_data_size * 2, response->body().size());
}

class MetadataIntegrationTest : public HttpProtocolIntegrationTest {
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.

can we change Http2MetadataIntegrationTest to be MetadataIntegrationTest and run all the tests for H2/H3?

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.

Done!

Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
https://github.com/google/quiche/compare/cc9724b90..70bc4dfde

```
$ git log cc9724b90..70bc4dfde --date=short --no-merges --format="%ad %al %s"

2024-03-13 rch Return false from QuicSpdyStream::OnMetadataFrameEnd if the stream has been reset.
2024-03-13 martinduke Update MOQT to draft-03 wire image. Does not add ANNOUNCE_CANCEL because it does not have a code point.
2024-03-13 martinduke Rename kGenericError to kInternalError.
```

Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
@RyanTheOptimist
Copy link
Copy Markdown
Contributor Author

@alyssawilk PTAL. The QUICHE update which the size limit tests require will be landed in #32892. In the meantime, I've patched that .bzl file change into this PR so I'll need a main merge once that lands. But it's ready for review, I think!

@RyanTheOptimist
Copy link
Copy Markdown
Contributor Author

/retest

1 similar comment
@RyanTheOptimist
Copy link
Copy Markdown
Contributor Author

/retest

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.

looks like test/integration/protocol_integration_test.cc has some Metadata tests as well as http2_flood_integration_test.cc

Given it's hidden and we're the only users I'm OK with a TODO to turn those up and landing to kick off import - your call if that's helpful or if you want to just land the right proper fix :-)

return dynamic_cast<QuicFilterManagerConnectionImpl*>(session());
}

void EnvoyQuicClientStream::OnMetadataComplete(size_t /*frame_len*/,
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 think we should either make it an error, or add an e2e test it doesn't cause problems in Envoy.


void EnvoyQuicServerStream::OnMetadataComplete(size_t /*frame_len*/,
const quic::QuicHeaderList& header_list) {
request_decoder_->decodeMetadata(metadataMapFromHeaderList(header_list));
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.

Ok I didn't know if we had concerns about character validation, since I think Envoy has some assumptions there, but maybe not for metadata.
cc @yanavlasov

Comment thread test/integration/multiplexed_integration_test.cc
@@ -571,13 +588,13 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) {
EXPECT_EQ(4, response->metadataMapsDecodedCount());

// Upstream responds with headers, 100-continue and data.
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.

optional, switch to adding expect 100continue to default_request_headers_

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.

Done.

@@ -884,14 +911,14 @@ void Http2MetadataIntegrationTest::runHeaderOnlyTest(bool send_request_body, siz
Http::TestRequestHeaderMapImpl{{":method", "POST"},
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.

default request headers here and below?

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.

Done.

[&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void {
RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, "");
ConfigHelper::HttpProtocolOptions protocol_options;
protocol_options.mutable_explicit_http_config()
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.

Http2MetadataIntegrationTest -> MetadataIntegraionTest?

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.

Whoops, done!

Signed-off-by: Ryan Hamilton <rch@google.com>
@RyanTheOptimist
Copy link
Copy Markdown
Contributor Author

looks like test/integration/protocol_integration_test.cc has some Metadata tests as well as http2_flood_integration_test.cc

Given it's hidden and we're the only users I'm OK with a TODO to turn those up and landing to kick off import - your call if that's helpful or if you want to just land the right proper fix :-)

Thanks! Yeah, if it's ok with you, I'd love to address this in a follow up. Hopefully it's straightforward :)

Signed-off-by: Ryan Hamilton <rch@google.com>
Copy link
Copy Markdown
Contributor Author

@RyanTheOptimist RyanTheOptimist left a comment

Choose a reason for hiding this comment

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

Thanks for the comments! The QUICHE merge landed so this PR no longer includes it.

[&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void {
RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, "");
ConfigHelper::HttpProtocolOptions protocol_options;
protocol_options.mutable_explicit_http_config()
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.

Whoops, done!

Comment thread test/integration/multiplexed_integration_test.cc
@@ -884,14 +911,14 @@ void Http2MetadataIntegrationTest::runHeaderOnlyTest(bool send_request_body, siz
Http::TestRequestHeaderMapImpl{{":method", "POST"},
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.

Done.

@@ -571,13 +588,13 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) {
EXPECT_EQ(4, response->metadataMapsDecodedCount());

// Upstream responds with headers, 100-continue and data.
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.

Done.

return dynamic_cast<QuicFilterManagerConnectionImpl*>(session());
}

void EnvoyQuicClientStream::OnMetadataComplete(size_t /*frame_len*/,
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.

Oh! I think we have a test for this already! ProxyMetadataInResponse does:

  // Sends metadata before response header.                                                                                                                                                 
  const std::string key = "key";
  std::string value = std::string(80 * 1024, '1');
  Http::MetadataMap metadata_map = {{key, value}};
  Http::MetadataMapPtr metadata_map_ptr = std::make_unique<Http::MetadataMap>(metadata_map);
  Http::MetadataMapVector metadata_map_vector;
  metadata_map_vector.push_back(std::move(metadata_map_ptr));
  upstream_request_->encodeMetadata(metadata_map_vector);
  upstream_request_->encodeHeaders(default_response_headers_, false);
  upstream_request_->encodeData(12, true);


void EnvoyQuicServerStream::OnMetadataComplete(size_t /*frame_len*/,
const quic::QuicHeaderList& header_list) {
request_decoder_->decodeMetadata(metadataMapFromHeaderList(header_list));
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.

@birenroy might also know

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.

mildly prefer an explicit TODO for the rest of tests but realistically I know you're good for it =P

@RyanTheOptimist RyanTheOptimist enabled auto-merge (squash) March 14, 2024 17:29
@alyssawilk alyssawilk disabled auto-merge March 14, 2024 17:30
@alyssawilk alyssawilk merged commit 640f016 into envoyproxy:main Mar 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deps Approval required for changes to Envoy's external dependencies

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants