From 58606021040b5719db9b6ff3d18ceebdff50fcda Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 14 Apr 2021 08:31:10 -0400 Subject: [PATCH 001/209] grid: clean up return value (#15950) Risk Level: n/a (code not yet used) Testing: existing tests pass Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk --- source/common/http/conn_pool_grid.cc | 16 ++++++++++------ source/common/http/conn_pool_grid.h | 13 ++++++++----- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc index 18cf101e0a3e9..e41007825e01b 100644 --- a/source/common/http/conn_pool_grid.cc +++ b/source/common/http/conn_pool_grid.cc @@ -36,13 +36,14 @@ ConnectivityGrid::WrapperCallbacks::ConnectionAttemptCallbacks::ConnectionAttemp WrapperCallbacks& parent, PoolIterator it) : parent_(parent), pool_it_(it), cancellable_(nullptr) {} -bool ConnectivityGrid::WrapperCallbacks::ConnectionAttemptCallbacks::newStream() { +ConnectivityGrid::StreamCreationResult +ConnectivityGrid::WrapperCallbacks::ConnectionAttemptCallbacks::newStream() { auto* cancellable = pool().newStream(parent_.decoder_, *this); if (cancellable == nullptr) { - return true; + return StreamCreationResult::ImmediateResult; } cancellable_ = cancellable; - return false; + return StreamCreationResult::StreamCreationPending; } void ConnectivityGrid::WrapperCallbacks::ConnectionAttemptCallbacks::onPoolFailure( @@ -82,7 +83,7 @@ void ConnectivityGrid::WrapperCallbacks::deleteThis() { removeFromList(grid_.wrapped_callbacks_); } -bool ConnectivityGrid::WrapperCallbacks::newStream() { +ConnectivityGrid::StreamCreationResult ConnectivityGrid::WrapperCallbacks::newStream() { ENVOY_LOG(trace, "{} pool attempting to create a new stream to host '{}'.", describePool(**current_), grid_.host_->hostname()); auto attempt = std::make_unique(*this, current_); @@ -137,7 +138,10 @@ bool ConnectivityGrid::WrapperCallbacks::tryAnotherConnection() { // If there are no other pools to try, return false. return false; } - // Create a new connection attempt for the next pool. + // Create a new connection attempt for the next pool. If we reach this point + // return true regardless of if newStream resulted in an immediate result or + // an async call, as either way the attempt will result in success/failure + // callbacks. current_ = next_pool.value(); newStream(); return true; @@ -209,7 +213,7 @@ ConnectionPool::Cancellable* ConnectivityGrid::newStream(Http::ResponseDecoder& ConnectionPool::Cancellable* ret = wrapped_callback.get(); LinkedList::moveIntoList(std::move(wrapped_callback), wrapped_callbacks_); // Note that in the case of immediate attempt/failure, newStream will delete this. - if (wrapped_callbacks_.front()->newStream()) { + if (wrapped_callbacks_.front()->newStream() == StreamCreationResult::ImmediateResult) { // If newStream succeeds, return nullptr as the caller has received their // callback and does not need a cancellable handle. return nullptr; diff --git a/source/common/http/conn_pool_grid.h b/source/common/http/conn_pool_grid.h index db1dea5f28994..bd1989f81e6e6 100644 --- a/source/common/http/conn_pool_grid.h +++ b/source/common/http/conn_pool_grid.h @@ -19,6 +19,11 @@ class ConnectivityGrid : public ConnectionPool::Instance, std::vector protocols_; }; + enum class StreamCreationResult { + ImmediateResult, + StreamCreationPending, + }; + using PoolIterator = std::list::iterator; // This is a class which wraps a caller's connection pool callbacks to @@ -38,8 +43,7 @@ class ConnectivityGrid : public ConnectionPool::Instance, public: ConnectionAttemptCallbacks(WrapperCallbacks& parent, PoolIterator it); - // Returns true if a stream is immediately created, false if it is pending. - bool newStream(); + StreamCreationResult newStream(); // ConnectionPool::Callbacks void onPoolFailure(ConnectionPool::PoolFailureReason reason, @@ -67,9 +71,8 @@ class ConnectivityGrid : public ConnectionPool::Instance, // ConnectionPool::Cancellable void cancel(Envoy::ConnectionPool::CancelPolicy cancel_policy) override; - // Attempt to create a new stream for pool(). Returns true if the stream has - // been created. - bool newStream(); + // Attempt to create a new stream for pool(). + StreamCreationResult newStream(); // Removes this from the owning list, deleting it. void deleteThis(); From 69b97db5db2c01d8df5d14face7637512ca89e7a Mon Sep 17 00:00:00 2001 From: code Date: Wed, 14 Apr 2021 23:26:45 +0800 Subject: [PATCH 002/209] extended request options to support passing metadata match criteria (#15863) Commit Message: extended request options to support passing metadata match criteria Additional Description: Extended request options to support passing metadata match criteria. Check #15163 for more info about that why we need this PR. This PR is just a possible solution. I would be willing to modify it if there is a more elegant solution. Risk Level: Low. Testing: Added. Docs Changes: N/A. Signed-off-by: wbpcode --- include/envoy/http/async_client.h | 13 ++++ source/common/http/async_client_impl.cc | 3 + test/common/http/async_client_impl_test.cc | 91 ++++++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/include/envoy/http/async_client.h b/include/envoy/http/async_client.h index 5121a3c7b969f..b38eabc2d76c0 100644 --- a/include/envoy/http/async_client.h +++ b/include/envoy/http/async_client.h @@ -205,6 +205,13 @@ class AsyncClient { parent_context = v; return *this; } + // Set dynamic metadata of async stream. If a metadata record with filter name 'envoy.lb' is + // provided, metadata match criteria of async stream route will be overridden by the metadata + // and then used by the subset load balancer. + StreamOptions& setMetadata(const envoy::config::core::v3::Metadata& m) { + metadata = m; + return *this; + } // For gmock test bool operator==(const StreamOptions& src) const { @@ -230,6 +237,8 @@ class AsyncClient { // Provides parent context. Currently, this holds stream info from the caller. ParentContext parent_context; + + envoy::config::core::v3::Metadata metadata; }; /** @@ -261,6 +270,10 @@ class AsyncClient { StreamOptions::setParentContext(v); return *this; } + RequestOptions& setMetadata(const envoy::config::core::v3::Metadata& m) { + StreamOptions::setMetadata(m); + return *this; + } RequestOptions& setParentSpan(Tracing::Span& parent_span) { parent_span_ = &parent_span; return *this; diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index ffe65b5ed62f5..044bf497b4c42 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -87,6 +87,9 @@ AsyncStreamImpl::AsyncStreamImpl(AsyncClientImpl& parent, AsyncClient::StreamCal route_(std::make_shared(parent_.cluster_->name(), options.timeout, options.hash_policy)), send_xff_(options.send_xff) { + + stream_info_.dynamicMetadata().MergeFrom(options.metadata); + if (options.buffer_body_for_retry) { buffered_body_ = std::make_unique(); } diff --git a/test/common/http/async_client_impl_test.cc b/test/common/http/async_client_impl_test.cc index 87574d77f2e7c..cbff548d733a8 100644 --- a/test/common/http/async_client_impl_test.cc +++ b/test/common/http/async_client_impl_test.cc @@ -342,6 +342,97 @@ TEST_F(AsyncClientImplTest, BasicHashPolicy) { response_decoder_->decodeData(data, true); } +TEST_F(AsyncClientImplTest, WithoutMetadata) { + message_->body().add("test body"); + Buffer::Instance& data = message_->body(); + + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke([&](ResponseDecoder& decoder, + ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { + callbacks.onPoolReady(stream_encoder_, cm_.thread_local_cluster_.conn_pool_.host_, + stream_info_, {}); + response_decoder_ = &decoder; + return nullptr; + })); + + EXPECT_CALL(cm_.thread_local_cluster_, httpConnPool(_, _, _)) + .WillOnce( + Invoke([&](Upstream::ResourcePriority, absl::optional, + Upstream::LoadBalancerContext* context) -> Http::ConnectionPool::Instance* { + EXPECT_EQ(context->metadataMatchCriteria(), nullptr); + return &cm_.thread_local_cluster_.conn_pool_; + })); + + TestRequestHeaderMapImpl copy(message_->headers()); + copy.addCopy("x-envoy-internal", "true"); + copy.addCopy("x-forwarded-for", "127.0.0.1"); + copy.addCopy(":scheme", "http"); + + EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(©), false)); + + AsyncClient::RequestOptions options; + envoy::config::core::v3::Metadata metadata; + metadata.mutable_filter_metadata()->insert( + {"fake_test_domain", MessageUtil::keyValueStruct("fake_test_key", "fake_test_value")}); + options.setMetadata(metadata); + + auto* request = client_.send(std::move(message_), callbacks_, options); + EXPECT_NE(request, nullptr); + + expectSuccess(request, 200); + + ResponseHeaderMapPtr response_headers(new TestResponseHeaderMapImpl{{":status", "200"}}); + response_decoder_->decodeHeaders(std::move(response_headers), false); + response_decoder_->decodeData(data, true); +} + +TEST_F(AsyncClientImplTest, WithMetadata) { + message_->body().add("test body"); + Buffer::Instance& data = message_->body(); + + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke([&](ResponseDecoder& decoder, + ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { + callbacks.onPoolReady(stream_encoder_, cm_.thread_local_cluster_.conn_pool_.host_, + stream_info_, {}); + response_decoder_ = &decoder; + return nullptr; + })); + + EXPECT_CALL(cm_.thread_local_cluster_, httpConnPool(_, _, _)) + .WillOnce( + Invoke([&](Upstream::ResourcePriority, absl::optional, + Upstream::LoadBalancerContext* context) -> Http::ConnectionPool::Instance* { + EXPECT_NE(context->metadataMatchCriteria(), nullptr); + EXPECT_EQ(context->metadataMatchCriteria()->metadataMatchCriteria().at(0)->name(), + "fake_test_key"); + return &cm_.thread_local_cluster_.conn_pool_; + })); + + TestRequestHeaderMapImpl copy(message_->headers()); + copy.addCopy("x-envoy-internal", "true"); + copy.addCopy("x-forwarded-for", "127.0.0.1"); + copy.addCopy(":scheme", "http"); + + EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(©), false)); + + AsyncClient::RequestOptions options; + envoy::config::core::v3::Metadata metadata; + metadata.mutable_filter_metadata()->insert( + {Envoy::Config::MetadataFilters::get().ENVOY_LB, + MessageUtil::keyValueStruct("fake_test_key", "fake_test_value")}); + options.setMetadata(metadata); + + auto* request = client_.send(std::move(message_), callbacks_, options); + EXPECT_NE(request, nullptr); + + expectSuccess(request, 200); + + ResponseHeaderMapPtr response_headers(new TestResponseHeaderMapImpl{{":status", "200"}}); + response_decoder_->decodeHeaders(std::move(response_headers), false); + response_decoder_->decodeData(data, true); +} + TEST_F(AsyncClientImplTest, Retry) { ON_CALL(runtime_.snapshot_, featureEnabled("upstream.use_retry", 100)) .WillByDefault(Return(true)); From d357fc0ced01e5990fa719c7d4c36e85b717b22c Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 14 Apr 2021 16:48:38 +0100 Subject: [PATCH 003/209] docs: Fix emphasize lines in literalincludes (#15969) Signed-off-by: Ryan Northey --- .../start/quick-start/configuration-dynamic-control-plane.rst | 4 ++-- .../start/quick-start/configuration-dynamic-filesystem.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/root/start/quick-start/configuration-dynamic-control-plane.rst b/docs/root/start/quick-start/configuration-dynamic-control-plane.rst index d14e74a79de50..1ccdcfc56a745 100644 --- a/docs/root/start/quick-start/configuration-dynamic-control-plane.rst +++ b/docs/root/start/quick-start/configuration-dynamic-control-plane.rst @@ -69,6 +69,6 @@ The ``xds_cluster`` is configured to query a control plane at http://my-control- .. literalinclude:: _include/envoy-dynamic-control-plane-demo.yaml :language: yaml :linenos: - :lines: 17-35 + :lines: 17-39 :lineno-start: 17 - :emphasize-lines: 3-17 + :emphasize-lines: 3-21 diff --git a/docs/root/start/quick-start/configuration-dynamic-filesystem.rst b/docs/root/start/quick-start/configuration-dynamic-filesystem.rst index dc6c02100e1b3..a251aa3886054 100644 --- a/docs/root/start/quick-start/configuration-dynamic-filesystem.rst +++ b/docs/root/start/quick-start/configuration-dynamic-filesystem.rst @@ -89,4 +89,4 @@ proxies over ``TLS`` to https://www.envoyproxy.io. .. literalinclude:: _include/envoy-dynamic-cds-demo.yaml :language: yaml :linenos: - :emphasize-lines: 8, 14-15, 19-20 + :emphasize-lines: 12, 18-19, 23-24 From dffcf01d6c6f3370b0276c12781fc8445047cfbe Mon Sep 17 00:00:00 2001 From: williamsfu99 <32112201+williamsfu99@users.noreply.github.com> Date: Wed, 14 Apr 2021 09:37:42 -0700 Subject: [PATCH 004/209] thrift proxy: add upstream_rq_time histograms to cluster metrics (#15884) In #15668 we began supporting cluster level metrics for the thrift_proxy connection manager. These metrics were limited to messageType counters only; now that these are working OK we are comfortable moving to cluster level histograms for upstream_rq_time. Risk Level: Low Testing: New unit tests Docs Changes: future pr Release Notes: updated Signed-off-by: William Fu --- .../thrift_filters/router_filter.rst | 18 ++- docs/root/version_history/current.rst | 3 +- .../network/thrift_proxy/conn_manager.h | 4 + .../network/thrift_proxy/filters/filter.h | 5 + .../thrift_proxy/router/router_impl.cc | 39 +++-- .../network/thrift_proxy/router/router_impl.h | 59 +++++--- .../filters/network/thrift_proxy/mocks.h | 1 + .../network/thrift_proxy/router_test.cc | 134 +++++++++++++----- 8 files changed, 187 insertions(+), 76 deletions(-) diff --git a/docs/root/configuration/other_protocols/thrift_filters/router_filter.rst b/docs/root/configuration/other_protocols/thrift_filters/router_filter.rst index 171f6550bdb45..009c22e3e50a3 100644 --- a/docs/root/configuration/other_protocols/thrift_filters/router_filter.rst +++ b/docs/root/configuration/other_protocols/thrift_filters/router_filter.rst @@ -25,15 +25,19 @@ The filter outputs generic routing error statistics in the *thrift. no_healthy_upstream, Counter, Total requests with no healthy upstream endpoints available. -The filter also outputs MessageType statistics in the upstream cluster's stat scope. +The filter is also responsible for cluster-level statistics derived from routed upstream clusters. +Since these stats utilize the underlying cluster scope, we prefix with the `thrift` namespace. .. csv-table:: :header: Name, Type, Description :widths: 1, 1, 2 - request_call, Counter, Total requests with the "Call" message type. - request_oneway, Counter, Total requests with the "Oneway" message type. - request_invalid_type, Counter, Total requests with an unsupported message type. - response_reply, Counter, Total responses with the "Reply" message type. Includes both successes and errors. - response_exception, Counter, Total responses with the "Exception" message type. - response_invalid_type, Counter, Total responses with an unsupported message type. + thrift.upstream_rq_call, Counter, Total requests with the "Call" message type. + thrift.upstream_rq_oneway, Counter, Total requests with the "Oneway" message type. + thrift.upstream_rq_invalid_type, Counter, Total requests with an unsupported message type. + thrift.upstream_resp_reply, Counter, Total responses with the "Reply" message type. Sums both Successses and Errors. + thrift.upstream_resp_success, Counter, Total Replies that are considered "Successes". + thrift.upstream_resp_error, Counter, Total Replies that are considered "Errors". + thrift.upstream_resp_exception, Counter, Total responses with the "Exception" message type. + thrift.upstream_resp_invalid_type, Counter, Total responses with an unsupported message type. + thrift.upstream_rq_time, Histogram, total rq time from rq complete to resp complete; includes oneway messages. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 121d391e93449..1bb975d173d8c 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -166,7 +166,8 @@ New Features * tcp_proxy: added a :ref:`use_post field ` for using HTTP POST to proxy TCP streams. * tcp_proxy: added a :ref:`headers_to_add field ` for setting additional headers to the HTTP requests for TCP proxing. * thrift_proxy: added a :ref:`max_requests_per_connection field ` for setting maximum requests for per downstream connection. -* thrift_proxy: added per upstream metrics within the :ref:`thrift router ` for messagetype in request/response. +* thrift_proxy: added per upstream metrics within the :ref:`thrift router ` for messagetype counters in request/response. +* thrift_proxy: added per upstream metrics within the :ref:`thrift router ` for request time histograms. * tls peer certificate validation: added :ref:`SPIFFE validator ` for supporting isolated multiple trust bundles in a single listener or cluster. * tracing: added the :ref:`pack_trace_reason ` field as well as explicit configuration for the built-in :ref:`UuidRequestIdConfig ` diff --git a/source/extensions/filters/network/thrift_proxy/conn_manager.h b/source/extensions/filters/network/thrift_proxy/conn_manager.h index 879b2d5e7be00..eb056aaed1221 100644 --- a/source/extensions/filters/network/thrift_proxy/conn_manager.h +++ b/source/extensions/filters/network/thrift_proxy/conn_manager.h @@ -127,6 +127,7 @@ class ConnectionManager : public Network::ReadFilter, // ThriftFilters::DecoderFilterCallbacks uint64_t streamId() const override { return parent_.stream_id_; } const Network::Connection* connection() const override { return parent_.connection(); } + Event::Dispatcher& dispatcher() override { return parent_.dispatcher(); } void continueDecoding() override; Router::RouteConstSharedPtr route() override { return parent_.route(); } TransportType downstreamTransportType() const override { @@ -206,6 +207,9 @@ class ConnectionManager : public Network::ReadFilter, // ThriftFilters::DecoderFilterCallbacks uint64_t streamId() const override { return stream_id_; } const Network::Connection* connection() const override; + Event::Dispatcher& dispatcher() override { + return parent_.read_callbacks_->connection().dispatcher(); + } void continueDecoding() override { parent_.continueDecoding(); } Router::RouteConstSharedPtr route() override; TransportType downstreamTransportType() const override { diff --git a/source/extensions/filters/network/thrift_proxy/filters/filter.h b/source/extensions/filters/network/thrift_proxy/filters/filter.h index a19ab8ef5351e..b8b73ddf517b5 100644 --- a/source/extensions/filters/network/thrift_proxy/filters/filter.h +++ b/source/extensions/filters/network/thrift_proxy/filters/filter.h @@ -43,6 +43,11 @@ class DecoderFilterCallbacks { */ virtual const Network::Connection* connection() const PURE; + /** + * @return Event::Dispatcher& the thread local dispatcher for allocating timers, etc. + */ + virtual Event::Dispatcher& dispatcher() PURE; + /** * Continue iterating through the filter chain with buffered data. This routine can only be * called if the filter has previously returned StopIteration from one of the DecoderFilter diff --git a/source/extensions/filters/network/thrift_proxy/router/router_impl.cc b/source/extensions/filters/network/thrift_proxy/router/router_impl.cc index 92ddc7eae09fb..f239463073bfb 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/router/router_impl.cc @@ -241,15 +241,15 @@ FilterStatus Router::messageBegin(MessageMetadataSharedPtr metadata) { metadata->methodName()); switch (metadata->messageType()) { case MessageType::Call: - incClusterScopeCounter(request_call_); + incClusterScopeCounter({upstream_rq_call_}); break; case MessageType::Oneway: - incClusterScopeCounter(request_oneway_); + incClusterScopeCounter({upstream_rq_oneway_}); break; default: - incClusterScopeCounter(request_invalid_type_); + incClusterScopeCounter({upstream_rq_invalid_type_}); break; } @@ -353,20 +353,20 @@ void Router::onUpstreamData(Buffer::Instance& data, bool end_stream) { ENVOY_STREAM_LOG(debug, "response complete", *callbacks_); switch (callbacks_->responseMetadata()->messageType()) { case MessageType::Reply: - incClusterScopeCounter(response_reply_); + incClusterScopeCounter({upstream_resp_reply_}); if (callbacks_->responseSuccess()) { - incClusterScopeCounter(response_reply_success_); + incClusterScopeCounter({upstream_resp_reply_success_}); } else { - incClusterScopeCounter(response_reply_error_); + incClusterScopeCounter({upstream_resp_reply_error_}); } break; case MessageType::Exception: - incClusterScopeCounter(response_exception_); + incClusterScopeCounter({upstream_resp_exception_}); break; default: - incClusterScopeCounter(response_invalid_type_); + incClusterScopeCounter({upstream_resp_invalid_type_}); break; } upstream_request_->onResponseComplete(); @@ -522,9 +522,14 @@ void Router::UpstreamRequest::onRequestStart(bool continue_decoding) { } } -void Router::UpstreamRequest::onRequestComplete() { request_complete_ = true; } +void Router::UpstreamRequest::onRequestComplete() { + Event::Dispatcher& dispatcher = parent_.callbacks_->dispatcher(); + downstream_request_complete_time_ = dispatcher.timeSource().monotonicTime(); + request_complete_ = true; +} void Router::UpstreamRequest::onResponseComplete() { + chargeResponseTiming(); response_complete_ = true; conn_state_ = nullptr; conn_data_.reset(); @@ -542,6 +547,8 @@ void Router::UpstreamRequest::onResetStream(ConnectionPool::PoolFailureReason re return; } + chargeResponseTiming(); + switch (reason) { case ConnectionPool::PoolFailureReason::Overflow: parent_.callbacks_->sendLocalReply( @@ -576,6 +583,20 @@ void Router::UpstreamRequest::onResetStream(ConnectionPool::PoolFailureReason re } } +void Router::UpstreamRequest::chargeResponseTiming() { + if (charged_response_timing_) { + return; + } + charged_response_timing_ = true; + Event::Dispatcher& dispatcher = parent_.callbacks_->dispatcher(); + const std::chrono::milliseconds response_time = + std::chrono::duration_cast( + dispatcher.timeSource().monotonicTime() - downstream_request_complete_time_); + const uint64_t count = response_time.count(); + parent_.recordClusterScopeHistogram({parent_.upstream_rq_time_}, + Stats::Histogram::Unit::Milliseconds, count); +} + } // namespace Router } // namespace ThriftProxy } // namespace NetworkFilters diff --git a/source/extensions/filters/network/thrift_proxy/router/router_impl.h b/source/extensions/filters/network/thrift_proxy/router/router_impl.h index 99162191e5f0d..e125c69fce0b8 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router_impl.h +++ b/source/extensions/filters/network/thrift_proxy/router/router_impl.h @@ -180,14 +180,16 @@ class Router : public Tcp::ConnectionPool::UpstreamCallbacks, Stats::Scope& scope) : cluster_manager_(cluster_manager), stats_(generateStats(stat_prefix, scope)), stat_name_set_(scope.symbolTable().makeSet("thrift_proxy")), - request_call_(stat_name_set_->add("request_call")), - request_oneway_(stat_name_set_->add("request_oneway")), - request_invalid_type_(stat_name_set_->add("request_invalid_type")), - response_reply_(stat_name_set_->add("response_reply")), - response_reply_success_(stat_name_set_->add("response_success")), - response_reply_error_(stat_name_set_->add("response_error")), - response_exception_(stat_name_set_->add("response_exception")), - response_invalid_type_(stat_name_set_->add("response_invalid_type")), + symbol_table_(scope.symbolTable()), + upstream_rq_call_(stat_name_set_->add("thrift.upstream_rq_call")), + upstream_rq_oneway_(stat_name_set_->add("thrift.upstream_rq_oneway")), + upstream_rq_invalid_type_(stat_name_set_->add("thrift.upstream_rq_invalid_type")), + upstream_resp_reply_(stat_name_set_->add("thrift.upstream_resp_reply")), + upstream_resp_reply_success_(stat_name_set_->add("thrift.upstream_resp_success")), + upstream_resp_reply_error_(stat_name_set_->add("thrift.upstream_resp_error")), + upstream_resp_exception_(stat_name_set_->add("thrift.upstream_resp_exception")), + upstream_resp_invalid_type_(stat_name_set_->add("thrift.upstream_resp_invalid_type")), + upstream_rq_time_(stat_name_set_->add("thrift.upstream_rq_time")), passthrough_supported_(false) {} ~Router() override = default; @@ -197,11 +199,6 @@ class Router : public Tcp::ConnectionPool::UpstreamCallbacks, void setDecoderFilterCallbacks(ThriftFilters::DecoderFilterCallbacks& callbacks) override; bool passthroughSupported() const override { return passthrough_supported_; } - // Stats - void incClusterScopeCounter(Stats::StatName name) { - cluster_->statsScope().counterFromStatName(name).inc(); - } - // ProtocolConverter FilterStatus transportBegin(MessageMetadataSharedPtr metadata) override; FilterStatus transportEnd() override; @@ -245,6 +242,7 @@ class Router : public Tcp::ConnectionPool::UpstreamCallbacks, void onResponseComplete(); void onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr host); void onResetStream(ConnectionPool::PoolFailureReason reason); + void chargeResponseTiming(); Router& parent_; Tcp::ConnectionPool::Instance& conn_pool_; @@ -261,8 +259,25 @@ class Router : public Tcp::ConnectionPool::UpstreamCallbacks, bool request_complete_ : 1; bool response_started_ : 1; bool response_complete_ : 1; + + bool charged_response_timing_{false}; + MonotonicTime downstream_request_complete_time_; }; + // Stats + void incClusterScopeCounter(const Stats::StatNameVec& names) const { + const Stats::SymbolTable::StoragePtr stat_name_storage = symbol_table_.join(names); + cluster_->statsScope().counterFromStatName(Stats::StatName(stat_name_storage.get())).inc(); + } + + void recordClusterScopeHistogram(const Stats::StatNameVec& names, Stats::Histogram::Unit unit, + uint64_t count) const { + const Stats::SymbolTable::StoragePtr stat_name_storage = symbol_table_.join(names); + cluster_->statsScope() + .histogramFromStatName(Stats::StatName(stat_name_storage.get()), unit) + .recordValue(count); + } + void convertMessageBegin(MessageMetadataSharedPtr metadata); void cleanup(); RouterStats generateStats(const std::string& prefix, Stats::Scope& scope) { @@ -274,14 +289,16 @@ class Router : public Tcp::ConnectionPool::UpstreamCallbacks, Upstream::ClusterManager& cluster_manager_; RouterStats stats_; Stats::StatNameSetPtr stat_name_set_; - const Stats::StatName request_call_; - const Stats::StatName request_oneway_; - const Stats::StatName request_invalid_type_; - const Stats::StatName response_reply_; - const Stats::StatName response_reply_success_; - const Stats::StatName response_reply_error_; - const Stats::StatName response_exception_; - const Stats::StatName response_invalid_type_; + Stats::SymbolTable& symbol_table_; + const Stats::StatName upstream_rq_call_; + const Stats::StatName upstream_rq_oneway_; + const Stats::StatName upstream_rq_invalid_type_; + const Stats::StatName upstream_resp_reply_; + const Stats::StatName upstream_resp_reply_success_; + const Stats::StatName upstream_resp_reply_error_; + const Stats::StatName upstream_resp_exception_; + const Stats::StatName upstream_resp_invalid_type_; + const Stats::StatName upstream_rq_time_; ThriftFilters::DecoderFilterCallbacks* callbacks_{}; RouteConstSharedPtr route_{}; diff --git a/test/extensions/filters/network/thrift_proxy/mocks.h b/test/extensions/filters/network/thrift_proxy/mocks.h index 5595bd92dd0f5..7db68172d259a 100644 --- a/test/extensions/filters/network/thrift_proxy/mocks.h +++ b/test/extensions/filters/network/thrift_proxy/mocks.h @@ -246,6 +246,7 @@ class MockDecoderFilterCallbacks : public DecoderFilterCallbacks { // ThriftProxy::ThriftFilters::DecoderFilterCallbacks MOCK_METHOD(uint64_t, streamId, (), (const)); MOCK_METHOD(const Network::Connection*, connection, (), (const)); + MOCK_METHOD(Event::Dispatcher&, dispatcher, ()); MOCK_METHOD(void, continueDecoding, ()); MOCK_METHOD(Router::RouteConstSharedPtr, route, ()); MOCK_METHOD(TransportType, downstreamTransportType, (), (const)); diff --git a/test/extensions/filters/network/thrift_proxy/router_test.cc b/test/extensions/filters/network/thrift_proxy/router_test.cc index f0af7824717f9..72410c685d493 100644 --- a/test/extensions/filters/network/thrift_proxy/router_test.cc +++ b/test/extensions/filters/network/thrift_proxy/router_test.cc @@ -132,6 +132,7 @@ class ThriftRouterTestBase { EXPECT_EQ(FilterStatus::StopIteration, router_->messageBegin(metadata_)); EXPECT_CALL(callbacks_, connection()).WillRepeatedly(Return(&connection_)); + EXPECT_CALL(callbacks_, dispatcher()).WillRepeatedly(ReturnRef(dispatcher_)); EXPECT_EQ(&connection_, router_->downstreamConnection()); // Not yet implemented: @@ -194,6 +195,7 @@ class ThriftRouterTestBase { Invoke([&]() -> Tcp::ConnectionPool::ConnectionState* { return conn_state_.get(); })); EXPECT_CALL(callbacks_, connection()).WillRepeatedly(Return(&connection_)); + EXPECT_CALL(callbacks_, dispatcher()).WillRepeatedly(ReturnRef(dispatcher_)); EXPECT_EQ(&connection_, router_->downstreamConnection()); // Not yet implemented: @@ -342,6 +344,8 @@ class ThriftRouterTestBase { NiceMock context_; NiceMock connection_; + NiceMock dispatcher_; + NiceMock time_source_; NiceMock callbacks_; NiceMock* transport_{}; NiceMock* protocol_{}; @@ -422,7 +426,7 @@ TEST_F(ThriftRouterTest, PoolRemoteConnectionFailure) { startRequest(MessageType::Call); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); EXPECT_CALL(callbacks_, sendLocalReply(_, _)) @@ -442,7 +446,7 @@ TEST_F(ThriftRouterTest, PoolLocalConnectionFailure) { startRequest(MessageType::Call); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_.poolFailure( @@ -455,7 +459,7 @@ TEST_F(ThriftRouterTest, PoolTimeout) { startRequest(MessageType::Call); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); EXPECT_CALL(callbacks_, sendLocalReply(_, _)) @@ -475,7 +479,7 @@ TEST_F(ThriftRouterTest, PoolOverflowFailure) { startRequest(MessageType::Call); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); EXPECT_CALL(callbacks_, sendLocalReply(_, _)) @@ -494,7 +498,7 @@ TEST_F(ThriftRouterTest, PoolConnectionFailureWithOnewayMessage) { startRequest(MessageType::Oneway); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_oneway") + .counterFromString("thrift.upstream_rq_oneway") .value()); EXPECT_CALL(callbacks_, sendLocalReply(_, _)).Times(0); @@ -561,7 +565,7 @@ TEST_F(ThriftRouterTest, ClusterMaintenanceMode) { EXPECT_EQ(FilterStatus::StopIteration, router_->messageBegin(metadata_)); EXPECT_EQ(1U, context_.scope().counterFromString("test.upstream_rq_maintenance_mode").value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); } @@ -586,7 +590,7 @@ TEST_F(ThriftRouterTest, NoHealthyHosts) { EXPECT_EQ(FilterStatus::StopIteration, router_->messageBegin(metadata_)); EXPECT_EQ(1U, context_.scope().counterFromString("test.no_healthy_upstream").value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); } @@ -794,13 +798,13 @@ TEST_F(ThriftRouterTest, ProtocolUpgrade) { destroyRouter(); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_reply") + .counterFromString("thrift.upstream_resp_reply") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_success") + .counterFromString("thrift.upstream_resp_success") .value()); } @@ -877,13 +881,13 @@ TEST_F(ThriftRouterTest, ProtocolUpgradeOnExistingUnusedConnection) { destroyRouter(); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_reply") + .counterFromString("thrift.upstream_resp_reply") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_success") + .counterFromString("thrift.upstream_resp_success") .value()); } @@ -927,16 +931,43 @@ TEST_F(ThriftRouterTest, ProtocolUpgradeSkippedOnExistingConnection) { destroyRouter(); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_reply") + .counterFromString("thrift.upstream_resp_reply") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_success") + .counterFromString("thrift.upstream_resp_success") .value()); } +TEST_F(ThriftRouterTest, PoolTimeoutUpstreamTimeMeasurement) { + initializeRouter(); + + Stats::MockStore cluster_scope; + ON_CALL(*context_.cluster_manager_.thread_local_cluster_.cluster_.info_, statsScope()) + .WillByDefault(ReturnRef(cluster_scope)); + EXPECT_CALL(cluster_scope, counter("thrift.upstream_rq_call")); + + startRequest(MessageType::Call); + + dispatcher_.time_system_.advanceTimeWait(std::chrono::milliseconds(500)); + EXPECT_CALL(cluster_scope, + histogram("thrift.upstream_rq_time", Stats::Histogram::Unit::Milliseconds)); + EXPECT_CALL(cluster_scope, + deliverHistogramToSinks( + testing::Property(&Stats::Metric::name, "thrift.upstream_rq_time"), 500)); + EXPECT_CALL(callbacks_, sendLocalReply(_, _)) + .WillOnce(Invoke([&](const DirectResponse& response, bool end_stream) -> void { + auto& app_ex = dynamic_cast(response); + EXPECT_EQ(AppExceptionType::InternalError, app_ex.type_); + EXPECT_THAT(app_ex.what(), ContainsRegex(".*connection failure.*")); + EXPECT_TRUE(end_stream); + })); + context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_.poolFailure( + ConnectionPool::PoolFailureReason::Timeout); +} + TEST_P(ThriftRouterFieldTypeTest, OneWay) { FieldType field_type = GetParam(); @@ -948,10 +979,10 @@ TEST_P(ThriftRouterFieldTypeTest, OneWay) { destroyRouter(); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_oneway") + .counterFromString("thrift.upstream_rq_oneway") .value()); EXPECT_EQ(0UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_reply") + .counterFromString("thrift.upstream_resp_reply") .value()); } @@ -967,16 +998,43 @@ TEST_P(ThriftRouterFieldTypeTest, Call) { destroyRouter(); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_reply") + .counterFromString("thrift.upstream_resp_reply") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_success") + .counterFromString("thrift.upstream_resp_success") .value()); } +TEST_P(ThriftRouterFieldTypeTest, CallWithUpstreamRqTime) { + FieldType field_type = GetParam(); + + initializeRouter(); + + Stats::MockStore cluster_scope; + ON_CALL(*context_.cluster_manager_.thread_local_cluster_.cluster_.info_, statsScope()) + .WillByDefault(ReturnRef(cluster_scope)); + EXPECT_CALL(cluster_scope, counter("thrift.upstream_rq_call")); + EXPECT_CALL(cluster_scope, counter("thrift.upstream_resp_reply")); + EXPECT_CALL(cluster_scope, counter("thrift.upstream_resp_success")); + + startRequest(MessageType::Call); + connectUpstream(); + sendTrivialStruct(field_type); + completeRequest(); + + dispatcher_.time_system_.advanceTimeWait(std::chrono::milliseconds(500)); + EXPECT_CALL(cluster_scope, + histogram("thrift.upstream_rq_time", Stats::Histogram::Unit::Milliseconds)); + EXPECT_CALL(cluster_scope, + deliverHistogramToSinks( + testing::Property(&Stats::Metric::name, "thrift.upstream_rq_time"), 500)); + returnResponse(); + destroyRouter(); +} + TEST_P(ThriftRouterFieldTypeTest, Call_Error) { FieldType field_type = GetParam(); @@ -989,16 +1047,16 @@ TEST_P(ThriftRouterFieldTypeTest, Call_Error) { destroyRouter(); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_reply") + .counterFromString("thrift.upstream_resp_reply") .value()); EXPECT_EQ(0UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_success") + .counterFromString("thrift.upstream_resp_success") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_error") + .counterFromString("thrift.upstream_resp_error") .value()); } @@ -1014,10 +1072,10 @@ TEST_P(ThriftRouterFieldTypeTest, Exception) { destroyRouter(); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_exception") + .counterFromString("thrift.upstream_resp_exception") .value()); } @@ -1033,10 +1091,10 @@ TEST_P(ThriftRouterFieldTypeTest, UnknownMessageTypes) { destroyRouter(); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_invalid_type") + .counterFromString("thrift.upstream_rq_invalid_type") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_invalid_type") + .counterFromString("thrift.upstream_resp_invalid_type") .value()); } @@ -1056,13 +1114,13 @@ TEST_P(ThriftRouterFieldTypeTest, StripServiceNameEnabled) { destroyRouter(); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_reply") + .counterFromString("thrift.upstream_resp_reply") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_success") + .counterFromString("thrift.upstream_resp_success") .value()); } @@ -1082,13 +1140,13 @@ TEST_P(ThriftRouterFieldTypeTest, StripServiceNameDisabled) { destroyRouter(); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_reply") + .counterFromString("thrift.upstream_resp_reply") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_success") + .counterFromString("thrift.upstream_resp_success") .value()); } @@ -1108,13 +1166,13 @@ TEST_F(ThriftRouterTest, CallWithExistingConnection) { destroyRouter(); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("request_call") + .counterFromString("thrift.upstream_rq_call") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_reply") + .counterFromString("thrift.upstream_resp_reply") .value()); EXPECT_EQ(1UL, context_.cluster_manager_.thread_local_cluster_.cluster_.info_->statsScope() - .counterFromString("response_success") + .counterFromString("thrift.upstream_resp_success") .value()); } From f782c8164a7eccf6b05622ae4c411bd971360fbe Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 14 Apr 2021 10:00:12 -0700 Subject: [PATCH 005/209] wasm: support adding trailers when they don't already exist. (#15831) Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/context.cc | 6 +++ .../http/wasm/test_data/headers_rust.rs | 14 ++++++- .../filters/http/wasm/test_data/test_cpp.cc | 24 ++++++++--- .../filters/http/wasm/wasm_filter_test.cc | 40 +++++++++++++++++++ 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 748119568bca7..fa75e27fa82e8 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -657,10 +657,16 @@ Http::HeaderMap* Context::getMap(WasmHeaderMapType type) { case WasmHeaderMapType::RequestHeaders: return request_headers_; case WasmHeaderMapType::RequestTrailers: + if (request_trailers_ == nullptr && request_body_buffer_ && end_of_stream_) { + request_trailers_ = &decoder_callbacks_->addDecodedTrailers(); + } return request_trailers_; case WasmHeaderMapType::ResponseHeaders: return response_headers_; case WasmHeaderMapType::ResponseTrailers: + if (response_trailers_ == nullptr && response_body_buffer_ && end_of_stream_) { + response_trailers_ = &encoder_callbacks_->addEncodedTrailers(); + } return response_trailers_; default: return nullptr; diff --git a/test/extensions/filters/http/wasm/test_data/headers_rust.rs b/test/extensions/filters/http/wasm/test_data/headers_rust.rs index c2aee58f7b0ba..1fbc30680e5eb 100644 --- a/test/extensions/filters/http/wasm/test_data/headers_rust.rs +++ b/test/extensions/filters/http/wasm/test_data/headers_rust.rs @@ -1,4 +1,4 @@ -use log::{trace, debug, error, info, warn}; +use log::{debug, error, info, trace, warn}; use proxy_wasm::traits::{Context, HttpContext}; use proxy_wasm::types::*; @@ -49,10 +49,13 @@ impl HttpContext for TestStream { action } - fn on_http_request_body(&mut self, body_size: usize, _: bool) -> Action { + fn on_http_request_body(&mut self, body_size: usize, end_of_stream: bool) -> Action { if let Some(body) = self.get_http_request_body(0, body_size) { error!("onBody {}", String::from_utf8(body).unwrap()); } + if end_of_stream { + self.add_http_request_trailer("newtrailer", "request"); + } Action::Continue } @@ -61,6 +64,13 @@ impl HttpContext for TestStream { Action::Continue } + fn on_http_response_body(&mut self, _: usize, end_of_stream: bool) -> Action { + if end_of_stream { + self.add_http_response_trailer("newtrailer", "response"); + } + Action::Continue + } + fn on_http_response_trailers(&mut self, _: usize) -> Action { Action::Pause } diff --git a/test/extensions/filters/http/wasm/test_data/test_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_cpp.cc index 705e468dce210..69f19e2c8b53d 100644 --- a/test/extensions/filters/http/wasm/test_data/test_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_cpp.cc @@ -38,6 +38,7 @@ class TestContext : public Context { FilterHeadersStatus onResponseHeaders(uint32_t, bool) override; FilterTrailersStatus onResponseTrailers(uint32_t) override; FilterDataStatus onRequestBody(size_t body_buffer_length, bool end_of_stream) override; + FilterDataStatus onResponseBody(size_t body_buffer_length, bool end_of_stream) override; void onLog() override; void onDone() override; @@ -306,11 +307,14 @@ FilterTrailersStatus TestContext::onResponseTrailers(uint32_t) { return FilterTrailersStatus::StopIteration; } -FilterDataStatus TestContext::onRequestBody(size_t body_buffer_length, bool) { +FilterDataStatus TestContext::onRequestBody(size_t body_buffer_length, bool end_of_stream) { auto test = root()->test_; if (test == "headers") { auto body = getBufferBytes(WasmBufferType::HttpRequestBody, 0, body_buffer_length); logError(std::string("onBody ") + std::string(body->view())); + if (end_of_stream) { + CHECK_RESULT(addRequestTrailer("newtrailer", "request")); + } } else if (test == "metadata") { std::string value; if (!getValue({"node", "metadata", "wasm_node_get_key"}, &value)) { @@ -335,6 +339,16 @@ FilterDataStatus TestContext::onRequestBody(size_t body_buffer_length, bool) { return FilterDataStatus::Continue; } +FilterDataStatus TestContext::onResponseBody(size_t, bool end_of_stream) { + auto test = root()->test_; + if (test == "headers") { + if (end_of_stream) { + CHECK_RESULT(addResponseTrailer("newtrailer", "response")); + } + } + return FilterDataStatus::Continue; +} + void TestContext::onLog() { auto test = root()->test_; if (test == "headers") { @@ -351,10 +365,10 @@ void TestContext::onLog() { logWarn("response bogus-trailer found"); } } else if (test == "cluster_metadata") { - std::string cluster_metadata; - if (getValue({"cluster_metadata", "filter_metadata", "namespace", "key"}, &cluster_metadata)) { - logWarn("cluster metadata: " + cluster_metadata); - } + std::string cluster_metadata; + if (getValue({"cluster_metadata", "filter_metadata", "namespace", "key"}, &cluster_metadata)) { + logWarn("cluster metadata: " + cluster_metadata); + } } else if (test == "property") { setFilterState("wasm_state", "wasm_value"); auto path = getRequestHeader(":path"); diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index 2e484228b1fd1..adfff9ce26f4d 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -191,6 +191,43 @@ TEST_P(WasmHttpFilterTest, AllHeadersAndTrailers) { filter().onDestroy(); } +TEST_P(WasmHttpFilterTest, AddTrailers) { + setupTest("", "headers"); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + EXPECT_CALL(filter(), + log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers")))); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); + EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody data")))).Times(2); + EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue")); + EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm")); + + Buffer::OwnedImpl data("data"); + Http::TestRequestTrailerMapImpl request_trailers{}; + EXPECT_CALL(decoder_callbacks_, addDecodedTrailers()).Times(0); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, false)); + EXPECT_CALL(decoder_callbacks_, addDecodedTrailers()).WillOnce(ReturnRef(request_trailers)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + EXPECT_THAT(request_trailers.get_("newtrailer"), Eq("request")); + + Http::TestResponseHeaderMapImpl response_headers{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().encodeHeaders(response_headers, false)); + EXPECT_THAT(response_headers.get_("test-status"), Eq("OK")); + + Http::TestResponseTrailerMapImpl response_trailers{}; + EXPECT_CALL(encoder_callbacks_, addEncodedTrailers()).Times(0); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, false)); + EXPECT_CALL(encoder_callbacks_, addEncodedTrailers()).WillOnce(ReturnRef(response_trailers)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter().encodeData(data, true)); + EXPECT_THAT(response_trailers.get_("newtrailer"), Eq("response")); + + filter().onDestroy(); +} + TEST_P(WasmHttpFilterTest, AllHeadersAndTrailersNotStarted) { setupTest("", "headers"); setupFilter(); @@ -224,7 +261,10 @@ TEST_P(WasmHttpFilterTest, HeadersOnlyRequestHeadersAndBody) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); EXPECT_FALSE(filter().endOfStream(proxy_wasm::WasmStreamType::Request)); Buffer::OwnedImpl data("hello"); + Http::TestRequestTrailerMapImpl request_trailers{}; + EXPECT_CALL(decoder_callbacks_, addDecodedTrailers()).WillOnce(ReturnRef(request_trailers)); EXPECT_EQ(Http::FilterDataStatus::Continue, filter().decodeData(data, true)); + EXPECT_THAT(request_trailers.get_("newtrailer"), Eq("request")); filter().onDestroy(); } From 96573e2d230a5336cdc7cd674edc0fce8411fad0 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 14 Apr 2021 14:15:19 -0400 Subject: [PATCH 006/209] test: more fast-timeouts. (#15959) H2 waitForReset also picks up end stream, while H2 doesn't, so a bunch of QUIC tests were actually spinning on waitForReset until disconnect happened. Fixing it and fixing it forward by having a faster timeout which catches if we're waiting for the wrong thing. Risk Level: n/a Testing: passes locally at least =P Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk --- .../ext_proc/ext_proc_integration_test.cc | 2 +- .../http2_flood_integration_test.cc | 6 ++--- test/integration/http_integration.cc | 12 +++++----- .../http_timeout_integration_test.cc | 2 +- .../idle_timeout_integration_test.cc | 2 +- .../integration/integration_stream_decoder.cc | 10 +++++++- test/integration/integration_stream_decoder.h | 3 ++- .../multiplexed_integration_test.cc | 14 +++++------ .../multiplexed_upstream_integration_test.cc | 4 ++-- test/integration/protocol_integration_test.cc | 23 +++++++++---------- .../integration/quic_http_integration_test.cc | 2 +- .../tcp_tunneling_integration_test.cc | 6 ++--- test/integration/vhds_integration_test.cc | 2 +- test/integration/websocket_integration_test.h | 2 +- 14 files changed, 49 insertions(+), 41 deletions(-) diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index 58db76a08b85c..f7caddfab0caf 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -629,7 +629,7 @@ TEST_P(ExtProcIntegrationTest, GetAndRespondImmediatelyOnResponseBody) { // The stream should have been reset here before the complete // response was received. - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); } // Send a request, but wait longer than the "message timeout" before sending a response diff --git a/test/integration/http2_flood_integration_test.cc b/test/integration/http2_flood_integration_test.cc index 26caa01f26c1c..9d900353cb615 100644 --- a/test/integration/http2_flood_integration_test.cc +++ b/test/integration/http2_flood_integration_test.cc @@ -1351,7 +1351,7 @@ TEST_P(Http2FloodMitigationTest, UpstreamEmptyData) { } ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); EXPECT_EQ("200", response->headers().getStatusValue()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.http2.inbound_empty_frames_flood")->value()); @@ -1417,7 +1417,7 @@ TEST_P(Http2FloodMitigationTest, UpstreamPriorityOneOpenStream) { ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); // Downstream client should get stream reset since upstream sent headers but did not complete the // stream - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); EXPECT_EQ( 1, test_server_->counter("cluster.cluster_0.http2.inbound_priority_frames_flood")->value()); } @@ -1475,7 +1475,7 @@ TEST_P(Http2FloodMitigationTest, UpstreamRstStreamOnStreamIdleTimeout) { // Verify that when RST_STREAM overflows upstream queue it is handled correctly // by causing upstream connection to be disconnected. ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); // EXPECT_EQ("408", response->headers().getStatusValue()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.http2.outbound_control_flood")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_idle_timeout")->value()); diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index f04b8366654f2..8960e143407b0 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -693,7 +693,7 @@ void HttpIntegrationTest::testRouterUpstreamDisconnectBeforeResponseComplete( if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); } else { - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); codec_client_->close(); } @@ -1144,7 +1144,7 @@ void HttpIntegrationTest::testLargeRequestUrl(uint32_t url_size, uint32_t max_he EXPECT_TRUE(response->complete()); EXPECT_EQ("431", response->headers().Status()->value().getStringView()); } else { - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); codec_client_->close(); } } else { @@ -1192,7 +1192,7 @@ void HttpIntegrationTest::testLargeRequestHeaders(uint32_t size, uint32_t count, EXPECT_TRUE(response->complete()); EXPECT_EQ("431", response->headers().getStatusValue()); } else { - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); codec_client_->close(); } } else { @@ -1235,7 +1235,7 @@ void HttpIntegrationTest::testLargeRequestTrailers(uint32_t size, uint32_t max_s } else { // Expect a stream reset when the size of the trailers is larger than the maximum // limit. - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); codec_client_->close(); EXPECT_FALSE(response->complete()); } @@ -1432,7 +1432,7 @@ void HttpIntegrationTest::testMaxStreamDuration() { if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); } else { - response->waitForReset(); + ASSERT_TRUE(response->waitForEndStream()); codec_client_->close(); } } @@ -1477,7 +1477,7 @@ void HttpIntegrationTest::testMaxStreamDurationWithRetry(bool invoke_retry_upstr if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); } else { - response->waitForReset(); + ASSERT_TRUE(response->waitForEndStream()); codec_client_->close(); } diff --git a/test/integration/http_timeout_integration_test.cc b/test/integration/http_timeout_integration_test.cc index b0ac8dd841e2f..ec5e964107580 100644 --- a/test/integration/http_timeout_integration_test.cc +++ b/test/integration/http_timeout_integration_test.cc @@ -211,7 +211,7 @@ TEST_P(HttpTimeoutIntegrationTest, GlobalTimeoutAfterHeadersBeforeBodyResetsUpst ASSERT_TRUE(upstream_request_->waitForReset(std::chrono::seconds(15))); - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); codec_client_->close(); diff --git a/test/integration/idle_timeout_integration_test.cc b/test/integration/idle_timeout_integration_test.cc index 3418fadabf8a7..40328d9a1971d 100644 --- a/test/integration/idle_timeout_integration_test.cc +++ b/test/integration/idle_timeout_integration_test.cc @@ -71,7 +71,7 @@ class IdleTimeoutIntegrationTest : public HttpProtocolIntegrationTest { if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); } else { - response.waitForReset(); + ASSERT_TRUE(response.waitForReset()); codec_client_->close(); } if (!stat_name.empty()) { diff --git a/test/integration/integration_stream_decoder.cc b/test/integration/integration_stream_decoder.cc index 8803a39e3b3e0..bf8fe2aec2f3c 100644 --- a/test/integration/integration_stream_decoder.cc +++ b/test/integration/integration_stream_decoder.cc @@ -69,11 +69,19 @@ AssertionResult IntegrationStreamDecoder::waitForEndStream(std::chrono::millisec return AssertionSuccess(); } -void IntegrationStreamDecoder::waitForReset() { +AssertionResult IntegrationStreamDecoder::waitForReset(std::chrono::milliseconds timeout) { if (!saw_reset_) { + // Set a timer to stop the dispatcher if the timeout has been exceeded. + Event::TimerPtr timer(dispatcher_.createTimer([this]() -> void { dispatcher_.exit(); })); + timer->enableTimer(timeout); waiting_for_reset_ = true; dispatcher_.run(Event::Dispatcher::RunType::Block); + // If the timer has fired, this timed out before a reset was received. + if (!timer->enabled()) { + return AssertionFailure() << "Timed out waiting for reset."; + } } + return AssertionSuccess(); } void IntegrationStreamDecoder::decode100ContinueHeaders(Http::ResponseHeaderMapPtr&& headers) { diff --git a/test/integration/integration_stream_decoder.h b/test/integration/integration_stream_decoder.h index 2d022134e0aad..06697260ec034 100644 --- a/test/integration/integration_stream_decoder.h +++ b/test/integration/integration_stream_decoder.h @@ -42,7 +42,8 @@ class IntegrationStreamDecoder : public Http::ResponseDecoder, public Http::Stre void waitForBodyData(uint64_t size); testing::AssertionResult waitForEndStream(std::chrono::milliseconds timeout = TestUtility::DefaultTimeout); - void waitForReset(); + ABSL_MUST_USE_RESULT testing::AssertionResult + waitForReset(std::chrono::milliseconds timeout = TestUtility::DefaultTimeout); void clearBody() { body_.clear(); } // Http::StreamDecoder diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index 08ba60fe263bf..f270088f010bd 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -142,7 +142,7 @@ TEST_P(Http2IntegrationTest, CodecStreamIdleTimeout) { upstream_request_->encodeHeaders(default_response_headers_, false); upstream_request_->encodeData(70000, true); test_server_->waitForCounterEq("http2.tx_flush_timeout", 1); - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); } TEST_P(Http2IntegrationTest, Http2DownstreamKeepalive) { @@ -171,7 +171,7 @@ TEST_P(Http2IntegrationTest, Http2DownstreamKeepalive) { test_server_->waitForCounterEq("http2.keepalive_timeout", 1, std::chrono::milliseconds(timeout_ms * 2)); - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); } static std::string response_metadata_filter = R"EOF( @@ -299,7 +299,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { upstream_request_->encodeResetStream(); // Verifies stream is reset. - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); ASSERT_FALSE(response->complete()); } @@ -502,7 +502,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadataReachSizeLimit) { upstream_request_->encodeData(12, true); // Verifies reset is received. - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); ASSERT_FALSE(response->complete()); } @@ -882,8 +882,8 @@ TEST_P(Http2IntegrationTest, CodecErrorAfterStreamStart) { Buffer::OwnedImpl bogus_data("some really bogus data"); codec_client_->rawConnection().write(bogus_data, false); - // Verifies reset is received. - response->waitForReset(); + // Verifies error is received. + ASSERT_TRUE(response->waitForEndStream()); } TEST_P(Http2IntegrationTest, Http2BadMagic) { @@ -1690,7 +1690,7 @@ TEST_P(Http2IntegrationTest, OnLocalReply) { { default_request_headers_.addCopy("reset", "yes"); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); ASSERT_FALSE(response->complete()); } } diff --git a/test/integration/multiplexed_upstream_integration_test.cc b/test/integration/multiplexed_upstream_integration_test.cc index c9d98d42fdcb6..da0b16223b410 100644 --- a/test/integration/multiplexed_upstream_integration_test.cc +++ b/test/integration/multiplexed_upstream_integration_test.cc @@ -184,7 +184,7 @@ TEST_P(Http2UpstreamIntegrationTest, BidirectionalStreamingReset) { // Reset the stream. upstream_request_->encodeResetStream(); - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); EXPECT_FALSE(response->complete()); } @@ -386,7 +386,7 @@ TEST_P(Http2UpstreamIntegrationTest, UpstreamConnectionCloseWithManyStreams) { ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); // Ensure the streams are all reset successfully. for (uint32_t i = 1; i < num_requests; ++i) { - responses[i]->waitForReset(); + ASSERT_TRUE(responses[i]->waitForReset()); } } diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 36e048b10bc37..18be31fc32130 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -1192,7 +1192,7 @@ TEST_P(DownstreamProtocolIntegrationTest, HeadersWithUnderscoresCauseRequestReje ASSERT_TRUE(response->complete()); EXPECT_EQ("400", response->headers().getStatusValue()); } else { - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); codec_client_->close(); ASSERT_TRUE(response->reset()); EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason()); @@ -1257,7 +1257,7 @@ TEST_P(ProtocolIntegrationTest, 304WithBody) { // 304-with-body, is there a protocol error on the active stream. if (downstream_protocol_ >= Http::CodecClient::Type::HTTP2 && upstreamProtocol() >= FakeHttpConnection::Type::HTTP2) { - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); } } @@ -1438,7 +1438,7 @@ TEST_P(DownstreamProtocolIntegrationTest, InvalidContentLengthAllowed) { if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); } else { - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); codec_client_->close(); } @@ -1497,7 +1497,7 @@ TEST_P(DownstreamProtocolIntegrationTest, MultipleContentLengthsAllowed) { if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); } else { - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); codec_client_->close(); } @@ -1553,7 +1553,7 @@ name: local-reply-during-encode-data // Response was aborted after headers were sent to the client. // The stream was reset. Client does not receive body or trailers. - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); EXPECT_FALSE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); EXPECT_EQ(0, response->body().length()); @@ -1622,7 +1622,7 @@ TEST_P(DownstreamProtocolIntegrationTest, ManyRequestTrailersRejected) { EXPECT_TRUE(response->complete()); EXPECT_EQ("431", response->headers().getStatusValue()); } else { - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); codec_client_->close(); } } @@ -2193,8 +2193,7 @@ TEST_P(DownstreamProtocolIntegrationTest, BasicMaxStreamTimeout) { ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); test_server_->waitForCounterGe("http.config_test.downstream_rq_max_duration_reached", 1); - response->waitForReset(); - EXPECT_TRUE(response->complete()); + ASSERT_TRUE(response->waitForEndStream()); } TEST_P(DownstreamProtocolIntegrationTest, BasicMaxStreamTimeoutLegacy) { @@ -2214,7 +2213,7 @@ TEST_P(DownstreamProtocolIntegrationTest, BasicMaxStreamTimeoutLegacy) { ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); test_server_->waitForCounterGe("http.config_test.downstream_rq_max_duration_reached", 1); - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); EXPECT_FALSE(response->complete()); EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("max_duration_timeout")); } @@ -2259,7 +2258,7 @@ TEST_P(DownstreamProtocolIntegrationTest, ConnectIsBlocked) { EXPECT_EQ("404", response->headers().getStatusValue()); EXPECT_TRUE(response->complete()); } else { - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); ASSERT_TRUE(codec_client_->waitForDisconnect()); } } @@ -2288,7 +2287,7 @@ TEST_P(DownstreamProtocolIntegrationTest, ConnectStreamRejection) { auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ {":method", "CONNECT"}, {":path", "/"}, {":authority", "host"}}); - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); EXPECT_FALSE(codec_client_->disconnected()); } @@ -2321,7 +2320,7 @@ TEST_P(DownstreamProtocolIntegrationTest, Test100AndDisconnectLegacy) { ASSERT_TRUE(codec_client_->waitForDisconnect()); EXPECT_FALSE(response->complete()); } else { - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); EXPECT_FALSE(response->complete()); } } diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index afce0fb4af7f5..65352aea97969 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -544,7 +544,7 @@ TEST_P(QuicHttpIntegrationTest, Reset101SwitchProtocolResponse) { ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "101"}}, false); - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); codec_client_->close(); EXPECT_FALSE(response->complete()); } diff --git a/test/integration/tcp_tunneling_integration_test.cc b/test/integration/tcp_tunneling_integration_test.cc index bf97b2c482c26..93ace7f7f52f1 100644 --- a/test/integration/tcp_tunneling_integration_test.cc +++ b/test/integration/tcp_tunneling_integration_test.cc @@ -169,7 +169,7 @@ TEST_P(ConnectTerminationIntegrationTest, UpstreamClose) { // Tear down by closing the upstream connection. ASSERT_TRUE(fake_raw_upstream_connection_->close()); - response_->waitForReset(); + ASSERT_TRUE(response_->waitForReset()); } TEST_P(ConnectTerminationIntegrationTest, TestTimeout) { @@ -179,7 +179,7 @@ TEST_P(ConnectTerminationIntegrationTest, TestTimeout) { setUpConnection(); // Wait for the timeout to close the connection. - response_->waitForReset(); + ASSERT_TRUE(response_->waitForReset()); ASSERT_TRUE(fake_raw_upstream_connection_->waitForHalfClose()); } @@ -229,7 +229,7 @@ TEST_P(ConnectTerminationIntegrationTest, BasicMaxStreamDuration) { if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); } else { - response_->waitForReset(); + ASSERT_TRUE(response_->waitForReset()); codec_client_->close(); } } diff --git a/test/integration/vhds_integration_test.cc b/test/integration/vhds_integration_test.cc index 52e51b1bd5018..f310ded250f42 100644 --- a/test/integration/vhds_integration_test.cc +++ b/test/integration/vhds_integration_test.cc @@ -691,7 +691,7 @@ TEST_P(VhdsIntegrationTest, VhdsOnDemandUpdateHttpConnectionCloses) { vhds_stream_->sendGrpcMessage(vhds_update); codec_client_->sendReset(encoder); - response->waitForReset(); + ASSERT_TRUE(response->waitForReset()); EXPECT_TRUE(codec_client_->connected()); cleanupUpstreamAndDownstream(); diff --git a/test/integration/websocket_integration_test.h b/test/integration/websocket_integration_test.h index 5f3aae0edf99a..1f3aa362ebf20 100644 --- a/test/integration/websocket_integration_test.h +++ b/test/integration/websocket_integration_test.h @@ -40,7 +40,7 @@ class WebsocketIntegrationTest : public HttpProtocolIntegrationTest { void waitForClientDisconnectOrReset( Http::StreamResetReason reason = Http::StreamResetReason::RemoteReset) { if (downstreamProtocol() != Http::CodecClient::Type::HTTP1) { - response_->waitForReset(); + ASSERT_TRUE(response_->waitForReset()); ASSERT_EQ(reason, response_->resetReason()); } else { ASSERT_TRUE(codec_client_->waitForDisconnect()); From 432cc0671d8b96c3e440b01d2eaf34aa5ac127db Mon Sep 17 00:00:00 2001 From: Lahiru Udayanga Date: Thu, 15 Apr 2021 02:07:23 +0530 Subject: [PATCH 007/209] docs : update wasm service example plugin config as a bootstrap extension (#15914) The PR updates the example plugin configuration section related to wasm service in the envoy documentation. Signed-off-by: Lahiru Udayanga --- .../configuration/other_features/wasm_service.rst | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/root/configuration/other_features/wasm_service.rst b/docs/root/configuration/other_features/wasm_service.rst index 0026d65886930..72f718f80851f 100644 --- a/docs/root/configuration/other_features/wasm_service.rst +++ b/docs/root/configuration/other_features/wasm_service.rst @@ -10,15 +10,23 @@ Example plugin configuration: .. code-block:: yaml - wasm: - config: + bootstrap_extensions: + - name: envoy.bootstrap.wasm + typed_config: + "@type": type.googleapis.com/envoy.extensions.wasm.v3.WasmService + singleton: true config: name: "my_plugin" + configuration: + "@type": type.googleapis.com/google.protobuf.StringValue + value: | + { + "my_config_value": "my_value" + } vm_config: runtime: "envoy.wasm.runtime.v8" code: local: filename: "/etc/envoy_filter_http_wasm_example.wasm" - singleton: true The preceding snippet configures a plugin singleton service from a Wasm binary on local disk. From d6a9f3165ac957a2546e075d87dafc7ed108f97e Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 15 Apr 2021 16:31:51 +0100 Subject: [PATCH 008/209] docs: Cleanups for 1.18.0 release (#15983) Signed-off-by: Ryan Northey --- api/envoy/config/cluster/v3/cluster.proto | 8 ++--- .../config/route/v3/route_components.proto | 4 +-- .../route/v4alpha/route_components.proto | 4 +-- .../v3/http_connection_manager.proto | 6 ++-- .../v4alpha/http_connection_manager.proto | 6 ++-- api/envoy/extensions/wasm/v3/wasm.proto | 2 +- docs/build.sh | 2 ++ .../debugging/why_is_envoy_sending_413s.rst | 2 +- .../quick-start/_include/envoy-demo.yaml | 6 ---- .../quick-start/configuration-static.rst | 8 ++--- docs/root/start/quick-start/run-envoy.rst | 9 +---- .../envoy/config/cluster/v3/cluster.proto | 8 ++--- .../config/cluster/v4alpha/cluster.proto | 8 ++--- .../config/route/v3/route_components.proto | 4 +-- .../route/v4alpha/route_components.proto | 4 +-- .../v3/http_connection_manager.proto | 6 ++-- .../v4alpha/http_connection_manager.proto | 6 ++-- .../envoy/extensions/wasm/v3/wasm.proto | 2 +- tools/code_format/BUILD | 6 ++++ tools/code_format/rst_check.py | 36 +++++++++++++++++++ tools/protodoc/protodoc.py | 3 +- 21 files changed, 85 insertions(+), 55 deletions(-) create mode 100755 tools/code_format/rst_check.py diff --git a/api/envoy/config/cluster/v3/cluster.proto b/api/envoy/config/cluster/v3/cluster.proto index bd441dea49a08..e72a0aa80e128 100644 --- a/api/envoy/config/cluster/v3/cluster.proto +++ b/api/envoy/config/cluster/v3/cluster.proto @@ -756,7 +756,7 @@ message Cluster { // in the :ref:`http_protocol_options ` message. // upstream_http_protocol_options can be set via the cluster's // :ref:`extension_protocol_options`. - // See ref:`upstream_http_protocol_options + // See :ref:`upstream_http_protocol_options // ` // for example usage. core.v3.UpstreamHttpProtocolOptions upstream_http_protocol_options = 46 @@ -769,7 +769,7 @@ message Cluster { // in the :ref:`http_protocol_options ` message. // common_http_protocol_options can be set via the cluster's // :ref:`extension_protocol_options`. - // See ref:`upstream_http_protocol_options + // See :ref:`upstream_http_protocol_options // ` // for example usage. core.v3.HttpProtocolOptions common_http_protocol_options = 29 @@ -780,7 +780,7 @@ message Cluster { // :ref:`http_protocol_options ` message. // http_protocol_options can be set via the cluster's // :ref:`extension_protocol_options`. - // See ref:`upstream_http_protocol_options + // See :ref:`upstream_http_protocol_options // ` // for example usage. core.v3.Http1ProtocolOptions http_protocol_options = 13 @@ -796,7 +796,7 @@ message Cluster { // :ref:`http_protocol_options ` // message. http2_protocol_options can be set via the cluster's // :ref:`extension_protocol_options`. - // See ref:`upstream_http_protocol_options + // See :ref:`upstream_http_protocol_options // ` // for example usage. core.v3.Http2ProtocolOptions http2_protocol_options = 14 [ diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index e5946adafb19f..add42f121abad 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -455,7 +455,7 @@ message RouteMatch { // where Extended CONNECT requests may have a path, the path matchers will work if // there is a path present. // Note that CONNECT support is currently considered alpha in Envoy. - // [#comment:TODO(htuch): Replace the above comment with an alpha tag. + // [#comment: TODO(htuch): Replace the above comment with an alpha tag.] ConnectMatcher connect_matcher = 12; } @@ -770,7 +770,7 @@ message RouteAction { // Configuration for sending data upstream as a raw data payload. This is used for // CONNECT requests, when forwarding CONNECT payload as raw TCP. // Note that CONNECT support is currently considered alpha in Envoy. - // [#comment:TODO(htuch): Replace the above comment with an alpha tag. + // [#comment: TODO(htuch): Replace the above comment with an alpha tag.] ConnectConfig connect_config = 3; } diff --git a/api/envoy/config/route/v4alpha/route_components.proto b/api/envoy/config/route/v4alpha/route_components.proto index c70494a82efe3..072b5fb03232a 100644 --- a/api/envoy/config/route/v4alpha/route_components.proto +++ b/api/envoy/config/route/v4alpha/route_components.proto @@ -456,7 +456,7 @@ message RouteMatch { // where Extended CONNECT requests may have a path, the path matchers will work if // there is a path present. // Note that CONNECT support is currently considered alpha in Envoy. - // [#comment:TODO(htuch): Replace the above comment with an alpha tag. + // [#comment: TODO(htuch): Replace the above comment with an alpha tag.] ConnectMatcher connect_matcher = 12; } @@ -765,7 +765,7 @@ message RouteAction { // Configuration for sending data upstream as a raw data payload. This is used for // CONNECT requests, when forwarding CONNECT payload as raw TCP. // Note that CONNECT support is currently considered alpha in Envoy. - // [#comment:TODO(htuch): Replace the above comment with an alpha tag. + // [#comment: TODO(htuch): Replace the above comment with an alpha tag.] ConnectConfig connect_config = 3; } diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 51c4e229f1375..90e40369bddcd 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -239,7 +239,7 @@ message HttpConnectionManager { google.protobuf.BoolValue enabled = 3; } - // [#not-implemented-hide] Transformations that apply to path headers. Transformations are applied + // [#not-implemented-hide:] Transformations that apply to path headers. Transformations are applied // before any processing of requests by HTTP filters, routing, and matching. Only the normalized // path will be visible internally if a transformation is enabled. Any path rewrites that the // router performs (e.g. :ref:`regex_rewrite @@ -249,7 +249,7 @@ message HttpConnectionManager { // // Note: access logging and tracing will show the original *:path* header. message PathNormalizationOptions { - // [#not-implemented-hide] Normalization applies internally before any processing of requests by + // [#not-implemented-hide:] Normalization applies internally before any processing of requests by // HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults // to :ref:`NormalizePathRFC3986 // `. When not @@ -259,7 +259,7 @@ message HttpConnectionManager { // normalization due to disallowed characters.) type.http.v3.PathTransformation forwarding_transformation = 1; - // [#not-implemented-hide] Normalization only applies internally before any processing of + // [#not-implemented-hide:] Normalization only applies internally before any processing of // requests by HTTP filters, routing, and matching. These will be applied after full // transformation is applied. The *:path* header before this transformation will be restored in // the router filter and sent upstream unless it was mutated by a filter. Defaults to no diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index d442451cdae62..d40a8fb6a0559 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -238,7 +238,7 @@ message HttpConnectionManager { google.protobuf.BoolValue enabled = 3; } - // [#not-implemented-hide] Transformations that apply to path headers. Transformations are applied + // [#not-implemented-hide:] Transformations that apply to path headers. Transformations are applied // before any processing of requests by HTTP filters, routing, and matching. Only the normalized // path will be visible internally if a transformation is enabled. Any path rewrites that the // router performs (e.g. :ref:`regex_rewrite @@ -252,7 +252,7 @@ message HttpConnectionManager { "envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager." "PathNormalizationOptions"; - // [#not-implemented-hide] Normalization applies internally before any processing of requests by + // [#not-implemented-hide:] Normalization applies internally before any processing of requests by // HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults // to :ref:`NormalizePathRFC3986 // `. When not @@ -262,7 +262,7 @@ message HttpConnectionManager { // normalization due to disallowed characters.) type.http.v3.PathTransformation forwarding_transformation = 1; - // [#not-implemented-hide] Normalization only applies internally before any processing of + // [#not-implemented-hide:] Normalization only applies internally before any processing of // requests by HTTP filters, routing, and matching. These will be applied after full // transformation is applied. The *:path* header before this transformation will be restored in // the router filter and sent upstream unless it was mutated by a filter. Defaults to no diff --git a/api/envoy/extensions/wasm/v3/wasm.proto b/api/envoy/extensions/wasm/v3/wasm.proto index 5ed7b8e51d181..58b5c2130f6dd 100644 --- a/api/envoy/extensions/wasm/v3/wasm.proto +++ b/api/envoy/extensions/wasm/v3/wasm.proto @@ -46,7 +46,7 @@ message VmConfig { // VM plugin) to determine which VM will be used for the plugin. All plugins which use the same // *vm_id* and code will use the same VM. May be left blank. Sharing a VM between plugins can // reduce memory utilization and make sharing of data easier which may have security implications. - // See ref: "TODO: add ref" for details. + // [#comment: TODO: add ref for details.] string vm_id = 1; // The Wasm runtime type. diff --git a/docs/build.sh b/docs/build.sh index 7b6eee6ba76d4..860c261cc1bb4 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -171,6 +171,8 @@ rsync -av \ "${SCRIPT_DIR}"/_ext \ "${GENERATED_RST_DIR}" +bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/code_format:rst_check "${GENERATED_RST_DIR}" + # To speed up validate_fragment invocations in validating_code_block bazel build "${BAZEL_BUILD_OPTIONS[@]}" //tools/config_validation:validate_fragment diff --git a/docs/root/faq/debugging/why_is_envoy_sending_413s.rst b/docs/root/faq/debugging/why_is_envoy_sending_413s.rst index 39769282ea0a5..891c2ccea3f03 100644 --- a/docs/root/faq/debugging/why_is_envoy_sending_413s.rst +++ b/docs/root/faq/debugging/why_is_envoy_sending_413s.rst @@ -3,4 +3,4 @@ Why is Envoy sending 413s? ========================== -Envoy by default imposes limits to how much it will buffer for a given request. Generally, Envoy filters are designed to be streaming, and will pass data from downstream to upstream, or will simply pause processing while waiting for an external event (e.g. doing auth checks). Some filters, for example the buffer filter, require buffering the full request or response. If a request body is too large to buffer, but buffering is required by the filter, Envoy will send a 413. The buffer limits can be increased at the risk of making OOMs more possible. Please see the ref:`flow control docs ` for details. +Envoy by default imposes limits to how much it will buffer for a given request. Generally, Envoy filters are designed to be streaming, and will pass data from downstream to upstream, or will simply pause processing while waiting for an external event (e.g. doing auth checks). Some filters, for example the buffer filter, require buffering the full request or response. If a request body is too large to buffer, but buffering is required by the filter, Envoy will send a 413. The buffer limits can be increased at the risk of making OOMs more possible. Please see the :ref:`flow control docs ` for details. diff --git a/docs/root/start/quick-start/_include/envoy-demo.yaml b/docs/root/start/quick-start/_include/envoy-demo.yaml index 797c6280c9742..3d3b025a8b5e1 100644 --- a/docs/root/start/quick-start/_include/envoy-demo.yaml +++ b/docs/root/start/quick-start/_include/envoy-demo.yaml @@ -50,9 +50,3 @@ static_resources: typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext sni: www.envoyproxy.io - -admin: - address: - socket_address: - address: 0.0.0.0 - port_value: 9901 diff --git a/docs/root/start/quick-start/configuration-static.rst b/docs/root/start/quick-start/configuration-static.rst index 684003a2f0a5c..c269e7fd293e7 100644 --- a/docs/root/start/quick-start/configuration-static.rst +++ b/docs/root/start/quick-start/configuration-static.rst @@ -42,7 +42,7 @@ All paths are matched and routed to the ``service_envoyproxy_io`` :language: yaml :linenos: :lines: 1-34 - :emphasize-lines: 3-32 + :emphasize-lines: 3-31 .. _start_quick_start_static_clusters: @@ -54,6 +54,6 @@ proxies over ``TLS`` to https://www.envoyproxy.io. .. literalinclude:: _include/envoy-demo.yaml :language: yaml - :lineno-start: 32 - :lines: 32-53 - :emphasize-lines: 3-22 + :lineno-start: 29 + :lines: 29-52 + :emphasize-lines: 5-24 diff --git a/docs/root/start/quick-start/run-envoy.rst b/docs/root/start/quick-start/run-envoy.rst index 8459b0a12db78..73be3455dbf63 100644 --- a/docs/root/start/quick-start/run-envoy.rst +++ b/docs/root/start/quick-start/run-envoy.rst @@ -157,13 +157,6 @@ Check Envoy is proxying on http://localhost:10000. $ curl -v localhost:10000 ... -The Envoy admin endpoint should also be available at http://localhost:9901. - -.. code-block:: console - - $ curl -v localhost:9901 - ... - You can exit the server with `Ctrl-c`. See the :ref:`admin quick start guide ` for more information about the Envoy admin interface. @@ -325,7 +318,7 @@ to ``/dev/stdout``: :linenos: :lineno-start: 12 :lines: 12-22 - :emphasize-lines: 4-8 + :emphasize-lines: 4-7 The default configuration in the Envoy Docker container also logs access in this way. diff --git a/generated_api_shadow/envoy/config/cluster/v3/cluster.proto b/generated_api_shadow/envoy/config/cluster/v3/cluster.proto index 7044c50f3b836..9a56980d7aa79 100644 --- a/generated_api_shadow/envoy/config/cluster/v3/cluster.proto +++ b/generated_api_shadow/envoy/config/cluster/v3/cluster.proto @@ -757,7 +757,7 @@ message Cluster { // in the :ref:`http_protocol_options ` message. // upstream_http_protocol_options can be set via the cluster's // :ref:`extension_protocol_options`. - // See ref:`upstream_http_protocol_options + // See :ref:`upstream_http_protocol_options // ` // for example usage. core.v3.UpstreamHttpProtocolOptions upstream_http_protocol_options = 46 @@ -770,7 +770,7 @@ message Cluster { // in the :ref:`http_protocol_options ` message. // common_http_protocol_options can be set via the cluster's // :ref:`extension_protocol_options`. - // See ref:`upstream_http_protocol_options + // See :ref:`upstream_http_protocol_options // ` // for example usage. core.v3.HttpProtocolOptions common_http_protocol_options = 29 @@ -781,7 +781,7 @@ message Cluster { // :ref:`http_protocol_options ` message. // http_protocol_options can be set via the cluster's // :ref:`extension_protocol_options`. - // See ref:`upstream_http_protocol_options + // See :ref:`upstream_http_protocol_options // ` // for example usage. core.v3.Http1ProtocolOptions http_protocol_options = 13 @@ -797,7 +797,7 @@ message Cluster { // :ref:`http_protocol_options ` // message. http2_protocol_options can be set via the cluster's // :ref:`extension_protocol_options`. - // See ref:`upstream_http_protocol_options + // See :ref:`upstream_http_protocol_options // ` // for example usage. core.v3.Http2ProtocolOptions http2_protocol_options = 14 [ diff --git a/generated_api_shadow/envoy/config/cluster/v4alpha/cluster.proto b/generated_api_shadow/envoy/config/cluster/v4alpha/cluster.proto index a97ade452560c..bad0bc43faaf7 100644 --- a/generated_api_shadow/envoy/config/cluster/v4alpha/cluster.proto +++ b/generated_api_shadow/envoy/config/cluster/v4alpha/cluster.proto @@ -765,7 +765,7 @@ message Cluster { // in the :ref:`http_protocol_options ` message. // upstream_http_protocol_options can be set via the cluster's // :ref:`extension_protocol_options`. - // See ref:`upstream_http_protocol_options + // See :ref:`upstream_http_protocol_options // ` // for example usage. core.v4alpha.UpstreamHttpProtocolOptions hidden_envoy_deprecated_upstream_http_protocol_options = @@ -778,7 +778,7 @@ message Cluster { // in the :ref:`http_protocol_options ` message. // common_http_protocol_options can be set via the cluster's // :ref:`extension_protocol_options`. - // See ref:`upstream_http_protocol_options + // See :ref:`upstream_http_protocol_options // ` // for example usage. core.v4alpha.HttpProtocolOptions hidden_envoy_deprecated_common_http_protocol_options = 29 @@ -789,7 +789,7 @@ message Cluster { // :ref:`http_protocol_options ` message. // http_protocol_options can be set via the cluster's // :ref:`extension_protocol_options`. - // See ref:`upstream_http_protocol_options + // See :ref:`upstream_http_protocol_options // ` // for example usage. core.v4alpha.Http1ProtocolOptions hidden_envoy_deprecated_http_protocol_options = 13 @@ -805,7 +805,7 @@ message Cluster { // :ref:`http_protocol_options ` // message. http2_protocol_options can be set via the cluster's // :ref:`extension_protocol_options`. - // See ref:`upstream_http_protocol_options + // See :ref:`upstream_http_protocol_options // ` // for example usage. core.v4alpha.Http2ProtocolOptions hidden_envoy_deprecated_http2_protocol_options = 14 [ diff --git a/generated_api_shadow/envoy/config/route/v3/route_components.proto b/generated_api_shadow/envoy/config/route/v3/route_components.proto index b98ad53e08d89..3072e7445143a 100644 --- a/generated_api_shadow/envoy/config/route/v3/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v3/route_components.proto @@ -457,7 +457,7 @@ message RouteMatch { // where Extended CONNECT requests may have a path, the path matchers will work if // there is a path present. // Note that CONNECT support is currently considered alpha in Envoy. - // [#comment:TODO(htuch): Replace the above comment with an alpha tag. + // [#comment: TODO(htuch): Replace the above comment with an alpha tag.] ConnectMatcher connect_matcher = 12; string hidden_envoy_deprecated_regex = 3 [ @@ -795,7 +795,7 @@ message RouteAction { // Configuration for sending data upstream as a raw data payload. This is used for // CONNECT requests, when forwarding CONNECT payload as raw TCP. // Note that CONNECT support is currently considered alpha in Envoy. - // [#comment:TODO(htuch): Replace the above comment with an alpha tag. + // [#comment: TODO(htuch): Replace the above comment with an alpha tag.] ConnectConfig connect_config = 3; } diff --git a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto index 6924bc80e38ed..bf4d50ca3b212 100644 --- a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto @@ -457,7 +457,7 @@ message RouteMatch { // where Extended CONNECT requests may have a path, the path matchers will work if // there is a path present. // Note that CONNECT support is currently considered alpha in Envoy. - // [#comment:TODO(htuch): Replace the above comment with an alpha tag. + // [#comment: TODO(htuch): Replace the above comment with an alpha tag.] ConnectMatcher connect_matcher = 12; } @@ -775,7 +775,7 @@ message RouteAction { // Configuration for sending data upstream as a raw data payload. This is used for // CONNECT requests, when forwarding CONNECT payload as raw TCP. // Note that CONNECT support is currently considered alpha in Envoy. - // [#comment:TODO(htuch): Replace the above comment with an alpha tag. + // [#comment: TODO(htuch): Replace the above comment with an alpha tag.] ConnectConfig connect_config = 3; } diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 66a75d3aefcbe..4ac5f1289df6e 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -247,7 +247,7 @@ message HttpConnectionManager { google.protobuf.BoolValue enabled = 3; } - // [#not-implemented-hide] Transformations that apply to path headers. Transformations are applied + // [#not-implemented-hide:] Transformations that apply to path headers. Transformations are applied // before any processing of requests by HTTP filters, routing, and matching. Only the normalized // path will be visible internally if a transformation is enabled. Any path rewrites that the // router performs (e.g. :ref:`regex_rewrite @@ -257,7 +257,7 @@ message HttpConnectionManager { // // Note: access logging and tracing will show the original *:path* header. message PathNormalizationOptions { - // [#not-implemented-hide] Normalization applies internally before any processing of requests by + // [#not-implemented-hide:] Normalization applies internally before any processing of requests by // HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults // to :ref:`NormalizePathRFC3986 // `. When not @@ -267,7 +267,7 @@ message HttpConnectionManager { // normalization due to disallowed characters.) type.http.v3.PathTransformation forwarding_transformation = 1; - // [#not-implemented-hide] Normalization only applies internally before any processing of + // [#not-implemented-hide:] Normalization only applies internally before any processing of // requests by HTTP filters, routing, and matching. These will be applied after full // transformation is applied. The *:path* header before this transformation will be restored in // the router filter and sent upstream unless it was mutated by a filter. Defaults to no diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index d442451cdae62..d40a8fb6a0559 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -238,7 +238,7 @@ message HttpConnectionManager { google.protobuf.BoolValue enabled = 3; } - // [#not-implemented-hide] Transformations that apply to path headers. Transformations are applied + // [#not-implemented-hide:] Transformations that apply to path headers. Transformations are applied // before any processing of requests by HTTP filters, routing, and matching. Only the normalized // path will be visible internally if a transformation is enabled. Any path rewrites that the // router performs (e.g. :ref:`regex_rewrite @@ -252,7 +252,7 @@ message HttpConnectionManager { "envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager." "PathNormalizationOptions"; - // [#not-implemented-hide] Normalization applies internally before any processing of requests by + // [#not-implemented-hide:] Normalization applies internally before any processing of requests by // HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults // to :ref:`NormalizePathRFC3986 // `. When not @@ -262,7 +262,7 @@ message HttpConnectionManager { // normalization due to disallowed characters.) type.http.v3.PathTransformation forwarding_transformation = 1; - // [#not-implemented-hide] Normalization only applies internally before any processing of + // [#not-implemented-hide:] Normalization only applies internally before any processing of // requests by HTTP filters, routing, and matching. These will be applied after full // transformation is applied. The *:path* header before this transformation will be restored in // the router filter and sent upstream unless it was mutated by a filter. Defaults to no diff --git a/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto b/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto index 5ed7b8e51d181..58b5c2130f6dd 100644 --- a/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto +++ b/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto @@ -46,7 +46,7 @@ message VmConfig { // VM plugin) to determine which VM will be used for the plugin. All plugins which use the same // *vm_id* and code will use the same VM. May be left blank. Sharing a VM between plugins can // reduce memory utilization and make sharing of data easier which may have security implications. - // See ref: "TODO: add ref" for details. + // [#comment: TODO: add ref for details.] string vm_id = 1; // The Wasm runtime type. diff --git a/tools/code_format/BUILD b/tools/code_format/BUILD index 78297275b0fe1..20329d6dc9c10 100644 --- a/tools/code_format/BUILD +++ b/tools/code_format/BUILD @@ -21,3 +21,9 @@ py_binary( requirement("pep8-naming"), ], ) + +py_binary( + name = "rst_check", + srcs = ["rst_check.py"], + visibility = ["//visibility:public"], +) diff --git a/tools/code_format/rst_check.py b/tools/code_format/rst_check.py new file mode 100755 index 0000000000000..c133744d8f96e --- /dev/null +++ b/tools/code_format/rst_check.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 + +import subprocess +import sys + +# TODO(phlax): add rstcheck also + +# things we dont want to see in generated docs +# TODO(phlax): move to .rstcheck.cfg when available +RSTCHECK_GREP_FAIL = (" ref:", "\\[\\#") + + +def run_grep_check(check): + command = ["grep", "-nr", "--include", "\\*.rst"] + [check] + resp = subprocess.run(command, capture_output=True, cwd=sys.argv[1]) + + # this fails if returncode is 0 - ie it should not have any matches + if not resp.returncode: + # stdout and stderr are dumped to ensure we capture all errors + sys.stderr.write( + f"ERROR: rstcheck linting failed, found unwanted: '{check}'\n" + f"{resp.stdout.decode('utf-8')}\n" + f"{resp.stderr.decode('utf-8')}") + return len(resp.stdout.decode("utf-8").split("\n")) - 1 + + +def main(): + errors = 0 + for check in RSTCHECK_GREP_FAIL: + errors += run_grep_check(check) + if errors: + raise SystemExit(f"RST check failed: {errors} errors") + + +if __name__ == "__main__": + main() diff --git a/tools/protodoc/protodoc.py b/tools/protodoc/protodoc.py index 6e838486adba2..e3fbb4a940681 100755 --- a/tools/protodoc/protodoc.py +++ b/tools/protodoc/protodoc.py @@ -321,8 +321,7 @@ def format_message_as_json(type_context, msg): if lines: return '.. code-block:: json\n\n {\n' + ',\n'.join(indent_lines(4, lines)) + '\n }\n\n' - else: - return '.. code-block:: json\n\n {}\n\n' + return "" def normalize_field_type_name(field_fqn): From e2eebec1536ce7dc4af8a47ea00f2ae0d6381e63 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 15 Apr 2021 11:52:42 -0400 Subject: [PATCH 009/209] test: making assert required (#15972) Signed-off-by: Alyssa Wilk --- bazel/repositories.bzl | 3 +++ ci/filter_example_setup.sh | 2 +- .../composite/composite_filter_integration_test.cc | 4 ++-- .../extension_discovery_integration_test.cc | 12 ++++++------ test/integration/integration_stream_decoder.h | 2 +- test/integration/quic_http_integration_test.cc | 2 +- 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index c143dc01c3e7b..6a4730ac44a98 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -356,6 +356,9 @@ def _com_github_zlib_ng_zlib_ng(): patches = ["@envoy//bazel/foreign_cc:zlib_ng.patch"], ) +# If you're looking for envoy-filter-example / envoy_filter_example +# the hash is in ci/filter_example_setup.sh + def _org_brotli(): external_http_archive( name = "org_brotli", diff --git a/ci/filter_example_setup.sh b/ci/filter_example_setup.sh index c3926e5d2c7a5..4fef840e0389d 100644 --- a/ci/filter_example_setup.sh +++ b/ci/filter_example_setup.sh @@ -5,7 +5,7 @@ set -e # This is the hash on https://github.com/envoyproxy/envoy-filter-example.git we pin to. -ENVOY_FILTER_EXAMPLE_GITSHA="8f0f8bba38868e875613b74f57df38f543e764ba" +ENVOY_FILTER_EXAMPLE_GITSHA="d135632a6e96268ab2c6a62e1cceec25c728ccf9" ENVOY_FILTER_EXAMPLE_SRCDIR="${BUILD_DIR}/envoy-filter-example" # shellcheck disable=SC2034 diff --git a/test/extensions/filters/http/composite/composite_filter_integration_test.cc b/test/extensions/filters/http/composite/composite_filter_integration_test.cc index da206d4ecb0f0..8a290b633f963 100644 --- a/test/extensions/filters/http/composite/composite_filter_integration_test.cc +++ b/test/extensions/filters/http/composite/composite_filter_integration_test.cc @@ -62,7 +62,7 @@ TEST_P(CompositeFilterIntegrationTest, TestBasic) { waitForNextUpstreamRequest(); upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - response->waitForEndStream(); + ASSERT_TRUE(response->waitForEndStream()); EXPECT_THAT(response->headers(), Http::HttpStatusIs("200")); } @@ -73,7 +73,7 @@ TEST_P(CompositeFilterIntegrationTest, TestBasic) { {"match-header", "match"}, {":authority", "blah"}}; auto response = codec_client_->makeRequestWithBody(request_headers, 1024); - response->waitForEndStream(); + ASSERT_TRUE(response->waitForEndStream()); EXPECT_THAT(response->headers(), Http::HttpStatusIs("403")); } } diff --git a/test/integration/extension_discovery_integration_test.cc b/test/integration/extension_discovery_integration_test.cc index e4e0d6d8885b5..8d8271a21f89c 100644 --- a/test/integration/extension_discovery_integration_test.cc +++ b/test/integration/extension_discovery_integration_test.cc @@ -333,7 +333,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { Http::TestRequestHeaderMapImpl request_headers{ {":method", "GET"}, {":path", "/"}, {":scheme", "http"}, {":authority", "host"}}; auto response = codec_client_->makeHeaderOnlyRequest(request_headers); - response->waitForEndStream(); + ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); } @@ -341,7 +341,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { {":method", "GET"}, {":path", "/private/key"}, {":scheme", "http"}, {":authority", "host"}}; { auto response = codec_client_->makeHeaderOnlyRequest(banned_request_headers); - response->waitForEndStream(); + ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ("403", response->headers().getStatusValue()); } @@ -352,7 +352,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { test_server_->waitForCounterGe("http.config_test.extension_config_discovery.foo.config_reload", 2); auto response = codec_client_->makeHeaderOnlyRequest(banned_request_headers); - response->waitForEndStream(); + ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ("500", response->headers().getStatusValue()); } @@ -364,7 +364,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { test_server_->waitForCounterGe("http.config_test.extension_config_discovery.foo.config_reload", 3); auto response = codec_client_->makeHeaderOnlyRequest(banned_request_headers); - response->waitForEndStream(); + ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ("403", response->headers().getStatusValue()); } @@ -389,7 +389,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccessWithTtlWithDefault) { {":method", "GET"}, {":path", "/private/key"}, {":scheme", "http"}, {":authority", "host"}}; { auto response = codec_client_->makeHeaderOnlyRequest(banned_request_headers); - response->waitForEndStream(); + ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); } @@ -400,7 +400,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccessWithTtlWithDefault) { test_server_->waitForCounterGe("http.config_test.extension_config_discovery.foo.config_reload", 2); auto response = codec_client_->makeHeaderOnlyRequest(banned_request_headers); - response->waitForEndStream(); + ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ("403", response->headers().getStatusValue()); } diff --git a/test/integration/integration_stream_decoder.h b/test/integration/integration_stream_decoder.h index 06697260ec034..6f706c663267f 100644 --- a/test/integration/integration_stream_decoder.h +++ b/test/integration/integration_stream_decoder.h @@ -40,7 +40,7 @@ class IntegrationStreamDecoder : public Http::ResponseDecoder, public Http::Stre // can be used if the previous body data is not relevant and the test wants to wait for a specific // amount of new data without considering the existing body size. void waitForBodyData(uint64_t size); - testing::AssertionResult + ABSL_MUST_USE_RESULT testing::AssertionResult waitForEndStream(std::chrono::milliseconds timeout = TestUtility::DefaultTimeout); ABSL_MUST_USE_RESULT testing::AssertionResult waitForReset(std::chrono::milliseconds timeout = TestUtility::DefaultTimeout); diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 65352aea97969..33671493049e1 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -558,7 +558,7 @@ TEST_P(QuicHttpIntegrationTest, ResetRequestWithoutAuthorityHeader) { request_encoder_ = &encoder_decoder.first; auto response = std::move(encoder_decoder.second); - response->waitForEndStream(); + ASSERT_TRUE(response->waitForEndStream()); codec_client_->close(); ASSERT_TRUE(response->complete()); EXPECT_EQ("400", response->headers().getStatusValue()); From 5450a45bff9de83a40ad18265500902b8c57c478 Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Thu, 15 Apr 2021 09:31:34 -0700 Subject: [PATCH 010/209] Win32 docs for `run-envoy.rst` section (#15813) Signed-off-by: Sotiris Nanopoulos --- docs/_ext/powershell_lexer.py | 5 + docs/conf.py | 8 +- docs/root/start/quick-start/run-envoy.rst | 158 +++++++++++++++------- 3 files changed, 118 insertions(+), 53 deletions(-) create mode 100644 docs/_ext/powershell_lexer.py diff --git a/docs/_ext/powershell_lexer.py b/docs/_ext/powershell_lexer.py new file mode 100644 index 0000000000000..74a077708245d --- /dev/null +++ b/docs/_ext/powershell_lexer.py @@ -0,0 +1,5 @@ +from pygments.lexers import get_lexer_by_name + + +def setup(app): + app.add_lexer('powershell', get_lexer_by_name('powershell')) diff --git a/docs/conf.py b/docs/conf.py index d4fa5605f588f..f3eebfd4fb1ba 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -72,7 +72,7 @@ def setup(app): extensions = [ 'sphinxcontrib.httpdomain', 'sphinx.ext.extlinks', 'sphinx.ext.ifconfig', 'sphinx_tabs.tabs', - 'sphinx_copybutton', 'validating_code_block', 'sphinxext.rediraffe' + 'sphinx_copybutton', 'validating_code_block', 'sphinxext.rediraffe', 'powershell_lexer' ] extlinks = { 'repo': ('https://github.com/envoyproxy/envoy/blob/{}/%s'.format(blob_sha), ''), @@ -81,9 +81,11 @@ def setup(app): # Setup global substitutions if 'pre-release' in release_level: - substitutions = [('|envoy_docker_image|', 'envoy-dev:{}'.format(blob_sha))] + substitutions = [('|envoy_docker_image|', 'envoy-dev:{}'.format(blob_sha)), + ('|envoy_windows_docker_image|', 'envoy-windows-dev:{}'.format(blob_sha))] else: - substitutions = [('|envoy_docker_image|', 'envoy:{}'.format(blob_sha))] + substitutions = [('|envoy_docker_image|', 'envoy:{}'.format(blob_sha)), + ('|envoy_windows_docker_image|', 'envoy-windows:{}'.format(blob_sha))] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/root/start/quick-start/run-envoy.rst b/docs/root/start/quick-start/run-envoy.rst index 73be3455dbf63..3b2c1749a3163 100644 --- a/docs/root/start/quick-start/run-envoy.rst +++ b/docs/root/start/quick-start/run-envoy.rst @@ -23,7 +23,7 @@ Once you have :ref:`installed Envoy `, you can check the version inform $ envoy --version ... - .. tab:: Docker + .. tab:: Docker (Linux Image) .. substitution-code-block:: console @@ -31,6 +31,14 @@ Once you have :ref:`installed Envoy `, you can check the version inform envoyproxy/|envoy_docker_image| \ --version ... + .. tab:: Docker (Windows Image) + + .. substitution-code-block:: powershell + + PS> docker run --rm + 'envoyproxy/|envoy_windows_docker_image|' + --version + ... .. _start_quick_start_help: @@ -49,7 +57,7 @@ flag: $ envoy --help ... - .. tab:: Docker + .. tab:: Docker (Linux Image) .. substitution-code-block:: console @@ -58,6 +66,15 @@ flag: --help ... + .. tab:: Docker (Windows Image) + + .. substitution-code-block:: powershell + + PS> docker run --rm + 'envoyproxy/|envoy_windows_docker_image|' + --help + ... + .. _start_quick_start_config: Run Envoy with the demo configuration @@ -77,7 +94,7 @@ The ``-c`` or ``--config-path`` flag tells Envoy the path to its initial configu $ envoy -c envoy-demo.yaml ... - .. tab:: Docker + .. tab:: Docker (Linux Image) You can start the Envoy Docker image without specifying a configuration file, and it will use the demo config by default. @@ -104,52 +121,33 @@ The ``-c`` or ``--config-path`` flag tells Envoy the path to its initial configu -c /envoy-custom.yaml ... - .. tab:: Windows Service + .. tab:: Docker (Windows Image) - .. note:: + You can start the Envoy Docker image without specifying a configuration file, and + it will use the demo config by default. - This feature is still in Experimental state. + .. substitution-code-block:: powershell - You can start Envoy as Windows Service that is managed under `Windows Service Control Manager `_. + PS> docker run --rm -it + -p '9901:9901' + -p '10000:10000' + 'envoyproxy/|envoy_windows_docker_image|' + ... - First, you need to create the service. Assuming you have a custom configuration in the current directory named ``envoy-custom.yaml``. After you create the service you - can start it. + To specify a custom configuration you can mount the config into the container, and specify the path with ``-c``. - From an **administrator** prompt run the following commands (note that you need replace C:\EnvoyProxy\ with the path to the envoy.exe binary and the config file): + Assuming you have a custom configuration in the current directory named ``envoy-custom.yaml``, from PowerShell run: - .. substitution-code-block:: console + .. substitution-code-block:: powershell - > sc create EnvoyProxy binpath="C:\EnvoyProxy\envoy.exe --config-path C:\EnvoyProxy\envoy-demo.yaml" start=auto depend=Tcpip/Afd - [SC] CreateService SUCCESS - > sc start EnvoyProxy - SERVICE_NAME: envoyproxy - TYPE : 10 WIN32_OWN_PROCESS - STATE : 2 START_PENDING - (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN) - WIN32_EXIT_CODE : 0 (0x0) - SERVICE_EXIT_CODE : 0 (0x0) - CHECKPOINT : 0x0 - WAIT_HINT : 0x7d0 - PID : 3924 - FLAGS : - > sc query EnvoyProxy - SERVICE_NAME: envoyproxy - TYPE : 10 WIN32_OWN_PROCESS - STATE : 4 RUNNING - (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN) - WIN32_EXIT_CODE : 0 (0x0) - SERVICE_EXIT_CODE : 0 (0x0) - CHECKPOINT : 0x0 - WAIT_HINT : 0x0 + PS> docker run --rm -it + -v "$PWD\:`"C:\envoy-configs`"" + -p '9901:9901' + -p '10000:10000' + 'envoyproxy/|envoy_windows_docker_image|' + -c 'C:\envoy-configs\envoy-custom.yaml' ... - Use `sc.exe `_ to configure the service startup and error handling. - - .. tip:: - - The output of ``sc query envoyproxy`` contains the exit code of Envoy Proxy. In case the arguments are invalid we set it to ``E_INVALIDARG``. For more information - Envoy is reporting startup failures with error messages on Windows Event Viewer. - Check Envoy is proxying on http://localhost:10000. .. code-block:: console @@ -186,12 +184,21 @@ Next, start the Envoy server using the override configuration: .. tab:: System + On Linux/Mac: run: + .. code-block:: console $ envoy -c envoy-demo.yaml --config-yaml "$(cat envoy-override.yaml)" ... - .. tab:: Docker + On Windows run: + + .. code-block:: powershell + + $ envoy -c envoy-demo.yaml --config-yaml "$(Get-Content -Raw envoy-override.yaml)" + ... + + .. tab:: Docker (Linux Image) .. substitution-code-block:: console @@ -203,6 +210,18 @@ Next, start the Envoy server using the override configuration: --config-yaml "$(cat envoy-override.yaml)" ... + .. tab:: Docker (Windows Image) + + .. substitution-code-block:: powershell + + PS> docker run --rm -it + -p '9902:9902' + -p '10000:10000' + 'envoyproxy/|envoy_windows_docker_image|' + -c 'C:\ProgramData\envoy.yaml' + --config-yaml "$(Get-Content -Raw envoy-override.yaml)" + ... + The Envoy admin interface should now be available on http://localhost:9902. .. code-block:: console @@ -253,7 +272,7 @@ For invalid configuration the process will print the errors and exit with ``1``. [2020-11-08 12:36:06.549][11][info][config] [source/server/configuration_impl.cc:121] loading stats sink configuration configuration 'my-envoy-config.yaml' OK - .. tab:: Docker + .. tab:: Docker (Linux Image) .. substitution-code-block:: console @@ -276,6 +295,20 @@ For invalid configuration the process will print the errors and exit with ``1``. [2020-11-08 12:36:06.549][11][info][config] [source/server/configuration_impl.cc:121] loading stats sink configuration configuration 'my-envoy-config.yaml' OK + .. tab:: Docker (Windows Image) + + .. substitution-code-block:: powershell + + PS> docker run --rm -it + -v "$PWD\:`"C:\envoy-configs`"" + -p '9901:9901' + -p '10000:10000' + 'envoyproxy/|envoy_windows_docker_image|' + --mode validate + -c 'C:\envoy-configs\my-envoy-config.yaml' + + configuration 'my-envoy-config.yaml' OK + Envoy logging ------------- @@ -292,7 +325,7 @@ This can be overridden using :option:`--log-path`. $ mkdir logs $ envoy -c envoy-demo.yaml --log-path logs/custom.log - .. tab:: Docker + .. tab:: Docker (Linux Image) .. substitution-code-block:: console @@ -305,6 +338,24 @@ This can be overridden using :option:`--log-path`. -c /etc/envoy/envoy.yaml \ --log-path logs/custom.log + .. tab:: Docker (Windows Image) + + .. substitution-code-block:: powershell + + PS> mkdir logs + PS> docker run --rm -it + -p '10000:10000' + -v "$PWD\logs\:`"C:\logs`"" + 'envoyproxy/|envoy_windows_docker_image|' + -c 'C:\ProgramData\envoy.yaml' + --log-path 'C:\logs\custom.log' + + .. note:: + + Envoy on a Windows system Envoy will output to ``CON`` by default. + + This can also be used as a logging path when configuring logging. + :ref:`Access log ` paths can be set for the :ref:`admin interface `, and for configured :ref:`listeners `. @@ -331,12 +382,6 @@ Some Envoy :ref:`filters and extensions ` may also have additiona Envoy can be configured to log to :ref:`different formats `, and to :ref:`different outputs ` in addition to files and ``stdout/err``. -.. note:: - - If you are running Envoy on a Windows system Envoy will output to ``CON`` by default. - - This can also be used as a logging path when configuring logging. - Debugging Envoy --------------- @@ -368,7 +413,7 @@ which are set to ``debug`` and ``trace`` respectively. $ envoy -c envoy-demo.yaml -l off --component-log-level upstream:debug,connection:trace ... - .. tab:: Docker + .. tab:: Docker (Linux Image) .. substitution-code-block:: console @@ -381,6 +426,19 @@ which are set to ``debug`` and ``trace`` respectively. --component-log-level upstream:debug,connection:trace ... + .. tab:: Docker (Windows Image) + + .. substitution-code-block:: powershell + + PS> mkdir logs + PS> docker run --rm -it + -p '10000:10000' + envoyproxy/|envoy_windws_docker_image| + -c 'C:\ProgramData\envoy.yaml' + -l off + --component-log-level 'upstream:debug,connection:trace' + ... + .. tip:: See ``ALL_LOGGER_IDS`` in :repo:`logger.h ` for a list of components. From 22351ae2574a0e3b6ba7d48f1653ba4c6659da70 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 15 Apr 2021 18:44:33 +0100 Subject: [PATCH 011/209] build: Bump build image to 3d0491e2 (#15986) Signed-off-by: Ryan Northey --- .bazelrc | 2 +- .devcontainer/Dockerfile | 2 +- examples/wasm-cc/docker-compose-wasm.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bazelrc b/.bazelrc index 726fa791bf370..b97516ba3f9f0 100644 --- a/.bazelrc +++ b/.bazelrc @@ -247,7 +247,7 @@ build:remote-clang-cl --config=rbe-toolchain-clang-cl # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:e33c93e6d79804bf95ff80426d10bdcc9096c785 +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:3d0491e2034287959a292806e3891fd0b7dd2703 build:docker-sandbox --spawn_strategy=docker build:docker-sandbox --strategy=Javac=docker build:docker-sandbox --strategy=Closure=docker diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5ffd2d2a6ee9c..17524a28f2574 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/envoy-ci/envoy-build:e33c93e6d79804bf95ff80426d10bdcc9096c785 +FROM gcr.io/envoy-ci/envoy-build:3d0491e2034287959a292806e3891fd0b7dd2703 ARG USERNAME=vscode ARG USER_UID=501 diff --git a/examples/wasm-cc/docker-compose-wasm.yaml b/examples/wasm-cc/docker-compose-wasm.yaml index d9b2f31f9bd39..eb7424995583b 100644 --- a/examples/wasm-cc/docker-compose-wasm.yaml +++ b/examples/wasm-cc/docker-compose-wasm.yaml @@ -2,7 +2,7 @@ version: "3.7" services: wasm_compile_update: - image: envoyproxy/envoy-build-ubuntu:e33c93e6d79804bf95ff80426d10bdcc9096c785 + image: envoyproxy/envoy-build-ubuntu:3d0491e2034287959a292806e3891fd0b7dd2703 command: | bash -c "bazel build //examples/wasm-cc:envoy_filter_http_wasm_updated_example.wasm \ && cp -a bazel-bin/examples/wasm-cc/* /build" @@ -12,7 +12,7 @@ services: - ./lib:/build wasm_compile: - image: envoyproxy/envoy-build-ubuntu:e33c93e6d79804bf95ff80426d10bdcc9096c785 + image: envoyproxy/envoy-build-ubuntu:3d0491e2034287959a292806e3891fd0b7dd2703 command: | bash -c "bazel build //examples/wasm-cc:envoy_filter_http_wasm_example.wasm \ && cp -a bazel-bin/examples/wasm-cc/* /build" From 2d6cce3fe8aaa0929be6e09150ed6c1d6cc08bed Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 15 Apr 2021 13:58:17 -0400 Subject: [PATCH 012/209] docs: release note cleanup (#15993) Signed-off-by: Alyssa Wilk --- docs/root/version_history/current.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 1bb975d173d8c..5cfe265ecc38b 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -8,7 +8,7 @@ Incompatible Behavior Changes * config: the v2 xDS API is no longer supported by the Envoy binary. * grpc_stats: the default value for :ref:`stats_for_all_methods ` is switched from true to false, in order to avoid possible memory exhaustion due to an untrusted downstream sending a large number of unique method names. The previous default value was deprecated in version 1.14.0. This only changes the behavior when the value is not set. The previous behavior can be used by setting the value to true. This behavior change by be overridden by setting runtime feature `envoy.deprecated_features.grpc_stats_filter_enable_stats_for_all_methods_by_default`. * http: fixing a standards compliance issue with :scheme. The :scheme header sent upstream is now based on the original URL scheme, rather than set based on the security of the upstream connection. This behavior can be temporarily reverted by setting `envoy.reloadable_features.preserve_downstream_scheme` to false. -* http: http3 is now enabled/disabled via build option `--define http3=disabled` rather than the extension framework. Behavior is the same, but builds may be affected for platforms or build configurations where http3 is not supported. +* http: http3 is now enabled/disabled via build option `--define http3=disabled` rather than the extension framework. The behavior is the same, but builds may be affected for platforms or build configurations where http3 is not supported. * http: resolving inconsistencies between :scheme and X-Forwarded-Proto. :scheme will now be set for all HTTP/1.1 requests. This changes the behavior of the gRPC access logger, Wasm filters, CSRF filter and oath2 filter for HTTP/1 traffic, where :scheme was previously not set. This change also validates that for front-line Envoys (Envoys configured with :ref:`xff_num_trusted_hops ` set to 0 and :ref:`use_remote_address ` set to true) that HTTP/1.1 https schemed requests can not be sent over non-TLS connections. All behavioral changes listed here can be temporarily reverted by setting `envoy.reloadable_features.add_and_validate_scheme_header` to false. * http: when a protocol error is detected in response from upstream, Envoy sends 502 BadGateway downstream and access log entry contains UPE flag. This behavior change can be overwritten to use error code 503 by setting `envoy.reloadable_features.return_502_for_upstream_protocol_errors` to false. @@ -27,7 +27,7 @@ Minor Behavior Changes logging, :ref:`auto_host_rewrite `, etc. Setting the hostname manually allows overriding the internal hostname used for such features while still allowing the original DNS resolution name to be used. -* grpc_json_transcoder: filter now adheres to encoder and decoder buffer limits. Requests and responses +* grpc_json_transcoder: the filter now adheres to encoder and decoder buffer limits. Requests and responses that require buffering over the limits will be directly rejected. The behavior can be reverted by disabling runtime feature `envoy.reloadable_features.grpc_json_transcoder_adhere_to_buffer_limits`. To reduce or increase the buffer limits the filter adheres to, reference the :ref:`flow control documentation `. @@ -48,17 +48,17 @@ Minor Behavior Changes depending on the Envoy deployment, the feature flag may need to be flipped on both downstream and upstream instances, depending on the reason. * http: added support for internal redirects with bodies. This behavior can be disabled temporarily by setting `envoy.reloadable_features.internal_redirects_with_body` to false. -* http: allow to use path canonicalizer from `googleurl `_ +* http: increase the maximum allowed number of initial connection WINDOW_UPDATE frames sent by the peer from 1 to 5. +* http: no longer adding content-length: 0 for requests which should not have bodies. This behavior can be temporarily reverted by setting `envoy.reloadable_features.dont_add_content_length_for_bodiless_requests` false. +* http: switched the path canonicalizer to `googleurl `_ instead of `//source/common/chromium_url`. The new path canonicalizer is enabled by default. To revert to the legacy path canonicalizer, enable the runtime flag `envoy.reloadable_features.remove_forked_chromium_url`. -* http: increase the maximum allowed number of initial connection WINDOW_UPDATE frames sent by the peer from 1 to 5. -* http: no longer adding content-length: 0 for requests which should not have bodies. This behavior can be temporarily reverted by setting `envoy.reloadable_features.dont_add_content_length_for_bodiless_requests` false. -* http: upstream flood and abuse checks increment the count of opened HTTP/2 streams when Envoy sends +* http: upstream flood and abuse checks now increment the count of opened HTTP/2 streams when Envoy sends initial HEADERS frame for the new stream. Before the counter was incrementred when Envoy received response HEADERS frame with the END_HEADERS flag set from upstream server. * lua: added function `timestamp` to provide millisecond resolution timestamps by passing in `EnvoyTimestampResolution.MILLISECOND`. -* oauth filter: added the optional parameter :ref:`auth_scopes ` with default value of 'user' if not provided. Enables this value to be overridden in the Authorization request to the OAuth provider. +* oauth filter: added the optional parameter :ref:`auth_scopes ` with default value of 'user' if not provided. This allows this value to be overridden in the Authorization request to the OAuth provider. * perf: allow reading more bytes per operation from raw sockets to improve performance. * router: extended custom date formatting to DOWNSTREAM_PEER_CERT_V_START and DOWNSTREAM_PEER_CERT_V_END when using :ref:`custom request/response header formats `. * router: made the path rewrite available without finalizing headers, so the filter could calculate the current value of the final url. @@ -87,8 +87,8 @@ Bug Fixes * filter_chain: fix filter chain matching with the server name as the case-insensitive way. * grpc-web: fix local reply and non-proto-encoded gRPC response handling for small response bodies. This fix can be temporarily reverted by setting `envoy.reloadable_features.grpc_web_fix_non_proto_encoded_response_handling` to false. * grpc_http_bridge: the downstream HTTP status is now correctly set for trailers-only responses from the upstream. -* header map: pick the right delimiter to append multiple header values to the same key. Previouly header with multiple values are coalesced with ",", after this fix cookie headers should be coalesced with " ;". This doesn't affect Http1 or Http2 requests because these 2 codecs coalesce cookie headers before adding it to header map. To revert to the old behavior, set the runtime feature `envoy.reloadable_features.header_map_correctly_coalesce_cookies` to false. -* http: avoid grpc-status overwrite on Http::Utility::sendLocalReply() if that field has already been set. +* header map: pick the right delimiter to append multiple header values to the same key. Previouly header with multiple values were coalesced with ",", after this fix cookie headers should be coalesced with " ;". This doesn't affect Http1 or Http2 requests because these 2 codecs coalesce cookie headers before adding it to header map. To revert to the old behavior, set the runtime feature `envoy.reloadable_features.header_map_correctly_coalesce_cookies` to false. +* http: avoid grpc-status overwrite on when sending local replies if that field has already been set. * http: disallowing "host:" in request_headers_to_add for behavioral consistency with rejecting :authority header. This behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_host_like_authority` to false. * http: fixed an issue where Enovy did not handle peer stream limits correctly, and queued streams in nghttp2 rather than establish new connections. This behavior can be temporarily reverted by setting `envoy.reloadable_features.improved_stream_limit_handling` to false. * http: fixed a bug where setting :ref:`MaxStreamDuration proto ` did not disable legacy timeout defaults. @@ -149,7 +149,7 @@ New Features * http: change frame flood and abuse checks to the upstream HTTP/2 codec to ON by default. It can be disabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to false. * http: hash multiple header values instead of only hash the first header value. It can be disabled by setting the `envoy.reloadable_features.hash_multiple_header_values` runtime key to false. See the :ref:`HashPolicy's Header configuration ` for more information. * json: introduced new JSON parser (https://github.com/nlohmann/json) to replace RapidJSON. The new parser is disabled by default. To test the new RapidJSON parser, enable the runtime feature `envoy.reloadable_features.remove_legacy_json`. -* kill_request: :ref:`Kill Request ` Now supports bidirection killing. +* kill_request: :ref:`Kill Request ` now supports bidirection killing. * listener: added an optional :ref:`stat_prefix `. * loadbalancer: added the ability to specify the hash_key for a host when using a consistent hashing loadbalancer (ringhash, maglev) using the :ref:`LbEndpoint.Metadata ` e.g.: ``"envoy.lb": {"hash_key": "..."}``. * log: added a new custom flag ``%j`` to the log pattern to print the actual message to log as JSON escaped string. From 299ae2869de22ec7f1a3a3a32c01d06928c5c0f5 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 15 Apr 2021 18:59:02 +0100 Subject: [PATCH 013/209] build-tools: Bump build tools in repository_locations.bzl (#15992) Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 51275efc4e7fd..7dcf20bec831c 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -65,11 +65,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "envoy-build-tools", project_desc = "Common build tools shared by the Envoy/UDPA ecosystem", project_url = "https://github.com/envoyproxy/envoy-build-tools", - version = "d36336060aa1ade024654dec009adc4a19e3efd6", - sha256 = "7b379e69eafb883cebb52a5ed11b1a12710dd77a33de79c0bc207f6458a5f712", + version = "50ea97bb4289441f4f87537e8c145ffb79b1c9ba", + sha256 = "3c905c71e9f2f29f4c24d9e980c95a45c73510e10b9baa6199e3fa4acfafba5e", strip_prefix = "envoy-build-tools-{version}", urls = ["https://github.com/envoyproxy/envoy-build-tools/archive/{version}.tar.gz"], - release_date = "2021-01-28", + release_date = "2021-04-14", use_category = ["build"], ), boringssl = dict( From 17cbc22c7b9dccc89f5a2f205c936086c8c25833 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 15 Apr 2021 14:00:18 -0400 Subject: [PATCH 014/209] tagging example config test as large (#15990) Signed-off-by: Alyssa Wilk --- test/config_test/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/test/config_test/BUILD b/test/config_test/BUILD index d62b4cc423ee0..929fa0eb3b5c7 100644 --- a/test/config_test/BUILD +++ b/test/config_test/BUILD @@ -15,6 +15,7 @@ exports_files(["example_configs_test_setup.sh"]) envoy_cc_test( name = "example_configs_test", + size = "large", srcs = [ "example_configs_test.cc", ], From 2dd8c334fb9e070654d5c77bae9f573a3510616c Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 15 Apr 2021 19:47:47 +0100 Subject: [PATCH 015/209] python: Integrate linting tools (#15922) Signed-off-by: Ryan Northey --- .gitignore | 1 + .yapfignore | 7 + bazel/repositories_extra.bzl | 7 - ci/do_ci.sh | 8 +- ci/format_pre.sh | 12 +- tools/README.md | 101 ++- tools/base/BUILD | 7 + tools/base/checker.py | 276 ++++++++ tools/base/runner.py | 55 ++ tools/base/tests/test_checker.py | 641 +++++++++++++++++++ tools/base/tests/test_runner.py | 153 ++++- tools/base/tests/test_utils.py | 74 +++ tools/base/utils.py | 47 +- tools/code_format/BUILD | 11 +- tools/code_format/python_check.py | 135 ++++ tools/code_format/python_flake8.py | 19 - tools/code_format/tests/test_python_check.py | 392 ++++++++++++ tools/testing/plugin.py | 1 - 18 files changed, 1889 insertions(+), 58 deletions(-) create mode 100644 .yapfignore create mode 100644 tools/base/checker.py create mode 100644 tools/base/tests/test_checker.py create mode 100755 tools/code_format/python_check.py delete mode 100644 tools/code_format/python_flake8.py create mode 100644 tools/code_format/tests/test_python_check.py diff --git a/.gitignore b/.gitignore index ae1f29656b59a..94d0778d92875 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ cmake-build-debug /linux bazel.output.txt *~ +.coverage diff --git a/.yapfignore b/.yapfignore new file mode 100644 index 0000000000000..0409026f278ae --- /dev/null +++ b/.yapfignore @@ -0,0 +1,7 @@ +*generated* +*venv* +*protos* +*~ +*_pb2.py +*tests* +*#* diff --git a/bazel/repositories_extra.bzl b/bazel/repositories_extra.bzl index 9b4274271d3aa..bda3919952dd9 100644 --- a/bazel/repositories_extra.bzl +++ b/bazel/repositories_extra.bzl @@ -6,13 +6,6 @@ load("@proxy_wasm_cpp_host//bazel/cargo:crates.bzl", "proxy_wasm_cpp_host_fetch_ def _python_deps(): py_repositories() - # REMOVE!!! - pip_install( - name = "sometools_pip3", - requirements = "@envoy//tools/sometools:requirements.txt", - extra_pip_args = ["--require-hashes"], - ) - pip_install( name = "config_validation_pip3", requirements = "@envoy//tools/config_validation:requirements.txt", diff --git a/ci/do_ci.sh b/ci/do_ci.sh index c9421a0c90ae8..f906a04bbd7bb 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -7,9 +7,9 @@ set -e build_setup_args="" if [[ "$1" == "format_pre" || "$1" == "fix_format" || "$1" == "check_format" || "$1" == "check_repositories" || \ - "$1" == "check_spelling" || "$1" == "fix_spelling" || "$1" == "bazel.clang_tidy" || \ - "$1" == "check_spelling_pedantic" || "$1" == "fix_spelling_pedantic" ]]; then - build_setup_args="-nofetch" + "$1" == "check_spelling" || "$1" == "fix_spelling" || "$1" == "bazel.clang_tidy" || "$1" == "tooling" || \ + "$1" == "check_spelling_pedantic" || "$1" == "fix_spelling_pedantic" ]]; then + build_setup_args="-nofetch" fi # TODO(phlax): Clarify and/or integrate SRCDIR and ENVOY_SRCDIR @@ -470,8 +470,10 @@ elif [[ "$CI_TARGET" == "tooling" ]]; then echo "Run pytest tooling tests..." bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/testing:pytest_python_pytest -- --cov-collect /tmp/.coverage-envoy bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/testing:pytest_python_coverage -- --cov-collect /tmp/.coverage-envoy + bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/base:pytest_checker -- --cov-collect /tmp/.coverage-envoy bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/base:pytest_runner -- --cov-collect /tmp/.coverage-envoy bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/base:pytest_utils -- --cov-collect /tmp/.coverage-envoy + bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/code_format:pytest_python_check -- --cov-collect /tmp/.coverage-envoy bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/testing:python_coverage -- --fail-under=95 /tmp/.coverage-envoy /source/generated/tooling exit 0 elif [[ "$CI_TARGET" == "verify_examples" ]]; then diff --git a/ci/format_pre.sh b/ci/format_pre.sh index 86ae89d484e27..92df445170971 100755 --- a/ci/format_pre.sh +++ b/ci/format_pre.sh @@ -43,18 +43,8 @@ CURRENT=shellcheck CURRENT=configs bazel run "${BAZEL_BUILD_OPTIONS[@]}" //configs:example_configs_validation -# TODO(phlax): update to use bazel and python_flake8/python_check -# this will simplify this code to a single line CURRENT=python -"${ENVOY_SRCDIR}"/tools/code_format/format_python_tools.sh check || { - "${ENVOY_SRCDIR}"/tools/code_format/format_python_tools.sh fix - git diff HEAD | tee "${DIFF_OUTPUT}" - raise () { - # this throws an error without exiting - return 1 - } - raise -} +bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/code_format:python_check -- --diff-file="$DIFF_OUTPUT" --fix "$(pwd)" if [[ "${#FAILED[@]}" -ne "0" ]]; then echo "TESTS FAILED:" >&2 diff --git a/tools/README.md b/tools/README.md index 22a3ed20a6bbb..77244a0ced465 100644 --- a/tools/README.md +++ b/tools/README.md @@ -10,7 +10,7 @@ We will assume that `sometools` does not yet exist and will also need a `require and `bazel` rule to configure the dependencies. In most cases of adding a tool, it is likely you will not need to create a new set of dependencies, and -you can skip to the ("Add Python requirements")[#add-python-requirements] section. +you can skip to the ["Add Python requirements"](#add-python-requirements) section. We will also assume that you have `python3` and `pip` installed and working in your local environment. @@ -149,8 +149,6 @@ add a test runner to ensure the file is tested. ```starlark envoy_py_binary( name = "tools.sometools.mytool", - srcs = ["mytool.py"], - visibility = ["//visibility:public"], deps = [ requirement("requests"), requirement("pyyaml"), @@ -229,7 +227,7 @@ def test_mytool_main(): This example use the mock library to patch all of the method calls, and then tests that they have been called with the expected values. -You can run the test as follows: +You can run the test using the (automatically generated) `//tools/sometools:pytest_mytool` target as follows: ```console $ bazel run //tools/sometools:pytest_mytool @@ -314,15 +312,13 @@ This will drop you into the Python debugger (`pdb`) at the breakpoint. A base class for writing tools that need to parse command line arguments has been provided. -To make use of it in this example we will need to add the runner as a dependency to the `tools.base.mytool` target. +To make use of it in this example we will need to add the runner as a dependency to the `tools.sometools.mytool` target. -Edit `tools/sometools/BUILD` and change the `tools.base.mytool` target to the following: +Edit `tools/sometools/BUILD` and change the `tools.sometools.mytool` target to the following: ```starlark envoy_py_binary( - name = "tools.base.mytool", - srcs = ["mytool.py"], - visibility = ["//visibility:public"], + name = "tools.sometools.mytool", deps = [ "//tools/base:runner", requirement("requests"), @@ -389,3 +385,90 @@ or directly with `python`: $ ./tools/sometools/mytool.py -h ... ``` + +### Using the `tools.base.checker.Checker` class + +A base class for writing checkers (for example, linting tools) has also been provided. + +Any classes subclassing `tools.base.checker.Checker` should provide a tuple of `__class__.checks`. + +For each named check in `checks` the `Checker` will expect a method of the same name with the prefix `check_`. + +For example, setting `checks` to the tuple `("check1", "check2")` the `Checker` will run the methods `check_check1` and `check_check2` in order. + +Let's look at an example. + +First, we need to add the bazel target. + +Edit `tools/sometools/BUILD` and add a `tools.sometools.mychecker` target with a dependency on the base `Checker`. + +```starlark +envoy_py_binary( + name = "tools.sometools.mychecker", + deps = [ + "//tools/base:checker", + ], +) +``` + +Next add the `MyChecker` class to `tools/sometools/mychecker.py` as follows: + +```python +#!/usr/bin/env python3 + +import sys + +from tools.base.checker import Checker + + +class MyChecker(Checker): + checks = ("check1", "check2") + + def check_check1(self) -> None: + # checking code for check1 + try: + do_something() + except NotSuchABadError: + self.warn("check1", ["Doing something didn't work out quite as expected 8/"]) + except ATerribleError: + self.error("check1", ["Oh noes, something went badly wrong! 8("]) + else: + self.succeed("check1", ["All good 8)"]) + + def check_check2(self) -> None: + # checking code for check2 + try: + do_something_else() + except NotSuchABadError: + self.warn("check2", ["Doing something else didn't work out quite as expected 8/"]) + except ATerribleError: + self.error("check2", ["Oh noes, something else went badly wrong! 8("]) + else: + self.succeed("check2", ["All good 8)"]) + + +def main(*args) -> int: + return MyChecker(*args).run() + + +if __name__ == "__main__": + sys.exit(main(*sys.argv[1:])) +``` + +Just like with the `Runner` class described [above](#using-the-toolsbaserunnerrunner-class) you can +use both with and without `bazel`. To use without, you will need make it executable, and the end +user will need to have any dependencies locally installed. + +Notice in the check methods the results of the check are logged to one of `self.error`, `self.warn`, +or `self.succeed`. Each takes a `list` of outcomes. The results will be summarized to the user at the +end of all checks. + +Just like with `Runner` a help menu is automatically created, and you can add custom arguments if +required. + +Also like `Runner`, any added `Checker` classes are expected to have unit tests, and a `pytest_mychecker` bazel target +is automatically added. With the above example, the test file should be located at `tools/sometools/tests/test_mychecker.py`. + +One key difference with the `Checker` tools and its derivatives is that it expects a `path` either specified +with `--path` or as an argument. This is used as a context (for example the Envoy src directory) for +running the checks. diff --git a/tools/base/BUILD b/tools/base/BUILD index c5656e230a900..d38233b15eea3 100644 --- a/tools/base/BUILD +++ b/tools/base/BUILD @@ -8,3 +8,10 @@ envoy_package() envoy_py_library("tools.base.runner") envoy_py_library("tools.base.utils") + +envoy_py_library( + "tools.base.checker", + deps = [ + ":runner", + ], +) diff --git a/tools/base/checker.py b/tools/base/checker.py new file mode 100644 index 0000000000000..4bdef7a5632bd --- /dev/null +++ b/tools/base/checker.py @@ -0,0 +1,276 @@ +import argparse +import os +from functools import cached_property +from typing import Sequence, Tuple, Type + +from tools.base import runner + + +class Checker(runner.Runner): + """Runs check methods prefixed with `check_` and named in `self.checks` + + Check methods should call the `self.warn`, `self.error` or `self.succeed` + depending upon the outcome of the checks. + """ + checks: Tuple[str, ...] = () + + def __init__(self, *args): + super().__init__(*args) + self.success = {} + self.errors = {} + self.warnings = {} + + @property + def diff(self) -> bool: + """Flag to determine whether the checker should print diffs to the console""" + return self.args.diff + + @property + def error_count(self) -> int: + """Count of all errors found""" + return sum(len(e) for e in self.errors.values()) + + @property + def failed(self) -> dict: + """Dictionary of errors per check""" + return dict((k, (len(v))) for k, v in self.errors.items()) + + @property + def fix(self) -> bool: + """Flag to determine whether the checker should attempt to fix found problems""" + return self.args.fix + + @property + def has_failed(self) -> bool: + """Shows whether there are any failures""" + # add logic for warn/error + return bool(self.failed or self.warned) + + @cached_property + def path(self) -> str: + """The "path" - usually Envoy src dir. This is used for finding configs for the tooling and should be a dir""" + try: + path = self.args.path or self.args.paths[0] + except IndexError: + raise self.parser.error( + "Missing path: `path` must be set either as an arg or with --path") + if not os.path.isdir(path): + raise self.parser.error( + "Incorrect path: `path` must be a directory, set either as first arg or with --path" + ) + return path + + @property + def paths(self) -> list: + """List of paths to apply checks to""" + return self.args.paths or [self.path] + + @property + def show_summary(self) -> bool: + """Show a summary at the end or not""" + return bool(self.args.summary or self.error_count or self.warning_count) + + @property + def status(self) -> dict: + """Dictionary showing current success/warnings/errors""" + return dict( + success=self.success_count, + errors=self.error_count, + warnings=self.warning_count, + failed=self.failed, + warned=self.warned, + succeeded=self.succeeded) + + @property + def succeeded(self) -> dict: + """Dictionary of successful checks grouped by check type""" + return dict((k, (len(v))) for k, v in self.success.items()) + + @property + def success_count(self) -> int: + """Current count of successful checks""" + return sum(len(e) for e in self.success.values()) + + @cached_property + def summary(self) -> "CheckerSummary": + """Instance of the checker's summary class""" + return self.summary_class(self) + + @property + def summary_class(self) -> Type["CheckerSummary"]: + """Checker's summary class""" + return CheckerSummary + + @property + def warned(self) -> dict: + """Dictionary of warned checks grouped by check type""" + return dict((k, (len(v))) for k, v in self.warnings.items()) + + @property + def warning_count(self) -> int: + """Current count of warned checks""" + return sum(len(e) for e in self.warnings.values()) + + def add_arguments(self, parser: argparse.ArgumentParser) -> None: + """Add arguments to the arg parser""" + parser.add_argument( + "--fix", action="store_true", default=False, help="Attempt to fix in place") + parser.add_argument( + "--diff", + action="store_true", + default=False, + help="Display a diff in the console where available") + parser.add_argument( + "--warning", + "-w", + choices=["warn", "error"], + default="warn", + help="Handle warnings as warnings or errors") + parser.add_argument( + "--summary", action="store_true", default=False, help="Show a summary of check runs") + parser.add_argument( + "--summary-errors", + type=int, + default=5, + help="Number of errors to show in the summary, -1 shows all") + parser.add_argument( + "--summary-warnings", + type=int, + default=5, + help="Number of warnings to show in the summary, -1 shows all") + parser.add_argument( + "--check", + "-c", + choices=self.checks, + nargs="*", + help="Specify which checks to run, can be specified for multiple checks") + for check in self.checks: + parser.add_argument( + f"--config-{check}", default="", help=f"Custom configuration for the {check} check") + parser.add_argument( + "--path", + "-p", + default=None, + help= + "Path to the test root (usually Envoy source dir). If not specified the first path of paths is used" + ) + parser.add_argument( + "--log-level", + "-l", + choices=["info", "warn", "debug", "error"], + default="info", + help="Log level to display") + parser.add_argument( + "paths", + nargs="*", + help= + "Paths to check. At least one path must be specified, or the `path` argument should be provided" + ) + + def error(self, name: str, errors: list, log: bool = True) -> int: + """Record (and log) errors for a check type""" + self.errors[name] = self.errors.get(name, []) + self.errors[name].extend(errors) + if log: + self.log.error("\n".join(errors)) + return 1 + + def get_checks(self) -> Sequence[str]: + """Get list of checks for this checker class filtered according to user args""" + return ( + self.checks if not self.args.check else + [check for check in self.args.check if check in self.checks]) + + def on_check_run(self, check: str) -> None: + """Callback hook called after each check run""" + pass + + def on_checks_begin(self) -> None: + """Callback hook called before all checks""" + pass + + def on_checks_complete(self) -> int: + """Callback hook called after all checks have run, and returning the final outcome of a checks_run""" + if self.show_summary: + self.summary.print_summary() + return 1 if self.has_failed else 0 + + def run_checks(self) -> int: + """Run all configured checks and return the sum of their error counts""" + checks = self.get_checks() + self.on_checks_begin() + for check in checks: + self.log.info(f"[CHECKS:{self.name}] {check}") + getattr(self, f"check_{check}")() + self.on_check_run(check) + return self.on_checks_complete() + + def succeed(self, name: str, success: list, log: bool = True) -> None: + """Record (and log) success for a check type""" + + self.success[name] = self.success.get(name, []) + self.success[name].extend(success) + if log: + self.log.info("\n".join(success)) + + def warn(self, name: str, warnings: list, log: bool = True) -> None: + """Record (and log) warnings for a check type""" + + self.warnings[name] = self.warnings.get(name, []) + self.warnings[name].extend(warnings) + if log: + self.log.warning("\n".join(warnings)) + + +class ForkingChecker(Checker): + + @cached_property + def fork(self): + return runner.ForkingAdapter(self) + + +class CheckerSummary(object): + + def __init__(self, checker: Checker): + self.checker = checker + + @property + def max_errors(self) -> int: + """Maximum errors to display in summary""" + return self.checker.args.summary_errors + + @property + def max_warnings(self) -> int: + """Maximum warnings to display in summary""" + return self.checker.args.summary_warnings + + def print_failed(self, problem_type): + _out = [] + _max = getattr(self, f"max_{problem_type}") + for check, problems in getattr(self.checker, problem_type).items(): + _msg = f"[{problem_type.upper()}:{self.checker.name}] {check}" + _max = (min(len(problems), _max) if _max >= 0 else len(problems)) + msg = ( + f"{_msg}: (showing first {_max} of {len(problems)})" if + (len(problems) > _max and _max > 0) else (f"{_msg}:" if _max != 0 else _msg)) + _out.extend(self._section(msg, problems[:_max])) + if _out: + self.checker.log.error("\n".join(_out)) + + def print_status(self) -> None: + """Print summary status to stderr""" + self.checker.log.warning( + "\n".join(self._section(f"[SUMMARY:{self.checker.name}] {self.checker.status}"))) + + def print_summary(self) -> None: + """Write summary to stderr""" + self.print_failed("warnings") + self.print_failed("errors") + self.print_status() + + def _section(self, message: str, lines: list = None) -> list: + """Print a summary section""" + section = ["", "-" * 80, "", f"{message}"] + if lines: + section += lines + return section diff --git a/tools/base/runner.py b/tools/base/runner.py index 1b5115703fe99..be1bf1e8bec7c 100644 --- a/tools/base/runner.py +++ b/tools/base/runner.py @@ -3,8 +3,21 @@ # import argparse +import logging +import os +import subprocess +import sys from functools import cached_property +LOG_LEVELS = (("info", logging.INFO), ("debug", logging.DEBUG), ("warn", logging.WARN), + ("error", logging.ERROR)) + + +class LogFilter(logging.Filter): + + def filter(self, rec): + return rec.levelno in (logging.DEBUG, logging.INFO) + class Runner(object): @@ -21,6 +34,30 @@ def extra_args(self) -> list: """Unparsed args""" return self.parser.parse_known_args(self._args)[1] + @cached_property + def log(self) -> logging.Logger: + """Instantiated logger""" + logger = logging.getLogger(self.name) + logger.setLevel(self.log_level) + stdout_handler = logging.StreamHandler(sys.stdout) + stdout_handler.setLevel(logging.DEBUG) + stdout_handler.addFilter(LogFilter()) + stderr_handler = logging.StreamHandler(sys.stderr) + stderr_handler.setLevel(logging.WARN) + logger.addHandler(stdout_handler) + logger.addHandler(stderr_handler) + return logger + + @cached_property + def log_level(self) -> int: + """Log level parsed from args""" + return dict(LOG_LEVELS)[self.args.log_level] + + @property + def name(self) -> str: + """Name of the runner""" + return self.__class__.__name__ + @cached_property def parser(self) -> argparse.ArgumentParser: """Argparse parser""" @@ -28,6 +65,24 @@ def parser(self) -> argparse.ArgumentParser: self.add_arguments(parser) return parser + @cached_property + def path(self) -> str: + return os.getcwd() + def add_arguments(self, parser: argparse.ArgumentParser) -> None: """Override this method to add custom arguments to the arg parser""" pass + + +class ForkingAdapter(object): + + def __init__(self, context: Runner): + self.context = context + + def __call__(self, *args, **kwargs) -> subprocess.CompletedProcess: + return self.fork(*args, **kwargs) + + def fork(self, *args, capture_output: bool = True, **kwargs) -> subprocess.CompletedProcess: + """Fork a subprocess, using self.context.path as the cwd by default""" + kwargs["cwd"] = kwargs.get("cwd", self.context.path) + return subprocess.run(*args, capture_output=capture_output, **kwargs) diff --git a/tools/base/tests/test_checker.py b/tools/base/tests/test_checker.py new file mode 100644 index 0000000000000..6f3d2e63e787d --- /dev/null +++ b/tools/base/tests/test_checker.py @@ -0,0 +1,641 @@ +from unittest.mock import MagicMock, patch, PropertyMock + +import pytest + +from tools.base.checker import Checker, CheckerSummary, ForkingChecker + + +class DummyChecker(Checker): + + def __init__(self): + self.args = PropertyMock() + + +class DummyCheckerWithChecks(Checker): + checks = ("check1", "check2") + + def __init__(self, *args): + self.check1 = MagicMock() + self.check2 = MagicMock() + + def check_check1(self): + self.check1() + + def check_check2(self): + self.check2() + + +def test_checker_constructor(): + super_mock = patch("tools.base.checker.runner.Runner.__init__") + + with super_mock as m_super: + checker = Checker("path1", "path2", "path3") + + assert ( + list(m_super.call_args) + == [('path1', 'path2', 'path3'), {}]) + assert checker.summary_class == CheckerSummary + + +def test_checker_diff(): + checker = Checker("path1", "path2", "path3") + args_mock = patch( + "tools.base.checker.Checker.args", + new_callable=PropertyMock) + + with args_mock as m_args: + assert checker.diff == m_args.return_value.diff + assert "diff" not in checker.__dict__ + + +def test_checker_error_count(): + checker = Checker("path1", "path2", "path3") + checker.errors = dict(foo=["err"] * 3, bar=["err"] * 5, baz=["err"] * 7) + assert checker.error_count == 15 + assert "error_count" not in checker.__dict__ + + +def test_checker_failed(): + checker = Checker("path1", "path2", "path3") + checker.errors = dict(foo=["err"] * 3, bar=["err"] * 5, baz=["err"] * 7) + assert checker.failed == {'foo': 3, 'bar': 5, 'baz': 7} + assert "failed" not in checker.__dict__ + + +def test_checker_fix(): + checker = Checker("path1", "path2", "path3") + args_mock = patch( + "tools.base.checker.Checker.args", + new_callable=PropertyMock) + + with args_mock as m_args: + assert checker.fix == m_args.return_value.fix + assert "fix" not in checker.__dict__ + + +@pytest.mark.parametrize("failed", [True, False]) +@pytest.mark.parametrize("warned", [True, False]) +def test_checker_has_failed(patches, failed, warned): + checker = Checker("path1", "path2", "path3") + patched = patches( + ("Checker.failed", dict(new_callable=PropertyMock)), + ("Checker.warned", dict(new_callable=PropertyMock)), + prefix="tools.base.checker") + + with patched as (m_failed, m_warned): + m_failed.return_value = failed + m_warned.return_value = warned + result = checker.has_failed + + if failed or warned: + assert result is True + else: + assert result is False + assert "has_failed" not in checker.__dict__ + + +@pytest.mark.parametrize("path", [None, "PATH"]) +@pytest.mark.parametrize("paths", [[], ["PATH0"]]) +@pytest.mark.parametrize("isdir", [True, False]) +def test_checker_path(patches, path, paths, isdir): + class DummyError(Exception): + pass + checker = Checker("path1", "path2", "path3") + patched = patches( + ("Checker.args", dict(new_callable=PropertyMock)), + ("Checker.parser", dict(new_callable=PropertyMock)), + "os.path.isdir", + prefix="tools.base.checker") + + with patched as (m_args, m_parser, m_isdir): + m_parser.return_value.error = DummyError + m_args.return_value.path = path + m_args.return_value.paths = paths + m_isdir.return_value = isdir + if not path and not paths: + with pytest.raises(DummyError) as e: + checker.path + assert ( + e.value.args + == ('Missing path: `path` must be set either as an arg or with --path',)) + elif not isdir: + with pytest.raises(DummyError) as e: + checker.path + assert ( + e.value.args + == ('Incorrect path: `path` must be a directory, set either as first arg or with --path',)) + else: + assert checker.path == path or paths[0] + assert "path" in checker.__dict__ + if path or paths: + assert ( + list(m_isdir.call_args) + == [(path or paths[0],), {}]) + + +@pytest.mark.parametrize("paths", [[], ["path1", "path2"]]) +def test_checker_paths(patches, paths): + checker = Checker("path1", "path2", "path3") + patched = patches( + ("Checker.args", dict(new_callable=PropertyMock)), + ("Checker.path", dict(new_callable=PropertyMock)), + prefix="tools.base.checker") + + with patched as (m_args, m_path): + m_args.return_value.paths = paths + result = checker.paths + + if paths: + assert result == paths + else: + assert result == [m_path.return_value] + assert "paths" not in checker.__dict__ + + +@pytest.mark.parametrize("summary", [True, False]) +@pytest.mark.parametrize("error_count", [0, 1]) +@pytest.mark.parametrize("warning_count", [0, 1]) +def test_checker_show_summary(patches, summary, error_count, warning_count): + checker = Checker("path1", "path2", "path3") + patched = patches( + ("Checker.args", dict(new_callable=PropertyMock)), + ("Checker.error_count", dict(new_callable=PropertyMock)), + ("Checker.warning_count", dict(new_callable=PropertyMock)), + prefix="tools.base.checker") + + with patched as (m_args, m_errors, m_warnings): + m_args.return_value.summary = summary + m_errors.return_value = error_count + m_warnings.return_value = warning_count + result = checker.show_summary + + if summary or error_count or warning_count: + assert result is True + else: + assert result is False + assert "show_summary" not in checker.__dict__ + + +def test_checker_status(patches): + checker = Checker("path1", "path2", "path3") + patched = patches( + ("Checker.success_count", dict(new_callable=PropertyMock)), + ("Checker.error_count", dict(new_callable=PropertyMock)), + ("Checker.warning_count", dict(new_callable=PropertyMock)), + ("Checker.failed", dict(new_callable=PropertyMock)), + ("Checker.warned", dict(new_callable=PropertyMock)), + ("Checker.succeeded", dict(new_callable=PropertyMock)), + prefix="tools.base.checker") + + with patched as args: + (m_success_count, m_error_count, m_warning_count, + m_failed, m_warned, m_succeeded) = args + assert ( + checker.status + == dict( + success=m_success_count.return_value, + errors=m_error_count.return_value, + warnings=m_warning_count.return_value, + failed=m_failed.return_value, + warned=m_warned.return_value, + succeeded=m_succeeded.return_value)) + assert "status" not in checker.__dict__ + + +def test_checker_succeeded(): + checker = Checker("path1", "path2", "path3") + checker.success = dict( + foo=["check"] * 3, + bar=["check"] * 5, + baz=["check"] * 7) + assert ( + checker.succeeded + == dict(foo=3, bar=5, baz=7)) + assert "succeeded" not in checker.__dict__ + + +def test_checker_success_count(): + checker = Checker("path1", "path2", "path3") + checker.success = dict(foo=["err"] * 3, bar=["err"] * 5, baz=["err"] * 7) + assert checker.success_count == 15 + assert "success_count" not in checker.__dict__ + + +def test_checker_summary(): + checker = Checker("path1", "path2", "path3") + summary_mock = patch( + "tools.base.checker.Checker.summary_class", + new_callable=PropertyMock) + + with summary_mock as m_summary: + assert checker.summary == m_summary.return_value.return_value + + assert ( + list(m_summary.return_value.call_args) + == [(checker,), {}]) + assert "summary" in checker.__dict__ + + +def test_checker_warned(): + checker = Checker("path1", "path2", "path3") + checker.warnings = dict( + foo=["check"] * 3, + bar=["check"] * 5, + baz=["check"] * 7) + assert ( + checker.warned + == dict(foo=3, bar=5, baz=7)) + assert "warned" not in checker.__dict__ + + +def test_checker_warning_count(): + checker = Checker("path1", "path2", "path3") + checker.warnings = dict(foo=["warn"] * 3, bar=["warn"] * 5, baz=["warn"] * 7) + assert checker.warning_count == 15 + assert "warning_count" not in checker.__dict__ + + +def test_checker_add_arguments(): + checker = DummyCheckerWithChecks("path1", "path2", "path3") + parser = MagicMock() + checker.add_arguments(parser) + assert ( + list(list(c) for c in parser.add_argument.call_args_list) + == [[('--fix',), + {'action': 'store_true', + 'default': False, + 'help': 'Attempt to fix in place'}], + [('--diff',), + {'action': 'store_true', + 'default': False, + 'help': 'Display a diff in the console where available'}], + [('--warning', '-w'), + {'choices': ['warn', 'error'], + 'default': 'warn', + 'help': 'Handle warnings as warnings or errors'}], + [('--summary',), + {'action': 'store_true', + 'default': False, + 'help': 'Show a summary of check runs'}], + [('--summary-errors',), + {'type': int, + 'default': 5, + 'help': 'Number of errors to show in the summary, -1 shows all'}], + [('--summary-warnings',), + {'type': int, + 'default': 5, + 'help': 'Number of warnings to show in the summary, -1 shows all'}], + [('--check', '-c'), + {'choices': ("check1", "check2"), + 'nargs': '*', + 'help': 'Specify which checks to run, can be specified for multiple checks'}], + [('--config-check1',), + {'default': '', + 'help': 'Custom configuration for the check1 check'}], + [('--config-check2',), + {'default': '', + 'help': 'Custom configuration for the check2 check'}], + [('--path', '-p'), + {'default': None, + 'help': 'Path to the test root (usually Envoy source dir). If not specified the first path of paths is used'}], + [('--log-level', '-l'), + {'choices': ['info', 'warn', 'debug', 'error'], + 'default': 'info', 'help': 'Log level to display'}], + [('paths',), + {'nargs': '*', + 'help': 'Paths to check. At least one path must be specified, or the `path` argument should be provided'}]]) + + +TEST_ERRORS = ( + {}, + dict(myerror=[]), + dict(myerror=["a", "b", "c"]), + dict(othererror=["other1", "other2", "other3"]), + dict(othererror=["other1", "other2", "other3"], myerror=["a", "b", "c"])) + + +@pytest.mark.parametrize("log", [True, False]) +@pytest.mark.parametrize("errors", TEST_ERRORS) +def test_checker_error(patches, log, errors): + checker = Checker("path1", "path2", "path3") + log_mock = patch( + "tools.base.checker.Checker.log", + new_callable=PropertyMock) + checker.errors = errors.copy() + + with log_mock as m_log: + assert checker.error("mycheck", ["err1", "err2", "err3"], log) == 1 + + assert checker.errors["mycheck"] == errors.get("mycheck", []) + ["err1", "err2", "err3"] + for k, v in errors.items(): + if k != "mycheck": + assert checker.errors[k] == v + if log: + assert ( + list(m_log.return_value.error.call_args) + == [('err1\nerr2\nerr3',), {}]) + else: + assert not m_log.return_value.error.called + + +TEST_CHECKS = ( + None, + (), + ("check1", ), + ("check1", "check2", "check3"), + ("check3", "check4", "check5"), + ("check4", "check5")) + + +@pytest.mark.parametrize("checks", TEST_CHECKS) +def test_checker_get_checks(checks): + checker = Checker("path1", "path2", "path3") + checker.checks = ("check1", "check2", "check3") + args_mock = patch( + "tools.base.checker.Checker.args", + new_callable=PropertyMock) + + with args_mock as m_args: + m_args.return_value.check = checks + if checks: + assert ( + checker.get_checks() + == [check for check in checker.checks if check in checks or []]) + else: + assert checker.get_checks() == checker.checks + + +def test_checker_on_check_run(): + checker = Checker("path1", "path2", "path3") + assert not checker.on_check_run("checkname") + + +def test_checker_on_checks_begin(): + checker = Checker("path1", "path2", "path3") + assert checker.on_checks_begin() is None + + +@pytest.mark.parametrize("failed", [True, False]) +@pytest.mark.parametrize("show_summary", [True, False]) +def test_checker_on_checks_complete(patches, failed, show_summary): + checker = Checker("path1", "path2", "path3") + patched = patches( + ("Checker.has_failed", dict(new_callable=PropertyMock)), + ("Checker.show_summary", dict(new_callable=PropertyMock)), + ("Checker.summary", dict(new_callable=PropertyMock)), + prefix="tools.base.checker") + + with patched as (m_failed, m_show_summary, m_summary): + m_failed.return_value = failed + m_show_summary.return_value = show_summary + assert checker.on_checks_complete() is (1 if failed else 0) + + if show_summary: + assert ( + list(m_summary.return_value.print_summary.call_args) + == [(), {}]) + else: + assert not m_summary.return_value.print_summary.called + + +def test_checker_run_checks(patches): + checker = DummyCheckerWithChecks("path1", "path2", "path3") + patched = patches( + "Checker.get_checks", + "Checker.on_checks_begin", + "Checker.on_checks_complete", + ("Checker.log", dict(new_callable=PropertyMock)), + ("Checker.name", dict(new_callable=PropertyMock)), + prefix="tools.base.checker") + + with patched as (m_get, m_begin, m_complete, m_log, m_name): + m_get.return_value = ("check1", "check2") + assert checker.run_checks() == m_complete.return_value + + assert ( + list(m_get.call_args) + == [(), {}]) + assert ( + list(m_begin.call_args) + == [(), {}]) + assert ( + list(m_complete.call_args) + == [(), {}]) + assert ( + list(list(c) for c in m_log.return_value.info.call_args_list) + == [[(f"[CHECKS:{m_name.return_value}] check1",), {}], + [(f"[CHECKS:{m_name.return_value}] check2",), {}]]) + assert ( + list(checker.check1.call_args) + == [(), {}]) + assert ( + list(checker.check2.call_args) + == [(), {}]) + + +TEST_WARNS = ( + {}, + dict(mywarn=[]), + dict(mywarn=["a", "b", "c"]), + dict(otherwarn=["other1", "other2", "other3"]), + dict(otherwarn=["other1", "other2", "other3"], mywarn=["a", "b", "c"])) + + +@pytest.mark.parametrize("log", [True, False]) +@pytest.mark.parametrize("warns", TEST_WARNS) +def test_checker_warn(patches, log, warns): + checker = Checker("path1", "path2", "path3") + log_mock = patch( + "tools.base.checker.Checker.log", + new_callable=PropertyMock) + checker.warnings = warns.copy() + + with log_mock as m_log: + checker.warn("mycheck", ["warn1", "warn2", "warn3"], log) + + assert checker.warnings["mycheck"] == warns.get("mycheck", []) + ["warn1", "warn2", "warn3"] + for k, v in warns.items(): + if k != "mycheck": + assert checker.warnings[k] == v + if log: + assert ( + list(m_log.return_value.warning.call_args) + == [('warn1\nwarn2\nwarn3',), {}]) + else: + assert not m_log.return_value.warn.called + + +TEST_SUCCESS = ( + {}, + dict(mysuccess=[]), + dict(mysuccess=["a", "b", "c"]), + dict(othersuccess=["other1", "other2", "other3"]), + dict(othersuccess=["other1", "other2", "other3"], mysuccess=["a", "b", "c"])) + + +@pytest.mark.parametrize("log", [True, False]) +@pytest.mark.parametrize("success", TEST_SUCCESS) +def test_checker_succeed(patches, log, success): + checker = Checker("path1", "path2", "path3") + log_mock = patch( + "tools.base.checker.Checker.log", + new_callable=PropertyMock) + checker.success = success.copy() + + with log_mock as m_log: + checker.succeed("mycheck", ["success1", "success2", "success3"], log) + + assert checker.success["mycheck"] == success.get("mycheck", []) + ["success1", "success2", "success3"] + for k, v in success.items(): + if k != "mycheck": + assert checker.success[k] == v + if log: + assert ( + list(m_log.return_value.info.call_args) + == [('success1\nsuccess2\nsuccess3',), {}]) + else: + assert not m_log.return_value.info.called + + +# ForkingChecker tests + +def test_forkingchecker_fork(): + checker = ForkingChecker("path1", "path2", "path3") + forking_mock = patch("tools.base.checker.runner.ForkingAdapter") + + with forking_mock as m_fork: + assert checker.fork == m_fork.return_value + assert ( + list(m_fork.call_args) + == [(checker,), {}]) + assert "fork" in checker.__dict__ + + +# CheckerSummary tests + +def test_checker_summary_constructor(): + checker = DummyChecker() + summary = CheckerSummary(checker) + assert summary.checker == checker + + +@pytest.mark.parametrize("max_errors", [-1, 0, 1, 23]) +def test_checker_summary_max_errors(max_errors): + checker = DummyChecker() + summary = CheckerSummary(checker) + checker.args.summary_errors = max_errors + assert summary.max_errors == max_errors + + +@pytest.mark.parametrize("max_warnings", [-1, 0, 1, 23]) +def test_checker_summary_max_warnings(max_warnings): + checker = DummyChecker() + summary = CheckerSummary(checker) + checker.args.summary_warnings = max_warnings + assert summary.max_warnings == max_warnings + + +def test_checker_summary_print_summary(patches): + checker = DummyChecker() + summary = CheckerSummary(checker) + patched = patches( + "CheckerSummary.print_failed", + "CheckerSummary.print_status", + prefix="tools.base.checker") + + with patched as (m_failed, m_status): + summary.print_summary() + assert ( + list(list(c) for c in m_failed.call_args_list) + == [[('warnings',), {}], [('errors',), {}]]) + assert m_status.called + + +TEST_SECTIONS = ( + ("MSG1", ["a", "b", "c"]), + ("MSG2", []), + ("MSG3", None)) + + +@pytest.mark.parametrize("section", TEST_SECTIONS) +def test_checker_summary_section(section): + checker = DummyChecker() + summary = CheckerSummary(checker) + message, lines = section + expected = [ + "", + "-" * 80, + "", + f"{message}"] + if lines: + expected += lines + assert summary._section(message, lines) == expected + + +def test_checker_summary_print_status(patches): + checker = DummyChecker() + summary = CheckerSummary(checker) + patched = patches( + "CheckerSummary._section", + prefix="tools.base.checker") + + summary.checker = MagicMock() + with patched as (m_section, ): + m_section.return_value = ["A", "B", "C"] + summary.print_status() + assert ( + list(m_section.call_args) + == [(f"[SUMMARY:{summary.checker.name}] {summary.checker.status}",), {}]) + assert ( + list(summary.checker.log.warning.call_args) + == [('A\nB\nC',), {}]) + + +@pytest.mark.parametrize("problem_type", ("errors", "warnings")) +@pytest.mark.parametrize("max_display", (-1, 0, 1, 23)) +@pytest.mark.parametrize("problems", ({}, dict(foo=["problem1"]), dict(foo=["problem1", "problem2"], bar=["problem3", "problem4"]))) +def test_checker_summary_print_failed(patches, problem_type, max_display, problems): + checker = DummyChecker() + summary = CheckerSummary(checker) + patched = patches( + "CheckerSummary._section", + (f"CheckerSummary.max_{problem_type}", dict(new_callable=PropertyMock)), + prefix="tools.base.checker") + + with patched as (m_section, m_max): + summary.checker = MagicMock() + setattr(summary.checker, f"{problem_type}", problems) + m_max.return_value = max_display + m_section.return_value = ["A", "B", "C"] + summary.print_failed(problem_type) + + if not problems: + assert not summary.checker.log.error.called + assert not m_section.called + return + assert ( + list(summary.checker.log.error.call_args) + == [("\n".join(['A\nB\nC'] * len(problems)),), {}]) + if max_display == 0: + expected = [ + [(f"[{problem_type.upper()}:{summary.checker.name}] {prob}", []), {}] + for prob in problems] + else: + def _problems(prob): + return ( + problems[prob][:max_display] + if max_display > 0 + else problems[prob]) + def _extra(prob): + return ( + f": (showing first {max_display} of {len(problems)})" + if len(problems[prob]) > max_display and max_display >= 0 + else (":" + if max_display != 0 + else "")) + expected = [ + [(f"[{problem_type.upper()}:{summary.checker.name}] {prob}{_extra(prob)}", _problems(prob)), {}] + for prob in problems] + assert ( + list(list(c) for c in m_section.call_args_list) + == expected) diff --git a/tools/base/tests/test_runner.py b/tools/base/tests/test_runner.py index 970fd14a3afac..497a68b32db54 100644 --- a/tools/base/tests/test_runner.py +++ b/tools/base/tests/test_runner.py @@ -1,5 +1,9 @@ import importlib -from unittest.mock import patch, PropertyMock +import logging +import sys +from unittest.mock import MagicMock, patch, PropertyMock + +import pytest from tools.base import runner @@ -9,6 +13,12 @@ importlib.reload(runner) +class DummyRunner(runner.Runner): + + def __init__(self): + self.args = PropertyMock() + + def test_runner_constructor(): run = runner.Runner("path1", "path2", "path3") assert run._args == ("path1", "path2", "path3") @@ -50,6 +60,66 @@ def test_runner_extra_args(): assert "extra_args" in run.__dict__ +def test_runner_log(patches): + run = runner.Runner("path1", "path2", "path3") + patched = patches( + "logging.getLogger", + "logging.StreamHandler", + "LogFilter", + ("Runner.log_level", dict(new_callable=PropertyMock)), + ("Runner.name", dict(new_callable=PropertyMock)), + prefix="tools.base.runner") + + with patched as (m_logger, m_stream, m_filter, m_level, m_name): + _loggers = (MagicMock(), MagicMock()) + m_stream.side_effect = _loggers + assert run.log == m_logger.return_value + assert ( + list(m_logger.return_value.setLevel.call_args) + == [(m_level.return_value,), {}]) + assert ( + list(list(c) for c in m_stream.call_args_list) + == [[(sys.stdout,), {}], + [(sys.stderr,), {}]]) + assert ( + list(_loggers[0].setLevel.call_args) + == [(logging.DEBUG,), {}]) + assert ( + list(_loggers[0].addFilter.call_args) + == [(m_filter.return_value,), {}]) + assert ( + list(_loggers[1].setLevel.call_args) + == [(logging.WARN,), {}]) + assert ( + list(list(c) for c in m_logger.return_value.addHandler.call_args_list) + == [[(_loggers[0],), {}], [(_loggers[1],), {}]]) + assert "log" in run.__dict__ + + +def test_runner_log_level(patches): + run = runner.Runner("path1", "path2", "path3") + patched = patches( + "dict", + ("Runner.args", dict(new_callable=PropertyMock)), + prefix="tools.base.runner") + with patched as (m_dict, m_args): + assert run.log_level == m_dict.return_value.__getitem__.return_value + + assert ( + list(m_dict.call_args) + == [(runner.LOG_LEVELS, ), {}]) + assert ( + list(m_dict.return_value.__getitem__.call_args) + == [(m_args.return_value.log_level,), {}]) + assert "log_level" in run.__dict__ + + +def test_runner_name(): + run = DummyRunner() + assert run.name == run.__class__.__name__ + assert "name" not in run.__dict__ + + def test_runner_parser(patches): run = runner.Runner("path1", "path2", "path3") patched = patches( @@ -68,6 +138,87 @@ def test_runner_parser(patches): assert "parser" in run.__dict__ +def test_checker_path(): + run = runner.Runner("path1", "path2", "path3") + cwd_mock = patch("tools.base.runner.os.getcwd") + + with cwd_mock as m_cwd: + assert run.path == m_cwd.return_value + + assert ( + list(m_cwd.call_args) + == [(), {}]) + + def test_runner_add_arguments(patches): run = runner.Runner("path1", "path2", "path3") assert run.add_arguments("PARSER") is None + + +# LogFilter tests +@pytest.mark.parametrize("level", [logging.DEBUG, logging.INFO, logging.WARN, logging.ERROR, None, "giraffe"]) +def test_runner_log_filter(level): + logfilter = runner.LogFilter() + + class DummyRecord(object): + levelno = level + + if level in [logging.DEBUG, logging.INFO]: + assert logfilter.filter(DummyRecord()) + else: + assert not logfilter.filter(DummyRecord()) + + +# ForkingAdapter tests + +def test_forkingadapter_constructor(): + _runner = DummyRunner() + adapter = runner.ForkingAdapter(_runner) + assert adapter.context == _runner + + +def test_forkingadapter_call(): + _runner = DummyRunner() + adapter = runner.ForkingAdapter(_runner) + fork_mock = patch("tools.base.runner.ForkingAdapter.fork") + + with fork_mock as m_fork: + assert ( + adapter( + "arg1", "arg2", "arg3", + kwa1="foo", + kwa2="bar", + kwa3="baz") + == m_fork.return_value) + assert ( + list(m_fork.call_args) + == [('arg1', 'arg2', 'arg3'), + {'kwa1': 'foo', 'kwa2': 'bar', 'kwa3': 'baz'}]) + + +@pytest.mark.parametrize("args", [(), ("a", "b")]) +@pytest.mark.parametrize("cwd", [None, "NONE", "PATH"]) +@pytest.mark.parametrize("capture_output", ["NONE", True, False]) +def test_forkingadapter_fork(patches, args, cwd, capture_output): + adapter = runner.ForkingAdapter(DummyRunner()) + patched = patches( + "subprocess.run", + ("Runner.path", dict(new_callable=PropertyMock)), + prefix="tools.base.runner") + + with patched as (m_run, m_path): + kwargs = {} + if cwd != "NONE": + kwargs["cwd"] = cwd + if capture_output != "NONE": + kwargs["capture_output"] = capture_output + assert adapter.fork(*args, **kwargs) == m_run.return_value + + expected = {'capture_output': True, 'cwd': cwd} + if capture_output is False: + expected["capture_output"] = False + if cwd == "NONE": + expected["cwd"] = m_path.return_value + assert ( + list(m_run.call_args) + == [args, expected]) diff --git a/tools/base/tests/test_utils.py b/tools/base/tests/test_utils.py index 78f83a5ae999d..4d48a9ac5d41f 100644 --- a/tools/base/tests/test_utils.py +++ b/tools/base/tests/test_utils.py @@ -1,4 +1,8 @@ import importlib +import sys +from contextlib import contextmanager + +import pytest from tools.base import utils @@ -8,6 +12,76 @@ importlib.reload(utils) +def test_util_buffered_stdout(): + stdout = [] + + with utils.buffered(stdout=stdout): + print("test1") + print("test2") + sys.stdout.write("test3\n") + sys.stderr.write("error0\n") + + assert stdout == ["test1", "test2", "test3"] + + +def test_util_buffered_stderr(): + stderr = [] + + with utils.buffered(stderr=stderr): + print("test1") + print("test2") + sys.stdout.write("test3\n") + sys.stderr.write("error0\n") + sys.stderr.write("error1\n") + + assert stderr == ["error0", "error1"] + + +def test_util_buffered_stdout_stderr(): + stdout = [] + stderr = [] + + with utils.buffered(stdout=stdout, stderr=stderr): + print("test1") + print("test2") + sys.stdout.write("test3\n") + sys.stderr.write("error0\n") + sys.stderr.write("error1\n") + + assert stdout == ["test1", "test2", "test3"] + assert stderr == ["error0", "error1"] + + +def test_util_buffered_no_stdout_stderr(): + + with pytest.raises(utils.BufferUtilError): + with utils.buffered(): + pass + + +def test_util_nested(): + + fun1_args = [] + fun2_args = [] + + @contextmanager + def fun1(arg): + fun1_args.append(arg) + yield "FUN1" + + @contextmanager + def fun2(arg): + fun2_args.append(arg) + yield "FUN2" + + with utils.nested(fun1("A"), fun2("B")) as (fun1_yield, fun2_yield): + assert fun1_yield == "FUN1" + assert fun2_yield == "FUN2" + + assert fun1_args == ["A"] + assert fun2_args == ["B"] + + def test_util_coverage_with_data_file(patches): patched = patches( "ConfigParser", diff --git a/tools/base/utils.py b/tools/base/utils.py index 203910c37f414..68c3c57a927c6 100644 --- a/tools/base/utils.py +++ b/tools/base/utils.py @@ -2,11 +2,12 @@ # Provides shared utils used by other python modules # +import io import os import tempfile from configparser import ConfigParser -from contextlib import contextmanager -from typing import Iterator +from contextlib import ExitStack, contextmanager, redirect_stderr, redirect_stdout +from typing import Callable, Iterator, List, Optional, Union # this is testing specific - consider moving to tools.testing.utils @@ -26,3 +27,45 @@ def coverage_with_data_file(data_file: str) -> Iterator[str]: with open(tmprc, "w") as f: parser.write(f) yield tmprc + + +class BufferUtilError(Exception): + pass + + +@contextmanager +def nested(*contexts): + with ExitStack() as stack: + yield [stack.enter_context(context) for context in contexts] + + +@contextmanager +def buffered( + stdout: list = None, + stderr: list = None, + mangle: Optional[Callable[[list], list]] = None) -> Iterator[None]: + """Captures stdout and stderr and feeds lines to supplied lists""" + + mangle = mangle or (lambda lines: lines) + + if stdout is None and stderr is None: + raise BufferUtilError("You must specify stdout and/or stderr") + + contexts: List[Union[redirect_stderr[io.StringIO], redirect_stdout[io.StringIO]]] = [] + + if stdout is not None: + _stdout = io.StringIO() + contexts.append(redirect_stdout(_stdout)) + if stderr is not None: + _stderr = io.StringIO() + contexts.append(redirect_stderr(_stderr)) + + with nested(*contexts): + yield + + if stdout is not None: + _stdout.seek(0) + stdout.extend(mangle(_stdout.read().strip().split("\n"))) + if stderr is not None: + _stderr.seek(0) + stderr.extend(mangle(_stderr.read().strip().split("\n"))) diff --git a/tools/code_format/BUILD b/tools/code_format/BUILD index 20329d6dc9c10..b3ad0b3e1cf00 100644 --- a/tools/code_format/BUILD +++ b/tools/code_format/BUILD @@ -1,6 +1,6 @@ -load("@rules_python//python:defs.bzl", "py_binary") load("@pylint_pip3//:requirements.bzl", "requirement") load("//bazel:envoy_build_system.bzl", "envoy_package") +load("//tools/base:envoy_python.bzl", "envoy_py_binary") licenses(["notice"]) # Apache 2 @@ -12,13 +12,14 @@ exports_files([ "envoy_build_fixer.py", ]) -py_binary( - name = "python_flake8", - srcs = ["python_flake8.py"], - visibility = ["//visibility:public"], +envoy_py_binary( + name = "tools.code_format.python_check", deps = [ + "//tools/base:checker", + "//tools/base:utils", requirement("flake8"), requirement("pep8-naming"), + requirement("yapf"), ], ) diff --git a/tools/code_format/python_check.py b/tools/code_format/python_check.py new file mode 100755 index 0000000000000..d84ab1a42eeb7 --- /dev/null +++ b/tools/code_format/python_check.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 + +# usage +# +# with bazel: +# +# bazel run //tools/code_format:python_check -- -h +# +# alternatively, if you have the necessary python deps available +# +# ./tools/code_format/python_check.py -h +# +# python requires: flake8, yapf +# + +import os +import sys +from functools import cached_property + +from flake8.main.application import Application as Flake8Application + +import yapf + +from tools.base import checker, utils + +FLAKE8_CONFIG = '.flake8' +YAPF_CONFIG = '.style.yapf' + +# TODO(phlax): add checks for: +# - isort + + +class PythonChecker(checker.ForkingChecker): + checks = ("flake8", "yapf") + + @property + def diff_file_path(self) -> str: + return self.args.diff_file + + @cached_property + def flake8_app(self) -> Flake8Application: + flake8_app = Flake8Application() + flake8_app.initialize(self.flake8_args) + return flake8_app + + @property + def flake8_args(self) -> list: + return ["--config", self.flake8_config_path, self.path] + + @property + def flake8_config_path(self) -> str: + return os.path.join(self.path, FLAKE8_CONFIG) + + @property + def recurse(self) -> bool: + """Flag to determine whether to apply checks recursively""" + return self.args.recurse + + @property + def yapf_config_path(self) -> str: + return os.path.join(self.path, YAPF_CONFIG) + + @property + def yapf_files(self): + return yapf.file_resources.GetCommandLineFiles( + self.args.paths, + recursive=self.recurse, + exclude=yapf.file_resources.GetExcludePatternsForDir(self.path)) + + def add_arguments(self, parser) -> None: + super().add_arguments(parser) + parser.add_argument( + "--recurse", + "-r", + choices=["yes", "no"], + default="yes", + help="Recurse path or paths directories") + parser.add_argument( + "--diff-file", default=None, help="Specify the path to a diff file with fixes") + + def check_flake8(self) -> None: + """Run flake8 on files and/or repo""" + errors = [] + with utils.buffered(stdout=errors, mangle=self._strip_lines): + self.flake8_app.run_checks() + self.flake8_app.report() + if errors: + self.error("flake8", errors) + + def check_yapf(self) -> None: + """Run flake8 on files and/or repo""" + for python_file in self.yapf_files: + self.yapf_run(python_file) + + def on_check_run(self, check: str) -> None: + if check not in self.failed and check not in self.warned: + self.succeed(check, [f"[CHECKS:{self.name}] {check}: success"]) + + def on_checks_complete(self) -> int: + if self.diff_file_path and self.has_failed: + result = self.fork(["git", "diff", "HEAD"]) + with open(self.diff_file_path, "wb") as f: + f.write(result.stdout) + return super().on_checks_complete() + + def yapf_format(self, python_file: str) -> tuple: + return yapf.yapf_api.FormatFile( + python_file, + style_config=self.yapf_config_path, + in_place=self.fix, + print_diff=not self.fix) + + def yapf_run(self, python_file: str) -> None: + reformatted_source, encoding, changed = self.yapf_format(python_file) + if not changed: + return self.succeed("yapf", [f"{python_file}: success"]) + if self.fix: + return self.warn("yapf", [f"{python_file}: reformatted"]) + if reformatted_source: + return self.warn("yapf", [reformatted_source]) + self.error("yapf", [python_file]) + + def _strip_line(self, line) -> str: + return line[len(self.path) + 1:] if line.startswith(f"{self.path}/") else line + + def _strip_lines(self, lines) -> list: + return [self._strip_line(line) for line in lines if line] + + +def main(*args: list) -> None: + return PythonChecker(*args).run_checks() + + +if __name__ == "__main__": + sys.exit(main(*sys.argv[1:])) diff --git a/tools/code_format/python_flake8.py b/tools/code_format/python_flake8.py deleted file mode 100644 index 49ba8ced694d5..0000000000000 --- a/tools/code_format/python_flake8.py +++ /dev/null @@ -1,19 +0,0 @@ -import subprocess -import sys - -# explicitly use python3 linter -FLAKE8_COMMAND = ("python3", "-m", "flake8", ".") - - -def main(): - resp = subprocess.run(FLAKE8_COMMAND, capture_output=True, cwd=sys.argv[1]) - if resp.returncode: - # stdout and stderr are dumped to ensure we capture all errors - raise SystemExit( - "ERROR: flake8 linting failed: \n" - f"{resp.stdout.decode('utf-8')}\n" - f"{resp.stderr.decode('utf-8')}") - - -if __name__ == "__main__": - main() diff --git a/tools/code_format/tests/test_python_check.py b/tools/code_format/tests/test_python_check.py new file mode 100644 index 0000000000000..b74b893f7d8f0 --- /dev/null +++ b/tools/code_format/tests/test_python_check.py @@ -0,0 +1,392 @@ +from contextlib import contextmanager +from unittest.mock import patch, MagicMock, PropertyMock + +import pytest + +from tools.code_format import python_check + + +def test_python_checker_constructor(): + checker = python_check.PythonChecker("path1", "path2", "path3") + assert checker.checks == ("flake8", "yapf") + assert checker.args.paths == ['path1', 'path2', 'path3'] + + +def test_python_diff_path(): + checker = python_check.PythonChecker("path1", "path2", "path3") + args_mock = patch("tools.code_format.python_check.PythonChecker.args", new_callable=PropertyMock) + + with args_mock as m_args: + assert checker.diff_file_path == m_args.return_value.diff_file + + +def test_python_flake8_app(patches): + checker = python_check.PythonChecker("path1", "path2", "path3") + patched = patches( + ("PythonChecker.flake8_args", dict(new_callable=PropertyMock)), + "Flake8Application", + prefix="tools.code_format.python_check") + + with patched as (m_flake8_args, m_flake8_app): + assert checker.flake8_app == m_flake8_app.return_value + + assert ( + list(m_flake8_app.call_args) + == [(), {}]) + assert ( + list(m_flake8_app.return_value.initialize.call_args) + == [(m_flake8_args.return_value,), {}]) + + +def test_python_flake8_args(patches): + checker = python_check.PythonChecker("path1", "path2", "path3") + patched = patches( + ("PythonChecker.flake8_config_path", dict(new_callable=PropertyMock)), + ("PythonChecker.path", dict(new_callable=PropertyMock)), + prefix="tools.code_format.python_check") + + with patched as (m_flake8_config, m_path): + assert ( + checker.flake8_args + == ['--config', + m_flake8_config.return_value, m_path.return_value]) + + +def test_python_flake8_config_path(patches): + checker = python_check.PythonChecker("path1", "path2", "path3") + patched = patches( + ("PythonChecker.path", dict(new_callable=PropertyMock)), + "os.path.join", + prefix="tools.code_format.python_check") + + with patched as (m_path, m_join): + assert checker.flake8_config_path == m_join.return_value + + assert ( + list(m_join.call_args) + == [(m_path.return_value, python_check.FLAKE8_CONFIG), {}]) + + +def test_python_yapf_config_path(patches): + checker = python_check.PythonChecker("path1", "path2", "path3") + patched = patches( + ("PythonChecker.path", dict(new_callable=PropertyMock)), + "os.path.join", + prefix="tools.code_format.python_check") + + with patched as (m_path, m_join): + assert checker.yapf_config_path == m_join.return_value + + assert ( + list(m_join.call_args) + == [(m_path.return_value, python_check.YAPF_CONFIG), {}]) + + +def test_python_yapf_files(patches): + checker = python_check.PythonChecker("path1", "path2", "path3") + + patched = patches( + ("PythonChecker.args", dict(new_callable=PropertyMock)), + ("PythonChecker.path", dict(new_callable=PropertyMock)), + "yapf.file_resources.GetCommandLineFiles", + "yapf.file_resources.GetExcludePatternsForDir", + prefix="tools.code_format.python_check") + + with patched as (m_args, m_path, m_yapf_files, m_yapf_exclude): + assert checker.yapf_files == m_yapf_files.return_value + + assert ( + list(m_yapf_files.call_args) + == [(m_args.return_value.paths,), + {'recursive': m_args.return_value.recurse, + 'exclude': m_yapf_exclude.return_value}]) + assert ( + list(m_yapf_exclude.call_args) + == [(m_path.return_value,), {}]) + + +def test_python_add_arguments(patches): + checker = python_check.PythonChecker("path1", "path2", "path3") + add_mock = patch("tools.code_format.python_check.checker.ForkingChecker.add_arguments") + m_parser = MagicMock() + + with add_mock as m_add: + checker.add_arguments(m_parser) + + assert ( + list(m_add.call_args) + == [(m_parser,), {}]) + assert ( + list(list(c) for c in m_parser.add_argument.call_args_list) + == [[('--recurse', '-r'), + {'choices': ['yes', 'no'], + 'default': 'yes', + 'help': 'Recurse path or paths directories'}], + [('--diff-file',), + {'default': None, 'help': 'Specify the path to a diff file with fixes'}]]) + + +@pytest.mark.parametrize("errors", [[], ["err1", "err2"]]) +def test_python_check_flake8(patches, errors): + checker = python_check.PythonChecker("path1", "path2", "path3") + + patched = patches( + "utils.buffered", + "PythonChecker.error", + "PythonChecker._strip_lines", + ("PythonChecker.flake8_app", dict(new_callable=PropertyMock)), + prefix="tools.code_format.python_check") + + @contextmanager + def mock_buffered(stdout=None, mangle=None): + yield + stdout.extend(errors) + + with patched as (m_buffered, m_error, m_mangle, m_flake8_app): + m_buffered.side_effect = mock_buffered + checker.check_flake8() + + assert ( + list(m_buffered.call_args) + == [(), {'stdout': errors, 'mangle': m_mangle}]) + assert ( + list(m_flake8_app.return_value.run_checks.call_args) + == [(), {}]) + assert ( + list(m_flake8_app.return_value.report.call_args) + == [(), {}]) + + if errors: + assert ( + list(m_error.call_args) + == [('flake8', ['err1', 'err2']), {}]) + else: + assert not m_error.called + + +def test_python_check_recurse(): + checker = python_check.PythonChecker("path1", "path2", "path3") + args_mock = patch( + "tools.code_format.python_check.PythonChecker.args", + new_callable=PropertyMock) + + with args_mock as m_args: + assert checker.recurse == m_args.return_value.recurse + assert "recurse" not in checker.__dict__ + + +def test_python_check_yapf(patches): + checker = python_check.PythonChecker("path1", "path2", "path3") + patched = patches( + "PythonChecker.yapf_run", + ("PythonChecker.yapf_files", dict(new_callable=PropertyMock)), + prefix="tools.code_format.python_check") + + with patched as (m_yapf_run, m_yapf_files): + m_yapf_files.return_value = ["file1", "file2", "file3"] + checker.check_yapf() + + assert ( + list(list(c) for c in m_yapf_files.call_args_list) + == [[(), {}]]) + assert ( + list(list(c) for c in m_yapf_run.call_args_list) + == [[('file1',), {}], [('file2',), {}], [('file3',), {}]]) + + +TEST_CHECK_RESULTS = ( + ("check1", [], []), + ("check1", ["check2", "check3"], ["check4", "check5"]), + ("check1", ["check1", "check3"], ["check4", "check5"]), + ("check1", ["check2", "check3"], ["check1", "check5"]), + ("check1", ["check1", "check3"], ["check1", "check5"])) + + +@pytest.mark.parametrize("results", TEST_CHECK_RESULTS) +def test_python_on_check_run(patches, results): + checker = python_check.PythonChecker("path1", "path2", "path3") + checkname, errors, warnings = results + patched = patches( + "PythonChecker.succeed", + ("PythonChecker.name", dict(new_callable=PropertyMock)), + ("PythonChecker.failed", dict(new_callable=PropertyMock)), + ("PythonChecker.warned", dict(new_callable=PropertyMock)), + prefix="tools.code_format.python_check") + + with patched as (m_succeed, m_name, m_failed, m_warned): + m_failed.return_value = errors + m_warned.return_value = warnings + checker.on_check_run(checkname) + + if checkname in warnings or checkname in errors: + assert not m_succeed.called + else: + assert ( + list(m_succeed.call_args) + == [(checkname, [f"[CHECKS:{m_name.return_value}] {checkname}: success"]), {}]) + + +TEST_CHECKS_COMPLETE = ( + ("DIFF1", False), + ("DIFF1", True), + ("", False), + ("", True)) + + +@pytest.mark.parametrize("results", TEST_CHECKS_COMPLETE) +def test_python_on_checks_complete(patches, results): + checker = python_check.PythonChecker("path1", "path2", "path3") + diff_path, failed = results + patched = patches( + "open", + "checker.ForkingChecker.fork", + "checker.Checker.on_checks_complete", + ("PythonChecker.diff_file_path", dict(new_callable=PropertyMock)), + ("PythonChecker.has_failed", dict(new_callable=PropertyMock)), + prefix="tools.code_format.python_check") + + with patched as (m_open, m_fork, m_super, m_diff, m_failed): + m_diff.return_value = diff_path + m_failed.return_value = failed + assert checker.on_checks_complete() == m_super.return_value + + if diff_path and failed: + assert ( + list(m_fork.call_args) + == [(['git', 'diff', 'HEAD'],), {}]) + assert ( + list(m_open.call_args) + == [(diff_path, 'wb'), {}]) + assert ( + list(m_open.return_value.__enter__.return_value.write.call_args) + == [(m_fork.return_value.stdout,), {}]) + else: + assert not m_fork.called + assert not m_open.called + + assert ( + list(m_super.call_args) + == [(), {}]) + + +@pytest.mark.parametrize("fix", [True, False]) +def test_python_yapf_format(patches, fix): + checker = python_check.PythonChecker("path1", "path2", "path3") + patched = patches( + "yapf.yapf_api.FormatFile", + ("PythonChecker.yapf_config_path", dict(new_callable=PropertyMock)), + ("PythonChecker.fix", dict(new_callable=PropertyMock)), + prefix="tools.code_format.python_check") + + with patched as (m_format, m_config, m_fix): + m_fix.return_value = fix + assert checker.yapf_format("FILENAME") == m_format.return_value + + assert ( + list(m_format.call_args) + == [('FILENAME',), + {'style_config': m_config.return_value, + 'in_place': fix, + 'print_diff': not fix}]) + assert ( + list(list(c) for c in m_fix.call_args_list) + == [[(), {}], [(), {}]]) + + +TEST_FORMAT_RESULTS = ( + ("", "", True), + ("", "", False), + ("REFORMAT", "", True), + ("REFORMAT", "", False)) + + +@pytest.mark.parametrize("format_results", TEST_FORMAT_RESULTS) +@pytest.mark.parametrize("fix", [True, False]) +def test_python_yapf_run(patches, fix, format_results): + checker = python_check.PythonChecker("path1", "path2", "path3") + reformat, encoding, changed = format_results + patched = patches( + "PythonChecker.yapf_format", + "PythonChecker.succeed", + "PythonChecker.warn", + "PythonChecker.error", + ("PythonChecker.fix", dict(new_callable=PropertyMock)), + prefix="tools.code_format.python_check") + + with patched as (m_format, m_succeed, m_warn, m_error, m_fix): + m_fix.return_value = fix + m_format.return_value = format_results + checker.yapf_run("FILENAME") + + if not changed: + assert ( + list(m_succeed.call_args) + == [('yapf', ['FILENAME: success']), {}]) + assert not m_warn.called + assert not m_error.called + assert not m_fix.called + return + assert not m_succeed.called + if fix: + assert not m_error.called + assert len(m_warn.call_args_list) == 1 + assert ( + list(m_warn.call_args) + == [('yapf', [f'FILENAME: reformatted']), {}]) + return + if reformat: + assert not m_error.called + assert len(m_warn.call_args_list) == 1 + assert ( + list(m_warn.call_args) + == [('yapf', [reformat]), {}]) + return + assert not m_warn.called + assert ( + list(m_error.call_args) + == [('yapf', ['FILENAME']), {}]) + + +def test_python_strip_lines(): + checker = python_check.PythonChecker("path1", "path2", "path3") + strip_mock = patch("tools.code_format.python_check.PythonChecker._strip_line") + lines = ["", "foo", "", "bar", "", "", "baz", "", ""] + + with strip_mock as m_strip: + assert ( + checker._strip_lines(lines) + == [m_strip.return_value] * 3) + + assert ( + list(list(c) for c in m_strip.call_args_list) + == [[('foo',), {}], [('bar',), {}], [('baz',), {}]]) + + +@pytest.mark.parametrize("line", ["REMOVE/foo", "REMOVE", "bar", "other", "REMOVE/baz", "baz"]) +def test_python_strip_line(line): + checker = python_check.PythonChecker("path1", "path2", "path3") + path_mock = patch( + "tools.code_format.python_check.PythonChecker.path", + new_callable=PropertyMock) + + with path_mock as m_path: + m_path.return_value = "REMOVE" + assert ( + checker._strip_line(line) + == line[7:] if line.startswith(f"REMOVE/") else line) + + +def test_python_checker_main(): + class_mock = patch("tools.code_format.python_check.PythonChecker") + + with class_mock as m_class: + assert ( + python_check.main("arg0", "arg1", "arg2") + == m_class.return_value.run_checks.return_value) + + assert ( + list(m_class.call_args) + == [('arg0', 'arg1', 'arg2'), {}]) + assert ( + list(m_class.return_value.run_checks.call_args) + == [(), {}]) diff --git a/tools/testing/plugin.py b/tools/testing/plugin.py index d654d23de584e..af9f487fb8f18 100644 --- a/tools/testing/plugin.py +++ b/tools/testing/plugin.py @@ -2,7 +2,6 @@ # This is pytest plugin providing fixtures for tests. # -import importlib from contextlib import contextmanager, ExitStack from typing import ContextManager, Iterator from unittest.mock import patch From 12e35dbc82f07ba1d97230d08d94f85c30c57c47 Mon Sep 17 00:00:00 2001 From: htuch Date: Thu, 15 Apr 2021 14:48:38 -0400 Subject: [PATCH 016/209] cve_scan: add some false positives. (#15944) This hadn't been run in a while, will re-enable in CI when this lands. Signed-off-by: Harvey Tuch --- tools/dependency/cve_scan.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/dependency/cve_scan.py b/tools/dependency/cve_scan.py index 5b0e709feb236..85c4e7a0264df 100755 --- a/tools/dependency/cve_scan.py +++ b/tools/dependency/cve_scan.py @@ -47,12 +47,20 @@ 'CVE-2020-8169', 'CVE-2020-8177', 'CVE-2020-8284', - # Node.js issue unrelated to http-parse (Node TLS). + # Node.js issue unrelated to http-parser (Node TLS). 'CVE-2020-8265', # Node.js request smuggling. # https://github.com/envoyproxy/envoy/pull/14686 validates that this does # not apply to Envoy. 'CVE-2020-8287', + # Envoy is operating post Brotli 1.0.9 release, so not affected by this. + 'CVE-2020-8927', + # Node.js issue unrelated to http-parser (*). + 'CVE-2021-22883', + 'CVE-2021-22884', + # False positive on the match heuristic, fixed in Curl 7.76.0. + 'CVE-2021-22876', + 'CVE-2021-22890', ]) # Subset of CVE fields that are useful below. From 60681666a13deffe322e9f11c0a2463d42ce8dfd Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Thu, 15 Apr 2021 13:07:45 -0700 Subject: [PATCH 017/209] build: workaround for sed issue with en_US.UTF-8 locale. (#15997) Signed-off-by: Piotr Sikora --- test/exe/version_out_test.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/exe/version_out_test.sh b/test/exe/version_out_test.sh index 6308287a2ce01..2adc293236c40 100755 --- a/test/exe/version_out_test.sh +++ b/test/exe/version_out_test.sh @@ -2,6 +2,9 @@ set -e -o pipefail +# Undo LC_ALL=en_US.UTF-8, since it breaks sed. +export LC_ALL=C + ENVOY_BIN="${TEST_SRCDIR}/envoy/source/exe/envoy-static" COMMIT=$(${ENVOY_BIN} --version | \ From 8ae87f296696d194ae198b8c70be76433a120aaf Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 15 Apr 2021 21:46:13 +0100 Subject: [PATCH 018/209] tools: Remove rst_check code for now (#16000) Signed-off-by: Ryan Northey --- docs/build.sh | 2 -- tools/code_format/BUILD | 6 ------ tools/code_format/rst_check.py | 36 ---------------------------------- 3 files changed, 44 deletions(-) delete mode 100755 tools/code_format/rst_check.py diff --git a/docs/build.sh b/docs/build.sh index 860c261cc1bb4..7b6eee6ba76d4 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -171,8 +171,6 @@ rsync -av \ "${SCRIPT_DIR}"/_ext \ "${GENERATED_RST_DIR}" -bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/code_format:rst_check "${GENERATED_RST_DIR}" - # To speed up validate_fragment invocations in validating_code_block bazel build "${BAZEL_BUILD_OPTIONS[@]}" //tools/config_validation:validate_fragment diff --git a/tools/code_format/BUILD b/tools/code_format/BUILD index b3ad0b3e1cf00..95784d0ef412f 100644 --- a/tools/code_format/BUILD +++ b/tools/code_format/BUILD @@ -22,9 +22,3 @@ envoy_py_binary( requirement("yapf"), ], ) - -py_binary( - name = "rst_check", - srcs = ["rst_check.py"], - visibility = ["//visibility:public"], -) diff --git a/tools/code_format/rst_check.py b/tools/code_format/rst_check.py deleted file mode 100755 index c133744d8f96e..0000000000000 --- a/tools/code_format/rst_check.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/python3 - -import subprocess -import sys - -# TODO(phlax): add rstcheck also - -# things we dont want to see in generated docs -# TODO(phlax): move to .rstcheck.cfg when available -RSTCHECK_GREP_FAIL = (" ref:", "\\[\\#") - - -def run_grep_check(check): - command = ["grep", "-nr", "--include", "\\*.rst"] + [check] - resp = subprocess.run(command, capture_output=True, cwd=sys.argv[1]) - - # this fails if returncode is 0 - ie it should not have any matches - if not resp.returncode: - # stdout and stderr are dumped to ensure we capture all errors - sys.stderr.write( - f"ERROR: rstcheck linting failed, found unwanted: '{check}'\n" - f"{resp.stdout.decode('utf-8')}\n" - f"{resp.stderr.decode('utf-8')}") - return len(resp.stdout.decode("utf-8").split("\n")) - 1 - - -def main(): - errors = 0 - for check in RSTCHECK_GREP_FAIL: - errors += run_grep_check(check) - if errors: - raise SystemExit(f"RST check failed: {errors} errors") - - -if __name__ == "__main__": - main() From e7c114224f75571fc64542c4e641a73768e2df71 Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Thu, 25 Mar 2021 12:23:23 -0400 Subject: [PATCH 019/209] http: Fixing empty metadata map handling (#230) (#253) Commit Message: Fixing a crash when the decoder receives an empty metadata map. Additional Description: Upon receiving an empty metadata map and trying to decode it an assertion is triggered in debug mode, and a seg-fault occurs in release mode. The proposed fix ignores the empty metadata maps and updates a stats if one is received. Risk Level: Medium for Envoy's running with Metadata support. Testing: Added integration tests. Docs Changes: Added a codec stats counter description. Release Notes: Added bug fix description. Platform Specific Features: N/A. Fixes a fuzz bug: 25303 Signed-off-by: Tony Allen --- .../http/http_conn_man/stats.rst | 1 + docs/root/version_history/current.rst | 1 + source/common/http/http2/codec_impl.cc | 8 +- source/common/http/http2/codec_stats.h | 1 + test/common/http/http2/http2_frame.cc | 2 +- test/common/http/http2/http2_frame.h | 2 +- .../integration/integration_stream_decoder.cc | 1 + test/integration/integration_stream_decoder.h | 2 + .../multiplexed_integration_test.cc | 97 +++++++++++++++++++ 9 files changed, 112 insertions(+), 3 deletions(-) diff --git a/docs/root/configuration/http/http_conn_man/stats.rst b/docs/root/configuration/http/http_conn_man/stats.rst index c6aa07f284d49..9c7d441b7fff2 100644 --- a/docs/root/configuration/http/http_conn_man/stats.rst +++ b/docs/root/configuration/http/http_conn_man/stats.rst @@ -135,6 +135,7 @@ On the upstream side all http2 statistics are rooted at *cluster..http2.* inbound_empty_frames_flood, Counter, Total number of connections terminated for exceeding the limit on consecutive inbound frames with an empty payload and no end stream flag. The limit is configured by setting the :ref:`max_consecutive_inbound_frames_with_empty_payload config setting `. inbound_priority_frames_flood, Counter, Total number of connections terminated for exceeding the limit on inbound frames of type PRIORITY. The limit is configured by setting the :ref:`max_inbound_priority_frames_per_stream config setting `. inbound_window_update_frames_flood, Counter, Total number of connections terminated for exceeding the limit on inbound frames of type WINDOW_UPDATE. The limit is configured by setting the :ref:`max_inbound_window_updateframes_per_data_frame_sent config setting `. + metadata_empty_frames, Counter, Total number of metadata frames that were received and contained empty maps. outbound_flood, Counter, Total number of connections terminated for exceeding the limit on outbound frames of all types. The limit is configured by setting the :ref:`max_outbound_frames config setting `. outbound_control_flood, Counter, "Total number of connections terminated for exceeding the limit on outbound frames of types PING, SETTINGS and RST_STREAM. The limit is configured by setting the :ref:`max_outbound_control_frames config setting `." requests_rejected_with_underscores_in_headers, Counter, Total numbers of rejected requests due to header names containing underscores. This action is configured by setting the :ref:`headers_with_underscores_action config setting `. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 5cfe265ecc38b..dc68196493f02 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -92,6 +92,7 @@ Bug Fixes * http: disallowing "host:" in request_headers_to_add for behavioral consistency with rejecting :authority header. This behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_host_like_authority` to false. * http: fixed an issue where Enovy did not handle peer stream limits correctly, and queued streams in nghttp2 rather than establish new connections. This behavior can be temporarily reverted by setting `envoy.reloadable_features.improved_stream_limit_handling` to false. * http: fixed a bug where setting :ref:`MaxStreamDuration proto ` did not disable legacy timeout defaults. +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. * http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. * jwt_authn: reject requests with a proper error if JWT has the wrong issuer when allow_missing is used. Before this change, the requests are accepted. * listener: prevent crashing when an unknown listener config proto is received and debug logging is enabled. diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index 41c083acf2acb..574d9ef9d40c6 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -567,7 +567,13 @@ MetadataDecoder& ConnectionImpl::StreamImpl::getMetadataDecoder() { } void ConnectionImpl::StreamImpl::onMetadataDecoded(MetadataMapPtr&& metadata_map_ptr) { - decoder().decodeMetadata(std::move(metadata_map_ptr)); + // Empty metadata maps should not be decoded. + if (metadata_map_ptr->empty()) { + ENVOY_CONN_LOG(debug, "decode metadata called with empty map, skipping", parent_.connection_); + parent_.stats_.metadata_empty_frames_.inc(); + } else { + decoder().decodeMetadata(std::move(metadata_map_ptr)); + } } ConnectionImpl::ConnectionImpl(Network::Connection& connection, CodecStats& stats, diff --git a/source/common/http/http2/codec_stats.h b/source/common/http/http2/codec_stats.h index ba9d1592095bb..eb11291f82f4b 100644 --- a/source/common/http/http2/codec_stats.h +++ b/source/common/http/http2/codec_stats.h @@ -19,6 +19,7 @@ namespace Http2 { COUNTER(inbound_empty_frames_flood) \ COUNTER(inbound_priority_frames_flood) \ COUNTER(inbound_window_update_frames_flood) \ + COUNTER(metadata_empty_frames) \ COUNTER(outbound_control_flood) \ COUNTER(outbound_flood) \ COUNTER(requests_rejected_with_underscores_in_headers) \ diff --git a/test/common/http/http2/http2_frame.cc b/test/common/http/http2/http2_frame.cc index 6717ec49b3b85..266f7680dc45c 100644 --- a/test/common/http/http2/http2_frame.cc +++ b/test/common/http/http2/http2_frame.cc @@ -248,7 +248,7 @@ Http2Frame Http2Frame::makeWindowUpdateFrame(uint32_t stream_index, uint32_t inc // Note: encoder in codebase persists multiple maps, with each map representing an individual frame. Http2Frame Http2Frame::makeMetadataFrameFromMetadataMap(uint32_t stream_index, - MetadataMap& metadata_map, + const MetadataMap& metadata_map, MetadataFlags flags) { const int numberOfNameValuePairs = metadata_map.size(); absl::FixedArray nameValues(numberOfNameValuePairs); diff --git a/test/common/http/http2/http2_frame.h b/test/common/http/http2/http2_frame.h index 53465a6f9248a..370316fc787cf 100644 --- a/test/common/http/http2/http2_frame.h +++ b/test/common/http/http2/http2_frame.h @@ -149,7 +149,7 @@ class Http2Frame { static Http2Frame makeWindowUpdateFrame(uint32_t stream_index, uint32_t increment); static Http2Frame makeMetadataFrameFromMetadataMap(uint32_t stream_index, - MetadataMap& metadata_map, + const MetadataMap& metadata_map, MetadataFlags flags); static Http2Frame makeMalformedRequest(uint32_t stream_index); diff --git a/test/integration/integration_stream_decoder.cc b/test/integration/integration_stream_decoder.cc index bf8fe2aec2f3c..bc40535a5fe47 100644 --- a/test/integration/integration_stream_decoder.cc +++ b/test/integration/integration_stream_decoder.cc @@ -123,6 +123,7 @@ void IntegrationStreamDecoder::decodeTrailers(Http::ResponseTrailerMapPtr&& trai } void IntegrationStreamDecoder::decodeMetadata(Http::MetadataMapPtr&& metadata_map) { + metadata_maps_decoded_count_++; // Combines newly received metadata with the existing metadata. for (const auto& metadata : *metadata_map) { duplicated_metadata_key_count_[metadata.first]++; diff --git a/test/integration/integration_stream_decoder.h b/test/integration/integration_stream_decoder.h index 6f706c663267f..74d06a75e544c 100644 --- a/test/integration/integration_stream_decoder.h +++ b/test/integration/integration_stream_decoder.h @@ -34,6 +34,7 @@ class IntegrationStreamDecoder : public Http::ResponseDecoder, public Http::Stre const Http::ResponseTrailerMapPtr& trailers() { return trailers_; } const Http::MetadataMap& metadataMap() { return *metadata_map_; } uint64_t keyCount(std::string key) { return duplicated_metadata_key_count_[key]; } + uint32_t metadataMapsDecodedCount() const { return metadata_maps_decoded_count_; } void waitForContinueHeaders(); void waitForHeaders(); // This function waits until body_ has at least size bytes in it (it might have more). clearBody() @@ -80,6 +81,7 @@ class IntegrationStreamDecoder : public Http::ResponseDecoder, public Http::Stre bool waiting_for_headers_{}; bool saw_reset_{}; Http::StreamResetReason reset_reason_{}; + uint32_t metadata_maps_decoded_count_{}; }; using IntegrationStreamDecoderPtr = std::unique_ptr; diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index f270088f010bd..0b9653b387f86 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -204,6 +204,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ(response->metadataMap().find(key)->second, value); + EXPECT_EQ(1, response->metadataMapsDecodedCount()); // Sends the second request. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -223,6 +224,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ(response->metadataMap().find(key)->second, value); + EXPECT_EQ(1, response->metadataMapsDecodedCount()); // Sends the third request. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -242,6 +244,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ(response->metadataMap().find(key)->second, value); + EXPECT_EQ(1, response->metadataMapsDecodedCount()); // Sends the fourth request. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -262,6 +265,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ(response->metadataMap().find(key)->second, value); + EXPECT_EQ(1, response->metadataMapsDecodedCount()); // Sends the fifth request. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -282,6 +286,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ(response->metadataMap().find(key)->second, value); + EXPECT_EQ(1, response->metadataMapsDecodedCount()); // Sends the sixth request. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -332,6 +337,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadata) { // Verifies multiple metadata are received by the client. ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); + EXPECT_EQ(4, response->metadataMapsDecodedCount()); for (int i = 0; i < size; i++) { for (const auto& metadata : *multiple_vecs[i][0]) { EXPECT_EQ(response->metadataMap().find(metadata.first)->second, metadata.second); @@ -365,6 +371,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyInvalidMetadata) { // Verifies metadata is not received by the client. ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); + EXPECT_EQ(0, response->metadataMapsDecodedCount()); EXPECT_EQ(response->metadataMap().size(), 0); } @@ -406,6 +413,7 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { expected_metadata_keys.insert("data"); verifyExpectedMetadata(response->metadataMap(), expected_metadata_keys); EXPECT_EQ(response->keyCount("duplicate"), 2); + EXPECT_EQ(2, response->metadataMapsDecodedCount()); // Upstream responds with headers, data and trailers. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -420,6 +428,7 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { expected_metadata_keys.insert("trailers"); verifyExpectedMetadata(response->metadataMap(), expected_metadata_keys); EXPECT_EQ(response->keyCount("duplicate"), 3); + EXPECT_EQ(4, response->metadataMapsDecodedCount()); // Upstream responds with headers, 100-continue and data. response = @@ -442,6 +451,7 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { expected_metadata_keys.insert("100-continue"); verifyExpectedMetadata(response->metadataMap(), expected_metadata_keys); EXPECT_EQ(response->keyCount("duplicate"), 4); + EXPECT_EQ(4, response->metadataMapsDecodedCount()); // Upstream responds with headers and metadata that will not be consumed. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -460,6 +470,7 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { expected_metadata_keys.insert("aaa"); expected_metadata_keys.insert("keep"); verifyExpectedMetadata(response->metadataMap(), expected_metadata_keys); + EXPECT_EQ(2, response->metadataMapsDecodedCount()); // Upstream responds with headers, data and metadata that will be consumed. response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -479,6 +490,7 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { expected_metadata_keys.insert("replace"); verifyExpectedMetadata(response->metadataMap(), expected_metadata_keys); EXPECT_EQ(response->keyCount("duplicate"), 2); + EXPECT_EQ(3, response->metadataMapsDecodedCount()); } TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadataReachSizeLimit) { @@ -1638,6 +1650,91 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, Http2FrameIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); +// Tests sending an empty metadata map from downstream. +TEST_P(Http2FrameIntegrationTest, DownstreamSendingEmptyMetadata) { + // Allow metadata usage. + config_helper_.addConfigModifier([&](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() + ->mutable_http2_protocol_options() + ->set_allow_metadata(true); + ConfigHelper::setProtocolOptions(*bootstrap.mutable_static_resources()->mutable_clusters(0), + protocol_options); + }); + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { hcm.mutable_http2_protocol_options()->set_allow_metadata(true); }); + + // This test uses an Http2Frame and not the encoder's encodeMetadata method, + // because encodeMetadata fails when an empty metadata map is sent. + beginSession(); + FakeHttpConnectionPtr fake_upstream_connection; + FakeStreamPtr fake_upstream_request; + + const uint32_t client_stream_idx = 1; + // Send request. + const Http2Frame request = + Http2Frame::makePostRequest(client_stream_idx, "host", "/path/to/long/url"); + sendFrame(request); + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->waitForNewStream(*dispatcher_, fake_upstream_request)); + + // Send metadata frame with empty metadata map. + const Http::MetadataMap empty_metadata_map; + const Http2Frame empty_metadata_map_frame = Http2Frame::makeMetadataFrameFromMetadataMap( + client_stream_idx, empty_metadata_map, Http2Frame::MetadataFlags::EndMetadata); + sendFrame(empty_metadata_map_frame); + + // Send an empty data frame to close the stream. + const Http2Frame empty_data_frame = + Http2Frame::makeEmptyDataFrame(client_stream_idx, Http2Frame::DataFlags::EndStream); + sendFrame(empty_data_frame); + + // Upstream sends a reply. + ASSERT_TRUE(fake_upstream_request->waitForEndStream(*dispatcher_)); + const Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + fake_upstream_request->encodeHeaders(response_headers, true); + + // Make sure that a response from upstream is received by the client, and + // close the connection. + const auto response = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, response.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, response.responseStatus()); + EXPECT_EQ(1, test_server_->counter("http2.metadata_empty_frames")->value()); + + // Cleanup. + tcp_client_->close(); +} + +// Tests that an empty metadata map from upstream is ignored. +TEST_P(Http2MetadataIntegrationTest, UpstreamSendingEmptyMetadata) { + initialize(); + + // Send a request and make sure an upstream connection is established. + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(); + auto* upstream = fake_upstreams_.front().get(); + + // Send response headers. + upstream_request_->encodeHeaders(default_response_headers_, false); + // Send an empty metadata map back from upstream. + const Http::MetadataMap empty_metadata_map; + const Http2Frame empty_metadata_frame = Http2Frame::makeMetadataFrameFromMetadataMap( + 1, empty_metadata_map, Http2Frame::MetadataFlags::EndMetadata); + ASSERT_TRUE(upstream->rawWriteConnection( + 0, std::string(empty_metadata_frame.begin(), empty_metadata_frame.end()))); + // Send an empty data frame after the metadata frame to end the stream. + upstream_request_->encodeData(0, true); + + // Verifies that no metadata was received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(0, response->metadataMapsDecodedCount()); + EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.http2.metadata_empty_frames")->value()); +} + // Tests upstream sending a metadata frame after ending a stream. TEST_P(Http2MetadataIntegrationTest, UpstreamMetadataAfterEndStream) { initialize(); From e383908bf29ce78d196b2f6cd4f41e7862acf6e8 Mon Sep 17 00:00:00 2001 From: asraa Date: Thu, 25 Mar 2021 14:12:10 -0400 Subject: [PATCH 020/209] Fix grpc-timeout integer overflow (#237) Fixes CVE-2021-28682, a remotely exploitable integer overflow. Signed-off-by: Asra Ali Co-authored-by: Tony Allen Signed-off-by: Tony Allen --- source/common/grpc/common.cc | 19 ++++++++++++------- source/common/grpc/common.h | 2 ++ test/common/grpc/common_test.cc | 18 ++++++++++++++++-- test/common/http/conn_manager_impl_test.cc | 13 +++++++++++++ test/common/router/router_test.cc | 10 ++++++++++ 5 files changed, 53 insertions(+), 9 deletions(-) diff --git a/source/common/grpc/common.cc b/source/common/grpc/common.cc index f673367d179d9..306062d781420 100644 --- a/source/common/grpc/common.cc +++ b/source/common/grpc/common.cc @@ -164,12 +164,18 @@ Common::getGrpcTimeout(const Http::RequestHeaderMap& request_headers) { const Http::HeaderEntry* header_grpc_timeout_entry = request_headers.GrpcTimeout(); std::chrono::milliseconds timeout; if (header_grpc_timeout_entry) { - uint64_t grpc_timeout; - // TODO(dnoe): Migrate to pure string_view (#6580) - std::string grpc_timeout_string(header_grpc_timeout_entry->value().getStringView()); - const char* unit = StringUtil::strtoull(grpc_timeout_string.c_str(), grpc_timeout); - if (unit != nullptr && *unit != '\0') { - switch (*unit) { + int64_t grpc_timeout; + absl::string_view timeout_entry = header_grpc_timeout_entry->value().getStringView(); + if (timeout_entry.empty()) { + // Must be of the form TimeoutValue TimeoutUnit. See + // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests. + return absl::nullopt; + } + // TimeoutValue must be a positive integer of at most 8 digits. + if (absl::SimpleAtoi(timeout_entry.substr(0, timeout_entry.size() - 1), &grpc_timeout) && + grpc_timeout >= 0 && static_cast(grpc_timeout) <= MAX_GRPC_TIMEOUT_VALUE) { + const char unit = timeout_entry[timeout_entry.size() - 1]; + switch (unit) { case 'H': return std::chrono::hours(grpc_timeout); case 'M': @@ -204,7 +210,6 @@ void Common::toGrpcTimeout(const std::chrono::milliseconds& timeout, uint64_t time = timeout.count(); static const char units[] = "mSMH"; const char* unit = units; // start with milliseconds - static constexpr size_t MAX_GRPC_TIMEOUT_VALUE = 99999999; if (time > MAX_GRPC_TIMEOUT_VALUE) { time /= 1000; // Convert from milliseconds to seconds unit++; diff --git a/source/common/grpc/common.h b/source/common/grpc/common.h index f76082610f317..dcade2ad8015a 100644 --- a/source/common/grpc/common.h +++ b/source/common/grpc/common.h @@ -178,6 +178,8 @@ class Common { private: static void checkForHeaderOnlyError(Http::ResponseMessage& http_response); + + static constexpr size_t MAX_GRPC_TIMEOUT_VALUE = 99999999; }; } // namespace Grpc diff --git a/test/common/grpc/common_test.cc b/test/common/grpc/common_test.cc index 3f6d88dd00963..4a54ee4b6bbbe 100644 --- a/test/common/grpc/common_test.cc +++ b/test/common/grpc/common_test.cc @@ -78,6 +78,9 @@ TEST(GrpcContextTest, GetGrpcTimeout) { Http::TestRequestHeaderMapImpl missing_unit{{"grpc-timeout", "123"}}; EXPECT_EQ(absl::nullopt, Common::getGrpcTimeout(missing_unit)); + Http::TestRequestHeaderMapImpl small_missing_unit{{"grpc-timeout", "1"}}; + EXPECT_EQ(absl::nullopt, Common::getGrpcTimeout(small_missing_unit)); + Http::TestRequestHeaderMapImpl illegal_unit{{"grpc-timeout", "123F"}}; EXPECT_EQ(absl::nullopt, Common::getGrpcTimeout(illegal_unit)); @@ -102,8 +105,19 @@ TEST(GrpcContextTest, GetGrpcTimeout) { Http::TestRequestHeaderMapImpl unit_nanoseconds{{"grpc-timeout", "12345678n"}}; EXPECT_EQ(std::chrono::milliseconds(13), Common::getGrpcTimeout(unit_nanoseconds)); - // Max 8 digits and no leading whitespace or +- signs are not enforced on decode, - // so we don't test for them. + // Test max 8 digits to prevent millisecond overflow. + Http::TestRequestHeaderMapImpl value_overflow{{"grpc-timeout", "6666666666666H"}}; + EXPECT_EQ(absl::nullopt, Common::getGrpcTimeout(value_overflow)); + + // Reject negative values. + Http::TestRequestHeaderMapImpl value_negative{{"grpc-timeout", "-1S"}}; + EXPECT_EQ(absl::nullopt, Common::getGrpcTimeout(value_negative)); + + // Allow positive values marked with +. + Http::TestRequestHeaderMapImpl value_positive{{"grpc-timeout", "+1S"}}; + EXPECT_EQ(std::chrono::milliseconds(1000), Common::getGrpcTimeout(value_positive)); + + // No leading whitespace are not enforced on decode so we don't test for them. } TEST(GrpcCommonTest, GrpcStatusDetailsBin) { diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 75aae57d4ab04..f173cce762049 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -2533,6 +2533,19 @@ TEST_F(HttpConnectionManagerImplTest, DurationTimeout) { decoder_filters_[0]->callbacks_->clusterInfo(); } + // With an invalid gRPC timeout, refreshing cached route will not use header and use stream + // duration. + latched_headers->setGrpcTimeout("6666666666666H"); + { + // 25ms used already from previous case so timer is set to be 5ms. + EXPECT_CALL(*timer, enableTimer(std::chrono::milliseconds(5), _)); + EXPECT_CALL(route_config_provider_.route_config_->route_->route_entry_, maxStreamDuration()) + .Times(2) + .WillRepeatedly(Return(std::chrono::milliseconds(30))); + decoder_filters_[0]->callbacks_->clearRouteCache(); + decoder_filters_[0]->callbacks_->clusterInfo(); + } + // Cleanup. EXPECT_CALL(*timer, disableTimer()); EXPECT_CALL(*decoder_filters_[0], onStreamComplete()); diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 94033e7721c74..9ffbd3eab416c 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -5376,6 +5376,16 @@ TEST(RouterFilterUtilityTest, FinalTimeout) { EXPECT_EQ("5", headers.get_("x-envoy-expected-rq-timeout-ms")); EXPECT_EQ("5m", headers.get_("grpc-timeout")); } + { + NiceMock route; + EXPECT_CALL(route, maxGrpcTimeout()) + .WillRepeatedly(Return(absl::optional(10000))); + Http::TestRequestHeaderMapImpl headers{{"content-type", "application/grpc"}, + {"grpc-timeout", "6666666666666H"}}; + FilterUtility::finalTimeout(route, headers, true, true, false, false); + EXPECT_EQ("10000", headers.get_("x-envoy-expected-rq-timeout-ms")); + EXPECT_EQ("10000m", headers.get_("grpc-timeout")); + } { NiceMock route; EXPECT_CALL(route, timeout()).WillOnce(Return(std::chrono::milliseconds(10))); From 35783a5559f5e883533fdbe7b913dd63d4dc772e Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Thu, 25 Mar 2021 15:04:52 -0700 Subject: [PATCH 021/209] ssl: fix crash when peer sends an SSL Alert with an unknown code (#239) Fix for CVE-2021-28683 (crash when peer sends an SSL Alert with an unknown code) Signed-off-by: Greg Greenway Co-authored-by: Christoph Pakulski Signed-off-by: Tony Allen --- .../transport_sockets/tls/context_impl.cc | 7 +++-- .../transport_sockets/tls/ssl_socket.cc | 7 +++-- .../tls/integration/ssl_integration_test.cc | 30 +++++++++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index b637fe81a5ab9..29c82704c8ea4 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -176,9 +176,10 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c if (ctx.cert_chain_ == nullptr || !SSL_CTX_use_certificate(ctx.ssl_ctx_.get(), ctx.cert_chain_.get())) { while (uint64_t err = ERR_get_error()) { - ENVOY_LOG_MISC(debug, "SSL error: {}:{}:{}:{}", err, ERR_lib_error_string(err), - ERR_func_error_string(err), ERR_GET_REASON(err), - ERR_reason_error_string(err)); + ENVOY_LOG_MISC(debug, "SSL error: {}:{}:{}:{}", err, + absl::NullSafeStringView(ERR_lib_error_string(err)), + absl::NullSafeStringView(ERR_func_error_string(err)), ERR_GET_REASON(err), + absl::NullSafeStringView(ERR_reason_error_string(err))); } throw EnvoyException( absl::StrCat("Failed to load certificate chain from ", ctx.cert_chain_file_path_)); diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index dbcb87543c7c1..aba8e24dca695 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -210,9 +210,10 @@ void SslSocket::drainErrorQueue() { if (failure_reason_.empty()) { failure_reason_ = "TLS error:"; } - failure_reason_.append(absl::StrCat(" ", err, ":", ERR_lib_error_string(err), ":", - ERR_func_error_string(err), ":", - ERR_reason_error_string(err))); + failure_reason_.append(absl::StrCat(" ", err, ":", + absl::NullSafeStringView(ERR_lib_error_string(err)), ":", + absl::NullSafeStringView(ERR_func_error_string(err)), ":", + absl::NullSafeStringView(ERR_reason_error_string(err)))); } if (!failure_reason_.empty()) { ENVOY_CONN_LOG(debug, "{}", callbacks_->connection(), failure_reason_); diff --git a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc index 2aa491cb833da..25ca940ceec49 100644 --- a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc +++ b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc @@ -92,6 +92,36 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, SslIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); +// Test that Envoy behaves correctly when receiving an SSLAlert for an unspecified code. The codes +// are defined in the standard, and assigned codes have a string associated with them in BoringSSL, +// which is included in logs. For an unknown code, verify that no crash occurs. +TEST_P(SslIntegrationTest, UnknownSslAlert) { + initialize(); + Network::ClientConnectionPtr connection = makeSslClientConnection({}); + ConnectionStatusCallbacks callbacks; + connection->addConnectionCallbacks(callbacks); + connection->connect(); + while (!callbacks.connected()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + Ssl::ConnectionInfoConstSharedPtr ssl_info = connection->ssl(); + SSL* ssl = + dynamic_cast(ssl_info.get()) + ->ssl(); + ASSERT_EQ(connection->state(), Network::Connection::State::Open); + ASSERT_NE(ssl, nullptr); + SSL_send_fatal_alert(ssl, 255); + while (!callbacks.closed()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + const std::string counter_name = listenerStatPrefix("ssl.connection_error"); + Stats::CounterSharedPtr counter = test_server_->counter(counter_name); + test_server_->waitForCounterGe(counter_name, 1); + connection->close(Network::ConnectionCloseType::NoFlush); +} + TEST_P(SslIntegrationTest, RouterRequestAndResponseWithGiantBodyBuffer) { ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { return makeSslClientConnection({}); From ec09c3e6a3ea9ce310c96e0ca3cbc9d756361a06 Mon Sep 17 00:00:00 2001 From: Tony Allen Date: Wed, 14 Apr 2021 21:50:17 -0700 Subject: [PATCH 022/209] 1.18.0 release Signed-off-by: Tony Allen --- RELEASES.md | 3 ++- VERSION | 2 +- docs/root/version_history/current.rst | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index a630f49633e78..bf7f9c0072f0e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -68,6 +68,7 @@ deadline of 3 weeks. | 1.15.0 | 2020/06/30 | 2020/07/07 | +7 days | 2021/07/07 | | 1.16.0 | 2020/09/30 | 2020/10/08 | +8 days | 2021/10/08 | | 1.17.0 | 2020/12/31 | 2021/01/11 | +11 days | 2022/01/11 | -| 1.18.0 | 2021/03/31 | | | | +| 1.18.0 | 2021/03/31 | 2021/04/15 | +15 days | 2022/04/15 | +| 1.19.0 | 2021/06/30 | | | | [repokitteh]: https://github.com/repokitteh diff --git a/VERSION b/VERSION index ee017091ff37b..84cc529467b05 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.18.0-dev +1.18.0 diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index dc68196493f02..5fb96ea9a9720 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -1,5 +1,5 @@ -1.18.0 (Pending) -================ +1.18.0 +====== Incompatible Behavior Changes ----------------------------- From c11469774e4f2a70e4c922f72a5f28046337c833 Mon Sep 17 00:00:00 2001 From: Tony Allen Date: Thu, 15 Apr 2021 10:16:35 -0600 Subject: [PATCH 023/209] Add release notes for v1.17.2, v1.16.3, v1.15.4, v1.14.7 Signed-off-by: Tony Allen --- docs/root/version_history/current.rst | 6 ++-- docs/root/version_history/v1.14.7.rst | 9 +++++ docs/root/version_history/v1.15.4.rst | 23 +++++++++++++ docs/root/version_history/v1.16.3.rst | 34 +++++++++++++++++++ docs/root/version_history/v1.17.1.rst | 28 +++++++++++++++ docs/root/version_history/v1.17.2.rst | 31 +++++++++++++++++ docs/root/version_history/version_history.rst | 5 +++ 7 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 docs/root/version_history/v1.14.7.rst create mode 100644 docs/root/version_history/v1.15.4.rst create mode 100644 docs/root/version_history/v1.16.3.rst create mode 100644 docs/root/version_history/v1.17.1.rst create mode 100644 docs/root/version_history/v1.17.2.rst diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 5fb96ea9a9720..d0d33a150f52f 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -1,5 +1,5 @@ -1.18.0 -====== +1.18.0 (April 15, 2021) +======================= Incompatible Behavior Changes ----------------------------- @@ -93,12 +93,14 @@ Bug Fixes * http: fixed an issue where Enovy did not handle peer stream limits correctly, and queued streams in nghttp2 rather than establish new connections. This behavior can be temporarily reverted by setting `envoy.reloadable_features.improved_stream_limit_handling` to false. * http: fixed a bug where setting :ref:`MaxStreamDuration proto ` did not disable legacy timeout defaults. * http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. * http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. * jwt_authn: reject requests with a proper error if JWT has the wrong issuer when allow_missing is used. Before this change, the requests are accepted. * listener: prevent crashing when an unknown listener config proto is received and debug logging is enabled. * mysql_filter: improve the codec ability of mysql filter at connection phase, it can now decode MySQL5.7+ connection phase protocol packet. * overload: fix a bug that can cause use-after-free when one scaled timer disables another one with the same duration. * sni: as the server name in sni should be case-insensitive, envoy will convert the server name as lower case first before any other process inside envoy. +* tls: fix a crash when peer sends a TLS Alert with an unknown code. * tls: fix the subject alternative name of the presented certificate matches the specified matchers as the case-insensitive way when it uses DNS name. * tls: fix issue where OCSP was inadvertently removed from SSL response in multi-context scenarios. * upstream: fix handling of moving endpoints between priorities when active health checks are enabled. Previously moving to a higher numbered priority was a NOOP, and moving to a lower numbered priority caused an abort. diff --git a/docs/root/version_history/v1.14.7.rst b/docs/root/version_history/v1.14.7.rst new file mode 100644 index 0000000000000..3d0584d8ab119 --- /dev/null +++ b/docs/root/version_history/v1.14.7.rst @@ -0,0 +1,9 @@ +1.14.7 (April 15, 2020) +======================= +Changes +------- +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. +* http: fixed bugs in datadog and squash filter's handling of responses with no bodies. +* http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. +* tls: fix detection of the upstream connection close event. diff --git a/docs/root/version_history/v1.15.4.rst b/docs/root/version_history/v1.15.4.rst new file mode 100644 index 0000000000000..977e24f54c7be --- /dev/null +++ b/docs/root/version_history/v1.15.4.rst @@ -0,0 +1,23 @@ +1.15.4 (April 15, 2021) +======================= + +Changes +------- + +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. +* http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. +* http: fixed bugs in datadog and squash filter's handling of responses with no bodies. +* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. +* tls: fix detection of the upstream connection close event. + +Removed Config or Runtime +------------------------- +*Normally occurs at the end of the* :ref:`deprecation period ` + +New Features +------------ + +Deprecated +---------- + diff --git a/docs/root/version_history/v1.16.3.rst b/docs/root/version_history/v1.16.3.rst new file mode 100644 index 0000000000000..e40fe3911d4d3 --- /dev/null +++ b/docs/root/version_history/v1.16.3.rst @@ -0,0 +1,34 @@ +1.16.3 (April 15, 2021) +======================= + +Incompatible Behavior Changes +----------------------------- +*Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +Minor Behavior Changes +---------------------- +*Changes that may cause incompatibilities for some users, but should not for most* + +Bug Fixes +--------- +*Changes expected to improve the state of the world and are unlikely to have negative effects* + +* aggregate cluster: fixed a crash due to a TLS initialization issue. +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. +* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. +* lua: fixed crash when Lua script contains streamInfo():downstreamSslConnection(). +* overload: fix a bug that can cause use-after-free when one scaled timer disables another one with the same duration. +* tls: fix a crash when peer sends a TLS Alert with an unknown code. +* tls: fix detection of the upstream connection close event. + +Removed Config or Runtime +------------------------- +*Normally occurs at the end of the* :ref:`deprecation period ` + +New Features +------------ + +Deprecated +---------- + diff --git a/docs/root/version_history/v1.17.1.rst b/docs/root/version_history/v1.17.1.rst new file mode 100644 index 0000000000000..0cafd45771a29 --- /dev/null +++ b/docs/root/version_history/v1.17.1.rst @@ -0,0 +1,28 @@ +1.17.1 (February 25, 2021) +========================== + +Incompatible Behavior Changes +----------------------------- +*Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +Minor Behavior Changes +---------------------- +*Changes that may cause incompatibilities for some users, but should not for most* + +Bug Fixes +--------- +*Changes expected to improve the state of the world and are unlikely to have negative effects* + +* jwt_authn: reject requests with a proper error if JWT has the wrong issuer when allow_missing is used. Before this change, the requests are accepted. +* overload: fix a bug that can cause use-after-free when one scaled timer disables another one with the same duration. + +Removed Config or Runtime +------------------------- +*Normally occurs at the end of the* :ref:`deprecation period ` + +New Features +------------ + +Deprecated +---------- + diff --git a/docs/root/version_history/v1.17.2.rst b/docs/root/version_history/v1.17.2.rst new file mode 100644 index 0000000000000..86e7b5a577967 --- /dev/null +++ b/docs/root/version_history/v1.17.2.rst @@ -0,0 +1,31 @@ +1.17.2 (April 15, 2021) +======================= + +Incompatible Behavior Changes +----------------------------- +*Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +Minor Behavior Changes +---------------------- +*Changes that may cause incompatibilities for some users, but should not for most* + +Bug Fixes +--------- +*Changes expected to improve the state of the world and are unlikely to have negative effects* + +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. +* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. +* tls: fix a crash when peer sends a TLS Alert with an unknown code. + +Removed Config or Runtime +------------------------- +*Normally occurs at the end of the* :ref:`deprecation period ` + +New Features +------------ +* dispatcher: supports a stack of `Envoy::ScopeTrackedObject` instead of a single tracked object. This will allow Envoy to dump more debug information on crash. + +Deprecated +---------- + diff --git a/docs/root/version_history/version_history.rst b/docs/root/version_history/version_history.rst index 05c5ffd155683..002778fb35f9d 100644 --- a/docs/root/version_history/version_history.rst +++ b/docs/root/version_history/version_history.rst @@ -7,14 +7,19 @@ Version history :titlesonly: current + v1.17.2 + v1.17.1 v1.17.0 + v1.16.3 v1.16.2 v1.16.1 v1.16.0 + v1.15.4 v1.15.3 v1.15.2 v1.15.1 v1.15.0 + v1.14.7 v1.14.6 v1.14.5 v1.14.4 From 345ffe37148b7a35b6e8e04db0300463689e3ff1 Mon Sep 17 00:00:00 2001 From: Tony Allen Date: Thu, 15 Apr 2021 13:20:25 -0700 Subject: [PATCH 024/209] Fix formatting of security patches Signed-off-by: Tony Allen --- source/common/common/interval_value.h | 9 +++++---- source/common/local_reply/local_reply.cc | 11 ++++++----- test/common/network/apple_dns_impl_test.cc | 16 ++++++++-------- test/test_common/environment.cc | 6 +++--- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/source/common/common/interval_value.h b/source/common/common/interval_value.h index e001a8a13a324..3a058eaae4b54 100644 --- a/source/common/common/interval_value.h +++ b/source/common/common/interval_value.h @@ -25,10 +25,11 @@ template class ClosedIntervalValue { // Returns a value that is as far from max as the original value is from min. // This guarantees that max().invert() == min() and min().invert() == max(). ClosedIntervalValue invert() const { - return ClosedIntervalValue(value_ == Interval::max_value ? Interval::min_value - : value_ == Interval::min_value - ? Interval::max_value - : Interval::max_value - (value_ - Interval::min_value)); + return ClosedIntervalValue(value_ == Interval::max_value + ? Interval::min_value + : value_ == Interval::min_value + ? Interval::max_value + : Interval::max_value - (value_ - Interval::min_value)); } // Comparisons are performed using the same operators on the underlying value diff --git a/source/common/local_reply/local_reply.cc b/source/common/local_reply/local_reply.cc index 753003f223b33..42f8d32b0d383 100644 --- a/source/common/local_reply/local_reply.cc +++ b/source/common/local_reply/local_reply.cc @@ -25,11 +25,12 @@ class BodyFormatter { BodyFormatter(const envoy::config::core::v3::SubstitutionFormatString& config, Api::Api& api) : formatter_(Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config, api)), content_type_( - !config.content_type().empty() ? config.content_type() - : config.format_case() == - envoy::config::core::v3::SubstitutionFormatString::FormatCase::kJsonFormat - ? Http::Headers::get().ContentTypeValues.Json - : Http::Headers::get().ContentTypeValues.Text) {} + !config.content_type().empty() + ? config.content_type() + : config.format_case() == + envoy::config::core::v3::SubstitutionFormatString::FormatCase::kJsonFormat + ? Http::Headers::get().ContentTypeValues.Json + : Http::Headers::get().ContentTypeValues.Text) {} void format(const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, diff --git a/test/common/network/apple_dns_impl_test.cc b/test/common/network/apple_dns_impl_test.cc index 7cc40baf6f193..9d9fbb534d127 100644 --- a/test/common/network/apple_dns_impl_test.cc +++ b/test/common/network/apple_dns_impl_test.cc @@ -350,12 +350,12 @@ TEST_F(AppleDnsImplFakeApiTest, SynchronousErrorInGetAddrInfo) { // The Query's sd ref will be deallocated. EXPECT_CALL(dns_service_, dnsServiceRefDeallocate(_)); - EXPECT_EQ(nullptr, - resolver_->resolve("foo.com", Network::DnsLookupFamily::Auto, - [](DnsResolver::ResolutionStatus, std::list&&) -> void { - // This callback should never be executed. - FAIL(); - })); + EXPECT_EQ(nullptr, resolver_->resolve( + "foo.com", Network::DnsLookupFamily::Auto, + [](DnsResolver::ResolutionStatus, std::list &&) -> void { + // This callback should never be executed. + FAIL(); + })); } TEST_F(AppleDnsImplFakeApiTest, QuerySynchronousCompletion) { @@ -428,7 +428,7 @@ TEST_F(AppleDnsImplFakeApiTest, IncorrectInterfaceIndexReturned) { resolver_->resolve( hostname, Network::DnsLookupFamily::Auto, - [](DnsResolver::ResolutionStatus, std::list&&) -> void { FAIL(); }); + [](DnsResolver::ResolutionStatus, std::list &&) -> void { FAIL(); }); } TEST_F(AppleDnsImplFakeApiTest, QueryCompletedWithError) { @@ -785,7 +785,7 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithNullAddress) { auto query = resolver_->resolve( hostname, Network::DnsLookupFamily::Auto, - [](DnsResolver::ResolutionStatus, std::list&&) -> void { FAIL(); }); + [](DnsResolver::ResolutionStatus, std::list &&) -> void { FAIL(); }); ASSERT_NE(nullptr, query); EXPECT_DEATH(reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, diff --git a/test/test_common/environment.cc b/test/test_common/environment.cc index 62c8d02c011a6..87f6c0a8bab15 100644 --- a/test/test_common/environment.cc +++ b/test/test_common/environment.cc @@ -362,9 +362,9 @@ std::string TestEnvironment::temporaryFileSubstitute(const std::string& path, out_json_string = substitute(out_json_string, version); auto name = Filesystem::fileSystemForTest().splitPathFromFilename(path).file_; - const std::string extension = absl::EndsWith(name, ".yaml") ? ".yaml" - : absl::EndsWith(name, ".pb_text") ? ".pb_text" - : ".json"; + const std::string extension = absl::EndsWith(name, ".yaml") + ? ".yaml" + : absl::EndsWith(name, ".pb_text") ? ".pb_text" : ".json"; const std::string out_json_path = TestEnvironment::temporaryPath(name) + ".with.ports" + extension; { From 96146055fd0648e76bb8721da33022cfaff70850 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 15 Apr 2021 18:49:14 -0400 Subject: [PATCH 025/209] release: 1.18.1 (#16020) Signed-off-by: Alyssa Wilk --- VERSION | 2 +- docs/root/version_history/current.rst | 190 +---------------- docs/root/version_history/v1.18.0.rst | 195 ++++++++++++++++++ docs/root/version_history/version_history.rst | 1 + source/common/common/interval_value.h | 9 +- source/common/local_reply/local_reply.cc | 11 +- test/common/network/apple_dns_impl_test.cc | 16 +- test/test_common/environment.cc | 6 +- 8 files changed, 218 insertions(+), 212 deletions(-) create mode 100644 docs/root/version_history/v1.18.0.rst diff --git a/VERSION b/VERSION index 84cc529467b05..ec6d649be6505 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.18.0 +1.18.1 diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index d0d33a150f52f..ca0baae62a9c6 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -1,195 +1,7 @@ 1.18.0 (April 15, 2021) ======================= -Incompatible Behavior Changes ------------------------------ -*Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* - -* config: the v2 xDS API is no longer supported by the Envoy binary. -* grpc_stats: the default value for :ref:`stats_for_all_methods ` is switched from true to false, in order to avoid possible memory exhaustion due to an untrusted downstream sending a large number of unique method names. The previous default value was deprecated in version 1.14.0. This only changes the behavior when the value is not set. The previous behavior can be used by setting the value to true. This behavior change by be overridden by setting runtime feature `envoy.deprecated_features.grpc_stats_filter_enable_stats_for_all_methods_by_default`. -* http: fixing a standards compliance issue with :scheme. The :scheme header sent upstream is now based on the original URL scheme, rather than set based on the security of the upstream connection. This behavior can be temporarily reverted by setting `envoy.reloadable_features.preserve_downstream_scheme` to false. -* http: http3 is now enabled/disabled via build option `--define http3=disabled` rather than the extension framework. The behavior is the same, but builds may be affected for platforms or build configurations where http3 is not supported. -* http: resolving inconsistencies between :scheme and X-Forwarded-Proto. :scheme will now be set for all HTTP/1.1 requests. This changes the behavior of the gRPC access logger, Wasm filters, CSRF filter and oath2 filter for HTTP/1 traffic, where :scheme was previously not set. This change also validates that for front-line Envoys (Envoys configured with :ref:`xff_num_trusted_hops ` set to 0 and :ref:`use_remote_address ` set to true) that HTTP/1.1 https schemed requests can not be sent over non-TLS connections. All behavioral changes listed here can be temporarily reverted by setting `envoy.reloadable_features.add_and_validate_scheme_header` to false. -* http: when a protocol error is detected in response from upstream, Envoy sends 502 BadGateway downstream and access log entry contains UPE flag. This behavior change can be overwritten to use error code 503 by setting `envoy.reloadable_features.return_502_for_upstream_protocol_errors` to false. - -Minor Behavior Changes ----------------------- -*Changes that may cause incompatibilities for some users, but should not for most* - -* access_logs: change command operator %UPSTREAM_CLUSTER% to resolve to :ref:`alt_stat_name ` if provided. This behavior can be reverted by disabling the runtime feature `envoy.reloadable_features.use_observable_cluster_name`. -* access_logs: fix substition formatter to recognize commands ending with an integer such as DOWNSTREAM_PEER_FINGERPRINT_256. -* access_logs: set the error flag `NC` for `no cluster found` instead of `NR` if the route is found but the corresponding cluster is not available. -* admin: added :ref:`observability_name ` information to GET /clusters?format=json :ref:`cluster status `. -* dns: both the :ref:`strict DNS ` and - :ref:`logical DNS ` cluster types now honor the - :ref:`hostname ` field if not empty. - Previously resolved hosts would have their hostname set to the configured DNS address for use with - logging, :ref:`auto_host_rewrite `, etc. - Setting the hostname manually allows overriding the internal hostname used for such features while - still allowing the original DNS resolution name to be used. -* grpc_json_transcoder: the filter now adheres to encoder and decoder buffer limits. Requests and responses - that require buffering over the limits will be directly rejected. The behavior can be reverted by - disabling runtime feature `envoy.reloadable_features.grpc_json_transcoder_adhere_to_buffer_limits`. - To reduce or increase the buffer limits the filter adheres to, reference the :ref:`flow control documentation `. -* hds: support custom health check port via :ref:`health_check_config `. -* healthcheck: the :ref:`health check filter ` now sends the - :ref:`x-envoy-immediate-health-check-fail ` header - for all responses when Envoy is in the health check failed state. Additionally, receiving the - :ref:`x-envoy-immediate-health-check-fail ` - header (either in response to normal traffic or in response to an HTTP :ref:`active health check `) will - cause Envoy to immediately :ref:`exclude ` the host from - load balancing calculations. This has the useful property that such hosts, which are being - explicitly told to disable traffic, will not be counted for panic routing calculations. See the - excluded documentation for more information. This behavior can be temporarily reverted by setting - the `envoy.reloadable_features.health_check.immediate_failure_exclude_from_cluster` feature flag - to false. Note that the runtime flag covers *both* the health check filter responding with - `x-envoy-immediate-health-check-fail` in all cases (versus just non-HC requests) as well as - whether receiving `x-envoy-immediate-health-check-fail` will cause exclusion or not. Thus, - depending on the Envoy deployment, the feature flag may need to be flipped on both downstream - and upstream instances, depending on the reason. -* http: added support for internal redirects with bodies. This behavior can be disabled temporarily by setting `envoy.reloadable_features.internal_redirects_with_body` to false. -* http: increase the maximum allowed number of initial connection WINDOW_UPDATE frames sent by the peer from 1 to 5. -* http: no longer adding content-length: 0 for requests which should not have bodies. This behavior can be temporarily reverted by setting `envoy.reloadable_features.dont_add_content_length_for_bodiless_requests` false. -* http: switched the path canonicalizer to `googleurl `_ - instead of `//source/common/chromium_url`. The new path canonicalizer is enabled by default. To - revert to the legacy path canonicalizer, enable the runtime flag - `envoy.reloadable_features.remove_forked_chromium_url`. -* http: upstream flood and abuse checks now increment the count of opened HTTP/2 streams when Envoy sends - initial HEADERS frame for the new stream. Before the counter was incrementred when Envoy received - response HEADERS frame with the END_HEADERS flag set from upstream server. -* lua: added function `timestamp` to provide millisecond resolution timestamps by passing in `EnvoyTimestampResolution.MILLISECOND`. -* oauth filter: added the optional parameter :ref:`auth_scopes ` with default value of 'user' if not provided. This allows this value to be overridden in the Authorization request to the OAuth provider. -* perf: allow reading more bytes per operation from raw sockets to improve performance. -* router: extended custom date formatting to DOWNSTREAM_PEER_CERT_V_START and DOWNSTREAM_PEER_CERT_V_END when using :ref:`custom request/response header formats `. -* router: made the path rewrite available without finalizing headers, so the filter could calculate the current value of the final url. -* tracing: added `upstream_cluster.name` tag that resolves to resolve to :ref:`alt_stat_name ` if provided (and otherwise the cluster name). -* udp: configuration has been added for :ref:`GRO ` - which used to be force enabled if the OS supports it. The default is now disabled for server - sockets and enabled for client sockets (see the new features section for links). -* upstream: host weight changes now cause a full load balancer rebuild as opposed to happening - atomically inline. This change has been made to support load balancer pre-computation of data - structures based on host weight, but may have performance implications if host weight changes - are very frequent. This change can be disabled by setting the `envoy.reloadable_features.upstream_host_weight_change_causes_rebuild` - feature flag to false. If setting this flag to false is required in a deployment please open an - issue against the project. - Bug Fixes --------- -*Changes expected to improve the state of the world and are unlikely to have negative effects* - -* active http health checks: properly handles HTTP/2 GOAWAY frames from the upstream. Previously a GOAWAY frame due to a graceful listener drain could cause improper failed health checks due to streams being refused by the upstream on a connection that is going away. To revert to old GOAWAY handling behavior, set the runtime feature `envoy.reloadable_features.health_check.graceful_goaway_handling` to false. -* adaptive concurrency: fixed a bug where concurrent requests on different worker threads could update minRTT back-to-back. -* buffer: tighten network connection read and write buffer high watermarks in preparation to more careful enforcement of read limits. Buffer high-watermark is now set to the exact configured value; previously it was set to value + 1. -* cdn_loop: check that the entirety of the :ref:`cdn_id ` field is a valid CDN identifier. -* cds: fix blocking the update for a warming cluster when the update is the same as the active version. -* ext_authz: emit :ref:`CheckResponse.dynamic_metadata ` when the external authorization response has "Denied" check status. -* fault injection: stop counting as active fault after delay elapsed. Previously fault injection filter continues to count the injected delay as an active fault even after it has elapsed. This produces incorrect output statistics and impacts the max number of consecutive faults allowed (e.g., for long-lived streams). This change decreases the active fault count when the delay fault is the only active and has gone finished. -* filter_chain: fix filter chain matching with the server name as the case-insensitive way. -* grpc-web: fix local reply and non-proto-encoded gRPC response handling for small response bodies. This fix can be temporarily reverted by setting `envoy.reloadable_features.grpc_web_fix_non_proto_encoded_response_handling` to false. -* grpc_http_bridge: the downstream HTTP status is now correctly set for trailers-only responses from the upstream. -* header map: pick the right delimiter to append multiple header values to the same key. Previouly header with multiple values were coalesced with ",", after this fix cookie headers should be coalesced with " ;". This doesn't affect Http1 or Http2 requests because these 2 codecs coalesce cookie headers before adding it to header map. To revert to the old behavior, set the runtime feature `envoy.reloadable_features.header_map_correctly_coalesce_cookies` to false. -* http: avoid grpc-status overwrite on when sending local replies if that field has already been set. -* http: disallowing "host:" in request_headers_to_add for behavioral consistency with rejecting :authority header. This behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_host_like_authority` to false. -* http: fixed an issue where Enovy did not handle peer stream limits correctly, and queued streams in nghttp2 rather than establish new connections. This behavior can be temporarily reverted by setting `envoy.reloadable_features.improved_stream_limit_handling` to false. -* http: fixed a bug where setting :ref:`MaxStreamDuration proto ` did not disable legacy timeout defaults. -* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. -* http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. -* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. -* jwt_authn: reject requests with a proper error if JWT has the wrong issuer when allow_missing is used. Before this change, the requests are accepted. -* listener: prevent crashing when an unknown listener config proto is received and debug logging is enabled. -* mysql_filter: improve the codec ability of mysql filter at connection phase, it can now decode MySQL5.7+ connection phase protocol packet. -* overload: fix a bug that can cause use-after-free when one scaled timer disables another one with the same duration. -* sni: as the server name in sni should be case-insensitive, envoy will convert the server name as lower case first before any other process inside envoy. -* tls: fix a crash when peer sends a TLS Alert with an unknown code. -* tls: fix the subject alternative name of the presented certificate matches the specified matchers as the case-insensitive way when it uses DNS name. -* tls: fix issue where OCSP was inadvertently removed from SSL response in multi-context scenarios. -* upstream: fix handling of moving endpoints between priorities when active health checks are enabled. Previously moving to a higher numbered priority was a NOOP, and moving to a lower numbered priority caused an abort. -* upstream: retry budgets will now set default values for xDS configurations. -* zipkin: fix 'verbose' mode to emit annotations for stream events. This was the documented behavior, but wasn't behaving as documented. - -Removed Config or Runtime -------------------------- -*Normally occurs at the end of the* :ref:`deprecation period ` - -* access_logs: removed legacy unbounded access logs and runtime guard `envoy.reloadable_features.disallow_unbounded_access_logs`. -* dns: removed legacy buggy wildcard matching path and runtime guard `envoy.reloadable_features.fix_wildcard_matching`. -* dynamic_forward_proxy: removed `envoy.reloadable_features.enable_dns_cache_circuit_breakers` and legacy code path. -* http: removed legacy connect behavior and runtime guard `envoy.reloadable_features.stop_faking_paths`. -* http: removed legacy connection close behavior and runtime guard `envoy.reloadable_features.fixed_connection_close`. -* http: removed legacy HTTP/1.1 error reporting path and runtime guard `envoy.reloadable_features.early_errors_via_hcm`. -* http: removed legacy sanitization path for upgrade response headers and runtime guard `envoy.reloadable_features.fix_upgrade_response`. -* http: removed legacy date header overwriting logic and runtime guard `envoy.reloadable_features.preserve_upstream_date deprecation`. -* http: removed legacy ALPN handling and runtime guard `envoy.reloadable_features.http_default_alpn`. -* listener: removed legacy runtime guard `envoy.reloadable_features.listener_in_place_filterchain_update`. -* router: removed `envoy.reloadable_features.consume_all_retry_headers` and legacy code path. -* router: removed `envoy.reloadable_features.preserve_query_string_in_path_redirects` and legacy code path. - -New Features ------------- - -* access log: added a new :ref:`OpenTelemetry access logger ` extension, allowing a flexible log structure with native Envoy access log formatting. -* access log: added the new response flag `NC` for upstream cluster not found. The error flag is set when the http or tcp route is found for the request but the cluster is not available. -* access log: added the :ref:`formatters ` extension point for custom formatters (command operators). -* access log: added support for cross platform writing to :ref:`standard output ` and :ref:`standard error `. -* access log: support command operator: %FILTER_CHAIN_NAME% for the downstream tcp and http request. -* access log: support command operator: %REQUEST_HEADERS_BYTES%, %RESPONSE_HEADERS_BYTES%, and %RESPONSE_TRAILERS_BYTES%. -* admin: added support for :ref:`access loggers ` to the admin interface. -* composite filter: added new :ref:`composite filter ` that can be used to instantiate different filter configuratios based on matching incoming data. -* compression: add brotli :ref:`compressor ` and :ref:`decompressor `. -* compression: extended the compression allow compressing when the content length header is not present. This behavior may be temporarily reverted by setting `envoy.reloadable_features.enable_compression_without_content_length_header` to false. -* config: add `envoy.features.fail_on_any_deprecated_feature` runtime key, which matches the behaviour of compile-time flag `ENVOY_DISABLE_DEPRECATED_FEATURES`, i.e. use of deprecated fields will cause a crash. -* config: the ``Node`` :ref:`dynamic context parameters ` are populated in discovery requests when set on the server instance. -* dispatcher: supports a stack of `Envoy::ScopeTrackedObject` instead of a single tracked object. This will allow Envoy to dump more debug information on crash. -* ext_authz: added :ref:`response_headers_to_add ` to support sending response headers to downstream clients on OK authorization checks via gRPC. -* ext_authz: added :ref:`allowed_client_headers_on_success ` to support sending response headers to downstream clients on OK external authorization checks via HTTP. -* grpc_json_transcoder: added :ref:`request_validation_options ` to reject invalid requests early. -* grpc_json_transcoder: filter can now be configured on per-route/per-vhost level as well. Leaving empty list of services in the filter configuration disables transcoding on the specific route. -* http: added support for `Envoy::ScopeTrackedObject` for HTTP/1 and HTTP/2 dispatching. Crashes while inside the dispatching loop should dump debug information. Furthermore, HTTP/1 and HTTP/2 clients now dumps the originating request whose response from the upstream caused Envoy to crash. -* http: added support for :ref:`preconnecting `. Preconnecting is off by default, but recommended for clusters serving latency-sensitive traffic, especially if using HTTP/1.1. -* http: added support for stream filters to mutate the cached route set by HCM route resolution. Useful for filters in a filter chain that want to override specific methods/properties of a route. See :ref:`http route mutation ` docs for more information. -* http: added new runtime config `envoy.reloadable_features.check_unsupported_typed_per_filter_config`, the default value is true. When the value is true, envoy will reject virtual host-specific typed per filter config when the filter doesn't support it. -* http: added the ability to preserve HTTP/1 header case across the proxy. See the :ref:`header casing ` documentation for more information. -* http: change frame flood and abuse checks to the upstream HTTP/2 codec to ON by default. It can be disabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to false. -* http: hash multiple header values instead of only hash the first header value. It can be disabled by setting the `envoy.reloadable_features.hash_multiple_header_values` runtime key to false. See the :ref:`HashPolicy's Header configuration ` for more information. -* json: introduced new JSON parser (https://github.com/nlohmann/json) to replace RapidJSON. The new parser is disabled by default. To test the new RapidJSON parser, enable the runtime feature `envoy.reloadable_features.remove_legacy_json`. -* kill_request: :ref:`Kill Request ` now supports bidirection killing. -* listener: added an optional :ref:`stat_prefix `. -* loadbalancer: added the ability to specify the hash_key for a host when using a consistent hashing loadbalancer (ringhash, maglev) using the :ref:`LbEndpoint.Metadata ` e.g.: ``"envoy.lb": {"hash_key": "..."}``. -* log: added a new custom flag ``%j`` to the log pattern to print the actual message to log as JSON escaped string. -* oauth filter: added the optional parameter :ref:`resources `. Set this value to add multiple "resource" parameters in the Authorization request sent to the OAuth provider. This acts as an identifier representing the protected resources the client is requesting a token for. -* original_dst: added support for :ref:`Original Destination ` on Windows. This enables the use of Envoy as a sidecar proxy on Windows. -* overload: add support for scaling :ref:`transport connection timeouts`. This can be used to reduce the TLS handshake timeout in response to overload. -* postgres: added ability to :ref:`terminate SSL`. -* rbac: added :ref:`shadow_rules_stat_prefix ` to allow adding custom prefix to the stats emitted by shadow rules. -* route config: added :ref:`allow_post field ` for allowing POST payload as raw TCP. -* route config: added :ref:`max_direct_response_body_size_bytes ` to set maximum :ref:`direct response body ` size in bytes. If not specified the default remains 4096 bytes. -* server: added *fips_mode* to :ref:`server compilation settings ` related statistic. -* server: added :option:`--enable-core-dump` flag to enable core dumps via prctl (Linux-based systems only). -* tcp_proxy: add support for converting raw TCP streams into HTTP/1.1 CONNECT requests. See :ref:`upgrade documentation ` for details. -* tcp_proxy: added a :ref:`use_post field ` for using HTTP POST to proxy TCP streams. -* tcp_proxy: added a :ref:`headers_to_add field ` for setting additional headers to the HTTP requests for TCP proxing. -* thrift_proxy: added a :ref:`max_requests_per_connection field ` for setting maximum requests for per downstream connection. -* thrift_proxy: added per upstream metrics within the :ref:`thrift router ` for messagetype counters in request/response. -* thrift_proxy: added per upstream metrics within the :ref:`thrift router ` for request time histograms. -* tls peer certificate validation: added :ref:`SPIFFE validator ` for supporting isolated multiple trust bundles in a single listener or cluster. -* tracing: added the :ref:`pack_trace_reason ` - field as well as explicit configuration for the built-in :ref:`UuidRequestIdConfig ` - request ID implementation. See the trace context propagation :ref:`architecture overview - ` for more information. -* udp: added :ref:`downstream ` and - :ref:`upstream ` statistics for dropped datagrams. -* udp: added :ref:`downstream_socket_config ` - listener configuration to allow configuration of downstream max UDP datagram size. Also added - :ref:`upstream_socket_config ` - UDP proxy configuration to allow configuration of upstream max UDP datagram size. The defaults for - both remain 1500 bytes. -* udp: added configuration for :ref:`GRO - `. The default is disabled for - :ref:`downstream sockets ` - and enabled for :ref:`upstream sockets `. - -Deprecated ----------- - -* admin: :ref:`access_log_path ` is deprecated in favor for :ref:`access loggers `. +code: fixed some whitespace to make fix_format happy. diff --git a/docs/root/version_history/v1.18.0.rst b/docs/root/version_history/v1.18.0.rst new file mode 100644 index 0000000000000..d0d33a150f52f --- /dev/null +++ b/docs/root/version_history/v1.18.0.rst @@ -0,0 +1,195 @@ +1.18.0 (April 15, 2021) +======================= + +Incompatible Behavior Changes +----------------------------- +*Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +* config: the v2 xDS API is no longer supported by the Envoy binary. +* grpc_stats: the default value for :ref:`stats_for_all_methods ` is switched from true to false, in order to avoid possible memory exhaustion due to an untrusted downstream sending a large number of unique method names. The previous default value was deprecated in version 1.14.0. This only changes the behavior when the value is not set. The previous behavior can be used by setting the value to true. This behavior change by be overridden by setting runtime feature `envoy.deprecated_features.grpc_stats_filter_enable_stats_for_all_methods_by_default`. +* http: fixing a standards compliance issue with :scheme. The :scheme header sent upstream is now based on the original URL scheme, rather than set based on the security of the upstream connection. This behavior can be temporarily reverted by setting `envoy.reloadable_features.preserve_downstream_scheme` to false. +* http: http3 is now enabled/disabled via build option `--define http3=disabled` rather than the extension framework. The behavior is the same, but builds may be affected for platforms or build configurations where http3 is not supported. +* http: resolving inconsistencies between :scheme and X-Forwarded-Proto. :scheme will now be set for all HTTP/1.1 requests. This changes the behavior of the gRPC access logger, Wasm filters, CSRF filter and oath2 filter for HTTP/1 traffic, where :scheme was previously not set. This change also validates that for front-line Envoys (Envoys configured with :ref:`xff_num_trusted_hops ` set to 0 and :ref:`use_remote_address ` set to true) that HTTP/1.1 https schemed requests can not be sent over non-TLS connections. All behavioral changes listed here can be temporarily reverted by setting `envoy.reloadable_features.add_and_validate_scheme_header` to false. +* http: when a protocol error is detected in response from upstream, Envoy sends 502 BadGateway downstream and access log entry contains UPE flag. This behavior change can be overwritten to use error code 503 by setting `envoy.reloadable_features.return_502_for_upstream_protocol_errors` to false. + +Minor Behavior Changes +---------------------- +*Changes that may cause incompatibilities for some users, but should not for most* + +* access_logs: change command operator %UPSTREAM_CLUSTER% to resolve to :ref:`alt_stat_name ` if provided. This behavior can be reverted by disabling the runtime feature `envoy.reloadable_features.use_observable_cluster_name`. +* access_logs: fix substition formatter to recognize commands ending with an integer such as DOWNSTREAM_PEER_FINGERPRINT_256. +* access_logs: set the error flag `NC` for `no cluster found` instead of `NR` if the route is found but the corresponding cluster is not available. +* admin: added :ref:`observability_name ` information to GET /clusters?format=json :ref:`cluster status `. +* dns: both the :ref:`strict DNS ` and + :ref:`logical DNS ` cluster types now honor the + :ref:`hostname ` field if not empty. + Previously resolved hosts would have their hostname set to the configured DNS address for use with + logging, :ref:`auto_host_rewrite `, etc. + Setting the hostname manually allows overriding the internal hostname used for such features while + still allowing the original DNS resolution name to be used. +* grpc_json_transcoder: the filter now adheres to encoder and decoder buffer limits. Requests and responses + that require buffering over the limits will be directly rejected. The behavior can be reverted by + disabling runtime feature `envoy.reloadable_features.grpc_json_transcoder_adhere_to_buffer_limits`. + To reduce or increase the buffer limits the filter adheres to, reference the :ref:`flow control documentation `. +* hds: support custom health check port via :ref:`health_check_config `. +* healthcheck: the :ref:`health check filter ` now sends the + :ref:`x-envoy-immediate-health-check-fail ` header + for all responses when Envoy is in the health check failed state. Additionally, receiving the + :ref:`x-envoy-immediate-health-check-fail ` + header (either in response to normal traffic or in response to an HTTP :ref:`active health check `) will + cause Envoy to immediately :ref:`exclude ` the host from + load balancing calculations. This has the useful property that such hosts, which are being + explicitly told to disable traffic, will not be counted for panic routing calculations. See the + excluded documentation for more information. This behavior can be temporarily reverted by setting + the `envoy.reloadable_features.health_check.immediate_failure_exclude_from_cluster` feature flag + to false. Note that the runtime flag covers *both* the health check filter responding with + `x-envoy-immediate-health-check-fail` in all cases (versus just non-HC requests) as well as + whether receiving `x-envoy-immediate-health-check-fail` will cause exclusion or not. Thus, + depending on the Envoy deployment, the feature flag may need to be flipped on both downstream + and upstream instances, depending on the reason. +* http: added support for internal redirects with bodies. This behavior can be disabled temporarily by setting `envoy.reloadable_features.internal_redirects_with_body` to false. +* http: increase the maximum allowed number of initial connection WINDOW_UPDATE frames sent by the peer from 1 to 5. +* http: no longer adding content-length: 0 for requests which should not have bodies. This behavior can be temporarily reverted by setting `envoy.reloadable_features.dont_add_content_length_for_bodiless_requests` false. +* http: switched the path canonicalizer to `googleurl `_ + instead of `//source/common/chromium_url`. The new path canonicalizer is enabled by default. To + revert to the legacy path canonicalizer, enable the runtime flag + `envoy.reloadable_features.remove_forked_chromium_url`. +* http: upstream flood and abuse checks now increment the count of opened HTTP/2 streams when Envoy sends + initial HEADERS frame for the new stream. Before the counter was incrementred when Envoy received + response HEADERS frame with the END_HEADERS flag set from upstream server. +* lua: added function `timestamp` to provide millisecond resolution timestamps by passing in `EnvoyTimestampResolution.MILLISECOND`. +* oauth filter: added the optional parameter :ref:`auth_scopes ` with default value of 'user' if not provided. This allows this value to be overridden in the Authorization request to the OAuth provider. +* perf: allow reading more bytes per operation from raw sockets to improve performance. +* router: extended custom date formatting to DOWNSTREAM_PEER_CERT_V_START and DOWNSTREAM_PEER_CERT_V_END when using :ref:`custom request/response header formats `. +* router: made the path rewrite available without finalizing headers, so the filter could calculate the current value of the final url. +* tracing: added `upstream_cluster.name` tag that resolves to resolve to :ref:`alt_stat_name ` if provided (and otherwise the cluster name). +* udp: configuration has been added for :ref:`GRO ` + which used to be force enabled if the OS supports it. The default is now disabled for server + sockets and enabled for client sockets (see the new features section for links). +* upstream: host weight changes now cause a full load balancer rebuild as opposed to happening + atomically inline. This change has been made to support load balancer pre-computation of data + structures based on host weight, but may have performance implications if host weight changes + are very frequent. This change can be disabled by setting the `envoy.reloadable_features.upstream_host_weight_change_causes_rebuild` + feature flag to false. If setting this flag to false is required in a deployment please open an + issue against the project. + +Bug Fixes +--------- +*Changes expected to improve the state of the world and are unlikely to have negative effects* + +* active http health checks: properly handles HTTP/2 GOAWAY frames from the upstream. Previously a GOAWAY frame due to a graceful listener drain could cause improper failed health checks due to streams being refused by the upstream on a connection that is going away. To revert to old GOAWAY handling behavior, set the runtime feature `envoy.reloadable_features.health_check.graceful_goaway_handling` to false. +* adaptive concurrency: fixed a bug where concurrent requests on different worker threads could update minRTT back-to-back. +* buffer: tighten network connection read and write buffer high watermarks in preparation to more careful enforcement of read limits. Buffer high-watermark is now set to the exact configured value; previously it was set to value + 1. +* cdn_loop: check that the entirety of the :ref:`cdn_id ` field is a valid CDN identifier. +* cds: fix blocking the update for a warming cluster when the update is the same as the active version. +* ext_authz: emit :ref:`CheckResponse.dynamic_metadata ` when the external authorization response has "Denied" check status. +* fault injection: stop counting as active fault after delay elapsed. Previously fault injection filter continues to count the injected delay as an active fault even after it has elapsed. This produces incorrect output statistics and impacts the max number of consecutive faults allowed (e.g., for long-lived streams). This change decreases the active fault count when the delay fault is the only active and has gone finished. +* filter_chain: fix filter chain matching with the server name as the case-insensitive way. +* grpc-web: fix local reply and non-proto-encoded gRPC response handling for small response bodies. This fix can be temporarily reverted by setting `envoy.reloadable_features.grpc_web_fix_non_proto_encoded_response_handling` to false. +* grpc_http_bridge: the downstream HTTP status is now correctly set for trailers-only responses from the upstream. +* header map: pick the right delimiter to append multiple header values to the same key. Previouly header with multiple values were coalesced with ",", after this fix cookie headers should be coalesced with " ;". This doesn't affect Http1 or Http2 requests because these 2 codecs coalesce cookie headers before adding it to header map. To revert to the old behavior, set the runtime feature `envoy.reloadable_features.header_map_correctly_coalesce_cookies` to false. +* http: avoid grpc-status overwrite on when sending local replies if that field has already been set. +* http: disallowing "host:" in request_headers_to_add for behavioral consistency with rejecting :authority header. This behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_host_like_authority` to false. +* http: fixed an issue where Enovy did not handle peer stream limits correctly, and queued streams in nghttp2 rather than establish new connections. This behavior can be temporarily reverted by setting `envoy.reloadable_features.improved_stream_limit_handling` to false. +* http: fixed a bug where setting :ref:`MaxStreamDuration proto ` did not disable legacy timeout defaults. +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. +* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. +* jwt_authn: reject requests with a proper error if JWT has the wrong issuer when allow_missing is used. Before this change, the requests are accepted. +* listener: prevent crashing when an unknown listener config proto is received and debug logging is enabled. +* mysql_filter: improve the codec ability of mysql filter at connection phase, it can now decode MySQL5.7+ connection phase protocol packet. +* overload: fix a bug that can cause use-after-free when one scaled timer disables another one with the same duration. +* sni: as the server name in sni should be case-insensitive, envoy will convert the server name as lower case first before any other process inside envoy. +* tls: fix a crash when peer sends a TLS Alert with an unknown code. +* tls: fix the subject alternative name of the presented certificate matches the specified matchers as the case-insensitive way when it uses DNS name. +* tls: fix issue where OCSP was inadvertently removed from SSL response in multi-context scenarios. +* upstream: fix handling of moving endpoints between priorities when active health checks are enabled. Previously moving to a higher numbered priority was a NOOP, and moving to a lower numbered priority caused an abort. +* upstream: retry budgets will now set default values for xDS configurations. +* zipkin: fix 'verbose' mode to emit annotations for stream events. This was the documented behavior, but wasn't behaving as documented. + +Removed Config or Runtime +------------------------- +*Normally occurs at the end of the* :ref:`deprecation period ` + +* access_logs: removed legacy unbounded access logs and runtime guard `envoy.reloadable_features.disallow_unbounded_access_logs`. +* dns: removed legacy buggy wildcard matching path and runtime guard `envoy.reloadable_features.fix_wildcard_matching`. +* dynamic_forward_proxy: removed `envoy.reloadable_features.enable_dns_cache_circuit_breakers` and legacy code path. +* http: removed legacy connect behavior and runtime guard `envoy.reloadable_features.stop_faking_paths`. +* http: removed legacy connection close behavior and runtime guard `envoy.reloadable_features.fixed_connection_close`. +* http: removed legacy HTTP/1.1 error reporting path and runtime guard `envoy.reloadable_features.early_errors_via_hcm`. +* http: removed legacy sanitization path for upgrade response headers and runtime guard `envoy.reloadable_features.fix_upgrade_response`. +* http: removed legacy date header overwriting logic and runtime guard `envoy.reloadable_features.preserve_upstream_date deprecation`. +* http: removed legacy ALPN handling and runtime guard `envoy.reloadable_features.http_default_alpn`. +* listener: removed legacy runtime guard `envoy.reloadable_features.listener_in_place_filterchain_update`. +* router: removed `envoy.reloadable_features.consume_all_retry_headers` and legacy code path. +* router: removed `envoy.reloadable_features.preserve_query_string_in_path_redirects` and legacy code path. + +New Features +------------ + +* access log: added a new :ref:`OpenTelemetry access logger ` extension, allowing a flexible log structure with native Envoy access log formatting. +* access log: added the new response flag `NC` for upstream cluster not found. The error flag is set when the http or tcp route is found for the request but the cluster is not available. +* access log: added the :ref:`formatters ` extension point for custom formatters (command operators). +* access log: added support for cross platform writing to :ref:`standard output ` and :ref:`standard error `. +* access log: support command operator: %FILTER_CHAIN_NAME% for the downstream tcp and http request. +* access log: support command operator: %REQUEST_HEADERS_BYTES%, %RESPONSE_HEADERS_BYTES%, and %RESPONSE_TRAILERS_BYTES%. +* admin: added support for :ref:`access loggers ` to the admin interface. +* composite filter: added new :ref:`composite filter ` that can be used to instantiate different filter configuratios based on matching incoming data. +* compression: add brotli :ref:`compressor ` and :ref:`decompressor `. +* compression: extended the compression allow compressing when the content length header is not present. This behavior may be temporarily reverted by setting `envoy.reloadable_features.enable_compression_without_content_length_header` to false. +* config: add `envoy.features.fail_on_any_deprecated_feature` runtime key, which matches the behaviour of compile-time flag `ENVOY_DISABLE_DEPRECATED_FEATURES`, i.e. use of deprecated fields will cause a crash. +* config: the ``Node`` :ref:`dynamic context parameters ` are populated in discovery requests when set on the server instance. +* dispatcher: supports a stack of `Envoy::ScopeTrackedObject` instead of a single tracked object. This will allow Envoy to dump more debug information on crash. +* ext_authz: added :ref:`response_headers_to_add ` to support sending response headers to downstream clients on OK authorization checks via gRPC. +* ext_authz: added :ref:`allowed_client_headers_on_success ` to support sending response headers to downstream clients on OK external authorization checks via HTTP. +* grpc_json_transcoder: added :ref:`request_validation_options ` to reject invalid requests early. +* grpc_json_transcoder: filter can now be configured on per-route/per-vhost level as well. Leaving empty list of services in the filter configuration disables transcoding on the specific route. +* http: added support for `Envoy::ScopeTrackedObject` for HTTP/1 and HTTP/2 dispatching. Crashes while inside the dispatching loop should dump debug information. Furthermore, HTTP/1 and HTTP/2 clients now dumps the originating request whose response from the upstream caused Envoy to crash. +* http: added support for :ref:`preconnecting `. Preconnecting is off by default, but recommended for clusters serving latency-sensitive traffic, especially if using HTTP/1.1. +* http: added support for stream filters to mutate the cached route set by HCM route resolution. Useful for filters in a filter chain that want to override specific methods/properties of a route. See :ref:`http route mutation ` docs for more information. +* http: added new runtime config `envoy.reloadable_features.check_unsupported_typed_per_filter_config`, the default value is true. When the value is true, envoy will reject virtual host-specific typed per filter config when the filter doesn't support it. +* http: added the ability to preserve HTTP/1 header case across the proxy. See the :ref:`header casing ` documentation for more information. +* http: change frame flood and abuse checks to the upstream HTTP/2 codec to ON by default. It can be disabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to false. +* http: hash multiple header values instead of only hash the first header value. It can be disabled by setting the `envoy.reloadable_features.hash_multiple_header_values` runtime key to false. See the :ref:`HashPolicy's Header configuration ` for more information. +* json: introduced new JSON parser (https://github.com/nlohmann/json) to replace RapidJSON. The new parser is disabled by default. To test the new RapidJSON parser, enable the runtime feature `envoy.reloadable_features.remove_legacy_json`. +* kill_request: :ref:`Kill Request ` now supports bidirection killing. +* listener: added an optional :ref:`stat_prefix `. +* loadbalancer: added the ability to specify the hash_key for a host when using a consistent hashing loadbalancer (ringhash, maglev) using the :ref:`LbEndpoint.Metadata ` e.g.: ``"envoy.lb": {"hash_key": "..."}``. +* log: added a new custom flag ``%j`` to the log pattern to print the actual message to log as JSON escaped string. +* oauth filter: added the optional parameter :ref:`resources `. Set this value to add multiple "resource" parameters in the Authorization request sent to the OAuth provider. This acts as an identifier representing the protected resources the client is requesting a token for. +* original_dst: added support for :ref:`Original Destination ` on Windows. This enables the use of Envoy as a sidecar proxy on Windows. +* overload: add support for scaling :ref:`transport connection timeouts`. This can be used to reduce the TLS handshake timeout in response to overload. +* postgres: added ability to :ref:`terminate SSL`. +* rbac: added :ref:`shadow_rules_stat_prefix ` to allow adding custom prefix to the stats emitted by shadow rules. +* route config: added :ref:`allow_post field ` for allowing POST payload as raw TCP. +* route config: added :ref:`max_direct_response_body_size_bytes ` to set maximum :ref:`direct response body ` size in bytes. If not specified the default remains 4096 bytes. +* server: added *fips_mode* to :ref:`server compilation settings ` related statistic. +* server: added :option:`--enable-core-dump` flag to enable core dumps via prctl (Linux-based systems only). +* tcp_proxy: add support for converting raw TCP streams into HTTP/1.1 CONNECT requests. See :ref:`upgrade documentation ` for details. +* tcp_proxy: added a :ref:`use_post field ` for using HTTP POST to proxy TCP streams. +* tcp_proxy: added a :ref:`headers_to_add field ` for setting additional headers to the HTTP requests for TCP proxing. +* thrift_proxy: added a :ref:`max_requests_per_connection field ` for setting maximum requests for per downstream connection. +* thrift_proxy: added per upstream metrics within the :ref:`thrift router ` for messagetype counters in request/response. +* thrift_proxy: added per upstream metrics within the :ref:`thrift router ` for request time histograms. +* tls peer certificate validation: added :ref:`SPIFFE validator ` for supporting isolated multiple trust bundles in a single listener or cluster. +* tracing: added the :ref:`pack_trace_reason ` + field as well as explicit configuration for the built-in :ref:`UuidRequestIdConfig ` + request ID implementation. See the trace context propagation :ref:`architecture overview + ` for more information. +* udp: added :ref:`downstream ` and + :ref:`upstream ` statistics for dropped datagrams. +* udp: added :ref:`downstream_socket_config ` + listener configuration to allow configuration of downstream max UDP datagram size. Also added + :ref:`upstream_socket_config ` + UDP proxy configuration to allow configuration of upstream max UDP datagram size. The defaults for + both remain 1500 bytes. +* udp: added configuration for :ref:`GRO + `. The default is disabled for + :ref:`downstream sockets ` + and enabled for :ref:`upstream sockets `. + +Deprecated +---------- + +* admin: :ref:`access_log_path ` is deprecated in favor for :ref:`access loggers `. + diff --git a/docs/root/version_history/version_history.rst b/docs/root/version_history/version_history.rst index 002778fb35f9d..0874c99b4f87f 100644 --- a/docs/root/version_history/version_history.rst +++ b/docs/root/version_history/version_history.rst @@ -7,6 +7,7 @@ Version history :titlesonly: current + v1.18.0 v1.17.2 v1.17.1 v1.17.0 diff --git a/source/common/common/interval_value.h b/source/common/common/interval_value.h index 3a058eaae4b54..e001a8a13a324 100644 --- a/source/common/common/interval_value.h +++ b/source/common/common/interval_value.h @@ -25,11 +25,10 @@ template class ClosedIntervalValue { // Returns a value that is as far from max as the original value is from min. // This guarantees that max().invert() == min() and min().invert() == max(). ClosedIntervalValue invert() const { - return ClosedIntervalValue(value_ == Interval::max_value - ? Interval::min_value - : value_ == Interval::min_value - ? Interval::max_value - : Interval::max_value - (value_ - Interval::min_value)); + return ClosedIntervalValue(value_ == Interval::max_value ? Interval::min_value + : value_ == Interval::min_value + ? Interval::max_value + : Interval::max_value - (value_ - Interval::min_value)); } // Comparisons are performed using the same operators on the underlying value diff --git a/source/common/local_reply/local_reply.cc b/source/common/local_reply/local_reply.cc index 42f8d32b0d383..753003f223b33 100644 --- a/source/common/local_reply/local_reply.cc +++ b/source/common/local_reply/local_reply.cc @@ -25,12 +25,11 @@ class BodyFormatter { BodyFormatter(const envoy::config::core::v3::SubstitutionFormatString& config, Api::Api& api) : formatter_(Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config, api)), content_type_( - !config.content_type().empty() - ? config.content_type() - : config.format_case() == - envoy::config::core::v3::SubstitutionFormatString::FormatCase::kJsonFormat - ? Http::Headers::get().ContentTypeValues.Json - : Http::Headers::get().ContentTypeValues.Text) {} + !config.content_type().empty() ? config.content_type() + : config.format_case() == + envoy::config::core::v3::SubstitutionFormatString::FormatCase::kJsonFormat + ? Http::Headers::get().ContentTypeValues.Json + : Http::Headers::get().ContentTypeValues.Text) {} void format(const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, diff --git a/test/common/network/apple_dns_impl_test.cc b/test/common/network/apple_dns_impl_test.cc index 9d9fbb534d127..7cc40baf6f193 100644 --- a/test/common/network/apple_dns_impl_test.cc +++ b/test/common/network/apple_dns_impl_test.cc @@ -350,12 +350,12 @@ TEST_F(AppleDnsImplFakeApiTest, SynchronousErrorInGetAddrInfo) { // The Query's sd ref will be deallocated. EXPECT_CALL(dns_service_, dnsServiceRefDeallocate(_)); - EXPECT_EQ(nullptr, resolver_->resolve( - "foo.com", Network::DnsLookupFamily::Auto, - [](DnsResolver::ResolutionStatus, std::list &&) -> void { - // This callback should never be executed. - FAIL(); - })); + EXPECT_EQ(nullptr, + resolver_->resolve("foo.com", Network::DnsLookupFamily::Auto, + [](DnsResolver::ResolutionStatus, std::list&&) -> void { + // This callback should never be executed. + FAIL(); + })); } TEST_F(AppleDnsImplFakeApiTest, QuerySynchronousCompletion) { @@ -428,7 +428,7 @@ TEST_F(AppleDnsImplFakeApiTest, IncorrectInterfaceIndexReturned) { resolver_->resolve( hostname, Network::DnsLookupFamily::Auto, - [](DnsResolver::ResolutionStatus, std::list &&) -> void { FAIL(); }); + [](DnsResolver::ResolutionStatus, std::list&&) -> void { FAIL(); }); } TEST_F(AppleDnsImplFakeApiTest, QueryCompletedWithError) { @@ -785,7 +785,7 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithNullAddress) { auto query = resolver_->resolve( hostname, Network::DnsLookupFamily::Auto, - [](DnsResolver::ResolutionStatus, std::list &&) -> void { FAIL(); }); + [](DnsResolver::ResolutionStatus, std::list&&) -> void { FAIL(); }); ASSERT_NE(nullptr, query); EXPECT_DEATH(reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, diff --git a/test/test_common/environment.cc b/test/test_common/environment.cc index 87f6c0a8bab15..62c8d02c011a6 100644 --- a/test/test_common/environment.cc +++ b/test/test_common/environment.cc @@ -362,9 +362,9 @@ std::string TestEnvironment::temporaryFileSubstitute(const std::string& path, out_json_string = substitute(out_json_string, version); auto name = Filesystem::fileSystemForTest().splitPathFromFilename(path).file_; - const std::string extension = absl::EndsWith(name, ".yaml") - ? ".yaml" - : absl::EndsWith(name, ".pb_text") ? ".pb_text" : ".json"; + const std::string extension = absl::EndsWith(name, ".yaml") ? ".yaml" + : absl::EndsWith(name, ".pb_text") ? ".pb_text" + : ".json"; const std::string out_json_path = TestEnvironment::temporaryPath(name) + ".with.ports" + extension; { From d362e791eb9e4efa8d87f6d878740e72dc8330ac Mon Sep 17 00:00:00 2001 From: Matt Klein Date: Thu, 15 Apr 2021 18:20:46 -0700 Subject: [PATCH 026/209] build: fix and bump to 1.18.2 (#16022) Once more unto the breach. Signed-off-by: Matt Klein --- VERSION | 2 +- docs/root/version_history/current.rst | 4 ++-- docs/root/version_history/v1.18.1.rst | 7 +++++++ docs/root/version_history/version_history.rst | 1 + test/integration/multiplexed_integration_test.cc | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 docs/root/version_history/v1.18.1.rst diff --git a/VERSION b/VERSION index ec6d649be6505..b57fc7228b623 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.18.1 +1.18.2 diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index ca0baae62a9c6..5b7d079bf56a9 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -1,7 +1,7 @@ -1.18.0 (April 15, 2021) +1.18.2 (April 15, 2021) ======================= Bug Fixes --------- -code: fixed some whitespace to make fix_format happy. +code: fixed more build issues on our path to a glorious release. diff --git a/docs/root/version_history/v1.18.1.rst b/docs/root/version_history/v1.18.1.rst new file mode 100644 index 0000000000000..5ee0e5fc97b28 --- /dev/null +++ b/docs/root/version_history/v1.18.1.rst @@ -0,0 +1,7 @@ +1.18.1 (April 15, 2021) +======================= + +Bug Fixes +--------- + +code: fixed some whitespace to make fix_format happy. diff --git a/docs/root/version_history/version_history.rst b/docs/root/version_history/version_history.rst index 0874c99b4f87f..24e9ae28e692e 100644 --- a/docs/root/version_history/version_history.rst +++ b/docs/root/version_history/version_history.rst @@ -7,6 +7,7 @@ Version history :titlesonly: current + v1.18.1 v1.18.0 v1.17.2 v1.17.1 diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index 0b9653b387f86..a3f10631a9e2c 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -1729,7 +1729,7 @@ TEST_P(Http2MetadataIntegrationTest, UpstreamSendingEmptyMetadata) { upstream_request_->encodeData(0, true); // Verifies that no metadata was received by the client. - response->waitForEndStream(); + ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ(0, response->metadataMapsDecodedCount()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.http2.metadata_empty_frames")->value()); From 22825906e35c1d61b495f7b5f2517249cc56f77d Mon Sep 17 00:00:00 2001 From: Matt Klein Date: Thu, 15 Apr 2021 20:20:24 -0700 Subject: [PATCH 027/209] release: kick off 1.19 (#16025) Signed-off-by: Matt Klein --- VERSION | 2 +- docs/root/version_history/current.rst | 23 ++++++++++++++++--- docs/root/version_history/v1.18.2.rst | 7 ++++++ docs/root/version_history/version_history.rst | 1 + 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 docs/root/version_history/v1.18.2.rst diff --git a/VERSION b/VERSION index b57fc7228b623..ff32b92aad5df 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.18.2 +1.19.0-dev diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 5b7d079bf56a9..caf01017ceedd 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -1,7 +1,24 @@ -1.18.2 (April 15, 2021) -======================= +1.19.0 (Pending) +================ + +Incompatible Behavior Changes +----------------------------- +*Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* + +Minor Behavior Changes +---------------------- +*Changes that may cause incompatibilities for some users, but should not for most* Bug Fixes --------- +*Changes expected to improve the state of the world and are unlikely to have negative effects* + +Removed Config or Runtime +------------------------- +*Normally occurs at the end of the* :ref:`deprecation period ` + +New Features +------------ -code: fixed more build issues on our path to a glorious release. +Deprecated +---------- diff --git a/docs/root/version_history/v1.18.2.rst b/docs/root/version_history/v1.18.2.rst new file mode 100644 index 0000000000000..5b7d079bf56a9 --- /dev/null +++ b/docs/root/version_history/v1.18.2.rst @@ -0,0 +1,7 @@ +1.18.2 (April 15, 2021) +======================= + +Bug Fixes +--------- + +code: fixed more build issues on our path to a glorious release. diff --git a/docs/root/version_history/version_history.rst b/docs/root/version_history/version_history.rst index 24e9ae28e692e..6696b431e9f23 100644 --- a/docs/root/version_history/version_history.rst +++ b/docs/root/version_history/version_history.rst @@ -7,6 +7,7 @@ Version history :titlesonly: current + v1.18.2 v1.18.1 v1.18.0 v1.17.2 From ad4919dd70276e4d89b011a03376fe6d20475547 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Fri, 16 Apr 2021 12:46:32 -0400 Subject: [PATCH 028/209] upstream adding QUIC-to-TCP failover (#15894) Risk Level: n/a (hidden by default) Testing: e2e tests Docs Changes: n/a Release Notes: n/a Part of #14829 --- .../http/v3/http_protocol_options.proto | 7 +++ .../http/v4alpha/http_protocol_options.proto | 7 +++ .../http/v3/http_protocol_options.proto | 7 +++ .../http/v4alpha/http_protocol_options.proto | 7 +++ source/common/http/conn_pool_grid.cc | 16 +---- source/common/quic/BUILD | 1 + .../quic/quic_transport_socket_factory.cc | 11 +++- .../quic/quic_transport_socket_factory.h | 27 ++++++-- source/common/upstream/BUILD | 1 + .../common/upstream/cluster_manager_impl.cc | 32 ++++++++-- source/common/upstream/upstream_impl.cc | 21 ++++--- .../transport_sockets/tls/ssl_socket.h | 2 + source/extensions/upstreams/http/config.cc | 4 ++ test/common/upstream/BUILD | 1 + test/common/upstream/upstream_impl_test.cc | 63 ++++++++++++++++++- test/config/utility.cc | 6 +- test/integration/base_integration_test.h | 2 + .../multiplexed_upstream_integration_test.cc | 42 +++++++++++++ .../multiplexed_upstream_integration_test.h | 7 +-- test/mocks/server/BUILD | 1 + .../transport_socket_factory_context.cc | 2 + .../server/transport_socket_factory_context.h | 4 ++ 22 files changed, 229 insertions(+), 42 deletions(-) diff --git a/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto b/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto index 00cac9d27336e..2ce22fe6c0a79 100644 --- a/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto +++ b/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto @@ -97,6 +97,13 @@ message HttpProtocolOptions { config.core.v3.Http1ProtocolOptions http_protocol_options = 1; config.core.v3.Http2ProtocolOptions http2_protocol_options = 2; + + // [#not-implemented-hide:] + // Unlike HTTP/1 and HTTP/2, HTTP/3 will not be configured unless it is + // present. If HTTP/3 is present, attempts to connect will first be made + // via HTTP/3, and if the HTTP/3 connection fails, TCP (HTTP/1 or HTTP/2 + // based on ALPN) will be used instead. + config.core.v3.Http3ProtocolOptions http3_protocol_options = 3; } // This contains options common across HTTP/1 and HTTP/2 diff --git a/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto b/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto index e3cf4476983a9..2011abc5a5a79 100644 --- a/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto +++ b/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto @@ -110,6 +110,13 @@ message HttpProtocolOptions { config.core.v4alpha.Http1ProtocolOptions http_protocol_options = 1; config.core.v4alpha.Http2ProtocolOptions http2_protocol_options = 2; + + // [#not-implemented-hide:] + // Unlike HTTP/1 and HTTP/2, HTTP/3 will not be configured unless it is + // present. If HTTP/3 is present, attempts to connect will first be made + // via HTTP/3, and if the HTTP/3 connection fails, TCP (HTTP/1 or HTTP/2 + // based on ALPN) will be used instead. + config.core.v4alpha.Http3ProtocolOptions http3_protocol_options = 3; } // This contains options common across HTTP/1 and HTTP/2 diff --git a/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto b/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto index 00cac9d27336e..2ce22fe6c0a79 100644 --- a/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto +++ b/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto @@ -97,6 +97,13 @@ message HttpProtocolOptions { config.core.v3.Http1ProtocolOptions http_protocol_options = 1; config.core.v3.Http2ProtocolOptions http2_protocol_options = 2; + + // [#not-implemented-hide:] + // Unlike HTTP/1 and HTTP/2, HTTP/3 will not be configured unless it is + // present. If HTTP/3 is present, attempts to connect will first be made + // via HTTP/3, and if the HTTP/3 connection fails, TCP (HTTP/1 or HTTP/2 + // based on ALPN) will be used instead. + config.core.v3.Http3ProtocolOptions http3_protocol_options = 3; } // This contains options common across HTTP/1 and HTTP/2 diff --git a/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto b/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto index e3cf4476983a9..2011abc5a5a79 100644 --- a/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto +++ b/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto @@ -110,6 +110,13 @@ message HttpProtocolOptions { config.core.v4alpha.Http1ProtocolOptions http_protocol_options = 1; config.core.v4alpha.Http2ProtocolOptions http2_protocol_options = 2; + + // [#not-implemented-hide:] + // Unlike HTTP/1 and HTTP/2, HTTP/3 will not be configured unless it is + // present. If HTTP/3 is present, attempts to connect will first be made + // via HTTP/3, and if the HTTP/3 connection fails, TCP (HTTP/1 or HTTP/2 + // based on ALPN) will be used instead. + config.core.v4alpha.Http3ProtocolOptions http3_protocol_options = 3; } // This contains options common across HTTP/1 and HTTP/2 diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc index e41007825e01b..5a3fdef5b5036 100644 --- a/source/common/http/conn_pool_grid.cc +++ b/source/common/http/conn_pool_grid.cc @@ -6,18 +6,6 @@ namespace Envoy { namespace Http { -// Helper function to make sure each protocol in expected_protocols is present -// in protocols (only used for an ASSERT in debug builds) -bool contains(const std::vector& protocols, - const std::vector& expected_protocols) { - for (auto protocol : expected_protocols) { - if (std::find(protocols.begin(), protocols.end(), protocol) == protocols.end()) { - return false; - } - } - return true; -} - absl::string_view describePool(const ConnectionPool::Instance& pool) { return pool.protocolDescription(); } @@ -157,10 +145,10 @@ ConnectivityGrid::ConnectivityGrid( : dispatcher_(dispatcher), random_generator_(random_generator), host_(host), priority_(priority), options_(options), transport_socket_options_(transport_socket_options), state_(state), next_attempt_duration_(next_attempt_duration), time_source_(time_source) { + // ProdClusterManagerFactory::allocateConnPool verifies the protocols are HTTP/1, HTTP/2 and + // HTTP/3. // TODO(#15649) support v6/v4, WiFi/cellular. ASSERT(connectivity_options.protocols_.size() == 3); - ASSERT(contains(connectivity_options.protocols_, - {Http::Protocol::Http11, Http::Protocol::Http2, Http::Protocol::Http3})); } ConnectivityGrid::~ConnectivityGrid() { diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index 506ee80b4294f..d0f3b48491c6b 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -371,6 +371,7 @@ envoy_cc_library( "//source/common/common:assert_lib", "//source/extensions/transport_sockets:well_known_names", "//source/extensions/transport_sockets/tls:context_config_lib", + "//source/extensions/transport_sockets/tls:ssl_socket_lib", "@envoy_api//envoy/extensions/transport_sockets/quic/v3:pkg_cc_proto", ], ) diff --git a/source/common/quic/quic_transport_socket_factory.cc b/source/common/quic/quic_transport_socket_factory.cc index 4e87a90606721..8611477d4cf27 100644 --- a/source/common/quic/quic_transport_socket_factory.cc +++ b/source/common/quic/quic_transport_socket_factory.cc @@ -1,8 +1,7 @@ #include "common/quic/quic_transport_socket_factory.h" -// #include "envoy/extensions/transport_sockets/tls/v3/tls.pb.validate.h" -#include "envoy/extensions/transport_sockets/quic/v3/quic_transport.pb.h" #include "envoy/extensions/transport_sockets/quic/v3/quic_transport.pb.validate.h" + #include "extensions/transport_sockets/tls/context_config_impl.h" namespace Envoy { @@ -34,9 +33,15 @@ QuicClientTransportSocketConfigFactory::createTransportSocketFactory( config, context.messageValidationVisitor()); auto client_config = std::make_unique( quic_transport.upstream_tls_context(), context); - return std::make_unique(std::move(client_config)); + return std::make_unique(std::move(client_config), context); } +QuicClientTransportSocketFactory::QuicClientTransportSocketFactory( + Ssl::ClientContextConfigPtr config, + Server::Configuration::TransportSocketFactoryContext& factory_context) + : fallback_factory_(std::make_unique( + std::move(config), factory_context.sslContextManager(), factory_context.scope())) {} + ProtobufTypes::MessagePtr QuicClientTransportSocketConfigFactory::createEmptyConfigProto() { return std::make_unique(); } diff --git a/source/common/quic/quic_transport_socket_factory.h b/source/common/quic/quic_transport_socket_factory.h index fdcdd557c91bb..381d77f8acb15 100644 --- a/source/common/quic/quic_transport_socket_factory.h +++ b/source/common/quic/quic_transport_socket_factory.h @@ -1,11 +1,13 @@ #pragma once +#include "envoy/extensions/transport_sockets/quic/v3/quic_transport.pb.h" #include "envoy/network/transport_socket.h" #include "envoy/server/transport_socket_config.h" #include "envoy/ssl/context_config.h" #include "common/common/assert.h" +#include "extensions/transport_sockets/tls/ssl_socket.h" #include "extensions/transport_sockets/well_known_names.h" namespace Envoy { @@ -25,6 +27,7 @@ class QuicTransportSocketFactoryBase : public Network::TransportSocketFactory { } bool implementsSecureTransport() const override { return true; } bool usesProxyProtocolOptions() const override { return false; } + bool supportsAlpn() const override { return true; } }; // TODO(danzh): when implement ProofSource, examine of it's necessary to @@ -42,13 +45,29 @@ class QuicServerTransportSocketFactory : public QuicTransportSocketFactoryBase { class QuicClientTransportSocketFactory : public QuicTransportSocketFactoryBase { public: - QuicClientTransportSocketFactory(Envoy::Ssl::ClientContextConfigPtr config) - : config_(std::move(config)) {} + QuicClientTransportSocketFactory( + Ssl::ClientContextConfigPtr config, + Server::Configuration::TransportSocketFactoryContext& factory_context); + + // As documented above for QuicTransportSocketFactoryBase, the actual HTTP/3 + // code does not create transport sockets. + // QuicClientTransportSocketFactory::createTransportSocket is called by the + // connection grid when upstream HTTP/3 fails over to TCP, and a raw SSL socket + // is needed. In this case the QuicClientTransportSocketFactory falls over to + // using the fallback factory. + Network::TransportSocketPtr + createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override { + return fallback_factory_->createTransportSocket(options); + } - const Ssl::ClientContextConfig& clientContextConfig() const { return *config_; } + // TODO(14829) make sure that clientContextConfig() is safe when secrets are updated. + const Ssl::ClientContextConfig& clientContextConfig() const { + return fallback_factory_->config(); + } private: - std::unique_ptr config_; + // The QUIC client transport socket can create TLS sockets for fallback to TCP. + std::unique_ptr fallback_factory_; }; // Base class to create above QuicTransportSocketFactory for server and client diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index b970eacd34199..efcb951029fcc 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -92,6 +92,7 @@ envoy_cc_library( "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ] + envoy_select_enable_http3([ "//source/common/http/http3:conn_pool_lib", + "//source/common/http:conn_pool_grid", ]), ) diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 35a0aefeef5ae..63eaac2820e46 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -45,6 +45,7 @@ #include "common/upstream/subset_lb.h" #ifdef ENVOY_ENABLE_QUIC +#include "common/http/conn_pool_grid.h" #include "common/http/http3/conn_pool.h" #endif @@ -59,6 +60,18 @@ void addOptionsIfNotNull(Network::Socket::OptionsSharedPtr& options, } } +// Helper function to make sure each protocol in expected_protocols is present +// in protocols (only used for an ASSERT in debug builds) +bool contains(const std::vector& protocols, + const std::vector& expected_protocols) { + for (auto protocol : expected_protocols) { + if (std::find(protocols.begin(), protocols.end(), protocol) == protocols.end()) { + return false; + } + } + return true; +} + } // namespace void ClusterManagerInitHelper::addCluster(ClusterManagerCluster& cm_cluster) { @@ -1505,14 +1518,25 @@ Http::ConnectionPool::InstancePtr ProdClusterManagerFactory::allocateConnPool( const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options, TimeSource& source, ClusterConnectivityState& state) { - if (protocols.size() == 2) { - ASSERT((protocols[0] == Http::Protocol::Http2 && protocols[1] == Http::Protocol::Http11) || - (protocols[1] == Http::Protocol::Http2 && protocols[0] == Http::Protocol::Http11)); + if (protocols.size() == 3 && runtime_.snapshot().featureEnabled("upstream.use_http3", 100)) { + ASSERT(contains(protocols, + {Http::Protocol::Http11, Http::Protocol::Http2, Http::Protocol::Http3})); +#ifdef ENVOY_ENABLE_QUIC + Envoy::Http::ConnectivityGrid::ConnectivityOptions coptions{protocols}; + return std::make_unique( + dispatcher, api_.randomGenerator(), host, priority, options, transport_socket_options, + state, source, std::chrono::milliseconds(300), coptions); +#else + // Should be blocked by configuration checking at an earlier point. + NOT_REACHED_GCOVR_EXCL_LINE; +#endif + } + if (protocols.size() >= 2) { + ASSERT(contains(protocols, {Http::Protocol::Http11, Http::Protocol::Http2})); return std::make_unique(dispatcher, api_.randomGenerator(), host, priority, options, transport_socket_options, state); } - if (protocols.size() == 1 && protocols[0] == Http::Protocol::Http2 && runtime_.snapshot().featureEnabled("upstream.use_http2", 100)) { return Http::Http2::allocateConnPool(dispatcher, api_.randomGenerator(), host, priority, diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 5d70b2f90fabf..070063f768589 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -925,16 +925,21 @@ ClusterInfoImpl::upstreamHttpProtocol(absl::optional downstream_ if (downstream_protocol.has_value() && features_ & Upstream::ClusterInfo::Features::USE_DOWNSTREAM_PROTOCOL) { return {downstream_protocol.value()}; - } else if (features_ & Upstream::ClusterInfo::Features::USE_ALPN) { - ASSERT(!(features_ & Upstream::ClusterInfo::Features::HTTP3)); - return {Http::Protocol::Http2, Http::Protocol::Http11}; - } else { - if (features_ & Upstream::ClusterInfo::Features::HTTP3) { - return {Http::Protocol::Http3}; + } + + if (features_ & Upstream::ClusterInfo::Features::USE_ALPN) { + if (!(features_ & Upstream::ClusterInfo::Features::HTTP3)) { + return {Http::Protocol::Http2, Http::Protocol::Http11}; } - return {(features_ & Upstream::ClusterInfo::Features::HTTP2) ? Http::Protocol::Http2 - : Http::Protocol::Http11}; + return {Http::Protocol::Http3, Http::Protocol::Http2, Http::Protocol::Http11}; } + + if (features_ & Upstream::ClusterInfo::Features::HTTP3) { + return {Http::Protocol::Http3}; + } + + return {(features_ & Upstream::ClusterInfo::Features::HTTP2) ? Http::Protocol::Http2 + : Http::Protocol::Http11}; } ClusterImplBase::ClusterImplBase( diff --git a/source/extensions/transport_sockets/tls/ssl_socket.h b/source/extensions/transport_sockets/tls/ssl_socket.h index 534dcc29fc472..5b9239d266053 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.h +++ b/source/extensions/transport_sockets/tls/ssl_socket.h @@ -116,6 +116,8 @@ class ClientSslSocketFactory : public Network::TransportSocketFactory, // Secret::SecretCallbacks void onAddOrUpdateSecret() override; + const Ssl::ClientContextConfig& config() const { return *config_; } + private: Envoy::Ssl::ContextManager& manager_; Stats::Scope& stats_scope_; diff --git a/source/extensions/upstreams/http/config.cc b/source/extensions/upstreams/http/config.cc index 9822271413b30..5cb2acc4187f3 100644 --- a/source/extensions/upstreams/http/config.cc +++ b/source/extensions/upstreams/http/config.cc @@ -48,6 +48,9 @@ getHttp3Options(const envoy::extensions::upstreams::http::v3::HttpProtocolOption options.use_downstream_protocol_config().has_http3_protocol_options()) { return options.use_downstream_protocol_config().http3_protocol_options(); } + if (options.has_auto_config()) { + return options.auto_config().http3_protocol_options(); + } return options.explicit_http_config().http3_protocol_options(); } @@ -107,6 +110,7 @@ ProtocolOptionsConfigImpl::ProtocolOptionsConfigImpl( if (options.has_auto_config()) { use_http2_ = true; use_alpn_ = true; + use_http3_ = options.auto_config().has_http3_protocol_options(); } } diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index e4b4b0948812f..c4964177d10fb 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -608,6 +608,7 @@ envoy_cc_test( "//source/common/upstream:static_cluster_lib", "//source/common/upstream:strict_dns_cluster_lib", "//source/extensions/transport_sockets/raw_buffer:config", + "//source/extensions/transport_sockets/tls:config", "//source/extensions/upstreams/http:config", "//source/server:transport_socket_config_lib", "//test/common/stats:stat_test_utility_lib", diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index fdcf38fda1e7a..a0ce86ae68ac1 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -62,7 +62,7 @@ class UpstreamImplTestBase { UpstreamImplTestBase() : api_(Api::createApiForTest(stats_, random_)) {} NiceMock admin_; - Ssl::MockContextManager ssl_context_manager_; + NiceMock ssl_context_manager_; NiceMock cm_; NiceMock local_info_; NiceMock dispatcher_; @@ -2218,7 +2218,7 @@ class ClusterInfoImplTest : public testing::Test { }; Stats::TestUtil::TestStore stats_; - Ssl::MockContextManager ssl_context_manager_; + NiceMock ssl_context_manager_; std::shared_ptr dns_resolver_{new NiceMock()}; NiceMock dispatcher_; NiceMock runtime_; @@ -3165,7 +3165,6 @@ TEST_F(ClusterInfoImplTest, Http3) { - exact: 127.0.0.1 )EOF", Network::Address::IpVersion::v4); - auto cluster1 = makeCluster(yaml); ASSERT_TRUE(cluster1->info()->idleTimeout().has_value()); EXPECT_EQ(std::chrono::hours(1), cluster1->info()->idleTimeout().value()); @@ -3250,6 +3249,64 @@ TEST_F(ClusterInfoImplTest, Http3BadConfig) { EXPECT_THROW_WITH_REGEX(makeCluster(yaml), EnvoyException, "HTTP3 requires a QuicUpstreamTransport transport socket: name.*"); } + +TEST_F(ClusterInfoImplTest, Http3Auto) { + const std::string yaml = TestEnvironment::substitute(R"EOF( + name: name + connect_timeout: 0.25s + type: STRICT_DNS + lb_policy: MAGLEV + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: foo.bar.com + port_value: 443 + transport_socket: + name: envoy.transport_sockets.quic + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicUpstreamTransport + upstream_tls_context: + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem" + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + match_subject_alt_names: + - exact: localhost + - exact: 127.0.0.1 + )EOF", + Network::Address::IpVersion::v4); + + auto cluster1 = makeCluster(yaml); + ASSERT_TRUE(cluster1->info()->idleTimeout().has_value()); + EXPECT_EQ(std::chrono::hours(1), cluster1->info()->idleTimeout().value()); + + const std::string auto_http3 = R"EOF( + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + auto_config: + http3_protocol_options: + quic_protocol_options: + max_concurrent_streams: 2 + common_http_protocol_options: + idle_timeout: 1s + )EOF"; + + auto auto_h3 = makeCluster(yaml + auto_http3); + EXPECT_EQ(Http::Protocol::Http3, + auto_h3->info()->upstreamHttpProtocol({Http::Protocol::Http10})[0]); + EXPECT_EQ( + auto_h3->info()->http3Options().quic_protocol_options().max_concurrent_streams().value(), 2); +} + #else TEST_F(ClusterInfoImplTest, Http3BadConfig) { const std::string yaml = TestEnvironment::substitute(R"EOF( diff --git a/test/config/utility.cc b/test/config/utility.cc index 7dfbbceba7765..8da3ea9c1bffc 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -713,6 +713,10 @@ void ConfigHelper::configureUpstreamTls(bool use_alpn, bool http3) { new_protocol_options.mutable_auto_config()->mutable_http2_protocol_options()->MergeFrom( old_protocol_options.explicit_http_config().http2_protocol_options()); } + if (http3 || old_protocol_options.explicit_http_config().has_http3_protocol_options()) { + new_protocol_options.mutable_auto_config()->mutable_http3_protocol_options()->MergeFrom( + old_protocol_options.explicit_http_config().http3_protocol_options()); + } (*cluster->mutable_typed_extension_protocol_options()) ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] .PackFrom(new_protocol_options); @@ -725,9 +729,9 @@ void ConfigHelper::configureUpstreamTls(bool use_alpn, bool http3) { // The test certs are for *.lyft.com, so make sure SNI matches. tls_context.set_sni("foo.lyft.com"); if (http3) { - cluster->mutable_transport_socket()->set_name("envoy.transport_sockets.quic"); envoy::extensions::transport_sockets::quic::v3::QuicUpstreamTransport quic_context; quic_context.mutable_upstream_tls_context()->CopyFrom(tls_context); + cluster->mutable_transport_socket()->set_name("envoy.transport_sockets.quic"); cluster->mutable_transport_socket()->mutable_typed_config()->PackFrom(quic_context); } else { cluster->mutable_transport_socket()->set_name("envoy.transport_sockets.tls"); diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index cc3008b249f27..1baab383b20e8 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -469,6 +469,8 @@ class BaseIntegrationTest : protected Logger::Loggable { bool v2_bootstrap_{false}; private: + friend class MixedUpstreamIntegrationTest; + // Configuration for the fake upstream. FakeUpstreamConfig upstream_config_{time_system_}; // True if initialized() has been called. diff --git a/test/integration/multiplexed_upstream_integration_test.cc b/test/integration/multiplexed_upstream_integration_test.cc index da0b16223b410..de2ac3cde8817 100644 --- a/test/integration/multiplexed_upstream_integration_test.cc +++ b/test/integration/multiplexed_upstream_integration_test.cc @@ -262,6 +262,7 @@ TEST_P(Http2UpstreamIntegrationTest, SimultaneousRequestAlpn) { } TEST_P(Http2UpstreamIntegrationTest, LargeSimultaneousRequestWithBufferLimitsAlpn) { + EXCLUDE_UPSTREAM_HTTP3; // No H3 support yet. use_alpn_ = true; config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream. simultaneousRequest(1024 * 20, 1024 * 14 + 2, 1024 * 10 + 5, 1024 * 16); @@ -619,4 +620,45 @@ TEST_P(Http2UpstreamIntegrationTest, UpstreamGoaway) { cleanupUpstreamAndDownstream(); } +#ifdef ENVOY_ENABLE_QUIC + +class MixedUpstreamIntegrationTest : public Http2UpstreamIntegrationTest { +protected: + void initialize() override { + use_alpn_ = true; + Http2UpstreamIntegrationTest::initialize(); + } + void createUpstreams() override { + ASSERT_EQ(upstreamProtocol(), FakeHttpConnection::Type::HTTP3); + if (use_http2_) { + // Generally we always want to set these fields via accessors, which + // changes both the upstreams and Envoy's configuration at the same time. + // In this particular case, we want to change the upstreams without + // touching config, so edit the raw members directly. + upstream_config_.udp_fake_upstream_ = absl::nullopt; + upstream_config_.upstream_protocol_ = FakeHttpConnection::Type::HTTP2; + } + Http2UpstreamIntegrationTest::createUpstreams(); + upstream_config_.upstream_protocol_ = FakeHttpConnection::Type::HTTP3; + } + + bool use_http2_{false}; +}; + +TEST_P(MixedUpstreamIntegrationTest, SimultaneousRequestAutoWithHttp3) { + testRouterRequestAndResponseWithBody(0, 0, false); +} + +TEST_P(MixedUpstreamIntegrationTest, SimultaneousRequestAutoWithHttp2) { + use_http2_ = true; + testRouterRequestAndResponseWithBody(0, 0, false); +} + +INSTANTIATE_TEST_SUITE_P(Protocols, MixedUpstreamIntegrationTest, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( + {Http::CodecClient::Type::HTTP2}, {FakeHttpConnection::Type::HTTP3})), + HttpProtocolIntegrationTest::protocolTestParamsToString); + +#endif + } // namespace Envoy diff --git a/test/integration/multiplexed_upstream_integration_test.h b/test/integration/multiplexed_upstream_integration_test.h index 09d51ecf5fcf8..45e35a83b42e6 100644 --- a/test/integration/multiplexed_upstream_integration_test.h +++ b/test/integration/multiplexed_upstream_integration_test.h @@ -7,16 +7,13 @@ namespace Envoy { class Http2UpstreamIntegrationTest : public HttpProtocolIntegrationTest { public: - void SetUp() override { - HttpProtocolIntegrationTest::SetUp(); - + void initialize() override { upstream_tls_ = true; config_helper_.configureUpstreamTls(use_alpn_, upstreamProtocol() == FakeHttpConnection::Type::HTTP3); + HttpProtocolIntegrationTest::initialize(); } - void initialize() override { HttpIntegrationTest::initialize(); } - void bidirectionalStreaming(uint32_t bytes); void simultaneousRequest(uint32_t request1_bytes, uint32_t request2_bytes, uint32_t response1_bytes, uint32_t response2_bytes); diff --git a/test/mocks/server/BUILD b/test/mocks/server/BUILD index 1ab093a3b698d..7f4408de326d1 100644 --- a/test/mocks/server/BUILD +++ b/test/mocks/server/BUILD @@ -242,6 +242,7 @@ envoy_cc_mock( "//source/common/secret:secret_manager_impl_lib", "//test/mocks/api:api_mocks", "//test/mocks/server:config_tracker_mocks", + "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:cluster_manager_mocks", ], ) diff --git a/test/mocks/server/transport_socket_factory_context.cc b/test/mocks/server/transport_socket_factory_context.cc index 0e4e50231a7d7..88ea41bd20fab 100644 --- a/test/mocks/server/transport_socket_factory_context.cc +++ b/test/mocks/server/transport_socket_factory_context.cc @@ -17,6 +17,8 @@ MockTransportSocketFactoryContext::MockTransportSocketFactoryContext() ON_CALL(*this, api()).WillByDefault(ReturnRef(api_)); ON_CALL(*this, messageValidationVisitor()) .WillByDefault(ReturnRef(ProtobufMessage::getStrictValidationVisitor())); + ON_CALL(*this, sslContextManager()).WillByDefault(ReturnRef(context_manager_)); + ON_CALL(*this, scope()).WillByDefault(ReturnRef(store_)); } MockTransportSocketFactoryContext::~MockTransportSocketFactoryContext() = default; diff --git a/test/mocks/server/transport_socket_factory_context.h b/test/mocks/server/transport_socket_factory_context.h index 2e346518cf935..f14bdd0ac9fe1 100644 --- a/test/mocks/server/transport_socket_factory_context.h +++ b/test/mocks/server/transport_socket_factory_context.h @@ -5,6 +5,8 @@ #include "common/secret/secret_manager_impl.h" #include "test/mocks/api/mocks.h" +#include "test/mocks/ssl/mocks.h" +#include "test/mocks/stats/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "config_tracker.h" @@ -38,6 +40,8 @@ class MockTransportSocketFactoryContext : public TransportSocketFactoryContext { testing::NiceMock cluster_manager_; testing::NiceMock api_; testing::NiceMock config_tracker_; + testing::NiceMock context_manager_; + testing::NiceMock store_; std::unique_ptr secret_manager_; }; } // namespace Configuration From dbc899865def1e021e7fda12d00ec2d00ac73e99 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 16 Apr 2021 11:46:29 -0700 Subject: [PATCH 029/209] tools: default to clang-format-11 in check_format.py (#16024) Signed-off-by: Florin Coras --- tools/code_format/check_format.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index 442f56ff0c059..dc3761c05df70 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -143,7 +143,7 @@ "./api/bazel/envoy_http_archive.bzl", ) -CLANG_FORMAT_PATH = os.getenv("CLANG_FORMAT", "clang-format-10") +CLANG_FORMAT_PATH = os.getenv("CLANG_FORMAT", "clang-format-11") BUILDIFIER_PATH = paths.get_buildifier() BUILDOZER_PATH = paths.get_buildozer() ENVOY_BUILD_FIXER_PATH = os.path.join( From 639ca8bbf152aace5d006782f64f6fd417034948 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Apr 2021 11:46:56 -0700 Subject: [PATCH 030/209] build(deps): bump sphinx from 3.5.3 to 3.5.4 in /docs (#15918) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.5.3 to 3.5.4. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/commits/v3.5.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index ed7677bdb1c86..8e2a18417d71d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -86,9 +86,9 @@ smmap==4.0.0 \ snowballstemmer==2.1.0 \ --hash=sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2 \ --hash=sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914 -Sphinx==3.5.3 \ - --hash=sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d \ - --hash=sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc +Sphinx==3.5.4 \ + --hash=sha256:2320d4e994a191f4b4be27da514e46b3d6b420f2ff895d064f52415d342461e8 \ + --hash=sha256:19010b7b9fa0dc7756a6e105b2aacd3a80f798af3c25c273be64d7beeb482cb1 sphinx-copybutton==0.3.1 \ --hash=sha256:5125c718e763596e6e52d92e15ee0d6f4800ad3817939be6dee51218870b3e3d \ --hash=sha256:0e0461df394515284e3907e3f418a0c60ef6ab6c9a27a800c8552772d0a402a2 From d4e28efb56a79703faffc18d6af1a578770af2f1 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Fri, 16 Apr 2021 18:47:25 +0000 Subject: [PATCH 031/209] Fix ASAN error with uninitialized core_dump_enabled_ in test constructor (#15505) Signed-off-by: Yan Avlasov --- source/server/options_impl.cc | 12 ++-------- source/server/options_impl.h | 42 +++++++++++++++++------------------ 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/source/server/options_impl.cc b/source/server/options_impl.cc index c3dff6a795c89..f1c2ce979a776 100644 --- a/source/server/options_impl.cc +++ b/source/server/options_impl.cc @@ -411,16 +411,8 @@ Server::CommandLineOptionsPtr OptionsImpl::toCommandLineOptions() const { OptionsImpl::OptionsImpl(const std::string& service_cluster, const std::string& service_node, const std::string& service_zone, spdlog::level::level_enum log_level) - : base_id_(0u), use_dynamic_base_id_(false), base_id_path_(""), concurrency_(1u), - config_path_(""), config_yaml_(""), - local_address_ip_version_(Network::Address::IpVersion::v4), log_level_(log_level), - log_format_(Logger::Logger::DEFAULT_LOG_FORMAT), log_format_escaped_(false), - restart_epoch_(0u), service_cluster_(service_cluster), service_node_(service_node), - service_zone_(service_zone), file_flush_interval_msec_(10000), drain_time_(600), - parent_shutdown_time_(900), drain_strategy_(Server::DrainStrategy::Gradual), - mode_(Server::Mode::Serve), hot_restart_disabled_(false), signal_handling_enabled_(true), - mutex_tracing_enabled_(false), cpuset_threads_(false), socket_path_("@envoy_domain_socket"), - socket_mode_(0) {} + : log_level_(log_level), service_cluster_(service_cluster), service_node_(service_node), + service_zone_(service_zone) {} void OptionsImpl::disableExtensions(const std::vector& names) { for (const auto& name : names) { diff --git a/source/server/options_impl.h b/source/server/options_impl.h index 370480c755f75..cd5ac7232e65c 100644 --- a/source/server/options_impl.h +++ b/source/server/options_impl.h @@ -169,10 +169,10 @@ class OptionsImpl : public Server::Options, protected Logger::Loggable bootstrap_version_; @@ -181,35 +181,35 @@ class OptionsImpl : public Server::Options, protected Logger::Loggable> component_log_levels_; std::string component_log_level_str_; - std::string log_format_; - bool log_format_escaped_; + std::string log_format_{Logger::Logger::DEFAULT_LOG_FORMAT}; + bool log_format_escaped_{false}; std::string log_path_; - uint64_t restart_epoch_; + uint64_t restart_epoch_{0}; std::string service_cluster_; std::string service_node_; std::string service_zone_; - std::chrono::milliseconds file_flush_interval_msec_; - std::chrono::seconds drain_time_; - std::chrono::seconds parent_shutdown_time_; - Server::DrainStrategy drain_strategy_; - Server::Mode mode_; - bool hot_restart_disabled_; - bool signal_handling_enabled_; - bool mutex_tracing_enabled_; - bool core_dump_enabled_; - bool cpuset_threads_; + std::chrono::milliseconds file_flush_interval_msec_{10000}; + std::chrono::seconds drain_time_{600}; + std::chrono::seconds parent_shutdown_time_{900}; + Server::DrainStrategy drain_strategy_{Server::DrainStrategy::Gradual}; + Server::Mode mode_{Server::Mode::Serve}; + bool hot_restart_disabled_{false}; + bool signal_handling_enabled_{true}; + bool mutex_tracing_enabled_{false}; + bool core_dump_enabled_{false}; + bool cpuset_threads_{false}; std::vector disabled_extensions_; - uint32_t count_; + uint32_t count_{0}; // Initialization added here to avoid integration_admin_test failure caused by uninitialized // enable_fine_grain_logging_. bool enable_fine_grain_logging_ = false; - std::string socket_path_; - mode_t socket_mode_; + std::string socket_path_{"@envoy_domain_socket"}; + mode_t socket_mode_{0}; }; /** From 5e886ce6b582cdd638f320940bac0bfb17cca4fa Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Fri, 16 Apr 2021 14:48:06 -0400 Subject: [PATCH 032/209] quiche: tracking down invalid readDisable (#15998) Signed-off-by: Alyssa Wilk --- source/common/quic/quic_filter_manager_connection_impl.h | 8 ++++---- test/integration/fake_upstream.cc | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/source/common/quic/quic_filter_manager_connection_impl.h b/source/common/quic/quic_filter_manager_connection_impl.h index e897042b6de79..a1532222ddb53 100644 --- a/source/common/quic/quic_filter_manager_connection_impl.h +++ b/source/common/quic/quic_filter_manager_connection_impl.h @@ -50,10 +50,10 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, void noDelay(bool /*enable*/) override { // No-op. TCP_NODELAY doesn't apply to UDP. } - // TODO(#14829) both readDisable and detectEarlyCloseWhenReadDisabled are used for upstream QUIC - // and needs to be hooked up before it is production-safe. - void readDisable(bool /*disable*/) override {} - void detectEarlyCloseWhenReadDisabled(bool /*value*/) override {} + // Neither readDisable nor detectEarlyCloseWhenReadDisabled are supported for QUIC. + // Crash in debug mode if they are called. + void readDisable(bool /*disable*/) override { ASSERT(false); } + void detectEarlyCloseWhenReadDisabled(bool /*value*/) override { ASSERT(false); } bool readEnabled() const override { return true; } const Network::SocketAddressSetter& addressProvider() const override { return quic_connection_->connectionSocket()->addressProvider(); diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index 7c47e739c3c07..f0a67fe7a6cb7 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -552,7 +552,7 @@ void FakeUpstream::cleanUp() { bool FakeUpstream::createNetworkFilterChain(Network::Connection& connection, const std::vector&) { absl::MutexLock lock(&lock_); - if (read_disable_on_new_connection_) { + if (read_disable_on_new_connection_ && http_type_ != FakeHttpConnection::Type::HTTP3) { // Disable early close detection to avoid closing the network connection before full // initialization is complete. connection.detectEarlyCloseWhenReadDisabled(false); @@ -709,7 +709,8 @@ SharedConnectionWrapper& FakeUpstream::consumeConnection() { connection_wrapper->connection().dispatcher().isThreadSafe()); connection_wrapper->setParented(); connection_wrapper->moveBetweenLists(new_connections_, consumed_connections_); - if (read_disable_on_new_connection_ && connection_wrapper->connected()) { + if (read_disable_on_new_connection_ && connection_wrapper->connected() && + http_type_ != FakeHttpConnection::Type::HTTP3) { // Re-enable read and early close detection. auto& connection = connection_wrapper->connection(); connection.detectEarlyCloseWhenReadDisabled(true); From 2500a45fd97171c05fe0eff5b2814049719b2070 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Apr 2021 11:49:57 -0700 Subject: [PATCH 033/209] build(deps): bump flake8 from 3.9.0 to 3.9.1 in /tools/code_format (#16030) Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.9.0 to 3.9.1. - [Release notes](https://gitlab.com/pycqa/flake8/tags) - [Commits](https://gitlab.com/pycqa/flake8/compare/3.9.0...3.9.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/code_format/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/code_format/requirements.txt b/tools/code_format/requirements.txt index fca427f852a1b..e62476d2074d8 100644 --- a/tools/code_format/requirements.txt +++ b/tools/code_format/requirements.txt @@ -1,6 +1,6 @@ -flake8==3.9.0 \ - --hash=sha256:12d05ab02614b6aee8df7c36b97d1a3b2372761222b19b58621355e82acddcff \ - --hash=sha256:78873e372b12b093da7b5e5ed302e8ad9e988b38b063b61ad937f26ca58fc5f0 +flake8==3.9.1 \ + --hash=sha256:3b9f848952dddccf635be78098ca75010f073bfe14d2c6bda867154bea728d2a \ + --hash=sha256:1aa8990be1e689d96c745c5682b687ea49f2e05a443aff1f8251092b0014e378 importlib-metadata==3.10.0 \ --hash=sha256:d2d46ef77ffc85cbf7dac7e81dd663fde71c45326131bea8033b9bad42268ebe \ --hash=sha256:c9db46394197244adf2f0b08ec5bc3cf16757e9590b02af1fca085c16c0d600a From fbfeb994be07459b2a437878fced89754b9ff75a Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 19 Apr 2021 14:38:09 +0100 Subject: [PATCH 034/209] python: Add main test fixture and consolidate checker/runner mains (#16061) * python: Add main test fixture and consolidate checker/runner mains Signed-off-by: Ryan Northey --- tools/base/checker.py | 2 +- tools/base/tests/test_checker.py | 4 ++-- tools/code_format/python_check.py | 2 +- tools/code_format/tests/test_python_check.py | 18 ++++-------------- tools/testing/plugin.py | 15 +++++++++++++++ tools/testing/tests/test_python_coverage.py | 18 ++++-------------- tools/testing/tests/test_python_pytest.py | 18 ++++-------------- 7 files changed, 31 insertions(+), 46 deletions(-) diff --git a/tools/base/checker.py b/tools/base/checker.py index 4bdef7a5632bd..6dc87f8d99ada 100644 --- a/tools/base/checker.py +++ b/tools/base/checker.py @@ -195,7 +195,7 @@ def on_checks_complete(self) -> int: self.summary.print_summary() return 1 if self.has_failed else 0 - def run_checks(self) -> int: + def run(self) -> int: """Run all configured checks and return the sum of their error counts""" checks = self.get_checks() self.on_checks_begin() diff --git a/tools/base/tests/test_checker.py b/tools/base/tests/test_checker.py index 6f3d2e63e787d..c20d68cfd300d 100644 --- a/tools/base/tests/test_checker.py +++ b/tools/base/tests/test_checker.py @@ -398,7 +398,7 @@ def test_checker_on_checks_complete(patches, failed, show_summary): assert not m_summary.return_value.print_summary.called -def test_checker_run_checks(patches): +def test_checker_run(patches): checker = DummyCheckerWithChecks("path1", "path2", "path3") patched = patches( "Checker.get_checks", @@ -410,7 +410,7 @@ def test_checker_run_checks(patches): with patched as (m_get, m_begin, m_complete, m_log, m_name): m_get.return_value = ("check1", "check2") - assert checker.run_checks() == m_complete.return_value + assert checker.run() == m_complete.return_value assert ( list(m_get.call_args) diff --git a/tools/code_format/python_check.py b/tools/code_format/python_check.py index d84ab1a42eeb7..3556d226b8889 100755 --- a/tools/code_format/python_check.py +++ b/tools/code_format/python_check.py @@ -128,7 +128,7 @@ def _strip_lines(self, lines) -> list: def main(*args: list) -> None: - return PythonChecker(*args).run_checks() + return PythonChecker(*args).run() if __name__ == "__main__": diff --git a/tools/code_format/tests/test_python_check.py b/tools/code_format/tests/test_python_check.py index b74b893f7d8f0..4bd8c9463ae46 100644 --- a/tools/code_format/tests/test_python_check.py +++ b/tools/code_format/tests/test_python_check.py @@ -376,17 +376,7 @@ def test_python_strip_line(line): == line[7:] if line.startswith(f"REMOVE/") else line) -def test_python_checker_main(): - class_mock = patch("tools.code_format.python_check.PythonChecker") - - with class_mock as m_class: - assert ( - python_check.main("arg0", "arg1", "arg2") - == m_class.return_value.run_checks.return_value) - - assert ( - list(m_class.call_args) - == [('arg0', 'arg1', 'arg2'), {}]) - assert ( - list(m_class.return_value.run_checks.call_args) - == [(), {}]) +def test_python_checker_main(command_main): + command_main( + python_check.main, + "tools.code_format.python_check.PythonChecker") diff --git a/tools/testing/plugin.py b/tools/testing/plugin.py index af9f487fb8f18..49882722df637 100644 --- a/tools/testing/plugin.py +++ b/tools/testing/plugin.py @@ -39,3 +39,18 @@ def _patches(*args, prefix: str = "") -> ContextManager: @pytest.fixture def patches(): return _patches + + +def _command_main(main, handler): + class_mock = patch(handler) + + with class_mock as m_class: + assert (main("arg0", "arg1", "arg2") == m_class.return_value.run.return_value) + + assert (list(m_class.call_args) == [('arg0', 'arg1', 'arg2'), {}]) + assert (list(m_class.return_value.run.call_args) == [(), {}]) + + +@pytest.fixture +def command_main(): + return _command_main diff --git a/tools/testing/tests/test_python_coverage.py b/tools/testing/tests/test_python_coverage.py index 26cc3687a42e2..fe3edbf760ba8 100644 --- a/tools/testing/tests/test_python_coverage.py +++ b/tools/testing/tests/test_python_coverage.py @@ -80,17 +80,7 @@ def test_coverage_run(patches, cov_data): == [(m_cov_args.return_value,), {}]) -def test_coverage_main(): - class_mock = patch("tools.testing.python_coverage.CoverageRunner") - - with class_mock as m_class: - assert ( - python_coverage.main("arg0", "arg1", "arg2") - == m_class.return_value.run.return_value) - - assert ( - list(m_class.call_args) - == [('arg0', 'arg1', 'arg2'), {}]) - assert ( - list(m_class.return_value.run.call_args) - == [(), {}]) +def test_coverage_main(command_main): + command_main( + python_coverage.main, + "tools.testing.python_coverage.CoverageRunner") diff --git a/tools/testing/tests/test_python_pytest.py b/tools/testing/tests/test_python_pytest.py index e822d4ed20cd8..a25464c9c3564 100644 --- a/tools/testing/tests/test_python_pytest.py +++ b/tools/testing/tests/test_python_pytest.py @@ -77,17 +77,7 @@ def test_pytest_run(patches, cov_data): == [(m_py_args.return_value,), {}]) -def test_pytest_main(): - class_mock = patch("tools.testing.python_pytest.PytestRunner") - - with class_mock as m_class: - assert ( - python_pytest.main("arg0", "arg1", "arg2") - == m_class.return_value.run.return_value) - - assert ( - list(m_class.call_args) - == [('arg0', 'arg1', 'arg2'), {}]) - assert ( - list(m_class.return_value.run.call_args) - == [(), {}]) +def test_pytest_main(command_main): + command_main( + python_pytest.main, + "tools.testing.python_pytest.PytestRunner") From 751be72c2eb967ff03759b6e50dde106c8cf5c84 Mon Sep 17 00:00:00 2001 From: Taylor Barrella Date: Mon, 19 Apr 2021 07:17:46 -0700 Subject: [PATCH 035/209] http: split strict_1xx_and_204_response_headers into two settings (#15880) Signed-off-by: Taylor Barrella --- docs/root/version_history/current.rst | 6 ++++++ source/common/http/http1/codec_impl.cc | 18 +++++++++++------- source/common/http/http1/codec_impl.h | 5 +++-- source/common/runtime/runtime_features.cc | 3 ++- test/common/http/http1/codec_impl_test.cc | 8 ++++---- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index caf01017ceedd..e22f5520065a2 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -9,6 +9,12 @@ Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* +* http: replaced setting `envoy.reloadable_features.strict_1xx_and_204_response_headers` with settings + `envoy.reloadable_features.require_strict_1xx_and_204_response_headers` + (require upstream 1xx or 204 responses to not have Transfer-Encoding or non-zero Content-Length headers) and + `envoy.reloadable_features.send_strict_1xx_and_204_response_headers` + (do not send 1xx or 204 responses with these headers). Both are true by default. + Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index 3b245d4f8712e..ae4dc2a8a39a9 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -195,9 +195,10 @@ void StreamEncoderImpl::encodeHeadersBase(const RequestOrResponseHeaderMap& head } else if (connection_.protocol() == Protocol::Http10) { chunk_encoding_ = false; } else if (status && (*status < 200 || *status == 204) && - connection_.strict1xxAnd204Headers()) { - // TODO(zuercher): when the "envoy.reloadable_features.strict_1xx_and_204_response_headers" - // feature flag is removed, this block can be coalesced with the 100 Continue logic above. + connection_.sendStrict1xxAnd204Headers()) { + // TODO(zuercher): when the + // "envoy.reloadable_features.send_strict_1xx_and_204_response_headers" feature flag is + // removed, this block can be coalesced with the 100 Continue logic above. // For 1xx and 204 responses, do not send the chunked encoding header or enable chunked // encoding: https://tools.ietf.org/html/rfc7230#section-3.3.1 @@ -458,8 +459,10 @@ ConnectionImpl::ConnectionImpl(Network::Connection& connection, CodecStats& stat encode_only_header_key_formatter_(encodeOnlyFormatterFromSettings(settings)), processing_trailers_(false), handling_upgrade_(false), reset_stream_called_(false), deferred_end_stream_headers_(false), - strict_1xx_and_204_headers_(Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.strict_1xx_and_204_response_headers")), + require_strict_1xx_and_204_headers_(Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.require_strict_1xx_and_204_response_headers")), + send_strict_1xx_and_204_headers_(Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.send_strict_1xx_and_204_response_headers")), dispatching_(false), output_buffer_(connection.dispatcher().getWatermarkFactory().create( [&]() -> void { this->onBelowLowWatermark(); }, [&]() -> void { this->onAboveHighWatermark(); }, @@ -853,7 +856,8 @@ void ConnectionImpl::dumpState(std::ostream& os, int indent_level) const { os << spaces << "Http1::ConnectionImpl " << this << DUMP_MEMBER(dispatching_) << DUMP_MEMBER(dispatching_slice_already_drained_) << DUMP_MEMBER(reset_stream_called_) << DUMP_MEMBER(handling_upgrade_) << DUMP_MEMBER(deferred_end_stream_headers_) - << DUMP_MEMBER(strict_1xx_and_204_headers_) << DUMP_MEMBER(processing_trailers_) + << DUMP_MEMBER(require_strict_1xx_and_204_headers_) + << DUMP_MEMBER(send_strict_1xx_and_204_headers_) << DUMP_MEMBER(processing_trailers_) << DUMP_MEMBER(buffered_body_.length()); // Dump header parsing state, and any progress on headers. @@ -1279,7 +1283,7 @@ Envoy::StatusOr ClientConnectionImpl::onHeadersCompleteBase() { handling_upgrade_ = true; } - if (strict_1xx_and_204_headers_ && + if (require_strict_1xx_and_204_headers_ && (parser_->statusCode() < 200 || parser_->statusCode() == 204)) { if (headers->TransferEncoding()) { RETURN_IF_ERROR( diff --git a/source/common/http/http1/codec_impl.h b/source/common/http/http1/codec_impl.h index f4c5e7d46f8e9..031d972a5a08b 100644 --- a/source/common/http/http1/codec_impl.h +++ b/source/common/http/http1/codec_impl.h @@ -229,7 +229,7 @@ class ConnectionImpl : public virtual Connection, void onUnderlyingConnectionAboveWriteBufferHighWatermark() override { onAboveHighWatermark(); } void onUnderlyingConnectionBelowWriteBufferLowWatermark() override { onBelowLowWatermark(); } - bool strict1xxAnd204Headers() { return strict_1xx_and_204_headers_; } + bool sendStrict1xxAnd204Headers() { return send_strict_1xx_and_204_headers_; } // Codec errors found in callbacks are overridden within the http_parser library. This holds those // errors to propagate them through to dispatch() where we can handle the error. @@ -280,7 +280,8 @@ class ConnectionImpl : public virtual Connection, // HTTP/1 message has been flushed from the parser. This allows raising an HTTP/2 style headers // block with end stream set to true with no further protocol data remaining. bool deferred_end_stream_headers_ : 1; - const bool strict_1xx_and_204_headers_ : 1; + const bool require_strict_1xx_and_204_headers_ : 1; + const bool send_strict_1xx_and_204_headers_ : 1; bool dispatching_ : 1; bool dispatching_slice_already_drained_ : 1; diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index d4d24b58855e9..7b74ed4649e83 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -85,8 +85,9 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.preserve_downstream_scheme", "envoy.reloadable_features.remove_forked_chromium_url", "envoy.reloadable_features.require_ocsp_response_for_must_staple_certs", + "envoy.reloadable_features.require_strict_1xx_and_204_response_headers", "envoy.reloadable_features.return_502_for_upstream_protocol_errors", - "envoy.reloadable_features.strict_1xx_and_204_response_headers", + "envoy.reloadable_features.send_strict_1xx_and_204_response_headers", "envoy.reloadable_features.tls_use_io_handle_bio", "envoy.reloadable_features.treat_host_like_authority", "envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure", diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index e8dd865582394..49ab6beb301ab 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -2295,7 +2295,7 @@ TEST_F(Http1ClientConnectionImplTest, 204ResponseContentLengthNotAllowed) { { TestScopedRuntime scoped_runtime; Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.strict_1xx_and_204_response_headers", "false"}}); + {{"envoy.reloadable_features.require_strict_1xx_and_204_response_headers", "false"}}); initialize(); @@ -2331,7 +2331,7 @@ TEST_F(Http1ClientConnectionImplTest, 204ResponseWithContentLength0) { { TestScopedRuntime scoped_runtime; Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.strict_1xx_and_204_response_headers", "false"}}); + {{"envoy.reloadable_features.require_strict_1xx_and_204_response_headers", "false"}}); NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); @@ -2365,7 +2365,7 @@ TEST_F(Http1ClientConnectionImplTest, 204ResponseTransferEncodingNotAllowed) { { TestScopedRuntime scoped_runtime; Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.strict_1xx_and_204_response_headers", "false"}}); + {{"envoy.reloadable_features.require_strict_1xx_and_204_response_headers", "false"}}); initialize(); @@ -2467,7 +2467,7 @@ TEST_F(Http1ClientConnectionImplTest, 101ResponseTransferEncodingNotAllowed) { { TestScopedRuntime scoped_runtime; Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.strict_1xx_and_204_response_headers", "false"}}); + {{"envoy.reloadable_features.require_strict_1xx_and_204_response_headers", "false"}}); initialize(); From f5665d01cf1b8744d20030c70dd18b2987c8f673 Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Mon, 19 Apr 2021 09:35:06 -0700 Subject: [PATCH 036/209] grid: Track HTTP/3 broken-ness in ConnectivityGrid. (#15933) * grid: Track HTTP/3 broken-ness in ConnectivityGrid. Mark HTTP/3 as broken when the HTTP/3 connect attempt fails but the fallback attempt succeeds. When HTTP/3 is broken, do not attempt to initiate new HTTP/3 connections. Add an assert that the Grid's host and the callback's host are the same. Signed-off-by: Ryan Hamilton --- source/common/http/conn_pool_grid.cc | 110 ++++++++++++++++++------ source/common/http/conn_pool_grid.h | 30 +++++-- test/common/http/conn_pool_grid_test.cc | 69 +++++++++++++-- 3 files changed, 171 insertions(+), 38 deletions(-) diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc index 5a3fdef5b5036..995e0dc0c7f72 100644 --- a/source/common/http/conn_pool_grid.cc +++ b/source/common/http/conn_pool_grid.cc @@ -6,15 +6,17 @@ namespace Envoy { namespace Http { +namespace { absl::string_view describePool(const ConnectionPool::Instance& pool) { return pool.protocolDescription(); } +} // namespace ConnectivityGrid::WrapperCallbacks::WrapperCallbacks(ConnectivityGrid& grid, Http::ResponseDecoder& decoder, PoolIterator pool_it, ConnectionPool::Callbacks& callbacks) - : grid_(grid), decoder_(decoder), inner_callbacks_(callbacks), + : grid_(grid), decoder_(decoder), inner_callbacks_(&callbacks), next_attempt_timer_( grid_.dispatcher_.createTimer([this]() -> void { tryAnotherConnection(); })), current_(pool_it) {} @@ -24,6 +26,12 @@ ConnectivityGrid::WrapperCallbacks::ConnectionAttemptCallbacks::ConnectionAttemp WrapperCallbacks& parent, PoolIterator it) : parent_(parent), pool_it_(it), cancellable_(nullptr) {} +ConnectivityGrid::WrapperCallbacks::ConnectionAttemptCallbacks::~ConnectionAttemptCallbacks() { + if (cancellable_ != nullptr) { + cancel(Envoy::ConnectionPool::CancelPolicy::Default); + } +} + ConnectivityGrid::StreamCreationResult ConnectivityGrid::WrapperCallbacks::ConnectionAttemptCallbacks::newStream() { auto* cancellable = pool().newStream(parent_.decoder_, *this); @@ -37,14 +45,21 @@ ConnectivityGrid::WrapperCallbacks::ConnectionAttemptCallbacks::newStream() { void ConnectivityGrid::WrapperCallbacks::ConnectionAttemptCallbacks::onPoolFailure( ConnectionPool::PoolFailureReason reason, absl::string_view transport_failure_reason, Upstream::HostDescriptionConstSharedPtr host) { + cancellable_ = nullptr; // Attempt failed and can no longer be cancelled. parent_.onConnectionAttemptFailed(this, reason, transport_failure_reason, host); } void ConnectivityGrid::WrapperCallbacks::onConnectionAttemptFailed( ConnectionAttemptCallbacks* attempt, ConnectionPool::PoolFailureReason reason, absl::string_view transport_failure_reason, Upstream::HostDescriptionConstSharedPtr host) { + ASSERT(host == grid_.host_); ENVOY_LOG(trace, "{} pool failed to create connection to host '{}'.", - describePool(attempt->pool()), grid_.host_->hostname()); + describePool(attempt->pool()), host->hostname()); + if (grid_.isPoolHttp3(attempt->pool())) { + http3_attempt_failed_ = true; + } + maybeMarkHttp3Broken(); + auto delete_this_on_return = attempt->removeFromList(connection_attempts_); // If there is another connection attempt in flight then let that proceed. @@ -58,12 +73,15 @@ void ConnectivityGrid::WrapperCallbacks::onConnectionAttemptFailed( } // If this point is reached, all pools have been tried. Pass the pool failure up to the - // original caller. - ConnectionPool::Callbacks& callbacks = inner_callbacks_; - ENVOY_LOG(trace, "Passing pool failure up to caller.", describePool(attempt->pool()), - grid_.host_->hostname()); + // original caller, if the caller hasn't already been notified. + ConnectionPool::Callbacks* callbacks = inner_callbacks_; + inner_callbacks_ = nullptr; deleteThis(); - callbacks.onPoolFailure(reason, transport_failure_reason, host); + if (callbacks != nullptr) { + ENVOY_LOG(trace, "Passing pool failure up to caller.", describePool(attempt->pool()), + host->hostname()); + callbacks->onPoolFailure(reason, transport_failure_reason, host); + } } void ConnectivityGrid::WrapperCallbacks::deleteThis() { @@ -87,37 +105,65 @@ void ConnectivityGrid::WrapperCallbacks::onConnectionAttemptReady( ConnectionAttemptCallbacks* attempt, RequestEncoder& encoder, Upstream::HostDescriptionConstSharedPtr host, const StreamInfo::StreamInfo& info, absl::optional protocol) { + ASSERT(host == grid_.host_); ENVOY_LOG(trace, "{} pool successfully connected to host '{}'.", describePool(attempt->pool()), - grid_.host_->hostname()); - auto delete_on_return = attempt->removeFromList(connection_attempts_); - // The first successful connection is passed up, and all others will be canceled. - // TODO: Ensure that if HTTP/2 succeeds, we can allow the HTTP/3 connection to run to completion. - for (auto& attempt : connection_attempts_) { - attempt->cancel(Envoy::ConnectionPool::CancelPolicy::Default); + host->hostname()); + if (!grid_.isPoolHttp3(attempt->pool())) { + tcp_attempt_succeeded_ = true; + } + maybeMarkHttp3Broken(); + + auto delete_this_on_return = attempt->removeFromList(connection_attempts_); + ConnectionPool::Callbacks* callbacks = inner_callbacks_; + inner_callbacks_ = nullptr; + // If an HTTP/3 connection attempts is in progress, let it complete so that if it succeeds + // it can be used for future requests. But if there is a TCP connection attempt in progress, + // cancel it. + if (grid_.isPoolHttp3(attempt->pool())) { + cancelAllPendingAttempts(Envoy::ConnectionPool::CancelPolicy::Default); + } + if (connection_attempts_.empty()) { + deleteThis(); + } + if (callbacks != nullptr) { + callbacks->onPoolReady(encoder, host, info, protocol); + } +} + +void ConnectivityGrid::WrapperCallbacks::maybeMarkHttp3Broken() { + if (http3_attempt_failed_ && tcp_attempt_succeeded_) { + ENVOY_LOG(trace, "Marking HTTP/3 broken for host '{}'.", grid_.host_->hostname()); + grid_.setIsHttp3Broken(true); } - ConnectionPool::Callbacks& callbacks = inner_callbacks_; - deleteThis(); - return callbacks.onPoolReady(encoder, host, info, protocol); } void ConnectivityGrid::WrapperCallbacks::ConnectionAttemptCallbacks::onPoolReady( RequestEncoder& encoder, Upstream::HostDescriptionConstSharedPtr host, const StreamInfo::StreamInfo& info, absl::optional protocol) { + cancellable_ = nullptr; // Attempt succeeded and can no longer be cancelled. parent_.onConnectionAttemptReady(this, encoder, host, info, protocol); } void ConnectivityGrid::WrapperCallbacks::ConnectionAttemptCallbacks::cancel( Envoy::ConnectionPool::CancelPolicy cancel_policy) { - cancellable_->cancel(cancel_policy); + auto cancellable = cancellable_; + cancellable_ = nullptr; // Prevent repeated cancellations. + cancellable->cancel(cancel_policy); } void ConnectivityGrid::WrapperCallbacks::cancel(Envoy::ConnectionPool::CancelPolicy cancel_policy) { // If the newStream caller cancels the stream request, pass the cancellation on // to each connection attempt. + cancelAllPendingAttempts(cancel_policy); + deleteThis(); +} + +void ConnectivityGrid::WrapperCallbacks::cancelAllPendingAttempts( + Envoy::ConnectionPool::CancelPolicy cancel_policy) { for (auto& attempt : connection_attempts_) { attempt->cancel(cancel_policy); } - deleteThis(); + connection_attempts_.clear(); } bool ConnectivityGrid::WrapperCallbacks::tryAnotherConnection() { @@ -154,13 +200,16 @@ ConnectivityGrid::ConnectivityGrid( ConnectivityGrid::~ConnectivityGrid() { // Ignore drained callbacks while the pools are destroyed below. destroying_ = true; + // Callbacks might have pending streams registered with the pools, so cancel and delete + // the callback before deleting the pools. + wrapped_callbacks_.clear(); pools_.clear(); } absl::optional ConnectivityGrid::createNextPool() { // Pools are created by newStream, which should not be called during draining. ASSERT(drained_callbacks_.empty()); - // Right now, only H3 and ALPN are supported, so if there are 2 pools we're done. + // Right now, only H3 and TCP are supported, so if there are 2 pools we're done. if (pools_.size() == 2 || !drained_callbacks_.empty()) { return absl::nullopt; } @@ -193,17 +242,22 @@ ConnectionPool::Cancellable* ConnectivityGrid::newStream(Http::ResponseDecoder& if (pools_.empty()) { createNextPool(); } - - // TODO(#15649) track pools with successful connections: don't always start at - // the front of the list. - auto wrapped_callback = - std::make_unique(*this, decoder, pools_.begin(), callbacks); + PoolIterator pool = pools_.begin(); + if (is_http3_broken_) { + ENVOY_LOG(trace, "HTTP/3 is broken to host '{}', skipping.", describePool(**pool), + host_->hostname()); + // Since HTTP/3 is broken, presumably both pools have already been created so this + // is just to be safe. + createNextPool(); + ++pool; + } + auto wrapped_callback = std::make_unique(*this, decoder, pool, callbacks); ConnectionPool::Cancellable* ret = wrapped_callback.get(); LinkedList::moveIntoList(std::move(wrapped_callback), wrapped_callbacks_); - // Note that in the case of immediate attempt/failure, newStream will delete this. if (wrapped_callbacks_.front()->newStream() == StreamCreationResult::ImmediateResult) { // If newStream succeeds, return nullptr as the caller has received their - // callback and does not need a cancellable handle. + // callback and does not need a cancellable handle. At this point the + // WrappedCallbacks object has also been deleted. return nullptr; } return ret; @@ -248,6 +302,10 @@ absl::optional ConnectivityGrid::nextPool(PoolIt return createNextPool(); } +bool ConnectivityGrid::isPoolHttp3(const ConnectionPool::Instance& pool) { + return &pool == pools_.begin()->get(); +} + void ConnectivityGrid::onDrainReceived() { // Don't do any work under the stack of ~ConnectivityGrid() if (destroying_) { diff --git a/source/common/http/conn_pool_grid.h b/source/common/http/conn_pool_grid.h index bd1989f81e6e6..d4d2ae2fc92db 100644 --- a/source/common/http/conn_pool_grid.h +++ b/source/common/http/conn_pool_grid.h @@ -42,6 +42,7 @@ class ConnectivityGrid : public ConnectionPool::Instance, public LinkedObject { public: ConnectionAttemptCallbacks(WrapperCallbacks& parent, PoolIterator it); + ~ConnectionAttemptCallbacks() override; StreamCreationResult newStream(); @@ -74,9 +75,6 @@ class ConnectivityGrid : public ConnectionPool::Instance, // Attempt to create a new stream for pool(). StreamCreationResult newStream(); - // Removes this from the owning list, deleting it. - void deleteThis(); - // Called on pool failure or timeout to kick off another connection attempt. // Returns true if there is a failover pool and a connection has been // attempted, false if all pools have been tried. @@ -95,6 +93,16 @@ class ConnectivityGrid : public ConnectionPool::Instance, absl::optional protocol); private: + // Removes this from the owning list, deleting it. + void deleteThis(); + + // Marks HTTP/3 broken if the HTTP/3 attempt failed but a TCP attempt succeeded. + // While HTTP/3 is broken the grid will not attempt to make new HTTP/3 connections. + void maybeMarkHttp3Broken(); + + // Cancels any pending attempts and deletes them. + void cancelAllPendingAttempts(Envoy::ConnectionPool::CancelPolicy cancel_policy); + // Tracks all the connection attempts which currently in flight. std::list connection_attempts_; @@ -103,12 +111,17 @@ class ConnectivityGrid : public ConnectionPool::Instance, // The decoder for the original newStream, needed to create streams on subsequent pools. Http::ResponseDecoder& decoder_; // The callbacks from the original caller, which must get onPoolFailure or - // onPoolReady unless there is call to cancel(). - ConnectionPool::Callbacks& inner_callbacks_; + // onPoolReady unless there is call to cancel(). Will be nullptr if the caller + // has been notified while attempts are still pending. + ConnectionPool::Callbacks* inner_callbacks_; // The timer which tracks when new connections should be attempted. Event::TimerPtr next_attempt_timer_; // The iterator to the last pool which had a connection attempt. PoolIterator current_; + // True if the HTTP/3 attempt failed. + bool http3_attempt_failed_{}; + // True if the TCP attempt succeeded. + bool tcp_attempt_succeeded_{}; }; using WrapperCallbacksPtr = std::unique_ptr; @@ -134,6 +147,12 @@ class ConnectivityGrid : public ConnectionPool::Instance, // Returns the next pool in the ordered priority list. absl::optional nextPool(PoolIterator pool_it); + // Returns true if pool is the grid's HTTP/3 connection pool. + bool isPoolHttp3(const ConnectionPool::Instance& pool); + + bool isHttp3Broken() const { return is_http3_broken_; } + void setIsHttp3Broken(bool is_http3_broken) { is_http3_broken_ = is_http3_broken; } + private: friend class ConnectivityGridForTest; @@ -155,6 +174,7 @@ class ConnectivityGrid : public ConnectionPool::Instance, Upstream::ClusterConnectivityState& state_; std::chrono::milliseconds next_attempt_duration_; TimeSource& time_source_; + bool is_http3_broken_{}; // Tracks how many drains are needed before calling drain callbacks. This is // set to the number of pools when the first drain callbacks are added, and diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index 27426a6a93810..0406db0f28e79 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -46,14 +46,14 @@ class ConnectivityGridForTest : public ConnectivityGrid { if (immediate_success_) { callbacks.onPoolReady(*encoder_, host(), *info_, absl::nullopt); return nullptr; - } else if (immediate_failure_) { + } + if (immediate_failure_) { callbacks.onPoolFailure(ConnectionPool::PoolFailureReason::LocalConnectionFailure, "reason", host()); return nullptr; - } else { - callbacks_.push_back(&callbacks); - return &cancel_; } + callbacks_.push_back(&callbacks); + return &cancel_; })); if (pools_.size() == 1) { EXPECT_CALL(*first(), protocolDescription()) @@ -97,11 +97,11 @@ class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testin public: ConnectivityGridTest() : options_({Http::Protocol::Http11, Http::Protocol::Http2, Http::Protocol::Http3}), - host_(new NiceMock()), grid_(dispatcher_, random_, Upstream::makeTestHost(cluster_, "hostname", "tcp://127.0.0.1:9000", simTime()), Upstream::ResourcePriority::Default, socket_options_, transport_socket_options_, - state_, simTime(), std::chrono::milliseconds(300), options_) { + state_, simTime(), std::chrono::milliseconds(300), options_), + host_(grid_.host()) { grid_.info_ = &info_; grid_.encoder_ = &encoder_; } @@ -109,12 +109,12 @@ class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testin const Network::ConnectionSocket::OptionsSharedPtr socket_options_; const Network::TransportSocketOptionsSharedPtr transport_socket_options_; ConnectivityGrid::ConnectivityOptions options_; - Upstream::HostDescriptionConstSharedPtr host_; Upstream::ClusterConnectivityState state_; NiceMock dispatcher_; std::shared_ptr cluster_{new NiceMock()}; NiceMock random_; ConnectivityGridForTest grid_; + Upstream::HostDescriptionConstSharedPtr host_; NiceMock callbacks_; NiceMock decoder_; @@ -134,6 +134,7 @@ TEST_F(ConnectivityGridTest, Success) { ASSERT_NE(grid_.callbacks(), nullptr); EXPECT_CALL(callbacks_.pool_ready_, ready()); grid_.callbacks()->onPoolReady(encoder_, host_, info_, absl::nullopt); + EXPECT_FALSE(grid_.isHttp3Broken()); } // Test the first pool successfully connecting under the stack of newStream. @@ -143,6 +144,7 @@ TEST_F(ConnectivityGridTest, ImmediateSuccess) { EXPECT_CALL(callbacks_.pool_ready_, ready()); EXPECT_EQ(grid_.newStream(decoder_, callbacks_), nullptr); EXPECT_NE(grid_.first(), nullptr); + EXPECT_FALSE(grid_.isHttp3Broken()); } // Test the first pool failing and the second connecting. @@ -171,6 +173,7 @@ TEST_F(ConnectivityGridTest, FailureThenSuccessSerial) { EXPECT_CALL(callbacks_.pool_ready_, ready()); EXPECT_LOG_CONTAINS("trace", "second pool successfully connected to host 'hostname'", grid_.callbacks(1)->onPoolReady(encoder_, host_, info_, absl::nullopt)); + EXPECT_TRUE(grid_.isHttp3Broken()); } // Test both connections happening in parallel and the second connecting. @@ -200,6 +203,7 @@ TEST_F(ConnectivityGridTest, TimeoutThenSuccessParallelSecondConnects) { EXPECT_NE(grid_.callbacks(), nullptr); EXPECT_CALL(callbacks_.pool_ready_, ready()); grid_.callbacks(1)->onPoolReady(encoder_, host_, info_, absl::nullopt); + EXPECT_TRUE(grid_.isHttp3Broken()); } // Test both connections happening in parallel and the first connecting. @@ -227,6 +231,38 @@ TEST_F(ConnectivityGridTest, TimeoutThenSuccessParallelFirstConnects) { EXPECT_NE(grid_.callbacks(0), nullptr); EXPECT_CALL(callbacks_.pool_ready_, ready()); grid_.callbacks(0)->onPoolReady(encoder_, host_, info_, absl::nullopt); + EXPECT_FALSE(grid_.isHttp3Broken()); +} + +// Test both connections happening in parallel and the second connecting before +// the first eventually fails. +TEST_F(ConnectivityGridTest, TimeoutThenSuccessParallelSecondConnectsFirstFail) { + EXPECT_EQ(grid_.first(), nullptr); + + // This timer will be returned and armed as the grid creates the wrapper's failover timer. + Event::MockTimer* failover_timer = new NiceMock(&dispatcher_); + + grid_.newStream(decoder_, callbacks_); + EXPECT_NE(grid_.first(), nullptr); + EXPECT_TRUE(failover_timer->enabled_); + + // Kick off the second connection. + failover_timer->invokeCallback(); + EXPECT_NE(grid_.second(), nullptr); + + // onPoolReady should be passed from the pool back to the original caller. + EXPECT_NE(grid_.callbacks(1), nullptr); + EXPECT_CALL(callbacks_.pool_ready_, ready()); + grid_.callbacks(1)->onPoolReady(encoder_, host_, info_, absl::nullopt); + EXPECT_FALSE(grid_.isHttp3Broken()); + + // onPoolFailure should not be passed up the first time. Instead the grid + // should wait on the other pool + EXPECT_NE(grid_.callbacks(0), nullptr); + EXPECT_CALL(callbacks_.pool_failure_, ready()).Times(0); + grid_.callbacks(0)->onPoolFailure(ConnectionPool::PoolFailureReason::LocalConnectionFailure, + "reason", host_); + EXPECT_TRUE(grid_.isHttp3Broken()); } // Test that after the first pool fails, subsequent connections will @@ -260,6 +296,7 @@ TEST_F(ConnectivityGridTest, ImmediateDoubleFailure) { grid_.immediate_failure_ = true; EXPECT_CALL(callbacks_.pool_failure_, ready()); EXPECT_EQ(grid_.newStream(decoder_, callbacks_), nullptr); + EXPECT_FALSE(grid_.isHttp3Broken()); } // Test both connections happening in parallel and both failing. @@ -287,6 +324,7 @@ TEST_F(ConnectivityGridTest, TimeoutDoubleFailureParallel) { EXPECT_CALL(callbacks_.pool_failure_, ready()); grid_.callbacks(1)->onPoolFailure(ConnectionPool::PoolFailureReason::LocalConnectionFailure, "reason", host_); + EXPECT_FALSE(grid_.isHttp3Broken()); } // Test cancellation @@ -384,6 +422,23 @@ TEST_F(ConnectivityGridTest, NoDrainOnTeardown) { EXPECT_FALSE(drain_received); } +// Test that when HTTP/3 is broken then the HTTP/3 pool is skipped. +TEST_F(ConnectivityGridTest, SuccessAfterBroken) { + grid_.setIsHttp3Broken(true); + EXPECT_EQ(grid_.first(), nullptr); + + EXPECT_LOG_CONTAINS("trace", "HTTP/3 is broken to host 'first', skipping.", + EXPECT_NE(grid_.newStream(decoder_, callbacks_), nullptr)); + EXPECT_NE(grid_.first(), nullptr); + EXPECT_NE(grid_.second(), nullptr); + + // onPoolReady should be passed from the pool back to the original caller. + ASSERT_NE(grid_.callbacks(), nullptr); + EXPECT_CALL(callbacks_.pool_ready_, ready()); + grid_.callbacks()->onPoolReady(encoder_, host_, info_, absl::nullopt); + EXPECT_TRUE(grid_.isHttp3Broken()); +} + #ifdef ENVOY_ENABLE_QUICHE } // namespace From 75f6d1aeaa8069da119f0a1065d3e6dde301385b Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Mon, 19 Apr 2021 09:48:59 -0700 Subject: [PATCH 037/209] docs: Dmitri Dolguikh to manage Stable Releases in 2021 Q2. (#16055) Fixes #16027. Signed-off-by: Piotr Sikora --- RELEASES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.md b/RELEASES.md index bf7f9c0072f0e..688ea8bb8819e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -53,6 +53,7 @@ stable releases and sending announcements about them. This role is rotating on a | 2020 Q3 | Yuchen Dai ([lambdai](https://github.com/lambdai)) | | 2020 Q4 | Christoph Pakulski ([cpakulski](https://github.com/cpakulski)) | | 2021 Q1 | Rei Shimizu ([Shikugawa](https://github.com/Shikugawa)) | +| 2021 Q2 | Dmitri Dolguikh ([dmitri-d](https://github.com/dmitri-d)) | ## Release schedule From 04b34ca8ee536750738750c8c39b34d7e1040e4c Mon Sep 17 00:00:00 2001 From: qqustc Date: Mon, 19 Apr 2021 14:15:45 -0400 Subject: [PATCH 038/209] kill_request: Use RELEASE_ASSERT instead of raise(SIGABRT) in kill_request filter (#15999) Signed-off-by: qqustc@gmail.com --- .../http/kill_request/kill_request_filter.cc | 4 ++-- .../kill_request_filter_integration_test.cc | 7 ++++--- .../http/kill_request/kill_request_filter_test.cc | 15 ++++++++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/source/extensions/filters/http/kill_request/kill_request_filter.cc b/source/extensions/filters/http/kill_request/kill_request_filter.cc index c5147483c0d7c..7cf10a23bc1b2 100644 --- a/source/extensions/filters/http/kill_request/kill_request_filter.cc +++ b/source/extensions/filters/http/kill_request/kill_request_filter.cc @@ -68,7 +68,7 @@ Http::FilterHeadersStatus KillRequestFilter::decodeHeaders(Http::RequestHeaderMa if (is_kill_request && is_correct_direction && isKillRequestEnabled()) { // Crash Envoy. - raise(SIGABRT); + RELEASE_ASSERT(false, "KillRequestFilter is crashing Envoy!!!"); } return Http::FilterHeadersStatus::Continue; @@ -81,7 +81,7 @@ Http::FilterHeadersStatus KillRequestFilter::encodeHeaders(Http::ResponseHeaderM if (isKillRequest(headers) && isKillRequestEnabled()) { // Crash Envoy. - raise(SIGABRT); + RELEASE_ASSERT(false, "KillRequestFilter is crashing Envoy!!!"); } return Http::FilterHeadersStatus::Continue; diff --git a/test/extensions/filters/http/kill_request/kill_request_filter_integration_test.cc b/test/extensions/filters/http/kill_request/kill_request_filter_integration_test.cc index 1d63e0cb9d4c3..d995c10aad043 100644 --- a/test/extensions/filters/http/kill_request/kill_request_filter_integration_test.cc +++ b/test/extensions/filters/http/kill_request/kill_request_filter_integration_test.cc @@ -43,7 +43,7 @@ TEST_P(KillRequestFilterIntegrationTestAllProtocols, KillRequestCrashEnvoy) { {"x-envoy-kill-request", "true"}}; EXPECT_DEATH(sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 1024), - ""); + "KillRequestFilter is crashing Envoy!!!"); } // Request crash Envoy controlled via response. @@ -70,7 +70,8 @@ TEST_P(KillRequestFilterIntegrationTestAllProtocols, KillRequestCrashEnvoyOnResp Http::TestResponseHeaderMapImpl kill_response_headers = default_response_headers_; kill_response_headers.addCopy("x-envoy-kill-request", "true"); - EXPECT_DEATH(sendRequestAndWaitForResponse(request_headers, 0, kill_response_headers, 1024), ""); + EXPECT_DEATH(sendRequestAndWaitForResponse(request_headers, 0, kill_response_headers, 1024), + "KillRequestFilter is crashing Envoy!!!"); } TEST_P(KillRequestFilterIntegrationTestAllProtocols, KillRequestCrashEnvoyWithCustomKillHeader) { @@ -93,7 +94,7 @@ name: envoy.filters.http.kill_request {"x-custom-kill-request", "true"}}; EXPECT_DEATH(sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 1024), - ""); + "KillRequestFilter is crashing Envoy!!!"); } TEST_P(KillRequestFilterIntegrationTestAllProtocols, KillRequestDisabledWhenHeaderIsMissing) { diff --git a/test/extensions/filters/http/kill_request/kill_request_filter_test.cc b/test/extensions/filters/http/kill_request/kill_request_filter_test.cc index c392b01d9c47a..581bfa0d34864 100644 --- a/test/extensions/filters/http/kill_request/kill_request_filter_test.cc +++ b/test/extensions/filters/http/kill_request/kill_request_filter_test.cc @@ -48,7 +48,8 @@ TEST_F(KillRequestFilterTest, KillRequestCrashEnvoy) { request_headers_.addCopy("x-envoy-kill-request", "true"); ON_CALL(random_generator_, random()).WillByDefault(Return(0)); - EXPECT_DEATH(filter_->decodeHeaders(request_headers_, false), ""); + EXPECT_DEATH(filter_->decodeHeaders(request_headers_, false), + "KillRequestFilter is crashing Envoy!!!"); } TEST_F(KillRequestFilterTest, KillRequestCrashEnvoyWithCustomKillHeader) { @@ -59,7 +60,8 @@ TEST_F(KillRequestFilterTest, KillRequestCrashEnvoyWithCustomKillHeader) { request_headers_.addCopy("x-custom-kill-request", "true"); ON_CALL(random_generator_, random()).WillByDefault(Return(0)); - EXPECT_DEATH(filter_->decodeHeaders(request_headers_, false), ""); + EXPECT_DEATH(filter_->decodeHeaders(request_headers_, false), + "KillRequestFilter is crashing Envoy!!!"); } TEST_F(KillRequestFilterTest, KillRequestWithMillionDenominatorCrashEnvoy) { @@ -70,7 +72,8 @@ TEST_F(KillRequestFilterTest, KillRequestWithMillionDenominatorCrashEnvoy) { request_headers_.addCopy("x-envoy-kill-request", "yes"); ON_CALL(random_generator_, random()).WillByDefault(Return(0)); - EXPECT_DEATH(filter_->decodeHeaders(request_headers_, false), ""); + EXPECT_DEATH(filter_->decodeHeaders(request_headers_, false), + "KillRequestFilter is crashing Envoy!!!"); } TEST_F(KillRequestFilterTest, KillRequestDisabledWhenIsKillRequestEnabledReturnsFalse) { @@ -184,7 +187,8 @@ TEST_F(KillRequestFilterTest, CanKillOnResponse) { // We should crash on the OUTBOUND request ON_CALL(random_generator_, random()).WillByDefault(Return(0)); response_headers_.addCopy("x-envoy-kill-request", "true"); - EXPECT_DEATH(filter_->encodeHeaders(response_headers_, false), ""); + EXPECT_DEATH(filter_->encodeHeaders(response_headers_, false), + "KillRequestFilter is crashing Envoy!!!"); } TEST_F(KillRequestFilterTest, KillsBasedOnDirection) { @@ -201,7 +205,8 @@ TEST_F(KillRequestFilterTest, KillsBasedOnDirection) { // We should crash on the RESPONSE kill request ON_CALL(random_generator_, random()).WillByDefault(Return(0)); response_headers_.addCopy("x-envoy-kill-request", "true"); - EXPECT_DEATH(filter_->encodeHeaders(response_headers_, false), ""); + EXPECT_DEATH(filter_->encodeHeaders(response_headers_, false), + "KillRequestFilter is crashing Envoy!!!"); } } // namespace From e1935de16f4d2cf366da9b1f0330eae85f9265a7 Mon Sep 17 00:00:00 2001 From: WangCong Date: Tue, 20 Apr 2021 04:59:51 +0800 Subject: [PATCH 039/209] config: support determine terminal filter at runtime (#15803) Currently we validate terminal filter before getting real protobuf message, now envoy can determine a filter whether is a terminal filter base on the protobuf message. Signed-off-by: qinggniq --- ci/filter_example_setup.sh | 2 +- .../intro/arch_overview/http/http_filters.rst | 2 +- .../filter/http/filter_config_provider.h | 6 ++- include/envoy/server/filter_config.h | 4 +- source/common/config/utility.h | 2 +- .../http/filter_config_discovery_impl.cc | 53 +++++++++++++++---- .../http/filter_config_discovery_impl.h | 17 ++++-- .../filters/http/common/factory_base.h | 11 ++++ .../extensions/filters/http/router/config.h | 6 ++- .../filters/network/common/factory_base.h | 11 +++- .../filters/network/direct_response/config.cc | 6 ++- .../extensions/filters/network/echo/config.cc | 5 +- .../network/http_connection_manager/config.cc | 14 +++-- .../network/http_connection_manager/config.h | 7 +-- source/server/listener_manager_impl.cc | 7 +-- .../http/filter_config_discovery_impl_test.cc | 36 +++++++++++-- .../network/dubbo_proxy/config_test.cc | 2 +- .../http_connection_manager/config_test.cc | 3 +- .../network/redis_proxy/config_test.cc | 8 +-- .../filters/network/tcp_proxy/config_test.cc | 2 +- .../network/thrift_proxy/config_test.cc | 2 +- test/integration/BUILD | 2 + .../extension_discovery_integration_test.cc | 48 +++++++++++++++-- test/integration/filters/BUILD | 19 +++++++ .../filters/set_is_terminal_filter.cc | 42 +++++++++++++++ .../set_is_terminal_filter_config.proto | 9 ++++ .../tcp_conn_pool_integration_test.cc | 5 +- test/server/config_validation/server_test.cc | 5 +- test/server/listener_manager_impl_test.cc | 11 +++- 29 files changed, 288 insertions(+), 59 deletions(-) create mode 100644 test/integration/filters/set_is_terminal_filter.cc create mode 100644 test/integration/filters/set_is_terminal_filter_config.proto diff --git a/ci/filter_example_setup.sh b/ci/filter_example_setup.sh index 4fef840e0389d..0d0cd1f602805 100644 --- a/ci/filter_example_setup.sh +++ b/ci/filter_example_setup.sh @@ -5,7 +5,7 @@ set -e # This is the hash on https://github.com/envoyproxy/envoy-filter-example.git we pin to. -ENVOY_FILTER_EXAMPLE_GITSHA="d135632a6e96268ab2c6a62e1cceec25c728ccf9" +ENVOY_FILTER_EXAMPLE_GITSHA="dfdc226d44d1b7c300e6e691e2e8ada98b045edb" ENVOY_FILTER_EXAMPLE_SRCDIR="${BUILD_DIR}/envoy-filter-example" # shellcheck disable=SC2034 diff --git a/docs/root/intro/arch_overview/http/http_filters.rst b/docs/root/intro/arch_overview/http/http_filters.rst index 0bca514dd95f0..1c1079c982a65 100644 --- a/docs/root/intro/arch_overview/http/http_filters.rst +++ b/docs/root/intro/arch_overview/http/http_filters.rst @@ -41,7 +41,7 @@ decoder/encoder filters): - A - B # The last configured filter has to be a terminal filter, as determined by the - # NamedHttpFilterConfigFactory::isTerminalFilter() function. This is most likely the router + # NamedHttpFilterConfigFactory::isTerminalFilterByProto(config, context) function. This is most likely the router # filter. - C diff --git a/include/envoy/filter/http/filter_config_provider.h b/include/envoy/filter/http/filter_config_provider.h index 273a004a60f18..097c1616099ca 100644 --- a/include/envoy/filter/http/filter_config_provider.h +++ b/include/envoy/filter/http/filter_config_provider.h @@ -35,11 +35,15 @@ class FilterConfigProviderManager { * @param filter_config_name the filter config resource name. * @param factory_context is the context to use for the filter config provider. * @param stat_prefix supplies the stat_prefix to use for the provider stats. + * @param last_filter_in_filter_chain indicates whether this filter is the last filter in the + * configured chain + * @param filter_chain_type is the filter chain type */ virtual DynamicFilterConfigProviderPtr createDynamicFilterConfigProvider( const envoy::config::core::v3::ExtensionConfigSource& config_source, const std::string& filter_config_name, Server::Configuration::FactoryContext& factory_context, - const std::string& stat_prefix) PURE; + const std::string& stat_prefix, bool last_filter_in_filter_chain, + const std::string& filter_chain_type) PURE; /** * Get an FilterConfigProviderPtr for a statically inlined filter config. diff --git a/include/envoy/server/filter_config.h b/include/envoy/server/filter_config.h index 42aa7db3a6bdc..cea17eceabdef 100644 --- a/include/envoy/server/filter_config.h +++ b/include/envoy/server/filter_config.h @@ -131,7 +131,7 @@ class NamedNetworkFilterConfigFactory : public ProtocolOptionsFactory { /** * @return bool true if this filter must be the last filter in a filter chain, false otherwise. */ - virtual bool isTerminalFilter() { return false; } + virtual bool isTerminalFilterByProto(const Protobuf::Message&, FactoryContext&) { return false; } }; /** @@ -206,7 +206,7 @@ class NamedHttpFilterConfigFactory : public ProtocolOptionsFactory { /** * @return bool true if this filter must be the last filter in a filter chain, false otherwise. */ - virtual bool isTerminalFilter() { return false; } + virtual bool isTerminalFilterByProto(const Protobuf::Message&, FactoryContext&) { return false; } /** * @return FilterDependenciesPtr specification of dependencies required or diff --git a/source/common/config/utility.h b/source/common/config/utility.h index 6ab115b2fc56b..187be996622ca 100644 --- a/source/common/config/utility.h +++ b/source/common/config/utility.h @@ -458,7 +458,7 @@ class Utility { * @throws EnvoyException if there is a mismatch between design and configuration. */ static void validateTerminalFilters(const std::string& name, const std::string& filter_type, - const char* filter_chain_type, bool is_terminal_filter, + const std::string& filter_chain_type, bool is_terminal_filter, bool last_filter_in_current_config) { if (is_terminal_filter && !last_filter_in_current_config) { ExceptionUtil::throwEnvoyException( diff --git a/source/common/filter/http/filter_config_discovery_impl.cc b/source/common/filter/http/filter_config_discovery_impl.cc index 85fc1d6246ac8..8f5f74b000258 100644 --- a/source/common/filter/http/filter_config_discovery_impl.cc +++ b/source/common/filter/http/filter_config_discovery_impl.cc @@ -32,17 +32,25 @@ void validateTypeUrlHelper(const std::string& type_url, DynamicFilterConfigProviderImpl::DynamicFilterConfigProviderImpl( FilterConfigSubscriptionSharedPtr& subscription, const absl::flat_hash_set& require_type_urls, + Server::Configuration::FactoryContext& factory_context, - Envoy::Http::FilterFactoryCb default_config) + Envoy::Http::FilterFactoryCb default_config, bool last_filter_in_filter_chain, + const std::string& filter_chain_type) : subscription_(subscription), require_type_urls_(require_type_urls), default_configuration_(default_config ? absl::make_optional(default_config) : absl::nullopt), - tls_(factory_context.threadLocal()), - init_target_("DynamicFilterConfigProviderImpl", [this]() { - subscription_->start(); - // This init target is used to activate the subscription but not wait for a response. It is - // used whenever a default config is provided to be used while waiting for a response. - init_target_.ready(); - }) { + tls_(factory_context.threadLocal()), init_target_("DynamicFilterConfigProviderImpl", + [this]() { + subscription_->start(); + // This init target is used to activate + // the subscription but not wait for a + // response. It is used whenever a default + // config is provided to be used while + // waiting for a response. + init_target_.ready(); + }), + last_filter_in_filter_chain_(last_filter_in_filter_chain), + filter_chain_type_(filter_chain_type) { + subscription_->filter_config_providers_.insert(this); tls_.set([](Event::Dispatcher&) { return std::make_shared(); }); } @@ -78,6 +86,13 @@ void DynamicFilterConfigProviderImpl::onConfigUpdate(Envoy::Http::FilterFactoryC }); } +void DynamicFilterConfigProviderImpl::validateTerminalFilter(const std::string& name, + const std::string& filter_type, + bool is_terminal_filter) { + Config::Utility::validateTerminalFilters(name, filter_type, filter_chain_type_, + is_terminal_filter, last_filter_in_filter_chain_); +} + void DynamicFilterConfigProviderImpl::onConfigRemoved( Config::ConfigAppliedCb applied_on_all_threads) { tls_.runOnAllThreads( @@ -156,9 +171,14 @@ void FilterConfigSubscription::onConfigUpdate( } ProtobufTypes::MessagePtr message = Config::Utility::translateAnyToFactoryConfig( filter_config.typed_config(), validator_, factory); + bool is_terminal_filter = factory.isTerminalFilterByProto(*message, factory_context_); + for (auto* provider : filter_config_providers_) { + provider->validateTerminalFilter(filter_config_name_, factory.name(), is_terminal_filter); + } Envoy::Http::FilterFactoryCb factory_callback = factory.createFilterFactoryFromProto(*message, stat_prefix_, factory_context_); ENVOY_LOG(debug, "Updating filter config {}", filter_config_name_); + Common::applyToAllWithCleanup( filter_config_providers_, [&factory_callback, &version_info](DynamicFilterConfigProviderImpl* provider, @@ -170,6 +190,8 @@ void FilterConfigSubscription::onConfigUpdate( last_config_ = factory_callback; last_type_url_ = type_url; last_version_info_ = version_info; + last_filter_name_ = factory.name(); + last_filter_is_terminal_ = is_terminal_filter; } void FilterConfigSubscription::onConfigUpdate( @@ -188,6 +210,8 @@ void FilterConfigSubscription::onConfigUpdate( last_config_hash_ = 0; last_config_ = absl::nullopt; last_type_url_ = ""; + last_filter_is_terminal_ = false; + last_filter_name_ = ""; } else if (!added_resources.empty()) { onConfigUpdate(added_resources, added_resources[0].get().version()); } @@ -235,7 +259,8 @@ std::shared_ptr FilterConfigProviderManagerImpl::getSu DynamicFilterConfigProviderPtr FilterConfigProviderManagerImpl::createDynamicFilterConfigProvider( const envoy::config::core::v3::ExtensionConfigSource& config_source, const std::string& filter_config_name, Server::Configuration::FactoryContext& factory_context, - const std::string& stat_prefix) { + const std::string& stat_prefix, bool last_filter_in_filter_config, + const std::string& filter_chain_type) { auto subscription = getSubscription(config_source.config_source(), filter_config_name, factory_context, stat_prefix); // For warming, wait until the subscription receives the first response to indicate readiness. @@ -249,6 +274,7 @@ DynamicFilterConfigProviderPtr FilterConfigProviderManagerImpl::createDynamicFil auto factory_type_url = TypeUtil::typeUrlToDescriptorFullName(type_url); require_type_urls.emplace(factory_type_url); } + Envoy::Http::FilterFactoryCb default_config = nullptr; if (config_source.has_default_config()) { auto* default_factory = @@ -265,12 +291,17 @@ DynamicFilterConfigProviderPtr FilterConfigProviderManagerImpl::createDynamicFil ProtobufTypes::MessagePtr message = Config::Utility::translateAnyToFactoryConfig( config_source.default_config(), factory_context.messageValidationVisitor(), *default_factory); + Config::Utility::validateTerminalFilters( + filter_config_name, default_factory->name(), filter_chain_type, + default_factory->isTerminalFilterByProto(*message, factory_context), + last_filter_in_filter_config); default_config = default_factory->createFilterFactoryFromProto(*message, stat_prefix, factory_context); } auto provider = std::make_unique( - subscription, require_type_urls, factory_context, default_config); + subscription, require_type_urls, factory_context, default_config, + last_filter_in_filter_config, filter_chain_type); // Ensure the subscription starts if it has not already. if (config_source.apply_default_config_without_warming()) { @@ -286,6 +317,8 @@ DynamicFilterConfigProviderPtr FilterConfigProviderManagerImpl::createDynamicFil if (subscription->lastConfig().has_value()) { TRY_ASSERT_MAIN_THREAD { provider->validateTypeUrl(subscription->lastTypeUrl()); + provider->validateTerminalFilter(filter_config_name, subscription->lastFilterName(), + subscription->isLastFilterTerminal()); last_config_valid = true; } END_TRY catch (const EnvoyException& e) { diff --git a/source/common/filter/http/filter_config_discovery_impl.h b/source/common/filter/http/filter_config_discovery_impl.h index 05aed5646ff9d..5027bc863f5f5 100644 --- a/source/common/filter/http/filter_config_discovery_impl.h +++ b/source/common/filter/http/filter_config_discovery_impl.h @@ -37,11 +37,15 @@ class DynamicFilterConfigProviderImpl : public DynamicFilterConfigProvider { DynamicFilterConfigProviderImpl(FilterConfigSubscriptionSharedPtr& subscription, const absl::flat_hash_set& require_type_urls, Server::Configuration::FactoryContext& factory_context, - Envoy::Http::FilterFactoryCb default_config); + Envoy::Http::FilterFactoryCb default_config, + bool last_filter_in_filter_chain, + const std::string& filter_chain_type); + ~DynamicFilterConfigProviderImpl() override; void validateTypeUrl(const std::string& type_url) const; - + void validateTerminalFilter(const std::string& name, const std::string& filter_type, + bool is_terminal_filter); // Config::ExtensionConfigProvider const std::string& name() override; absl::optional config() override; @@ -74,6 +78,8 @@ class DynamicFilterConfigProviderImpl : public DynamicFilterConfigProvider { // case no warming is requested by any other filter config provider. Init::TargetImpl init_target_; + const bool last_filter_in_filter_chain_; + const std::string filter_chain_type_; friend class FilterConfigProviderManagerImpl; }; @@ -115,6 +121,8 @@ class FilterConfigSubscription const absl::optional& lastConfig() { return last_config_; } const std::string& lastTypeUrl() { return last_type_url_; } const std::string& lastVersionInfo() { return last_version_info_; } + const std::string& lastFilterName() { return last_filter_name_; } + bool isLastFilterTerminal() { return last_filter_is_terminal_; } void incrementConflictCounter(); private: @@ -134,6 +142,8 @@ class FilterConfigSubscription absl::optional last_config_{absl::nullopt}; std::string last_type_url_; std::string last_version_info_; + std::string last_filter_name_; + bool last_filter_is_terminal_; Server::Configuration::FactoryContext& factory_context_; ProtobufMessage::ValidationVisitor& validator_; @@ -183,7 +193,8 @@ class FilterConfigProviderManagerImpl : public FilterConfigProviderManager, DynamicFilterConfigProviderPtr createDynamicFilterConfigProvider( const envoy::config::core::v3::ExtensionConfigSource& config_source, const std::string& filter_config_name, Server::Configuration::FactoryContext& factory_context, - const std::string& stat_prefix) override; + const std::string& stat_prefix, bool last_filter_in_filter_chain, + const std::string& filter_chain_type) override; FilterConfigProviderPtr createStaticFilterConfigProvider(const Envoy::Http::FilterFactoryCb& config, diff --git a/source/extensions/filters/http/common/factory_base.h b/source/extensions/filters/http/common/factory_base.h index 6aee7a15b36a6..35073f3d540ef 100644 --- a/source/extensions/filters/http/common/factory_base.h +++ b/source/extensions/filters/http/common/factory_base.h @@ -42,10 +42,21 @@ class FactoryBase : public Server::Configuration::NamedHttpFilterConfigFactory { std::string name() const override { return name_; } + bool isTerminalFilterByProto(const Protobuf::Message& proto_config, + Server::Configuration::FactoryContext& context) override { + return isTerminalFilterByProtoTyped(MessageUtil::downcastAndValidate( + proto_config, context.messageValidationVisitor()), + context); + } + protected: FactoryBase(const std::string& name) : name_(name) {} private: + virtual bool isTerminalFilterByProtoTyped(const ConfigProto&, + Server::Configuration::FactoryContext&) { + return false; + } virtual Http::FilterFactoryCb createFilterFactoryFromProtoTyped(const ConfigProto& proto_config, const std::string& stats_prefix, diff --git a/source/extensions/filters/http/router/config.h b/source/extensions/filters/http/router/config.h index d17cafe32ef94..604c3423481bf 100644 --- a/source/extensions/filters/http/router/config.h +++ b/source/extensions/filters/http/router/config.h @@ -22,9 +22,11 @@ class RouterFilterConfig public: RouterFilterConfig() : FactoryBase(HttpFilterNames::get().Router) {} - bool isTerminalFilter() override { return true; } - private: + bool isTerminalFilterByProtoTyped(const envoy::extensions::filters::http::router::v3::Router&, + Server::Configuration::FactoryContext&) override { + return true; + } Http::FilterFactoryCb createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::router::v3::Router& proto_config, const std::string& stat_prefix, Server::Configuration::FactoryContext& context) override; diff --git a/source/extensions/filters/network/common/factory_base.h b/source/extensions/filters/network/common/factory_base.h index ba5bcf52159ae..9e818369b4a8e 100644 --- a/source/extensions/filters/network/common/factory_base.h +++ b/source/extensions/filters/network/common/factory_base.h @@ -44,13 +44,22 @@ class FactoryBase : public Server::Configuration::NamedNetworkFilterConfigFactor std::string name() const override { return name_; } - bool isTerminalFilter() override { return is_terminal_filter_; } + bool isTerminalFilterByProto(const Protobuf::Message& proto_config, + Server::Configuration::FactoryContext& context) override { + return isTerminalFilterByProtoTyped(MessageUtil::downcastAndValidate( + proto_config, context.messageValidationVisitor()), + context); + } protected: FactoryBase(const std::string& name, bool is_terminal = false) : name_(name), is_terminal_filter_(is_terminal) {} private: + virtual bool isTerminalFilterByProtoTyped(const ConfigProto&, + Server::Configuration::FactoryContext&) { + return is_terminal_filter_; + } virtual Network::FilterFactoryCb createFilterFactoryFromProtoTyped(const ConfigProto& proto_config, Server::Configuration::FactoryContext& context) PURE; diff --git a/source/extensions/filters/network/direct_response/config.cc b/source/extensions/filters/network/direct_response/config.cc index 1e8691234404a..ff680dc835714 100644 --- a/source/extensions/filters/network/direct_response/config.cc +++ b/source/extensions/filters/network/direct_response/config.cc @@ -32,7 +32,11 @@ class DirectResponseConfigFactory }; } - bool isTerminalFilter() override { return true; } + bool isTerminalFilterByProtoTyped( + const envoy::extensions::filters::network::direct_response::v3::Config&, + Server::Configuration::FactoryContext&) override { + return true; + } }; /** diff --git a/source/extensions/filters/network/echo/config.cc b/source/extensions/filters/network/echo/config.cc index 179c14d2a3df7..119b1252d8931 100644 --- a/source/extensions/filters/network/echo/config.cc +++ b/source/extensions/filters/network/echo/config.cc @@ -29,7 +29,10 @@ class EchoConfigFactory }; } - bool isTerminalFilter() override { return true; } + bool isTerminalFilterByProtoTyped(const envoy::extensions::filters::network::echo::v3::Echo&, + Server::Configuration::FactoryContext&) override { + return true; + } }; /** diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 03562a77568ea..4b6b0ba70c64a 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -505,8 +505,8 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( void HttpConnectionManagerConfig::processFilter( const envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter& proto_config, - int i, absl::string_view prefix, FilterFactoriesList& filter_factories, - const char* filter_chain_type, bool last_filter_in_current_config) { + int i, const std::string& prefix, FilterFactoriesList& filter_factories, + const std::string& filter_chain_type, bool last_filter_in_current_config) { ENVOY_LOG(debug, " {} filter #{}", prefix, i); if (proto_config.config_type_case() == envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter::ConfigTypeCase:: @@ -524,7 +524,7 @@ void HttpConnectionManagerConfig::processFilter( proto_config, context_.messageValidationVisitor(), factory); Http::FilterFactoryCb callback = factory.createFilterFactoryFromProto(*message, stats_prefix_, context_); - bool is_terminal = factory.isTerminalFilter(); + bool is_terminal = factory.isTerminalFilterByProto(*message, context_); Config::Utility::validateTerminalFilters(proto_config.name(), factory.name(), filter_chain_type, is_terminal, last_filter_in_current_config); auto filter_config_provider = filter_config_provider_manager_.createStaticFilterConfigProvider( @@ -542,7 +542,7 @@ void HttpConnectionManagerConfig::processFilter( void HttpConnectionManagerConfig::processDynamicFilterConfig( const std::string& name, const envoy::config::core::v3::ExtensionConfigSource& config_discovery, - FilterFactoriesList& filter_factories, const char* filter_chain_type, + FilterFactoriesList& filter_factories, const std::string& filter_chain_type, bool last_filter_in_current_config) { ENVOY_LOG(debug, " dynamic filter name: {}", name); if (config_discovery.apply_default_config_without_warming() && @@ -558,13 +558,11 @@ void HttpConnectionManagerConfig::processDynamicFilterConfig( throw EnvoyException( fmt::format("Error: no factory found for a required type URL {}.", factory_type_url)); } - Config::Utility::validateTerminalFilters(name, factory->name(), filter_chain_type, - factory->isTerminalFilter(), - last_filter_in_current_config); } auto filter_config_provider = filter_config_provider_manager_.createDynamicFilterConfigProvider( - config_discovery, name, context_, stats_prefix_); + config_discovery, name, context_, stats_prefix_, last_filter_in_current_config, + filter_chain_type); filter_factories.push_back(std::move(filter_config_provider)); } diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index d2c889a12aa98..f6685a644fb69 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -186,12 +186,13 @@ class HttpConnectionManagerConfig : Logger::Loggable, void processFilter(const envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter& proto_config, - int i, absl::string_view prefix, FilterFactoriesList& filter_factories, - const char* filter_chain_type, bool last_filter_in_current_config); + int i, const std::string& prefix, FilterFactoriesList& filter_factories, + const std::string& filter_chain_type, bool last_filter_in_current_config); void processDynamicFilterConfig(const std::string& name, const envoy::config::core::v3::ExtensionConfigSource& config_discovery, - FilterFactoriesList& filter_factories, const char* filter_chain_type, + FilterFactoriesList& filter_factories, + const std::string& filter_chain_type, bool last_filter_in_current_config); void createFilterChainForFactories(Http::FilterChainFactoryCallbacks& callbacks, const FilterFactoriesList& filter_factories); diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index a89344806468c..81b44c3d36316 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -107,11 +107,12 @@ std::vector ProdListenerComponentFactory::createNetwor Config::Utility::getAndCheckFactory( proto_config); - Config::Utility::validateTerminalFilters(filters[i].name(), factory.name(), "network", - factory.isTerminalFilter(), i == filters.size() - 1); - auto message = Config::Utility::translateToFactoryConfig( proto_config, filter_chain_factory_context.messageValidationVisitor(), factory); + Config::Utility::validateTerminalFilters( + filters[i].name(), factory.name(), "network", + factory.isTerminalFilterByProto(*message, filter_chain_factory_context), + i == filters.size() - 1); Network::FilterFactoryCb callback = factory.createFilterFactoryFromProto(*message, filter_chain_factory_context); ret.push_back(callback); diff --git a/test/common/filter/http/filter_config_discovery_impl_test.cc b/test/common/filter/http/filter_config_discovery_impl_test.cc index 70e7a36e80c0b..33cff50a28367 100644 --- a/test/common/filter/http/filter_config_discovery_impl_test.cc +++ b/test/common/filter/http/filter_config_discovery_impl_test.cc @@ -78,7 +78,9 @@ class FilterConfigDiscoveryImplTest : public FilterConfigDiscoveryTestBase { ~FilterConfigDiscoveryImplTest() override { factory_context_.thread_local_.shutdownThread(); } DynamicFilterConfigProviderPtr createProvider(std::string name, bool warm, - bool default_configuration) { + bool default_configuration, + bool last_filter_config = true) { + EXPECT_CALL(init_manager_, add(_)); envoy::config::core::v3::ExtensionConfigSource config_source; @@ -107,11 +109,11 @@ config_source: { ads: {} } } return filter_config_provider_manager_->createDynamicFilterConfigProvider( - config_source, name, factory_context_, "xds."); + config_source, name, factory_context_, "xds.", last_filter_config, "http"); } - void setup(bool warm = true, bool default_configuration = false) { - provider_ = createProvider("foo", warm, default_configuration); + void setup(bool warm = true, bool default_configuration = false, bool last_filter_config = true) { + provider_ = createProvider("foo", warm, default_configuration, last_filter_config); callbacks_ = factory_context_.cluster_manager_.subscription_factory_.callbacks_; EXPECT_CALL(*factory_context_.cluster_manager_.subscription_factory_.subscription_, start(_)); if (!warm) { @@ -376,6 +378,32 @@ TEST_F(FilterConfigDiscoveryImplTest, DualProvidersInvalid) { EXPECT_EQ(0UL, scope_.counter("xds.extension_config_discovery.foo.config_reload").value()); } +// Raise exception when filter is not the last filter in filter chain, but the filter is terminal +// filter. +TEST_F(FilterConfigDiscoveryImplTest, TerminalFilterInvalid) { + InSequence s; + setup(true, false, false); + const std::string response_yaml = R"EOF( + version_info: "1" + resources: + - "@type": type.googleapis.com/envoy.config.core.v3.TypedExtensionConfig + name: foo + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + )EOF"; + const auto response = + TestUtility::parseYaml(response_yaml); + const auto decoded_resources = + TestUtility::decodeResources(response); + EXPECT_CALL(init_watcher_, ready()); + EXPECT_THROW_WITH_MESSAGE( + callbacks_->onConfigUpdate(decoded_resources.refvec_, response.version_info()), + EnvoyException, + "Error: terminal filter named foo of type envoy.filters.http.router must be the last filter " + "in a http filter chain."); + EXPECT_EQ(0UL, scope_.counter("xds.extension_config_discovery.foo.config_reload").value()); +} + } // namespace } // namespace Http } // namespace Filter diff --git a/test/extensions/filters/network/dubbo_proxy/config_test.cc b/test/extensions/filters/network/dubbo_proxy/config_test.cc index bdf4b37204d0f..d98fe3feef351 100644 --- a/test/extensions/filters/network/dubbo_proxy/config_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/config_test.cc @@ -62,7 +62,7 @@ TEST_F(DubboFilterConfigTest, ValidProtoConfiguration) { NiceMock context; DubboProxyFilterConfigFactory factory; Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(config, context); - EXPECT_TRUE(factory.isTerminalFilter()); + EXPECT_TRUE(factory.isTerminalFilterByProto(config, context)); Network::MockConnection connection; EXPECT_CALL(connection, addReadFilter(_)); cb(connection); diff --git a/test/extensions/filters/network/http_connection_manager/config_test.cc b/test/extensions/filters/network/http_connection_manager/config_test.cc index 40ec2c38e4f98..367f615749be0 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_test.cc @@ -1278,7 +1278,7 @@ stat_prefix: router EXPECT_CALL(context_.thread_local_, allocateSlot()); Network::FilterFactoryCb cb1 = factory.createFilterFactoryFromProto(proto_config, context_); Network::FilterFactoryCb cb2 = factory.createFilterFactoryFromProto(proto_config, context_); - EXPECT_TRUE(factory.isTerminalFilter()); + EXPECT_TRUE(factory.isTerminalFilterByProto(proto_config, context_)); } TEST_F(HttpConnectionManagerConfigTest, BadHttpConnectionMangerConfig) { @@ -1850,6 +1850,7 @@ stat_prefix: router config_source: { resource_api_version: V3, ads: {} } default_config: "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck + pass_through_mode: false type_urls: - type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck )EOF"; diff --git a/test/extensions/filters/network/redis_proxy/config_test.cc b/test/extensions/filters/network/redis_proxy/config_test.cc index ce0276dfae1a6..798c8f573fda7 100644 --- a/test/extensions/filters/network/redis_proxy/config_test.cc +++ b/test/extensions/filters/network/redis_proxy/config_test.cc @@ -89,7 +89,7 @@ stat_prefix: foo NiceMock context; RedisProxyFilterConfigFactory factory; Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, context); - EXPECT_TRUE(factory.isTerminalFilter()); + EXPECT_TRUE(factory.isTerminalFilterByProto(proto_config, context)); Network::MockConnection connection; EXPECT_CALL(connection, addReadFilter(_)); cb(connection); @@ -118,7 +118,7 @@ stat_prefix: foo NiceMock context; RedisProxyFilterConfigFactory factory; Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, context); - EXPECT_TRUE(factory.isTerminalFilter()); + EXPECT_TRUE(factory.isTerminalFilterByProto(proto_config, context)); Network::MockConnection connection; EXPECT_CALL(connection, addReadFilter(_)); cb(connection); @@ -139,7 +139,7 @@ stat_prefix: foo NiceMock context; RedisProxyFilterConfigFactory factory; Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, context); - EXPECT_TRUE(factory.isTerminalFilter()); + EXPECT_TRUE(factory.isTerminalFilterByProto(proto_config, context)); Network::MockConnection connection; EXPECT_CALL(connection, addReadFilter(_)); cb(connection); @@ -200,7 +200,7 @@ stat_prefix: foo NiceMock context; RedisProxyFilterConfigFactory factory; Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, context); - EXPECT_TRUE(factory.isTerminalFilter()); + EXPECT_TRUE(factory.isTerminalFilterByProto(proto_config, context)); Network::MockConnection connection; EXPECT_CALL(connection, addReadFilter(_)); cb(connection); diff --git a/test/extensions/filters/network/tcp_proxy/config_test.cc b/test/extensions/filters/network/tcp_proxy/config_test.cc index efb3be1fb8faf..1de0240088ee4 100644 --- a/test/extensions/filters/network/tcp_proxy/config_test.cc +++ b/test/extensions/filters/network/tcp_proxy/config_test.cc @@ -148,7 +148,7 @@ TEST(ConfigTest, ConfigTest) { config.set_stat_prefix("prefix"); config.set_cluster("cluster"); - EXPECT_TRUE(factory.isTerminalFilter()); + EXPECT_TRUE(factory.isTerminalFilterByProto(config, context)); Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(config, context); Network::MockConnection connection; diff --git a/test/extensions/filters/network/thrift_proxy/config_test.cc b/test/extensions/filters/network/thrift_proxy/config_test.cc index 9b3db4c4ebc8d..aa52a2143ed4b 100644 --- a/test/extensions/filters/network/thrift_proxy/config_test.cc +++ b/test/extensions/filters/network/thrift_proxy/config_test.cc @@ -57,7 +57,7 @@ class ThriftFilterConfigTestBase { void testConfig(envoy::extensions::filters::network::thrift_proxy::v3::ThriftProxy& config) { Network::FilterFactoryCb cb; EXPECT_NO_THROW({ cb = factory_.createFilterFactoryFromProto(config, context_); }); - EXPECT_TRUE(factory_.isTerminalFilter()); + EXPECT_TRUE(factory_.isTerminalFilterByProto(config, context_)); Network::MockConnection connection; EXPECT_CALL(connection, addReadFilter(_)); diff --git a/test/integration/BUILD b/test/integration/BUILD index 96225d6c76d6b..499a76930b776 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1097,6 +1097,8 @@ envoy_cc_test( deps = [ ":http_integration_lib", "//test/common/grpc:grpc_client_integration_lib", + "//test/integration/filters:set_is_terminal_filter_config_proto_cc_proto", + "//test/integration/filters:set_is_terminal_filter_lib", "//test/integration/filters:set_response_code_filter_config_proto_cc_proto", "//test/integration/filters:set_response_code_filter_lib", "//test/test_common:utility_lib", diff --git a/test/integration/extension_discovery_integration_test.cc b/test/integration/extension_discovery_integration_test.cc index 8d8271a21f89c..d1a9b80cc3dfd 100644 --- a/test/integration/extension_discovery_integration_test.cc +++ b/test/integration/extension_discovery_integration_test.cc @@ -4,6 +4,7 @@ #include "envoy/service/extension/v3/config_discovery.pb.h" #include "test/common/grpc/grpc_client_integration.h" +#include "test/integration/filters/set_is_terminal_filter_config.pb.h" #include "test/integration/filters/set_response_code_filter_config.pb.h" #include "test/integration/http_integration.h" #include "test/test_common/utility.h" @@ -50,6 +51,8 @@ std::string allowAllConfig() { return "code: 200"; } std::string invalidConfig() { return "code: 90"; } +std::string terminalFilterConfig() { return "is_terminal_filter: true"; } + class ExtensionDiscoveryIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, public HttpIntegrationTest { public: @@ -68,6 +71,8 @@ class ExtensionDiscoveryIntegrationTest : public Grpc::GrpcClientIntegrationPara auto* discovery = filter->mutable_config_discovery(); discovery->add_type_urls( "type.googleapis.com/test.integration.filters.SetResponseCodeFilterConfig"); + discovery->add_type_urls( + "type.googleapis.com/test.integration.filters.SetIsTerminalFilterConfig"); discovery->add_type_urls( "type.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher"); if (set_default_config) { @@ -222,19 +227,29 @@ class ExtensionDiscoveryIntegrationTest : public Grpc::GrpcClientIntegrationPara } void sendXdsResponse(const std::string& name, const std::string& version, - const std::string& yaml_config, bool ttl = false) { + const std::string& yaml_config, bool ttl = false, + bool is_set_resp_code_config = true) { envoy::service::discovery::v3::DiscoveryResponse response; response.set_version_info(version); response.set_type_url("type.googleapis.com/envoy.config.core.v3.TypedExtensionConfig"); - const auto configuration = - TestUtility::parseYaml( - yaml_config); + envoy::config::core::v3::TypedExtensionConfig typed_config; typed_config.set_name(name); envoy::service::discovery::v3::Resource resource; resource.set_name(name); - typed_config.mutable_typed_config()->PackFrom(configuration); + + if (is_set_resp_code_config) { + const auto configuration = + TestUtility::parseYaml( + yaml_config); + typed_config.mutable_typed_config()->PackFrom(configuration); + } else { + const auto configuration = + TestUtility::parseYaml( + yaml_config); + typed_config.mutable_typed_config()->PackFrom(configuration); + } resource.mutable_resource()->PackFrom(typed_config); if (ttl) { resource.mutable_ttl()->set_seconds(1); @@ -692,5 +707,28 @@ TEST_P(ExtensionDiscoveryIntegrationTest, DestroyDuringInit) { ecds_connection_.reset(); } +// Validate that a listener update should fail if the subscribed extension configuration make filter +// terminal but the filter position is not at the last position at filter chain. +TEST_P(ExtensionDiscoveryIntegrationTest, BasicFailTerminalFilterNotAtEndOfFilterChain) { + on_server_init_function_ = [&]() { waitXdsStream(); }; + addDynamicFilter("foo", false, false); + initialize(); + test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + registerTestServerPorts({"http"}); + sendXdsResponse("foo", "1", terminalFilterConfig(), false, false); + test_server_->waitForCounterGe("http.config_test.extension_config_discovery.foo.config_fail", 1); + test_server_->waitUntilListenersReady(); + test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/"}, {":scheme", "http"}, {":authority", "host"}}; + auto response = codec_client_->makeHeaderOnlyRequest(request_headers); + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("500", response->headers().getStatusValue()); +} + } // namespace } // namespace Envoy diff --git a/test/integration/filters/BUILD b/test/integration/filters/BUILD index 6d5782b380704..5d8c073a5bb76 100644 --- a/test/integration/filters/BUILD +++ b/test/integration/filters/BUILD @@ -265,6 +265,25 @@ envoy_proto_library( srcs = [":set_response_code_filter_config.proto"], ) +envoy_cc_test_library( + name = "set_is_terminal_filter_lib", + srcs = [ + "set_is_terminal_filter.cc", + ], + deps = [ + ":set_is_terminal_filter_config_proto_cc_proto", + "//include/envoy/http:filter_interface", + "//include/envoy/registry", + "//source/extensions/filters/http/common:factory_base_lib", + "//source/extensions/filters/http/common:pass_through_filter_lib", + ], +) + +envoy_proto_library( + name = "set_is_terminal_filter_config_proto", + srcs = [":set_is_terminal_filter_config.proto"], +) + envoy_cc_test_library( name = "stop_iteration_and_continue", srcs = [ diff --git a/test/integration/filters/set_is_terminal_filter.cc b/test/integration/filters/set_is_terminal_filter.cc new file mode 100644 index 0000000000000..bc693629451fb --- /dev/null +++ b/test/integration/filters/set_is_terminal_filter.cc @@ -0,0 +1,42 @@ +#include + +#include "envoy/http/filter.h" +#include "envoy/registry/registry.h" + +#include "extensions/filters/http/common/factory_base.h" +#include "extensions/filters/http/common/pass_through_filter.h" + +#include "test/integration/filters/set_is_terminal_filter_config.pb.h" +#include "test/integration/filters/set_is_terminal_filter_config.pb.validate.h" + +#include "absl/strings/match.h" + +namespace Envoy { + +// A test filter that control whether it's a terminal filter by protobuf. +class SetIsTerminalFilter : public Http::PassThroughFilter {}; + +class SetIsTerminalFilterFactory : public Extensions::HttpFilters::Common::FactoryBase< + test::integration::filters::SetIsTerminalFilterConfig> { +public: + SetIsTerminalFilterFactory() : FactoryBase("set-is-terminal-filter") {} + +private: + Http::FilterFactoryCb + createFilterFactoryFromProtoTyped(const test::integration::filters::SetIsTerminalFilterConfig&, + const std::string&, + Server::Configuration::FactoryContext&) override { + + return [](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamFilter(std::make_shared()); + }; + } + bool isTerminalFilterByProtoTyped( + const test::integration::filters::SetIsTerminalFilterConfig& proto_config, + Server::Configuration::FactoryContext&) override { + return proto_config.is_terminal_filter(); + } +}; + +REGISTER_FACTORY(SetIsTerminalFilterFactory, Server::Configuration::NamedHttpFilterConfigFactory); +} // namespace Envoy diff --git a/test/integration/filters/set_is_terminal_filter_config.proto b/test/integration/filters/set_is_terminal_filter_config.proto new file mode 100644 index 0000000000000..5a12a4289c64d --- /dev/null +++ b/test/integration/filters/set_is_terminal_filter_config.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package test.integration.filters; + +import "validate/validate.proto"; + +message SetIsTerminalFilterConfig { + bool is_terminal_filter = 1; +} diff --git a/test/integration/tcp_conn_pool_integration_test.cc b/test/integration/tcp_conn_pool_integration_test.cc index a4cca156cb67c..bf02e97f5ccf0 100644 --- a/test/integration/tcp_conn_pool_integration_test.cc +++ b/test/integration/tcp_conn_pool_integration_test.cc @@ -101,7 +101,10 @@ class TestFilterConfigFactory : public Server::Configuration::NamedNetworkFilter } std::string name() const override { CONSTRUCT_ON_FIRST_USE(std::string, "envoy.test.router"); } - bool isTerminalFilter() override { return true; } + bool isTerminalFilterByProto(const Protobuf::Message&, + Server::Configuration::FactoryContext&) override { + return true; + } }; } // namespace diff --git a/test/server/config_validation/server_test.cc b/test/server/config_validation/server_test.cc index 0228d6dbf9518..8ec76fe3a9615 100644 --- a/test/server/config_validation/server_test.cc +++ b/test/server/config_validation/server_test.cc @@ -104,7 +104,10 @@ class RuntimeFeatureValidationServerTest : public ValidationServerTest { return ProtobufTypes::MessagePtr{new ProtobufWkt::Struct()}; } - bool isTerminalFilter() override { return true; } + bool isTerminalFilterByProto(const Protobuf::Message&, + Server::Configuration::FactoryContext&) override { + return true; + } }; }; diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 805214bbccb14..fc370054d27a2 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -495,7 +495,10 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, NotTerminalLast) { filter_chains: - filters: - name: envoy.filters.network.tcp_proxy - typed_config: {} + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: tcp + cluster: cluster - name: unknown_but_will_not_be_processed typed_config: {} )EOF"; @@ -539,7 +542,11 @@ class TestStatsConfigFactory : public Configuration::NamedNetworkFilterConfigFac } std::string name() const override { return "stats_test"; } - bool isTerminalFilter() override { return true; } + + bool isTerminalFilterByProto(const Protobuf::Message&, + Server::Configuration::FactoryContext&) override { + return true; + } private: Network::FilterFactoryCb commonFilterFactory(Configuration::FactoryContext& context) { From bb45090ca57a0697ffa53825d705e6fda65063b1 Mon Sep 17 00:00:00 2001 From: Xin Date: Mon, 19 Apr 2021 19:36:57 -0400 Subject: [PATCH 040/209] [delta xds]: remove cached nonces on reconnection (#16037) See #15767, remove cached nonces on reconnection. Risk Level: LOW Testing: unit test Fixes #15767 Signed-off-by: Xin Zhuang Co-authored-by: alyssawilk --- source/common/config/new_grpc_mux_impl.cc | 1 + source/common/config/new_grpc_mux_impl.h | 6 ++ source/common/config/pausable_ack_queue.cc | 3 + source/common/config/pausable_ack_queue.h | 1 + test/common/config/new_grpc_mux_impl_test.cc | 58 +++++++++++++++++++- 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/source/common/config/new_grpc_mux_impl.cc b/source/common/config/new_grpc_mux_impl.cc index 58890dfbdf08e..0a5e6997bc6a2 100644 --- a/source/common/config/new_grpc_mux_impl.cc +++ b/source/common/config/new_grpc_mux_impl.cc @@ -108,6 +108,7 @@ void NewGrpcMuxImpl::onStreamEstablished() { UNREFERENCED_PARAMETER(type_url); subscription->sub_state_.markStreamFresh(); } + pausable_ack_queue_.clear(); trySendDiscoveryRequests(); } diff --git a/source/common/config/new_grpc_mux_impl.h b/source/common/config/new_grpc_mux_impl.h index 8c10e477da3bb..002f10eb14988 100644 --- a/source/common/config/new_grpc_mux_impl.h +++ b/source/common/config/new_grpc_mux_impl.h @@ -67,6 +67,12 @@ class NewGrpcMuxImpl // TODO(fredlas) remove this from the GrpcMux interface. void start() override; + GrpcStream& + grpcStreamForTest() { + return grpc_stream_; + } + struct SubscriptionStuff { SubscriptionStuff(const std::string& type_url, const LocalInfo::LocalInfo& local_info, const bool use_namespace_matching, Event::Dispatcher& dispatcher) diff --git a/source/common/config/pausable_ack_queue.cc b/source/common/config/pausable_ack_queue.cc index dc6f01773f6a1..b18cb9754f5eb 100644 --- a/source/common/config/pausable_ack_queue.cc +++ b/source/common/config/pausable_ack_queue.cc @@ -20,6 +20,9 @@ bool PausableAckQueue::empty() { return true; } +// In the event of a reconnection, clear all the cached nonces. +void PausableAckQueue::clear() { storage_.clear(); } + const UpdateAck& PausableAckQueue::front() { for (const auto& entry : storage_) { if (pauses_[entry.type_url_] == 0) { diff --git a/source/common/config/pausable_ack_queue.h b/source/common/config/pausable_ack_queue.h index 5535e262598f5..eb04358398df7 100644 --- a/source/common/config/pausable_ack_queue.h +++ b/source/common/config/pausable_ack_queue.h @@ -23,6 +23,7 @@ class PausableAckQueue { void pause(const std::string& type_url); void resume(const std::string& type_url); bool paused(const std::string& type_url) const; + void clear(); private: // It's ok for non-existent subs to be paused/resumed. The cleanest way to support that is to give diff --git a/test/common/config/new_grpc_mux_impl_test.cc b/test/common/config/new_grpc_mux_impl_test.cc index b4962a3f8af41..d7e43ed2ad0f0 100644 --- a/test/common/config/new_grpc_mux_impl_test.cc +++ b/test/common/config/new_grpc_mux_impl_test.cc @@ -2,6 +2,7 @@ #include "envoy/config/endpoint/v3/endpoint.pb.h" #include "envoy/config/endpoint/v3/endpoint.pb.validate.h" +#include "envoy/event/timer.h" #include "envoy/service/discovery/v3/discovery.pb.h" #include "common/common/empty_string.h" @@ -29,11 +30,13 @@ #include "gtest/gtest.h" using testing::_; +using testing::DoAll; using testing::InSequence; using testing::Invoke; using testing::NiceMock; using testing::Return; using testing::ReturnRef; +using testing::SaveArg; namespace Envoy { namespace Config { @@ -63,7 +66,8 @@ class NewGrpcMuxImplTestBase : public testing::Test { const std::vector& resource_names_unsubscribe, const std::string& nonce = "", const Protobuf::int32 error_code = Grpc::Status::WellKnownGrpcStatus::Ok, - const std::string& error_message = "") { + const std::string& error_message = "", + const std::map& initial_resource_versions = {}) { API_NO_BOOST(envoy::api::v2::DeltaDiscoveryRequest) expected_request; expected_request.mutable_node()->CopyFrom(API_DOWNGRADE(local_info_.node())); for (const auto& resource : resource_names_subscribe) { @@ -72,6 +76,9 @@ class NewGrpcMuxImplTestBase : public testing::Test { for (const auto& resource : resource_names_unsubscribe) { expected_request.add_resource_names_unsubscribe(resource); } + for (const auto& v : initial_resource_versions) { + (*expected_request.mutable_initial_resource_versions())[v.first] = v.second; + } expected_request.set_response_nonce(nonce); expected_request.set_type_url(type_url); if (error_code != Grpc::Status::WellKnownGrpcStatus::Ok) { @@ -123,6 +130,55 @@ TEST_F(NewGrpcMuxImplTest, DynamicContextParameters) { expectSendMessage("foo", {}, {"x", "y"}); } +// Validate cached nonces are cleared on reconnection. +TEST_F(NewGrpcMuxImplTest, ReconnectionResetsNonceAndAcks) { + Event::MockTimer* grpc_stream_retry_timer{new Event::MockTimer()}; + Event::MockTimer* ttl_mgr_timer{new NiceMock()}; + Event::TimerCb grpc_stream_retry_timer_cb; + EXPECT_CALL(dispatcher_, createTimer_(_)) + .WillOnce( + testing::DoAll(SaveArg<0>(&grpc_stream_retry_timer_cb), Return(grpc_stream_retry_timer))) + // Happens when adding a type url watch. + .WillRepeatedly(Return(ttl_mgr_timer)); + setup(); + InSequence s; + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + auto foo_sub = grpc_mux_->addWatch(type_url, {"x", "y"}, callbacks_, resource_decoder_, {}); + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + // Send on connection. + expectSendMessage(type_url, {"x", "y"}, {}); + grpc_mux_->start(); + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_system_version_info("3000"); + response->set_nonce("111"); + auto add_response_resource = [](const std::string& name, const std::string& version, + envoy::service::discovery::v3::DeltaDiscoveryResponse& response) { + envoy::config::endpoint::v3::ClusterLoadAssignment cla; + cla.set_cluster_name(name); + auto res = response.add_resources(); + res->set_name(name); + res->mutable_resource()->PackFrom(cla); + res->set_version(version); + }; + add_response_resource("x", "2000", *response); + add_response_resource("y", "3000", *response); + // Pause EDS to allow the ACK to be cached. + auto resume_cds = grpc_mux_->pause(type_url); + grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); + // Now disconnect. + // Grpc stream retry timer will kick in and reconnection will happen. + EXPECT_CALL(*grpc_stream_retry_timer, enableTimer(_, _)) + .WillOnce(Invoke(grpc_stream_retry_timer_cb)); + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + // initial_resource_versions should contain client side all resource:version info. + expectSendMessage(type_url, {"x", "y"}, {}, "", Grpc::Status::WellKnownGrpcStatus::Ok, "", + {{"x", "2000"}, {"y", "3000"}}); + grpc_mux_->grpcStreamForTest().onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Canceled, ""); + // Destruction of the EDS subscription will issue an "unsubscribe" request. + expectSendMessage(type_url, {}, {"x", "y"}); +} + // Test that we simply ignore a message for an unknown type_url, with no ill effects. TEST_F(NewGrpcMuxImplTest, DiscoveryResponseNonexistentSub) { setup(); From d95f0802857e6de5f7204df6f0deaf1a4c0b1c5e Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 20 Apr 2021 00:40:23 +0100 Subject: [PATCH 041/209] tooling: Update pipcheck tool (#16033) Signed-off-by: Ryan Northey --- ci/do_ci.sh | 1 + tools/dependency/BUILD | 8 +- tools/dependency/pip_check.py | 105 ++++++------ tools/dependency/tests/test_pip_check.py | 193 +++++++++++++++++++++++ 4 files changed, 253 insertions(+), 54 deletions(-) create mode 100644 tools/dependency/tests/test_pip_check.py diff --git a/ci/do_ci.sh b/ci/do_ci.sh index f906a04bbd7bb..ec81bb8c2e019 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -474,6 +474,7 @@ elif [[ "$CI_TARGET" == "tooling" ]]; then bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/base:pytest_runner -- --cov-collect /tmp/.coverage-envoy bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/base:pytest_utils -- --cov-collect /tmp/.coverage-envoy bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/code_format:pytest_python_check -- --cov-collect /tmp/.coverage-envoy + bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/dependency:pytest_pip_check -- --cov-collect /tmp/.coverage-envoy bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/testing:python_coverage -- --fail-under=95 /tmp/.coverage-envoy /source/generated/tooling exit 0 elif [[ "$CI_TARGET" == "verify_examples" ]]; then diff --git a/tools/dependency/BUILD b/tools/dependency/BUILD index 93df9e9955f06..3bbd85b8ce529 100644 --- a/tools/dependency/BUILD +++ b/tools/dependency/BUILD @@ -1,6 +1,7 @@ load("@rules_python//python:defs.bzl", "py_binary") load("@deps_pip3//:requirements.bzl", "requirement") load("//bazel:envoy_build_system.bzl", "envoy_package") +load("//tools/base:envoy_python.bzl", "envoy_py_binary") licenses(["notice"]) # Apache 2 @@ -46,11 +47,10 @@ py_binary( ], ) -py_binary( - name = "pip_check", - srcs = ["pip_check.py"], - visibility = ["//visibility:public"], +envoy_py_binary( + name = "tools.dependency.pip_check", deps = [ + "//tools/base:checker", requirement("PyYaml"), ], ) diff --git a/tools/dependency/pip_check.py b/tools/dependency/pip_check.py index 3297e8c5962fd..f0347e372990d 100755 --- a/tools/dependency/pip_check.py +++ b/tools/dependency/pip_check.py @@ -1,4 +1,15 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 + +# usage +# +# with bazel: +# +# bazel //tools/dependency:pip_check -- -h +# +# alternatively, if you have the necessary python deps available +# +# ./tools/dependency/pip_check.py -h +# import os import sys @@ -6,39 +17,20 @@ import yaml +from tools.base import checker + DEPENDABOT_CONFIG = ".github/dependabot.yml" +REQUIREMENTS_FILENAME = "requirements.txt" # TODO(phlax): add checks for: # - requirements can be installed together -# - pip-compile formatting (maybe do that in code_format) - - -# TODO(phlax): move this to a base module -class Checker(object): - """Runs check methods prefixed with `check_` and named in `self.checks` - - check methods should return the count of errors and handle writing to stderr - """ - checks = () - - def __init__(self, path: str): - self.path = path - - def error_lines(self, errors: list) -> str: - """Transform a list of errors to a string for output""" - return "\n - ".join([""] + errors) - - def run_checks(self) -> int: - """Run all configured checks and return the sum of their error counts""" - return sum(getattr(self, f"check_{check}")() for check in self.checks) +# - pip-compile formatting - def write_errors(self, errors: list, pre: str, post: str) -> None: - """Write errors to stderr with pre/post ambles""" - sys.stderr.write(f"\n{pre}: \n{self.error_lines(errors)}\n\n{post}\n\n") - -class PipChecker(Checker): +class PipChecker(checker.Checker): checks = ("dependabot",) + _dependabot_config = DEPENDABOT_CONFIG + _requirements_filename = REQUIREMENTS_FILENAME @cached_property def config_requirements(self) -> set: @@ -51,44 +43,57 @@ def config_requirements(self) -> set: @cached_property def dependabot_config(self) -> dict: """Parsed dependabot config""" - with open(os.path.join(self.path, DEPENDABOT_CONFIG)) as f: + with open(os.path.join(self.path, self.dependabot_config_path)) as f: return yaml.safe_load(f.read()) + @property + def dependabot_config_path(self) -> str: + return self._dependabot_config + @cached_property def requirements_dirs(self) -> set: """Set of found directories in the repo containing requirements.txt""" return set( root[len(self.path):] for root, dirs, files in os.walk(self.path) - if "requirements.txt" in files) + if self.requirements_filename in files) - def check_dependabot(self) -> int: - """Check that dependabot config matches requirements.txt files found in repo""" - missing_dirs = self.config_requirements - self.requirements_dirs - missing_config = self.requirements_dirs - self.config_requirements + @property + def requirements_filename(self) -> str: + return self._requirements_filename + def check_dependabot(self) -> None: + """Check that dependabot config matches requirements.txt files found in repo""" + missing_dirs = self.config_requirements.difference(self.requirements_dirs) + missing_config = self.requirements_dirs.difference(self.config_requirements) + correct = self.requirements_dirs.intersection(self.config_requirements) + if correct: + self.dependabot_success(correct) + if missing_dirs: + self.dependabot_errors( + missing_dirs, + f"Missing {self.requirements_filename} dir, specified in dependabot config") if missing_config: - self.write_errors( - sorted(missing_config), "Missing requirements config for", - "Either add the missing config or remove the file/s") + self.dependabot_errors( + missing_config, + f"Missing dependabot config for {self.requirements_filename} in dir") - if missing_dirs: - self.write_errors( - sorted(missing_dirs), "Missing requirements files for", - "Either add the missing file/s or remove the config") + def dependabot_success(self, correct: list) -> None: + self.succeed( + "dependabot", ([ + f"Correct dependabot config for {self.requirements_filename} in dir: {dirname}" + for dirname in sorted(correct) + ])) - return len(missing_config | missing_dirs) + def dependabot_errors(self, missing: list, msg: str) -> None: + self.error( + "dependabot", + ([f"[ERROR:{self.name}] (dependabot) {msg}: {dirname}" for dirname in sorted(missing)])) -def main(path: str) -> None: - errors = PipChecker(path).run_checks() - if errors: - raise SystemExit(f"Pip checks failed: {errors} errors") +def main(*args) -> int: + return PipChecker(*args).run() if __name__ == "__main__": - try: - path = sys.argv[1] - except IndexError: - raise SystemExit("Pip check tool must be called with a path argument") - main(path) + sys.exit(main(*sys.argv[1:])) diff --git a/tools/dependency/tests/test_pip_check.py b/tools/dependency/tests/test_pip_check.py new file mode 100644 index 0000000000000..0cdf07987f49e --- /dev/null +++ b/tools/dependency/tests/test_pip_check.py @@ -0,0 +1,193 @@ +from unittest.mock import patch, PropertyMock + +import pytest + +from tools.dependency import pip_check + + +def test_pip_checker_constructor(): + checker = pip_check.PipChecker("path1", "path2", "path3") + assert checker.checks == ("dependabot",) + assert checker.dependabot_config_path == pip_check.DEPENDABOT_CONFIG == ".github/dependabot.yml" + assert checker.requirements_filename == pip_check.REQUIREMENTS_FILENAME == "requirements.txt" + assert checker.args.paths == ['path1', 'path2', 'path3'] + + +def test_pip_checker_config_requirements(): + checker = pip_check.PipChecker("path1", "path2", "path3") + + config_mock = patch( + "tools.dependency.pip_check.PipChecker.dependabot_config", + new_callable=PropertyMock) + + with config_mock as m_config: + m_config.return_value.__getitem__.return_value = [ + {"package-ecosystem": "pip", "directory": "dir1"}, + {"package-ecosystem": "not-pip", "directory": "dir2"}, + {"package-ecosystem": "pip", "directory": "dir3"}] + assert checker.config_requirements == {'dir1', 'dir3'} + assert ( + list(m_config.return_value.__getitem__.call_args) + == [('updates',), {}]) + + +def test_pip_checker_dependabot_config(patches): + checker = pip_check.PipChecker("path1", "path2", "path3") + patched = patches( + "open", + "yaml.safe_load", + ("PipChecker.path", dict(new_callable=PropertyMock)), + "os.path.join", + prefix="tools.dependency.pip_check") + + with patched as (m_open, m_yaml, m_path, m_join): + assert checker.dependabot_config == m_yaml.return_value + + assert ( + list(m_join.call_args) + == [(m_path.return_value, checker._dependabot_config), {}]) + assert ( + list(m_yaml.call_args) + == [(m_open.return_value.__enter__.return_value.read.return_value,), {}]) + assert ( + list(m_open.return_value.__enter__.return_value.read.call_args,) + == [(), {}]) + assert ( + list(m_open.call_args) + == [(m_join.return_value,), {}]) + + +def test_pip_checker_requirements_dirs(patches): + checker = pip_check.PipChecker("path1", "path2", "path3") + + dummy_walker = [ + ["ROOT1", ["DIR1", "DIR2"], ["FILE1", "FILE2", "FILE3"]], + ["ROOT2", ["DIR1", "DIR2"], ["FILE1", "FILE2", "REQUIREMENTS_FILE", "FILE3"]], + ["ROOT3", ["DIR1", "DIR2"], ["FILE1", "FILE2", "REQUIREMENTS_FILE", "FILE3"]], + ["ROOT4", ["DIR1", "DIR2"], ["FILE1", "FILE2", "FILE3"]]] + + patched = patches( + ("PipChecker.requirements_filename", dict(new_callable=PropertyMock)), + ("PipChecker.path", dict(new_callable=PropertyMock)), + "os.walk", + prefix="tools.dependency.pip_check") + + with patched as (m_reqs, m_path, m_walk): + m_reqs.return_value = "REQUIREMENTS_FILE" + m_path.return_value = "ROO" + m_walk.return_value = dummy_walker + assert checker.requirements_dirs == {'T3', 'T2'} + + assert m_reqs.called + assert m_path.called + assert ( + list(m_walk.call_args) + == [('ROO',), {}]) + + +TEST_REQS = ( + (set(), set()), + (set(["A", "B"]), set()), + (set(["A", "B"]), set(["B", "C"])), + (set(["A", "B", "C"]), set(["A", "B", "C"])), + (set(), set(["B", "C"]))) + + +@pytest.mark.parametrize("requirements", TEST_REQS) +def test_pip_checker_check_dependabot(patches, requirements): + config, dirs = requirements + checker = pip_check.PipChecker("path1", "path2", "path3") + + patched = patches( + ("PipChecker.config_requirements", dict(new_callable=PropertyMock)), + ("PipChecker.requirements_dirs", dict(new_callable=PropertyMock)), + ("PipChecker.requirements_filename", dict(new_callable=PropertyMock)), + "PipChecker.dependabot_success", + "PipChecker.dependabot_errors", + prefix="tools.dependency.pip_check") + + with patched as (m_config, m_dirs, m_fname, m_success, m_errors): + m_config.return_value = config + m_dirs.return_value = dirs + assert not checker.check_dependabot() + + if config & dirs: + assert ( + list(m_success.call_args) + == [(config & dirs, ), {}]) + else: + assert not m_success.called + + if config - dirs: + assert ( + [(config - dirs, f"Missing {m_fname.return_value} dir, specified in dependabot config"), {}] + in list(list(c) for c in m_errors.call_args_list)) + + if dirs - config: + assert ( + [(dirs - config, f"Missing dependabot config for {m_fname.return_value} in dir"), {}] + in list(list(c) for c in m_errors.call_args_list)) + + if not config - dirs and not dirs - config: + assert not m_errors.called + + +def test_pip_checker_dependabot_success(patches): + checker = pip_check.PipChecker("path1", "path2", "path3") + succeed_mock = patch + success = set(["C", "D", "B", "A"]) + + patched = patches( + "PipChecker.succeed", + ("PipChecker.requirements_filename", dict(new_callable=PropertyMock)), + prefix="tools.dependency.pip_check") + + with patched as (m_succeed, m_fname): + checker.dependabot_success(success) + + assert ( + list(m_succeed.call_args) + == [('dependabot', + [f'Correct dependabot config for {m_fname.return_value} in dir: A', + f'Correct dependabot config for {m_fname.return_value} in dir: B', + f'Correct dependabot config for {m_fname.return_value} in dir: C', + f'Correct dependabot config for {m_fname.return_value} in dir: D']), {}]) + + +def test_pip_checker_dependabot_errors(patches): + checker = pip_check.PipChecker("path1", "path2", "path3") + succeed_mock = patch + errors = set(["C", "D", "B", "A"]) + MSG = "ERROR MESSAGE" + + patched = patches( + "PipChecker.error", + ("PipChecker.name", dict(new_callable=PropertyMock)), + prefix="tools.dependency.pip_check") + + with patched as (m_error, m_name): + checker.dependabot_errors(errors, MSG) + + assert ( + list(m_error.call_args) + == [('dependabot', + [f"[ERROR:{m_name.return_value}] (dependabot) {MSG}: A", + f"[ERROR:{m_name.return_value}] (dependabot) {MSG}: B", + f"[ERROR:{m_name.return_value}] (dependabot) {MSG}: C", + f"[ERROR:{m_name.return_value}] (dependabot) {MSG}: D"]), {}]) + + +def test_pip_checker_main(): + class_mock = patch("tools.dependency.pip_check.PipChecker") + + with class_mock as m_class: + assert ( + pip_check.main("arg0", "arg1", "arg2") + == m_class.return_value.run.return_value) + + assert ( + list(m_class.call_args) + == [('arg0', 'arg1', 'arg2'), {}]) + assert ( + list(m_class.return_value.run.call_args) + == [(), {}]) From df0f51de0b7021fdde7e5985002df43adab1090a Mon Sep 17 00:00:00 2001 From: htuch Date: Mon, 19 Apr 2021 19:43:57 -0400 Subject: [PATCH 042/209] owners: add @wrowe as a maintainer. (#16068) Welcome! Signed-off-by: Harvey Tuch --- OWNERS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OWNERS.md b/OWNERS.md index 4aad19618f680..5bd6d9adedcf7 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -37,6 +37,8 @@ routing PRs, questions, etc. to the right place. * Lua, access logging, and general miscellany. * Joshua Marantz ([jmarantz](https://github.com/jmarantz)) (jmarantz@google.com) * Stats, abseil, scalability, and performance. +* William A Rowe Jr ([wrowe](https://github.com/wrowe)) (wrowe@vmware.com) + * Windows port and CI build, `bazel/foreign_cc` build and dependencies liason. * Antonio Vicente ([antoniovicente](https://github.com/antoniovicente)) (avd@google.com) * Event management, security, performance, data plane. From 49627920267cacb5d033fca4324cb132c0818093 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Mon, 19 Apr 2021 16:55:42 -0700 Subject: [PATCH 043/209] tls: remove legacy socket BIOs and runtime guard (#16023) * tls: remove legacy socket BIOs and runtime guard Signed-off-by: Florin Coras --- docs/root/version_history/current.rst | 2 ++ source/common/runtime/runtime_features.cc | 1 - source/extensions/transport_sockets/tls/ssl_socket.cc | 10 ++-------- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index e22f5520065a2..f21ae4ae6da03 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -23,6 +23,8 @@ Removed Config or Runtime ------------------------- *Normally occurs at the end of the* :ref:`deprecation period ` +* tls: removed `envoy.reloadable_features.tls_use_io_handle_bio` runtime guard and legacy code path. + New Features ------------ diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 7b74ed4649e83..220c36aab04c1 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -88,7 +88,6 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.require_strict_1xx_and_204_response_headers", "envoy.reloadable_features.return_502_for_upstream_protocol_errors", "envoy.reloadable_features.send_strict_1xx_and_204_response_headers", - "envoy.reloadable_features.tls_use_io_handle_bio", "envoy.reloadable_features.treat_host_like_authority", "envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure", "envoy.reloadable_features.upstream_host_weight_change_causes_rebuild", diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index aba8e24dca695..a9fcfbf84ed52 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -72,14 +72,8 @@ void SslSocket::setTransportSocketCallbacks(Network::TransportSocketCallbacks& c provider->registerPrivateKeyMethod(rawSsl(), *this, callbacks_->connection().dispatcher()); } - BIO* bio; - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.tls_use_io_handle_bio")) { - // Use custom BIO that reads from/writes to IoHandle - bio = BIO_new_io_handle(&callbacks_->ioHandle()); - } else { - // TODO(fcoras): remove once the io_handle_bio proves to be stable - bio = BIO_new_socket(callbacks_->ioHandle().fdDoNotUse(), 0); - } + // Use custom BIO that reads from/writes to IoHandle + BIO* bio = BIO_new_io_handle(&callbacks_->ioHandle()); SSL_set_bio(rawSsl(), bio, bio); } From 457238e0b2a2de8b5214a87ce603090abf7f46c3 Mon Sep 17 00:00:00 2001 From: Greg Brail Date: Mon, 19 Apr 2021 17:16:54 -0700 Subject: [PATCH 044/209] Enable header modifications with body in "buffered" mode (#15935) This works by returning "StopIteration" after the headers have been processed until a response comes back from the "headers" message. The body is watermarked in the meantime. As a result, large bodies will still stream through without exceeding the buffer limit when the body mode is not "BUFFERED." Risk Level: Medium. Changes how bodies are handled. Testing: Existing unit and integration tests, plus some manual verification using external servers. Release Notes: Servers for the ext_proc filter can now use the HeaderMutation message when sending back a response to a request_body or response_body message to change headers at the same time as the body. This is only possible when the BodySendMode is BUFFERED. Signed-off-by: Gregory Brail --- .../filters/http/ext_proc/ext_proc.cc | 32 +- .../filters/http/ext_proc/ext_proc.h | 16 +- .../filters/http/ext_proc/mutation_utils.cc | 11 +- .../filters/http/ext_proc/mutation_utils.h | 7 +- .../filters/http/ext_proc/processor_state.cc | 67 ++- .../filters/http/ext_proc/processor_state.h | 36 +- .../ext_proc/ext_proc_integration_test.cc | 48 +- .../filters/http/ext_proc/filter_test.cc | 494 +++++++++++------- .../filters/http/ext_proc/ordering_test.cc | 98 +++- 9 files changed, 585 insertions(+), 224 deletions(-) diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 0d99659d203df..25751c158b0d7 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -82,7 +82,7 @@ FilterHeadersStatus Filter::onHeaders(ProcessorState& state, Http::HeaderMap& he ENVOY_LOG(debug, "Sending headers message"); stream_->send(std::move(req), false); stats_.stream_msgs_sent_.inc(); - return FilterHeadersStatus::StopAllIterationAndWatermark; + return FilterHeadersStatus::StopIteration; } FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool end_stream) { @@ -100,6 +100,21 @@ FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool end_st Http::FilterDataStatus Filter::onData(ProcessorState& state, ProcessingMode::BodySendMode body_mode, Buffer::Instance& data, bool end_stream) { + if (state.callbackState() == ProcessorState::CallbackState::Headers) { + ENVOY_LOG(trace, "Header processing still in progress -- holding body data"); + // We don't know what to do with the body until the response comes back. + // We must buffer it in case we need it when that happens. + if (end_stream) { + // Indicate to continue processing when the response returns. + state.setBodySendDeferred(true); + return FilterDataStatus::StopIterationAndBuffer; + } else { + // Raise a watermark to prevent a buffer overflow until the response comes back. + state.requestWatermark(); + return FilterDataStatus::StopIterationAndWatermark; + } + } + switch (body_mode) { case ProcessingMode::BUFFERED: if (end_stream) { @@ -115,15 +130,20 @@ Http::FilterDataStatus Filter::onData(ProcessorState& state, ProcessingMode::Bod } // The body has been buffered and we need to send the buffer + ENVOY_LOG(debug, "Sending request body message"); state.addBufferedData(data); state.setCallbackState(ProcessorState::CallbackState::BufferedBody); state.startMessageTimer(std::bind(&Filter::onMessageTimeout, this), config_->messageTimeout()); sendBodyChunk(state, *state.bufferedData(), true); - } else { - ENVOY_LOG(trace, "onData: Buffering"); + // Since we just just moved the data into the buffer, return NoBuffer + // so that we do not buffer this chunk twice. + return FilterDataStatus::StopIterationNoBuffer; } + + ENVOY_LOG(trace, "onData: Buffering"); return FilterDataStatus::StopIterationAndBuffer; + case ProcessingMode::BUFFERED_PARTIAL: case ProcessingMode::STREAMED: ENVOY_LOG(debug, "Ignoring unimplemented request body processing mode"); @@ -203,10 +223,12 @@ void Filter::onReceiveMessage(std::unique_ptr&& r) { switch (response->response_case()) { case ProcessingResponse::ResponseCase::kRequestHeaders: - message_handled = decoding_state_.handleHeadersResponse(response->request_headers()); + message_handled = decoding_state_.handleHeadersResponse(response->request_headers(), + processing_mode_.request_body_mode()); break; case ProcessingResponse::ResponseCase::kResponseHeaders: - message_handled = encoding_state_.handleHeadersResponse(response->response_headers()); + message_handled = encoding_state_.handleHeadersResponse(response->response_headers(), + processing_mode_.response_body_mode()); break; case ProcessingResponse::ResponseCase::kRequestBody: message_handled = decoding_state_.handleBodyResponse(response->request_body()); diff --git a/source/extensions/filters/http/ext_proc/ext_proc.h b/source/extensions/filters/http/ext_proc/ext_proc.h index 5c7aecb980c91..716d7bb3b558d 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.h +++ b/source/extensions/filters/http/ext_proc/ext_proc.h @@ -91,7 +91,10 @@ class Filter : public Logger::Loggable, public: Filter(const FilterConfigSharedPtr& config, ExternalProcessorClientPtr&& client) : config_(config), client_(std::move(client)), stats_(config->stats()), - processing_mode_(config->processingMode()) {} + decoding_state_(*this), encoding_state_(*this), processing_mode_(config->processingMode()) { + } + + const FilterConfig& config() const { return *config_; } void onDestroy() override; void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override; @@ -114,12 +117,19 @@ class Filter : public Logger::Loggable, void onGrpcClose() override; + void onMessageTimeout(); + + void sendBufferedData(const ProcessorState& state, bool end_stream) { + sendBodyChunk(state, *state.bufferedData(), end_stream); + } + private: StreamOpenState openStream(); - void onMessageTimeout(); + void cleanUpTimers(); void clearAsyncState(); void sendImmediateResponse(const envoy::service::ext_proc::v3alpha::ImmediateResponse& response); + void sendBodyChunk(const ProcessorState& state, const Buffer::Instance& data, bool end_stream); Http::FilterHeadersStatus onHeaders(ProcessorState& state, Http::HeaderMap& headers, bool end_stream); @@ -128,8 +138,6 @@ class Filter : public Logger::Loggable, envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode::BodySendMode body_mode, Buffer::Instance& data, bool end_stream); - void sendBodyChunk(const ProcessorState& state, const Buffer::Instance& data, bool end_stream); - const FilterConfigSharedPtr config_; const ExternalProcessorClientPtr client_; ExtProcFilterStats stats_; diff --git a/source/extensions/filters/http/ext_proc/mutation_utils.cc b/source/extensions/filters/http/ext_proc/mutation_utils.cc index ca88df8842e01..111e7a537d105 100644 --- a/source/extensions/filters/http/ext_proc/mutation_utils.cc +++ b/source/extensions/filters/http/ext_proc/mutation_utils.cc @@ -42,7 +42,10 @@ void MutationUtils::applyCommonHeaderResponse(const HeadersResponse& response, void MutationUtils::applyHeaderMutations(const HeaderMutation& mutation, Http::HeaderMap& headers) { for (const auto& remove_header : mutation.remove_headers()) { if (Http::HeaderUtility::isRemovableHeader(remove_header)) { + ENVOY_LOG(trace, "Removing header {}", remove_header); headers.remove(LowerCaseString(remove_header)); + } else { + ENVOY_LOG(debug, "Header {} is not removable", remove_header); } } @@ -55,19 +58,25 @@ void MutationUtils::applyHeaderMutations(const HeaderMutation& mutation, Http::H // filter. However, the router handles this same protobuf and uses "true" // as the default instead. const bool append = PROTOBUF_GET_WRAPPED_OR_DEFAULT(sh, append, false); + ENVOY_LOG(trace, "Setting header {} append = {}", sh.header().key(), append); if (append) { headers.addCopy(LowerCaseString(sh.header().key()), sh.header().value()); } else { headers.setCopy(LowerCaseString(sh.header().key()), sh.header().value()); } + } else { + ENVOY_LOG(debug, "Header {} is not settable", sh.header().key()); } } } -void MutationUtils::applyCommonBodyResponse(const BodyResponse& response, +void MutationUtils::applyCommonBodyResponse(const BodyResponse& response, Http::HeaderMap* headers, Buffer::Instance& buffer) { if (response.has_response()) { const auto& common_response = response.response(); + if (headers != nullptr && common_response.has_header_mutation()) { + applyHeaderMutations(common_response.header_mutation(), *headers); + } if (common_response.has_body_mutation()) { applyBodyMutations(common_response.body_mutation(), buffer); } diff --git a/source/extensions/filters/http/ext_proc/mutation_utils.h b/source/extensions/filters/http/ext_proc/mutation_utils.h index 6b8f03c2a29a9..7a71c6ad3388d 100644 --- a/source/extensions/filters/http/ext_proc/mutation_utils.h +++ b/source/extensions/filters/http/ext_proc/mutation_utils.h @@ -4,12 +4,14 @@ #include "envoy/http/header_map.h" #include "envoy/service/ext_proc/v3alpha/external_processor.pb.h" +#include "common/common/logger.h" + namespace Envoy { namespace Extensions { namespace HttpFilters { namespace ExternalProcessing { -class MutationUtils { +class MutationUtils : public Logger::Loggable { public: // Convert a header map until a protobuf static void buildHttpHeaders(const Http::HeaderMap& headers_in, @@ -26,8 +28,9 @@ class MutationUtils { Http::HeaderMap& headers); // Apply mutations that are common to body responses. + // Mutations will be applied to the header map if it is not null. static void applyCommonBodyResponse(const envoy::service::ext_proc::v3alpha::BodyResponse& body, - Buffer::Instance& buffer); + Http::HeaderMap* headers, Buffer::Instance& buffer); // Modify a buffer based on a set of mutations from a protobuf static void applyBodyMutations(const envoy::service::ext_proc::v3alpha::BodyMutation& mutation, diff --git a/source/extensions/filters/http/ext_proc/processor_state.cc b/source/extensions/filters/http/ext_proc/processor_state.cc index afa1293e047c0..d085b5815fce2 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.cc +++ b/source/extensions/filters/http/ext_proc/processor_state.cc @@ -1,5 +1,6 @@ #include "extensions/filters/http/ext_proc/processor_state.h" +#include "extensions/filters/http/ext_proc/ext_proc.h" #include "extensions/filters/http/ext_proc/mutation_utils.h" namespace Envoy { @@ -7,6 +8,9 @@ namespace Extensions { namespace HttpFilters { namespace ExternalProcessing { +using envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode; +using envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode_BodySendMode; + using envoy::service::ext_proc::v3alpha::BodyResponse; using envoy::service::ext_proc::v3alpha::HeadersResponse; @@ -17,13 +21,35 @@ void ProcessorState::startMessageTimer(Event::TimerCb cb, std::chrono::milliseco message_timer_->enableTimer(timeout); } -bool ProcessorState::handleHeadersResponse(const HeadersResponse& response) { +bool ProcessorState::handleHeadersResponse(const HeadersResponse& response, + ProcessingMode_BodySendMode body_mode) { if (callback_state_ == CallbackState::Headers) { ENVOY_LOG(debug, "applying headers response"); MutationUtils::applyCommonHeaderResponse(response, *headers_); - headers_ = nullptr; callback_state_ = CallbackState::Idle; + clearWatermark(); message_timer_->disableTimer(); + + if (body_mode == ProcessingMode::BUFFERED) { + if (body_send_deferred_) { + // If we get here, then all the body data came in before the header message + // was complete, and the server wants the body. So, don't continue filter + // processing, but send the buffered request body now. + ENVOY_LOG(debug, "Sending buffered request body message"); + callback_state_ = CallbackState::BufferedBody; + startMessageTimer(std::bind(&Filter::onMessageTimeout, &filter_), + filter_.config().messageTimeout()); + filter_.sendBufferedData(*this, true); + } + + // Otherwise, we're not ready to continue processing because then + // we won't be able to modify the headers any more, so do nothing and + // let the doData callback handle body chunks until the end is reached. + return true; + } + + // If we got here, then the processor doesn't care about the body, so we can just continue. + headers_ = nullptr; continueProcessing(); return true; } @@ -33,9 +59,10 @@ bool ProcessorState::handleHeadersResponse(const HeadersResponse& response) { bool ProcessorState::handleBodyResponse(const BodyResponse& response) { if (callback_state_ == CallbackState::BufferedBody) { ENVOY_LOG(debug, "Applying body response to buffered data"); - modifyBufferedData([&response](Buffer::Instance& data) { - MutationUtils::applyCommonBodyResponse(response, data); + modifyBufferedData([this, &response](Buffer::Instance& data) { + MutationUtils::applyCommonBodyResponse(response, headers_, data); }); + headers_ = nullptr; callback_state_ = CallbackState::Idle; message_timer_->disableTimer(); continueProcessing(); @@ -58,6 +85,38 @@ void ProcessorState::cleanUpTimer() const { } } +void DecodingProcessorState::requestWatermark() { + if (!watermark_requested_) { + ENVOY_LOG(debug, "Watermark raised on decoding"); + watermark_requested_ = true; + decoder_callbacks_->onDecoderFilterAboveWriteBufferHighWatermark(); + } +} + +void DecodingProcessorState::clearWatermark() { + if (watermark_requested_) { + ENVOY_LOG(debug, "Watermark lowered on decoding"); + watermark_requested_ = false; + decoder_callbacks_->onDecoderFilterBelowWriteBufferLowWatermark(); + } +} + +void EncodingProcessorState::requestWatermark() { + if (!watermark_requested_) { + ENVOY_LOG(debug, "Watermark raised on encoding"); + watermark_requested_ = true; + encoder_callbacks_->onEncoderFilterAboveWriteBufferHighWatermark(); + } +} + +void EncodingProcessorState::clearWatermark() { + if (watermark_requested_) { + ENVOY_LOG(debug, "Watermark lowered on decoding"); + watermark_requested_ = false; + encoder_callbacks_->onEncoderFilterBelowWriteBufferLowWatermark(); + } +} + } // namespace ExternalProcessing } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/ext_proc/processor_state.h b/source/extensions/filters/http/ext_proc/processor_state.h index ca60bfba9ec38..aafb2d73f81bd 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.h +++ b/source/extensions/filters/http/ext_proc/processor_state.h @@ -2,6 +2,7 @@ #include "envoy/buffer/buffer.h" #include "envoy/event/timer.h" +#include "envoy/extensions/filters/http/ext_proc/v3alpha/processing_mode.pb.h" #include "envoy/http/filter.h" #include "envoy/http/header_map.h" #include "envoy/service/ext_proc/v3alpha/external_processor.pb.h" @@ -13,6 +14,8 @@ namespace Extensions { namespace HttpFilters { namespace ExternalProcessing { +class Filter; + class ProcessorState : public Logger::Loggable { public: enum class CallbackState { @@ -23,17 +26,29 @@ class ProcessorState : public Logger::Loggable { BufferedBody, }; + explicit ProcessorState(Filter& filter) : filter_(filter) {} + ProcessorState(const ProcessorState&) = delete; virtual ~ProcessorState() = default; + ProcessorState& operator=(const ProcessorState&) = delete; + CallbackState callbackState() const { return callback_state_; } void setCallbackState(CallbackState state) { callback_state_ = state; } bool callbacksIdle() const { return callback_state_ == CallbackState::Idle; } + void setBodySendDeferred(bool deferred) { body_send_deferred_ = deferred; } + void setHeaders(Http::HeaderMap* headers) { headers_ = headers; } void startMessageTimer(Event::TimerCb cb, std::chrono::milliseconds timeout); void cleanUpTimer() const; - bool handleHeadersResponse(const envoy::service::ext_proc::v3alpha::HeadersResponse& response); + // Idempotent methods for watermarking the body + virtual void requestWatermark() PURE; + virtual void clearWatermark() PURE; + + bool handleHeadersResponse( + const envoy::service::ext_proc::v3alpha::HeadersResponse& response, + envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode_BodySendMode body_mode); bool handleBodyResponse(const envoy::service::ext_proc::v3alpha::BodyResponse& response); virtual const Buffer::Instance* bufferedData() const PURE; @@ -49,14 +64,23 @@ class ProcessorState : public Logger::Loggable { mutableBody(envoy::service::ext_proc::v3alpha::ProcessingRequest& request) const PURE; protected: + Filter& filter_; Http::StreamFilterCallbacks* filter_callbacks_; CallbackState callback_state_ = CallbackState::Idle; + // Keep track of whether we must send the body when the header processing callback is done. + bool body_send_deferred_ = false; + // Keep track of whether we requested a watermark. + bool watermark_requested_ = false; Http::HeaderMap* headers_ = nullptr; Event::TimerPtr message_timer_; }; class DecodingProcessorState : public ProcessorState { public: + explicit DecodingProcessorState(Filter& filter) : ProcessorState(filter) {} + DecodingProcessorState(const DecodingProcessorState&) = delete; + DecodingProcessorState& operator=(const DecodingProcessorState&) = delete; + void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) { decoder_callbacks_ = &callbacks; filter_callbacks_ = &callbacks; @@ -86,12 +110,19 @@ class DecodingProcessorState : public ProcessorState { return request.mutable_request_body(); } + void requestWatermark() override; + void clearWatermark() override; + private: Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; }; class EncodingProcessorState : public ProcessorState { public: + explicit EncodingProcessorState(Filter& filter) : ProcessorState(filter) {} + EncodingProcessorState(const EncodingProcessorState&) = delete; + EncodingProcessorState& operator=(const EncodingProcessorState&) = delete; + void setEncoderFilterCallbacks(Http::StreamEncoderFilterCallbacks& callbacks) { encoder_callbacks_ = &callbacks; filter_callbacks_ = &callbacks; @@ -121,6 +152,9 @@ class EncodingProcessorState : public ProcessorState { return request.mutable_response_body(); } + void requestWatermark() override; + void clearWatermark() override; + private: Http::StreamEncoderFilterCallbacks* encoder_callbacks_{}; }; diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index f7caddfab0caf..4f37f47e91250 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -424,7 +424,7 @@ TEST_P(ExtProcIntegrationTest, GetAndSetHeaders) { // Test the filter using the default configuration by connecting to // an ext_proc server that responds to the response_headers message -// by requesting to modify the request headers. +// by requesting to modify the response headers. TEST_P(ExtProcIntegrationTest, GetAndSetHeadersOnResponse) { initializeConfig(); HttpIntegrationTest::initialize(); @@ -446,8 +446,8 @@ TEST_P(ExtProcIntegrationTest, GetAndSetHeadersOnResponse) { // Test the filter with a response body callback enabled using an // an ext_proc server that responds to the response_body message -// by requesting to modify the response body. -TEST_P(ExtProcIntegrationTest, GetAndSetBodyOnResponse) { +// by requesting to modify the response body and headers. +TEST_P(ExtProcIntegrationTest, GetAndSetBodyAndHeadersOnResponse) { proto_config_.mutable_processing_mode()->set_response_body_mode(ProcessingMode::BUFFERED); initializeConfig(); HttpIntegrationTest::initialize(); @@ -468,14 +468,49 @@ TEST_P(ExtProcIntegrationTest, GetAndSetBodyOnResponse) { EXPECT_TRUE(body.end_of_stream()); auto* body_mut = body_resp.mutable_response()->mutable_body_mutation(); body_mut->set_body("Hello, World!"); + auto* header_mut = body_resp.mutable_response()->mutable_header_mutation(); + auto* header_add = header_mut->add_set_headers(); + header_add->mutable_header()->set_key("x-testing-response-header"); + header_add->mutable_header()->set_value("Yes"); return true; }); verifyDownstreamResponse(*response, 200); EXPECT_THAT(response->headers(), SingleHeaderValueIs("content-length", "13")); + EXPECT_THAT(response->headers(), SingleHeaderValueIs("x-testing-response-header", "Yes")); EXPECT_EQ("Hello, World!", response->body()); } +// Test the filter with a response body callback enabled using an +// an ext_proc server that responds to the response_body message +// by requesting to modify the response body and headers, using a response +// big enough to require multiple chunks. +TEST_P(ExtProcIntegrationTest, GetAndSetBodyAndHeadersOnBigResponse) { + proto_config_.mutable_processing_mode()->set_response_body_mode(ProcessingMode::BUFFERED); + initializeConfig(); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + processRequestHeadersMessage(true, absl::nullopt); + + Buffer::OwnedImpl full_response; + TestUtility::feedBufferWithRandomCharacters(full_response, 4000); + handleUpstreamRequestWithResponse(full_response, 1000); + + processResponseHeadersMessage(false, absl::nullopt); + // Should get just one message with the body + processResponseBodyMessage(false, [](const HttpBody& body, BodyResponse& body_resp) { + EXPECT_TRUE(body.end_of_stream()); + auto* header_mut = body_resp.mutable_response()->mutable_header_mutation(); + auto* header_add = header_mut->add_set_headers(); + header_add->mutable_header()->set_key("x-testing-response-header"); + header_add->mutable_header()->set_value("Yes"); + return true; + }); + + verifyDownstreamResponse(*response, 200); + EXPECT_THAT(response->headers(), SingleHeaderValueIs("x-testing-response-header", "Yes")); +} + // Test the filter with both body callbacks enabled and have the // ext_proc server change both of them. TEST_P(ExtProcIntegrationTest, GetAndSetBodyOnBoth) { @@ -627,9 +662,10 @@ TEST_P(ExtProcIntegrationTest, GetAndRespondImmediatelyOnResponseBody) { immediate.set_details("Failed because you are not authorized"); }); - // The stream should have been reset here before the complete - // response was received. - ASSERT_TRUE(response->waitForReset()); + // Since we are stopping iteration on headers, and since the response is short, + // we actually get an error message here. + verifyDownstreamResponse(*response, 401); + EXPECT_EQ("{\"reason\": \"Not authorized\"}", response->body()); } // Send a request, but wait longer than the "message timeout" before sending a response diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index 94cf2c2bbabbd..5f440cd5ea921 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -104,6 +104,7 @@ class HttpFilterTest : public testing::Test { // Expect a request_headers request, and send back a valid response. void processRequestHeaders( + bool buffering_data, absl::optional> cb) { ASSERT_FALSE(last_request_processed_); @@ -116,12 +117,15 @@ class HttpFilterTest : public testing::Test { (*cb)(headers, *response, *headers_response); } last_request_processed_ = true; - EXPECT_CALL(decoder_callbacks_, continueDecoding()); + if (!buffering_data) { + EXPECT_CALL(decoder_callbacks_, continueDecoding()); + } stream_callbacks_->onReceiveMessage(std::move(response)); } // Expect a response_headers request, and send back a valid response void processResponseHeaders( + bool buffering_data, absl::optional> cb) { ASSERT_FALSE(last_request_processed_); @@ -134,7 +138,9 @@ class HttpFilterTest : public testing::Test { (*cb)(headers, *response, *headers_response); } last_request_processed_ = true; - EXPECT_CALL(encoder_callbacks_, continueEncoding()); + if (!buffering_data) { + EXPECT_CALL(encoder_callbacks_, continueEncoding()); + } stream_callbacks_->onReceiveMessage(std::move(response)); } @@ -187,7 +193,6 @@ class HttpFilterTest : public testing::Test { Http::TestResponseHeaderMapImpl response_headers_; Http::TestRequestTrailerMapImpl request_trailers_; Http::TestResponseTrailerMapImpl response_trailers_; - Buffer::OwnedImpl data_; }; // Using the default configuration, test the filter with a processor that @@ -208,42 +213,43 @@ TEST_F(HttpFilterTest, SimplestPost) { request_headers_.addCopy(LowerCaseString("content-length"), 10); request_headers_.addCopy(LowerCaseString("x-some-other-header"), "yes"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); - - processRequestHeaders([](const HttpHeaders& header_req, ProcessingResponse&, HeadersResponse&) { - EXPECT_FALSE(header_req.end_of_stream()); - Http::TestRequestHeaderMapImpl expected{{":path", "/"}, - {":method", "POST"}, - {":scheme", "http"}, - {"host", "host"}, - {"content-type", "text/plain"}, - {"content-length", "10"}, - {"x-some-other-header", "yes"}}; - EXPECT_THAT(header_req.headers(), HeaderProtosEqual(expected)); - }); - - data_.add("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(data_, true)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); + + processRequestHeaders(false, + [](const HttpHeaders& header_req, ProcessingResponse&, HeadersResponse&) { + EXPECT_FALSE(header_req.end_of_stream()); + Http::TestRequestHeaderMapImpl expected{{":path", "/"}, + {":method", "POST"}, + {":scheme", "http"}, + {"host", "host"}, + {"content-type", "text/plain"}, + {"content-length", "10"}, + {"x-some-other-header", "yes"}}; + EXPECT_THAT(header_req.headers(), HeaderProtosEqual(expected)); + }); + + Buffer::OwnedImpl req_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); response_headers_.addCopy(LowerCaseString(":status"), "200"); response_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); response_headers_.addCopy(LowerCaseString("content-length"), "3"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->encodeHeaders(response_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); - processResponseHeaders([](const HttpHeaders& header_resp, ProcessingResponse&, HeadersResponse&) { - EXPECT_FALSE(header_resp.end_of_stream()); - Http::TestRequestHeaderMapImpl expected_response{ - {":status", "200"}, {"content-type", "text/plain"}, {"content-length", "3"}}; - EXPECT_THAT(header_resp.headers(), HeaderProtosEqual(expected_response)); - }); + processResponseHeaders( + false, [](const HttpHeaders& header_resp, ProcessingResponse&, HeadersResponse&) { + EXPECT_FALSE(header_resp.end_of_stream()); + Http::TestRequestHeaderMapImpl expected_response{ + {":status", "200"}, {"content-type", "text/plain"}, {"content-length", "3"}}; + EXPECT_THAT(header_resp.headers(), HeaderProtosEqual(expected_response)); + }); - data_.add("bar"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, false)); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, true)); + Buffer::OwnedImpl resp_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + Buffer::OwnedImpl empty_data; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); filter_->onDestroy(); @@ -267,21 +273,21 @@ TEST_F(HttpFilterTest, PostAndChangeHeaders) { request_headers_.addCopy(LowerCaseString("x-some-other-header"), "yes"); request_headers_.addCopy(LowerCaseString("x-do-we-want-this"), "no"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); - - processRequestHeaders([](const HttpHeaders&, ProcessingResponse&, HeadersResponse& header_resp) { - auto headers_mut = header_resp.mutable_response()->mutable_header_mutation(); - auto add1 = headers_mut->add_set_headers(); - add1->mutable_header()->set_key("x-new-header"); - add1->mutable_header()->set_value("new"); - add1->mutable_append()->set_value(false); - auto add2 = headers_mut->add_set_headers(); - add2->mutable_header()->set_key("x-some-other-header"); - add2->mutable_header()->set_value("no"); - add2->mutable_append()->set_value(true); - *headers_mut->add_remove_headers() = "x-do-we-want-this"; - }); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); + + processRequestHeaders( + false, [](const HttpHeaders&, ProcessingResponse&, HeadersResponse& header_resp) { + auto headers_mut = header_resp.mutable_response()->mutable_header_mutation(); + auto add1 = headers_mut->add_set_headers(); + add1->mutable_header()->set_key("x-new-header"); + add1->mutable_header()->set_value("new"); + add1->mutable_append()->set_value(false); + auto add2 = headers_mut->add_set_headers(); + add2->mutable_header()->set_key("x-some-other-header"); + add2->mutable_header()->set_value("no"); + add2->mutable_append()->set_value(true); + *headers_mut->add_remove_headers() = "x-do-we-want-this"; + }); // We should now have changed the original header a bit Http::TestRequestHeaderMapImpl expected{{":path", "/"}, @@ -293,29 +299,28 @@ TEST_F(HttpFilterTest, PostAndChangeHeaders) { {"x-some-other-header", "no"}}; EXPECT_THAT(&request_headers_, HeaderMapEqualIgnoreOrder(&expected)); - data_.add("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(data_, true)); + Buffer::OwnedImpl req_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); response_headers_.addCopy(LowerCaseString(":status"), "200"); response_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); response_headers_.addCopy(LowerCaseString("content-length"), "3"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->encodeHeaders(response_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); - processResponseHeaders( - [](const HttpHeaders& response_headers, ProcessingResponse&, HeadersResponse& header_resp) { - EXPECT_FALSE(response_headers.end_of_stream()); - Http::TestRequestHeaderMapImpl expected_response{ - {":status", "200"}, {"content-type", "text/plain"}, {"content-length", "3"}}; - EXPECT_THAT(response_headers.headers(), HeaderProtosEqual(expected_response)); + processResponseHeaders(false, [](const HttpHeaders& response_headers, ProcessingResponse&, + HeadersResponse& header_resp) { + EXPECT_FALSE(response_headers.end_of_stream()); + Http::TestRequestHeaderMapImpl expected_response{ + {":status", "200"}, {"content-type", "text/plain"}, {"content-length", "3"}}; + EXPECT_THAT(response_headers.headers(), HeaderProtosEqual(expected_response)); - auto* resp_headers_mut = header_resp.mutable_response()->mutable_header_mutation(); - auto* resp_add1 = resp_headers_mut->add_set_headers(); - resp_add1->mutable_header()->set_key("x-new-header"); - resp_add1->mutable_header()->set_value("new"); - }); + auto* resp_headers_mut = header_resp.mutable_response()->mutable_header_mutation(); + auto* resp_add1 = resp_headers_mut->add_set_headers(); + resp_add1->mutable_header()->set_key("x-new-header"); + resp_add1->mutable_header()->set_value("new"); + }); // We should now have changed the original header a bit Http::TestRequestHeaderMapImpl final_expected_response{{":status", "200"}, @@ -324,9 +329,10 @@ TEST_F(HttpFilterTest, PostAndChangeHeaders) { {"x-new-header", "new"}}; EXPECT_THAT(&response_headers_, HeaderMapEqualIgnoreOrder(&final_expected_response)); - data_.add("bar"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, false)); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, true)); + Buffer::OwnedImpl resp_data("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + Buffer::OwnedImpl empty_data; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); filter_->onDestroy(); @@ -349,8 +355,7 @@ TEST_F(HttpFilterTest, PostAndRespondImmediately) { HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); Http::TestResponseHeaderMapImpl immediate_response_headers; EXPECT_CALL(encoder_callbacks_, sendLocalReply(Http::Code::BadRequest, "Bad request", _, @@ -382,14 +387,15 @@ TEST_F(HttpFilterTest, PostAndRespondImmediately) { {"content-type", "text/plain"}, {"x-another-thing", "1"}, {"x-another-thing", "2"}}; EXPECT_THAT(&immediate_response_headers, HeaderMapEqualIgnoreOrder(&expected_response_headers)); - data_.add("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(data_, true)); + Buffer::OwnedImpl req_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, true)); - data_.add("bar"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, false)); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, true)); + Buffer::OwnedImpl resp_data("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + Buffer::OwnedImpl empty_data; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); filter_->onDestroy(); @@ -412,17 +418,15 @@ TEST_F(HttpFilterTest, PostAndRespondImmediatelyOnResponse) { HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); - processRequestHeaders(absl::nullopt); + processRequestHeaders(false, absl::nullopt); - data_.add("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(data_, true)); + Buffer::OwnedImpl req_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->encodeHeaders(response_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); EXPECT_FALSE(last_request_.async_mode()); ASSERT_TRUE(last_request_.has_response_headers()); @@ -443,9 +447,10 @@ TEST_F(HttpFilterTest, PostAndRespondImmediatelyOnResponse) { stream_callbacks_->onReceiveMessage(std::move(resp2)); EXPECT_TRUE(immediate_response_headers.empty()); - data_.add("bar"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, false)); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, true)); + Buffer::OwnedImpl resp_data("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + Buffer::OwnedImpl empty_data; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); filter_->onDestroy(); @@ -477,9 +482,8 @@ TEST_F(HttpFilterTest, PostAndChangeRequestBodyBuffered) { request_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); request_headers_.addCopy(LowerCaseString("content-length"), 100); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); - processRequestHeaders(absl::nullopt); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); + processRequestHeaders(true, absl::nullopt); Buffer::OwnedImpl req_data; TestUtility::feedBufferWithRandomCharacters(req_data, 100); @@ -487,12 +491,12 @@ TEST_F(HttpFilterTest, PostAndChangeRequestBodyBuffered) { setUpDecodingBuffering(buffered_data); // Testing the case where we just have one chunk of data and it is buffered. - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_data, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_data, true)); processRequestBody( - [&req_data](const HttpBody& req_body, ProcessingResponse&, BodyResponse& body_resp) { + [&buffered_data](const HttpBody& req_body, ProcessingResponse&, BodyResponse& body_resp) { EXPECT_TRUE(req_body.end_of_stream()); EXPECT_EQ(100, req_body.body().size()); - EXPECT_EQ(req_body.body(), req_data.toString()); + EXPECT_EQ(req_body.body(), buffered_data.toString()); auto* body_mut = body_resp.mutable_response()->mutable_body_mutation(); body_mut->set_body("Replaced!"); }); @@ -505,9 +509,8 @@ TEST_F(HttpFilterTest, PostAndChangeRequestBodyBuffered) { response_headers_.addCopy(LowerCaseString("content-length"), "3"); EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encode100ContinueHeaders(response_headers_)); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->encodeHeaders(response_headers_, false)); - processResponseHeaders(absl::nullopt); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); + processResponseHeaders(false, absl::nullopt); Buffer::OwnedImpl resp_data; resp_data.add("bar"); @@ -521,6 +524,142 @@ TEST_F(HttpFilterTest, PostAndChangeRequestBodyBuffered) { EXPECT_EQ(1, config_->stats().streams_closed_.value()); } +// Using a configuration with buffering set for the request body, +// test the filter with a processor that changes the request body, +// passing the data in chunks that come before the request callback +// is complete. +TEST_F(HttpFilterTest, PostAndChangeRequestBodyBufferedComesFast) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + request_header_mode: "SEND" + response_header_mode: "SEND" + request_body_mode: "BUFFERED" + response_body_mode: "NONE" + request_trailer_mode: "SKIP" + response_trailer_mode: "SKIP" + )EOF"); + + // Create synthetic HTTP request + HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); + request_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); + request_headers_.addCopy(LowerCaseString("content-length"), 100); + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); + + Buffer::OwnedImpl req_data_1("Hello"); + Buffer::OwnedImpl req_data_2(", "); + Buffer::OwnedImpl req_data_3("there, "); + Buffer::OwnedImpl req_data_4("World!"); + Buffer::OwnedImpl buffered_data; + setUpDecodingBuffering(buffered_data); + + // Buffering and callback isn't complete so we should watermark + EXPECT_CALL(decoder_callbacks_, onDecoderFilterAboveWriteBufferHighWatermark()); + EXPECT_EQ(FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(req_data_1, false)); + buffered_data.add(req_data_1); + + // Pretend that Envoy ignores the watermark and keep sending. It often does! + EXPECT_EQ(FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(req_data_2, false)); + buffered_data.add(req_data_2); + EXPECT_EQ(FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(req_data_3, false)); + buffered_data.add(req_data_3); + EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_data_4, true)); + buffered_data.add(req_data_4); + + EXPECT_CALL(decoder_callbacks_, onDecoderFilterBelowWriteBufferLowWatermark()); + processRequestHeaders(true, absl::nullopt); + + processRequestBody([](const HttpBody& req_body, ProcessingResponse&, BodyResponse&) { + EXPECT_TRUE(req_body.end_of_stream()); + EXPECT_EQ("Hello, there, World!", req_body.body()); + }); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + + response_headers_.addCopy(LowerCaseString(":status"), "200"); + response_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); + response_headers_.addCopy(LowerCaseString("content-length"), "3"); + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); + processResponseHeaders(false, absl::nullopt); + + Buffer::OwnedImpl resp_data; + resp_data.add("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + filter_->onDestroy(); +} + +// Using a configuration with buffering set for the request body, +// test the filter with a processor that changes the request body, +// passing the data so that some chunks come before the request callback +// is complete and others come later. +TEST_F(HttpFilterTest, PostAndChangeRequestBodyBufferedComesALittleFast) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + request_header_mode: "SEND" + response_header_mode: "SEND" + request_body_mode: "BUFFERED" + response_body_mode: "NONE" + request_trailer_mode: "SKIP" + response_trailer_mode: "SKIP" + )EOF"); + + // Create synthetic HTTP request + HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); + request_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); + request_headers_.addCopy(LowerCaseString("content-length"), 100); + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); + + Buffer::OwnedImpl req_data_1("Hello"); + Buffer::OwnedImpl req_data_2(", "); + Buffer::OwnedImpl req_data_3("there, "); + Buffer::OwnedImpl req_data_4("World!"); + Buffer::OwnedImpl buffered_data; + setUpDecodingBuffering(buffered_data); + + // Buffering and callback isn't complete so we should watermark + EXPECT_CALL(decoder_callbacks_, onDecoderFilterAboveWriteBufferHighWatermark()); + EXPECT_EQ(FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(req_data_1, false)); + buffered_data.add(req_data_1); + EXPECT_EQ(FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(req_data_2, false)); + buffered_data.add(req_data_2); + + // Now the headers response comes in before we get all the data + EXPECT_CALL(decoder_callbacks_, onDecoderFilterBelowWriteBufferLowWatermark()); + processRequestHeaders(true, absl::nullopt); + + EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_data_3, false)); + buffered_data.add(req_data_3); + // In this case, the filter builds the buffer on the last call + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_data_4, true)); + + processRequestBody([](const HttpBody& req_body, ProcessingResponse&, BodyResponse&) { + EXPECT_TRUE(req_body.end_of_stream()); + EXPECT_EQ("Hello, there, World!", req_body.body()); + }); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + + response_headers_.addCopy(LowerCaseString(":status"), "200"); + response_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); + response_headers_.addCopy(LowerCaseString("content-length"), "3"); + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); + processResponseHeaders(false, absl::nullopt); + + Buffer::OwnedImpl resp_data; + resp_data.add("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, true)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + filter_->onDestroy(); +} + // Using a configuration with buffering set for the request and // response bodies but not the headers, and with each body // delivered as a single chunk, test the filter with a processor that @@ -551,15 +690,15 @@ TEST_F(HttpFilterTest, PostAndChangeBothBodiesBufferedOneChunk) { Buffer::OwnedImpl buffered_request_data; setUpDecodingBuffering(buffered_request_data); - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_data, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_data, true)); - processRequestBody( - [&req_data](const HttpBody& req_body, ProcessingResponse&, BodyResponse& body_resp) { - EXPECT_TRUE(req_body.end_of_stream()); - EXPECT_EQ(req_data.toString(), req_body.body()); - auto* body_mut = body_resp.mutable_response()->mutable_body_mutation(); - body_mut->set_clear_body(true); - }); + processRequestBody([&buffered_request_data](const HttpBody& req_body, ProcessingResponse&, + BodyResponse& body_resp) { + EXPECT_TRUE(req_body.end_of_stream()); + EXPECT_EQ(buffered_request_data.toString(), req_body.body()); + auto* body_mut = body_resp.mutable_response()->mutable_body_mutation(); + body_mut->set_clear_body(true); + }); EXPECT_EQ(0, buffered_request_data.length()); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); @@ -575,15 +714,15 @@ TEST_F(HttpFilterTest, PostAndChangeBothBodiesBufferedOneChunk) { TestUtility::feedBufferWithRandomCharacters(resp_data, 100); Buffer::OwnedImpl buffered_response_data; setUpEncodingBuffering(buffered_response_data); - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->encodeData(resp_data, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_data, true)); - processResponseBody( - [&resp_data](const HttpBody& req_body, ProcessingResponse&, BodyResponse& body_resp) { - EXPECT_TRUE(req_body.end_of_stream()); - EXPECT_EQ(resp_data.toString(), req_body.body()); - auto* body_mut = body_resp.mutable_response()->mutable_body_mutation(); - body_mut->set_body("Hello, World!"); - }); + processResponseBody([&buffered_response_data](const HttpBody& req_body, ProcessingResponse&, + BodyResponse& body_resp) { + EXPECT_TRUE(req_body.end_of_stream()); + EXPECT_EQ(buffered_response_data.toString(), req_body.body()); + auto* body_mut = body_resp.mutable_response()->mutable_body_mutation(); + body_mut->set_body("Hello, World!"); + }); EXPECT_EQ("Hello, World!", buffered_response_data.toString()); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); @@ -628,7 +767,7 @@ TEST_F(HttpFilterTest, PostAndChangeBothBodiesBufferedMultiChunk) { EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_data, false)); // At this point, Envoy adds data to the buffer buffered_req_data.add(req_data); - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(empty_data, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(empty_data, true)); processRequestBody( [&buffered_req_data](const HttpBody& req_body, ProcessingResponse&, BodyResponse& body_resp) { @@ -662,7 +801,7 @@ TEST_F(HttpFilterTest, PostAndChangeBothBodiesBufferedMultiChunk) { buffered_resp_data.add(resp_data_1); EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->encodeData(resp_data_2, false)); buffered_resp_data.add(resp_data_2); - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->encodeData(resp_data_3, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_data_3, true)); // After this call, the callback should have been used to add the third chunk to the buffer processResponseBody([&buffered_resp_data](const HttpBody& req_body, ProcessingResponse&, @@ -705,12 +844,12 @@ TEST_F(HttpFilterTest, PostAndIgnoreStreamedBodiesUntilImplemented) { request_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); request_headers_.addCopy(LowerCaseString("content-length"), 100); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); - processRequestHeaders(absl::nullopt); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); + processRequestHeaders(false, absl::nullopt); - TestUtility::feedBufferWithRandomCharacters(data_, 100); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(data_, true)); + Buffer::OwnedImpl req_data; + TestUtility::feedBufferWithRandomCharacters(req_data, 100); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); response_headers_.addCopy(LowerCaseString(":status"), "200"); @@ -718,12 +857,12 @@ TEST_F(HttpFilterTest, PostAndIgnoreStreamedBodiesUntilImplemented) { response_headers_.addCopy(LowerCaseString("content-length"), "100"); EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encode100ContinueHeaders(response_headers_)); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->encodeHeaders(response_headers_, false)); - processResponseHeaders(absl::nullopt); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); + processResponseHeaders(false, absl::nullopt); - TestUtility::feedBufferWithRandomCharacters(data_, 100); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, true)); + Buffer::OwnedImpl resp_data; + TestUtility::feedBufferWithRandomCharacters(resp_data, 100); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); filter_->onDestroy(); @@ -745,8 +884,7 @@ TEST_F(HttpFilterTest, RespondImmediatelyDefault) { HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); Http::TestResponseHeaderMapImpl immediate_response_headers; EXPECT_CALL(encoder_callbacks_, sendLocalReply(Http::Code::OK, "", _, Eq(absl::nullopt), "")) @@ -759,14 +897,15 @@ TEST_F(HttpFilterTest, RespondImmediatelyDefault) { stream_callbacks_->onReceiveMessage(std::move(resp1)); EXPECT_TRUE(immediate_response_headers.empty()); - data_.add("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(data_, true)); + Buffer::OwnedImpl req_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, true)); - data_.add("bar"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, false)); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, true)); + Buffer::OwnedImpl resp_data("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + Buffer::OwnedImpl empty_data; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); filter_->onDestroy(); } @@ -783,8 +922,7 @@ TEST_F(HttpFilterTest, RespondImmediatelyGrpcError) { HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); Http::TestResponseHeaderMapImpl immediate_response_headers; EXPECT_CALL(encoder_callbacks_, sendLocalReply(Http::Code::Forbidden, "", _, Eq(999), "")) @@ -799,14 +937,15 @@ TEST_F(HttpFilterTest, RespondImmediatelyGrpcError) { stream_callbacks_->onReceiveMessage(std::move(resp1)); EXPECT_TRUE(immediate_response_headers.empty()); - data_.add("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(data_, true)); + Buffer::OwnedImpl req_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, true)); - data_.add("bar"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, false)); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, true)); + Buffer::OwnedImpl resp_data("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + Buffer::OwnedImpl empty_data; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); filter_->onDestroy(); } @@ -824,8 +963,7 @@ TEST_F(HttpFilterTest, PostAndFail) { // Create synthetic HTTP request HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); // Oh no! The remote server had a failure! Http::TestResponseHeaderMapImpl immediate_response_headers; @@ -839,14 +977,15 @@ TEST_F(HttpFilterTest, PostAndFail) { server_closed_stream_ = true; stream_callbacks_->onGrpcError(Grpc::Status::Internal); - data_.add("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(data_, true)); + Buffer::OwnedImpl req_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, true)); - data_.add("bar"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, false)); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, true)); + Buffer::OwnedImpl resp_data("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + Buffer::OwnedImpl empty_data; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); filter_->onDestroy(); EXPECT_TRUE(immediate_response_headers.empty()); @@ -869,8 +1008,7 @@ TEST_F(HttpFilterTest, PostAndFailOnResponse) { // Create synthetic HTTP request HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); EXPECT_FALSE(last_request_.async_mode()); ASSERT_TRUE(last_request_.has_request_headers()); @@ -881,12 +1019,11 @@ TEST_F(HttpFilterTest, PostAndFailOnResponse) { resp1->mutable_request_headers(); stream_callbacks_->onReceiveMessage(std::move(resp1)); - data_.add("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(data_, true)); + Buffer::OwnedImpl req_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->encodeHeaders(response_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); // Oh no! The remote server had a failure! Http::TestResponseHeaderMapImpl immediate_response_headers; @@ -900,9 +1037,10 @@ TEST_F(HttpFilterTest, PostAndFailOnResponse) { server_closed_stream_ = true; stream_callbacks_->onGrpcError(Grpc::Status::Internal); - data_.add("bar"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, false)); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, true)); + Buffer::OwnedImpl resp_data("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + Buffer::OwnedImpl empty_data; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); filter_->onDestroy(); // The other side closed the stream @@ -929,22 +1067,22 @@ TEST_F(HttpFilterTest, PostAndIgnoreFailure) { // Create synthetic HTTP request HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); // Oh no! The remote server had a failure which we will ignore EXPECT_CALL(decoder_callbacks_, continueDecoding()); server_closed_stream_ = true; stream_callbacks_->onGrpcError(Grpc::Status::Internal); - data_.add("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(data_, true)); + Buffer::OwnedImpl req_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, true)); - data_.add("bar"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, false)); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, true)); + Buffer::OwnedImpl resp_data("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + Buffer::OwnedImpl empty_data; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); filter_->onDestroy(); @@ -967,8 +1105,7 @@ TEST_F(HttpFilterTest, PostAndClose) { // Create synthetic HTTP request HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); EXPECT_FALSE(last_request_.async_mode()); ASSERT_TRUE(last_request_.has_request_headers()); @@ -978,14 +1115,15 @@ TEST_F(HttpFilterTest, PostAndClose) { server_closed_stream_ = true; stream_callbacks_->onGrpcClose(); - data_.add("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(data_, true)); + Buffer::OwnedImpl req_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, true)); - data_.add("bar"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, false)); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, true)); + Buffer::OwnedImpl resp_data("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + Buffer::OwnedImpl empty_data; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); filter_->onDestroy(); @@ -1011,8 +1149,7 @@ TEST_F(HttpFilterTest, ProcessingModeRequestHeadersOnly) { )EOF"); HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); EXPECT_FALSE(last_request_.async_mode()); ASSERT_TRUE(last_request_.has_request_headers()); @@ -1059,12 +1196,12 @@ TEST_F(HttpFilterTest, ProcessingModeOverrideResponseHeaders) { )EOF"); HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); - processRequestHeaders([](const HttpHeaders&, ProcessingResponse& response, HeadersResponse&) { - response.mutable_mode_override()->set_response_header_mode(ProcessingMode::SKIP); - }); + processRequestHeaders( + false, [](const HttpHeaders&, ProcessingResponse& response, HeadersResponse&) { + response.mutable_mode_override()->set_response_header_mode(ProcessingMode::SKIP); + }); Buffer::OwnedImpl first_chunk("foo"); EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(first_chunk, true)); @@ -1118,10 +1255,9 @@ TEST_F(HttpFilterTest, ProcessingModeResponseHeadersOnly) { response_headers_.addCopy(LowerCaseString(":status"), "200"); response_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); response_headers_.addCopy(LowerCaseString("content-length"), "3"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->encodeHeaders(response_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); - processResponseHeaders(absl::nullopt); + processResponseHeaders(false, absl::nullopt); Http::TestRequestHeaderMapImpl final_expected_response{ {":status", "200"}, {"content-type", "text/plain"}, {"content-length", "3"}}; @@ -1152,8 +1288,7 @@ TEST_F(HttpFilterTest, OutOfOrder) { )EOF"); HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); - EXPECT_EQ(FilterHeadersStatus::StopAllIterationAndWatermark, - filter_->decodeHeaders(request_headers_, false)); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); EXPECT_FALSE(last_request_.async_mode()); ASSERT_TRUE(last_request_.has_request_headers()); @@ -1165,14 +1300,15 @@ TEST_F(HttpFilterTest, OutOfOrder) { resp1->mutable_request_body(); stream_callbacks_->onReceiveMessage(std::move(resp1)); - data_.add("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(data_, true)); + Buffer::OwnedImpl req_data("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, true)); - data_.add("bar"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, false)); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(data_, true)); + Buffer::OwnedImpl resp_data("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + Buffer::OwnedImpl empty_data; + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true)); EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); filter_->onDestroy(); diff --git a/test/extensions/filters/http/ext_proc/ordering_test.cc b/test/extensions/filters/http/ext_proc/ordering_test.cc index e27fe1a7e6468..3d111e7e778dc 100644 --- a/test/extensions/filters/http/ext_proc/ordering_test.cc +++ b/test/extensions/filters/http/ext_proc/ordering_test.cc @@ -87,8 +87,7 @@ class OrderingTest : public testing::Test { void sendRequestHeadersGet(bool expect_callback) { HttpTestUtility::addDefaultHeaders(request_headers_, "GET"); - EXPECT_EQ(expect_callback ? FilterHeadersStatus::StopAllIterationAndWatermark - : FilterHeadersStatus::Continue, + EXPECT_EQ(expect_callback ? FilterHeadersStatus::StopIteration : FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, true)); } @@ -96,15 +95,13 @@ class OrderingTest : public testing::Test { HttpTestUtility::addDefaultHeaders(request_headers_, "POST"); request_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); request_headers_.addCopy(LowerCaseString("content-length"), "10"); - EXPECT_EQ(expect_callback ? FilterHeadersStatus::StopAllIterationAndWatermark - : FilterHeadersStatus::Continue, + EXPECT_EQ(expect_callback ? FilterHeadersStatus::StopIteration : FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); } void sendResponseHeaders(bool expect_callback) { response_headers_.setStatus(200); - EXPECT_EQ(expect_callback ? FilterHeadersStatus::StopAllIterationAndWatermark - : FilterHeadersStatus::Continue, + EXPECT_EQ(expect_callback ? FilterHeadersStatus::StopIteration : FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, false)); } @@ -268,7 +265,6 @@ TEST_F(OrderingTest, DefaultOrderingAllCallbacks) { EXPECT_CALL(stream_delegate_, send(_, false)); sendRequestHeadersPost(true); - EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestHeadersReply(); Buffer::OwnedImpl req_body_1; @@ -282,7 +278,7 @@ TEST_F(OrderingTest, DefaultOrderingAllCallbacks) { req_buffer.add(req_body_1); EXPECT_CALL(stream_delegate_, send(_, false)); - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_body_2, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_body_2, true)); EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestBodyReply(); sendRequestTrailers(); @@ -293,10 +289,9 @@ TEST_F(OrderingTest, DefaultOrderingAllCallbacks) { EXPECT_CALL(stream_delegate_, send(_, false)); sendResponseHeaders(true); - EXPECT_CALL(encoder_callbacks_, continueEncoding()); sendResponseHeadersReply(); EXPECT_CALL(stream_delegate_, send(_, false)); - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->encodeData(resp_body_1, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_body_1, true)); EXPECT_CALL(encoder_callbacks_, continueEncoding()); sendResponseBodyReply(); sendResponseTrailers(); @@ -331,12 +326,10 @@ TEST_F(OrderingTest, DefaultOrderingAllCallbacksInterleaved) { EXPECT_CALL(stream_delegate_, send(_, false)); sendRequestHeadersPost(true); - EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestHeadersReply(); EXPECT_CALL(stream_delegate_, send(_, false)); sendResponseHeaders(true); - EXPECT_CALL(encoder_callbacks_, continueEncoding()); sendResponseHeadersReply(); EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_body_1, false)); @@ -344,7 +337,7 @@ TEST_F(OrderingTest, DefaultOrderingAllCallbacksInterleaved) { EXPECT_CALL(stream_delegate_, send(_, false)); expectBufferedRequest(req_buffer, true); - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_body_2, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_body_2, true)); EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestBodyReply(); @@ -352,12 +345,77 @@ TEST_F(OrderingTest, DefaultOrderingAllCallbacksInterleaved) { EXPECT_CALL(stream_delegate_, send(_, false)); expectBufferedResponse(resp_buffer, true); - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->encodeData(resp_body_2, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_body_2, true)); EXPECT_CALL(encoder_callbacks_, continueEncoding()); sendResponseBodyReply(); sendResponseTrailers(); } +// A normal call with response buffering on. All response data comes back before the +// request callback finishes. +TEST_F(OrderingTest, ResponseAllDataComesFast) { + initialize([](ExternalProcessor& cfg) { + auto* pm = cfg.mutable_processing_mode(); + pm->set_response_body_mode(ProcessingMode::BUFFERED); + }); + + EXPECT_CALL(stream_delegate_, send(_, false)); + sendRequestHeadersGet(true); + EXPECT_CALL(decoder_callbacks_, continueDecoding()); + sendRequestHeadersReply(); + + Buffer::OwnedImpl resp_body_1("Dummy response"); + Buffer::OwnedImpl resp_buffer; + expectBufferedResponse(resp_buffer, true); + + EXPECT_CALL(stream_delegate_, send(_, false)); + sendResponseHeaders(true); + // The rest of the data might come in even before the response headers + // response comes back. + EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->encodeData(resp_body_1, true)); + + // When the response does comes back, we should immediately send the body to the server + EXPECT_CALL(stream_delegate_, send(_, false)); + sendResponseHeadersReply(); + EXPECT_CALL(encoder_callbacks_, continueEncoding()); + sendResponseBodyReply(); +} + +// A normal call with response buffering on. Some response data comes back before the +// response headers callback finishes. +TEST_F(OrderingTest, ResponseSomeDataComesFast) { + initialize([](ExternalProcessor& cfg) { + auto* pm = cfg.mutable_processing_mode(); + pm->set_response_body_mode(ProcessingMode::BUFFERED); + }); + + EXPECT_CALL(stream_delegate_, send(_, false)); + sendRequestHeadersGet(true); + EXPECT_CALL(decoder_callbacks_, continueDecoding()); + sendRequestHeadersReply(); + + Buffer::OwnedImpl resp_body_1("Dummy response"); + Buffer::OwnedImpl resp_body_2(" the end"); + Buffer::OwnedImpl resp_buffer; + expectBufferedResponse(resp_buffer, true); + + EXPECT_CALL(stream_delegate_, send(_, false)); + sendResponseHeaders(true); + // Some of the data might come back but we should watermark so that we + // don't fill the buffer. + EXPECT_CALL(encoder_callbacks_, onEncoderFilterAboveWriteBufferHighWatermark()); + EXPECT_EQ(FilterDataStatus::StopIterationAndWatermark, filter_->encodeData(resp_body_1, false)); + + // When the response does comes back, we should lift the watermark + EXPECT_CALL(encoder_callbacks_, onEncoderFilterBelowWriteBufferLowWatermark()); + sendResponseHeadersReply(); + + EXPECT_CALL(stream_delegate_, send(_, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_body_2, true)); + EXPECT_CALL(encoder_callbacks_, continueEncoding()); + sendResponseBodyReply(); +} + // An immediate response on the request path TEST_F(OrderingTest, ImmediateResponseOnRequest) { initialize(absl::nullopt); @@ -449,10 +507,9 @@ TEST_F(OrderingTest, IncorrectRequestBodyReply) { EXPECT_CALL(stream_delegate_, send(_, false)); sendRequestHeadersPost(true); - EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestHeadersReply(); EXPECT_CALL(stream_delegate_, send(_, false)); - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_body_1, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_body_1, true)); EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendResponseBodyReply(); // Wrong message here sendRequestTrailers(); @@ -631,7 +688,6 @@ TEST_F(OrderingTest, TimeoutOnResponseBody) { EXPECT_CALL(stream_delegate_, send(_, false)); sendRequestHeadersPost(true); EXPECT_CALL(*request_timer, disableTimer()); - EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestHeadersReply(); Buffer::OwnedImpl req_body; @@ -641,7 +697,7 @@ TEST_F(OrderingTest, TimeoutOnResponseBody) { EXPECT_CALL(*request_timer, enableTimer(kMessageTimeout, nullptr)); EXPECT_CALL(stream_delegate_, send(_, false)); - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_body, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_body, true)); EXPECT_CALL(*request_timer, disableTimer()); EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestBodyReply(); @@ -659,11 +715,10 @@ TEST_F(OrderingTest, TimeoutOnResponseBody) { EXPECT_CALL(stream_delegate_, send(_, false)); sendResponseHeaders(true); EXPECT_CALL(*response_timer, disableTimer()); - EXPECT_CALL(encoder_callbacks_, continueEncoding()); sendResponseHeadersReply(); EXPECT_CALL(*response_timer, enableTimer(kMessageTimeout, nullptr)); EXPECT_CALL(stream_delegate_, send(_, false)); - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->encodeData(resp_body, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_body, true)); // Now, fire the timeout, which will end everything EXPECT_CALL(encoder_callbacks_, sendLocalReply(Http::Code::InternalServerError, _, _, _, _)); @@ -684,7 +739,6 @@ TEST_F(OrderingTest, TimeoutOnRequestBody) { EXPECT_CALL(stream_delegate_, send(_, false)); sendRequestHeadersPost(true); EXPECT_CALL(*request_timer, disableTimer()); - EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestHeadersReply(); Buffer::OwnedImpl req_body; @@ -698,7 +752,7 @@ TEST_F(OrderingTest, TimeoutOnRequestBody) { EXPECT_CALL(*request_timer, enableTimer(kMessageTimeout, nullptr)); EXPECT_CALL(stream_delegate_, send(_, false)); - EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_body, true)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_body, true)); // Now fire the timeout and expect a 500 error EXPECT_CALL(encoder_callbacks_, sendLocalReply(Http::Code::InternalServerError, _, _, _, _)); From a809844ddeda2fce1f5d3cdfe6204fafb7fd4452 Mon Sep 17 00:00:00 2001 From: "Addo.Zhang" Date: Tue, 20 Apr 2021 09:16:11 +0800 Subject: [PATCH 045/209] docs: fix missing underscore in field name idle_timeout (#16070) Signed-off-by: addozhang --- docs/root/faq/configuration/timeouts.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/root/faq/configuration/timeouts.rst b/docs/root/faq/configuration/timeouts.rst index 33640d6bcdcd6..7ca390c45d06b 100644 --- a/docs/root/faq/configuration/timeouts.rst +++ b/docs/root/faq/configuration/timeouts.rst @@ -19,7 +19,7 @@ Connection timeouts Connection timeouts apply to the entire HTTP connection and all streams the connection carries. -* The HTTP protocol :ref:`idle timeout ` +* The HTTP protocol :ref:`idle_timeout ` is defined in a generic message used by both the HTTP connection manager as well as upstream cluster HTTP connections. The idle timeout is the time at which a downstream or upstream connection will be terminated if there are no active streams. The default idle timeout if not From fe806d1d60b9e0448c6d9dd29869f10eafb7dc26 Mon Sep 17 00:00:00 2001 From: Douglas Reid Date: Tue, 20 Apr 2021 06:04:51 -0700 Subject: [PATCH 046/209] tracing: fix zipkin annotation timestamp serialization issue (#16073) * Fix handling of timestamps in zipkin annotations / logs. The intended replacement from string to int was not happening as expected. this causes ingestion issues with non-lenient collectors, such as jaeger Signed-off-by: Douglas Reid --- docs/root/version_history/current.rst | 2 + .../extensions/tracers/zipkin/span_buffer.cc | 2 +- .../tracers/zipkin/span_buffer_test.cc | 56 ++++++++++--------- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index f21ae4ae6da03..5a2ca3d708776 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -19,6 +19,8 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* +* zipkin: fix timestamp serializaiton in annotations. A prior bug fix exposed an issue with timestamps being serialized as strings. + Removed Config or Runtime ------------------------- *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/tracers/zipkin/span_buffer.cc b/source/extensions/tracers/zipkin/span_buffer.cc index 77266afe8e5cb..df1682e7881ae 100644 --- a/source/extensions/tracers/zipkin/span_buffer.cc +++ b/source/extensions/tracers/zipkin/span_buffer.cc @@ -133,7 +133,7 @@ JsonV2Serializer::toListOfSpans(const Span& zipkin_span, Util::Replacements& rep auto* annotation_entry_fields = annotation_entry.mutable_fields(); (*annotation_entry_fields)[ANNOTATION_VALUE] = ValueUtil::stringValue(annotation.value()); (*annotation_entry_fields)[ANNOTATION_TIMESTAMP] = - Util::uint64Value(annotation.timestamp(), annotation.value(), replacements); + Util::uint64Value(annotation.timestamp(), ANNOTATION_TIMESTAMP, replacements); annotation_entries.push_back(ValueUtil::structValue(annotation_entry)); continue; } diff --git a/test/extensions/tracers/zipkin/span_buffer_test.cc b/test/extensions/tracers/zipkin/span_buffer_test.cc index b75fce28305a4..9af90529a713a 100644 --- a/test/extensions/tracers/zipkin/span_buffer_test.cc +++ b/test/extensions/tracers/zipkin/span_buffer_test.cc @@ -23,9 +23,11 @@ namespace { // If this default timestamp is wrapped as double (using ValueUtil::numberValue()) and then it is // serialized using Protobuf::util::MessageToJsonString, it renders as: 1.58432429547687e+15. constexpr uint64_t DEFAULT_TEST_TIMESTAMP = 1584324295476870; +constexpr uint64_t ANNOTATION_TEST_TIMESTAMP = 1584324295476871; constexpr uint64_t DEFAULT_TEST_DURATION = 2584324295476870; const Util::Replacements DEFAULT_TEST_REPLACEMENTS = { - {"DEFAULT_TEST_TIMESTAMP", std::to_string(DEFAULT_TEST_TIMESTAMP)}}; + {"DEFAULT_TEST_TIMESTAMP", std::to_string(DEFAULT_TEST_TIMESTAMP)}, + {"ANNOTATION_TEST_TIMESTAMP", std::to_string(ANNOTATION_TEST_TIMESTAMP)}}; const Util::Replacements DEFAULT_TEST_DURATIONS = { {"DEFAULT_TEST_DURATION", std::to_string(DEFAULT_TEST_DURATION)}}; @@ -44,7 +46,7 @@ Endpoint createEndpoint(const IpType ip_type) { Annotation createAnnotation(const absl::string_view value, const IpType ip_type) { Annotation annotation; annotation.setValue(value.data()); - annotation.setTimestamp(DEFAULT_TEST_TIMESTAMP); + annotation.setTimestamp(ANNOTATION_TEST_TIMESTAMP); annotation.setEndpoint(createEndpoint(ip_type)); return annotation; } @@ -53,7 +55,7 @@ Annotation createAnnotation(const absl::string_view value, const IpType ip_type) Annotation createLog(const absl::string_view value) { Annotation log; log.setValue(value.data()); - log.setTimestamp(DEFAULT_TEST_TIMESTAMP); + log.setTimestamp(ANNOTATION_TEST_TIMESTAMP); return log; } @@ -161,12 +163,12 @@ TEST(ZipkinSpanBufferTest, ConstructBuffer) { R"("name":"",)" R"("id":"0000000000000001",)" R"("duration":DEFAULT_TEST_DURATION,)" - R"("annotations":[{"timestamp":DEFAULT_TEST_TIMESTAMP,)" + R"("annotations":[{"timestamp":ANNOTATION_TEST_TIMESTAMP,)" R"("value":"cs",)" R"("endpoint":{"ipv4":"1.2.3.4",)" R"("port":8080,)" R"("serviceName":"service1"}},)" - R"({"timestamp":DEFAULT_TEST_TIMESTAMP,)" + R"({"timestamp":ANNOTATION_TEST_TIMESTAMP,)" R"("value":"sr",)" R"("endpoint":{"ipv4":"1.2.3.4",)" R"("port":8080,)" @@ -179,12 +181,12 @@ TEST(ZipkinSpanBufferTest, ConstructBuffer) { R"("name":"",)" R"("id":"0000000000000001",)" R"("duration":DEFAULT_TEST_DURATION,)" - R"("annotations":[{"timestamp":DEFAULT_TEST_TIMESTAMP,)" + R"("annotations":[{"timestamp":ANNOTATION_TEST_TIMESTAMP,)" R"("value":"cs",)" R"("endpoint":{"ipv4":"1.2.3.4",)" R"("port":8080,)" R"("serviceName":"service1"}},)" - R"({"timestamp":DEFAULT_TEST_TIMESTAMP,)" + R"({"timestamp":ANNOTATION_TEST_TIMESTAMP,)" R"("value":"sr",)" R"("endpoint":{"ipv4":"1.2.3.4",)" R"("port":8080,)" @@ -195,12 +197,12 @@ TEST(ZipkinSpanBufferTest, ConstructBuffer) { R"("name":"",)" R"("id":"0000000000000001",)" R"("duration":DEFAULT_TEST_DURATION,)" - R"("annotations":[{"timestamp":DEFAULT_TEST_TIMESTAMP,)" + R"("annotations":[{"timestamp":ANNOTATION_TEST_TIMESTAMP,)" R"("value":"cs",)" R"("endpoint":{"ipv4":"1.2.3.4",)" R"("port":8080,)" R"("serviceName":"service1"}},)" - R"({"timestamp":DEFAULT_TEST_TIMESTAMP,)" + R"({"timestamp":ANNOTATION_TEST_TIMESTAMP,)" R"("value":"sr",)" R"("endpoint":{"ipv4":"1.2.3.4",)" R"("port":8080,)" @@ -229,13 +231,13 @@ TEST(ZipkinSpanBufferTest, SerializeSpan) { R"("traceId":"0000000000000001",)" R"("id":"0000000000000001",)" R"("kind":"CLIENT",)" - R"("timestamp":DEFAULT_TEST_TIMESTAMP,)" + R"("timestamp":ANNOTATION_TEST_TIMESTAMP,)" R"("duration":DEFAULT_TEST_DURATION,)" R"("localEndpoint":{)" R"("serviceName":"service1",)" R"("ipv4":"1.2.3.4",)" R"("port":8080},)" - R"("annotations":[{"timestamp":DEFAULT_TEST_TIMESTAMP,"value":"log_1"}],)" + R"("annotations":[{"timestamp":ANNOTATION_TEST_TIMESTAMP,"value":"log_1"}],)" R"("tags":{)" R"("response_size":"DEFAULT_TEST_DURATION"},)" "}]"), @@ -247,7 +249,7 @@ TEST(ZipkinSpanBufferTest, SerializeSpan) { R"("traceId":"0000000000000001",)" R"("id":"0000000000000001",)" R"("kind":"CLIENT",)" - R"("timestamp":DEFAULT_TEST_TIMESTAMP,)" + R"("timestamp":ANNOTATION_TEST_TIMESTAMP,)" R"("duration":DEFAULT_TEST_DURATION,)" R"("localEndpoint":{)" R"("serviceName":"service1",)" @@ -264,7 +266,7 @@ TEST(ZipkinSpanBufferTest, SerializeSpan) { R"("traceId":"0000000000000001",)" R"("id":"0000000000000001",)" R"("kind":"CLIENT",)" - R"("timestamp":DEFAULT_TEST_TIMESTAMP,)" + R"("timestamp":ANNOTATION_TEST_TIMESTAMP,)" R"("duration":DEFAULT_TEST_DURATION,)" R"("localEndpoint":{)" R"("serviceName":"service1",)" @@ -276,7 +278,7 @@ TEST(ZipkinSpanBufferTest, SerializeSpan) { R"("traceId":"0000000000000001",)" R"("id":"0000000000000001",)" R"("kind":"SERVER",)" - R"("timestamp":DEFAULT_TEST_TIMESTAMP,)" + R"("timestamp":ANNOTATION_TEST_TIMESTAMP,)" R"("duration":DEFAULT_TEST_DURATION,)" R"("localEndpoint":{)" R"("serviceName":"service1",)" @@ -294,7 +296,7 @@ TEST(ZipkinSpanBufferTest, SerializeSpan) { R"("traceId":"0000000000000001",)" R"("id":"0000000000000001",)" R"("kind":"CLIENT",)" - R"("timestamp":DEFAULT_TEST_TIMESTAMP,)" + R"("timestamp":ANNOTATION_TEST_TIMESTAMP,)" R"("duration":DEFAULT_TEST_DURATION,)" R"("localEndpoint":{)" R"("serviceName":"service1",)" @@ -306,7 +308,7 @@ TEST(ZipkinSpanBufferTest, SerializeSpan) { R"("traceId":"0000000000000001",)" R"("id":"0000000000000001",)" R"("kind":"SERVER",)" - R"("timestamp":DEFAULT_TEST_TIMESTAMP,)" + R"("timestamp":ANNOTATION_TEST_TIMESTAMP,)" R"("duration":DEFAULT_TEST_DURATION,)" R"("localEndpoint":{)" R"("serviceName":"service1",)" @@ -324,7 +326,7 @@ TEST(ZipkinSpanBufferTest, SerializeSpan) { R"("traceId":"AAAAAAAAAAE=",)" R"("id":"AQAAAAAAAAA=",)" R"("kind":"CLIENT",)" - R"("timestamp":"DEFAULT_TEST_TIMESTAMP",)" + R"("timestamp":"ANNOTATION_TEST_TIMESTAMP",)" R"("duration":"DEFAULT_TEST_DURATION",)" R"("localEndpoint":{)" R"("serviceName":"service1",)" @@ -343,13 +345,13 @@ TEST(ZipkinSpanBufferTest, SerializeSpan) { R"("traceId":"AAAAAAAAAAE=",)" R"("id":"AQAAAAAAAAA=",)" R"("kind":"CLIENT",)" - R"("timestamp":"DEFAULT_TEST_TIMESTAMP",)" + R"("timestamp":"ANNOTATION_TEST_TIMESTAMP",)" R"("duration":"DEFAULT_TEST_DURATION",)" R"("localEndpoint":{)" R"("serviceName":"service1",)" R"("ipv6":"IAENuIWjAAAAAIouA3BERA==",)" R"("port":7334},)" - R"("annotations":[{"timestamp":"DEFAULT_TEST_TIMESTAMP","value":"log_1"}],)" + R"("annotations":[{"timestamp":"ANNOTATION_TEST_TIMESTAMP","value":"log_1"}],)" R"("tags":{)" R"("response_size":"DEFAULT_TEST_DURATION"})" "}]}"), @@ -362,7 +364,7 @@ TEST(ZipkinSpanBufferTest, SerializeSpan) { R"("traceId":"AAAAAAAAAAE=",)" R"("id":"AQAAAAAAAAA=",)" R"("kind":"CLIENT",)" - R"("timestamp":"DEFAULT_TEST_TIMESTAMP",)" + R"("timestamp":"ANNOTATION_TEST_TIMESTAMP",)" R"("duration":"DEFAULT_TEST_DURATION",)" R"("localEndpoint":{)" R"("serviceName":"service1",)" @@ -374,7 +376,7 @@ TEST(ZipkinSpanBufferTest, SerializeSpan) { R"("traceId":"AAAAAAAAAAE=",)" R"("id":"AQAAAAAAAAA=",)" R"("kind":"SERVER",)" - R"("timestamp":"DEFAULT_TEST_TIMESTAMP",)" + R"("timestamp":"ANNOTATION_TEST_TIMESTAMP",)" R"("duration":"DEFAULT_TEST_DURATION",)" R"("localEndpoint":{)" R"("serviceName":"service1",)" @@ -393,7 +395,7 @@ TEST(ZipkinSpanBufferTest, SerializeSpan) { R"("traceId":"AAAAAAAAAAE=",)" R"("id":"AQAAAAAAAAA=",)" R"("kind":"CLIENT",)" - R"("timestamp":"DEFAULT_TEST_TIMESTAMP",)" + R"("timestamp":"ANNOTATION_TEST_TIMESTAMP",)" R"("duration":"DEFAULT_TEST_DURATION",)" R"("localEndpoint":{)" R"("serviceName":"service1",)" @@ -405,7 +407,7 @@ TEST(ZipkinSpanBufferTest, SerializeSpan) { R"("traceId":"AAAAAAAAAAE=",)" R"("id":"AQAAAAAAAAA=",)" R"("kind":"SERVER",)" - R"("timestamp":"DEFAULT_TEST_TIMESTAMP",)" + R"("timestamp":"ANNOTATION_TEST_TIMESTAMP",)" R"("duration":"DEFAULT_TEST_DURATION",)" R"("localEndpoint":{)" R"("serviceName":"service1",)" @@ -444,11 +446,11 @@ TEST(ZipkinSpanBufferTest, TestSerializeTimestampInTheFuture) { bufferDeprecatedJsonV1.addSpan(createSpan({"cs"}, IpType::V4)); // We do "HasSubstr" here since we could not compare the serialized JSON of a ProtobufWkt::Struct // object, since the positions of keys are not consistent between calls. - EXPECT_THAT(bufferDeprecatedJsonV1.serialize(), HasSubstr(R"("timestamp":1584324295476870)")); + EXPECT_THAT(bufferDeprecatedJsonV1.serialize(), HasSubstr(R"("timestamp":1584324295476871)")); EXPECT_THAT(bufferDeprecatedJsonV1.serialize(), Not(HasSubstr(R"("timestamp":1.58432429547687e+15)"))); EXPECT_THAT(bufferDeprecatedJsonV1.serialize(), - Not(HasSubstr(R"("timestamp":"1584324295476870")"))); + Not(HasSubstr(R"("timestamp":"1584324295476871")"))); EXPECT_THAT(bufferDeprecatedJsonV1.serialize(), HasSubstr(R"("duration":2584324295476870)")); EXPECT_THAT(bufferDeprecatedJsonV1.serialize(), Not(HasSubstr(R"("duration":2.584324295476870e+15)"))); @@ -458,9 +460,9 @@ TEST(ZipkinSpanBufferTest, TestSerializeTimestampInTheFuture) { SpanBuffer bufferJsonV2( envoy::config::trace::v3::ZipkinConfig::hidden_envoy_deprecated_HTTP_JSON_V1, true, 2); bufferJsonV2.addSpan(createSpan({"cs"}, IpType::V4)); - EXPECT_THAT(bufferJsonV2.serialize(), HasSubstr(R"("timestamp":1584324295476870)")); + EXPECT_THAT(bufferJsonV2.serialize(), HasSubstr(R"("timestamp":1584324295476871)")); EXPECT_THAT(bufferJsonV2.serialize(), Not(HasSubstr(R"("timestamp":1.58432429547687e+15)"))); - EXPECT_THAT(bufferJsonV2.serialize(), Not(HasSubstr(R"("timestamp":"1584324295476870")"))); + EXPECT_THAT(bufferJsonV2.serialize(), Not(HasSubstr(R"("timestamp":"1584324295476871")"))); EXPECT_THAT(bufferJsonV2.serialize(), HasSubstr(R"("duration":2584324295476870)")); EXPECT_THAT(bufferJsonV2.serialize(), Not(HasSubstr(R"("duration":2.584324295476870e+15)"))); EXPECT_THAT(bufferJsonV2.serialize(), Not(HasSubstr(R"("duration":"2584324295476870")"))); From 92dab8ff4c5cc3f75824b5d716ae398fbd8b6e22 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Tue, 20 Apr 2021 14:07:38 +0000 Subject: [PATCH 047/209] Reserve field ID for an upcoming patch (#16069) Signed-off-by: Yan Avlasov --- .../http_connection_manager/v3/http_connection_manager.proto | 4 ++-- .../v4alpha/http_connection_manager.proto | 4 ++-- .../http_connection_manager/v3/http_connection_manager.proto | 4 ++-- .../v4alpha/http_connection_manager.proto | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 90e40369bddcd..66575c40b1ccc 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -34,7 +34,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 45] +// [#next-free-field: 46] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -271,7 +271,7 @@ message HttpConnectionManager { type.http.v3.PathTransformation http_filter_transformation = 2; } - reserved 27, 11; + reserved 27, 11, 45; reserved "idle_timeout"; diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index d40a8fb6a0559..b292548832e13 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -33,7 +33,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 45] +// [#next-free-field: 46] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"; @@ -274,7 +274,7 @@ message HttpConnectionManager { type.http.v3.PathTransformation http_filter_transformation = 2; } - reserved 27, 11; + reserved 27, 11, 45; reserved "idle_timeout"; diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 4ac5f1289df6e..c9c6ac3a55573 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -36,7 +36,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 45] +// [#next-free-field: 46] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -279,7 +279,7 @@ message HttpConnectionManager { type.http.v3.PathTransformation http_filter_transformation = 2; } - reserved 27; + reserved 27, 45; // Supplies the type of codec that the connection manager should use. CodecType codec_type = 1 [(validate.rules).enum = {defined_only: true}]; diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index d40a8fb6a0559..b292548832e13 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -33,7 +33,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 45] +// [#next-free-field: 46] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"; @@ -274,7 +274,7 @@ message HttpConnectionManager { type.http.v3.PathTransformation http_filter_transformation = 2; } - reserved 27, 11; + reserved 27, 11, 45; reserved "idle_timeout"; From 6ca22105cc9da8abd6b2965d572044b8729c89c3 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 20 Apr 2021 15:34:04 +0100 Subject: [PATCH 048/209] dependabot: Aggregate updates (#16079) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 6 +++--- examples/grpc-bridge/client/requirements.txt | 2 +- tools/code_format/requirements.txt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 8e2a18417d71d..758ba5716dbc9 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -116,9 +116,9 @@ sphinxcontrib-jsmath==1.0.1 \ sphinxcontrib-qthelp==1.0.3 \ --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 \ --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6 -sphinxext-rediraffe==0.2.6 \ - --hash=sha256:e09ca25cf7d5e86f94389eedd48cf915fe6a46e89362dd07b0d17f2be8c122fd \ - --hash=sha256:dc8bbe6b3334a3bfc8ed0d1d6b2e831de9c06f123fa7d635cfb8192141ca4140 +sphinxext-rediraffe==0.2.7 \ + --hash=sha256:9e430a52d4403847f4ffb3a8dd6dfc34a9fe43525305131f52ed899743a5fd8c \ + --hash=sha256:651dcbfae5ffda9ffd534dfb8025f36120e5efb6ea1a33f5420023862b9f725d sphinxcontrib-serializinghtml==1.1.4 \ --hash=sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc \ --hash=sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a diff --git a/examples/grpc-bridge/client/requirements.txt b/examples/grpc-bridge/client/requirements.txt index e95b4f9752738..436a97af70862 100644 --- a/examples/grpc-bridge/client/requirements.txt +++ b/examples/grpc-bridge/client/requirements.txt @@ -1,4 +1,4 @@ requests>=2.22.0 grpcio grpcio-tools -protobuf==3.15.7 +protobuf==3.15.8 diff --git a/tools/code_format/requirements.txt b/tools/code_format/requirements.txt index e62476d2074d8..06cfb439f0a9b 100644 --- a/tools/code_format/requirements.txt +++ b/tools/code_format/requirements.txt @@ -1,9 +1,9 @@ flake8==3.9.1 \ --hash=sha256:3b9f848952dddccf635be78098ca75010f073bfe14d2c6bda867154bea728d2a \ --hash=sha256:1aa8990be1e689d96c745c5682b687ea49f2e05a443aff1f8251092b0014e378 -importlib-metadata==3.10.0 \ - --hash=sha256:d2d46ef77ffc85cbf7dac7e81dd663fde71c45326131bea8033b9bad42268ebe \ - --hash=sha256:c9db46394197244adf2f0b08ec5bc3cf16757e9590b02af1fca085c16c0d600a +importlib-metadata==4.0.0 \ + --hash=sha256:19192b88d959336bfa6bdaaaef99aeafec179eca19c47c804e555703ee5f07ef \ + --hash=sha256:2e881981c9748d7282b374b68e759c87745c25427b67ecf0cc67fb6637a1bff9 mccabe==0.6.1 \ --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f From d4c16b5cff4023c360964efa963ab7d8d1f5d578 Mon Sep 17 00:00:00 2001 From: Alex Konradi Date: Tue, 20 Apr 2021 12:47:56 -0400 Subject: [PATCH 049/209] overload: remove runtime override for http/2 goaway (#16041) Remove deprecated runtime override envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2. Multiplexing HTTP connections (HTTP/2, HTTP/3) will always be sent a GOAWAY when the overload action for disabling connection keepalive triggers. Signed-off-by: Alex Konradi --- docs/root/version_history/current.rst | 1 + source/common/http/conn_manager_impl.cc | 6 +---- source/common/runtime/runtime_features.cc | 1 - test/common/http/conn_manager_impl_test_2.cc | 25 ++++---------------- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 5a2ca3d708776..3f38173481c40 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -25,6 +25,7 @@ Removed Config or Runtime ------------------------- *Normally occurs at the end of the* :ref:`deprecation period ` +* http: removed `envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2`; Envoy will now always send GOAWAY to HTTP2 downstreams when the :ref:`disable_keepalive ` overload action is active. * tls: removed `envoy.reloadable_features.tls_use_io_handle_bio` runtime guard and legacy code path. New Features diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 62dc98ffa864b..7a13c37bfa25e 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1362,11 +1362,7 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ResponseHeaderMap& heade connection_manager_.random_generator_.bernoulli( connection_manager_.overload_disable_keepalive_ref_.value())) { ENVOY_STREAM_LOG(debug, "disabling keepalive due to envoy overload", *this); - if (connection_manager_.codec_->protocol() < Protocol::Http2 || - Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2")) { - drain_connection_due_to_overload = true; - } + drain_connection_due_to_overload = true; connection_manager_.stats_.named_.downstream_cx_overload_disable_keepalive_.inc(); } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 220c36aab04c1..2f444057c2b30 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -80,7 +80,6 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.http2_skip_encoding_empty_trailers", "envoy.reloadable_features.improved_stream_limit_handling", "envoy.reloadable_features.internal_redirects_with_body", - "envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2", "envoy.reloadable_features.prefer_quic_kernel_bpf_packet_routing", "envoy.reloadable_features.preserve_downstream_scheme", "envoy.reloadable_features.remove_forked_chromium_url", diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index fea68bdfc9c50..83bda9907062a 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -2187,21 +2187,9 @@ TEST_F(HttpConnectionManagerImplTest, DisableHttp1KeepAliveWhenOverloaded) { EXPECT_EQ(1U, stats_.named_.downstream_cx_overload_disable_keepalive_.value()); } -class DrainH2HttpConnectionManagerImplTest : public HttpConnectionManagerImplTest, - public testing::WithParamInterface { -public: - DrainH2HttpConnectionManagerImplTest() { - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2", "true"}}); - } - -private: - TestScopedRuntime runtime_; -}; - -// Verify that, if the runtime option is enabled, HTTP2 connections will receive -// a GOAWAY message when the overload action is triggered. -TEST_P(DrainH2HttpConnectionManagerImplTest, DisableHttp2KeepAliveWhenOverloaded) { +// Verify that HTTP2 connections will receive a GOAWAY message when the overload action is +// triggered. +TEST_F(HttpConnectionManagerImplTest, DisableHttp2KeepAliveWhenOverloaded) { Server::OverloadActionState disable_http_keep_alive = Server::OverloadActionState::saturated(); ON_CALL(overload_manager_.overload_state_, getState(Server::OverloadActionNames::get().DisableHttpKeepAlive)) @@ -2209,9 +2197,7 @@ TEST_P(DrainH2HttpConnectionManagerImplTest, DisableHttp2KeepAliveWhenOverloaded codec_->protocol_ = Protocol::Http2; setup(false, ""); - if (GetParam()) { - EXPECT_CALL(*codec_, shutdownNotice); - } + EXPECT_CALL(*codec_, shutdownNotice); std::shared_ptr filter(new NiceMock()); EXPECT_CALL(filter_factory_, createFilterChain(_)) @@ -2244,9 +2230,6 @@ TEST_P(DrainH2HttpConnectionManagerImplTest, DisableHttp2KeepAliveWhenOverloaded EXPECT_EQ(1, stats_.named_.downstream_cx_overload_disable_keepalive_.value()); } -INSTANTIATE_TEST_SUITE_P(WithRuntimeOverride, DrainH2HttpConnectionManagerImplTest, - testing::Bool()); - TEST_F(HttpConnectionManagerImplTest, TestStopAllIterationAndBufferOnDecodingPathFirstFilter) { setup(false, "envoy-custom-server", false); setUpEncoderAndDecoder(true, true); From d8419244075bd3bafb117f6fc8de6200cc882f5d Mon Sep 17 00:00:00 2001 From: danzh Date: Tue, 20 Apr 2021 13:01:10 -0400 Subject: [PATCH 050/209] quiche: support downstream using SDS (#15821) Signed-off-by: Dan Zhang --- source/common/quic/envoy_quic_proof_source.cc | 12 +- .../quic/quic_transport_socket_factory.cc | 13 +- .../quic/quic_transport_socket_factory.h | 68 +++++- source/server/listener_impl.cc | 11 +- test/common/grpc/BUILD | 1 + test/common/grpc/grpc_client_integration.h | 25 +- test/common/http/conn_pool_grid_test.cc | 1 + .../quic/envoy_quic_proof_source_test.cc | 79 ++++--- test/config/utility.cc | 48 ++-- test/config/utility.h | 8 +- test/integration/BUILD | 1 + test/integration/base_integration_test.cc | 1 + test/integration/http_integration.cc | 2 +- .../integration/quic_http_integration_test.cc | 13 +- .../sds_dynamic_integration_test.cc | 214 ++++++++++++------ test/integration/utility.cc | 6 +- test/integration/utility.h | 3 +- .../listener_manager_impl_quic_only_test.cc | 2 +- test/test_common/environment.cc | 8 + test/test_common/environment.h | 15 ++ 20 files changed, 362 insertions(+), 169 deletions(-) diff --git a/source/common/quic/envoy_quic_proof_source.cc b/source/common/quic/envoy_quic_proof_source.cc index 71f1908854d90..bc0dfb8c8de8d 100644 --- a/source/common/quic/envoy_quic_proof_source.cc +++ b/source/common/quic/envoy_quic_proof_source.cc @@ -24,7 +24,6 @@ EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address absl::optional> cert_config_ref = res.cert_config_; if (!cert_config_ref.has_value()) { - ENVOY_LOG(warn, "No matching filter chain found for handshake."); return nullptr; } auto& cert_config = cert_config_ref.value().get(); @@ -95,17 +94,16 @@ EnvoyQuicProofSource::getTlsCertConfigAndFilterChain(const quic::QuicSocketAddre filter_chain_manager_.findFilterChain(connection_socket); if (filter_chain == nullptr) { listener_stats_.no_filter_chain_match_.inc(); + ENVOY_LOG(warn, "No matching filter chain found for handshake."); return {absl::nullopt, absl::nullopt}; } - const Network::TransportSocketFactory& transport_socket_factory = - filter_chain->transportSocketFactory(); + auto& transport_socket_factory = + dynamic_cast(filter_chain->transportSocketFactory()); std::vector> tls_cert_configs = - dynamic_cast(transport_socket_factory) - .serverContextConfig() - .tlsCertificates(); - + transport_socket_factory.getTlsCertificates(); if (tls_cert_configs.empty()) { + ENVOY_LOG(warn, "No certificate is configured in transport socket config."); return {absl::nullopt, absl::nullopt}; } // Only return the first TLS cert config. diff --git a/source/common/quic/quic_transport_socket_factory.cc b/source/common/quic/quic_transport_socket_factory.cc index 8611477d4cf27..5be0794ba5ec5 100644 --- a/source/common/quic/quic_transport_socket_factory.cc +++ b/source/common/quic/quic_transport_socket_factory.cc @@ -16,7 +16,10 @@ QuicServerTransportSocketConfigFactory::createTransportSocketFactory( config, context.messageValidationVisitor()); auto server_config = std::make_unique( quic_transport.downstream_tls_context(), context); - return std::make_unique(std::move(server_config)); + auto factory = + std::make_unique(context.scope(), std::move(server_config)); + factory->initialize(); + return factory; } ProtobufTypes::MessagePtr QuicServerTransportSocketConfigFactory::createEmptyConfigProto() { @@ -33,13 +36,17 @@ QuicClientTransportSocketConfigFactory::createTransportSocketFactory( config, context.messageValidationVisitor()); auto client_config = std::make_unique( quic_transport.upstream_tls_context(), context); - return std::make_unique(std::move(client_config), context); + auto factory = + std::make_unique(std::move(client_config), context); + factory->initialize(); + return factory; } QuicClientTransportSocketFactory::QuicClientTransportSocketFactory( Ssl::ClientContextConfigPtr config, Server::Configuration::TransportSocketFactoryContext& factory_context) - : fallback_factory_(std::make_unique( + : QuicTransportSocketFactoryBase(factory_context.scope(), "client"), + fallback_factory_(std::make_unique( std::move(config), factory_context.sslContextManager(), factory_context.scope())) {} ProtobufTypes::MessagePtr QuicClientTransportSocketConfigFactory::createEmptyConfigProto() { diff --git a/source/common/quic/quic_transport_socket_factory.h b/source/common/quic/quic_transport_socket_factory.h index 381d77f8acb15..ec8d182ce17e7 100644 --- a/source/common/quic/quic_transport_socket_factory.h +++ b/source/common/quic/quic_transport_socket_factory.h @@ -13,13 +13,38 @@ namespace Envoy { namespace Quic { +#define QUIC_TRANSPORT_SOCKET_FACTORY_STATS(COUNTER) \ + COUNTER(context_config_update_by_sds) \ + COUNTER(upstream_context_secrets_not_ready) \ + COUNTER(downstream_context_secrets_not_ready) + +struct QuicTransportSocketFactoryStats { + QUIC_TRANSPORT_SOCKET_FACTORY_STATS(GENERATE_COUNTER_STRUCT) +}; + +namespace { + +QuicTransportSocketFactoryStats generateStats(Stats::Scope& store, const std::string& perspective) { + return {QUIC_TRANSPORT_SOCKET_FACTORY_STATS( + POOL_COUNTER_PREFIX(store, fmt::format("quic_{}_transport_socket_factory.", perspective)))}; +} + +} // namespace + // Base class for QUIC transport socket factory. // Because QUIC stack handles all L4 data, there is no need of a real transport // socket for QUIC in current implementation. This factory doesn't provides a // transport socket, instead, its derived class provides TLS context config for // server and client. -class QuicTransportSocketFactoryBase : public Network::TransportSocketFactory { +class QuicTransportSocketFactoryBase : public Network::TransportSocketFactory, + protected Logger::Loggable { public: + QuicTransportSocketFactoryBase(Stats::Scope& store, const std::string& perspective) + : stats_(generateStats(store, perspective)) {} + + // To be called right after construction. + virtual void initialize() PURE; + // Network::TransportSocketFactory Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr /*options*/) const override { @@ -28,19 +53,42 @@ class QuicTransportSocketFactoryBase : public Network::TransportSocketFactory { bool implementsSecureTransport() const override { return true; } bool usesProxyProtocolOptions() const override { return false; } bool supportsAlpn() const override { return true; } + +protected: + virtual void onSecretUpdated() PURE; + QuicTransportSocketFactoryStats stats_; }; // TODO(danzh): when implement ProofSource, examine of it's necessary to // differentiate server and client side context config. class QuicServerTransportSocketFactory : public QuicTransportSocketFactoryBase { public: - QuicServerTransportSocketFactory(Ssl::ServerContextConfigPtr config) - : config_(std::move(config)) {} + QuicServerTransportSocketFactory(Stats::Scope& store, Ssl::ServerContextConfigPtr config) + : QuicTransportSocketFactoryBase(store, "server"), config_(std::move(config)) {} + + void initialize() override { + config_->setSecretUpdateCallback([this]() { + // The callback also updates config_ with the new secret. + onSecretUpdated(); + }); + } + + // Return TLS certificates if the context config is ready. + std::vector> + getTlsCertificates() const { + if (!config_->isReady()) { + ENVOY_LOG(warn, "SDS hasn't finished updating Ssl context config yet."); + stats_.downstream_context_secrets_not_ready_.inc(); + return {}; + } + return config_->tlsCertificates(); + } - const Ssl::ServerContextConfig& serverContextConfig() const { return *config_; } +protected: + void onSecretUpdated() override { stats_.context_config_update_by_sds_.inc(); } private: - std::unique_ptr config_; + Ssl::ServerContextConfigPtr config_; }; class QuicClientTransportSocketFactory : public QuicTransportSocketFactoryBase { @@ -49,6 +97,10 @@ class QuicClientTransportSocketFactory : public QuicTransportSocketFactoryBase { Ssl::ClientContextConfigPtr config, Server::Configuration::TransportSocketFactoryContext& factory_context); + void initialize() override { + // TODO(14829) fallback_factory_ needs to call onSecretUpdated() upon SDS update. + } + // As documented above for QuicTransportSocketFactoryBase, the actual HTTP/3 // code does not create transport sockets. // QuicClientTransportSocketFactory::createTransportSocket is called by the @@ -65,6 +117,12 @@ class QuicClientTransportSocketFactory : public QuicTransportSocketFactoryBase { return fallback_factory_->config(); } +protected: + void onSecretUpdated() override { + // fallback_factory_ will update the stats. + // TODO(14829) Client transport socket factory may also need to update quic crypto. + } + private: // The QUIC client transport socket can create TLS sockets for fallback to TCP. std::unique_ptr fallback_factory_; diff --git a/source/server/listener_impl.cc b/source/server/listener_impl.cc index ed1ff37e85a63..4c1f5f19dfa66 100644 --- a/source/server/listener_impl.cc +++ b/source/server/listener_impl.cc @@ -316,13 +316,12 @@ ListenerImpl::ListenerImpl(const envoy::config::listener::v3::Listener& config, createListenerFilterFactories(socket_type); validateFilterChains(socket_type); buildFilterChains(); - if (socket_type == Network::Socket::Type::Datagram) { - return; + if (socket_type != Network::Socket::Type::Datagram) { + buildSocketOptions(); + buildOriginalDstListenerFilter(); + buildProxyProtocolListenerFilter(); + buildTlsInspectorListenerFilter(); } - buildSocketOptions(); - buildOriginalDstListenerFilter(); - buildProxyProtocolListenerFilter(); - buildTlsInspectorListenerFilter(); if (!workers_started_) { // Initialize dynamic_init_manager_ from Server's init manager if it's not initialized. // NOTE: listener_init_target_ should be added to parent's initManager at the end of the diff --git a/test/common/grpc/BUILD b/test/common/grpc/BUILD index 86752e0f318cc..ed8fb7f6170de 100644 --- a/test/common/grpc/BUILD +++ b/test/common/grpc/BUILD @@ -132,6 +132,7 @@ envoy_cc_test_library( deps = [ "//source/common/common:assert_lib", "//test/mocks/secret:secret_mocks", + "//test/test_common:environment_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], diff --git a/test/common/grpc/grpc_client_integration.h b/test/common/grpc/grpc_client_integration.h index 80321fc835925..112b73f965456 100644 --- a/test/common/grpc/grpc_client_integration.h +++ b/test/common/grpc/grpc_client_integration.h @@ -4,6 +4,7 @@ #include "common/common/assert.h" +#include "test/test_common/environment.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -11,8 +12,6 @@ namespace Envoy { namespace Grpc { -// Support parameterizing over gRPC client type. -enum class ClientType { EnvoyGrpc, GoogleGrpc }; // Support parameterizing over state-of-the-world xDS vs delta xDS. enum class SotwOrDelta { Sotw, Delta }; @@ -118,35 +117,19 @@ class DeltaSotwIntegrationParamTest #define XDS_DEPRECATED_FEATURE_TEST_SKIP #endif // ENVOY_DISABLE_DEPRECATED_FEATURES -#ifdef ENVOY_GOOGLE_GRPC #define GRPC_CLIENT_INTEGRATION_PARAMS \ testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ - testing::Values(Grpc::ClientType::EnvoyGrpc, Grpc::ClientType::GoogleGrpc)) + testing::ValuesIn(TestEnvironment::getsGrpcVersionsForTest())) #define VERSIONED_GRPC_CLIENT_INTEGRATION_PARAMS \ testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ - testing::Values(Grpc::ClientType::EnvoyGrpc, Grpc::ClientType::GoogleGrpc), \ + testing::ValuesIn(TestEnvironment::getsGrpcVersionsForTest()), \ testing::Values(envoy::config::core::v3::ApiVersion::V3, \ envoy::config::core::v3::ApiVersion::V2, \ envoy::config::core::v3::ApiVersion::AUTO)) #define DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS \ testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ - testing::Values(Grpc::ClientType::EnvoyGrpc, Grpc::ClientType::GoogleGrpc), \ + testing::ValuesIn(TestEnvironment::getsGrpcVersionsForTest()), \ testing::Values(Grpc::SotwOrDelta::Sotw, Grpc::SotwOrDelta::Delta)) -#else -#define GRPC_CLIENT_INTEGRATION_PARAMS \ - testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ - testing::Values(Grpc::ClientType::EnvoyGrpc)) -#define VERSIONED_GRPC_CLIENT_INTEGRATION_PARAMS \ - testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ - testing::Values(Grpc::ClientType::EnvoyGrpc), \ - testing::Values(envoy::config::core::v3::ApiVersion::V3, \ - envoy::config::core::v3::ApiVersion::V2, \ - envoy::config::core::v3::ApiVersion::AUTO)) -#define DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS \ - testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ - testing::Values(Grpc::ClientType::EnvoyGrpc), \ - testing::Values(Grpc::SotwOrDelta::Sotw, Grpc::SotwOrDelta::Delta)) -#endif // ENVOY_GOOGLE_GRPC } // namespace Grpc } // namespace Envoy diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index 0406db0f28e79..d4a2e2097ebc3 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -455,6 +455,7 @@ TEST_F(ConnectivityGridTest, RealGrid) { // Set the cluster up to have a quic transport socket. Envoy::Ssl::ClientContextConfigPtr config(new NiceMock()); auto factory = std::make_unique(std::move(config)); + factory->initialize(); auto& matcher = static_cast(*cluster_->transport_socket_matcher_); EXPECT_CALL(matcher, resolve(_)) diff --git a/test/common/quic/envoy_quic_proof_source_test.cc b/test/common/quic/envoy_quic_proof_source_test.cc index a837a686c293c..898678ff374fc 100644 --- a/test/common/quic/envoy_quic_proof_source_test.cc +++ b/test/common/quic/envoy_quic_proof_source_test.cc @@ -133,11 +133,17 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { EnvoyQuicProofSourceTest() : server_address_(quic::QuicIpAddress::Loopback4(), 12345), client_address_(quic::QuicIpAddress::Loopback4(), 54321), - transport_socket_factory_(std::make_unique()), + mock_context_config_(new Ssl::MockServerContextConfig()), listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), POOL_GAUGE(listener_config_.listenerScope()), POOL_HISTOGRAM(listener_config_.listenerScope()))}), - proof_source_(listen_socket_, filter_chain_manager_, listener_stats_) {} + proof_source_(listen_socket_, filter_chain_manager_, listener_stats_) { + EXPECT_CALL(*mock_context_config_, setSecretUpdateCallback(_)).Times(testing::AtLeast(1u)); + transport_socket_factory_ = std::make_unique( + listener_config_.listenerScope(), + std::unique_ptr(mock_context_config_)); + transport_socket_factory_->initialize(); + } void expectCertChainAndPrivateKey(const std::string& cert, bool expect_private_key) { EXPECT_CALL(listen_socket_, ioHandle()).Times(expect_private_key ? 2u : 1u); @@ -153,14 +159,12 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { return &filter_chain_; })); EXPECT_CALL(filter_chain_, transportSocketFactory()) - .WillRepeatedly(ReturnRef(transport_socket_factory_)); + .WillRepeatedly(ReturnRef(*transport_socket_factory_)); + EXPECT_CALL(*mock_context_config_, isReady()).WillRepeatedly(Return(true)); std::vector> tls_cert_configs{ std::reference_wrapper(tls_cert_config_)}; - EXPECT_CALL(dynamic_cast( - transport_socket_factory_.serverContextConfig()), - tlsCertificates()) - .WillRepeatedly(Return(tls_cert_configs)); + EXPECT_CALL(*mock_context_config_, tlsCertificates()).WillRepeatedly(Return(tls_cert_configs)); EXPECT_CALL(tls_cert_config_, certificateChain()).WillOnce(ReturnRef(cert)); if (expect_private_key) { EXPECT_CALL(tls_cert_config_, privateKey()).WillOnce(ReturnRef(pkey_)); @@ -189,7 +193,8 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { Network::MockFilterChainManager filter_chain_manager_; Network::MockListenSocket listen_socket_; testing::NiceMock listener_config_; - QuicServerTransportSocketFactory transport_socket_factory_; + Ssl::MockServerContextConfig* mock_context_config_; + std::unique_ptr transport_socket_factory_; Ssl::MockTlsCertificateConfig tls_cert_config_; Server::ListenerStats listener_stats_; EnvoyQuicProofSource proof_source_; @@ -200,16 +205,30 @@ TEST_F(EnvoyQuicProofSourceTest, TestGetProof) { testGetProof(true); } -TEST_F(EnvoyQuicProofSourceTest, GetProofFailNoFilterChain) { - bool called = false; - auto callback = std::make_unique(called, false, server_config_, version_, - chlo_hash_, filter_chain_); - EXPECT_CALL(listen_socket_, ioHandle()); +TEST_F(EnvoyQuicProofSourceTest, GetProofFailBadConfig) { + // No filter chain. + EXPECT_CALL(listen_socket_, ioHandle()).Times(3); EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) - .WillRepeatedly(Invoke([&](const Network::ConnectionSocket&) { return nullptr; })); - proof_source_.GetProof(server_address_, client_address_, hostname_, server_config_, version_, - chlo_hash_, std::move(callback)); - EXPECT_TRUE(called); + .WillOnce(Invoke([&](const Network::ConnectionSocket&) { return nullptr; })); + testGetProof(false); + + // Cert not ready. + EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) + .WillOnce(Invoke([&](const Network::ConnectionSocket&) { return &filter_chain_; })); + EXPECT_CALL(filter_chain_, transportSocketFactory()) + .WillOnce(ReturnRef(*transport_socket_factory_)); + EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(false)); + testGetProof(false); + + // No certs in config. + EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) + .WillOnce(Invoke([&](const Network::ConnectionSocket&) { return &filter_chain_; })); + EXPECT_CALL(filter_chain_, transportSocketFactory()) + .WillOnce(ReturnRef(*transport_socket_factory_)); + EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(true)); + std::vector> tls_cert_configs{}; + EXPECT_CALL(*mock_context_config_, tlsCertificates()).WillOnce(Return(tls_cert_configs)); + testGetProof(false); } TEST_F(EnvoyQuicProofSourceTest, GetProofFailNoCertConfig) { @@ -229,11 +248,9 @@ TEST_F(EnvoyQuicProofSourceTest, GetProofFailNoCertConfig) { return &filter_chain_; })); EXPECT_CALL(filter_chain_, transportSocketFactory()) - .WillRepeatedly(ReturnRef(transport_socket_factory_)); - - EXPECT_CALL(dynamic_cast( - transport_socket_factory_.serverContextConfig()), - tlsCertificates()) + .WillRepeatedly(ReturnRef(*transport_socket_factory_)); + EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(true)); + EXPECT_CALL(*mock_context_config_, tlsCertificates()) .WillRepeatedly( Return(std::vector>{})); proof_source_.GetProof(server_address_, client_address_, hostname_, server_config_, version_, @@ -278,17 +295,14 @@ TEST_F(EnvoyQuicProofSourceTest, UnexpectedPrivateKey) { EXPECT_CALL(listen_socket_, ioHandle()); EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) .WillOnce(Invoke([&](const Network::ConnectionSocket&) { return &filter_chain_; })); - auto server_context_config = std::make_unique(); - auto server_context_config_ptr = server_context_config.get(); - QuicServerTransportSocketFactory transport_socket_factory(std::move(server_context_config)); EXPECT_CALL(filter_chain_, transportSocketFactory()) - .WillRepeatedly(ReturnRef(transport_socket_factory)); + .WillRepeatedly(ReturnRef(*transport_socket_factory_)); Ssl::MockTlsCertificateConfig tls_cert_config; std::vector> tls_cert_configs{ std::reference_wrapper(tls_cert_config)}; - EXPECT_CALL(*server_context_config_ptr, tlsCertificates()) - .WillRepeatedly(Return(tls_cert_configs)); + EXPECT_CALL(*mock_context_config_, tlsCertificates()).WillRepeatedly(Return(tls_cert_configs)); + EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(true)); std::string rsa_pkey_1024_len(R"(-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQC79hDq/OwN3ke3EF6Ntdi9R+VSrl9MStk992l1us8lZhq+e0zU OlvxbUeZ8wyVkzs1gqI1it1IwF+EpdGhHhjggZjg040GD3HWSuyCzpHh+nLwJxtQ @@ -314,17 +328,14 @@ TEST_F(EnvoyQuicProofSourceTest, InvalidPrivateKey) { EXPECT_CALL(listen_socket_, ioHandle()); EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) .WillOnce(Invoke([&](const Network::ConnectionSocket&) { return &filter_chain_; })); - auto server_context_config = std::make_unique(); - auto server_context_config_ptr = server_context_config.get(); - QuicServerTransportSocketFactory transport_socket_factory(std::move(server_context_config)); EXPECT_CALL(filter_chain_, transportSocketFactory()) - .WillRepeatedly(ReturnRef(transport_socket_factory)); + .WillRepeatedly(ReturnRef(*transport_socket_factory_)); Ssl::MockTlsCertificateConfig tls_cert_config; std::vector> tls_cert_configs{ std::reference_wrapper(tls_cert_config)}; - EXPECT_CALL(*server_context_config_ptr, tlsCertificates()) - .WillRepeatedly(Return(tls_cert_configs)); + EXPECT_CALL(*mock_context_config_, tlsCertificates()).WillRepeatedly(Return(tls_cert_configs)); + EXPECT_CALL(*mock_context_config_, isReady()).WillOnce(Return(true)); std::string invalid_pkey("abcdefg"); EXPECT_CALL(tls_cert_config, privateKey()).WillOnce(ReturnRef(invalid_pkey)); proof_source_.ComputeTlsSignature(server_address_, client_address_, hostname_, diff --git a/test/config/utility.cc b/test/config/utility.cc index 8da3ea9c1bffc..0a444ee9cff49 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -78,7 +78,7 @@ std::string ConfigHelper::baseConfig() { Platform::null_device_path, Platform::null_device_path); } -std::string ConfigHelper::baseUdpListenerConfig() { +std::string ConfigHelper::baseUdpListenerConfig(std::string listen_address) { return fmt::format(R"EOF( admin: access_log: @@ -106,11 +106,11 @@ std::string ConfigHelper::baseUdpListenerConfig() { name: listener_0 address: socket_address: - address: 0.0.0.0 + address: {} port_value: 0 protocol: udp )EOF", - Platform::null_device_path); + Platform::null_device_path, listen_address); } std::string ConfigHelper::tcpProxyConfig() { @@ -194,7 +194,7 @@ std::string ConfigHelper::httpProxyConfig(bool downstream_use_quic) { // it's better to combine with HTTP_PROXY_CONFIG, and use config modifiers to // specify quic specific things. std::string ConfigHelper::quicHttpProxyConfig() { - return absl::StrCat(baseUdpListenerConfig(), fmt::format(R"EOF( + return absl::StrCat(baseUdpListenerConfig("127.0.0.1"), fmt::format(R"EOF( filter_chains: transport_socket: name: envoy.transport_sockets.quic @@ -226,7 +226,7 @@ std::string ConfigHelper::quicHttpProxyConfig() { udp_listener_config: quic_options: {{}} )EOF", - Platform::null_device_path)); + Platform::null_device_path)); } std::string ConfigHelper::defaultBufferFilter() { @@ -1066,6 +1066,30 @@ void ConfigHelper::setClientCodec(envoy::extensions::filters::network::http_conn } } +void ConfigHelper::configDownstreamTransportSocketWithTls( + envoy::config::bootstrap::v3::Bootstrap& bootstrap, + std::function + configure_tls_context) { + for (auto& listener : *bootstrap.mutable_static_resources()->mutable_listeners()) { + ASSERT(listener.filter_chains_size() > 0); + auto* filter_chain = listener.mutable_filter_chains(0); + auto* transport_socket = filter_chain->mutable_transport_socket(); + if (listener.has_udp_listener_config() && listener.udp_listener_config().has_quic_options()) { + transport_socket->set_name("envoy.transport_sockets.quic"); + envoy::extensions::transport_sockets::quic::v3::QuicDownstreamTransport + quic_transport_socket_config; + configure_tls_context(*quic_transport_socket_config.mutable_downstream_tls_context() + ->mutable_common_tls_context()); + transport_socket->mutable_typed_config()->PackFrom(quic_transport_socket_config); + } else if (!listener.has_udp_listener_config()) { + transport_socket->set_name("envoy.transport_sockets.tls"); + envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context; + configure_tls_context(*tls_context.mutable_common_tls_context()); + transport_socket->mutable_typed_config()->PackFrom(tls_context); + } + } +} + void ConfigHelper::addSslConfig(const ServerSslOptions& options) { RELEASE_ASSERT(!finalized_, ""); @@ -1082,20 +1106,16 @@ void ConfigHelper::addSslConfig(const ServerSslOptions& options) { } void ConfigHelper::addQuicDownstreamTransportSocketConfig(bool reuse_port) { - envoy::extensions::transport_sockets::quic::v3::QuicDownstreamTransport - quic_transport_socket_config; - auto tls_context = quic_transport_socket_config.mutable_downstream_tls_context(); - ConfigHelper::initializeTls(ConfigHelper::ServerSslOptions().setRsaCert(true).setTlsV13(true), - *tls_context->mutable_common_tls_context()); for (auto& listener : *bootstrap_.mutable_static_resources()->mutable_listeners()) { if (listener.udp_listener_config().has_quic_options()) { - ASSERT(listener.filter_chains_size() > 0); - auto* filter_chain = listener.mutable_filter_chains(0); - auto* transport_socket = filter_chain->mutable_transport_socket(); - transport_socket->mutable_typed_config()->PackFrom(quic_transport_socket_config); listener.set_reuse_port(reuse_port); } } + configDownstreamTransportSocketWithTls( + bootstrap_, + [](envoy::extensions::transport_sockets::tls::v3::CommonTlsContext& common_tls_context) { + initializeTls(ServerSslOptions().setRsaCert(true).setTlsV13(true), common_tls_context); + }); } bool ConfigHelper::setAccessLog( diff --git a/test/config/utility.h b/test/config/utility.h index 0b18cde7756b3..f8daf258f7ed5 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -116,7 +116,7 @@ class ConfigHelper { static std::string baseConfig(); // A basic configuration (admin port, cluster_0, one udp listener) with no network filters. - static std::string baseUdpListenerConfig(); + static std::string baseUdpListenerConfig(std::string listen_address = "0.0.0.0"); // A string for a tls inspector listener filter which can be used with addListenerFilter() static std::string tlsInspectorFilter(); @@ -229,6 +229,12 @@ class ConfigHelper { void setClientCodec(envoy::extensions::filters::network::http_connection_manager::v3:: HttpConnectionManager::CodecType type); + // Add TLS configuration for either SSL or QUIC transport socket according to listener config. + void configDownstreamTransportSocketWithTls( + envoy::config::bootstrap::v3::Bootstrap& bootstrap, + std::function + configure_tls_context); + // Add the default SSL configuration. void addSslConfig(const ServerSslOptions& options); void addSslConfig() { addSslConfig({}); } diff --git a/test/integration/BUILD b/test/integration/BUILD index 499a76930b776..8ab9096c0c194 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1176,6 +1176,7 @@ envoy_cc_test( "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/quic/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", "@envoy_api//envoy/service/secret/v3:pkg_cc_proto", diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index add69acf4457d..cf0b1d193aaf6 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -67,6 +67,7 @@ BaseIntegrationTest::BaseIntegrationTest(const InstanceConstSharedPtrFn& upstrea return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); })); ON_CALL(factory_context_, api()).WillByDefault(ReturnRef(*api_)); + ON_CALL(factory_context_, scope()).WillByDefault(ReturnRef(stats_store_)); } BaseIntegrationTest::BaseIntegrationTest(Network::Address::IpVersion version, diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 8960e143407b0..99827c4020d8d 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -328,7 +328,7 @@ void HttpIntegrationTest::initialize() { // Needs to be instantiated before base class calls initialize() which starts a QUIC listener // according to the config. quic_transport_socket_factory_ = - IntegrationUtil::createQuicUpstreamTransportSocketFactory(*api_, san_to_match_); + IntegrationUtil::createQuicUpstreamTransportSocketFactory(*api_, stats_store_, san_to_match_); // Needed to config QUIC transport socket factory, and needs to be added before base class calls // initialize(). diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 33671493049e1..d1eb590cd64e9 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -36,6 +36,7 @@ #include "common/quic/envoy_quic_utils.h" #include "common/quic/quic_transport_socket_factory.h" #include "test/common/quic/test_utils.h" +#include "test/config/integration/certs/clientcert_hash.h" #include "extensions/transport_sockets/tls/context_config_impl.h" namespace Envoy { @@ -223,24 +224,24 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers constexpr auto timeout_first = std::chrono::seconds(15); constexpr auto timeout_subsequent = std::chrono::milliseconds(10); if (GetParam().first == Network::Address::IpVersion::v4) { - test_server_->waitForCounterEq("listener.0.0.0.0_0.downstream_cx_total", 8u, timeout_first); + test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_cx_total", 8u, timeout_first); } else { - test_server_->waitForCounterEq("listener.[__]_0.downstream_cx_total", 8u, timeout_first); + test_server_->waitForCounterEq("listener.[__1]_0.downstream_cx_total", 8u, timeout_first); } for (size_t i = 0; i < concurrency_; ++i) { if (GetParam().first == Network::Address::IpVersion::v4) { test_server_->waitForGaugeEq( - fmt::format("listener.0.0.0.0_0.worker_{}.downstream_cx_active", i), 1u, + fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_active", i), 1u, timeout_subsequent); test_server_->waitForCounterEq( - fmt::format("listener.0.0.0.0_0.worker_{}.downstream_cx_total", i), 1u, + fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_total", i), 1u, timeout_subsequent); } else { test_server_->waitForGaugeEq( - fmt::format("listener.[__]_0.worker_{}.downstream_cx_active", i), 1u, + fmt::format("listener.[__1]_0.worker_{}.downstream_cx_active", i), 1u, timeout_subsequent); test_server_->waitForCounterEq( - fmt::format("listener.[__]_0.worker_{}.downstream_cx_total", i), 1u, + fmt::format("listener.[__1]_0.worker_{}.downstream_cx_total", i), 1u, timeout_subsequent); } } diff --git a/test/integration/sds_dynamic_integration_test.cc b/test/integration/sds_dynamic_integration_test.cc index ff714e02c034e..44c38873b31ce 100644 --- a/test/integration/sds_dynamic_integration_test.cc +++ b/test/integration/sds_dynamic_integration_test.cc @@ -3,6 +3,8 @@ #include "envoy/config/bootstrap/v3/bootstrap.pb.h" #include "envoy/config/core/v3/config_source.pb.h" +#include "envoy/extensions/transport_sockets/quic/v3/quic_transport.pb.h" +#include "envoy/extensions/transport_sockets/quic/v3/quic_transport.pb.validate.h" #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" #include "envoy/service/discovery/v3/discovery.pb.h" #include "envoy/service/secret/v3/sds.pb.h" @@ -13,6 +15,10 @@ #include "common/network/connection_impl.h" #include "common/network/utility.h" +#ifdef ENVOY_ENABLE_QUIC +#include "common/quic/client_connection_factory_impl.h" +#endif + #include "extensions/transport_sockets/tls/context_config_impl.h" #include "extensions/transport_sockets/tls/context_manager_impl.h" #include "extensions/transport_sockets/tls/ssl_socket.h" @@ -40,17 +46,57 @@ namespace Ssl { // Hack to force linking of the service: https://github.com/google/protobuf/issues/4221. const envoy::service::secret::v3::SdsDummy _sds_dummy; +struct TestParams { + Network::Address::IpVersion ip_version; + Grpc::ClientType sds_grpc_type; + bool test_quic; +}; + +std::string sdsTestParamsToString(const ::testing::TestParamInfo& p) { + return fmt::format( + "{}_{}_{}", p.param.ip_version == Network::Address::IpVersion::v4 ? "IPv4" : "IPv6", + p.param.sds_grpc_type == Grpc::ClientType::GoogleGrpc ? "GoogleGrpc" : "EnvoyGrpc", + p.param.test_quic ? "UsesQuic" : "UsesTcp"); +} + +std::vector getSdsTestsParams(bool test_quic) { + std::vector ret; + for (auto ip_version : TestEnvironment::getIpVersionsForTest()) { + for (auto sds_grpc_type : TestEnvironment::getsGrpcVersionsForTest()) { + ret.push_back(TestParams{ip_version, sds_grpc_type, false}); + if (test_quic) { +#ifdef ENVOY_ENABLE_QUIC + ret.push_back(TestParams{ip_version, sds_grpc_type, true}); +#else + ENVOY_LOG_MISC(warn, "Skipping HTTP/3 as support is compiled out"); +#endif + } + } + } + return ret; +} + // Sds integration base class with following support: // * functions to create sds upstream, and send sds response // * functions to create secret protobuf. -class SdsDynamicIntegrationBaseTest : public Grpc::GrpcClientIntegrationParamTest, - public HttpIntegrationTest { +class SdsDynamicIntegrationBaseTest : public Grpc::BaseGrpcClientIntegrationParamTest, + public HttpIntegrationTest, + public testing::TestWithParam { public: SdsDynamicIntegrationBaseTest() - : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, ipVersion()), + : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, GetParam().ip_version), server_cert_("server_cert"), validation_secret_("validation_secret"), client_cert_("client_cert") {} + SdsDynamicIntegrationBaseTest(Http::CodecClient::Type downstream_protocol, + Network::Address::IpVersion version, const std::string& config) + : HttpIntegrationTest(downstream_protocol, version, config), server_cert_("server_cert"), + validation_secret_("validation_secret"), client_cert_("client_cert"), + test_quic_(GetParam().test_quic) {} + + Network::Address::IpVersion ipVersion() const override { return GetParam().ip_version; } + Grpc::ClientType clientType() const override { return GetParam().sds_grpc_type; } + protected: void createSdsStream(FakeUpstream&) { createXdsConnection(); @@ -141,33 +187,31 @@ class SdsDynamicIntegrationBaseTest : public Grpc::GrpcClientIntegrationParamTes const std::string validation_secret_; const std::string client_cert_; bool v3_resource_api_{false}; + bool test_quic_; }; // Downstream SDS integration test: static Listener with ssl cert from SDS class SdsDynamicDownstreamIntegrationTest : public SdsDynamicIntegrationBaseTest { public: + SdsDynamicDownstreamIntegrationTest() + : SdsDynamicIntegrationBaseTest((GetParam().test_quic ? Http::CodecClient::Type::HTTP3 + : Http::CodecClient::Type::HTTP1), + GetParam().ip_version, + ConfigHelper::httpProxyConfig(GetParam().test_quic)) {} + void initialize() override { + ASSERT(test_quic_ ? downstream_protocol_ == Http::CodecClient::Type::HTTP3 + : downstream_protocol_ == Http::CodecClient::Type::HTTP1); config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { - envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context; - auto* common_tls_context = tls_context.mutable_common_tls_context(); - auto* transport_socket = bootstrap.mutable_static_resources() - ->mutable_listeners(0) - ->mutable_filter_chains(0) - ->mutable_transport_socket(); - common_tls_context->add_alpn_protocols(Http::Utility::AlpnNames::get().Http11); - - auto* validation_context = common_tls_context->mutable_validation_context(); - validation_context->mutable_trusted_ca()->set_filename( - TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); - validation_context->add_verify_certificate_hash(TEST_CLIENT_CERT_HASH); - - // Modify the listener ssl cert to use SDS from sds_cluster - auto* secret_config = common_tls_context->add_tls_certificate_sds_secret_configs(); - setUpSdsConfig(secret_config, "server_cert"); - - transport_socket->set_name("envoy.transport_sockets.tls"); - transport_socket->mutable_typed_config()->PackFrom(tls_context); + config_helper_.configDownstreamTransportSocketWithTls( + bootstrap, + [this]( + envoy::extensions::transport_sockets::tls::v3::CommonTlsContext& common_tls_context) { + configToUseSds(common_tls_context); + }); + }); + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { // Add a static sds cluster auto* sds_cluster = bootstrap.mutable_static_resources()->add_clusters(); sds_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); @@ -179,21 +223,62 @@ class SdsDynamicDownstreamIntegrationTest : public SdsDynamicIntegrationBaseTest client_ssl_ctx_ = createClientSslTransportSocketFactory({}, context_manager_, *api_); } + void configToUseSds( + envoy::extensions::transport_sockets::tls::v3::CommonTlsContext& common_tls_context) { + common_tls_context.add_alpn_protocols(test_quic_ ? Http::Utility::AlpnNames::get().Http3 + : Http::Utility::AlpnNames::get().Http11); + + auto* validation_context = common_tls_context.mutable_validation_context(); + validation_context->mutable_trusted_ca()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); + validation_context->add_verify_certificate_hash(TEST_CLIENT_CERT_HASH); + + // Modify the listener ssl cert to use SDS from sds_cluster + auto* secret_config = common_tls_context.add_tls_certificate_sds_secret_configs(); + setUpSdsConfig(secret_config, "server_cert"); + } + void createUpstreams() override { create_xds_upstream_ = true; HttpIntegrationTest::createUpstreams(); } + void waitForSdsUpdateStats(size_t times) { + test_server_->waitForCounterGe( + listenerStatPrefix(test_quic_ + ? "quic_server_transport_socket_factory.context_config_update_by_sds" + : "server_ssl_socket_factory.ssl_context_update_by_sds"), + times, std::chrono::milliseconds(5000)); + } + void TearDown() override { cleanUpXdsConnection(); client_ssl_ctx_.reset(); } Network::ClientConnectionPtr makeSslClientConnection() { - Network::Address::InstanceConstSharedPtr address = getSslAddress(version_, lookupPort("http")); - return dispatcher_->createClientConnection(address, Network::Address::InstanceConstSharedPtr(), - client_ssl_ctx_->createTransportSocket(nullptr), - nullptr); + int port = lookupPort("http"); + if (downstream_protocol_ <= Http::CodecClient::Type::HTTP2) { + Network::Address::InstanceConstSharedPtr address = getSslAddress(version_, port); + return dispatcher_->createClientConnection( + address, Network::Address::InstanceConstSharedPtr(), + client_ssl_ctx_->createTransportSocket(nullptr), nullptr); + } +#ifdef ENVOY_ENABLE_QUIC + std::string url = "udp://" + Network::Test::getLoopbackAddressUrlString(version_) + ":" + + std::to_string(port); + Network::Address::InstanceConstSharedPtr local_address; + if (version_ == Network::Address::IpVersion::v4) { + local_address = Network::Utility::getLocalAddress(Network::Address::IpVersion::v4); + } else { + // Docker only works with loopback v6 address. + local_address = std::make_shared("::1"); + } + return Quic::createQuicNetworkConnection(*quic_connection_persistent_info_, *dispatcher_, + Network::Utility::resolveUrl(url), local_address); +#else + NOT_REACHED_GCOVR_EXCL_LINE; +#endif } protected: @@ -201,7 +286,7 @@ class SdsDynamicDownstreamIntegrationTest : public SdsDynamicIntegrationBaseTest }; INSTANTIATE_TEST_SUITE_P(IpVersionsClientType, SdsDynamicDownstreamIntegrationTest, - GRPC_CLIENT_INTEGRATION_PARAMS); + testing::ValuesIn(getSdsTestsParams(true)), sdsTestParamsToString); class SdsDynamicKeyRotationIntegrationTest : public SdsDynamicDownstreamIntegrationTest { protected: @@ -221,10 +306,8 @@ class SdsDynamicKeyRotationIntegrationTest : public SdsDynamicDownstreamIntegrat // We don't care about multiple gRPC types here, Envoy gRPC is fine, the // interest is on the filesystem. -INSTANTIATE_TEST_SUITE_P( - IpVersionsClientType, SdsDynamicKeyRotationIntegrationTest, - testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), - testing::Values(Grpc::ClientType::EnvoyGrpc))); +INSTANTIATE_TEST_SUITE_P(IpVersionsClientType, SdsDynamicKeyRotationIntegrationTest, + testing::ValuesIn(getSdsTestsParams(true)), sdsTestParamsToString); // Validate that a basic key-cert rotation works via symlink rename. TEST_P(SdsDynamicKeyRotationIntegrationTest, BasicRotation) { @@ -239,8 +322,7 @@ TEST_P(SdsDynamicKeyRotationIntegrationTest, BasicRotation) { initialize(); // Initial update from filesystem. - test_server_->waitForCounterGe( - listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds"), 1); + waitForSdsUpdateStats(1); ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { return makeSslClientConnection(); @@ -251,8 +333,7 @@ TEST_P(SdsDynamicKeyRotationIntegrationTest, BasicRotation) { // Rotate. TestEnvironment::renameFile(TestEnvironment::temporaryPath("root/new"), TestEnvironment::temporaryPath("root/current")); - test_server_->waitForCounterGe( - listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds"), 2); + waitForSdsUpdateStats(2); // The rotation is not a SDS attempt, so no change to these stats. EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_success")->value()); EXPECT_EQ(0, test_server_->counter("sds.server_cert.update_rejected")->value()); @@ -274,8 +355,7 @@ TEST_P(SdsDynamicKeyRotationIntegrationTest, EmptyRotation) { initialize(); // Initial update from filesystem. - test_server_->waitForCounterGe( - listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds"), 1); + waitForSdsUpdateStats(1); ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { return makeSslClientConnection(); @@ -288,10 +368,7 @@ TEST_P(SdsDynamicKeyRotationIntegrationTest, EmptyRotation) { TestEnvironment::renameFile(TestEnvironment::temporaryPath("root/empty"), TestEnvironment::temporaryPath("root/current")); test_server_->waitForCounterEq("sds.server_cert.key_rotation_failed", 1); - EXPECT_EQ(1, - test_server_ - ->counter(listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds")) - ->value()); + waitForSdsUpdateStats(1); // The rotation is not a SDS attempt, so no change to these stats. EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_success")->value()); EXPECT_EQ(0, test_server_->counter("sds.server_cert.update_rejected")->value()); @@ -340,9 +417,8 @@ TEST_P(SdsDynamicDownstreamIntegrationTest, WrongSecretFirst) { sendSdsResponse(getServerSecret()); - // Wait for ssl_context_updated_by_sds counter. - test_server_->waitForCounterGe( - listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds"), 1); + // Wait for sds update counter. + waitForSdsUpdateStats(1); ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { return makeSslClientConnection(); @@ -360,22 +436,15 @@ class SdsDynamicDownstreamCertValidationContextTest : public SdsDynamicDownstrea void initialize() override { config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { - auto* transport_socket = bootstrap.mutable_static_resources() - ->mutable_listeners(0) - ->mutable_filter_chains(0) - ->mutable_transport_socket(); - envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context; - auto* common_tls_context = tls_context.mutable_common_tls_context(); - common_tls_context->add_alpn_protocols(Http::Utility::AlpnNames::get().Http11); - - auto* tls_certificate = common_tls_context->add_tls_certificates(); - tls_certificate->mutable_certificate_chain()->set_filename( - TestEnvironment::runfilesPath("test/config/integration/certs/servercert.pem")); - tls_certificate->mutable_private_key()->set_filename( - TestEnvironment::runfilesPath("test/config/integration/certs/serverkey.pem")); - setUpSdsValidationContext(common_tls_context); - transport_socket->set_name("envoy.transport_sockets.tls"); - transport_socket->mutable_typed_config()->PackFrom(tls_context); + config_helper_.configDownstreamTransportSocketWithTls( + bootstrap, + [this]( + envoy::extensions::transport_sockets::tls::v3::CommonTlsContext& common_tls_context) { + common_tls_context.add_alpn_protocols(test_quic_ + ? Http::Utility::AlpnNames::get().Http3 + : Http::Utility::AlpnNames::get().Http11); + configureInlinedCerts(&common_tls_context); + }); // Add a static sds cluster auto* sds_cluster = bootstrap.mutable_static_resources()->add_clusters(); @@ -408,6 +477,16 @@ class SdsDynamicDownstreamCertValidationContextTest : public SdsDynamicDownstrea client_ssl_ctx_ = createClientSslTransportSocketFactory({}, context_manager_, *api_); } + void configureInlinedCerts( + envoy::extensions::transport_sockets::tls::v3::CommonTlsContext* common_tls_context) { + auto* tls_certificate = common_tls_context->add_tls_certificates(); + tls_certificate->mutable_certificate_chain()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/servercert.pem")); + tls_certificate->mutable_private_key()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/serverkey.pem")); + setUpSdsValidationContext(common_tls_context); + } + void setUpSdsValidationContext( envoy::extensions::transport_sockets::tls::v3::CommonTlsContext* common_tls_context) { if (use_combined_validation_context_) { @@ -465,7 +544,7 @@ class SdsDynamicDownstreamCertValidationContextTest : public SdsDynamicDownstrea }; INSTANTIATE_TEST_SUITE_P(IpVersionsClientType, SdsDynamicDownstreamCertValidationContextTest, - GRPC_CLIENT_INTEGRATION_PARAMS); + testing::ValuesIn(getSdsTestsParams(true)), sdsTestParamsToString); // A test that SDS server send a good certificate validation context for a static listener. // The first ssl request should be OK. @@ -521,8 +600,7 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, BasicWithSharedSecret) { // depending on the verification_secret were updated. test_server_->waitForCounterGe( "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); - test_server_->waitForCounterGe( - listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds"), 1); + waitForSdsUpdateStats(1); ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { return makeSslClientConnection(); @@ -549,8 +627,7 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, CombinedValidationContextW // depending on the verification_secret were updated. test_server_->waitForCounterGe( "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); - test_server_->waitForCounterGe( - listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds"), 1); + waitForSdsUpdateStats(1); ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { return makeSslClientConnection(); @@ -563,6 +640,7 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, CombinedValidationContextW } // Upstream SDS integration test: a static cluster has ssl cert from SDS. +// TODO(15034) Enable SDS support in QUIC upstream. class SdsDynamicUpstreamIntegrationTest : public SdsDynamicIntegrationBaseTest { public: void initialize() override { @@ -608,7 +686,7 @@ class SdsDynamicUpstreamIntegrationTest : public SdsDynamicIntegrationBaseTest { }; INSTANTIATE_TEST_SUITE_P(IpVersions, SdsDynamicUpstreamIntegrationTest, - GRPC_CLIENT_INTEGRATION_PARAMS); + testing::ValuesIn(getSdsTestsParams(false)), sdsTestParamsToString); // To test a static cluster with sds. SDS send a good client secret first. // The first request should work. @@ -674,6 +752,7 @@ TEST_P(SdsDynamicUpstreamIntegrationTest, WrongSecretFirst) { } // Test CDS with SDS. A cluster provided by CDS raises new SDS request for upstream cert. +// TODO(15034) Enable SDS support in QUIC upstream. class SdsCdsIntegrationTest : public SdsDynamicIntegrationBaseTest { public: void initialize() override { @@ -763,7 +842,8 @@ class SdsCdsIntegrationTest : public SdsDynamicIntegrationBaseTest { FakeStreamPtr sds_stream_; }; -INSTANTIATE_TEST_SUITE_P(IpVersions, SdsCdsIntegrationTest, GRPC_CLIENT_INTEGRATION_PARAMS); +INSTANTIATE_TEST_SUITE_P(IpVersions, SdsCdsIntegrationTest, + testing::ValuesIn(getSdsTestsParams(true)), sdsTestParamsToString); TEST_P(SdsCdsIntegrationTest, BasicSuccess) { on_server_init_function_ = [this]() { diff --git a/test/integration/utility.cc b/test/integration/utility.cc index 37286f7561126..0f78a4a113df1 100644 --- a/test/integration/utility.cc +++ b/test/integration/utility.cc @@ -124,10 +124,11 @@ class TestConnectionCallbacks : public Network::ConnectionCallbacks { }; Network::TransportSocketFactoryPtr -IntegrationUtil::createQuicUpstreamTransportSocketFactory(Api::Api& api, +IntegrationUtil::createQuicUpstreamTransportSocketFactory(Api::Api& api, Stats::Store& store, const std::string& san_to_match) { NiceMock context; ON_CALL(context, api()).WillByDefault(testing::ReturnRef(api)); + ON_CALL(context, scope()).WillByDefault(testing::ReturnRef(store)); envoy::extensions::transport_sockets::quic::v3::QuicUpstreamTransport quic_transport_socket_config; auto* tls_context = quic_transport_socket_config.mutable_upstream_tls_context(); @@ -204,7 +205,8 @@ IntegrationUtil::makeSingleRequest(const Network::Address::InstanceConstSharedPt #ifdef ENVOY_ENABLE_QUIC Network::TransportSocketFactoryPtr transport_socket_factory = - createQuicUpstreamTransportSocketFactory(api, "spiffe://lyft.com/backend-team"); + createQuicUpstreamTransportSocketFactory(api, mock_stats_store, + "spiffe://lyft.com/backend-team"); std::unique_ptr persistent_info; persistent_info = std::make_unique( *dispatcher, *transport_socket_factory, mock_stats_store, time_system, addr); diff --git a/test/integration/utility.h b/test/integration/utility.h index d05a222821baa..f143676f86d36 100644 --- a/test/integration/utility.h +++ b/test/integration/utility.h @@ -198,7 +198,8 @@ class IntegrationUtil { * @return TransportSocketFactoryPtr the client transport socket factory. */ static Network::TransportSocketFactoryPtr - createQuicUpstreamTransportSocketFactory(Api::Api& api, const std::string& san_to_match); + createQuicUpstreamTransportSocketFactory(Api::Api& api, Stats::Store& store, + const std::string& san_to_match); }; // A set of connection callbacks which tracks connection state. diff --git a/test/server/listener_manager_impl_quic_only_test.cc b/test/server/listener_manager_impl_quic_only_test.cc index d20ac9c7d42dc..52e1fee586ef5 100644 --- a/test/server/listener_manager_impl_quic_only_test.cc +++ b/test/server/listener_manager_impl_quic_only_test.cc @@ -125,7 +125,7 @@ reuse_port: true auto& quic_socket_factory = dynamic_cast( filter_chain->transportSocketFactory()); EXPECT_TRUE(quic_socket_factory.implementsSecureTransport()); - EXPECT_TRUE(quic_socket_factory.serverContextConfig().isReady()); + EXPECT_FALSE(quic_socket_factory.getTlsCertificates().empty()); } #endif diff --git a/test/test_common/environment.cc b/test/test_common/environment.cc index 62c8d02c011a6..63fffcafc7fab 100644 --- a/test/test_common/environment.cc +++ b/test/test_common/environment.cc @@ -254,6 +254,14 @@ Server::Options& TestEnvironment::getOptions() { return *options; } +std::vector TestEnvironment::getsGrpcVersionsForTest() { +#ifdef ENVOY_GOOGLE_GRPC + return {Grpc::ClientType::EnvoyGrpc, Grpc::ClientType::GoogleGrpc}; +#else + return {Grpc::ClientType::EnvoyGrpc}; +#endif +} + const std::string& TestEnvironment::temporaryDirectory() { CONSTRUCT_ON_FIRST_USE(std::string, getTemporaryDirectory()); } diff --git a/test/test_common/environment.h b/test/test_common/environment.h index 828cfb1113cdf..935c495c95e55 100644 --- a/test/test_common/environment.h +++ b/test/test_common/environment.h @@ -16,6 +16,14 @@ #include "tools/cpp/runfiles/runfiles.h" namespace Envoy { + +namespace Grpc { + +// Support parameterizing over gRPC client type. +enum class ClientType { EnvoyGrpc, GoogleGrpc }; + +} // namespace Grpc + class TestEnvironment { public: using PortMap = absl::node_hash_map; @@ -54,6 +62,13 @@ class TestEnvironment { */ static std::vector getIpVersionsForTest(); + /** + * Tests can be run with Envoy Grpc and Google Grpc or Envoy Grpc alone by setting compiler option + * `--define google_grpc=disabled`. + * @return a vector of Grpc versions to test. + */ + static std::vector getsGrpcVersionsForTest(); + /** * Obtain command-line options reference. * @return Server::Options& with command-line options. From 6b8a898ee99ee00330a2498d8beb7124259b4da4 Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Tue, 20 Apr 2021 12:16:46 -0700 Subject: [PATCH 051/209] server: fix TAP validation mode (#15932) Signed-off-by: Sotiris Nanopoulos --- configs/envoy-tap-config.yaml | 69 +++++++++++++++++++++++ docs/root/version_history/current.rst | 1 + source/server/config_validation/BUILD | 2 + source/server/config_validation/admin.cc | 2 +- source/server/config_validation/admin.h | 9 +++ source/server/config_validation/server.cc | 3 +- source/server/config_validation/server.h | 4 +- test/mocks/server/admin.cc | 3 + 8 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 configs/envoy-tap-config.yaml diff --git a/configs/envoy-tap-config.yaml b/configs/envoy-tap-config.yaml new file mode 100644 index 0000000000000..f8d4ef0836484 --- /dev/null +++ b/configs/envoy-tap-config.yaml @@ -0,0 +1,69 @@ +admin: + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 9901 +static_resources: + listeners: + - name: listener_0 + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + access_log: + - name: envoy.access_loggers.stdout + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + http_filters: + - name: envoy.filters.http.router + clusters: + - name: service_envoyproxy_io + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: service_envoyproxy_io + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.envoyproxy.io + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tap + typed_config: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tap.v3.Tap + common_config: + admin_config: + config_id: api-gateway + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + allow_renegotiation: true + common_tls_context: + tls_params: + tls_minimum_protocol_version: TLSv1_2 + sni: "service" diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 3f38173481c40..0ccbc03a6c205 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -19,6 +19,7 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* +* validation: fix an issue that causes TAP sockets to panic during config validation mode. * zipkin: fix timestamp serializaiton in annotations. A prior bug fix exposed an issue with timestamps being serialized as strings. Removed Config or Runtime diff --git a/source/server/config_validation/BUILD b/source/server/config_validation/BUILD index cabe630583071..bcf753dcf197c 100644 --- a/source/server/config_validation/BUILD +++ b/source/server/config_validation/BUILD @@ -9,8 +9,10 @@ envoy_cc_library( srcs = ["admin.cc"], hdrs = ["admin.h"], deps = [ + "//include/envoy/network:listen_socket_interface", "//include/envoy/server:admin_interface", "//source/common/common:assert_lib", + "//source/common/network:listen_socket_lib", "//source/server/admin:config_tracker_lib", ], ) diff --git a/source/server/config_validation/admin.cc b/source/server/config_validation/admin.cc index b0ecaf1014865..8a0874ee0e119 100644 --- a/source/server/config_validation/admin.cc +++ b/source/server/config_validation/admin.cc @@ -10,7 +10,7 @@ bool ValidationAdmin::addHandler(const std::string&, const std::string&, Handler bool ValidationAdmin::removeHandler(const std::string&) { return true; } -const Network::Socket& ValidationAdmin::socket() { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } +const Network::Socket& ValidationAdmin::socket() { return *socket_; } ConfigTracker& ValidationAdmin::getConfigTracker() { return config_tracker_; } diff --git a/source/server/config_validation/admin.h b/source/server/config_validation/admin.h index f10a5f8b6d889..68a309aee4bb9 100644 --- a/source/server/config_validation/admin.h +++ b/source/server/config_validation/admin.h @@ -1,8 +1,10 @@ #pragma once +#include "envoy/network/listen_socket.h" #include "envoy/server/admin.h" #include "common/common/assert.h" +#include "common/network/listen_socket_impl.h" #include "server/admin/config_tracker_impl.h" @@ -16,6 +18,12 @@ namespace Server { */ class ValidationAdmin : public Admin { public: + // We want to implement the socket interface without implementing the http listener function. + // This is useful for TAP because it wants to emit warnings when the address type is UDS + explicit ValidationAdmin(Network::Address::InstanceConstSharedPtr address) + : socket_(address ? std::make_shared(nullptr, std::move(address), + nullptr) + : nullptr) {} bool addHandler(const std::string&, const std::string&, HandlerCb, bool, bool) override; bool removeHandler(const std::string&) override; const Network::Socket& socket() override; @@ -32,6 +40,7 @@ class ValidationAdmin : public Admin { private: ConfigTrackerImpl config_tracker_; + Network::SocketSharedPtr socket_; }; } // namespace Server diff --git a/source/server/config_validation/server.cc b/source/server/config_validation/server.cc index 2dced0c68d5db..100647275b8c7 100644 --- a/source/server/config_validation/server.cc +++ b/source/server/config_validation/server.cc @@ -92,8 +92,9 @@ void ValidationInstance::initialize(const Options& options, overload_manager_ = std::make_unique( dispatcher(), stats(), threadLocal(), bootstrap.overload_manager(), messageValidationContext().staticValidationVisitor(), *api_, options_); - listener_manager_ = std::make_unique(*this, *this, *this, false); Configuration::InitialImpl initial_config(bootstrap, options, *this); + admin_ = std::make_unique(initial_config.admin().address()); + listener_manager_ = std::make_unique(*this, *this, *this, false); thread_local_.registerThread(*dispatcher_, true); runtime_singleton_ = std::make_unique( component_factory.createRuntime(*this, initial_config)); diff --git a/source/server/config_validation/server.h b/source/server/config_validation/server.h index dc137d8fc2711..1919a6643ad85 100644 --- a/source/server/config_validation/server.h +++ b/source/server/config_validation/server.h @@ -69,7 +69,7 @@ class ValidationInstance final : Logger::Loggable, Filesystem::Instance& file_system); // Server::Instance - Admin& admin() override { return admin_; } + Admin& admin() override { return *admin_; } Api::Api& api() override { return *api_; } Upstream::ClusterManager& clusterManager() override { return *config_.clusterManager(); } Ssl::ContextManager& sslContextManager() override { return *ssl_context_manager_; } @@ -192,7 +192,7 @@ class ValidationInstance final : Logger::Loggable, ThreadLocal::InstanceImpl thread_local_; Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; - Server::ValidationAdmin admin_; + std::unique_ptr admin_; Singleton::ManagerPtr singleton_manager_; std::unique_ptr runtime_singleton_; Random::RandomGeneratorImpl random_generator_; diff --git a/test/mocks/server/admin.cc b/test/mocks/server/admin.cc index 61d5e6dea1753..db35d25034e4f 100644 --- a/test/mocks/server/admin.cc +++ b/test/mocks/server/admin.cc @@ -3,6 +3,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::_; using testing::Return; using testing::ReturnRef; @@ -13,6 +14,8 @@ MockAdmin::MockAdmin() { ON_CALL(*this, getConfigTracker()).WillByDefault(ReturnRef(config_tracker_)); ON_CALL(*this, concurrency()).WillByDefault(Return(1)); ON_CALL(*this, socket()).WillByDefault(ReturnRef(socket_)); + ON_CALL(*this, addHandler(_, _, _, _, _)).WillByDefault(Return(true)); + ON_CALL(*this, removeHandler(_)).WillByDefault(Return(true)); } MockAdmin::~MockAdmin() = default; From 24abcda513cea6192a59f452cfb96d7e883d55d6 Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Tue, 20 Apr 2021 16:41:51 -0400 Subject: [PATCH 052/209] Minor cleanup. (#16082) Signed-off-by: Kevin Baichoo --- .../filters/http/grpc_json_transcoder/http_body_utils.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/extensions/filters/http/grpc_json_transcoder/http_body_utils.h b/source/extensions/filters/http/grpc_json_transcoder/http_body_utils.h index 629af665a0694..82ef031ca35be 100644 --- a/source/extensions/filters/http/grpc_json_transcoder/http_body_utils.h +++ b/source/extensions/filters/http/grpc_json_transcoder/http_body_utils.h @@ -14,11 +14,11 @@ namespace GrpcJsonTranscoder { class HttpBodyUtils { public: static bool parseMessageByFieldPath(Protobuf::io::ZeroCopyInputStream* stream, - const std::vector& field_path, + const std::vector& field_path, Protobuf::Message* message); static void appendHttpBodyEnvelope(Buffer::Instance& output, - const std::vector& request_body_field_path, + const std::vector& request_body_field_path, std::string content_type, uint64_t content_length); }; From 245d5fe0713eb22337fce411f5a01ffb7722635a Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Tue, 20 Apr 2021 17:49:32 -0400 Subject: [PATCH 053/209] Buffer: Track number of allocated bytes across buffers. (#15859) Creates mechanisms to account for allocated bytes in buffers. This will be followed up in a PR wiring this through the H2 codec. Related #15791 Signed-off-by: Kevin Baichoo --- include/envoy/buffer/buffer.h | 36 +++ source/common/buffer/buffer_impl.cc | 31 +- source/common/buffer/buffer_impl.h | 89 +++++- test/common/buffer/buffer_fuzz.cc | 5 + test/common/buffer/buffer_test.cc | 10 +- test/common/buffer/owned_impl_test.cc | 273 ++++++++++++++++++ .../postgres_proxy/postgres_decoder_test.cc | 1 + .../user_space/io_handle_impl_test.cc | 2 +- 8 files changed, 425 insertions(+), 22 deletions(-) diff --git a/include/envoy/buffer/buffer.h b/include/envoy/buffer/buffer.h index a4bdfc9459dad..d00130d48d901 100644 --- a/include/envoy/buffer/buffer.h +++ b/include/envoy/buffer/buffer.h @@ -85,6 +85,34 @@ class ReservationSlicesOwner { using ReservationSlicesOwnerPtr = std::unique_ptr; +/** + * An interface for accounting for the usage for byte tracking in buffers. + * + * Currently this is only used by L7 streams to track the amount of memory + * allocated in buffers by the stream. + */ +class BufferMemoryAccount { +public: + virtual ~BufferMemoryAccount() = default; + + /** + * Charges the account for using the specified amount of memory. + * + * @param amount the amount to debit. + */ + virtual void charge(uint64_t amount) PURE; + + /** + * Called to credit the account for an amount of memory + * is no longer used. + * + * @param amount the amount to credit. + */ + virtual void credit(uint64_t amount) PURE; +}; + +using BufferMemoryAccountSharedPtr = std::shared_ptr; + /** * A basic buffer abstraction. */ @@ -101,6 +129,14 @@ class Instance { */ virtual void addDrainTracker(std::function drain_tracker) PURE; + /** + * Binds the account to be charged for resources used by the buffer. This + * should only be called once. + * + * @param account a shared_ptr to the account to charge. + */ + virtual void bindAccount(BufferMemoryAccountSharedPtr account) PURE; + /** * Copy data into the buffer (deprecated, use absl::string_view variant * instead). diff --git a/source/common/buffer/buffer_impl.cc b/source/common/buffer/buffer_impl.cc index 4209ba05674b8..6d2241171a135 100644 --- a/source/common/buffer/buffer_impl.cc +++ b/source/common/buffer/buffer_impl.cc @@ -1,6 +1,7 @@ #include "common/buffer/buffer_impl.h" #include +#include #include #include "common/common/assert.h" @@ -25,7 +26,7 @@ void OwnedImpl::addImpl(const void* data, uint64_t size) { bool new_slice_needed = slices_.empty(); while (size != 0) { if (new_slice_needed) { - slices_.emplace_back(size); + slices_.emplace_back(Slice(size, account_)); } uint64_t copy_size = slices_.back().append(src, size); src += copy_size; @@ -40,6 +41,13 @@ void OwnedImpl::addDrainTracker(std::function drain_tracker) { slices_.back().addDrainTracker(std::move(drain_tracker)); } +void OwnedImpl::bindAccount(BufferMemoryAccountSharedPtr account) { + ASSERT(slices_.empty()); + // We don't yet have an account bound. + ASSERT(!account_); + account_ = std::move(account); +} + void OwnedImpl::add(const void* data, uint64_t size) { addImpl(data, size); } void OwnedImpl::addBufferFragment(BufferFragment& fragment) { @@ -61,7 +69,7 @@ void OwnedImpl::prepend(absl::string_view data) { bool new_slice_needed = slices_.empty(); while (size != 0) { if (new_slice_needed) { - slices_.emplace_front(size); + slices_.emplace_front(Slice(size, account_)); } uint64_t copy_size = slices_.front().prepend(data.data(), size); size -= copy_size; @@ -77,6 +85,7 @@ void OwnedImpl::prepend(Instance& data) { uint64_t slice_size = other.slices_.back().dataSize(); length_ += slice_size; slices_.emplace_front(std::move(other.slices_.back())); + slices_.front().maybeChargeAccount(account_); other.slices_.pop_back(); other.length_ -= slice_size; } @@ -189,7 +198,7 @@ SliceDataPtr OwnedImpl::extractMutableFrontSlice() { slices_.pop_front(); if (!slice.isMutable()) { // Create a mutable copy of the immutable slice data. - Slice mutable_slice{size}; + Slice mutable_slice{size, nullptr}; auto copy_size = mutable_slice.append(slice.data(), size); ASSERT(copy_size == size); // Drain trackers for the immutable slice will be called as part of the slice destructor. @@ -197,7 +206,7 @@ SliceDataPtr OwnedImpl::extractMutableFrontSlice() { } else { // Make sure drain trackers are called before ownership of the slice is transferred from // the buffer to the caller. - slice.callAndClearDrainTrackers(); + slice.callAndClearDrainTrackersAndCharges(); return std::make_unique(std::move(slice)); } } @@ -222,7 +231,7 @@ void* OwnedImpl::linearize(uint32_t size) { return nullptr; } if (slices_[0].dataSize() < size) { - Slice new_slice{size}; + Slice new_slice{size, account_}; Slice::Reservation reservation = new_slice.reserve(size); ASSERT(reservation.mem_ != nullptr); ASSERT(reservation.len_ == size); @@ -255,6 +264,7 @@ void OwnedImpl::coalesceOrAddSlice(Slice&& other_slice) { other_slice.transferDrainTrackersTo(slices_.back()); } else { // Take ownership of the slice. + other_slice.maybeChargeAccount(account_); slices_.emplace_back(std::move(other_slice)); length_ += slice_size; } @@ -343,7 +353,9 @@ Reservation OwnedImpl::reserveWithMaxLength(uint64_t max_length) { break; } - Slice slice(size, slices_owner->free_list_); + // We will tag the reservation slices on commit. This avoids unnecessary + // work in the case that the entire reservation isn't used. + Slice slice(size, nullptr, slices_owner->free_list_); const auto raw_slice = slice.reserve(size); reservation_slices.push_back(raw_slice); slices_owner->owned_slices_.emplace_back(std::move(slice)); @@ -378,7 +390,7 @@ ReservationSingleSlice OwnedImpl::reserveSingleSlice(uint64_t length, bool separ if (reservable_size >= length) { reservation_slice = slices_.back().reserve(length); } else { - Slice slice(length); + Slice slice(length, account_); reservation_slice = slice.reserve(length); slice_owner->owned_slice_ = std::move(slice); } @@ -405,6 +417,7 @@ void OwnedImpl::commit(uint64_t length, absl::Span slices, for (uint32_t i = 0; i < slices.size() && bytes_remaining > 0; i++) { Slice& owned_slice = owned_slices[i]; if (owned_slice.data() != nullptr) { + owned_slice.maybeChargeAccount(account_); slices_.emplace_back(std::move(owned_slice)); } slices[i].len_ = std::min(slices[i].len_, bytes_remaining); @@ -543,6 +556,8 @@ OwnedImpl::OwnedImpl(const Instance& data) : OwnedImpl() { add(data); } OwnedImpl::OwnedImpl(const void* data, uint64_t size) : OwnedImpl() { add(data, size); } +OwnedImpl::OwnedImpl(BufferMemoryAccountSharedPtr account) : account_(std::move(account)) {} + std::string OwnedImpl::toString() const { std::string output; output.reserve(length()); @@ -556,7 +571,7 @@ std::string OwnedImpl::toString() const { void OwnedImpl::postProcess() {} void OwnedImpl::appendSliceForTest(const void* data, uint64_t size) { - slices_.emplace_back(size); + slices_.emplace_back(Slice(size, account_)); slices_.back().append(data, size); length_ += size; } diff --git a/source/common/buffer/buffer_impl.h b/source/common/buffer/buffer_impl.h index 33b9bedb51621..a794b0e020514 100644 --- a/source/common/buffer/buffer_impl.h +++ b/source/common/buffer/buffer_impl.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "envoy/buffer/buffer.h" @@ -50,13 +51,22 @@ class Slice { Slice() = default; /** - * Create an empty mutable Slice that owns its storage. + * Create an empty mutable Slice that owns its storage, which it charges to + * the provided account, if any. * @param min_capacity number of bytes of space the slice should have. Actual capacity is rounded * up to the next multiple of 4kb. + * @param account the account to charge. + * @param freelist to search for the backing storage, if any. */ - Slice(uint64_t min_capacity, absl::optional free_list = absl::nullopt) + Slice(uint64_t min_capacity, BufferMemoryAccountSharedPtr account, + absl::optional free_list = absl::nullopt) : capacity_(sliceSize(min_capacity)), storage_(newStorage(capacity_, free_list)), - base_(storage_.get()), data_(0), reservable_(0) {} + base_(storage_.get()), data_(0), reservable_(0) { + if (account) { + account->charge(capacity_); + account_ = account; + } + } /** * Create an immutable Slice that refers to an external buffer fragment. @@ -72,6 +82,7 @@ class Slice { Slice(Slice&& rhs) noexcept { storage_ = std::move(rhs.storage_); drain_trackers_ = std::move(rhs.drain_trackers_); + account_ = std::move(rhs.account_); base_ = rhs.base_; data_ = rhs.data_; reservable_ = rhs.reservable_; @@ -85,11 +96,12 @@ class Slice { Slice& operator=(Slice&& rhs) noexcept { if (this != &rhs) { - callAndClearDrainTrackers(); + callAndClearDrainTrackersAndCharges(); freeStorage(std::move(storage_), capacity_); storage_ = std::move(rhs.storage_); drain_trackers_ = std::move(rhs.drain_trackers_); + account_ = std::move(rhs.account_); base_ = rhs.base_; data_ = rhs.data_; reservable_ = rhs.reservable_; @@ -105,12 +117,12 @@ class Slice { } ~Slice() { - callAndClearDrainTrackers(); + callAndClearDrainTrackersAndCharges(); freeStorage(std::move(storage_), capacity_); } void freeStorage(FreeListReference free_list) { - callAndClearDrainTrackers(); + callAndClearDrainTrackersAndCharges(); freeStorage(std::move(storage_), capacity_, free_list); } @@ -279,7 +291,7 @@ class Slice { } /** - * Move all drain trackers from the current slice to the destination slice. + * Move all drain trackers and charges from the current slice to the destination slice. */ void transferDrainTrackersTo(Slice& destination) { destination.drain_trackers_.splice(destination.drain_trackers_.end(), drain_trackers_); @@ -297,11 +309,30 @@ class Slice { * Call all drain trackers associated with the slice, then clear * the drain tracker list. */ - void callAndClearDrainTrackers() { + void callAndClearDrainTrackersAndCharges() { for (const auto& drain_tracker : drain_trackers_) { drain_tracker(); } drain_trackers_.clear(); + + if (account_) { + account_->credit(capacity_); + account_.reset(); + } + } + + /** + * Charges the provided account for the resources if these conditions hold: + * - we're not already charging for this slice + * - the given account is non-null + * - the slice owns backing memory + */ + void maybeChargeAccount(const BufferMemoryAccountSharedPtr& account) { + if (account_ != nullptr || storage_ == nullptr || account == nullptr) { + return; + } + account->charge(capacity_); + account_ = account; } static constexpr uint32_t default_slice_size_ = 16384; @@ -383,6 +414,10 @@ class Slice { /** Hooks to execute when the slice is destroyed. */ std::list> drain_trackers_; + + /** Account associated with this slice. This may be null. When + * coalescing with another slice, we do not transfer over their account. */ + BufferMemoryAccountSharedPtr account_; }; class OwnedImpl; @@ -633,9 +668,11 @@ class OwnedImpl : public LibEventInstance { OwnedImpl(absl::string_view data); OwnedImpl(const Instance& data); OwnedImpl(const void* data, uint64_t size); + OwnedImpl(BufferMemoryAccountSharedPtr account); // Buffer::Instance void addDrainTracker(std::function drain_tracker) override; + void bindAccount(BufferMemoryAccountSharedPtr account) override; void add(const void* data, uint64_t size) override; void addBufferFragment(BufferFragment& fragment) override; void add(absl::string_view data) override; @@ -730,6 +767,8 @@ class OwnedImpl : public LibEventInstance { /** Sum of the dataSize of all slices. */ OverflowDetectingUInt64 length_; + BufferMemoryAccountSharedPtr account_; + struct OwnedImplReservationSlicesOwner : public ReservationSlicesOwner { virtual absl::Span ownedSlices() PURE; }; @@ -798,5 +837,39 @@ class OwnedBufferFragmentImpl final : public BufferFragment, public InlineStorag using OwnedBufferFragmentImplPtr = std::unique_ptr; +/** + * A BufferMemoryAccountImpl tracks allocated bytes across associated buffers and + * slices that originate from those buffers, or are untagged and pass through an + * associated buffer. + */ +class BufferMemoryAccountImpl : public BufferMemoryAccount { +public: + BufferMemoryAccountImpl() = default; + ~BufferMemoryAccountImpl() override { ASSERT(buffer_memory_allocated_ == 0); } + + // Make not copyable + BufferMemoryAccountImpl(const BufferMemoryAccountImpl&) = delete; + BufferMemoryAccountImpl& operator=(const BufferMemoryAccountImpl&) = delete; + + // Make not movable. + BufferMemoryAccountImpl(BufferMemoryAccountImpl&&) = delete; + BufferMemoryAccountImpl& operator=(BufferMemoryAccountImpl&&) = delete; + + uint64_t balance() const { return buffer_memory_allocated_; } + void charge(uint64_t amount) override { + // Check overflow + ASSERT(std::numeric_limits::max() - buffer_memory_allocated_ >= amount); + buffer_memory_allocated_ += amount; + } + + void credit(uint64_t amount) override { + ASSERT(buffer_memory_allocated_ >= amount); + buffer_memory_allocated_ -= amount; + } + +private: + uint64_t buffer_memory_allocated_ = 0; +}; + } // namespace Buffer } // namespace Envoy diff --git a/test/common/buffer/buffer_fuzz.cc b/test/common/buffer/buffer_fuzz.cc index ffe24dcbed0d5..493f31b8b31db 100644 --- a/test/common/buffer/buffer_fuzz.cc +++ b/test/common/buffer/buffer_fuzz.cc @@ -74,6 +74,11 @@ class StringBuffer : public Buffer::Instance { drain_tracker(); } + void bindAccount(Buffer::BufferMemoryAccountSharedPtr) override { + // Not implemented. + ASSERT(false); + } + void add(const void* data, uint64_t size) override { FUZZ_ASSERT(start_ + size_ + size <= data_.size()); ::memcpy(mutableEnd(), data, size); diff --git a/test/common/buffer/buffer_test.cc b/test/common/buffer/buffer_test.cc index 28027fcab5b13..d301d634ce6fa 100644 --- a/test/common/buffer/buffer_test.cc +++ b/test/common/buffer/buffer_test.cc @@ -25,7 +25,7 @@ class SliceTest : public testing::TestWithParam { auto slice = std::make_unique(*fragment); return slice; } else { - auto slice = std::make_unique(data.size()); + auto slice = std::make_unique(data.size(), nullptr); slice->append(data.data(), data.size()); return slice; } @@ -160,7 +160,7 @@ TEST_F(OwnedSliceTest, Create) { static constexpr std::pair Sizes[] = { {0, 0}, {1, 4096}, {64, 4096}, {4095, 4096}, {4096, 4096}, {4097, 8192}, {65535, 65536}}; for (const auto& [size, expected_size] : Sizes) { - Slice slice{size}; + Slice slice{size, nullptr}; EXPECT_NE(nullptr, slice.data()); EXPECT_EQ(0, slice.dataSize()); EXPECT_LE(size, slice.reservableSize()); @@ -169,7 +169,7 @@ TEST_F(OwnedSliceTest, Create) { } TEST_F(OwnedSliceTest, ReserveCommit) { - Slice slice{100}; + Slice slice{100, nullptr}; const uint64_t initial_capacity = slice.reservableSize(); EXPECT_LE(100, initial_capacity); @@ -231,7 +231,7 @@ TEST_F(OwnedSliceTest, ReserveCommit) { // Try to commit a reservation from the wrong slice, and verify that the slice rejects it. Slice::Reservation reservation = slice.reserve(10); expectReservationSuccess(reservation, slice, 10); - Slice other_slice{100}; + Slice other_slice{100, nullptr}; Slice::Reservation other_reservation = other_slice.reserve(10); expectReservationSuccess(other_reservation, other_slice, 10); EXPECT_FALSE(slice.commit(other_reservation)); @@ -265,7 +265,7 @@ TEST_F(OwnedSliceTest, ReserveCommit) { TEST_F(OwnedSliceTest, Drain) { // Create a slice and commit all the available space. - Slice slice{100}; + Slice slice{100, nullptr}; Slice::Reservation reservation = slice.reserve(slice.reservableSize()); bool committed = slice.commit(reservation); EXPECT_TRUE(committed); diff --git a/test/common/buffer/owned_impl_test.cc b/test/common/buffer/owned_impl_test.cc index f8746a11e34ba..667d37460e306 100644 --- a/test/common/buffer/owned_impl_test.cc +++ b/test/common/buffer/owned_impl_test.cc @@ -1,3 +1,5 @@ +#include + #include "envoy/api/io_error.h" #include "common/buffer/buffer_impl.h" @@ -1263,6 +1265,277 @@ TEST_F(OwnedImplTest, FrontSlice) { EXPECT_EQ(1, buffer.frontSlice().len_); } +TEST(BufferMemoryAccountTest, ManagesAccountBalance) { + auto account = std::make_shared(); + Buffer::OwnedImpl buffer(account); + ASSERT_EQ(account->balance(), 0); + + // Check the balance increases as expected. + { + // New slice created + buffer.add("Hello"); + EXPECT_EQ(account->balance(), 4096); + + // Should just be added to existing slice. + buffer.add(" World!"); + EXPECT_EQ(account->balance(), 4096); + + // Trigger new slice creation with add. + const std::string long_string(4096, 'a'); + buffer.add(long_string); + EXPECT_EQ(account->balance(), 8192); + + // AppendForTest also adds new slice. + buffer.appendSliceForTest("Extra Slice"); + EXPECT_EQ(account->balance(), 12288); + } + + // Check the balance drains as slices are consumed. + { + // Shouldn't trigger slice free yet + buffer.drain(4095); + EXPECT_EQ(account->balance(), 12288); + + // Trigger slice reclaim. + buffer.drain(1); + EXPECT_EQ(account->balance(), 8192); + + // Reclaim next slice + buffer.drain(std::string("Hello World!").length()); + EXPECT_EQ(account->balance(), 4096); + + // Reclaim remaining + buffer.drain(std::string("Extra Slice").length()); + EXPECT_EQ(account->balance(), 0); + } +} + +TEST(BufferMemoryAccountTest, BufferAccountsForUnownedSliceMovedInto) { + auto account = std::make_shared(); + Buffer::OwnedImpl accounted_buffer(account); + + Buffer::OwnedImpl unowned_buffer; + unowned_buffer.add("Unaccounted Slice"); + ASSERT_EQ(account->balance(), 0); + + // Transfer over buffer + accounted_buffer.move(unowned_buffer); + EXPECT_EQ(account->balance(), 4096); + + accounted_buffer.drain(accounted_buffer.length()); + EXPECT_EQ(account->balance(), 0); +} + +TEST(BufferMemoryAccountTest, BufferFragmentsShouldNotHaveAnAssociatedAccount) { + auto buffer_one_account = std::make_shared(); + Buffer::OwnedImpl buffer_one(buffer_one_account); + ASSERT_EQ(buffer_one_account->balance(), 0); + + auto buffer_two_account = std::make_shared(); + Buffer::OwnedImpl buffer_two(buffer_two_account); + ASSERT_EQ(buffer_two_account->balance(), 0); + + const char data[] = "hello world"; + BufferFragmentImpl frag(data, 11, nullptr); + buffer_one.addBufferFragment(frag); + EXPECT_EQ(buffer_one_account->balance(), 0); + EXPECT_EQ(buffer_one.length(), 11); + + // Transfer over buffer + buffer_two.move(buffer_one); + EXPECT_EQ(buffer_two_account->balance(), 0); + EXPECT_EQ(buffer_two.length(), 11); + + buffer_two.drain(buffer_two.length()); + EXPECT_EQ(buffer_two_account->balance(), 0); + EXPECT_EQ(buffer_two.length(), 0); +} + +TEST(BufferMemoryAccountTest, SliceRemainsAttachToOriginalAccountWhenMoved) { + auto buffer_one_account = std::make_shared(); + Buffer::OwnedImpl buffer_one(buffer_one_account); + ASSERT_EQ(buffer_one_account->balance(), 0); + + auto buffer_two_account = std::make_shared(); + Buffer::OwnedImpl buffer_two(buffer_two_account); + ASSERT_EQ(buffer_two_account->balance(), 0); + + buffer_one.add("Charged to Account One"); + EXPECT_EQ(buffer_one_account->balance(), 4096); + EXPECT_EQ(buffer_two_account->balance(), 0); + + // Transfer over buffer, still tied to account one. + buffer_two.move(buffer_one); + EXPECT_EQ(buffer_one_account->balance(), 4096); + EXPECT_EQ(buffer_two_account->balance(), 0); + + buffer_two.drain(buffer_two.length()); + EXPECT_EQ(buffer_one_account->balance(), 0); + EXPECT_EQ(buffer_two_account->balance(), 0); +} + +TEST(BufferMemoryAccountTest, + SliceRemainsAttachToOriginalAccountWhenMovedUnlessCoalescedIntoExistingSlice) { + auto buffer_one_account = std::make_shared(); + Buffer::OwnedImpl buffer_one(buffer_one_account); + ASSERT_EQ(buffer_one_account->balance(), 0); + + auto buffer_two_account = std::make_shared(); + Buffer::OwnedImpl buffer_two(buffer_two_account); + ASSERT_EQ(buffer_two_account->balance(), 0); + + buffer_one.add("Will Coalesce"); + buffer_two.add("To be Coalesce into:"); + EXPECT_EQ(buffer_one_account->balance(), 4096); + EXPECT_EQ(buffer_two_account->balance(), 4096); + + // Transfer over buffer, slices coalesce, crediting account one. + buffer_two.move(buffer_one); + EXPECT_EQ(buffer_one_account->balance(), 0); + EXPECT_EQ(buffer_two_account->balance(), 4096); + + buffer_two.drain(std::string("To be Coalesce into:Will Coalesce").length()); + EXPECT_EQ(buffer_one_account->balance(), 0); + EXPECT_EQ(buffer_two_account->balance(), 0); +} + +TEST(BufferMemoryAccountTest, SliceCanRemainAttachedToOriginalAccountWhenMovedAndCoalescedInto) { + auto buffer_one_account = std::make_shared(); + Buffer::OwnedImpl buffer_one(buffer_one_account); + ASSERT_EQ(buffer_one_account->balance(), 0); + + auto buffer_two_account = std::make_shared(); + Buffer::OwnedImpl buffer_two(buffer_two_account); + ASSERT_EQ(buffer_two_account->balance(), 0); + + auto buffer_three_account = std::make_shared(); + Buffer::OwnedImpl buffer_three(buffer_three_account); + ASSERT_EQ(buffer_three_account->balance(), 0); + + buffer_one.add("Will Coalesce"); + buffer_two.add("To be Coalesce into:"); + EXPECT_EQ(buffer_one_account->balance(), 4096); + EXPECT_EQ(buffer_two_account->balance(), 4096); + + // Transfer buffers, leading to slice coalescing in third buffer. + buffer_three.move(buffer_two); + buffer_three.move(buffer_one); + EXPECT_EQ(buffer_one_account->balance(), 0); + EXPECT_EQ(buffer_two_account->balance(), 4096); + EXPECT_EQ(buffer_three_account->balance(), 0); + + buffer_three.drain(std::string("To be Coalesce into:Will Coalesce").length()); + EXPECT_EQ(buffer_two_account->balance(), 0); +} + +TEST(BufferMemoryAccountTest, LinearizedBufferShouldChargeItsAssociatedAccount) { + auto buffer_one_account = std::make_shared(); + Buffer::OwnedImpl buffer_one(buffer_one_account); + ASSERT_EQ(buffer_one_account->balance(), 0); + + auto buffer_two_account = std::make_shared(); + Buffer::OwnedImpl buffer_two(buffer_two_account); + ASSERT_EQ(buffer_two_account->balance(), 0); + + auto buffer_three_account = std::make_shared(); + Buffer::OwnedImpl buffer_three(buffer_three_account); + ASSERT_EQ(buffer_three_account->balance(), 0); + + const std::string long_string(4096, 'a'); + buffer_one.add(long_string); + buffer_two.add(long_string); + EXPECT_EQ(buffer_one_account->balance(), 4096); + EXPECT_EQ(buffer_two_account->balance(), 4096); + + // Move into the third buffer. + buffer_three.move(buffer_one); + buffer_three.move(buffer_two); + EXPECT_EQ(buffer_one_account->balance(), 4096); + EXPECT_EQ(buffer_two_account->balance(), 4096); + EXPECT_EQ(buffer_three_account->balance(), 0); + + // Linearize, which does a copy out of the slices. + buffer_three.linearize(8192); + EXPECT_EQ(buffer_one_account->balance(), 0); + EXPECT_EQ(buffer_two_account->balance(), 0); + EXPECT_EQ(buffer_three_account->balance(), 8192); +} + +TEST(BufferMemoryAccountTest, ManagesAccountBalanceWhenPrepending) { + auto prepend_to_account = std::make_shared(); + Buffer::OwnedImpl buffer_to_prepend_to(prepend_to_account); + ASSERT_EQ(prepend_to_account->balance(), 0); + + auto prepend_account = std::make_shared(); + Buffer::OwnedImpl buffer_to_prepend(prepend_account); + ASSERT_EQ(prepend_account->balance(), 0); + + Buffer::OwnedImpl unowned_buffer_to_prepend; + + unowned_buffer_to_prepend.add("World"); + buffer_to_prepend.add("Goodbye World"); + EXPECT_EQ(prepend_account->balance(), 4096); + + // Prepend the buffers. + buffer_to_prepend_to.prepend(buffer_to_prepend); + EXPECT_EQ(prepend_account->balance(), 4096); + EXPECT_EQ(prepend_to_account->balance(), 0); + + buffer_to_prepend_to.prepend(unowned_buffer_to_prepend); + EXPECT_EQ(prepend_to_account->balance(), 4096); + + // Prepend a string view. + buffer_to_prepend_to.prepend("Hello "); + EXPECT_EQ(prepend_to_account->balance(), 8192); +} + +TEST(BufferMemoryAccountTest, ExtractingSliceWithExistingStorageCreditsAccountOnce) { + auto buffer_account = std::make_shared(); + Buffer::OwnedImpl buffer(buffer_account); + ASSERT_EQ(buffer_account->balance(), 0); + + buffer.appendSliceForTest("Slice 1"); + buffer.appendSliceForTest("Slice 2"); + EXPECT_EQ(buffer_account->balance(), 8192); + + // Account should only be credited when slice is extracted. + // Not on slice dtor. + { + auto slice = buffer.extractMutableFrontSlice(); + EXPECT_EQ(buffer_account->balance(), 4096); + } + + EXPECT_EQ(buffer_account->balance(), 4096); +} + +TEST(BufferMemoryAccountTest, NewReservationSlicesOnlyChargedAfterCommit) { + auto buffer_account = std::make_shared(); + Buffer::OwnedImpl buffer(buffer_account); + ASSERT_EQ(buffer_account->balance(), 0); + + auto reservation = buffer.reserveForRead(); + EXPECT_EQ(buffer_account->balance(), 0); + + // We should only be charged for the slices committed. + reservation.commit(16384); + EXPECT_EQ(buffer_account->balance(), 16384); +} + +TEST(BufferMemoryAccountTest, ReservationShouldNotChargeForExistingSlice) { + auto buffer_account = std::make_shared(); + Buffer::OwnedImpl buffer(buffer_account); + ASSERT_EQ(buffer_account->balance(), 0); + + buffer.add("Many bytes remaining in this slice to use for reservation."); + EXPECT_EQ(buffer_account->balance(), 4096); + + // The account shouldn't be charged again at commit since the commit + // uses memory from the slice already charged for. + auto reservation = buffer.reserveForRead(); + reservation.commit(2000); + EXPECT_EQ(buffer_account->balance(), 4096); +} + } // namespace } // namespace Buffer } // namespace Envoy diff --git a/test/extensions/filters/network/postgres_proxy/postgres_decoder_test.cc b/test/extensions/filters/network/postgres_proxy/postgres_decoder_test.cc index ebf131a2acab1..9170a69e84844 100644 --- a/test/extensions/filters/network/postgres_proxy/postgres_decoder_test.cc +++ b/test/extensions/filters/network/postgres_proxy/postgres_decoder_test.cc @@ -537,6 +537,7 @@ TEST_F(PostgresProxyDecoderTest, TerminateSSL) { class FakeBuffer : public Buffer::Instance { public: MOCK_METHOD(void, addDrainTracker, (std::function), (override)); + MOCK_METHOD(void, bindAccount, (Buffer::BufferMemoryAccountSharedPtr), (override)); MOCK_METHOD(void, add, (const void*, uint64_t), (override)); MOCK_METHOD(void, addBufferFragment, (Buffer::BufferFragment&), (override)); MOCK_METHOD(void, add, (absl::string_view), (override)); diff --git a/test/extensions/io_socket/user_space/io_handle_impl_test.cc b/test/extensions/io_socket/user_space/io_handle_impl_test.cc index 117dc44d8fc32..790c7a0734ed4 100644 --- a/test/extensions/io_socket/user_space/io_handle_impl_test.cc +++ b/test/extensions/io_socket/user_space/io_handle_impl_test.cc @@ -28,7 +28,7 @@ MATCHER(IsInvalidAddress, "") { MATCHER(IsNotSupportedResult, "") { return arg.errno_ == SOCKET_ERROR_NOT_SUP; } ABSL_MUST_USE_RESULT std::pair allocateOneSlice(uint64_t size) { - Buffer::Slice mutable_slice(size); + Buffer::Slice mutable_slice(size, nullptr); auto slice = mutable_slice.reserve(size); EXPECT_NE(nullptr, slice.mem_); EXPECT_EQ(size, slice.len_); From 795a4f53f68e94a60e0bb6f1b4d64955cdf320b3 Mon Sep 17 00:00:00 2001 From: Nitin Goyal Date: Tue, 20 Apr 2021 16:27:12 -0700 Subject: [PATCH 054/209] http: refactor out stream rate limiter to common (#14828) Signed-off-by: Nitin --- source/extensions/filters/http/common/BUILD | 15 ++ .../http/common/stream_rate_limiter.cc | 117 ++++++++++++++ .../filters/http/common/stream_rate_limiter.h | 97 +++++++++++ source/extensions/filters/http/fault/BUILD | 1 + .../filters/http/fault/fault_filter.cc | 104 +----------- .../filters/http/fault/fault_filter.h | 64 +------- test/extensions/filters/http/common/BUILD | 21 +++ .../http/common/stream_rate_limiter_test.cc | 150 ++++++++++++++++++ .../fault/fault_filter_integration_test.cc | 24 +-- .../filters/http/fault/fault_filter_test.cc | 24 +-- 10 files changed, 435 insertions(+), 182 deletions(-) create mode 100644 source/extensions/filters/http/common/stream_rate_limiter.cc create mode 100644 source/extensions/filters/http/common/stream_rate_limiter.h create mode 100644 test/extensions/filters/http/common/stream_rate_limiter_test.cc diff --git a/source/extensions/filters/http/common/BUILD b/source/extensions/filters/http/common/BUILD index a0c427cf97838..d9a7558fb38ed 100644 --- a/source/extensions/filters/http/common/BUILD +++ b/source/extensions/filters/http/common/BUILD @@ -56,3 +56,18 @@ envoy_cc_library( "//source/extensions/filters/http:well_known_names", ], ) + +envoy_cc_library( + name = "stream_rate_limiter_lib", + srcs = ["stream_rate_limiter.cc"], + hdrs = ["stream_rate_limiter.h"], + deps = [ + "//include/envoy/common:token_bucket_interface", + "//include/envoy/event:dispatcher_interface", + "//include/envoy/event:timer_interface", + "//source/common/buffer:watermark_buffer_lib", + "//source/common/common:assert_lib", + "//source/common/common:empty_string", + "//source/common/common:token_bucket_impl_lib", + ], +) diff --git a/source/extensions/filters/http/common/stream_rate_limiter.cc b/source/extensions/filters/http/common/stream_rate_limiter.cc new file mode 100644 index 0000000000000..c5d39a28769fd --- /dev/null +++ b/source/extensions/filters/http/common/stream_rate_limiter.cc @@ -0,0 +1,117 @@ +#include "extensions/filters/http/common/stream_rate_limiter.h" + +#include + +#include "envoy/event/dispatcher.h" +#include "envoy/event/timer.h" + +#include "common/common/assert.h" +#include "common/common/token_bucket_impl.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Common { + +StreamRateLimiter::StreamRateLimiter( + uint64_t max_kbps, uint64_t max_buffered_data, std::function pause_data_cb, + std::function resume_data_cb, + std::function write_data_cb, std::function continue_cb, + std::function write_stats_cb, TimeSource& time_source, + Event::Dispatcher& dispatcher, const ScopeTrackedObject& scope, + std::shared_ptr token_bucket, std::chrono::milliseconds fill_interval) + : fill_interval_(std::move(fill_interval)), write_data_cb_(write_data_cb), + continue_cb_(continue_cb), write_stats_cb_(std::move(write_stats_cb)), scope_(scope), + token_bucket_(std::move(token_bucket)), + token_timer_(dispatcher.createTimer([this] { onTokenTimer(); })), + buffer_(resume_data_cb, pause_data_cb, + []() -> void { /* TODO(adisuissa): Handle overflow watermark */ }) { + ASSERT(max_buffered_data > 0); + ASSERT(fill_interval_.count() > 0); + ASSERT(fill_interval_.count() <= 1000); + auto max_tokens = kiloBytesToBytes(max_kbps); + if (!token_bucket_) { + // Initialize a new token bucket if caller didn't provide one. + // The token bucket is configured with a max token count of the number of bytes per second, + // and refills at the same rate, so that we have a per second limit which refills gradually + // in one fill_interval_ at a time. + token_bucket_ = std::make_shared(max_tokens, time_source, max_tokens); + } + // Reset the bucket to contain only one fill_interval worth of tokens. + // If the token bucket is shared, only first reset call will work. + auto initial_tokens = max_tokens * fill_interval_.count() / 1000; + token_bucket_->maybeReset(initial_tokens); + ENVOY_LOG(debug, + "StreamRateLimiter : fill_interval={}ms " + "initial_tokens={} max_tokens={}", + fill_interval_.count(), initial_tokens, max_tokens); + buffer_.setWatermarks(max_buffered_data); +} + +void StreamRateLimiter::onTokenTimer() { + Buffer::OwnedImpl data_to_write; + + // Try to obtain that as many tokens as bytes in the buffer, and then + // figure out how many bytes to write given the number of tokens we actually got. + const uint64_t tokens_obtained = token_bucket_->consume(buffer_.length(), true); + const uint64_t bytes_to_write = std::min(tokens_obtained, buffer_.length()); + ENVOY_LOG(debug, + "StreamRateLimiter : tokens_needed={} " + "tokens_obtained={} to_write={}", + buffer_.length(), tokens_obtained, bytes_to_write); + + // Move the data to write into the output buffer with as little copying as possible. + // NOTE: This might be moving zero bytes, but that should work fine. + data_to_write.move(buffer_, bytes_to_write); + write_stats_cb_(bytes_to_write); + + // If the buffer still contains data in it, we couldn't get enough tokens, so schedule the next + // token available time. + // In case of a shared token bucket, this algorithm will prioritize one stream at a time. + // TODO(nitgoy): add round-robin and other policies for rationing bandwidth. + if (buffer_.length() > 0) { + ENVOY_LOG(debug, + "StreamRateLimiter : scheduling wakeup for {}ms, " + "buffered={}", + fill_interval_.count(), buffer_.length()); + token_timer_->enableTimer(fill_interval_, &scope_); + } + + // Write the data out, indicating end stream if we saw end stream, there is no further data to + // send, and there are no trailers. + write_data_cb_(data_to_write, saw_end_stream_ && buffer_.length() == 0 && !saw_trailers_); + + // If there is no more data to send and we saw trailers, we need to continue iteration to release + // the trailers to further filters. + if (buffer_.length() == 0 && saw_trailers_) { + continue_cb_(); + } +} + +void StreamRateLimiter::writeData(Buffer::Instance& incoming_buffer, bool end_stream) { + auto len = incoming_buffer.length(); + buffer_.move(incoming_buffer); + saw_end_stream_ = end_stream; + ENVOY_LOG(debug, + "StreamRateLimiter : got new {} bytes of data. token " + "timer {} scheduled.", + len, !token_timer_->enabled() ? "now" : "already"); + if (!token_timer_->enabled()) { + // TODO(mattklein123): In an optimal world we would be able to continue iteration with the data + // we want in the buffer, but have a way to clear end_stream in case we can't send it all. + // The filter API does not currently support that and it will not be a trivial change to add. + // Instead we cheat here by scheduling the token timer to run immediately after the stack is + // unwound, at which point we can directly called encode/decodeData. + token_timer_->enableTimer(std::chrono::milliseconds(0), &scope_); + } +} + +bool StreamRateLimiter::onTrailers() { + saw_end_stream_ = true; + saw_trailers_ = true; + return buffer_.length() > 0; +} +} // namespace Common +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/common/stream_rate_limiter.h b/source/extensions/filters/http/common/stream_rate_limiter.h new file mode 100644 index 0000000000000..406791497155b --- /dev/null +++ b/source/extensions/filters/http/common/stream_rate_limiter.h @@ -0,0 +1,97 @@ +#pragma once +#include +#include +#include +#include + +#include "envoy/common/token_bucket.h" +#include "envoy/event/timer.h" +#include "envoy/runtime/runtime.h" + +#include "common/buffer/watermark_buffer.h" + +namespace Envoy { + +class ScopeTrackedObject; + +namespace Event { +class Timer; +} // namespace Event + +namespace Extensions { +namespace HttpFilters { +namespace Common { + +/** + * A generic HTTP stream rate limiter. It limits the rate of transfer for a stream to the specified + * max rate. It calls appropriate callbacks when the buffered data crosses certain high and low + * watermarks based on the max buffer size. It's used by the fault filter and bandwidth filter as + * the core logic for their stream limit functionality. + */ +class StreamRateLimiter : Logger::Loggable { +public: + static constexpr std::chrono::milliseconds DefaultFillInterval = std::chrono::milliseconds(50); + + static constexpr uint64_t kiloBytesToBytes(const uint64_t val) { return val * 1024; } + + /** + * @param max_kbps maximum rate in KiB/s. + * @param max_buffered_data maximum data to buffer before invoking the pause callback. + * @param pause_data_cb callback invoked when the limiter has buffered too much data. + * @param resume_data_cb callback invoked when the limiter has gone under the buffer limit. + * @param write_data_cb callback invoked to write data to the stream. + * @param continue_cb callback invoked to continue the stream. This is only used to continue + * trailers that have been paused during body flush. + * @param time_source the time source to run the token bucket with. + * @param dispatcher the stream's dispatcher to use for creating timers. + * @param scope the stream's scope + */ + StreamRateLimiter(uint64_t max_kbps, uint64_t max_buffered_data, + std::function pause_data_cb, std::function resume_data_cb, + std::function write_data_cb, + std::function continue_cb, std::function write_stats_cb, + TimeSource& time_source, Event::Dispatcher& dispatcher, + const ScopeTrackedObject& scope, + std::shared_ptr token_bucket = nullptr, + std::chrono::milliseconds fill_interval = DefaultFillInterval); + + /** + * Called by the stream to write data. All data writes happen asynchronously, the stream should + * be stopped after this call (all data will be drained from incoming_buffer). + */ + void writeData(Buffer::Instance& incoming_buffer, bool end_stream); + + /** + * Called if the stream receives trailers. + * Returns true if the read buffer is not completely drained yet. + */ + bool onTrailers(); + + /** + * Like the owning filter, we must handle inline destruction, so we have a destroy() method which + * kills any callbacks. + */ + void destroy() { token_timer_.reset(); } + bool destroyed() { return token_timer_ == nullptr; } + +private: + friend class StreamRateLimiterTest; + using TimerPtr = std::unique_ptr; + + void onTokenTimer(); + + const std::chrono::milliseconds fill_interval_; + const std::function write_data_cb_; + const std::function continue_cb_; + const std::function write_stats_cb_; + const ScopeTrackedObject& scope_; + std::shared_ptr token_bucket_; + Event::TimerPtr token_timer_; + bool saw_end_stream_{}; + bool saw_trailers_{}; + Buffer::WatermarkBuffer buffer_; +}; +} // namespace Common +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/fault/BUILD b/source/extensions/filters/http/fault/BUILD index 8778d38568685..db2a5a61ed97a 100644 --- a/source/extensions/filters/http/fault/BUILD +++ b/source/extensions/filters/http/fault/BUILD @@ -36,6 +36,7 @@ envoy_cc_library( "//source/common/stats:utility_lib", "//source/extensions/filters/common/fault:fault_config_lib", "//source/extensions/filters/http:well_known_names", + "//source/extensions/filters/http/common:stream_rate_limiter_lib", "@envoy_api//envoy/extensions/filters/http/fault/v3:pkg_cc_proto", "@envoy_api//envoy/type/v3:pkg_cc_proto", ], diff --git a/source/extensions/filters/http/fault/fault_filter.cc b/source/extensions/filters/http/fault/fault_filter.cc index f1b81fcdaddbf..ca1ff7f42a9e1 100644 --- a/source/extensions/filters/http/fault/fault_filter.cc +++ b/source/extensions/filters/http/fault/fault_filter.cc @@ -208,15 +208,18 @@ void FaultFilter::maybeSetupResponseRateLimit(const Http::RequestHeaderMap& requ config_->stats().response_rl_injected_.inc(); - response_limiter_ = std::make_unique( + response_limiter_ = std::make_unique( rate_kbps.value(), encoder_callbacks_->encoderBufferLimit(), [this] { encoder_callbacks_->onEncoderFilterAboveWriteBufferHighWatermark(); }, [this] { encoder_callbacks_->onEncoderFilterBelowWriteBufferLowWatermark(); }, [this](Buffer::Instance& data, bool end_stream) { encoder_callbacks_->injectEncodedDataToFilterChain(data, end_stream); }, - [this] { encoder_callbacks_->continueEncoding(); }, config_->timeSource(), - decoder_callbacks_->dispatcher(), decoder_callbacks_->scope()); + [this] { encoder_callbacks_->continueEncoding(); }, + [](uint64_t) { + // write stats callback. + }, + config_->timeSource(), decoder_callbacks_->dispatcher(), decoder_callbacks_->scope()); } bool FaultFilter::faultOverflow() { @@ -516,104 +519,13 @@ Http::FilterDataStatus FaultFilter::encodeData(Buffer::Instance& data, bool end_ Http::FilterTrailersStatus FaultFilter::encodeTrailers(Http::ResponseTrailerMap&) { if (response_limiter_ != nullptr) { - return response_limiter_->onTrailers(); + return response_limiter_->onTrailers() ? Http::FilterTrailersStatus::StopIteration + : Http::FilterTrailersStatus::Continue; } return Http::FilterTrailersStatus::Continue; } -StreamRateLimiter::StreamRateLimiter(uint64_t max_kbps, uint64_t max_buffered_data, - std::function pause_data_cb, - std::function resume_data_cb, - std::function write_data_cb, - std::function continue_cb, TimeSource& time_source, - Event::Dispatcher& dispatcher, const ScopeTrackedObject& scope) - : // bytes_per_time_slice is KiB converted to bytes divided by the number of ticks per second. - bytes_per_time_slice_((max_kbps * 1024) / SecondDivisor), write_data_cb_(write_data_cb), - continue_cb_(continue_cb), scope_(scope), - // The token bucket is configured with a max token count of the number of ticks per second, - // and refills at the same rate, so that we have a per second limit which refills gradually in - // ~63ms intervals. - token_bucket_(SecondDivisor, time_source, SecondDivisor), - token_timer_(dispatcher.createTimer([this] { onTokenTimer(); })), - buffer_(resume_data_cb, pause_data_cb, - []() -> void { /* TODO(adisuissa): Handle overflow watermark */ }) { - ASSERT(bytes_per_time_slice_ > 0); - ASSERT(max_buffered_data > 0); - buffer_.setWatermarks(max_buffered_data); -} - -void StreamRateLimiter::onTokenTimer() { - ENVOY_LOG(trace, "limiter: timer wakeup: buffered={}", buffer_.length()); - Buffer::OwnedImpl data_to_write; - - if (!saw_data_) { - // The first time we see any data on this stream (via writeData()), reset the number of tokens - // to 1. This will ensure that we start pacing the data at the desired rate (and don't send a - // full 1s of data right away which might not introduce enough delay for a stream that doesn't - // have enough data to span more than 1s of rate allowance). Once we reset, we will subsequently - // allow for bursting within the second to account for our data provider being bursty. - token_bucket_.maybeReset(1); - saw_data_ = true; - } - - // Compute the number of tokens needed (rounded up), try to obtain that many tickets, and then - // figure out how many bytes to write given the number of tokens we actually got. - const uint64_t tokens_needed = - (buffer_.length() + bytes_per_time_slice_ - 1) / bytes_per_time_slice_; - const uint64_t tokens_obtained = token_bucket_.consume(tokens_needed, true); - const uint64_t bytes_to_write = - std::min(tokens_obtained * bytes_per_time_slice_, buffer_.length()); - ENVOY_LOG(trace, "limiter: tokens_needed={} tokens_obtained={} to_write={}", tokens_needed, - tokens_obtained, bytes_to_write); - - // Move the data to write into the output buffer with as little copying as possible. - // NOTE: This might be moving zero bytes, but that should work fine. - data_to_write.move(buffer_, bytes_to_write); - - // If the buffer still contains data in it, we couldn't get enough tokens, so schedule the next - // token available time. - if (buffer_.length() > 0) { - const std::chrono::milliseconds ms = token_bucket_.nextTokenAvailable(); - if (ms.count() > 0) { - ENVOY_LOG(trace, "limiter: scheduling wakeup for {}ms", ms.count()); - token_timer_->enableTimer(ms, &scope_); - } - } - - // Write the data out, indicating end stream if we saw end stream, there is no further data to - // send, and there are no trailers. - write_data_cb_(data_to_write, saw_end_stream_ && buffer_.length() == 0 && !saw_trailers_); - - // If there is no more data to send and we saw trailers, we need to continue iteration to release - // the trailers to further filters. - if (buffer_.length() == 0 && saw_trailers_) { - continue_cb_(); - } -} - -void StreamRateLimiter::writeData(Buffer::Instance& incoming_buffer, bool end_stream) { - ENVOY_LOG(trace, "limiter: incoming data length={} buffered={}", incoming_buffer.length(), - buffer_.length()); - buffer_.move(incoming_buffer); - saw_end_stream_ = end_stream; - if (!token_timer_->enabled()) { - // TODO(mattklein123): In an optimal world we would be able to continue iteration with the data - // we want in the buffer, but have a way to clear end_stream in case we can't send it all. - // The filter API does not currently support that and it will not be a trivial change to add. - // Instead we cheat here by scheduling the token timer to run immediately after the stack is - // unwound, at which point we can directly called encode/decodeData. - token_timer_->enableTimer(std::chrono::milliseconds(0), &scope_); - } -} - -Http::FilterTrailersStatus StreamRateLimiter::onTrailers() { - saw_end_stream_ = true; - saw_trailers_ = true; - return buffer_.length() > 0 ? Http::FilterTrailersStatus::StopIteration - : Http::FilterTrailersStatus::Continue; -} - } // namespace Fault } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/fault/fault_filter.h b/source/extensions/filters/http/fault/fault_filter.h index 35acaed97a228..5a75f1244db70 100644 --- a/source/extensions/filters/http/fault/fault_filter.h +++ b/source/extensions/filters/http/fault/fault_filter.h @@ -19,6 +19,7 @@ #include "common/stats/symbol_table_impl.h" #include "extensions/filters/common/fault/fault_config.h" +#include "extensions/filters/http/common/stream_rate_limiter.h" namespace Envoy { namespace Extensions { @@ -145,67 +146,6 @@ class FaultFilterConfig { using FaultFilterConfigSharedPtr = std::shared_ptr; -/** - * An HTTP stream rate limiter. Split out for ease of testing and potential code reuse elsewhere. - */ -class StreamRateLimiter : Logger::Loggable { -public: - /** - * @param max_kbps maximum rate in KiB/s. - * @param max_buffered_data maximum data to buffer before invoking the pause callback. - * @param pause_data_cb callback invoked when the limiter has buffered too much data. - * @param resume_data_cb callback invoked when the limiter has gone under the buffer limit. - * @param write_data_cb callback invoked to write data to the stream. - * @param continue_cb callback invoked to continue the stream. This is only used to continue - * trailers that have been paused during body flush. - * @param time_source the time source to run the token bucket with. - * @param dispatcher the stream's dispatcher to use for creating timers. - * @param scope the stream's scope - */ - StreamRateLimiter(uint64_t max_kbps, uint64_t max_buffered_data, - std::function pause_data_cb, std::function resume_data_cb, - std::function write_data_cb, - std::function continue_cb, TimeSource& time_source, - Event::Dispatcher& dispatcher, const ScopeTrackedObject& scope); - - /** - * Called by the stream to write data. All data writes happen asynchronously, the stream should - * be stopped after this call (all data will be drained from incoming_buffer). - */ - void writeData(Buffer::Instance& incoming_buffer, bool end_stream); - - /** - * Called if the stream receives trailers. - */ - Http::FilterTrailersStatus onTrailers(); - - /** - * Like the owning filter, we must handle inline destruction, so we have a destroy() method which - * kills any callbacks. - */ - void destroy() { token_timer_.reset(); } - bool destroyed() { return token_timer_ == nullptr; } - -private: - void onTokenTimer(); - - // We currently divide each second into 16 segments for the token bucket. Thus, the rate limit is - // KiB per second, divided into 16 segments, ~63ms apart. 16 is used because it divides into 1024 - // evenly. - static constexpr uint64_t SecondDivisor = 16; - - const uint64_t bytes_per_time_slice_; - const std::function write_data_cb_; - const std::function continue_cb_; - const ScopeTrackedObject& scope_; - TokenBucketImpl token_bucket_; - Event::TimerPtr token_timer_; - bool saw_data_{}; - bool saw_end_stream_{}; - bool saw_trailers_{}; - Buffer::WatermarkBuffer buffer_; -}; - using AbortHttpAndGrpcStatus = std::pair, absl::optional>; /** @@ -279,7 +219,7 @@ class FaultFilter : public Http::StreamFilter, Logger::Loggable downstream_cluster_storage_; const FaultSettings* fault_settings_; bool fault_active_{}; - std::unique_ptr response_limiter_; + std::unique_ptr response_limiter_; std::string downstream_cluster_delay_percent_key_{}; std::string downstream_cluster_abort_percent_key_{}; std::string downstream_cluster_delay_duration_key_{}; diff --git a/test/extensions/filters/http/common/BUILD b/test/extensions/filters/http/common/BUILD index 9c5b60eb9789e..2462cf2b78577 100644 --- a/test/extensions/filters/http/common/BUILD +++ b/test/extensions/filters/http/common/BUILD @@ -52,6 +52,27 @@ envoy_extension_cc_test( ], ) +envoy_cc_test( + name = "stream_rate_limiter_test", + srcs = ["stream_rate_limiter_test.cc"], + deps = [ + "//include/envoy/event:dispatcher_interface", + "//source/common/buffer:buffer_lib", + "//source/common/common:empty_string", + "//source/common/http:header_map_lib", + "//source/common/http:headers_lib", + "//source/common/stats:stats_lib", + "//source/extensions/filters/http/common:stream_rate_limiter_lib", + "//test/common/http:common_lib", + "//test/mocks/http:http_mocks", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/stats:stats_mocks", + "//test/mocks/upstream:upstream_mocks", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + envoy_cc_test( name = "utility_test", srcs = [ diff --git a/test/extensions/filters/http/common/stream_rate_limiter_test.cc b/test/extensions/filters/http/common/stream_rate_limiter_test.cc new file mode 100644 index 0000000000000..d2e57de2d4f98 --- /dev/null +++ b/test/extensions/filters/http/common/stream_rate_limiter_test.cc @@ -0,0 +1,150 @@ +#include "envoy/event/dispatcher.h" + +#include "common/buffer/buffer_impl.h" +#include "common/common/empty_string.h" + +#include "extensions/filters/http/common/stream_rate_limiter.h" + +#include "test/common/http/common.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/runtime/mocks.h" +#include "test/mocks/stats/mocks.h" +#include "test/test_common/printers.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::AnyNumber; +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Common { + +class StreamRateLimiterTest : public testing::Test { +public: + void setUpTest(uint16_t limit_kbps, uint16_t fill_interval, + std::shared_ptr token_bucket = nullptr) { + EXPECT_CALL(decoder_callbacks_.dispatcher_, pushTrackedObject(_)).Times(AnyNumber()); + EXPECT_CALL(decoder_callbacks_.dispatcher_, popTrackedObject(_)).Times(AnyNumber()); + + limiter_ = std::make_unique( + limit_kbps, decoder_callbacks_.decoderBufferLimit(), + [this] { decoder_callbacks_.onDecoderFilterAboveWriteBufferHighWatermark(); }, + [this] { decoder_callbacks_.onDecoderFilterBelowWriteBufferLowWatermark(); }, + [this](Buffer::Instance& data, bool end_stream) { + decoder_callbacks_.injectDecodedDataToFilterChain(data, end_stream); + }, + [this] { decoder_callbacks_.continueDecoding(); }, + [](uint64_t /*len*/) { + // config->stats().decode_allowed_size_.set(len); + }, + time_system_, decoder_callbacks_.dispatcher_, decoder_callbacks_.scope(), token_bucket, + std::chrono::milliseconds(fill_interval)); + } + + void setUpTest(uint16_t limit_kbps) { + EXPECT_CALL(decoder_callbacks_.dispatcher_, pushTrackedObject(_)).Times(AnyNumber()); + EXPECT_CALL(decoder_callbacks_.dispatcher_, popTrackedObject(_)).Times(AnyNumber()); + + limiter_ = std::make_unique( + limit_kbps, decoder_callbacks_.decoderBufferLimit(), + [this] { decoder_callbacks_.onDecoderFilterAboveWriteBufferHighWatermark(); }, + [this] { decoder_callbacks_.onDecoderFilterBelowWriteBufferLowWatermark(); }, + [this](Buffer::Instance& data, bool end_stream) { + decoder_callbacks_.injectDecodedDataToFilterChain(data, end_stream); + }, + [this] { decoder_callbacks_.continueDecoding(); }, + [](uint64_t /*len*/) { + // config->stats().decode_allowed_size_.set(len); + }, + time_system_, decoder_callbacks_.dispatcher_, decoder_callbacks_.scope()); + } + + uint64_t fillInterval() { return limiter_->fill_interval_.count(); } + + NiceMock stats_; + NiceMock decoder_callbacks_; + NiceMock runtime_; + std::unique_ptr limiter_; + Buffer::OwnedImpl data_; + Event::SimulatedTimeSystem time_system_; +}; + +TEST_F(StreamRateLimiterTest, RateLimitOnSingleStream) { + ON_CALL(decoder_callbacks_, decoderBufferLimit()).WillByDefault(Return(1100)); + Event::MockTimer* token_timer = new NiceMock(&decoder_callbacks_.dispatcher_); + setUpTest(1); + + EXPECT_EQ(50UL, fillInterval()); + + // Send a small amount of data which should be within limit. + Buffer::OwnedImpl data1("hello"); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0), _)); + limiter_->writeData(data1, false); + EXPECT_CALL(decoder_callbacks_, + injectDecodedDataToFilterChain(BufferStringEqual("hello"), false)); + token_timer->invokeCallback(); + + // Advance time by 1s which should refill all tokens. + time_system_.advanceTimeWait(std::chrono::seconds(1)); + + // Send 1126 bytes of data which is 1s + 2 refill cycles of data. + EXPECT_CALL(decoder_callbacks_, onDecoderFilterAboveWriteBufferHighWatermark()); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0), _)); + Buffer::OwnedImpl data2(std::string(1126, 'a')); + limiter_->writeData(data2, false); + + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); + EXPECT_CALL(decoder_callbacks_, onDecoderFilterBelowWriteBufferLowWatermark()); + EXPECT_CALL(decoder_callbacks_, + injectDecodedDataToFilterChain(BufferStringEqual(std::string(1024, 'a')), false)); + token_timer->invokeCallback(); + + // Fire timer, also advance time. + time_system_.advanceTimeWait(std::chrono::milliseconds(50)); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); + EXPECT_CALL(decoder_callbacks_, + injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'a')), false)); + token_timer->invokeCallback(); + + // Get new data with current data buffered, not end_stream. + Buffer::OwnedImpl data3(std::string(51, 'b')); + limiter_->writeData(data3, false); + + // Fire timer, also advance time. + time_system_.advanceTimeWait(std::chrono::milliseconds(50)); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); + EXPECT_CALL(decoder_callbacks_, + injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'a')), false)); + token_timer->invokeCallback(); + + // Fire timer, also advance time. No timer enable because there is nothing + // buffered. + time_system_.advanceTimeWait(std::chrono::milliseconds(50)); + EXPECT_CALL(decoder_callbacks_, + injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'b')), false)); + token_timer->invokeCallback(); + + // Advance time by 1s for a full refill. + time_system_.advanceTimeWait(std::chrono::seconds(1)); + + // Now send 1024 in one shot with end_stream true which should go through and + // end the stream. + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0), _)); + Buffer::OwnedImpl data4(std::string(1024, 'c')); + limiter_->writeData(data4, true); + EXPECT_CALL(decoder_callbacks_, + injectDecodedDataToFilterChain(BufferStringEqual(std::string(1024, 'c')), true)); + token_timer->invokeCallback(); +} + +} // namespace Common +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/fault/fault_filter_integration_test.cc b/test/extensions/filters/http/fault/fault_filter_integration_test.cc index 2f31eaa7a9442..c00bf3ee55a0b 100644 --- a/test/extensions/filters/http/fault/fault_filter_integration_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_integration_test.cc @@ -99,15 +99,15 @@ TEST_P(FaultIntegrationTestAllProtocols, ResponseRateLimitNoTrailers) { EXPECT_EQ(1UL, test_server_->gauge("http.config_test.fault.active_faults")->value()); upstream_request_->encodeHeaders(default_response_headers_, false); - Buffer::OwnedImpl data(std::string(127, 'a')); + Buffer::OwnedImpl data(std::string(102, 'a')); upstream_request_->encodeData(data, true); // Wait for a tick worth of data. - response->waitForBodyData(64); + response->waitForBodyData(51); // Wait for a tick worth of data and end stream. - simTime().advanceTimeWait(std::chrono::milliseconds(63)); - response->waitForBodyData(127); + simTime().advanceTimeWait(std::chrono::milliseconds(50)); + response->waitForBodyData(102); ASSERT_TRUE(response->waitForEndStream()); EXPECT_EQ(0UL, test_server_->counter("http.config_test.fault.aborts_injected")->value()); @@ -338,15 +338,15 @@ TEST_P(FaultIntegrationTestHttp2, ResponseRateLimitTrailersBodyFlushed) { EXPECT_EQ(1UL, test_server_->gauge("http.config_test.fault.active_faults")->value()); upstream_request_->encodeHeaders(default_response_headers_, false); - Buffer::OwnedImpl data(std::string(127, 'a')); + Buffer::OwnedImpl data(std::string(102, 'a')); upstream_request_->encodeData(data, false); // Wait for a tick worth of data. - response->waitForBodyData(64); + response->waitForBodyData(51); // Advance time and wait for a tick worth of data. - simTime().advanceTimeWait(std::chrono::milliseconds(63)); - response->waitForBodyData(127); + simTime().advanceTimeWait(std::chrono::milliseconds(50)); + response->waitForBodyData(102); // Send trailers and wait for end stream. Http::TestResponseTrailerMapImpl trailers{{"hello", "world"}}; @@ -368,17 +368,17 @@ TEST_P(FaultIntegrationTestHttp2, ResponseRateLimitTrailersBodyNotFlushed) { codec_client_->makeHeaderOnlyRequest(default_request_headers_); waitForNextUpstreamRequest(); upstream_request_->encodeHeaders(default_response_headers_, false); - Buffer::OwnedImpl data(std::string(128, 'a')); + Buffer::OwnedImpl data(std::string(102, 'a')); upstream_request_->encodeData(data, false); Http::TestResponseTrailerMapImpl trailers{{"hello", "world"}}; upstream_request_->encodeTrailers(trailers); // Wait for a tick worth of data. - response->waitForBodyData(64); + response->waitForBodyData(51); // Advance time and wait for a tick worth of data, trailers, and end stream. - simTime().advanceTimeWait(std::chrono::milliseconds(63)); - response->waitForBodyData(128); + simTime().advanceTimeWait(std::chrono::milliseconds(50)); + response->waitForBodyData(102); ASSERT_TRUE(response->waitForEndStream()); EXPECT_NE(nullptr, response->trailers()); diff --git a/test/extensions/filters/http/fault/fault_filter_test.cc b/test/extensions/filters/http/fault/fault_filter_test.cc index c42b94091cd0d..c40a80171ed23 100644 --- a/test/extensions/filters/http/fault/fault_filter_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_test.cc @@ -1362,40 +1362,40 @@ TEST_F(FaultFilterRateLimitTest, ResponseRateLimitEnabled) { // Advance time by 1s which should refill all tokens. time_system_.advanceTimeWait(std::chrono::seconds(1)); - // Send 1152 bytes of data which is 1s + 2 refill cycles of data. + // Send 1126 bytes of data which is 1s + 2 refill cycles of data. EXPECT_CALL(encoder_filter_callbacks_, onEncoderFilterAboveWriteBufferHighWatermark()); EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0), _)); - Buffer::OwnedImpl data2(std::string(1152, 'a')); + Buffer::OwnedImpl data2(std::string(1126, 'a')); EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(data2, false)); - EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(63), _)); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, onEncoderFilterBelowWriteBufferLowWatermark()); EXPECT_CALL(encoder_filter_callbacks_, injectEncodedDataToFilterChain(BufferStringEqual(std::string(1024, 'a')), false)); token_timer->invokeCallback(); // Fire timer, also advance time. - time_system_.advanceTimeWait(std::chrono::milliseconds(63)); - EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(63), _)); + time_system_.advanceTimeWait(std::chrono::milliseconds(50)); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(64, 'a')), false)); + injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'a')), false)); token_timer->invokeCallback(); // Get new data with current data buffered, not end_stream. - Buffer::OwnedImpl data3(std::string(64, 'b')); + Buffer::OwnedImpl data3(std::string(51, 'b')); EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(data3, false)); // Fire timer, also advance time. - time_system_.advanceTimeWait(std::chrono::milliseconds(63)); - EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(63), _)); + time_system_.advanceTimeWait(std::chrono::milliseconds(50)); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(64, 'a')), false)); + injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'a')), false)); token_timer->invokeCallback(); // Fire timer, also advance time. No time enable because there is nothing buffered. - time_system_.advanceTimeWait(std::chrono::milliseconds(63)); + time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(64, 'b')), false)); + injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'b')), false)); token_timer->invokeCallback(); // Advance time by 1s for a full refill. From cc9816907a20d015d0ce089b972ee230295f9282 Mon Sep 17 00:00:00 2001 From: Teju Nareddy Date: Tue, 20 Apr 2021 19:45:33 -0400 Subject: [PATCH 055/209] grpc_json_transcoder: Switch tests to compare JSON objects (#16085) Signed-off-by: Teju Nareddy --- .../grpc_json_transcoder_integration_test.cc | 13 ++++++++++--- test/test_common/utility.h | 19 +++++++++++++++++-- test/test_common/utility_test.cc | 12 ++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc index 03b675cae9a07..851129b539c72 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc +++ b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc @@ -163,8 +163,15 @@ class GrpcJsonTranscoderIntegrationTest return Http::HeaderMap::Iterate::Continue; }); if (!expected_response_body.empty()) { - if (full_response) { - EXPECT_EQ(expected_response_body, response->body()); + const bool isJsonResponse = response->headers().getContentTypeValue() == "application/json"; + if (full_response && isJsonResponse) { + const bool isStreamingResponse = response->body()[0] == '['; + EXPECT_TRUE(TestUtility::jsonStringEqual(response->body(), expected_response_body, + isStreamingResponse)) + << "Response mismatch. \nGot : " << response->body() + << "\nWant: " << expected_response_body; + } else if (full_response) { + EXPECT_EQ(response->body(), expected_response_body); } else { EXPECT_TRUE(absl::StartsWith(response->body(), expected_response_body)); } @@ -1191,7 +1198,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, ServerStreamingGetExceedsBufferLimit) Status(), Http::TestResponseHeaderMapImpl{{":status", "200"}, {"content-type", "application/json"}}, // Incomplete response, not valid JSON. - R"([{"id":"1","author":"Neal Stephenson","title":"Readme"})", true, false, "", true, + R"([{"id":"1","author":"Neal Stephenson","title":"Readme"})", false, false, "", true, /*expect_response_complete=*/false); } diff --git a/test/test_common/utility.h b/test/test_common/utility.h index e0e8da053d990..4f2d61e4724dc 100644 --- a/test/test_common/utility.h +++ b/test/test_common/utility.h @@ -362,10 +362,16 @@ class TestUtility { * * @param lhs JSON string on LHS. * @param rhs JSON string on RHS. + * @param support_root_array Whether to support parsing JSON arrays. * @return bool indicating whether the JSON strings are equal. */ - static bool jsonStringEqual(const std::string& lhs, const std::string& rhs) { - return protoEqual(jsonToStruct(lhs), jsonToStruct(rhs)); + static bool jsonStringEqual(const std::string& lhs, const std::string& rhs, + bool support_root_array = false) { + if (!support_root_array) { + return protoEqual(jsonToStruct(lhs), jsonToStruct(rhs)); + } + + return protoEqual(jsonArrayToStruct(lhs), jsonArrayToStruct(rhs)); } /** @@ -640,6 +646,15 @@ class TestUtility { return message; } + static ProtobufWkt::Struct jsonArrayToStruct(const std::string& json) { + // Hacky: add a surrounding root message, allowing JSON to be parsed into a struct. + std::string root_message = absl::StrCat("{ \"testOnlyArrayRoot\": ", json, "}"); + + ProtobufWkt::Struct message; + MessageUtil::loadFromJson(root_message, message); + return message; + } + /** * Extract the Protobuf binary format of a google.protobuf.Message as a string. * @param message message of type type.googleapis.com/google.protobuf.Message. diff --git a/test/test_common/utility_test.cc b/test/test_common/utility_test.cc index 71190a50317e3..8aed3308f4953 100644 --- a/test/test_common/utility_test.cc +++ b/test/test_common/utility_test.cc @@ -149,4 +149,16 @@ TEST(BuffersEqual, NonAligned) { EXPECT_TRUE(TestUtility::buffersEqual(buffer1, buffer2)); } +TEST(JsonEquals, RootMessage) { + EXPECT_TRUE(TestUtility::jsonStringEqual(R"({ "a" : "b" })", R"({ "a" : "b" })")); + EXPECT_TRUE(TestUtility::jsonStringEqual(R"({ "a" : "b" })", R"({"a":"b"})")); +} + +TEST(JsonEquals, RootArray) { + EXPECT_TRUE(TestUtility::jsonStringEqual(R"([{ "a" : "b" }, { "c" : "d" }])", + R"([{ "a" : "b" }, { "c" : "d" }])", true)); + EXPECT_TRUE(TestUtility::jsonStringEqual(R"([{ "a" : "b" }, { "c" : "d" }])", + R"([{"a":"b"},{"c":"d"}])", true)); +} + } // namespace Envoy From 970b4d7d1088a4fc6739388701dfe16623ce9450 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 21 Apr 2021 02:04:02 +0100 Subject: [PATCH 056/209] protos: Remove (more) redundant imports (#16086) Signed-off-by: Ryan Northey --- api/envoy/extensions/filters/http/composite/v3/composite.proto | 3 --- .../matching/common_inputs/environment_variable/v3/input.proto | 2 -- .../envoy/extensions/filters/http/composite/v3/composite.proto | 3 --- .../matching/common_inputs/environment_variable/v3/input.proto | 2 -- 4 files changed, 10 deletions(-) diff --git a/api/envoy/extensions/filters/http/composite/v3/composite.proto b/api/envoy/extensions/filters/http/composite/v3/composite.proto index 51af8a77a1f38..706e7a5b24668 100644 --- a/api/envoy/extensions/filters/http/composite/v3/composite.proto +++ b/api/envoy/extensions/filters/http/composite/v3/composite.proto @@ -4,10 +4,7 @@ package envoy.extensions.filters.http.composite.v3; import "envoy/config/core/v3/extension.proto"; -import "google/protobuf/any.proto"; - import "udpa/annotations/status.proto"; -import "udpa/annotations/versioning.proto"; option java_package = "io.envoyproxy.envoy.extensions.filters.http.composite.v3"; option java_outer_classname = "CompositeProto"; diff --git a/api/envoy/extensions/matching/common_inputs/environment_variable/v3/input.proto b/api/envoy/extensions/matching/common_inputs/environment_variable/v3/input.proto index 13db7227c6401..6bbe86e688644 100644 --- a/api/envoy/extensions/matching/common_inputs/environment_variable/v3/input.proto +++ b/api/envoy/extensions/matching/common_inputs/environment_variable/v3/input.proto @@ -2,9 +2,7 @@ syntax = "proto3"; package envoy.extensions.matching.common_inputs.environment_variable.v3; -import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; -import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; option java_package = "io.envoyproxy.envoy.extensions.matching.common_inputs.environment_variable.v3"; diff --git a/generated_api_shadow/envoy/extensions/filters/http/composite/v3/composite.proto b/generated_api_shadow/envoy/extensions/filters/http/composite/v3/composite.proto index 51af8a77a1f38..706e7a5b24668 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/composite/v3/composite.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/composite/v3/composite.proto @@ -4,10 +4,7 @@ package envoy.extensions.filters.http.composite.v3; import "envoy/config/core/v3/extension.proto"; -import "google/protobuf/any.proto"; - import "udpa/annotations/status.proto"; -import "udpa/annotations/versioning.proto"; option java_package = "io.envoyproxy.envoy.extensions.filters.http.composite.v3"; option java_outer_classname = "CompositeProto"; diff --git a/generated_api_shadow/envoy/extensions/matching/common_inputs/environment_variable/v3/input.proto b/generated_api_shadow/envoy/extensions/matching/common_inputs/environment_variable/v3/input.proto index 13db7227c6401..6bbe86e688644 100644 --- a/generated_api_shadow/envoy/extensions/matching/common_inputs/environment_variable/v3/input.proto +++ b/generated_api_shadow/envoy/extensions/matching/common_inputs/environment_variable/v3/input.proto @@ -2,9 +2,7 @@ syntax = "proto3"; package envoy.extensions.matching.common_inputs.environment_variable.v3; -import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; -import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; option java_package = "io.envoyproxy.envoy.extensions.matching.common_inputs.environment_variable.v3"; From a12869fa9e9add4301a700978d5489e6a0cc0526 Mon Sep 17 00:00:00 2001 From: Ivan Zemlyanskiy Date: Wed, 21 Apr 2021 07:02:12 +0300 Subject: [PATCH 057/209] upgrade rules_go to v0.27.0 (#16065) (#16083) in the resent version of rules_go, the issue https://github.com/bazelbuild/rules_go/issues/2771 was fixed. It should address the bazel build issue on some Linux or MacOS (https://github.com/bazelbuild/bazel/issues/12986) Signed-off-by: izemlyanskiy --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 7dcf20bec831c..00c5aacc059f8 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -623,11 +623,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Go rules for Bazel", project_desc = "Bazel rules for the Go language", project_url = "https://github.com/bazelbuild/rules_go", - version = "0.25.0", - sha256 = "6f111c57fd50baf5b8ee9d63024874dd2a014b069426156c55adbf6d3d22cb7b", + version = "0.27.0", + sha256 = "69de5c704a05ff37862f7e0f5534d4f479418afc21806c887db544a316f3cb6b", urls = ["https://github.com/bazelbuild/rules_go/releases/download/v{version}/rules_go-v{version}.tar.gz"], use_category = ["build", "api"], - release_date = "2020-12-02", + release_date = "2021-03-17", implied_untracked_deps = [ "com_github_golang_protobuf", "io_bazel_rules_nogo", From 0c3702839c2f733654f1e74ec7708ffb67c6ec74 Mon Sep 17 00:00:00 2001 From: DavidKorczynski Date: Wed, 21 Apr 2021 14:01:10 +0100 Subject: [PATCH 058/209] udp listener fuzzer (#15974) * test: common: network: add udp listener fuzzer. Signed-off-by: davkor --- test/common/network/BUILD | 24 +++ test/common/network/udp_corpus/seed1.txt | 1 + test/common/network/udp_fuzz.cc | 183 +++++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 test/common/network/udp_corpus/seed1.txt create mode 100644 test/common/network/udp_fuzz.cc diff --git a/test/common/network/BUILD b/test/common/network/BUILD index e5949d4019314..e38391834b07a 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -393,6 +393,30 @@ envoy_cc_test( ], ) +envoy_cc_fuzz_test( + name = "udp_fuzz", + srcs = ["udp_fuzz.cc"], + corpus = "udp_corpus", + deps = [ + "udp_listener_impl_test_base_lib", + "//source/common/event:dispatcher_lib", + "//source/common/network:address_lib", + "//source/common/network:listener_lib", + "//source/common/network:socket_option_lib", + "//source/common/network:udp_packet_writer_handler_lib", + "//source/common/network:utility_lib", + "//source/common/stats:stats_lib", + "//test/common/network:listener_impl_test_base_lib", + "//test/mocks/network:network_mocks", + "//test/mocks/server:server_mocks", + "//test/test_common:environment_lib", + "//test/test_common:network_utility_lib", + "//test/test_common:threadsafe_singleton_injector_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + envoy_cc_fuzz_test( name = "utility_fuzz_test", srcs = ["utility_fuzz_test.cc"], diff --git a/test/common/network/udp_corpus/seed1.txt b/test/common/network/udp_corpus/seed1.txt new file mode 100644 index 0000000000000..5fb47689faf3c --- /dev/null +++ b/test/common/network/udp_corpus/seed1.txt @@ -0,0 +1 @@ +udp-seed1 diff --git a/test/common/network/udp_fuzz.cc b/test/common/network/udp_fuzz.cc new file mode 100644 index 0000000000000..792f1f93286b6 --- /dev/null +++ b/test/common/network/udp_fuzz.cc @@ -0,0 +1,183 @@ +#include + +#include "envoy/config/core/v3/base.pb.h" + +#include "common/api/os_sys_calls_impl.h" +#include "common/network/address_impl.h" +#include "common/network/socket_option_factory.h" +#include "common/network/socket_option_impl.h" +#include "common/network/udp_listener_impl.h" +#include "common/network/udp_packet_writer_handler_impl.h" +#include "common/network/utility.h" + +#include "test/common/network/udp_listener_impl_test_base.h" +#include "test/fuzz/fuzz_runner.h" +#include "test/mocks/api/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/test_common/environment.h" +#include "test/test_common/network_utility.h" +#include "test/test_common/threadsafe_singleton_injector.h" +#include "test/test_common/utility.h" + +namespace Envoy { +namespace { + +class OverrideOsSysCallsImpl : public Api::OsSysCallsImpl { +public: + MOCK_METHOD(bool, supportsUdpGro, (), (const)); + MOCK_METHOD(bool, supportsMmsg, (), (const)); +}; + +class UdpFuzz; + +class FuzzUdpListenerCallbacks : public Network::UdpListenerCallbacks { +public: + FuzzUdpListenerCallbacks(UdpFuzz* upf) : my_upf_(upf) {} + ~FuzzUdpListenerCallbacks() override = default; + void onData(Network::UdpRecvData&& data) override; + void onReadReady() override; + void onWriteReady(const Network::Socket& socket) override; + void onReceiveError(Api::IoError::IoErrorCode error_code) override; + void onDataWorker(Network::UdpRecvData&& data) override; + void post(Network::UdpRecvData&& data) override; + void onDatagramsDropped(uint32_t dropped) override; + uint32_t workerIndex() const override; + Network::UdpPacketWriter& udpPacketWriter() override; + +private: + UdpFuzz* my_upf_; +}; + +class UdpFuzz { +public: + UdpFuzz(const uint8_t* buf, size_t len) { + // Prepare environment + api_ = Api::createApiForTest(); + dispatcher_ = api_->allocateDispatcher("test_thread"); + ip_version_ = TestEnvironment::getIpVersionsForTest()[0]; + + server_socket_ = createServerSocket(true, ip_version_); + server_socket_->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); + server_socket_->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); + + // Create packet writer + udp_packet_writer_ = std::make_unique(server_socket_->ioHandle()); + + // Set up callbacks + FuzzUdpListenerCallbacks fuzzCallbacks(this); + + // Create listener with default config + envoy::config::core::v3::UdpSocketConfig config; + + FuzzedDataProvider provider(buf, len); + uint16_t SocketType = provider.ConsumeIntegralInRange(0, 2); + if (SocketType == 0) { + config.mutable_prefer_gro()->set_value(true); + ON_CALL(override_syscall_, supportsUdpGro()).WillByDefault(Return(true)); + } else if (SocketType == 1) { + ON_CALL(override_syscall_, supportsMmsg()).WillByDefault(Return(true)); + } else { + ON_CALL(override_syscall_, supportsMmsg()).WillByDefault(Return(false)); + ON_CALL(override_syscall_, supportsUdpGro()).WillByDefault(Return(false)); + } + + std::unique_ptr listener_ = + std::make_unique(dispatcherImpl(), server_socket_, fuzzCallbacks, + dispatcherImpl().timeSource(), config); + + Network::Address::Instance* send_to_addr_ = new Network::Address::Ipv4Instance( + "127.0.0.1", server_socket_->addressProvider().localAddress()->ip()->port()); + + // Now do all of the fuzzing + static const int MaxPackets = 15; + total_packets_ = provider.ConsumeIntegralInRange(1, MaxPackets); + Network::Test::UdpSyncPeer client_(ip_version_); + for (uint16_t i = 0; i < total_packets_; i++) { + std::string packet_ = + provider.ConsumeBytesAsString(provider.ConsumeIntegralInRange(1, 3000)); + if (packet_.empty()) { + packet_ = "EMPTY_PACKET"; + } + client_.write(packet_, *send_to_addr_); + } + dispatcher_->run(Event::Dispatcher::RunType::Block); + + // cleanup + delete send_to_addr_; + } + + Event::DispatcherImpl& dispatcherImpl() { + // We need access to the concrete impl type in order to instantiate a + // Test[Udp]Listener, which instantiates a [Udp]ListenerImpl, which requires + // a DispatcherImpl to access DispatcherImpl::base_, which is not part of + // the Dispatcher API. + Event::DispatcherImpl* impl = dynamic_cast(dispatcher_.get()); + return *impl; + } + + Network::SocketSharedPtr createServerSocket(bool bind, Network::Address::IpVersion version) { + // Set IP_FREEBIND to allow sendmsg to send with non-local IPv6 source + // address. + return std::make_shared( + Network::Test::getCanonicalLoopbackAddress(version), +#ifdef IP_FREEBIND + Network::SocketOptionFactory::buildIpFreebindOptions(), +#else + nullptr, +#endif + bind); + } + + Network::SocketSharedPtr server_socket_; + Event::DispatcherPtr dispatcher_; + Api::ApiPtr api_; + Network::UdpPacketWriterPtr udp_packet_writer_; + uint16_t sent_packets_ = 0; + uint16_t total_packets_; + Network::Address::IpVersion ip_version_; + NiceMock override_syscall_; + TestThreadsafeSingletonInjector os_calls{&override_syscall_}; +}; + +void FuzzUdpListenerCallbacks::onData(Network::UdpRecvData&& data) { + my_upf_->sent_packets_++; + if (my_upf_->sent_packets_ == my_upf_->total_packets_) { + my_upf_->dispatcher_->exit(); + } + UNREFERENCED_PARAMETER(data); +} + +void FuzzUdpListenerCallbacks::onReadReady() {} + +void FuzzUdpListenerCallbacks::onWriteReady(const Network::Socket& socket) { + UNREFERENCED_PARAMETER(socket); +} + +void FuzzUdpListenerCallbacks::onReceiveError(Api::IoError::IoErrorCode error_code) { + my_upf_->sent_packets_++; + if (my_upf_->sent_packets_ == my_upf_->total_packets_) { + my_upf_->dispatcher_->exit(); + } + UNREFERENCED_PARAMETER(error_code); +} +Network::UdpPacketWriter& FuzzUdpListenerCallbacks::udpPacketWriter() { + return *my_upf_->udp_packet_writer_; +} +uint32_t FuzzUdpListenerCallbacks::workerIndex() const { return 0; } +void FuzzUdpListenerCallbacks::onDataWorker(Network::UdpRecvData&& data) { + UNREFERENCED_PARAMETER(data); +} +void FuzzUdpListenerCallbacks::post(Network::UdpRecvData&& data) { UNREFERENCED_PARAMETER(data); } + +void FuzzUdpListenerCallbacks::onDatagramsDropped(uint32_t dropped) { + my_upf_->sent_packets_++; + if (my_upf_->sent_packets_ == my_upf_->total_packets_) { + my_upf_->dispatcher_->exit(); + } + UNREFERENCED_PARAMETER(dropped); +} + +DEFINE_FUZZER(const uint8_t* buf, size_t len) { UdpFuzz udp_instance(buf, len); } +} // namespace +} // namespace Envoy From 9b27f961d9fc56fb1f4840720750c6f65dda6c88 Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Thu, 22 Apr 2021 05:36:14 -0700 Subject: [PATCH 059/209] Bring parity to the docker images on the install docs pages (#16038) * Bring parity to the docker images in the install docs pages Signed-off-by: Sotiris Nanopoulos --- docs/root/start/install.rst | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/root/start/install.rst b/docs/root/start/install.rst index 3cb4d2b464d27..d83d4643fd4ec 100644 --- a/docs/root/start/install.rst +++ b/docs/root/start/install.rst @@ -133,12 +133,12 @@ You can install Envoy on Mac OSX using the official brew repositories, or from Install Envoy on Windows ~~~~~~~~~~~~~~~~~~~~~~~~ -You can run Envoy using the official Windows development Docker image. +You can run Envoy using the official Windows Docker image. -.. code-block:: console +.. substitution-code-block:: console - $ docker pull envoyproxy/envoy-windows-dev:latest - $ docker run --rm envoyproxy/envoy-windows-dev:latest --version + $ docker pull envoyproxy/|envoy_windows_docker_image| + $ docker run --rm envoyproxy/|envoy_windows_docker_image| --version .. _start_install_docker: @@ -207,6 +207,12 @@ The following table shows the available Docker images - - - + * - `envoyproxy/envoy-windows `_ + - Release binary with symbols stripped on top of a Windows Server 1809 base. + - |DOCKER_IMAGE_TAG_NAME| + - + - + - * - `envoyproxy/envoy-debug `_ - Release binary with debug symbols on top of an Ubuntu Bionic base. - |DOCKER_IMAGE_TAG_NAME| @@ -232,7 +238,7 @@ The following table shows the available Docker images - latest - latest * - `envoyproxy/envoy-windows-dev `_ - - Release binary with symbols stripped on top of a Windows 1809 base. + - Release binary with symbols stripped on top of a Windows Server 1809 base. Includes build tools. - - - latest From 118f66e87bae3961c5726ad46239a85cdadc316f Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Thu, 22 Apr 2021 08:06:12 -0700 Subject: [PATCH 060/209] xray: fix the default sampling rate for AWS X-Ray tracer (#15958) * Fix the default sampling rate for AWS X-Ray tracer extension to 5% and test for the same if no localized sampling rule is applied. Update the release notes with the bug fix information Signed-off-by: Sunil Narasimhamurthy --- docs/root/version_history/current.rst | 1 + source/extensions/tracers/xray/localized_sampling.cc | 6 +++++- source/extensions/tracers/xray/localized_sampling.h | 7 ++++++- .../tracers/xray/localized_sampling_test.cc | 11 +++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 0ccbc03a6c205..9a08d9a7e57a1 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -20,6 +20,7 @@ Bug Fixes *Changes expected to improve the state of the world and are unlikely to have negative effects* * validation: fix an issue that causes TAP sockets to panic during config validation mode. +* xray: fix the default sampling 'rate' for AWS X-Ray tracer extension to be 5% as opposed to 50%. * zipkin: fix timestamp serializaiton in annotations. A prior bug fix exposed an issue with timestamps being serialized as strings. Removed Config or Runtime diff --git a/source/extensions/tracers/xray/localized_sampling.cc b/source/extensions/tracers/xray/localized_sampling.cc index 66697f14a71fe..b16b3929898aa 100644 --- a/source/extensions/tracers/xray/localized_sampling.cc +++ b/source/extensions/tracers/xray/localized_sampling.cc @@ -10,8 +10,12 @@ namespace Extensions { namespace Tracers { namespace XRay { -constexpr double DefaultRate = 0.5; +// Corresponds to 5% sampling rate when no custom rules are applied. +constexpr double DefaultRate = 0.05; +// Determines how many requests to sample per second before default +// sampling rate kicks in when no custom rules are applied. constexpr int DefaultFixedTarget = 1; +// The required 'version' of sampling manifest file when localized sampling is applied. constexpr int SamplingFileVersion = 2; constexpr auto VersionJsonKey = "version"; constexpr auto DefaultRuleJsonKey = "default"; diff --git a/source/extensions/tracers/xray/localized_sampling.h b/source/extensions/tracers/xray/localized_sampling.h index f622d9f8874b6..f95a3da8fdd14 100644 --- a/source/extensions/tracers/xray/localized_sampling.h +++ b/source/extensions/tracers/xray/localized_sampling.h @@ -126,7 +126,7 @@ class LocalizedSamplingManifest { std::vector& customRules() { return custom_rules_; } /** - * @return true if the this manifest has a set of custom rules; otherwise false. + * @return true if this manifest has a set of custom rules; otherwise false. */ bool hasCustomRules() const { return !custom_rules_.empty(); } @@ -155,6 +155,11 @@ class LocalizedSamplingStrategy : public SamplingStrategy { */ bool usingDefaultManifest() const { return use_default_; } + /** + * @return the default manifest. Mainly for unit testing purposes. + */ + const LocalizedSamplingManifest& defaultManifest() const { return default_manifest_; } + private: bool shouldTrace(LocalizedSamplingRule& rule); LocalizedSamplingManifest default_manifest_; diff --git a/test/extensions/tracers/xray/localized_sampling_test.cc b/test/extensions/tracers/xray/localized_sampling_test.cc index 31b7530326720..d6056d7c6a3a0 100644 --- a/test/extensions/tracers/xray/localized_sampling_test.cc +++ b/test/extensions/tracers/xray/localized_sampling_test.cc @@ -33,6 +33,17 @@ TEST_F(LocalizedSamplingStrategyTest, BadJson) { ASSERT_TRUE(strategy.usingDefaultManifest()); } +TEST_F(LocalizedSamplingStrategyTest, EmptyRulesDefaultRate) { + NiceMock random_generator; + LocalizedSamplingStrategy strategy{"{{}", random_generator, time_system_}; + ASSERT_TRUE(strategy.usingDefaultManifest()); + // Make a copy of default_manifest_(LocalizedSamplingManifest object) since the + // object returned is a const reference and defaultRule() function is not a + // 'const member function' of LocalizedSamplingManifest class. + LocalizedSamplingManifest default_manifest_copy{strategy.defaultManifest()}; + ASSERT_EQ(default_manifest_copy.defaultRule().rate(), 0.05); +} + TEST_F(LocalizedSamplingStrategyTest, ValidCustomRules) { NiceMock random_generator; constexpr auto rules_json = R"EOF( From 048f3b85135ddcf1053924dda6b5a186bf471938 Mon Sep 17 00:00:00 2001 From: Alex Konradi Date: Thu, 22 Apr 2021 16:24:10 -0400 Subject: [PATCH 061/209] runtime: remove HCM stream error runtime override (#16040) * Remove HCM stream error runtime override. Remove envoy.reloadable_features.hcm_stream_error_on_invalid_message now deprecated Signed-off-by: Alex Konradi --- docs/root/version_history/current.rst | 1 + source/common/http/conn_manager_impl.cc | 4 +--- source/common/http/utility.cc | 8 ++------ source/common/runtime/runtime_features.cc | 1 - test/common/http/utility_test.cc | 12 +----------- test/integration/integration_test.cc | 6 +++--- 6 files changed, 8 insertions(+), 24 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 9a08d9a7e57a1..0fde709aaffee 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -27,6 +27,7 @@ Removed Config or Runtime ------------------------- *Normally occurs at the end of the* :ref:`deprecation period ` +* http: removed `envoy.reloadable_features.hcm_stream_error_on_invalid_message` for disabling closing HTTP/1.1 connections on error. Connection-closing can still be disabled by setting the HTTP/1 configuration :ref:`override_stream_error_on_invalid_http_message `. * http: removed `envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2`; Envoy will now always send GOAWAY to HTTP2 downstreams when the :ref:`disable_keepalive ` overload action is active. * tls: removed `envoy.reloadable_features.tls_use_io_handle_bio` runtime guard and legacy code path. diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 7a13c37bfa25e..3c5b9ae22f692 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1312,9 +1312,7 @@ absl::optional ConnectionManagerImpl::ActiveStream void ConnectionManagerImpl::ActiveStream::onLocalReply(Code code) { // The BadRequest error code indicates there has been a messaging error. - if (Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.hcm_stream_error_on_invalid_message") && - code == Http::Code::BadRequest && connection_manager_.codec_->protocol() < Protocol::Http2 && + if (code == Http::Code::BadRequest && connection_manager_.codec_->protocol() < Protocol::Http2 && !response_encoder_->streamErrorOnInvalidHttpMessage()) { state_.saw_connection_close_ = true; } diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index 2ee0b2f7fce34..435edbfdbe447 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -128,9 +128,7 @@ initializeAndValidateOptions(const envoy::config::core::v3::Http2ProtocolOptions bool hcm_stream_error_set, const Protobuf::BoolValue& hcm_stream_error) { auto ret = initializeAndValidateOptions(options); - if (Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.hcm_stream_error_on_invalid_message") && - !options.has_override_stream_error_on_invalid_http_message() && hcm_stream_error_set) { + if (!options.has_override_stream_error_on_invalid_http_message() && hcm_stream_error_set) { ret.mutable_override_stream_error_on_invalid_http_message()->set_value( hcm_stream_error.value()); } @@ -211,9 +209,7 @@ initializeAndValidateOptions(const envoy::config::core::v3::Http3ProtocolOptions return options; } envoy::config::core::v3::Http3ProtocolOptions options_clone(options); - if (Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.hcm_stream_error_on_invalid_message") && - hcm_stream_error_set) { + if (hcm_stream_error_set) { options_clone.mutable_override_stream_error_on_invalid_http_message()->set_value( hcm_stream_error.value()); } else { diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 2f444057c2b30..9280faf67a0f1 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -70,7 +70,6 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.grpc_web_fix_non_proto_encoded_response_handling", "envoy.reloadable_features.grpc_json_transcoder_adhere_to_buffer_limits", "envoy.reloadable_features.hash_multiple_header_values", - "envoy.reloadable_features.hcm_stream_error_on_invalid_message", "envoy.reloadable_features.health_check.graceful_goaway_handling", "envoy.reloadable_features.health_check.immediate_failure_exclude_from_cluster", "envoy.reloadable_features.http_match_on_all_headers", diff --git a/test/common/http/utility_test.cc b/test/common/http/utility_test.cc index 49cf81f341e27..52a23bad8a5a4 100644 --- a/test/common/http/utility_test.cc +++ b/test/common/http/utility_test.cc @@ -14,8 +14,8 @@ #include "common/network/address_impl.h" #include "test/mocks/http/mocks.h" +#include "test/mocks/protobuf/mocks.h" #include "test/test_common/printers.h" -#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -409,16 +409,6 @@ TEST(HttpUtility, ValidateStreamErrorsWithHcm) { EXPECT_TRUE(Envoy::Http2::Utility::initializeAndValidateOptions(http2_options, true, hcm_value) .override_stream_error_on_invalid_http_message() .value()); - - { - // With runtime flipped, override is ignored. - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.hcm_stream_error_on_invalid_message", "false"}}); - EXPECT_TRUE(Envoy::Http2::Utility::initializeAndValidateOptions(http2_options, true, hcm_value) - .override_stream_error_on_invalid_http_message() - .value()); - } } TEST(HttpUtility, ValidateStreamErrorConfigurationForHttp1) { diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index 5e394ee40f9a9..071e9e5ff06e2 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -964,9 +964,9 @@ TEST_P(IntegrationTest, PipelineWithTrailers) { // an inline sendLocalReply to make sure the "kick" works under the call stack // of dispatch as well as when a response is proxied from upstream. TEST_P(IntegrationTest, PipelineInline) { - // When deprecating this flag, set hcm.mutable_stream_error_on_invalid_http_message true. - config_helper_.addRuntimeOverride("envoy.reloadable_features.hcm_stream_error_on_invalid_message", - "false"); + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { hcm.mutable_stream_error_on_invalid_http_message()->set_value(true); }); autonomous_upstream_ = true; initialize(); From 15d71b0df1194763e78a9a5d48a59252edd64c97 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 23 Apr 2021 00:44:13 +0100 Subject: [PATCH 062/209] tooling: Add all pytests checker (#16031) another breakout from #15833 Signed-off-by: Ryan Northey --- ci/do_ci.sh | 9 +- tools/base/checker.py | 9 +- tools/base/runner.py | 52 ++++- tools/base/tests/test_checker.py | 45 ++-- tools/base/tests/test_runner.py | 159 +++++++++++-- tools/code_format/python_check.py | 2 +- tools/code_format/tests/test_python_check.py | 2 +- tools/testing/BUILD | 8 + tools/testing/all_pytests.py | 79 +++++++ tools/testing/tests/test_all_pytests.py | 231 +++++++++++++++++++ 10 files changed, 551 insertions(+), 45 deletions(-) create mode 100644 tools/testing/all_pytests.py create mode 100644 tools/testing/tests/test_all_pytests.py diff --git a/ci/do_ci.sh b/ci/do_ci.sh index ec81bb8c2e019..e832dd61ff31e 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -468,14 +468,7 @@ elif [[ "$CI_TARGET" == "cve_scan" ]]; then exit 0 elif [[ "$CI_TARGET" == "tooling" ]]; then echo "Run pytest tooling tests..." - bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/testing:pytest_python_pytest -- --cov-collect /tmp/.coverage-envoy - bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/testing:pytest_python_coverage -- --cov-collect /tmp/.coverage-envoy - bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/base:pytest_checker -- --cov-collect /tmp/.coverage-envoy - bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/base:pytest_runner -- --cov-collect /tmp/.coverage-envoy - bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/base:pytest_utils -- --cov-collect /tmp/.coverage-envoy - bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/code_format:pytest_python_check -- --cov-collect /tmp/.coverage-envoy - bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/dependency:pytest_pip_check -- --cov-collect /tmp/.coverage-envoy - bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/testing:python_coverage -- --fail-under=95 /tmp/.coverage-envoy /source/generated/tooling + bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/testing:all_pytests -- --cov-html /source/generated/tooling "${ENVOY_SRCDIR}" exit 0 elif [[ "$CI_TARGET" == "verify_examples" ]]; then run_ci_verify "*" wasm-cc diff --git a/tools/base/checker.py b/tools/base/checker.py index 6dc87f8d99ada..20d0cd003e734 100644 --- a/tools/base/checker.py +++ b/tools/base/checker.py @@ -222,11 +222,12 @@ def warn(self, name: str, warnings: list, log: bool = True) -> None: self.log.warning("\n".join(warnings)) -class ForkingChecker(Checker): +class ForkingChecker(runner.ForkingRunner, Checker): + pass - @cached_property - def fork(self): - return runner.ForkingAdapter(self) + +class BazelChecker(runner.BazelRunner, Checker): + pass class CheckerSummary(object): diff --git a/tools/base/runner.py b/tools/base/runner.py index be1bf1e8bec7c..31d7f62b85433 100644 --- a/tools/base/runner.py +++ b/tools/base/runner.py @@ -13,6 +13,10 @@ ("error", logging.ERROR)) +class BazelRunError(Exception): + pass + + class LogFilter(logging.Filter): def filter(self, rec): @@ -80,9 +84,53 @@ def __init__(self, context: Runner): self.context = context def __call__(self, *args, **kwargs) -> subprocess.CompletedProcess: - return self.fork(*args, **kwargs) + return self.subproc_run(*args, **kwargs) - def fork(self, *args, capture_output: bool = True, **kwargs) -> subprocess.CompletedProcess: + def subproc_run( + self, *args, capture_output: bool = True, **kwargs) -> subprocess.CompletedProcess: """Fork a subprocess, using self.context.path as the cwd by default""" kwargs["cwd"] = kwargs.get("cwd", self.context.path) return subprocess.run(*args, capture_output=capture_output, **kwargs) + + +class BazelAdapter(object): + + def __init__(self, context: "ForkingRunner"): + self.context = context + + def query(self, query: str) -> list: + """Run a bazel query and return stdout as list of lines""" + resp = self.context.subproc_run(["bazel", "query", f"'{query}'"]) + if resp.returncode: + raise BazelRunError(f"Bazel query failed: {resp}") + return resp.stdout.decode("utf-8").split("\n") + + def run( + self, + target: str, + *args, + capture_output: bool = False, + cwd: str = "", + raises: bool = True) -> subprocess.CompletedProcess: + """Run a bazel target and return the subprocess response""" + args = (("--",) + args) if args else args + bazel_args = ("bazel", "run", target) + args + resp = self.context.subproc_run( + bazel_args, capture_output=capture_output, cwd=cwd or self.context.path) + if resp.returncode and raises: + raise BazelRunError(f"Bazel run failed: {resp}") + return resp + + +class ForkingRunner(Runner): + + @cached_property + def subproc_run(self) -> ForkingAdapter: + return ForkingAdapter(self) + + +class BazelRunner(ForkingRunner): + + @cached_property + def bazel(self) -> BazelAdapter: + return BazelAdapter(self) diff --git a/tools/base/tests/test_checker.py b/tools/base/tests/test_checker.py index c20d68cfd300d..22cecd5be554a 100644 --- a/tools/base/tests/test_checker.py +++ b/tools/base/tests/test_checker.py @@ -2,7 +2,8 @@ import pytest -from tools.base.checker import Checker, CheckerSummary, ForkingChecker +from tools.base.checker import BazelChecker, Checker, CheckerSummary, ForkingChecker +from tools.base.runner import BazelRunner, ForkingRunner class DummyChecker(Checker): @@ -11,6 +12,18 @@ def __init__(self): self.args = PropertyMock() +class DummyForkingChecker(ForkingChecker): + + def __init__(self): + self.args = PropertyMock() + + +class DummyBazelChecker(BazelChecker): + + def __init__(self): + self.args = PropertyMock() + + class DummyCheckerWithChecks(Checker): checks = ("check1", "check2") @@ -497,20 +510,6 @@ def test_checker_succeed(patches, log, success): assert not m_log.return_value.info.called -# ForkingChecker tests - -def test_forkingchecker_fork(): - checker = ForkingChecker("path1", "path2", "path3") - forking_mock = patch("tools.base.checker.runner.ForkingAdapter") - - with forking_mock as m_fork: - assert checker.fork == m_fork.return_value - assert ( - list(m_fork.call_args) - == [(checker,), {}]) - assert "fork" in checker.__dict__ - - # CheckerSummary tests def test_checker_summary_constructor(): @@ -639,3 +638,19 @@ def _extra(prob): assert ( list(list(c) for c in m_section.call_args_list) == expected) + + +# ForkingChecker test + +def test_forkingchecker_constructor(): + checker = DummyForkingChecker() + assert isinstance(checker, ForkingRunner) + assert isinstance(checker, Checker) + + +# BazelChecker test + +def test_bazelchecker_constructor(): + checker = DummyBazelChecker() + assert isinstance(checker, BazelRunner) + assert isinstance(checker, Checker) diff --git a/tools/base/tests/test_runner.py b/tools/base/tests/test_runner.py index 497a68b32db54..db9102ea43e9e 100644 --- a/tools/base/tests/test_runner.py +++ b/tools/base/tests/test_runner.py @@ -19,6 +19,12 @@ def __init__(self): self.args = PropertyMock() +class DummyForkingRunner(runner.ForkingRunner): + + def __init__(self): + self.args = PropertyMock() + + def test_runner_constructor(): run = runner.Runner("path1", "path2", "path3") assert run._args == ("path1", "path2", "path3") @@ -71,8 +77,8 @@ def test_runner_log(patches): prefix="tools.base.runner") with patched as (m_logger, m_stream, m_filter, m_level, m_name): - _loggers = (MagicMock(), MagicMock()) - m_stream.side_effect = _loggers + loggers = (MagicMock(), MagicMock()) + m_stream.side_effect = loggers assert run.log == m_logger.return_value assert ( list(m_logger.return_value.setLevel.call_args) @@ -82,17 +88,17 @@ def test_runner_log(patches): == [[(sys.stdout,), {}], [(sys.stderr,), {}]]) assert ( - list(_loggers[0].setLevel.call_args) + list(loggers[0].setLevel.call_args) == [(logging.DEBUG,), {}]) assert ( - list(_loggers[0].addFilter.call_args) + list(loggers[0].addFilter.call_args) == [(m_filter.return_value,), {}]) assert ( - list(_loggers[1].setLevel.call_args) + list(loggers[1].setLevel.call_args) == [(logging.WARN,), {}]) assert ( list(list(c) for c in m_logger.return_value.addHandler.call_args_list) - == [[(_loggers[0],), {}], [(_loggers[1],), {}]]) + == [[(loggers[0],), {}], [(loggers[1],), {}]]) assert "log" in run.__dict__ @@ -169,18 +175,115 @@ class DummyRecord(object): assert not logfilter.filter(DummyRecord()) +# BazelAdapter tests + +def test_bazeladapter_constructor(): + run = DummyRunner() + adapter = runner.BazelAdapter(run) + assert adapter.context == run + + +@pytest.mark.parametrize("query_returns", [0, 1]) +def test_bazeladapter_query(query_returns): + run = DummyForkingRunner() + adapter = runner.BazelAdapter(run) + fork_mock = patch("tools.base.runner.ForkingAdapter.subproc_run") + + with fork_mock as m_fork: + m_fork.return_value.returncode = query_returns + if query_returns: + with pytest.raises(runner.BazelRunError) as result: + adapter.query("BAZEL QUERY") + else: + result = adapter.query("BAZEL QUERY") + + assert ( + list(m_fork.call_args) + == [(['bazel', 'query', "'BAZEL QUERY'"],), {}]) + + if query_returns: + assert result.errisinstance(runner.BazelRunError) + assert ( + result.value.args + == (f"Bazel query failed: {m_fork.return_value}",)) + assert not m_fork.return_value.stdout.decode.called + else: + assert ( + result + == m_fork.return_value.stdout.decode.return_value.split.return_value) + assert ( + list(m_fork.return_value.stdout.decode.call_args) + == [('utf-8',), {}]) + assert ( + list(m_fork.return_value.stdout.decode.return_value.split.call_args) + == [('\n',), {}]) + + +@pytest.mark.parametrize("cwd", [None, "", "SOMEPATH"]) +@pytest.mark.parametrize("raises", [None, True, False]) +@pytest.mark.parametrize("capture_output", [None, True, False]) +@pytest.mark.parametrize("run_returns", [0, 1]) +@pytest.mark.parametrize("args", [(), ("foo",), ("foo", "bar")]) +def test_bazeladapter_run(patches, run_returns, cwd, raises, args, capture_output): + run = DummyForkingRunner() + adapter = runner.BazelAdapter(run) + patched = patches( + "ForkingAdapter.subproc_run", + ("ForkingRunner.path", dict(new_callable=PropertyMock)), + prefix="tools.base.runner") + + adapter_args = ("BAZEL RUN",) + args + kwargs = {} + if raises is not None: + kwargs["raises"] = raises + if cwd is not None: + kwargs["cwd"] = cwd + if capture_output is not None: + kwargs["capture_output"] = capture_output + + with patched as (m_fork, m_path): + m_fork.return_value.returncode = run_returns + if run_returns and (raises is not False): + with pytest.raises(runner.BazelRunError) as result: + adapter.run(*adapter_args, **kwargs) + else: + result = adapter.run(*adapter_args, **kwargs) + + call_args = (("--",) + args) if args else args + bazel_args = ("bazel", "run", "BAZEL RUN") + call_args + bazel_kwargs = {} + bazel_kwargs["capture_output"] = ( + True + if capture_output is True + else False) + bazel_kwargs["cwd"] = ( + cwd + if cwd + else m_path.return_value) + assert ( + list(m_fork.call_args) + == [(bazel_args,), bazel_kwargs]) + if run_returns and (raises is not False): + assert result.errisinstance(runner.BazelRunError) + assert ( + result.value.args + == (f"Bazel run failed: {m_fork.return_value}",)) + else: + assert result == m_fork.return_value + + # ForkingAdapter tests def test_forkingadapter_constructor(): - _runner = DummyRunner() - adapter = runner.ForkingAdapter(_runner) - assert adapter.context == _runner + run = DummyRunner() + adapter = runner.ForkingAdapter(run) + assert adapter.context == run def test_forkingadapter_call(): - _runner = DummyRunner() - adapter = runner.ForkingAdapter(_runner) - fork_mock = patch("tools.base.runner.ForkingAdapter.fork") + run = DummyRunner() + adapter = runner.ForkingAdapter(run) + fork_mock = patch("tools.base.runner.ForkingAdapter.subproc_run") with fork_mock as m_fork: assert ( @@ -199,7 +302,7 @@ def test_forkingadapter_call(): @pytest.mark.parametrize("args", [(), ("a", "b")]) @pytest.mark.parametrize("cwd", [None, "NONE", "PATH"]) @pytest.mark.parametrize("capture_output", ["NONE", True, False]) -def test_forkingadapter_fork(patches, args, cwd, capture_output): +def test_forkingadapter_subproc_run(patches, args, cwd, capture_output): adapter = runner.ForkingAdapter(DummyRunner()) patched = patches( "subprocess.run", @@ -212,7 +315,7 @@ def test_forkingadapter_fork(patches, args, cwd, capture_output): kwargs["cwd"] = cwd if capture_output != "NONE": kwargs["capture_output"] = capture_output - assert adapter.fork(*args, **kwargs) == m_run.return_value + assert adapter.subproc_run(*args, **kwargs) == m_run.return_value expected = {'capture_output': True, 'cwd': cwd} if capture_output is False: @@ -222,3 +325,31 @@ def test_forkingadapter_fork(patches, args, cwd, capture_output): assert ( list(m_run.call_args) == [args, expected]) + + +# ForkingRunner tests + +def test_forkingrunner_fork(): + run = runner.ForkingRunner("path1", "path2", "path3") + forking_mock = patch("tools.base.runner.ForkingAdapter") + + with forking_mock as m_fork: + assert run.subproc_run == m_fork.return_value + assert ( + list(m_fork.call_args) + == [(run,), {}]) + assert "subproc_run" in run.__dict__ + + +# BazelRunner tests + +def test_bazelrunner_bazel(): + run = runner.BazelRunner("path1", "path2", "path3") + bazel_mock = patch("tools.base.runner.BazelAdapter") + + with bazel_mock as m_bazel: + assert run.bazel == m_bazel.return_value + assert ( + list(m_bazel.call_args) + == [(run,), {}]) + assert "bazel" in run.__dict__ diff --git a/tools/code_format/python_check.py b/tools/code_format/python_check.py index 3556d226b8889..9f4a556b62e8e 100755 --- a/tools/code_format/python_check.py +++ b/tools/code_format/python_check.py @@ -98,7 +98,7 @@ def on_check_run(self, check: str) -> None: def on_checks_complete(self) -> int: if self.diff_file_path and self.has_failed: - result = self.fork(["git", "diff", "HEAD"]) + result = self.subproc_run(["git", "diff", "HEAD"]) with open(self.diff_file_path, "wb") as f: f.write(result.stdout) return super().on_checks_complete() diff --git a/tools/code_format/tests/test_python_check.py b/tools/code_format/tests/test_python_check.py index 4bd8c9463ae46..0241c32fa45eb 100644 --- a/tools/code_format/tests/test_python_check.py +++ b/tools/code_format/tests/test_python_check.py @@ -239,7 +239,7 @@ def test_python_on_checks_complete(patches, results): diff_path, failed = results patched = patches( "open", - "checker.ForkingChecker.fork", + "checker.ForkingChecker.subproc_run", "checker.Checker.on_checks_complete", ("PythonChecker.diff_file_path", dict(new_callable=PropertyMock)), ("PythonChecker.has_failed", dict(new_callable=PropertyMock)), diff --git a/tools/testing/BUILD b/tools/testing/BUILD index 62fc0e6afca99..96351348a910c 100644 --- a/tools/testing/BUILD +++ b/tools/testing/BUILD @@ -40,3 +40,11 @@ envoy_py_binary( requirement("coverage"), ], ) + +envoy_py_binary( + name = "tools.testing.all_pytests", + deps = [ + "//tools/base:checker", + "//tools/base:utils", + ], +) diff --git a/tools/testing/all_pytests.py b/tools/testing/all_pytests.py new file mode 100644 index 0000000000000..058e2227a59ce --- /dev/null +++ b/tools/testing/all_pytests.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +# usage information: +# +# bazel run //tools/testing:all_pytests -- -h +# +# requires: bazel +# + +import os +import sys +from functools import cached_property + +from tools.base import checker, runner + + +class PytestChecker(checker.BazelChecker): + checks = ("pytests",) + + @property + def cov_enabled(self) -> bool: + return bool(self.args.cov_collect or self.args.cov_html) + + @property + def cov_html(self) -> str: + return self.args.cov_html + + @property + def cov_path(self): + return self.args.cov_collect or os.path.abspath(".coverage-envoy") + + @property + def pytest_bazel_args(self): + return ( + [f"--cov-collect", self.cov_path] + if self.cov_enabled + else []) + + @cached_property + def pytest_targets(self) -> set: + return set(target for target in self.bazel.query("tools/...") if ":pytest_" in target) + + def add_arguments(self, parser): + super().add_arguments(parser) + parser.add_argument( + "--cov-collect", + default=None, + help="Specify a path to collect coverage with") + parser.add_argument( + "--cov-html", + default=None, + help="Specify a path to collect html coverage with") + + def check_pytests(self) -> int: + for target in self.pytest_targets: + try: + self.bazel.run(target, *self.pytest_bazel_args) + self.succeed("pytest", [target]) + except runner.BazelRunError: + self.error("pytest", [f"{target} failed"]) + + def on_checks_begin(self): + if self.cov_path and os.path.exists(self.cov_path): + os.unlink(self.cov_path) + + def on_checks_complete(self): + if self.cov_html: + self.bazel.run( + "//tools/testing:python_coverage", + self.cov_path, self.cov_html) + return super().on_checks_complete() + + +def main(*args: list) -> None: + return PytestChecker(*args).run() + + +if __name__ == "__main__": + sys.exit(main(*sys.argv[1:])) diff --git a/tools/testing/tests/test_all_pytests.py b/tools/testing/tests/test_all_pytests.py new file mode 100644 index 0000000000000..a7272dd8b0ad5 --- /dev/null +++ b/tools/testing/tests/test_all_pytests.py @@ -0,0 +1,231 @@ + +from unittest.mock import patch, MagicMock, PropertyMock + +import pytest + +from tools.base.runner import BazelRunError +from tools.testing import all_pytests + + +def test_all_pytests_constructor(): + checker = all_pytests.PytestChecker("path1", "path2", "path3") + assert checker.checks == ("pytests",) + + +@pytest.mark.parametrize("cov_collect", ["", "somepath"]) +@pytest.mark.parametrize("cov_html", ["", "somepath"]) +def test_all_pytests_cov_enabled(cov_collect, cov_html): + checker = all_pytests.PytestChecker("path1", "path2", "path3") + args_mock = patch( + "tools.testing.all_pytests.PytestChecker.args", + new_callable=PropertyMock) + + with args_mock as m_args: + m_args.return_value.cov_collect = cov_collect + m_args.return_value.cov_html = cov_html + result = checker.cov_enabled + + if cov_collect or cov_html: + assert result is True + else: + assert result is False + assert "cov_enabled" not in checker.__dict__ + + +@pytest.mark.parametrize("cov_html", ["", "somepath"]) +def test_all_pytests_cov_html(cov_html): + checker = all_pytests.PytestChecker("path1", "path2", "path3") + args_mock = patch( + "tools.testing.all_pytests.PytestChecker.args", + new_callable=PropertyMock) + + with args_mock as m_args: + m_args.return_value.cov_html = cov_html + assert checker.cov_html == cov_html + assert "cov_html" not in checker.__dict__ + + +@pytest.mark.parametrize("cov_path", ["", "somepath"]) +def test_all_pytests_cov_path(patches, cov_path): + checker = all_pytests.PytestChecker("path1", "path2", "path3") + patched = patches( + "os.path.abspath", + ("PytestChecker.args", dict(new_callable=PropertyMock)), + prefix="tools.testing.all_pytests") + + with patched as (m_abspath, m_args): + m_args.return_value.cov_collect = cov_path + result = checker.cov_path + + if cov_path: + assert result == cov_path + assert not m_abspath.called + else: + assert result == m_abspath.return_value + assert ( + list(m_abspath.call_args) + == [('.coverage-envoy',), {}]) + assert "cov_path" not in checker.__dict__ + + +@pytest.mark.parametrize("cov_enabled", [True, False]) +def test_all_pytests_pytest_bazel_args(patches, cov_enabled): + checker = all_pytests.PytestChecker("path1", "path2", "path3") + patched = patches( + ("PytestChecker.cov_path", dict(new_callable=PropertyMock)), + ("PytestChecker.cov_enabled", dict(new_callable=PropertyMock)), + prefix="tools.testing.all_pytests") + + with patched as (m_path, m_enabled): + m_enabled.return_value = cov_enabled + result = checker.pytest_bazel_args + + if cov_enabled: + assert result == ['--cov-collect', m_path.return_value] + else: + assert result == [] + assert "pytest_bazel_args" not in checker.__dict__ + + +def test_all_pytests_pytest_targets(): + checker = all_pytests.PytestChecker("path1", "path2", "path3") + bazel_mock = patch("tools.testing.all_pytests.PytestChecker.bazel", new_callable=PropertyMock) + + with bazel_mock as m_bazel: + m_bazel.return_value.query.return_value = [ + "foo", ":pytest_foo", + ":notpytest_foo", ":not_foo", + "bar", "//asdf:pytest_barbaz"] + assert ( + checker.pytest_targets + == set([":pytest_foo", "//asdf:pytest_barbaz"])) + assert ( + list(m_bazel.return_value.query.call_args) + == [('tools/...',), {}]) + + +def test_all_pytests_add_arguments(): + checker = all_pytests.PytestChecker("path1", "path2", "path3") + parser = MagicMock() + super_mock = patch("tools.testing.all_pytests.checker.BazelChecker.add_arguments") + + with super_mock as m_super: + checker.add_arguments(parser) + + assert ( + list(m_super.call_args) + == [(parser,), {}]) + assert ( + list(list(c) for c in parser.add_argument.call_args_list) + == [[('--cov-collect',), + {'default': None, + 'help': 'Specify a path to collect coverage with'}], + [('--cov-html',), + {'default': None, + 'help': 'Specify a path to collect html coverage with'}]]) + + + + +def test_all_pytests_check_pytests(patches): + checker = all_pytests.PytestChecker("path1", "path2", "path3") + patched = patches( + "PytestChecker.error", + "PytestChecker.succeed", + ("PytestChecker.pytest_targets", dict(new_callable=PropertyMock)), + ("PytestChecker.bazel", dict(new_callable=PropertyMock)), + prefix="tools.testing.all_pytests") + + check_runs = dict( + check1=True, + check2=True, + check3=False, + check4=False, + check5=True, + check6=False, + check7=True) + + def _run_bazel(target): + if not check_runs[target]: + raise BazelRunError() + + with patched as (m_error, m_succeed, m_targets, m_bazel): + m_targets.return_value = check_runs.keys() + m_bazel.return_value.run.side_effect = _run_bazel + checker.check_pytests() + + assert ( + list(list(c) for c in m_bazel.return_value.run.call_args_list) + == [[('check1',), {}], + [('check2',), {}], + [('check3',), {}], + [('check4',), {}], + [('check5',), {}], + [('check6',), {}], + [('check7',), {}]]) + assert ( + list(list(c) for c in m_error.call_args_list) + == [[('pytest', ['check3 failed']), {}], + [('pytest', ['check4 failed']), {}], + [('pytest', ['check6 failed']), {}]]) + assert ( + list(list(c) for c in m_succeed.call_args_list) + == [[('pytest', ['check1']), {}], + [('pytest', ['check2']), {}], + [('pytest', ['check5']), {}], + [('pytest', ['check7']), {}]]) + + +@pytest.mark.parametrize("exists", [True, False]) +@pytest.mark.parametrize("cov_path", ["", "SOMEPATH"]) +def test_all_pytests_on_checks_begin(patches, exists, cov_path): + checker = all_pytests.PytestChecker("path1", "path2", "path3") + patched = patches( + "os.path.exists", + "os.unlink", + ("PytestChecker.cov_path", dict(new_callable=PropertyMock)), + prefix="tools.testing.all_pytests") + + with patched as (m_exists, m_unlink, m_cov_path): + m_cov_path.return_value = cov_path + m_exists.return_value = exists + checker.on_checks_begin() + + if cov_path and exists: + assert ( + list(m_unlink.call_args) + == [('SOMEPATH',), {}]) + else: + assert not m_unlink.called + + +@pytest.mark.parametrize("cov_html", ["", "SOMEPATH"]) +def test_all_pytests_on_checks_complete(patches, cov_html): + checker = all_pytests.PytestChecker("path1", "path2", "path3") + patched = patches( + ("PytestChecker.bazel", dict(new_callable=PropertyMock)), + "checker.Checker.on_checks_complete", + ("PytestChecker.cov_path", dict(new_callable=PropertyMock)), + ("PytestChecker.cov_html", dict(new_callable=PropertyMock)), + prefix="tools.testing.all_pytests") + + with patched as (m_bazel, m_complete, m_cov_path, m_cov_html): + m_cov_html.return_value = cov_html + assert checker.on_checks_complete() == m_complete.return_value + assert ( + list(m_complete.call_args) + == [(), {}]) + + if cov_html: + assert ( + list(m_bazel.return_value.run.call_args) + == [('//tools/testing:python_coverage', + m_cov_path.return_value, cov_html), {}]) + else: + assert not m_bazel.return_value.called + + +def test_coverage_main(command_main): + command_main( + all_pytests.main, + "tools.testing.all_pytests.PytestChecker") From c0c243e6171d47c6060b7c071408064fe0d58920 Mon Sep 17 00:00:00 2001 From: David Schinazi Date: Thu, 22 Apr 2021 17:07:09 -0700 Subject: [PATCH 063/209] Update QUICHE dependency (#16100) This PR updates our QUICHE dependency to commit 88c8d5903d851744410ea9840201b6507feae981. Signed-off-by: David Schinazi --- bazel/external/quiche.BUILD | 157 +++++++++--------- bazel/external/quiche.genrule_cmd | 2 +- bazel/repository_locations.bzl | 8 +- source/common/quic/BUILD | 8 +- source/common/quic/platform/BUILD | 1 - .../quic/platform/spdy_test_helpers_impl.h | 10 -- .../quic_filter_manager_connection_impl.cc | 1 - test/common/quic/BUILD | 4 +- test/common/quic/platform/BUILD | 5 +- ...pers_impl.h => quiche_test_helpers_impl.h} | 2 +- .../quic/platform/spdy_platform_test.cc | 63 ------- .../multiplexed_upstream_integration_test.cc | 15 +- test/integration/protocol_integration_test.cc | 2 - 13 files changed, 100 insertions(+), 178 deletions(-) delete mode 100644 source/common/quic/platform/spdy_test_helpers_impl.h rename test/common/quic/platform/{spdy_test_helpers_impl.h => quiche_test_helpers_impl.h} (84%) diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 7030b206df95c..54e123b0def28 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -86,6 +86,42 @@ envoy_cc_test_library( deps = [":http2_platform"], ) +envoy_cc_library( + name = "http2_core_http2_priority_write_scheduler_lib", + hdrs = ["quiche/http2/core/http2_priority_write_scheduler.h"], + copts = quiche_copts, + repository = "@envoy", + deps = [ + ":http2_core_write_scheduler_lib", + ":spdy_core_intrusive_list_lib", + ":spdy_core_protocol_lib", + ":spdy_platform", + ], +) + +envoy_cc_library( + name = "http2_core_priority_write_scheduler_lib", + hdrs = ["quiche/http2/core/priority_write_scheduler.h"], + copts = quiche_copts, + repository = "@envoy", + deps = [ + ":http2_core_write_scheduler_lib", + ":spdy_core_protocol_lib", + ":spdy_platform", + ], +) + +envoy_cc_library( + name = "http2_core_write_scheduler_lib", + hdrs = ["quiche/http2/core/write_scheduler.h"], + copts = quiche_copts, + repository = "@envoy", + deps = [ + ":quiche_common_platform_export", + ":spdy_core_protocol_lib", + ], +) + envoy_cc_library( name = "http2_platform", hdrs = [ @@ -741,12 +777,8 @@ envoy_cc_library( envoy_cc_library( name = "spdy_platform", hdrs = [ - "quiche/spdy/platform/api/spdy_bug_tracker.h", "quiche/spdy/platform/api/spdy_containers.h", "quiche/spdy/platform/api/spdy_estimate_memory_usage.h", - "quiche/spdy/platform/api/spdy_flag_utils.h", - "quiche/spdy/platform/api/spdy_flags.h", - "quiche/spdy/platform/api/spdy_logging.h", "quiche/spdy/platform/api/spdy_string_utils.h", ], repository = "@envoy", @@ -766,13 +798,6 @@ envoy_cc_library( deps = [":spdy_platform"], ) -envoy_cc_test_library( - name = "spdy_platform_test_helpers", - hdrs = ["quiche/spdy/platform/api/spdy_test_helpers.h"], - repository = "@envoy", - deps = ["@envoy//test/common/quic/platform:spdy_platform_test_helpers_impl_lib"], -) - envoy_cc_library( name = "spdy_core_alt_svc_wire_format_lib", srcs = ["quiche/spdy/core/spdy_alt_svc_wire_format.cc"], @@ -884,18 +909,6 @@ envoy_cc_library( repository = "@envoy", ) -envoy_cc_library( - name = "spdy_core_http2_priority_write_scheduler_lib", - hdrs = ["quiche/spdy/core/http2_priority_write_scheduler.h"], - repository = "@envoy", - deps = [ - ":spdy_core_intrusive_list_lib", - ":spdy_core_protocol_lib", - ":spdy_core_write_scheduler_lib", - ":spdy_platform", - ], -) - envoy_cc_library( name = "spdy_core_hpack_hpack_lib", srcs = [ @@ -943,18 +956,6 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "spdy_core_priority_write_scheduler_lib", - srcs = ["quiche/spdy/core/priority_write_scheduler.h"], - repository = "@envoy", - deps = [ - ":http2_platform", - ":spdy_core_protocol_lib", - ":spdy_core_write_scheduler_lib", - ":spdy_platform", - ], -) - envoy_cc_library( name = "spdy_core_protocol_lib", srcs = ["quiche/spdy/core/spdy_protocol.cc"], @@ -1072,6 +1073,16 @@ envoy_cc_library( ], ) +envoy_cc_test_library( + name = "quiche_common_platform_test_helpers_lib", + hdrs = ["quiche/common/platform/api/quiche_test_helpers.h"], + repository = "@envoy", + tags = ["nofips"], + deps = [ + "@envoy//test/common/quic/platform:quiche_common_platform_test_helpers_impl_lib", + ], +) + envoy_cc_test_library( name = "quic_platform_epoll_lib", hdrs = ["quiche/quic/platform/api/quic_epoll.h"], @@ -1251,6 +1262,7 @@ envoy_cc_test_library( srcs = ["quiche/common/test_tools/quiche_test_utils.cc"], hdrs = [ "quiche/common/platform/api/quiche_test.h", + "quiche/common/platform/api/quiche_test_helpers.h", "quiche/common/test_tools/quiche_test_utils.h", ], repository = "@envoy", @@ -1333,7 +1345,7 @@ envoy_cc_library( ) envoy_cc_library( - name = "quic_core_alarm_interface_lib", + name = "quic_core_alarm_lib", srcs = ["quiche/quic/core/quic_alarm.cc"], hdrs = ["quiche/quic/core/quic_alarm.h"], repository = "@envoy", @@ -1346,13 +1358,13 @@ envoy_cc_library( ) envoy_cc_library( - name = "quic_core_alarm_factory_interface_lib", + name = "quic_core_alarm_factory_lib", hdrs = ["quiche/quic/core/quic_alarm_factory.h"], repository = "@envoy", tags = ["nofips"], visibility = ["//visibility:public"], deps = [ - ":quic_core_alarm_interface_lib", + ":quic_core_alarm_lib", ":quic_core_one_block_arena_lib", ], ) @@ -1394,7 +1406,7 @@ envoy_cc_library( deps = [ ":quic_core_circular_deque_lib", ":quic_core_linux_socket_utils_lib", - ":quic_core_packet_writer_interface_lib", + ":quic_core_packet_writer_lib", ":quic_platform", ], ) @@ -1419,7 +1431,7 @@ envoy_cc_library( visibility = ["//visibility:public"], deps = [ ":quic_core_batch_writer_batch_writer_buffer_lib", - ":quic_core_packet_writer_interface_lib", + ":quic_core_packet_writer_lib", ":quic_core_types_lib", ":quic_platform", ], @@ -1822,8 +1834,8 @@ envoy_cc_library( repository = "@envoy", tags = ["nofips"], deps = [ - ":quic_core_alarm_factory_interface_lib", - ":quic_core_alarm_interface_lib", + ":quic_core_alarm_factory_lib", + ":quic_core_alarm_lib", ":quic_core_clock_lib", ":quic_core_frames_frames_lib", ":quic_core_interval_set_lib", @@ -1842,8 +1854,8 @@ envoy_cc_library( tags = ["nofips"], visibility = ["//visibility:public"], deps = [ - ":quic_core_alarm_factory_interface_lib", - ":quic_core_alarm_interface_lib", + ":quic_core_alarm_factory_lib", + ":quic_core_alarm_lib", ":quic_core_bandwidth_lib", ":quic_core_blocked_writer_interface_lib", ":quic_core_config_lib", @@ -1858,7 +1870,7 @@ envoy_cc_library( ":quic_core_network_blackhole_detector_lib", ":quic_core_one_block_arena_lib", ":quic_core_packet_creator_lib", - ":quic_core_packet_writer_interface_lib", + ":quic_core_packet_writer_lib", ":quic_core_packets_lib", ":quic_core_path_validator_lib", ":quic_core_proto_cached_network_parameters_proto_header", @@ -1964,7 +1976,7 @@ envoy_cc_library( ":quic_core_crypto_certificate_view_lib", ":quic_core_crypto_encryption_lib", ":quic_core_crypto_hkdf_lib", - ":quic_core_crypto_proof_source_interface_lib", + ":quic_core_crypto_proof_source_lib", ":quic_core_crypto_random_lib", ":quic_core_crypto_tls_handshake_lib", ":quic_core_data_lib", @@ -2092,7 +2104,7 @@ envoy_cc_library( ) envoy_cc_library( - name = "quic_core_crypto_proof_source_interface_lib", + name = "quic_core_crypto_proof_source_lib", srcs = [ "quiche/quic/core/crypto/proof_source.cc", "quiche/quic/core/crypto/quic_crypto_proof.cc", @@ -2142,7 +2154,7 @@ envoy_cc_library( repository = "@envoy", tags = ["nofips"], deps = [ - ":quic_core_crypto_proof_source_interface_lib", + ":quic_core_crypto_proof_source_lib", ":quic_core_types_lib", ":quic_platform_base", ], @@ -2337,7 +2349,7 @@ envoy_cc_library( tags = ["nofips"], visibility = ["//visibility:public"], deps = [ - ":quic_core_alarm_interface_lib", + ":quic_core_alarm_lib", ":quic_core_crypto_encryption_lib", ":quic_core_http_server_initiated_spdy_stream_lib", ":quic_core_http_spdy_session_lib", @@ -2493,7 +2505,7 @@ envoy_cc_library( ":quic_core_session_lib", ":quic_core_utils_lib", ":quic_core_versions_lib", - ":quic_core_web_transport_interface", + ":quic_core_web_transport_interface_lib", ":quic_core_web_transport_stream_adapter", ":quic_platform_base", ":quic_platform_mem_slice_storage", @@ -2542,8 +2554,8 @@ envoy_cc_library( repository = "@envoy", tags = ["nofips"], deps = [ - ":quic_core_alarm_factory_interface_lib", - ":quic_core_alarm_interface_lib", + ":quic_core_alarm_factory_lib", + ":quic_core_alarm_lib", ":quic_core_constants_lib", ":quic_core_one_block_arena_lib", ":quic_core_time_lib", @@ -2674,7 +2686,7 @@ envoy_cc_library( repository = "@envoy", tags = ["nofips"], deps = [ - ":quic_core_packet_writer_interface_lib", + ":quic_core_packet_writer_lib", ":quic_core_syscall_wrapper_lib", ":quic_core_types_lib", ":quic_platform", @@ -2689,8 +2701,8 @@ envoy_cc_library( tags = ["nofips"], visibility = ["//visibility:public"], deps = [ - ":quic_core_alarm_factory_interface_lib", - ":quic_core_alarm_interface_lib", + ":quic_core_alarm_factory_lib", + ":quic_core_alarm_lib", ":quic_core_constants_lib", ":quic_core_one_block_arena_lib", ":quic_core_time_lib", @@ -2734,7 +2746,7 @@ envoy_cc_library( ) envoy_cc_library( - name = "quic_core_packet_writer_interface_lib", + name = "quic_core_packet_writer_lib", srcs = ["quiche/quic/core/quic_packet_writer_wrapper.cc"], hdrs = [ "quiche/quic/core/quic_packet_writer.h", @@ -2765,6 +2777,7 @@ envoy_cc_library( repository = "@envoy", tags = ["nofips"], deps = [ + ":http2_core_priority_write_scheduler_lib", ":quic_core_ack_listener_interface_lib", ":quic_core_bandwidth_lib", ":quic_core_constants_lib", @@ -2776,8 +2789,6 @@ envoy_cc_library( ":quic_core_versions_lib", ":quic_platform", ":quic_platform_socket_address", - ":spdy_core_http2_priority_write_scheduler_lib", - ":spdy_core_priority_write_scheduler_lib", ], ) @@ -2789,14 +2800,14 @@ envoy_cc_library( repository = "@envoy", tags = ["nofips"], deps = [ - ":quic_core_alarm_factory_interface_lib", - ":quic_core_alarm_interface_lib", + ":quic_core_alarm_factory_lib", + ":quic_core_alarm_lib", ":quic_core_arena_scoped_ptr_lib", ":quic_core_clock_lib", ":quic_core_constants_lib", ":quic_core_crypto_random_lib", ":quic_core_one_block_arena_lib", - ":quic_core_packet_writer_interface_lib", + ":quic_core_packet_writer_lib", ":quic_core_types_lib", ":quic_platform", ], @@ -3162,7 +3173,7 @@ envoy_cc_library( ) envoy_cc_library( - name = "quic_core_web_transport_interface", + name = "quic_core_web_transport_interface_lib", hdrs = ["quiche/quic/core/web_transport_interface.h"], copts = quiche_copts, repository = "@envoy", @@ -3183,7 +3194,7 @@ envoy_cc_library( tags = ["nofips"], deps = [ ":quic_core_session_lib", - ":quic_core_web_transport_interface", + ":quic_core_web_transport_interface_lib", ], ) @@ -3217,8 +3228,8 @@ envoy_cc_library( tags = ["nofips"], visibility = ["//visibility:public"], deps = [ - ":quic_core_alarm_factory_interface_lib", - ":quic_core_alarm_interface_lib", + ":quic_core_alarm_factory_lib", + ":quic_core_alarm_lib", ":quic_core_blocked_writer_interface_lib", ":quic_core_connection_lib", ":quic_core_crypto_crypto_handshake_lib", @@ -3333,7 +3344,6 @@ envoy_cc_library( deps = [ ":quic_platform_base", ":quic_platform_socket_address", - ":spdy_core_priority_write_scheduler_lib", ], ) @@ -3440,7 +3450,7 @@ envoy_cc_library( ":quic_core_blocked_writer_interface_lib", ":quic_core_crypto_encryption_lib", ":quic_core_framer_lib", - ":quic_core_packet_writer_interface_lib", + ":quic_core_packet_writer_lib", ":quic_core_packets_lib", ":quic_core_session_lib", ":quic_core_types_lib", @@ -3636,7 +3646,7 @@ envoy_cc_test_library( ":quic_core_crypto_crypto_handshake_lib", ":quic_test_tools_mock_clock_lib", ":quic_test_tools_mock_random_lib", - ":quic_test_tools_test_utils_interface_lib", + ":quic_test_tools_test_utils_lib", ":quiche_common_platform", ], ) @@ -3657,12 +3667,12 @@ envoy_cc_test_library( ":quic_core_connection_lib", ":quic_core_crypto_crypto_handshake_lib", ":quic_core_http_client_lib", - ":quic_core_packet_writer_interface_lib", + ":quic_core_packet_writer_lib", ":quic_core_packets_lib", ":quic_core_types_lib", ":quic_core_versions_lib", ":quic_platform", - ":quic_test_tools_test_utils_interface_lib", + ":quic_test_tools_test_utils_lib", ], ) @@ -3864,7 +3874,7 @@ envoy_cc_test_library( ) envoy_cc_test_library( - name = "quic_test_tools_test_utils_interface_lib", + name = "quic_test_tools_test_utils_lib", srcs = [ "quiche/quic/test_tools/crypto_test_utils.cc", "quiche/quic/test_tools/mock_quic_session_visitor.cc", @@ -3894,14 +3904,14 @@ envoy_cc_test_library( ":quic_core_connection_stats_lib", ":quic_core_crypto_crypto_handshake_lib", ":quic_core_crypto_encryption_lib", - ":quic_core_crypto_proof_source_interface_lib", + ":quic_core_crypto_proof_source_lib", ":quic_core_crypto_random_lib", ":quic_core_data_lib", ":quic_core_framer_lib", ":quic_core_http_client_lib", ":quic_core_http_spdy_session_lib", ":quic_core_packet_creator_lib", - ":quic_core_packet_writer_interface_lib", + ":quic_core_packet_writer_lib", ":quic_core_packets_lib", ":quic_core_path_validator_lib", ":quic_core_received_packet_manager_lib", @@ -4077,7 +4087,6 @@ envoy_cc_test( envoy_cc_test( name = "http2_platform_api_test", srcs = [ - "quiche/http2/platform/api/http2_string_utils_test.cc", "quiche/http2/test_tools/http2_random_test.cc", ], repository = "@envoy", diff --git a/bazel/external/quiche.genrule_cmd b/bazel/external/quiche.genrule_cmd index 066c970476b67..b67f4dc74bdad 100644 --- a/bazel/external/quiche.genrule_cmd +++ b/bazel/external/quiche.genrule_cmd @@ -30,7 +30,7 @@ cat <sed_commands /^#include/ s!net/quic/platform/impl/quic_test_output_impl.h!test/common/quic/platform/quic_test_output_impl.h! /^#include/ s!net/quic/platform/impl/quic_thread_impl.h!test/common/quic/platform/quic_thread_impl.h! /^#include/ s!net/quiche/common/platform/impl/quiche_test_impl.h!test/common/quic/platform/quiche_test_impl.h! -/^#include/ s!net/spdy/platform/impl/spdy_test_helpers_impl.h!test/common/quic/platform/spdy_test_helpers_impl.h! +/^#include/ s!net/quiche/common/platform/impl/quiche_test_helpers_impl.h!test/common/quic/platform/quiche_test_helpers_impl.h! /^#include/ s!net/spdy/platform/impl/spdy_test_impl.h!test/common/quic/platform/spdy_test_impl.h! # Rewrite include directives for platform impl files. diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 00c5aacc059f8..d9127ffbea321 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -782,12 +782,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://quiche.googlesource.com/quiche", - # Static snapshot of https://quiche.googlesource.com/quiche/+archive/88c8d5903d851744410ea9840201b6507feae981.tar.gz - version = "88c8d5903d851744410ea9840201b6507feae981", - sha256 = "b254fa8f363be0637a468c14c3f57bf7aa2ae23b26a8d388ad0f31cb9863332d", + version = "6460972177446abe179ea430bf85b217c5ce240b", + sha256 = "5397ae241fc505e887203dc2c2f439549e42b1287687b155bcecac34536db434", + # Static snapshot of https://quiche.googlesource.com/quiche/+archive/{version}.tar.gz urls = ["https://storage.googleapis.com/quiche-envoy-integration/{version}.tar.gz"], use_category = ["dataplane_core"], - release_date = "2021-04-07", + release_date = "2021-04-21", cpe = "N/A", ), com_googlesource_googleurl = dict( diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index d0f3b48491c6b..d46b4b3bc28c8 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -20,7 +20,7 @@ envoy_cc_library( deps = [ "//include/envoy/event:dispatcher_interface", "//include/envoy/event:timer_interface", - "@com_googlesource_quiche//:quic_core_alarm_interface_lib", + "@com_googlesource_quiche//:quic_core_alarm_lib", "@com_googlesource_quiche//:quic_core_clock_lib", ], ) @@ -33,7 +33,7 @@ envoy_cc_library( tags = ["nofips"], deps = [ ":envoy_quic_alarm_lib", - "@com_googlesource_quiche//:quic_core_alarm_factory_interface_lib", + "@com_googlesource_quiche//:quic_core_alarm_factory_lib", "@com_googlesource_quiche//:quic_core_arena_scoped_ptr_lib", "@com_googlesource_quiche//:quic_core_one_block_arena_lib", ], @@ -61,7 +61,7 @@ envoy_cc_library( ":envoy_quic_utils_lib", "@com_googlesource_quiche//:quic_core_crypto_certificate_view_lib", "@com_googlesource_quiche//:quic_core_crypto_crypto_handshake_lib", - "@com_googlesource_quiche//:quic_core_crypto_proof_source_interface_lib", + "@com_googlesource_quiche//:quic_core_crypto_proof_source_lib", "@com_googlesource_quiche//:quic_core_data_lib", "@com_googlesource_quiche//:quic_core_versions_lib", ], @@ -402,7 +402,7 @@ envoy_cc_library( tags = ["nofips"], deps = [ ":envoy_quic_utils_lib", - "@com_googlesource_quiche//:quic_core_packet_writer_interface_lib", + "@com_googlesource_quiche//:quic_core_packet_writer_lib", ], ) diff --git a/source/common/quic/platform/BUILD b/source/common/quic/platform/BUILD index aa778e462c4a8..b704b29fd961d 100644 --- a/source/common/quic/platform/BUILD +++ b/source/common/quic/platform/BUILD @@ -263,7 +263,6 @@ envoy_cc_library( "spdy_containers_impl.h", "spdy_logging_impl.h", "spdy_string_utils_impl.h", - "spdy_test_helpers_impl.h", "spdy_test_utils_prod_impl.h", ], external_deps = [ diff --git a/source/common/quic/platform/spdy_test_helpers_impl.h b/source/common/quic/platform/spdy_test_helpers_impl.h deleted file mode 100644 index fa0676be84e3b..0000000000000 --- a/source/common/quic/platform/spdy_test_helpers_impl.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -// TODO: implement -#define EXPECT_SPDY_BUG_IMPL 0 diff --git a/source/common/quic/quic_filter_manager_connection_impl.cc b/source/common/quic/quic_filter_manager_connection_impl.cc index fe91c978e2f05..b58b0a4a25a53 100644 --- a/source/common/quic/quic_filter_manager_connection_impl.cc +++ b/source/common/quic/quic_filter_manager_connection_impl.cc @@ -87,7 +87,6 @@ void QuicFilterManagerConnectionImpl::close(Network::ConnectionCloseType type) { } else if (hasDataToWrite()) { // Quic connection has unsent data but caller wants to close right away. ASSERT(type == Network::ConnectionCloseType::NoFlush); - quic_connection_->OnCanWrite(); closeConnectionImmediately(); } else { // Quic connection doesn't have unsent data. It's up to the caller and diff --git a/test/common/quic/BUILD b/test/common/quic/BUILD index 567e93adce0e3..38d7fa11a2526 100644 --- a/test/common/quic/BUILD +++ b/test/common/quic/BUILD @@ -135,7 +135,7 @@ envoy_cc_test( "//test/test_common:simulated_time_system_lib", "@com_googlesource_quiche//:quic_test_tools_config_peer_lib", "@com_googlesource_quiche//:quic_test_tools_server_session_base_peer", - "@com_googlesource_quiche//:quic_test_tools_test_utils_interface_lib", + "@com_googlesource_quiche//:quic_test_tools_test_utils_lib", ], ) @@ -233,7 +233,7 @@ envoy_cc_test_library( deps = [ ":test_proof_source_lib", ":test_proof_verifier_lib", - "@com_googlesource_quiche//:quic_test_tools_test_utils_interface_lib", + "@com_googlesource_quiche//:quic_test_tools_test_utils_lib", ], ) diff --git a/test/common/quic/platform/BUILD b/test/common/quic/platform/BUILD index f2070b97934bc..56d9684c40343 100644 --- a/test/common/quic/platform/BUILD +++ b/test/common/quic/platform/BUILD @@ -70,7 +70,6 @@ envoy_cc_test( "//test/test_common:logging_lib", "//test/test_common:utility_lib", "@com_googlesource_quiche//:spdy_platform", - "@com_googlesource_quiche//:spdy_platform_test_helpers", ], ) @@ -198,8 +197,8 @@ envoy_cc_test_library( ) envoy_cc_test_library( - name = "spdy_platform_test_helpers_impl_lib", - hdrs = ["spdy_test_helpers_impl.h"], + name = "quiche_common_platform_test_helpers_impl_lib", + hdrs = ["quiche_test_helpers_impl.h"], deps = [ ":quic_platform_expect_bug_impl_lib", ], diff --git a/test/common/quic/platform/spdy_test_helpers_impl.h b/test/common/quic/platform/quiche_test_helpers_impl.h similarity index 84% rename from test/common/quic/platform/spdy_test_helpers_impl.h rename to test/common/quic/platform/quiche_test_helpers_impl.h index dc60146539328..faa89cbab1387 100644 --- a/test/common/quic/platform/spdy_test_helpers_impl.h +++ b/test/common/quic/platform/quiche_test_helpers_impl.h @@ -8,4 +8,4 @@ #include "test/common/quic/platform/quic_expect_bug_impl.h" -#define EXPECT_SPDY_BUG_IMPL EXPECT_QUIC_BUG_IMPL +#define EXPECT_QUICHE_BUG_IMPL EXPECT_QUIC_BUG_IMPL diff --git a/test/common/quic/platform/spdy_platform_test.cc b/test/common/quic/platform/spdy_platform_test.cc index 08a3f39f59c4d..d79e1d86f0ae2 100644 --- a/test/common/quic/platform/spdy_platform_test.cc +++ b/test/common/quic/platform/spdy_platform_test.cc @@ -6,12 +6,8 @@ #include "test/test_common/logging.h" #include "gtest/gtest.h" -#include "quiche/spdy/platform/api/spdy_bug_tracker.h" #include "quiche/spdy/platform/api/spdy_containers.h" #include "quiche/spdy/platform/api/spdy_estimate_memory_usage.h" -#include "quiche/spdy/platform/api/spdy_flags.h" -#include "quiche/spdy/platform/api/spdy_logging.h" -#include "quiche/spdy/platform/api/spdy_test_helpers.h" // Basic tests to validate functioning of the QUICHE spdy platform // implementation. For platform APIs in which the implementation is a simple @@ -25,71 +21,12 @@ namespace QuicListeners { namespace Quiche { namespace { -TEST(SpdyPlatformTest, SpdyBugTracker) { - EXPECT_DEBUG_DEATH(SPDY_BUG(bug_id) << "Here is a bug,", " bug"); - EXPECT_DEBUG_DEATH(SPDY_BUG_IF(bug_id, true) << "There is a bug,", " bug"); - EXPECT_LOG_NOT_CONTAINS("error", "", SPDY_BUG_IF(bug_id, false) << "A feature is not a bug."); - - EXPECT_EQ(true, FLAGS_spdy_always_log_bugs_for_tests); -} - TEST(SpdyPlatformTest, SpdyEstimateMemoryUsage) { std::string s = "foo"; // Stubbed out to always return 0. EXPECT_EQ(0, spdy::SpdyEstimateMemoryUsage(s)); } -TEST(SpdyPlatformTest, SpdyLog) { - // SPDY_LOG macros are defined to QUIC_LOG macros, which is tested in - // QuicPlatformTest. Here we just make sure SPDY_LOG macros compile. - SPDY_LOG(INFO) << "INFO log may not show up by default."; - SPDY_LOG(ERROR) << "ERROR log should show up by default."; - - // VLOG is only emitted if INFO is enabled and verbosity level is high enough. - SPDY_VLOG(1) << "VLOG(1)"; - - SPDY_DLOG(INFO) << "DLOG(INFO)"; - SPDY_DLOG(ERROR) << "DLOG(ERROR)"; - - SPDY_DLOG_IF(ERROR, true) << "DLOG_IF(ERROR, true)"; - SPDY_DLOG_IF(ERROR, false) << "DLOG_IF(ERROR, false)"; - - SPDY_DVLOG(2) << "DVLOG(2)"; - - SPDY_DVLOG_IF(3, true) << "DVLOG_IF(3, true)"; - SPDY_DVLOG_IF(4, false) << "DVLOG_IF(4, false)"; -} - -TEST(SpdyPlatformTest, SpdyString) { - std::string s = "foo"; - EXPECT_EQ('o', s[1]); -} - -TEST(SpdyPlatformTest, SpdyTestHelpers) { - auto bug = [](const char* error_message) { SPDY_BUG(bug_id) << error_message; }; - - EXPECT_SPDY_BUG(bug("bug one is expected"), "bug one"); - EXPECT_SPDY_BUG(bug("bug two is expected"), "bug two"); -} - -TEST(SpdyPlatformTest, SpdyFlags) { - auto& flag_registry = quiche::FlagRegistry::getInstance(); - flag_registry.resetFlags(); - EXPECT_FALSE(GetSpdyReloadableFlag(spdy_testonly_default_false)); - EXPECT_FALSE(GetSpdyRestartFlag(spdy_testonly_default_false)); - - flag_registry.findFlag("FLAGS_quic_reloadable_flag_spdy_testonly_default_false") - ->setValueFromString("true"); - EXPECT_TRUE(GetSpdyReloadableFlag(spdy_testonly_default_false)); - EXPECT_FALSE(GetSpdyRestartFlag(spdy_testonly_default_false)); - - flag_registry.resetFlags(); - flag_registry.findFlag("FLAGS_quic_restart_flag_spdy_testonly_default_false") - ->setValueFromString("yes"); - EXPECT_FALSE(GetSpdyReloadableFlag(spdy_testonly_default_false)); - EXPECT_TRUE(GetSpdyRestartFlag(spdy_testonly_default_false)); -} - } // namespace } // namespace Quiche } // namespace QuicListeners diff --git a/test/integration/multiplexed_upstream_integration_test.cc b/test/integration/multiplexed_upstream_integration_test.cc index de2ac3cde8817..0e1f51812e9ec 100644 --- a/test/integration/multiplexed_upstream_integration_test.cc +++ b/test/integration/multiplexed_upstream_integration_test.cc @@ -68,20 +68,11 @@ TEST_P(Http2UpstreamIntegrationTest, RouterUpstreamResponseBeforeRequestComplete testRouterUpstreamResponseBeforeRequestComplete(); } -TEST_P(Http2UpstreamIntegrationTest, Retry) { - EXCLUDE_UPSTREAM_HTTP3; // CHECK failed: max_plaintext_size_ (=18) >= PacketSize() (=20) - testRetry(); -} +TEST_P(Http2UpstreamIntegrationTest, Retry) { testRetry(); } -TEST_P(Http2UpstreamIntegrationTest, GrpcRetry) { - EXCLUDE_UPSTREAM_HTTP3; // CHECK failed: max_plaintext_size_ (=18) >= PacketSize() (=20) - testGrpcRetry(); -} +TEST_P(Http2UpstreamIntegrationTest, GrpcRetry) { testGrpcRetry(); } -TEST_P(Http2UpstreamIntegrationTest, Trailers) { - EXCLUDE_UPSTREAM_HTTP3; // CHECK failed: max_plaintext_size_ (=18) >= PacketSize() (=20) - testTrailers(1024, 2048, true, true); -} +TEST_P(Http2UpstreamIntegrationTest, Trailers) { testTrailers(1024, 2048, true, true); } TEST_P(Http2UpstreamIntegrationTest, TestSchemeAndXFP) { autonomous_upstream_ = true; diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 18be31fc32130..850c5c009494c 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -500,7 +500,6 @@ TEST_P(ProtocolIntegrationTest, LongHeaderValueWithSpaces) { } TEST_P(ProtocolIntegrationTest, Retry) { - EXCLUDE_UPSTREAM_HTTP3; // flakes with CHECK failed: (max_plaintext_size_) >= (PacketSize()). initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); auto response = codec_client_->makeRequestWithBody( @@ -552,7 +551,6 @@ TEST_P(ProtocolIntegrationTest, Retry) { } TEST_P(ProtocolIntegrationTest, RetryStreaming) { - EXCLUDE_UPSTREAM_HTTP3; // flakes with CHECK failed: (max_plaintext_size_) >= (PacketSize()). initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); auto encoder_decoder = From ed9403affc7efed9ef381e59a7a461de76e9f716 Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Fri, 23 Apr 2021 01:04:04 +0000 Subject: [PATCH 064/209] Add a missing work to WASM filter doc (#16106) Signed-off-by: Peter Jausovec --- docs/root/configuration/http/http_filters/wasm_filter.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/root/configuration/http/http_filters/wasm_filter.rst b/docs/root/configuration/http/http_filters/wasm_filter.rst index e0d683e83af87..ac3b6d2eb1326 100644 --- a/docs/root/configuration/http/http_filters/wasm_filter.rst +++ b/docs/root/configuration/http/http_filters/wasm_filter.rst @@ -10,7 +10,7 @@ Wasm The Wasm filter is experimental and is currently under active development. Capabilities will be expanded over time and the configuration structures are likely to change. -The HTTP Wasm filter is used implement an HTTP filter with a Wasm plugin. +The HTTP Wasm filter is used to implement an HTTP filter with a Wasm plugin. .. note:: From 8a2143613d43d17d1eb35a24b4a4a4c432215606 Mon Sep 17 00:00:00 2001 From: asraa Date: Thu, 22 Apr 2021 23:43:38 -0400 Subject: [PATCH 065/209] fix (#16135) Signed-off-by: Asra Ali Commit Message: Temporary CI fix before #16074 Risk Level: Low Testing: Docs Changes: Release Notes: Platform Specific Features: --- test/server/BUILD | 1 + test/server/config_validation/BUILD | 1 + 2 files changed, 2 insertions(+) diff --git a/test/server/BUILD b/test/server/BUILD index 555e845070923..7a591987be6b2 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -353,6 +353,7 @@ envoy_cc_fuzz_test( ] + select({ "//bazel:windows_x86_64": envoy_all_extensions(WINDOWS_SKIP_TARGETS), "//bazel:linux_ppc": envoy_all_extensions(PPC_SKIP_TARGETS), + "//bazel:gcc_build": [], "//conditions:default": envoy_all_extensions(), }), ) diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD index 9cc8548080e19..0f33ffe205e3a 100644 --- a/test/server/config_validation/BUILD +++ b/test/server/config_validation/BUILD @@ -90,6 +90,7 @@ envoy_cc_fuzz_test( ] + select({ "//bazel:windows_x86_64": envoy_all_extensions(WINDOWS_SKIP_TARGETS), "//bazel:linux_ppc": envoy_all_extensions(PPC_SKIP_TARGETS), + "//bazel:gcc_build": [], "//conditions:default": envoy_all_extensions(), }), ) From 8e1e0224eac5fd1c684cc8649a4db0488387eea8 Mon Sep 17 00:00:00 2001 From: Wayne Zhang Date: Fri, 23 Apr 2021 01:48:14 -0700 Subject: [PATCH 066/209] jwt_authn refactory: move threadlocal from JwksCache into JwksDataImpl (#16109) This change will not change the functionality, it just changes the internal implementation detail. JwKsCache holds both config and cache. Currently, the whole JwksCache object is in the thread local slot, but actually, only the cache data needs to be in in the thread local. This change will move the thread local data inside JwksDataImpl, only stores its {jwks, and expire} into thread local. Move JwksCache object out of thread local. This change is in preparation to support async fetch of remote_jwks proposed in https://github.com/envoyproxy/envoy/issues/14556#issuecomment-754203164. Detail changes: * Created `tls` for {jwks, expire} inside JwksDataImpl * Removed `tls` for JwksCache in FilterConfigImpl * Removed `enable_shared_from_this` from FilterConfigImpl Risk Level: Low Testing: Unit-tested. Docs Changes: None Release Notes: None Signed-off-by: Wayne Zhang --- .../filters/http/jwt_authn/filter_config.cc | 17 ++-- .../filters/http/jwt_authn/filter_config.h | 59 +++----------- .../filters/http/jwt_authn/filter_factory.cc | 2 +- .../filters/http/jwt_authn/jwks_cache.cc | 79 ++++++++++++------- .../filters/http/jwt_authn/jwks_cache.h | 3 +- test/extensions/filters/http/jwt_authn/BUILD | 1 + .../http/jwt_authn/all_verifier_test.cc | 2 +- .../http/jwt_authn/authenticator_test.cc | 7 +- .../http/jwt_authn/filter_config_test.cc | 14 ++-- .../filters/http/jwt_authn/jwks_cache_test.cc | 12 +-- .../http/jwt_authn/provider_verifier_test.cc | 4 +- 11 files changed, 92 insertions(+), 108 deletions(-) diff --git a/source/extensions/filters/http/jwt_authn/filter_config.cc b/source/extensions/filters/http/jwt_authn/filter_config.cc index 82d0fdd3ae425..a35ddf038824d 100644 --- a/source/extensions/filters/http/jwt_authn/filter_config.cc +++ b/source/extensions/filters/http/jwt_authn/filter_config.cc @@ -11,17 +11,16 @@ namespace Extensions { namespace HttpFilters { namespace JwtAuthn { -void FilterConfigImpl::init() { +FilterConfigImpl::FilterConfigImpl( + envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication proto_config, + const std::string& stats_prefix, Server::Configuration::FactoryContext& context) + : proto_config_(std::move(proto_config)), stats_(generateStats(stats_prefix, context.scope())), + cm_(context.clusterManager()), time_source_(context.dispatcher().timeSource()) { + ENVOY_LOG(debug, "Loaded JwtAuthConfig: {}", proto_config_.DebugString()); - // Note: `this` and `context` have a a lifetime of the listener. - // That may be shorter of the tls callback if the listener is torn shortly after it is created. - // We use a shared pointer to make sure this object outlives the tls callbacks. - auto shared_this = shared_from_this(); - tls_->set([shared_this](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(shared_this->proto_config_, shared_this->time_source_, - shared_this->api_); - }); + jwks_cache_ = + JwksCache::create(proto_config_, time_source_, context.api(), context.threadLocal()); std::vector names; for (const auto& it : proto_config_.requirement_map()) { diff --git a/source/extensions/filters/http/jwt_authn/filter_config.h b/source/extensions/filters/http/jwt_authn/filter_config.h index 9b6e64b7e6d79..d659dbf95135a 100644 --- a/source/extensions/filters/http/jwt_authn/filter_config.h +++ b/source/extensions/filters/http/jwt_authn/filter_config.h @@ -18,27 +18,6 @@ namespace Extensions { namespace HttpFilters { namespace JwtAuthn { -/** - * Making cache as a thread local object, its read/write operations don't need to be protected. - * Now it only has jwks_cache, but in the future, it will have token cache: to cache the tokens - * with their verification results. - */ -class ThreadLocalCache : public ThreadLocal::ThreadLocalObject { -public: - // Load the config from envoy config. - ThreadLocalCache(const envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication& config, - TimeSource& time_source, Api::Api& api) { - jwks_cache_ = JwksCache::create(config, time_source, api); - } - - // Get the JwksCache object. - JwksCache& getJwksCache() { return *jwks_cache_; } - -private: - // The JwksCache object. - JwksCachePtr jwks_cache_; -}; - /** * All stats for the Jwt Authn filter. @see stats_macros.h */ @@ -97,24 +76,15 @@ using FilterConfigSharedPtr = std::shared_ptr; */ class FilterConfigImpl : public Logger::Loggable, public FilterConfig, - public AuthFactory, - public std::enable_shared_from_this { + public AuthFactory { public: - ~FilterConfigImpl() override = default; + FilterConfigImpl(envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication proto_config, + const std::string& stats_prefix, Server::Configuration::FactoryContext& context); - // Finds the matcher that matched the header - static std::shared_ptr - create(envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication proto_config, - const std::string& stats_prefix, Server::Configuration::FactoryContext& context) { - // We can't use make_shared here because the constructor of this class is private. - std::shared_ptr ptr( - new FilterConfigImpl(proto_config, stats_prefix, context)); - ptr->init(); - return ptr; - } + ~FilterConfigImpl() override = default; - // Get per-thread cache object. - ThreadLocalCache& getCache() const { return tls_->getTyped(); } + // Get the JwksCache object. + JwksCache& getJwksCache() const { return *jwks_cache_; } Upstream::ClusterManager& cm() const { return cm_; } TimeSource& timeSource() const { return time_source_; } @@ -152,20 +122,10 @@ class FilterConfigImpl : public Logger::Loggable, const absl::optional& provider, bool allow_failed, bool allow_missing) const override { return Authenticator::create(check_audience, provider, allow_failed, allow_missing, - getCache().getJwksCache(), cm(), Common::JwksFetcher::create, - timeSource()); + getJwksCache(), cm(), Common::JwksFetcher::create, timeSource()); } private: - FilterConfigImpl(envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication proto_config, - const std::string& stats_prefix, Server::Configuration::FactoryContext& context) - : proto_config_(std::move(proto_config)), - stats_(generateStats(stats_prefix, context.scope())), - tls_(context.threadLocal().allocateSlot()), cm_(context.clusterManager()), - time_source_(context.dispatcher().timeSource()), api_(context.api()) {} - - void init(); - JwtAuthnFilterStats generateStats(const std::string& prefix, Stats::Scope& scope) { const std::string final_prefix = prefix + "jwt_authn."; return {ALL_JWT_AUTHN_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))}; @@ -182,8 +142,8 @@ class FilterConfigImpl : public Logger::Loggable, envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication proto_config_; // The stats for the filter. JwtAuthnFilterStats stats_; - // Thread local slot to store per-thread auth store - ThreadLocal::SlotPtr tls_; + // JwksCache + JwksCachePtr jwks_cache_; // the cluster manager object. Upstream::ClusterManager& cm_; // The list of rule matchers. @@ -197,7 +157,6 @@ class FilterConfigImpl : public Logger::Loggable, // all requirement_names for debug std::string all_requirement_names_; TimeSource& time_source_; - Api::Api& api_; }; } // namespace JwtAuthn diff --git a/source/extensions/filters/http/jwt_authn/filter_factory.cc b/source/extensions/filters/http/jwt_authn/filter_factory.cc index b99bab8132a3b..579571bd3d058 100644 --- a/source/extensions/filters/http/jwt_authn/filter_factory.cc +++ b/source/extensions/filters/http/jwt_authn/filter_factory.cc @@ -45,7 +45,7 @@ FilterFactory::createFilterFactoryFromProtoTyped(const JwtAuthentication& proto_ const std::string& prefix, Server::Configuration::FactoryContext& context) { validateJwtConfig(proto_config, context.api()); - auto filter_config = FilterConfigImpl::create(proto_config, prefix, context); + auto filter_config = std::make_shared(proto_config, prefix, context); return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { callbacks.addStreamDecoderFilter(std::make_shared(filter_config)); }; diff --git a/source/extensions/filters/http/jwt_authn/jwks_cache.cc b/source/extensions/filters/http/jwt_authn/jwks_cache.cc index 885e8d76eff5b..7204199fe3e59 100644 --- a/source/extensions/filters/http/jwt_authn/jwks_cache.cc +++ b/source/extensions/filters/http/jwt_authn/jwks_cache.cc @@ -26,25 +26,31 @@ namespace { // Default cache expiration time in 5 minutes. constexpr int PubkeyCacheExpirationSec = 600; +using JwksSharedPtr = std::shared_ptr<::google::jwt_verify::Jwks>; + class JwksDataImpl : public JwksCache::JwksData, public Logger::Loggable { public: - JwksDataImpl(const JwtProvider& jwt_provider, TimeSource& time_source, Api::Api& api) - : jwt_provider_(jwt_provider), time_source_(time_source) { + JwksDataImpl(const JwtProvider& jwt_provider, TimeSource& time_source, Api::Api& api, + ThreadLocal::SlotAllocator& tls) + : jwt_provider_(jwt_provider), time_source_(time_source), tls_(tls) { + std::vector audiences; for (const auto& aud : jwt_provider_.audiences()) { audiences.push_back(aud); } audiences_ = std::make_unique<::google::jwt_verify::CheckAudience>(audiences); + tls_.set([](Envoy::Event::Dispatcher&) { return std::make_shared(); }); + const auto inline_jwks = Config::DataSource::read(jwt_provider_.local_jwks(), true, api); if (!inline_jwks.empty()) { - auto ptr = setKey( - ::google::jwt_verify::Jwks::createFrom(inline_jwks, ::google::jwt_verify::Jwks::JWKS), - std::chrono::steady_clock::time_point::max()); - if (ptr->getStatus() != Status::Ok) { + auto jwks = + ::google::jwt_verify::Jwks::createFrom(inline_jwks, ::google::jwt_verify::Jwks::JWKS); + if (jwks->getStatus() != Status::Ok) { ENVOY_LOG(warn, "Invalid inline jwks for issuer: {}, jwks: {}", jwt_provider_.issuer(), inline_jwks); - jwks_obj_.reset(nullptr); + } else { + setJwksToAllThreads(std::move(jwks), std::chrono::steady_clock::time_point::max()); } } } @@ -55,15 +61,36 @@ class JwksDataImpl : public JwksCache::JwksData, public Logger::LoggableareAudiencesAllowed(jwt_audiences); } - const Jwks* getJwksObj() const override { return jwks_obj_.get(); } + const Jwks* getJwksObj() const override { return tls_->jwks_.get(); } - bool isExpired() const override { return time_source_.monotonicTime() >= expiration_time_; } + bool isExpired() const override { return time_source_.monotonicTime() >= tls_->expire_; } const ::google::jwt_verify::Jwks* setRemoteJwks(::google::jwt_verify::JwksPtr&& jwks) override { - return setKey(std::move(jwks), getRemoteJwksExpirationTime()); + // convert unique_ptr to shared_ptr + JwksSharedPtr shared_jwks(jwks.release()); + tls_->jwks_ = shared_jwks; + tls_->expire_ = getRemoteJwksExpirationTime(); + return shared_jwks.get(); } private: + struct ThreadLocalCache : public ThreadLocal::ThreadLocalObject { + // The jwks object. + JwksSharedPtr jwks_; + // The pubkey expiration time. + MonotonicTime expire_; + }; + + // Set jwks shared_ptr to all threads. + void setJwksToAllThreads(::google::jwt_verify::JwksPtr&& jwks, + std::chrono::steady_clock::time_point expire) { + JwksSharedPtr shared_jwks(jwks.release()); + tls_.runOnAllThreads([shared_jwks, expire](OptRef obj) { + obj->jwks_ = shared_jwks; + obj->expire_ = expire; + }); + } + // Get the expiration time for a remote Jwks std::chrono::steady_clock::time_point getRemoteJwksExpirationTime() const { auto expire = time_source_.monotonicTime(); @@ -76,34 +103,30 @@ class JwksDataImpl : public JwksCache::JwksData, public Logger::Loggable tls_; }; +using JwksDataImplPtr = std::unique_ptr; + class JwksCacheImpl : public JwksCache { public: // Load the config from envoy config. - JwksCacheImpl(const JwtAuthentication& config, TimeSource& time_source, Api::Api& api) { + JwksCacheImpl(const JwtAuthentication& config, TimeSource& time_source, Api::Api& api, + ThreadLocal::SlotAllocator& tls) { for (const auto& it : config.providers()) { const auto& provider = it.second; - jwks_data_map_.emplace(it.first, JwksDataImpl(provider, time_source, api)); + auto jwks_data = std::make_unique(provider, time_source, api, tls); if (issuer_ptr_map_.find(provider.issuer()) == issuer_ptr_map_.end()) { - issuer_ptr_map_.emplace(provider.issuer(), findByProvider(it.first)); + issuer_ptr_map_.emplace(provider.issuer(), jwks_data.get()); } + jwks_data_map_.emplace(it.first, std::move(jwks_data)); } } @@ -119,7 +142,7 @@ class JwksCacheImpl : public JwksCache { JwksData* findByProvider(const std::string& provider) override { const auto& it = jwks_data_map_.find(provider); if (it != jwks_data_map_.end()) { - return &it->second; + return it->second.get(); } // Verifier::innerCreate function makes sure that all provider names are defined. NOT_REACHED_GCOVR_EXCL_LINE; @@ -135,7 +158,7 @@ class JwksCacheImpl : public JwksCache { } // The Jwks data map indexed by provider. - absl::node_hash_map jwks_data_map_; + absl::node_hash_map jwks_data_map_; // The Jwks data pointer map indexed by issuer. absl::node_hash_map issuer_ptr_map_; }; @@ -144,8 +167,8 @@ class JwksCacheImpl : public JwksCache { JwksCachePtr JwksCache::create(const envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication& config, - TimeSource& time_source, Api::Api& api) { - return JwksCachePtr(new JwksCacheImpl(config, time_source, api)); + TimeSource& time_source, Api::Api& api, ThreadLocal::SlotAllocator& tls) { + return std::make_unique(config, time_source, api, tls); } } // namespace JwtAuthn diff --git a/source/extensions/filters/http/jwt_authn/jwks_cache.h b/source/extensions/filters/http/jwt_authn/jwks_cache.h index 2a4dbf1b5ab7e..e3b56348a7407 100644 --- a/source/extensions/filters/http/jwt_authn/jwks_cache.h +++ b/source/extensions/filters/http/jwt_authn/jwks_cache.h @@ -4,6 +4,7 @@ #include "envoy/common/pure.h" #include "envoy/common/time.h" #include "envoy/extensions/filters/http/jwt_authn/v3/config.pb.h" +#include "envoy/thread_local/thread_local.h" #include "jwt_verify_lib/jwks.h" @@ -69,7 +70,7 @@ class JwksCache { // Factory function to create an instance. static JwksCachePtr create(const envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication& config, - TimeSource& time_source, Api::Api& api); + TimeSource& time_source, Api::Api& api, ThreadLocal::SlotAllocator& tls); }; } // namespace JwtAuthn diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index d972cc18fc0e6..e750028c5816d 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -89,6 +89,7 @@ envoy_extension_cc_test( "//source/extensions/filters/http/common:jwks_fetcher_lib", "//source/extensions/filters/http/jwt_authn:jwks_cache_lib", "//test/extensions/filters/http/jwt_authn:test_common_lib", + "//test/mocks/thread_local:thread_local_mocks", "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/jwt_authn/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/jwt_authn/all_verifier_test.cc b/test/extensions/filters/http/jwt_authn/all_verifier_test.cc index ba198bb922a5f..55a4ed14cc67f 100644 --- a/test/extensions/filters/http/jwt_authn/all_verifier_test.cc +++ b/test/extensions/filters/http/jwt_authn/all_verifier_test.cc @@ -73,7 +73,7 @@ class AllVerifierTest : public testing::Test { } void createVerifier() { - filter_config_ = FilterConfigImpl::create(proto_config_, "", mock_factory_ctx_); + filter_config_ = std::make_shared(proto_config_, "", mock_factory_ctx_); verifier_ = Verifier::create(proto_config_.rules(0).requires(), proto_config_.providers(), *filter_config_); } diff --git a/test/extensions/filters/http/jwt_authn/authenticator_test.cc b/test/extensions/filters/http/jwt_authn/authenticator_test.cc index 69f297176450f..e45145eaea6cc 100644 --- a/test/extensions/filters/http/jwt_authn/authenticator_test.cc +++ b/test/extensions/filters/http/jwt_authn/authenticator_test.cc @@ -43,13 +43,12 @@ class AuthenticatorTest : public testing::Test { ::google::jwt_verify::CheckAudience* check_audience = nullptr, const absl::optional& provider = absl::make_optional(ProviderName), bool allow_failed = false, bool allow_missing = false) { - filter_config_ = FilterConfigImpl::create(proto_config_, "", mock_factory_ctx_); + filter_config_ = std::make_unique(proto_config_, "", mock_factory_ctx_); raw_fetcher_ = new MockJwksFetcher; fetcher_.reset(raw_fetcher_); auth_ = Authenticator::create( - check_audience, provider, allow_failed, allow_missing, - filter_config_->getCache().getJwksCache(), filter_config_->cm(), - [this](Upstream::ClusterManager&) { return std::move(fetcher_); }, + check_audience, provider, allow_failed, allow_missing, filter_config_->getJwksCache(), + filter_config_->cm(), [this](Upstream::ClusterManager&) { return std::move(fetcher_); }, filter_config_->timeSource()); jwks_ = Jwks::createFrom(PublicKey, Jwks::JWKS); EXPECT_TRUE(jwks_->getStatus() == Status::Ok); diff --git a/test/extensions/filters/http/jwt_authn/filter_config_test.cc b/test/extensions/filters/http/jwt_authn/filter_config_test.cc index 8406afd8c50d5..f221bc089f778 100644 --- a/test/extensions/filters/http/jwt_authn/filter_config_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_config_test.cc @@ -40,7 +40,7 @@ TEST(HttpJwtAuthnFilterConfigTest, FindByMatch) { TestUtility::loadFromYaml(config, proto_config); NiceMock context; - auto filter_conf = FilterConfigImpl::create(proto_config, "", context); + auto filter_conf = std::make_unique(proto_config, "", context); StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::FilterChain); EXPECT_TRUE(filter_conf->findVerifier( @@ -74,7 +74,7 @@ TEST(HttpJwtAuthnFilterConfigTest, FindByMatchDisabled) { TestUtility::loadFromYaml(config, proto_config); NiceMock context; - auto filter_conf = FilterConfigImpl::create(proto_config, "", context); + auto filter_conf = std::make_unique(proto_config, "", context); StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::FilterChain); EXPECT_TRUE(filter_conf->findVerifier( @@ -104,7 +104,7 @@ TEST(HttpJwtAuthnFilterConfigTest, FindByMatchWrongRequirementName) { TestUtility::loadFromYaml(config, proto_config); NiceMock context; - EXPECT_THROW_WITH_MESSAGE(FilterConfigImpl::create(proto_config, "", context), EnvoyException, + EXPECT_THROW_WITH_MESSAGE(FilterConfigImpl(proto_config, "", context), EnvoyException, "Wrong requirement_name: rr. It should be one of [r1]"); } @@ -137,7 +137,7 @@ TEST(HttpJwtAuthnFilterConfigTest, FindByMatchRequirementName) { TestUtility::loadFromYaml(config, proto_config); NiceMock context; - auto filter_conf = FilterConfigImpl::create(proto_config, "", context); + auto filter_conf = std::make_unique(proto_config, "", context); StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::FilterChain); EXPECT_TRUE(filter_conf->findVerifier( @@ -182,7 +182,7 @@ TEST(HttpJwtAuthnFilterConfigTest, VerifyTLSLifetime) { JwtAuthentication proto_config; TestUtility::loadFromYaml(config, proto_config); - auto filter_conf = FilterConfigImpl::create(proto_config, "", context); + auto filter_conf = std::make_unique(proto_config, "", context); } // Even though filter_conf is now de-allocated, using a reference to it might still work, as its @@ -222,7 +222,7 @@ TEST(HttpJwtAuthnFilterConfigTest, FindByFilterState) { TestUtility::loadFromYaml(config, proto_config); NiceMock context; - auto filter_conf = FilterConfigImpl::create(proto_config, "", context); + auto filter_conf = std::make_unique(proto_config, "", context); // Empty filter_state StreamInfo::FilterStateImpl filter_state1(StreamInfo::FilterState::LifeSpan::FilterChain); @@ -276,7 +276,7 @@ TEST(HttpJwtAuthnFilterConfigTest, FindByRequiremenMap) { TestUtility::loadFromYaml(config, proto_config); NiceMock context; - auto filter_conf = FilterConfigImpl::create(proto_config, "", context); + auto filter_conf = std::make_unique(proto_config, "", context); PerRouteConfig per_route; const Verifier* verifier; diff --git a/test/extensions/filters/http/jwt_authn/jwks_cache_test.cc b/test/extensions/filters/http/jwt_authn/jwks_cache_test.cc index 2c65688ffe7e1..a0fcdde765c15 100644 --- a/test/extensions/filters/http/jwt_authn/jwks_cache_test.cc +++ b/test/extensions/filters/http/jwt_authn/jwks_cache_test.cc @@ -9,6 +9,7 @@ #include "extensions/filters/http/jwt_authn/jwks_cache.h" #include "test/extensions/filters/http/jwt_authn/test_common.h" +#include "test/mocks/thread_local/mocks.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" @@ -31,7 +32,7 @@ class JwksCacheTest : public testing::Test { void setupCache(const std::string& config_str) { TestUtility::loadFromYaml(config_str, config_); - cache_ = JwksCache::create(config_, time_system_, *api_); + cache_ = JwksCache::create(config_, time_system_, *api_, tls_); } Event::SimulatedTimeSystem time_system_; @@ -39,6 +40,7 @@ class JwksCacheTest : public testing::Test { JwksCachePtr cache_; google::jwt_verify::JwksPtr jwks_; Api::ApiPtr api_; + ::testing::NiceMock tls_; }; // Test findByProvider @@ -82,7 +84,7 @@ TEST_F(JwksCacheTest, TestSetRemoteJwks) { auto& provider0 = (*config_.mutable_providers())[std::string(ProviderName)]; // Set cache_duration to 1 second to test expiration provider0.mutable_remote_jwks()->mutable_cache_duration()->set_seconds(1); - cache_ = JwksCache::create(config_, time_system_, *api_); + cache_ = JwksCache::create(config_, time_system_, *api_, tls_); auto jwks = cache_->findByIssuer("https://example.com"); EXPECT_TRUE(jwks->getJwksObj() == nullptr); @@ -101,7 +103,7 @@ TEST_F(JwksCacheTest, TestSetRemoteJwksWithDefaultCacheDuration) { auto& provider0 = (*config_.mutable_providers())[std::string(ProviderName)]; // Clear cache_duration to use default one. provider0.mutable_remote_jwks()->clear_cache_duration(); - cache_ = JwksCache::create(config_, time_system_, *api_); + cache_ = JwksCache::create(config_, time_system_, *api_, tls_); auto jwks = cache_->findByIssuer("https://example.com"); EXPECT_TRUE(jwks->getJwksObj() == nullptr); @@ -118,7 +120,7 @@ TEST_F(JwksCacheTest, TestGoodInlineJwks) { auto local_jwks = provider0.mutable_local_jwks(); local_jwks->set_inline_string(PublicKey); - cache_ = JwksCache::create(config_, time_system_, *api_); + cache_ = JwksCache::create(config_, time_system_, *api_, tls_); auto jwks = cache_->findByIssuer("https://example.com"); EXPECT_FALSE(jwks->getJwksObj() == nullptr); @@ -132,7 +134,7 @@ TEST_F(JwksCacheTest, TestBadInlineJwks) { auto local_jwks = provider0.mutable_local_jwks(); local_jwks->set_inline_string("BAD-JWKS"); - cache_ = JwksCache::create(config_, time_system_, *api_); + cache_ = JwksCache::create(config_, time_system_, *api_, tls_); auto jwks = cache_->findByIssuer("https://example.com"); EXPECT_TRUE(jwks->getJwksObj() == nullptr); diff --git a/test/extensions/filters/http/jwt_authn/provider_verifier_test.cc b/test/extensions/filters/http/jwt_authn/provider_verifier_test.cc index 2598fd40a19ae..becc7e3daa835 100644 --- a/test/extensions/filters/http/jwt_authn/provider_verifier_test.cc +++ b/test/extensions/filters/http/jwt_authn/provider_verifier_test.cc @@ -37,7 +37,7 @@ class ProviderVerifierTest : public testing::Test { } void createVerifier() { - filter_config_ = FilterConfigImpl::create(proto_config_, "", mock_factory_ctx_); + filter_config_ = std::make_unique(proto_config_, "", mock_factory_ctx_); verifier_ = Verifier::create(proto_config_.rules(0).requires(), proto_config_.providers(), *filter_config_); } @@ -181,7 +181,7 @@ TEST_F(ProviderVerifierTest, TestRequiresNonexistentProvider) { TestUtility::loadFromYaml(ExampleConfig, proto_config_); proto_config_.mutable_rules(0)->mutable_requires()->set_provider_name("nosuchprovider"); - EXPECT_THROW(FilterConfigImpl::create(proto_config_, "", mock_factory_ctx_), EnvoyException); + EXPECT_THROW(FilterConfigImpl(proto_config_, "", mock_factory_ctx_), EnvoyException); } } // namespace From f43a78e186f3fb8a7280079631b8b7b542f7dd65 Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Fri, 23 Apr 2021 15:57:09 -0400 Subject: [PATCH 067/209] Revert "honour routes timeout if max stream duration is set with out stream duration (#15585)" (#16133) This reverts commit ffa1680ae2619c8339733b9bd7071fc06f5179c9. Signed-off-by: Snow Pettersen --- source/common/http/conn_manager_impl.cc | 3 - test/common/http/conn_manager_impl_test.cc | 96 +------------------- test/common/http/conn_manager_impl_test_2.cc | 4 - 3 files changed, 1 insertion(+), 102 deletions(-) diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 3c5b9ae22f692..e85b4b07b3c4e 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1190,9 +1190,6 @@ void ConnectionManagerImpl::ActiveStream::refreshDurationTimeout() { const auto max_stream_duration = connection_manager_.config_.maxStreamDuration(); if (max_stream_duration.has_value() && max_stream_duration.value().count()) { timeout = max_stream_duration.value(); - } else if (route->timeout().count() != 0) { - // If max stream duration is not set either at route/HCM level, use the route timeout. - timeout = route->timeout(); } else { disable_timer = true; } diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index f173cce762049..1a071d2b38089 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -2191,10 +2191,7 @@ TEST_F(HttpConnectionManagerImplTest, NoPath) { // the default configuration aspects. TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutNotConfigured) { setup(false, ""); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, createTimer_(_)).Times(0); EXPECT_CALL(*codec_, dispatch(_)) .WillRepeatedly(Invoke([&](Buffer::Instance& data) -> Http::Status { @@ -2219,8 +2216,6 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutNotConfigured) { // headers, if it fires we don't faceplant. TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutGlobal) { stream_idle_timeout_ = std::chrono::milliseconds(10); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); setup(false, ""); EXPECT_CALL(*codec_, dispatch(_)).WillRepeatedly(Invoke([&](Buffer::Instance&) -> Http::Status { @@ -2364,8 +2359,6 @@ TEST_F(HttpConnectionManagerImplTest, DurationTimeout) { // Create the stream. EXPECT_CALL(*codec_, dispatch(_)) .WillRepeatedly(Invoke([&](Buffer::Instance& data) -> Http::Status { - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); Event::MockTimer* idle_timer = setUpTimer(); EXPECT_CALL(*idle_timer, enableTimer(_, _)); RequestDecoder* decoder = &conn_manager_->newStream(response_encoder_); @@ -2553,73 +2546,12 @@ TEST_F(HttpConnectionManagerImplTest, DurationTimeout) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } -// Test that verifies route timeout is used if if grpc timeout header is not set and -// max stream duration is not at route and HCM level. -// Regression test for https://github.com/envoyproxy/envoy/issues/15530. -// TODO(ramaraochavali): merge this with DurationTimeout test. -TEST_F(HttpConnectionManagerImplTest, DurationTimeoutUsesRouteTimeout) { - stream_idle_timeout_ = std::chrono::milliseconds(10); - setup(false, ""); - setupFilterChain(1, 0); - RequestHeaderMap* latched_headers = nullptr; - - EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, false)) - .WillOnce(Return(FilterHeadersStatus::StopIteration)); - - // Create the stream. - EXPECT_CALL(*codec_, dispatch(_)) - .WillRepeatedly(Invoke([&](Buffer::Instance& data) -> Http::Status { - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); - Event::MockTimer* idle_timer = setUpTimer(); - EXPECT_CALL(*idle_timer, enableTimer(_, _)); - RequestDecoder* decoder = &conn_manager_->newStream(response_encoder_); - EXPECT_CALL(*idle_timer, enableTimer(_, _)); - EXPECT_CALL(*idle_timer, disableTimer()); - RequestHeaderMapPtr headers{new TestRequestHeaderMapImpl{ - {":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; - latched_headers = headers.get(); - decoder->decodeHeaders(std::move(headers), false); - - data.drain(4); - return Http::okStatus(); - })); - Buffer::OwnedImpl fake_input("1234"); - conn_manager_->onData(fake_input, false); - - // Clear and refresh the route cache (checking clusterInfo refreshes the route cache) - decoder_filters_[0]->callbacks_->clearRouteCache(); - decoder_filters_[0]->callbacks_->clusterInfo(); - - Event::MockTimer* timer = setUpTimer(); - - // With no max stream duration, route timeout is used. - max_stream_duration_ = absl::nullopt; - EXPECT_CALL(*timer, enableTimer(std::chrono::milliseconds(22), _)); - EXPECT_CALL(route_config_provider_.route_config_->route_->route_entry_, maxStreamDuration()) - .Times(1) - .WillRepeatedly(Return(absl::nullopt)); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(22))); - decoder_filters_[0]->callbacks_->clearRouteCache(); - decoder_filters_[0]->callbacks_->clusterInfo(); - max_stream_duration_ = absl::nullopt; - - // Cleanup. - EXPECT_CALL(*timer, disableTimer()); - EXPECT_CALL(*decoder_filters_[0], onStreamComplete()); - EXPECT_CALL(*decoder_filters_[0], onDestroy()); - filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); -} - // Per-route timeouts override the global stream idle timeout. TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutRouteOverride) { stream_idle_timeout_ = std::chrono::milliseconds(10); setup(false, ""); ON_CALL(route_config_provider_.route_config_->route_->route_entry_, idleTimeout()) .WillByDefault(Return(std::chrono::milliseconds(30))); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); EXPECT_CALL(*codec_, dispatch(_)) .WillRepeatedly(Invoke([&](Buffer::Instance& data) -> Http::Status { @@ -2650,8 +2582,6 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutRouteZeroOverride) { setup(false, ""); ON_CALL(route_config_provider_.route_config_->route_->route_entry_, idleTimeout()) .WillByDefault(Return(std::chrono::milliseconds(0))); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); EXPECT_CALL(*codec_, dispatch(_)) .WillRepeatedly(Invoke([&](Buffer::Instance& data) -> Http::Status { @@ -2681,8 +2611,6 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterDownstreamHeaders setup(false, ""); ON_CALL(route_config_provider_.route_config_->route_->route_entry_, idleTimeout()) .WillByDefault(Return(std::chrono::milliseconds(10))); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); // Codec sends downstream request headers. EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { @@ -2724,8 +2652,6 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutNormalTermination) { setup(false, ""); ON_CALL(route_config_provider_.route_config_->route_->route_entry_, idleTimeout()) .WillByDefault(Return(std::chrono::milliseconds(10))); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); // Codec sends downstream request headers. Event::MockTimer* idle_timer = setUpTimer(); @@ -2756,8 +2682,6 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterDownstreamHeaders setup(false, ""); ON_CALL(route_config_provider_.route_config_->route_->route_entry_, idleTimeout()) .WillByDefault(Return(std::chrono::milliseconds(10))); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); // Codec sends downstream request headers. EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { @@ -2802,8 +2726,6 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterUpstreamHeaders) setup(false, ""); ON_CALL(route_config_provider_.route_config_->route_->route_entry_, idleTimeout()) .WillByDefault(Return(std::chrono::milliseconds(10))); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); // Store the basic request encoder during filter chain setup. std::shared_ptr filter(new NiceMock()); @@ -2853,8 +2775,6 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterBidiData) { setup(false, ""); ON_CALL(route_config_provider_.route_config_->route_->route_entry_, idleTimeout()) .WillByDefault(Return(std::chrono::milliseconds(10))); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); proxy_100_continue_ = true; // Store the basic request encoder during filter chain setup. @@ -3006,8 +2926,6 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutCallbackDisarmsAndReturns408 TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsNotDisarmedOnIncompleteRequestWithHeader) { request_timeout_ = std::chrono::milliseconds(10); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); setup(false, ""); EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> Http::Status { @@ -3035,8 +2953,6 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsNotDisarmedOnIncompleteReq TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnCompleteRequestWithHeader) { request_timeout_ = std::chrono::milliseconds(10); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); setup(false, ""); EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> Http::Status { @@ -3063,8 +2979,6 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnCompleteRequestW TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnCompleteRequestWithData) { request_timeout_ = std::chrono::milliseconds(10); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); setup(false, ""); EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { @@ -3092,8 +3006,6 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnCompleteRequestW TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnCompleteRequestWithTrailers) { request_timeout_ = std::chrono::milliseconds(10); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); setup(false, ""); EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { @@ -3123,8 +3035,6 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnCompleteRequestW TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnEncodeHeaders) { request_timeout_ = std::chrono::milliseconds(10); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); setup(false, ""); std::shared_ptr filter(new NiceMock()); EXPECT_CALL(filter_factory_, createFilterChain(_)) @@ -3160,8 +3070,6 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnEncodeHeaders) { TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnConnectionTermination) { request_timeout_ = std::chrono::milliseconds(10); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); setup(false, ""); Event::MockTimer* request_timer = setUpTimer(); @@ -3188,8 +3096,6 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnConnectionTermin TEST_F(HttpConnectionManagerImplTest, RequestHeaderTimeoutDisarmedAfterHeaders) { request_headers_timeout_ = std::chrono::milliseconds(10); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); setup(false, ""); Event::MockTimer* request_header_timer; diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index 83bda9907062a..d837d4d9fbadd 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -272,8 +272,6 @@ TEST_F(HttpConnectionManagerImplTest, IdleTimeoutNoCodec) { TEST_F(HttpConnectionManagerImplTest, IdleTimeout) { idle_timeout_ = (std::chrono::milliseconds(10)); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); Event::MockTimer* idle_timer = setUpTimer(); EXPECT_CALL(*idle_timer, enableTimer(_, _)); setup(false, ""); @@ -352,8 +350,6 @@ TEST_F(HttpConnectionManagerImplTest, ConnectionDurationNoCodec) { TEST_F(HttpConnectionManagerImplTest, ConnectionDuration) { max_connection_duration_ = (std::chrono::milliseconds(10)); - ON_CALL(route_config_provider_.route_config_->route_->route_entry_, timeout()) - .WillByDefault(Return(std::chrono::milliseconds(0))); Event::MockTimer* connection_duration_timer = setUpTimer(); EXPECT_CALL(*connection_duration_timer, enableTimer(_, _)); setup(false, ""); From d50749c4e22fa514a1acdb2e6e291beea808b916 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 23 Apr 2021 21:00:36 +0100 Subject: [PATCH 068/209] dependabot: Resolve updates (#16094) aggregate dependabot updates and use pip-compile to resolve dependency hashes Signed-off-by: Ryan Northey Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 171 ++++++++++++++++++----- tools/code_format/requirements.txt | 6 +- tools/deprecate_version/requirements.txt | 75 +++++++--- 3 files changed, 200 insertions(+), 52 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 758ba5716dbc9..611d0203b3bf0 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,34 +1,69 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile --generate-hashes docs/requirements.txt +# alabaster==0.7.12 \ --hash=sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359 \ --hash=sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02 -Babel==2.9.0 \ + # via + # -r docs/requirements.txt + # sphinx +babel==2.9.0 \ --hash=sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5 \ --hash=sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05 + # via + # -r docs/requirements.txt + # sphinx certifi==2020.12.5 \ - --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 \ - --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c + --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c \ + --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 + # via + # -r docs/requirements.txt + # requests chardet==4.0.0 \ - --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \ - --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa + --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \ + --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 + # via + # -r docs/requirements.txt + # requests docutils==0.16 \ --hash=sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af \ --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc + # via + # -r docs/requirements.txt + # sphinx + # sphinx-rtd-theme gitdb==4.0.7 \ --hash=sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0 \ --hash=sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005 -GitPython==3.1.14 \ - --hash=sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b \ - --hash=sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61 + # via + # -r docs/requirements.txt + # gitpython +gitpython==3.1.15 \ + --hash=sha256:05af150f47a5cca3f4b0af289b73aef8cf3c4fe2385015b06220cbcdee48bb6e \ + --hash=sha256:a77824e516d3298b04fb36ec7845e92747df8fcfee9cacc32dd6239f9652f867 + # via -r docs/requirements.txt idna==2.10 \ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 + # via + # -r docs/requirements.txt + # requests imagesize==1.2.0 \ --hash=sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1 \ --hash=sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1 -Jinja2==2.11.3 \ + # via + # -r docs/requirements.txt + # sphinx +jinja2==2.11.3 \ --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 -MarkupSafe==1.1.1 \ + # via + # -r docs/requirements.txt + # sphinx +markupsafe==1.1.1 \ --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ @@ -62,66 +97,136 @@ MarkupSafe==1.1.1 \ --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be + # via + # -r docs/requirements.txt + # jinja2 packaging==20.9 \ - --hash=sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a \ - --hash=sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5 -Pygments==2.8.1 \ - --hash=sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8 \ - --hash=sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94 + --hash=sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5 \ + --hash=sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a + # via + # -r docs/requirements.txt + # sphinx +pygments==2.8.1 \ + --hash=sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94 \ + --hash=sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8 + # via + # -r docs/requirements.txt + # sphinx + # sphinx-tabs pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b + # via + # -r docs/requirements.txt + # packaging pytz==2021.1 \ - --hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798 \ - --hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da + --hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da \ + --hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798 + # via + # -r docs/requirements.txt + # babel requests==2.25.1 \ - --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e \ - --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 + --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \ + --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e + # via + # -r docs/requirements.txt + # sphinx six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced + # via + # -r docs/requirements.txt + # sphinxcontrib-httpdomain smmap==4.0.0 \ - --hash=sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2 \ - --hash=sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182 + --hash=sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182 \ + --hash=sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2 + # via + # -r docs/requirements.txt + # gitdb snowballstemmer==2.1.0 \ --hash=sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2 \ --hash=sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914 -Sphinx==3.5.4 \ - --hash=sha256:2320d4e994a191f4b4be27da514e46b3d6b420f2ff895d064f52415d342461e8 \ - --hash=sha256:19010b7b9fa0dc7756a6e105b2aacd3a80f798af3c25c273be64d7beeb482cb1 + # via + # -r docs/requirements.txt + # sphinx sphinx-copybutton==0.3.1 \ - --hash=sha256:5125c718e763596e6e52d92e15ee0d6f4800ad3817939be6dee51218870b3e3d \ - --hash=sha256:0e0461df394515284e3907e3f418a0c60ef6ab6c9a27a800c8552772d0a402a2 + --hash=sha256:0e0461df394515284e3907e3f418a0c60ef6ab6c9a27a800c8552772d0a402a2 \ + --hash=sha256:5125c718e763596e6e52d92e15ee0d6f4800ad3817939be6dee51218870b3e3d + # via -r docs/requirements.txt sphinx-rtd-theme==0.5.2 \ - --hash=sha256:4a05bdbe8b1446d77a01e20a23ebc6777c74f43237035e76be89699308987d6f \ - --hash=sha256:32bd3b5d13dc8186d7a42fc816a23d32e83a4827d7d9882948e7b837c232da5a + --hash=sha256:32bd3b5d13dc8186d7a42fc816a23d32e83a4827d7d9882948e7b837c232da5a \ + --hash=sha256:4a05bdbe8b1446d77a01e20a23ebc6777c74f43237035e76be89699308987d6f + # via -r docs/requirements.txt sphinx-tabs==2.1.0 \ - --hash=sha256:9d8db61afe9499aa84b33bc1c0d891f8a0df88ae513739f5babde15430c1fdaf \ - --hash=sha256:0b5a8dd71d87197a01eef3b9d1e1a70513f4dd45e8af7783d1ab74c3fb2cbc6c + --hash=sha256:0b5a8dd71d87197a01eef3b9d1e1a70513f4dd45e8af7783d1ab74c3fb2cbc6c \ + --hash=sha256:9d8db61afe9499aa84b33bc1c0d891f8a0df88ae513739f5babde15430c1fdaf + # via -r docs/requirements.txt +sphinx==3.5.4 \ + --hash=sha256:19010b7b9fa0dc7756a6e105b2aacd3a80f798af3c25c273be64d7beeb482cb1 \ + --hash=sha256:2320d4e994a191f4b4be27da514e46b3d6b420f2ff895d064f52415d342461e8 + # via + # -r docs/requirements.txt + # sphinx-copybutton + # sphinx-rtd-theme + # sphinx-tabs + # sphinxcontrib-httpdomain + # sphinxext-rediraffe sphinxcontrib-applehelp==1.0.2 \ --hash=sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a \ --hash=sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58 + # via + # -r docs/requirements.txt + # sphinx sphinxcontrib-devhelp==1.0.2 \ --hash=sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e \ --hash=sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4 + # via + # -r docs/requirements.txt + # sphinx sphinxcontrib-htmlhelp==1.0.3 \ --hash=sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f \ --hash=sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b + # via + # -r docs/requirements.txt + # sphinx sphinxcontrib-httpdomain==1.7.0 \ --hash=sha256:1fb5375007d70bf180cdd1c79e741082be7aa2d37ba99efe561e1c2e3f38191e \ --hash=sha256:ac40b4fba58c76b073b03931c7b8ead611066a6aebccafb34dc19694f4eb6335 + # via -r docs/requirements.txt sphinxcontrib-jsmath==1.0.1 \ --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \ --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8 + # via + # -r docs/requirements.txt + # sphinx sphinxcontrib-qthelp==1.0.3 \ --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 \ --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6 -sphinxext-rediraffe==0.2.7 \ - --hash=sha256:9e430a52d4403847f4ffb3a8dd6dfc34a9fe43525305131f52ed899743a5fd8c \ - --hash=sha256:651dcbfae5ffda9ffd534dfb8025f36120e5efb6ea1a33f5420023862b9f725d + # via + # -r docs/requirements.txt + # sphinx sphinxcontrib-serializinghtml==1.1.4 \ --hash=sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc \ --hash=sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a + # via + # -r docs/requirements.txt + # sphinx +sphinxext-rediraffe==0.2.7 \ + --hash=sha256:651dcbfae5ffda9ffd534dfb8025f36120e5efb6ea1a33f5420023862b9f725d \ + --hash=sha256:9e430a52d4403847f4ffb3a8dd6dfc34a9fe43525305131f52ed899743a5fd8c + # via -r docs/requirements.txt +typing-extensions==3.7.4.3 \ + --hash=sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918 \ + --hash=sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c \ + --hash=sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f + # via gitpython urllib3==1.26.4 \ --hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \ --hash=sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937 + # via + # -r docs/requirements.txt + # requests + +# WARNING: The following packages were not pinned, but pip requires them to be +# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag. +# setuptools diff --git a/tools/code_format/requirements.txt b/tools/code_format/requirements.txt index 06cfb439f0a9b..5fce0f0c10e10 100644 --- a/tools/code_format/requirements.txt +++ b/tools/code_format/requirements.txt @@ -1,9 +1,9 @@ flake8==3.9.1 \ --hash=sha256:3b9f848952dddccf635be78098ca75010f073bfe14d2c6bda867154bea728d2a \ --hash=sha256:1aa8990be1e689d96c745c5682b687ea49f2e05a443aff1f8251092b0014e378 -importlib-metadata==4.0.0 \ - --hash=sha256:19192b88d959336bfa6bdaaaef99aeafec179eca19c47c804e555703ee5f07ef \ - --hash=sha256:2e881981c9748d7282b374b68e759c87745c25427b67ecf0cc67fb6637a1bff9 +importlib-metadata==4.0.1 \ + --hash=sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d \ + --hash=sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581 mccabe==0.6.1 \ --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f diff --git a/tools/deprecate_version/requirements.txt b/tools/deprecate_version/requirements.txt index 251c6494173c9..c374c121ce1eb 100644 --- a/tools/deprecate_version/requirements.txt +++ b/tools/deprecate_version/requirements.txt @@ -1,35 +1,78 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile --generate-hashes tools/deprecate_version/requirements.txt +# certifi==2020.12.5 \ - --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 \ - --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c + --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c \ + --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 + # via + # -r tools/deprecate_version/requirements.txt + # requests chardet==4.0.0 \ - --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \ - --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa -Deprecated==1.2.12 \ + --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \ + --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 + # via + # -r tools/deprecate_version/requirements.txt + # requests +deprecated==1.2.12 \ --hash=sha256:08452d69b6b5bc66e8330adde0a4f8642e969b9e1702904d137eeb29c8ffc771 \ --hash=sha256:6d2de2de7931a968874481ef30208fd4e08da39177d61d3d4ebdf4366e7dbca1 + # via + # -r tools/deprecate_version/requirements.txt + # pygithub gitdb==4.0.7 \ --hash=sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0 \ --hash=sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005 -GitPython==3.1.14 \ - --hash=sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b \ - --hash=sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61 + # via + # -r tools/deprecate_version/requirements.txt + # gitpython +gitpython==3.1.15 \ + --hash=sha256:05af150f47a5cca3f4b0af289b73aef8cf3c4fe2385015b06220cbcdee48bb6e \ + --hash=sha256:a77824e516d3298b04fb36ec7845e92747df8fcfee9cacc32dd6239f9652f867 + # via -r tools/deprecate_version/requirements.txt idna==2.10 \ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 -PyGithub==1.54.1 \ - --hash=sha256:87afd6a67ea582aa7533afdbf41635725f13d12581faed7e3e04b1579c0c0627 \ - --hash=sha256:300bc16e62886ca6537b0830e8f516ea4bc3ef12d308e0c5aff8bdbd099173d4 -PyJWT==1.7.1 \ + # via + # -r tools/deprecate_version/requirements.txt + # requests +pygithub==1.54.1 \ + --hash=sha256:300bc16e62886ca6537b0830e8f516ea4bc3ef12d308e0c5aff8bdbd099173d4 \ + --hash=sha256:87afd6a67ea582aa7533afdbf41635725f13d12581faed7e3e04b1579c0c0627 + # via -r tools/deprecate_version/requirements.txt +pyjwt==1.7.1 \ --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 + # via + # -r tools/deprecate_version/requirements.txt + # pygithub requests==2.25.1 \ - --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e \ - --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 + --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \ + --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e + # via + # -r tools/deprecate_version/requirements.txt + # pygithub smmap==4.0.0 \ - --hash=sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2 \ - --hash=sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182 + --hash=sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182 \ + --hash=sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2 + # via + # -r tools/deprecate_version/requirements.txt + # gitdb +typing-extensions==3.7.4.3 \ + --hash=sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918 \ + --hash=sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c \ + --hash=sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f + # via gitpython urllib3==1.26.4 \ --hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \ --hash=sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937 + # via + # -r tools/deprecate_version/requirements.txt + # requests wrapt==1.12.1 \ --hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7 + # via + # -r tools/deprecate_version/requirements.txt + # deprecated From 58a13570f3f4dea9bad8b8fa5e1221d7ed5056de Mon Sep 17 00:00:00 2001 From: danzh Date: Fri, 23 Apr 2021 16:56:26 -0400 Subject: [PATCH 069/209] disable tls resumption (#16147) Signed-off-by: Dan Zhang --- source/common/quic/platform/quiche_flags_impl.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/common/quic/platform/quiche_flags_impl.cc b/source/common/quic/platform/quiche_flags_impl.cc index 6475b71d92e7e..6b2f9a638df43 100644 --- a/source/common/quic/platform/quiche_flags_impl.cc +++ b/source/common/quic/platform/quiche_flags_impl.cc @@ -34,6 +34,8 @@ absl::flat_hash_map makeFlagMap() { #include "quiche/quic/core/quic_protocol_flags_list.h" #undef QUIC_PROTOCOL_FLAG + // TODO(danzh) Re-enable TLS resumption after #15912 is checked in. + FLAGS_quic_disable_server_tls_resumption->setValue(true); return flags; } From 7cd615c262dc1b871812209e37c15a49daea5173 Mon Sep 17 00:00:00 2001 From: phlax Date: Sat, 24 Apr 2021 23:45:06 +0100 Subject: [PATCH 070/209] api: Update xds_protocol doc to remove v2 and cleanup (#16097) * api: Update xds_protocol doc to remove v2 and cleanup Signed-off-by: Ryan Northey --- api/xds_protocol.rst | 334 ++++++++++++++++-------------------- docs/root/_include/ads.yaml | 51 ++++++ 2 files changed, 195 insertions(+), 190 deletions(-) create mode 100644 docs/root/_include/ads.yaml diff --git a/api/xds_protocol.rst b/api/xds_protocol.rst index 9a17eb332cd0e..46c7dacfb9661 100644 --- a/api/xds_protocol.rst +++ b/api/xds_protocol.rst @@ -8,9 +8,9 @@ querying one or more management servers. Collectively, these discovery services and their corresponding APIs are referred to as *xDS*. Resources are requested via *subscriptions*, by specifying a filesystem path to watch, initiating gRPC streams, or polling a REST-JSON URL. The -latter two methods involve sending requests with a :ref:`DiscoveryRequest ` +latter two methods involve sending requests with a :ref:`DiscoveryRequest ` proto payload. Resources are delivered in a -:ref:`DiscoveryResponse ` +:ref:`DiscoveryResponse ` proto payload in all methods. We discuss each type of subscription below. @@ -21,17 +21,6 @@ Every configuration resource in the xDS API has a type associated with it. Resou :repo:`versioning scheme `. Resource types are versioned independent of the transports described below. -The following v2 xDS resource types are supported: - -- :ref:`envoy.api.v2.Listener ` -- :ref:`envoy.api.v2.RouteConfiguration ` -- :ref:`envoy.api.v2.ScopedRouteConfiguration ` -- :ref:`envoy.api.v2.route.VirtualHost ` -- :ref:`envoy.api.v2.Cluster ` -- :ref:`envoy.api.v2.ClusterLoadAssignment ` -- :ref:`envoy.api.v2.Auth.Secret ` -- :ref:`envoy.service.discovery.v2.Runtime ` - The following v3 xDS resource types are supported: - :ref:`envoy.config.listener.v3.Listener ` @@ -44,8 +33,8 @@ The following v3 xDS resource types are supported: - :ref:`envoy.service.runtime.v3.Runtime ` The concept of `type URLs `_ -appears below, and takes the form `type.googleapis.com/` -- e.g., -`type.googleapis.com/envoy.api.v2.Cluster` for a `Cluster` resource. In various requests from +appears below, and takes the form ``type.googleapis.com/`` -- e.g., +``type.googleapis.com/envoy.config.cluster.v3.Cluster`` for a ``Cluster`` resource. In various requests from Envoy and responses by the management server, the resource type URL is stated. @@ -53,13 +42,13 @@ Filesystem subscriptions ------------------------ The simplest approach to delivering dynamic configuration is to place it -at a well known path specified in the :ref:`ConfigSource `. -Envoy will use `inotify` (`kqueue` on macOS) to monitor the file for +at a well known path specified in the :ref:`ConfigSource `. +Envoy will use ``inotify`` (``kqueue`` on macOS) to monitor the file for changes and parse the -:ref:`DiscoveryResponse ` proto in the file on update. +:ref:`DiscoveryResponse ` proto in the file on update. Binary protobufs, JSON, YAML and proto text are supported formats for the -:ref:`DiscoveryResponse `. +:ref:`DiscoveryResponse `. There is no mechanism available for filesystem subscriptions to ACK/NACK updates beyond stats counters and logs. The last valid configuration for @@ -75,20 +64,20 @@ API flow ~~~~~~~~ For typical HTTP routing scenarios, the core resource types for the client's configuration are -`Listener`, `RouteConfiguration`, `Cluster`, and `ClusterLoadAssignment`. Each `Listener` resource -may point to a `RouteConfiguration` resource, which may point to one or more `Cluster` resources, -and each `Cluster` resource may point to a `ClusterLoadAssignment` resource. +``Listener``, ``RouteConfiguration``, ``Cluster``, and ``ClusterLoadAssignment``. Each ``Listener`` resource +may point to a ``RouteConfiguration`` resource, which may point to one or more ``Cluster`` resources, +and each ``Cluster`` resource may point to a ``ClusterLoadAssignment`` resource. -Envoy fetches all `Listener` and `Cluster` resources at startup. It then fetches whatever -`RouteConfiguration` and `ClusterLoadAssignment` resources that are required by the `Listener` and -`Cluster` resources. In effect, every `Listener` or `Cluster` resource is a root to part of Envoy's +Envoy fetches all ``Listener`` and ``Cluster`` resources at startup. It then fetches whatever +``RouteConfiguration`` and ``ClusterLoadAssignment`` resources that are required by the ``Listener`` and +``Cluster`` resources. In effect, every ``Listener`` or ``Cluster`` resource is a root to part of Envoy's configuration tree. -A non-proxy client such as gRPC might start by fetching only the specific `Listener` resources -that it is interested in. It then fetches the `RouteConfiguration` resources required by those -`Listener` resources, followed by whichever `Cluster` resources are required by those -`RouteConfiguration` resources, followed by the `ClusterLoadAssignment` resources required -by the `Cluster` resources. In effect, the original `Listener` resources are the roots to +A non-proxy client such as gRPC might start by fetching only the specific ``Listener`` resources +that it is interested in. It then fetches the ``RouteConfiguration`` resources required by those +``Listener`` resources, followed by whichever ``Cluster`` resources are required by those +``RouteConfiguration`` resources, followed by the ``ClusterLoadAssignment`` resources required +by the ``Cluster`` resources. In effect, the original ``Listener`` resources are the roots to the client's configuration tree. Variants of the xDS Transport Protocol @@ -170,52 +159,52 @@ stream. The RPC service and methods for the aggregated protocol variants are: - Incremental: AggregatedDiscoveryService.DeltaAggregatedResources For all of the SotW methods, the request type is :ref:`DiscoveryRequest -` and the response type is :ref:`DiscoveryResponse -`. +` and the response type is :ref:`DiscoveryResponse +`. For all of the incremental methods, the request type is :ref:`DeltaDiscoveryRequest -` and the response type is :ref:`DeltaDiscoveryResponse -`. +` and the response type is :ref:`DeltaDiscoveryResponse +`. Configuring Which Variant to Use ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In the xDS API, the :ref:`ConfigSource ` message indicates how to -obtain resources of a particular type. If the :ref:`ConfigSource ` -contains a gRPC :ref:`ApiConfigSource `, it points to an +In the xDS API, the :ref:`ConfigSource ` message indicates how to +obtain resources of a particular type. If the :ref:`ConfigSource ` +contains a gRPC :ref:`ApiConfigSource `, it points to an upstream cluster for the management server; this will initiate an independent bidirectional gRPC stream for each xDS resource type, potentially to distinct management servers. If the -:ref:`ConfigSource ` contains a :ref:`AggregatedConfigSource -`, it tells the client to use :ref:`ADS +:ref:`ConfigSource ` contains a :ref:`AggregatedConfigSource +`, it tells the client to use :ref:`ADS `. Currently, the client is expected to be given some local configuration that tells it how to obtain -the :ref:`Listener ` and :ref:`Cluster ` resources. -:ref:`Listener ` resources may include a -:ref:`ConfigSource ` that indicates how the -:ref:`RouteConfiguration ` resources are obtained, and -:ref:`Cluster ` resources may include a -:ref:`ConfigSource ` that indicates how the -:ref:`ClusterLoadAssignment ` resources are obtained. +the :ref:`Listener ` and :ref:`Cluster ` resources. +:ref:`Listener ` resources may include a +:ref:`ConfigSource ` that indicates how the +:ref:`RouteConfiguration ` resources are obtained, and +:ref:`Cluster ` resources may include a +:ref:`ConfigSource ` that indicates how the +:ref:`ClusterLoadAssignment ` resources are obtained. Client Configuration """""""""""""""""""" -In Envoy, the bootstrap file contains two :ref:`ConfigSource ` -messages, one indicating how :ref:`Listener ` resources are obtained and -another indicating how :ref:`Cluster ` resources are obtained. It also -contains a separate :ref:`ApiConfigSource ` message indicating +In Envoy, the bootstrap file contains two :ref:`ConfigSource ` +messages, one indicating how :ref:`Listener ` resources are obtained and +another indicating how :ref:`Cluster ` resources are obtained. It also +contains a separate :ref:`ApiConfigSource ` message indicating how to contact the ADS server, which will be used whenever a :ref:`ConfigSource -` message (either in the bootstrap file or in a :ref:`Listener -` or :ref:`Cluster ` resource obtained from a +` message (either in the bootstrap file or in a :ref:`Listener +` or :ref:`Cluster ` resource obtained from a management server) contains an :ref:`AggregatedConfigSource -` message. +` message. In a gRPC client that uses xDS, only ADS is supported, and the bootstrap file contains the name of the ADS server, which will be used for all resources. The :ref:`ConfigSource -` messages in the :ref:`Listener ` and -:ref:`Cluster ` resources must contain :ref:`AggregatedConfigSource -` messages. +` messages in the :ref:`Listener ` and +:ref:`Cluster ` resources must contain :ref:`AggregatedConfigSource +` messages. The xDS transport Protocol ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -225,20 +214,20 @@ Transport API version In addition the resource type version described above, the xDS wire protocol has a transport version associated with it. This provides type versioning for messages such as -:ref:`DiscoveryRequest ` and :ref:`DiscoveryResponse -`. It is also encoded in the gRPC method name, so a server +:ref:`DiscoveryRequest ` and :ref:`DiscoveryResponse +`. It is also encoded in the gRPC method name, so a server can determine which version a client is speaking based on which method it calls. Basic Protocol Overview ^^^^^^^^^^^^^^^^^^^^^^^ -Each xDS stream begins with a :ref:`DiscoveryRequest ` from the +Each xDS stream begins with a :ref:`DiscoveryRequest ` from the client, which specifies the list of resources to subscribe to, the type URL corresponding to the subscribed resources, the node identifier, and an optional resource type instance version indicating the most recent version of the resource type that the client has already seen (see :ref:`ACK/NACK and resource type instance version ` for details). -The server will then send a :ref:`DiscoveryResponse ` containing +The server will then send a :ref:`DiscoveryResponse ` containing any resources that the client has subscribed to that have changed since the last resource type instance version that the client indicated it has seen. The server may send additional responses at any time when the subscribed resources change. @@ -247,9 +236,9 @@ Whenever the client receives a new response, it will send another request indica not the resources in the response were valid (see :ref:`ACK/NACK and resource type instance version ` for details). -All server responses will contain a :ref:`nonce`, and +All server responses will contain a :ref:`nonce`, and all subsequent requests from the client must set the -:ref:`response_nonce ` field to the most recent +:ref:`response_nonce ` field to the most recent nonce received from the server on that stream. This allows servers to determine which response a given request is associated with, which avoids various race conditions in the SotW protocol variants. Note that the nonce is valid only in the context of an individual xDS stream; it does @@ -271,15 +260,15 @@ Every xDS resource type has a version string that indicates the version for that Whenever one resource of that type changes, the version is changed. In a response sent by the xDS server, the -:ref:`version_info` field indicates the current +:ref:`version_info` field indicates the current version for that resource type. The client then sends another request to the server with the -:ref:`version_info` field indicating the most +:ref:`version_info` field indicating the most recent valid version seen by the client. This provides a way for the server to determine when it sends a version that the client considers invalid. (In the :ref:`incremental protocol variants `, the resource type instance version is sent by the server in the -:ref:`system_version_info` field. +:ref:`system_version_info` field. However, this information is not actually used by the client to communicate which resources are valid, because the incremental API variants have a separate mechanism for that.) @@ -288,7 +277,7 @@ protocol variants, each resource type has its own version even though all resour sent on the same stream. The resource type instance version is also separate for each xDS server (where an xDS server is -identified by a unique :ref:`ConfigSource `). When obtaining +identified by a unique :ref:`ConfigSource `). When obtaining resources of a given type from multiple xDS servers, each xDS server will have a different notion of version. @@ -311,11 +300,11 @@ An example EDS request might be: resource_names: - foo - bar - type_url: type.googleapis.com/envoy.api.v2.ClusterLoadAssignment + type_url: type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment response_nonce: The management server may reply either immediately or when the requested -resources are available with a :ref:`DiscoveryResponse `, e.g.: +resources are available with a :ref:`DiscoveryResponse `, e.g.: .. code:: yaml @@ -323,10 +312,10 @@ resources are available with a :ref:`DiscoveryResponse `, Envoy will send a new +After processing the :ref:`DiscoveryResponse `, Envoy will send a new request on the stream, specifying the last version successfully applied and the nonce provided by the management server. The version provides Envoy and the management server a shared notion of the currently applied configuration, @@ -336,7 +325,7 @@ ACK ^^^ If the update was successfully applied, the -:ref:`version_info ` will be **X**, as indicated +:ref:`version_info ` will be **X**, as indicated in the sequence diagram: .. figure:: diagrams/simple-ack.svg @@ -346,9 +335,9 @@ NACK ^^^^ If Envoy had instead rejected configuration -update **X**, it would reply with :ref:`error_detail ` +update **X**, it would reply with :ref:`error_detail ` populated and its previous version, which in this case was the empty -initial version. The :ref:`error_detail ` has +initial version. The :ref:`error_detail ` has more details around the exact error message populated in the message field: .. figure:: diagrams/simple-nack.svg @@ -366,7 +355,7 @@ After a NACK, an API update may succeed at a new version **Y**: :alt: ACK after NACK The preferred mechanism for a server to detect a NACK is to look for the presence of the -:ref:`error_detail ` field in the request sent by +:ref:`error_detail ` field in the request sent by the client. Some older servers may instead detect a NACK by looking at both the version and the nonce in the request: if the version in the request is not equal to the one sent by the server with that nonce, then the client has rejected the most recent version. However, this approach does not @@ -381,16 +370,16 @@ consider the following example: ACK and NACK semantics summary ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- The xDS client should ACK or NACK every :ref:`DiscoveryResponse ` +- The xDS client should ACK or NACK every :ref:`DiscoveryResponse ` received from the management server. The :ref:`response_nonce - ` field tells the server which of its responses + ` field tells the server which of its responses the ACK or NACK is associated with. - ACK signifies successful configuration update and contains the - :ref:`version_info ` from the - :ref:`DiscoveryResponse `. + :ref:`version_info ` from the + :ref:`DiscoveryResponse `. - NACK signifies unsuccessful configuration and is indicated by the presence of the - :ref:`error_detail ` field. The :ref:`version_info - ` indicates the most recent version that the + :ref:`error_detail ` field. The :ref:`version_info + ` indicates the most recent version that the client is using, although that may not be an older version in the case where the client has subscribed to a new resource from an existing version and that new resource is invalid (see example above). @@ -401,17 +390,17 @@ When to send an update ^^^^^^^^^^^^^^^^^^^^^^ The management server should only send updates to the Envoy client when -the resources in the :ref:`DiscoveryResponse ` have changed. Envoy replies -to any :ref:`DiscoveryResponse ` with a :ref:`DiscoveryRequest ` containing the +the resources in the :ref:`DiscoveryResponse ` have changed. Envoy replies +to any :ref:`DiscoveryResponse ` with a :ref:`DiscoveryRequest ` containing the ACK/NACK immediately after it has been either accepted or rejected. If the management server provides the same set of resources rather than waiting for a change to occur, it will cause needless work on both the client and the management server, which could have a severe performance impact. -Within a stream, new :ref:`DiscoveryRequests ` supersede any prior -:ref:`DiscoveryRequests ` having the same resource type. This means that +Within a stream, new :ref:`DiscoveryRequests ` supersede any prior +:ref:`DiscoveryRequests ` having the same resource type. This means that the management server only needs to respond to the latest -:ref:`DiscoveryRequest ` on each stream for any given resource type. +:ref:`DiscoveryRequest ` on each stream for any given resource type. .. _xds_protocol_resource_hints: @@ -420,13 +409,13 @@ How the client specifies what resources to return xDS requests allow the client to specify a set of resource names as a hint to the server about which resources the client is interested in. In the SotW protocol variants, this is done via the -:ref:`resource_names ` specified in the -:ref:`DiscoveryRequest `; in the incremental protocol variants, +:ref:`resource_names ` specified in the +:ref:`DiscoveryRequest `; in the incremental protocol variants, this is done via the :ref:`resource_names_subscribe -` and +` and :ref:`resource_names_unsubscribe -` fields in the -:ref:`DeltaDiscoveryRequest `. +` fields in the +:ref:`DeltaDiscoveryRequest `. Normally (see below for exceptions), requests must specify the set of resource names that the client is interested in. The management server must supply the requested resources if they exist. @@ -437,11 +426,11 @@ been asked for them and the resources have not changed since that time. If the l names becomes empty, that means that the client is no longer interested in any resources of the specified type. -For :ref:`Listener ` and :ref:`Cluster ` resource +For :ref:`Listener ` and :ref:`Cluster ` resource types, there is also a "wildcard" mode, which is triggered when the initial request on the stream for that resource type contains no resource names. In this case, the server should use site-specific business logic to determine the full set of resources that the client is interested -in, typically based on the client's :ref:`node ` identification. Note +in, typically based on the client's :ref:`node ` identification. Note that once a stream has entered wildcard mode for a given resource type, there is no way to change the stream out of wildcard mode; resource names specified in any subsequent request on the stream will be ignored. @@ -449,8 +438,8 @@ will be ignored. Client Behavior """"""""""""""" -Envoy will always use wildcard mode for :ref:`Listener ` and -:ref:`Cluster ` resources. However, other xDS clients (such as gRPC clients +Envoy will always use wildcard mode for :ref:`Listener ` and +:ref:`Cluster ` resources. However, other xDS clients (such as gRPC clients that use xDS) may specify explicit resource names for these resource types, for example if they only have a singleton listener and already know its name from some out-of-band configuration. @@ -463,9 +452,9 @@ may send a response containing only the changed resource; it does not need to re resources that have not changed, and the client must not delete the unchanged resources. In the SotW protocol variants, all resource types except for :ref:`Listener -` and :ref:`Cluster ` are grouped into responses +` and :ref:`Cluster ` are grouped into responses in the same way as in the incremental protocol variants. However, -:ref:`Listener ` and :ref:`Cluster ` resource types +:ref:`Listener ` and :ref:`Cluster ` resource types are handled differently: the server must include the complete state of the world, meaning that all resources of the relevant type that are needed by the client must be included, even if they did not change since the last response. This means that if the server has previously sent 100 @@ -487,21 +476,21 @@ Deleting Resources ^^^^^^^^^^^^^^^^^^ In the incremental proocol variants, the server signals the client that a resource should be -deleted via the :ref:`removed_resources ` +deleted via the :ref:`removed_resources ` field of the response. This tells the client to remove the resource from its local cache. In the SotW protocol variants, the criteria for deleting resources is more complex. For -:ref:`Listener ` and :ref:`Cluster ` resource types, +:ref:`Listener ` and :ref:`Cluster ` resource types, if a previously seen resource is not present in a new response, that indicates that the resource has been removed, and the client must delete it; a response containing no resources means to delete all resources of that type. However, for other resource types, the API provides no mechanism for the server to tell the client that resources have been deleted; instead, deletions are indicated implicitly by parent resources being changed to no longer refer to a child resource. For example, -when the client receives an LDS update removing a :ref:`Listener ` -that was previously pointing to :ref:`RouteConfiguration ` A, -if no other :ref:`Listener ` is pointing to :ref:`RouteConfiguration -` A, then the client may delete A. For those resource types, -an empty :ref:`DiscoveryResponse ` is effectively a no-op +when the client receives an LDS update removing a :ref:`Listener ` +that was previously pointing to :ref:`RouteConfiguration ` A, +if no other :ref:`Listener ` is pointing to :ref:`RouteConfiguration +` A, then the client may delete A. For those resource types, +an empty :ref:`DiscoveryResponse ` is effectively a no-op from the client's perspective. .. _xds_protocol_resource_not_existed: @@ -512,7 +501,7 @@ Knowing When a Requested Resource Does Not Exist The SotW protocol variants do not provide any explicit mechanism to determine when a requested resource does not exist. -Responses for :ref:`Listener ` and :ref:`Cluster ` +Responses for :ref:`Listener ` and :ref:`Cluster ` resource types must include all resources requested by the client. However, it may not be possible for the client to know that a resource does not exist based solely on its absence in a response, because the delivery of the updates is eventually consistent: if the client initially sends a @@ -529,12 +518,12 @@ previously. As a result, clients are expected to use a timeout (recommended duration is 15 seconds) after sending a request for a new resource, after which they will consider the requested resource to not exist if they have not received the resource. In Envoy, this is done for -:ref:`RouteConfiguration ` and :ref:`ClusterLoadAssignment -` resources during :ref:`resource warming +:ref:`RouteConfiguration ` and :ref:`ClusterLoadAssignment +` resources during :ref:`resource warming `. Note that this timeout is not strictly necessary when using wildcard mode for :ref:`Listener -` and :ref:`Cluster ` resource types, because +` and :ref:`Cluster ` resource types, because in that case every response will contain all existing resources that are relevant to the client, so the client can know that a resource does not exist by its absence in the next response it sees. However, using a timeout is still recommended in this case, since it protects @@ -551,16 +540,16 @@ Unsubscribing From Resources In the incremental protocol variants, resources can be unsubscribed to via the :ref:`resource_names_unsubscribe -` field. +` field. In the SotW protocol variants, each request must contain the full list of resource names being -subscribed to in the :ref:`resource_names ` field, +subscribed to in the :ref:`resource_names ` field, so unsubscribing to a set of resources is done by sending a new request containing all resource names that are still being subscribed to but not containing the resource names being unsubscribed to. For example, if the client had previously been subscribed to resources A and B but wishes to unsubscribe from B, it must send a new request containing only resource A. -Note that for :ref:`Listener ` and :ref:`Cluster ` +Note that for :ref:`Listener ` and :ref:`Cluster ` resource types where the stream is in "wildcard" mode (see :ref:`How the client specifies what resources to return ` for details), the set of resources being subscribed to is determined by the server instead of the client, so there is no mechanism @@ -570,14 +559,14 @@ Requesting Multiple Resources on a Single Stream ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For EDS/RDS, Envoy may either generate a distinct stream for each -resource of a given type (e.g. if each :ref:`ConfigSource ` has its own +resource of a given type (e.g. if each :ref:`ConfigSource ` has its own distinct upstream cluster for a management server), or may combine together multiple resource requests for a given resource type when they are destined for the same management server. While this is left to implementation specifics, management servers should be capable of -handling one or more :ref:`resource_names ` for a given resource type in +handling one or more :ref:`resource_names ` for a given resource type in each request. Both sequence diagrams below are valid for fetching two -EDS resources `{foo, bar}`: +EDS resources ``{foo, bar}``: |Multiple EDS requests on the same stream| |Multiple EDS requests on distinct streams| @@ -585,15 +574,15 @@ distinct streams| Resource updates ^^^^^^^^^^^^^^^^ -As discussed above, Envoy may update the list of :ref:`resource_names ` it -presents to the management server in each :ref:`DiscoveryRequest ` that -ACK/NACKs a specific :ref:`DiscoveryResponse `. In addition, Envoy may later -issue additional :ref:`DiscoveryRequests ` at a given :ref:`version_info ` to +As discussed above, Envoy may update the list of :ref:`resource_names ` it +presents to the management server in each :ref:`DiscoveryRequest ` that +ACK/NACKs a specific :ref:`DiscoveryResponse `. In addition, Envoy may later +issue additional :ref:`DiscoveryRequests ` at a given :ref:`version_info ` to update the management server with new resource hints. For example, if Envoy is at EDS version **X** and knows only about cluster ``foo``, but then receives a CDS update and learns about ``bar`` in addition, it may -issue an additional :ref:`DiscoveryRequest ` for **X** with `{foo,bar}` as -`resource_names`. +issue an additional :ref:`DiscoveryRequest ` for **X** with ``{foo,bar}`` as +``resource_names``. .. figure:: diagrams/cds-eds-resources.svg :alt: CDS response leads to EDS resource hint update @@ -602,26 +591,26 @@ There is a race condition that may arise here; if after a resource hint update is issued by Envoy at **X**, but before the management server processes the update it replies with a new version **Y**, the resource hint update may be interpreted as a rejection of **Y** by presenting an -**X** :ref:`version_info `. To avoid this, the management server provides a -``nonce`` that Envoy uses to indicate the specific :ref:`DiscoveryResponse ` -each :ref:`DiscoveryRequest ` corresponds to: +**X** :ref:`version_info `. To avoid this, the management server provides a +``nonce`` that Envoy uses to indicate the specific :ref:`DiscoveryResponse ` +each :ref:`DiscoveryRequest ` corresponds to: .. figure:: diagrams/update-race.svg :alt: EDS update race motivates nonces -The management server should not send a :ref:`DiscoveryResponse ` for any -:ref:`DiscoveryRequest ` that has a stale nonce. A nonce becomes stale +The management server should not send a :ref:`DiscoveryResponse ` for any +:ref:`DiscoveryRequest ` that has a stale nonce. A nonce becomes stale following a newer nonce being presented to Envoy in a -:ref:`DiscoveryResponse `. A management server does not need to send an +:ref:`DiscoveryResponse `. A management server does not need to send an update until it determines a new version is available. Earlier requests at a version then also become stale. It may process multiple -:ref:`DiscoveryRequests ` at a version until a new version is ready. +:ref:`DiscoveryRequests ` at a version until a new version is ready. .. figure:: diagrams/stale-requests.svg :alt: Requests become stale An implication of the above resource update sequencing is that Envoy -does not expect a :ref:`DiscoveryResponse ` for every :ref:`DiscoveryRequests ` +does not expect a :ref:`DiscoveryResponse ` for every :ref:`DiscoveryRequests ` it issues. .. _xds_protocol_resource_warming: @@ -633,10 +622,10 @@ Resource warming :ref:`Listeners ` go through warming before they can serve requests. This process happens both during :ref:`Envoy initialization ` -and when the `Cluster` or `Listener` is updated. Warming of -`Cluster` is completed only when a `ClusterLoadAssignment` response -is supplied by management server. Similarly, warming of `Listener` is -completed only when a `RouteConfiguration` is supplied by management +and when the ``Cluster`` or ``Listener`` is updated. Warming of +``Cluster`` is completed only when a ``ClusterLoadAssignment`` response +is supplied by management server. Similarly, warming of ``Listener`` is +completed only when a ``RouteConfiguration`` is supplied by management server if the listener refers to an RDS configuration. Management server is expected to provide the EDS/RDS updates during warming. If management server does not provide EDS/RDS responses, Envoy will not initialize @@ -650,7 +639,7 @@ Eventual consistency considerations Since Envoy's xDS APIs are eventually consistent, traffic may drop briefly during updates. For example, if only cluster **X** is known via -CDS/EDS, a `RouteConfiguration` references cluster **X** and is then +CDS/EDS, a ``RouteConfiguration`` references cluster **X** and is then adjusted to cluster **Y** just before the CDS/EDS update providing **Y**, traffic will be blackholed until **Y** is known about by the Envoy instance. @@ -695,7 +684,7 @@ be used, for example, to terminate a fault injection test when the management se be reached. For clients that support the *xds.config.supports-resource-ttl* client feature, A TTL field may -be specified on each :ref:`Resource `. Each resource will have its own TTL +be specified on each :ref:`Resource `. Each resource will have its own TTL expiry time, at which point the resource will be expired. Each xDS type may have different ways of handling such an expiry. @@ -703,7 +692,7 @@ To update the TTL associated with a *Resource*, the management server resends th new TTL. To remove the TTL, the management server resends the resource with the TTL field unset. To allow for lightweight TTL updates ("heartbeats"), a response can be sent that provides a -:ref:`Resource ` with the :ref:`resource ` +:ref:`Resource ` with the :ref:`resource ` unset and version matching the most recently sent version can be used to update the TTL. These resources will not be treated as resource updates, but only as TTL updates. @@ -711,7 +700,7 @@ SotW TTL ^^^^^^^^ In order to use TTL with SotW xDS, the relevant resources must be wrapped in a -:ref:`Resource `. This allows setting the same TTL field that is used for +:ref:`Resource `. This allows setting the same TTL field that is used for Delta xDS with SotW, without changing the SotW API. Heartbeats are supported for SotW as well: any resource within the response that look like a heartbeat resource will only be used to update the TTL. @@ -727,9 +716,9 @@ traffic drop when management servers are distributed. ADS allow a single management server, via a single gRPC stream, to deliver all API updates. This provides the ability to carefully sequence updates to avoid traffic drop. With ADS, a single stream is used with multiple independent -:ref:`DiscoveryRequest `/:ref:`DiscoveryResponse ` sequences multiplexed via the +:ref:`DiscoveryRequest `/:ref:`DiscoveryResponse ` sequences multiplexed via the type URL. For any given type URL, the above sequencing of -:ref:`DiscoveryRequest ` and :ref:`DiscoveryResponse ` messages applies. An +:ref:`DiscoveryRequest ` and :ref:`DiscoveryResponse ` messages applies. An example update sequence might look like: .. figure:: diagrams/ads.svg @@ -739,42 +728,7 @@ A single ADS stream is available per Envoy instance. An example minimal ``bootstrap.yaml`` fragment for ADS configuration is: -.. code:: yaml - - node: - id: - dynamic_resources: - cds_config: {ads: {}} - lds_config: {ads: {}} - ads_config: - api_type: GRPC - grpc_services: - envoy_grpc: - cluster_name: ads_cluster - static_resources: - clusters: - - name: ads_cluster - connect_timeout: { seconds: 5 } - type: STATIC - hosts: - - socket_address: - address: - port_value: - lb_policy: ROUND_ROBIN - # It is recommended to configure either HTTP/2 or TCP keepalives in order to detect - # connection issues, and allow Envoy to reconnect. TCP keepalive is less expensive, but - # may be inadequate if there is a TCP proxy between Envoy and the management server. - # HTTP/2 keepalive is slightly more expensive, but may detect issues through more types - # of intermediate proxies. - http2_protocol_options: - connection_keepalive: - interval: 30s - timeout: 5s - upstream_connection_options: - tcp_keepalive: - ... - admin: - ... +.. literalinclude:: ../_include/ads.yaml .. _xds_protocol_delta: @@ -798,16 +752,16 @@ state of xDS clients connected to it. There is no REST version of Incremental xDS yet. In the delta xDS wire protocol, the nonce field is required and used to -pair a :ref:`DeltaDiscoveryResponse ` -to a :ref:`DeltaDiscoveryRequest ` -ACK or NACK. Optionally, a response message level :ref:`system_version_info ` +pair a :ref:`DeltaDiscoveryResponse ` +to a :ref:`DeltaDiscoveryRequest ` +ACK or NACK. Optionally, a response message level :ref:`system_version_info ` is present for debugging purposes only. -:ref:`DeltaDiscoveryRequest ` can be sent in the following situations: +:ref:`DeltaDiscoveryRequest ` can be sent in the following situations: - Initial message in a xDS bidirectional gRPC stream. -- As an ACK or NACK response to a previous :ref:`DeltaDiscoveryResponse `. In this case the :ref:`response_nonce ` is set to the nonce value in the Response. ACK or NACK is determined by the absence or presence of :ref:`error_detail `. -- Spontaneous :ref:`DeltaDiscoveryRequests ` from the client. This can be done to dynamically add or remove elements from the tracked :ref:`resource_names ` set. In this case :ref:`response_nonce ` must be omitted. +- As an ACK or NACK response to a previous :ref:`DeltaDiscoveryResponse `. In this case the :ref:`response_nonce ` is set to the nonce value in the Response. ACK or NACK is determined by the absence or presence of :ref:`error_detail `. +- Spontaneous :ref:`DeltaDiscoveryRequests ` from the client. This can be done to dynamically add or remove elements from the tracked :ref:`resource_names ` set. In this case :ref:`response_nonce ` must be omitted. In this first example the client connects and receives a first update that it ACKs. The second update fails and the client NACKs the update. @@ -830,9 +784,9 @@ Resource names Resources are identified by a resource name or an alias. Aliases of a resource, if present, can be identified by the alias field in the -resource of a :ref:`DeltaDiscoveryResponse `. The resource name will be +resource of a :ref:`DeltaDiscoveryResponse `. The resource name will be returned in the name field in the resource of a -:ref:`DeltaDiscoveryResponse `. +:ref:`DeltaDiscoveryResponse `. .. _xds_protocol_delta_subscribe: @@ -840,12 +794,12 @@ Subscribing to Resources ^^^^^^^^^^^^^^^^^^^^^^^^ The client can send either an alias or the name of a resource in the -:ref:`resource_names_subscribe ` field of a :ref:`DeltaDiscoveryRequest ` in +:ref:`resource_names_subscribe ` field of a :ref:`DeltaDiscoveryRequest ` in order to subscribe to a resource. Both the names and aliases of resources should be checked in order to determine whether the entity in question has been subscribed to. -A :ref:`resource_names_subscribe ` field may contain resource names that the +A :ref:`resource_names_subscribe ` field may contain resource names that the server believes the client is already subscribed to, and furthermore has the most recent versions of. However, the server *must* still provide those resources in the response; due to implementation details hidden @@ -858,11 +812,11 @@ Unsubscribing from Resources ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When a client loses interest in some resources, it will indicate that -with the :ref:`resource_names_unsubscribe ` field of a -:ref:`DeltaDiscoveryRequest `. As with :ref:`resource_names_subscribe `, these +with the :ref:`resource_names_unsubscribe ` field of a +:ref:`DeltaDiscoveryRequest `. As with :ref:`resource_names_subscribe `, these may be resource names or aliases. -A :ref:`resource_names_unsubscribe ` field may contain superfluous resource +A :ref:`resource_names_unsubscribe ` field may contain superfluous resource names, which the server thought the client was already not subscribed to. The server must cleanly process such a request; it can simply ignore these phantom unsubscriptions. @@ -871,8 +825,8 @@ Knowing When a Requested Resource Does Not Exist ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When a resource subscribed to by a client does not exist, the server will send a :ref:`Resource -` whose :ref:`name ` field matches the -name that the client subscribed to and whose :ref:`resource ` +` whose :ref:`name ` field matches the +name that the client subscribed to and whose :ref:`resource ` field is unset. This allows the client to quickly determine when a resource does not exist without waiting for a timeout, as would be done in the SotW protocol variants. However, clients are still encouraged to use a timeout to protect against the case where the management server fails to send @@ -888,12 +842,12 @@ expected that there is only a single outstanding request at any point in time, and as a result the response nonce is optional in REST-JSON. The `JSON canonical transform of proto3 `__ -is used to encode :ref:`DiscoveryRequest ` and :ref:`DiscoveryResponse ` +is used to encode :ref:`DiscoveryRequest ` and :ref:`DiscoveryResponse ` messages. ADS is not available for REST-JSON polling. When the poll period is set to a small value, with the intention of long polling, then there is also a requirement to avoid sending a -:ref:`DiscoveryResponse ` unless a change to the underlying resources has +:ref:`DiscoveryResponse ` unless a change to the underlying resources has occurred via a :ref:`resource update `. .. |Multiple EDS requests on the same stream| image:: diagrams/eds-same-stream.svg diff --git a/docs/root/_include/ads.yaml b/docs/root/_include/ads.yaml new file mode 100644 index 0000000000000..81d30842e113c --- /dev/null +++ b/docs/root/_include/ads.yaml @@ -0,0 +1,51 @@ +node: + # set + cluster: envoy_cluster + # set + id: envoy_node + +dynamic_resources: + ads_config: + api_type: GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: ads_cluster + cds_config: + resource_api_version: V3 + ads: {} + lds_config: + resource_api_version: V3 + ads: {} + +static_resources: + clusters: + - name: ads_cluster + connect_timeout: 5s + type: STRICT_DNS + load_assignment: + cluster_name: ads_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + # set + address: my-control-plane + # set + port_value: 777 + # It is recommended to configure either HTTP/2 or TCP keepalives in order to detect + # connection issues, and allow Envoy to reconnect. TCP keepalive is less expensive, but + # may be inadequate if there is a TCP proxy between Envoy and the management server. + # HTTP/2 keepalive is slightly more expensive, but may detect issues through more types + # of intermediate proxies. + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http2_protocol_options: + connection_keepalive: + interval: 30s + timeout: 5s + upstream_connection_options: + tcp_keepalive: {} From 0143bc2ec23c621c6979687c5998633c9f126aa3 Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Mon, 26 Apr 2021 12:58:08 -0400 Subject: [PATCH 071/209] Fix protoleak. (#16101) Signed-off-by: Kevin Baichoo Commit Message: Fixed proto leaks. Additional Description: Let protos manage memory themselves vs explicitly handling it with set_foo and release_foo Risk Level: Low Testing: NA Docs Changes: NA Release Notes: NA Platform Specific Features: NA --- test/common/upstream/maglev_lb_test.cc | 10 ++-------- test/common/upstream/ring_hash_lb_test.cc | 10 ++-------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/test/common/upstream/maglev_lb_test.cc b/test/common/upstream/maglev_lb_test.cc index 514c0b9136947..8dd1de142c543 100644 --- a/test/common/upstream/maglev_lb_test.cc +++ b/test/common/upstream/maglev_lb_test.cc @@ -141,11 +141,8 @@ TEST_F(MaglevLoadBalancerTest, BasicWithHostName) { host_set_.healthy_hosts_ = host_set_.hosts_; host_set_.runCallbacks({}, {}); common_config_ = envoy::config::cluster::v3::Cluster::CommonLbConfig(); - auto chc = envoy::config::cluster::v3::Cluster::CommonLbConfig::ConsistentHashingLbConfig(); - chc.set_use_hostname_for_hashing(true); - common_config_.set_allocated_consistent_hashing_lb_config(&chc); + common_config_.mutable_consistent_hashing_lb_config()->set_use_hostname_for_hashing(true); init(7); - common_config_.release_consistent_hashing_lb_config(); EXPECT_EQ("maglev_lb.min_entries_per_host", lb_->stats().min_entries_per_host_.name()); EXPECT_EQ("maglev_lb.max_entries_per_host", lb_->stats().max_entries_per_host_.name()); @@ -179,11 +176,8 @@ TEST_F(MaglevLoadBalancerTest, BasicWithMetadataHashKey) { host_set_.healthy_hosts_ = host_set_.hosts_; host_set_.runCallbacks({}, {}); common_config_ = envoy::config::cluster::v3::Cluster::CommonLbConfig(); - auto chc = envoy::config::cluster::v3::Cluster::CommonLbConfig::ConsistentHashingLbConfig(); - chc.set_use_hostname_for_hashing(true); - common_config_.set_allocated_consistent_hashing_lb_config(&chc); + common_config_.mutable_consistent_hashing_lb_config()->set_use_hostname_for_hashing(true); init(7); - common_config_.release_consistent_hashing_lb_config(); EXPECT_EQ("maglev_lb.min_entries_per_host", lb_->stats().min_entries_per_host_.name()); EXPECT_EQ("maglev_lb.max_entries_per_host", lb_->stats().max_entries_per_host_.name()); diff --git a/test/common/upstream/ring_hash_lb_test.cc b/test/common/upstream/ring_hash_lb_test.cc index ac2c1854ee6fb..7e8ff95141ab5 100644 --- a/test/common/upstream/ring_hash_lb_test.cc +++ b/test/common/upstream/ring_hash_lb_test.cc @@ -287,12 +287,9 @@ TEST_P(RingHashLoadBalancerTest, BasicWithHostname) { config_.value().mutable_minimum_ring_size()->set_value(12); common_config_ = envoy::config::cluster::v3::Cluster::CommonLbConfig(); - auto chc = envoy::config::cluster::v3::Cluster::CommonLbConfig::ConsistentHashingLbConfig(); - chc.set_use_hostname_for_hashing(true); - common_config_.set_allocated_consistent_hashing_lb_config(&chc); + common_config_.mutable_consistent_hashing_lb_config()->set_use_hostname_for_hashing(true); init(); - common_config_.release_consistent_hashing_lb_config(); EXPECT_EQ("ring_hash_lb.size", lb_->stats().size_.name()); EXPECT_EQ("ring_hash_lb.min_hashes_per_host", lb_->stats().min_hashes_per_host_.name()); @@ -362,12 +359,9 @@ TEST_P(RingHashLoadBalancerTest, BasicWithMetadataHashKey) { config_.value().mutable_minimum_ring_size()->set_value(12); common_config_ = envoy::config::cluster::v3::Cluster::CommonLbConfig(); - auto chc = envoy::config::cluster::v3::Cluster::CommonLbConfig::ConsistentHashingLbConfig(); - chc.set_use_hostname_for_hashing(true); - common_config_.set_allocated_consistent_hashing_lb_config(&chc); + common_config_.mutable_consistent_hashing_lb_config()->set_use_hostname_for_hashing(true); init(); - common_config_.release_consistent_hashing_lb_config(); EXPECT_EQ("ring_hash_lb.size", lb_->stats().size_.name()); EXPECT_EQ("ring_hash_lb.min_hashes_per_host", lb_->stats().min_hashes_per_host_.name()); From 1d234a84e15355613af63bc545c8cf5261fc90b4 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 26 Apr 2021 18:10:51 +0100 Subject: [PATCH 072/209] python: Switch to exclusive list for flake8 (#16159) Signed-off-by: Ryan Northey --- .flake8 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.flake8 b/.flake8 index b08e61a08a26a..691d9ad0460a6 100644 --- a/.flake8 +++ b/.flake8 @@ -1,8 +1,8 @@ [flake8] -# TODO(phlax): make this an exclusive list and enable most tests -select = N802,F401,E111,F821,E9,F63,F72,F82 +# TODO(phlax): ignore less +ignore = W503,W504,E121,E126,E241,E125,E127,E129,E251,E265,E303,E306,E402,E501,E502,E711,E713,E722,E741,F523,F541,F841,N803,N806,N817,W605 # TODO(phlax): exclude less exclude = build_docs,.git,generated,test,examples,venv From 3e9b84809fbe78d0dd2c3c2e960aa2519505d251 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 26 Apr 2021 14:18:21 -0400 Subject: [PATCH 073/209] deprecating allow_500_after_100 (#16171) Risk Level: low (removing deprecated flag) Testing: n/a Release Notes: inline Fixes #16004 Signed-off-by: Alyssa Wilk --- docs/root/version_history/current.rst | 1 + source/common/router/router.cc | 53 +++++++------------ source/common/runtime/runtime_features.cc | 1 - test/integration/protocol_integration_test.cc | 19 ------- 4 files changed, 20 insertions(+), 54 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 0fde709aaffee..0e52daef9dcf6 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -27,6 +27,7 @@ Removed Config or Runtime ------------------------- *Normally occurs at the end of the* :ref:`deprecation period ` +* http: removed `envoy.reloadable_features.allow_500_after_100` runtime guard and the legacy code path. * http: removed `envoy.reloadable_features.hcm_stream_error_on_invalid_message` for disabling closing HTTP/1.1 connections on error. Connection-closing can still be disabled by setting the HTTP/1 configuration :ref:`override_stream_error_on_invalid_http_message `. * http: removed `envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2`; Envoy will now always send GOAWAY to HTTP2 downstreams when the :ref:`disable_keepalive ` overload action is active. * tls: removed `envoy.reloadable_features.tls_use_io_handle_bio` runtime guard and legacy code path. diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 9b63c3045acd1..ec67fdbd87b0a 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -966,19 +966,12 @@ void Filter::onStreamMaxDurationReached(UpstreamRequest& upstream_request) { upstream_request.removeFromList(upstream_requests_); cleanup(); - if (downstream_response_started_ && - !Runtime::runtimeFeatureEnabled("envoy.reloadable_features.allow_500_after_100")) { - callbacks_->streamInfo().setResponseCodeDetails( - StreamInfo::ResponseCodeDetails::get().UpstreamMaxStreamDurationReached); - callbacks_->resetStream(); - } else { - callbacks_->streamInfo().setResponseFlag( - StreamInfo::ResponseFlag::UpstreamMaxStreamDurationReached); - // sendLocalReply may instead reset the stream if downstream_response_started_ is true. - callbacks_->sendLocalReply( - Http::Code::RequestTimeout, "upstream max stream duration reached", modify_headers_, - absl::nullopt, StreamInfo::ResponseCodeDetails::get().UpstreamMaxStreamDurationReached); - } + callbacks_->streamInfo().setResponseFlag( + StreamInfo::ResponseFlag::UpstreamMaxStreamDurationReached); + // sendLocalReply may instead reset the stream if downstream_response_started_ is true. + callbacks_->sendLocalReply( + Http::Code::RequestTimeout, "upstream max stream duration reached", modify_headers_, + absl::nullopt, StreamInfo::ResponseCodeDetails::get().UpstreamMaxStreamDurationReached); } void Filter::updateOutlierDetection(Upstream::Outlier::Result result, @@ -1031,27 +1024,19 @@ void Filter::onUpstreamAbort(Http::Code code, StreamInfo::ResponseFlag response_ // If we have not yet sent anything downstream, send a response with an appropriate status code. // Otherwise just reset the ongoing response. callbacks_->streamInfo().setResponseFlag(response_flags); - if (downstream_response_started_ && - !Runtime::runtimeFeatureEnabled("envoy.reloadable_features.allow_500_after_100")) { - // This will destroy any created retry timers. - callbacks_->streamInfo().setResponseCodeDetails(details); - cleanup(); - callbacks_->resetStream(); - } else { - // This will destroy any created retry timers. - cleanup(); - // sendLocalReply may instead reset the stream if downstream_response_started_ is true. - callbacks_->sendLocalReply( - code, body, - [dropped, this](Http::ResponseHeaderMap& headers) { - if (dropped && !config_.suppress_envoy_headers_) { - headers.addReference(Http::Headers::get().EnvoyOverloaded, - Http::Headers::get().EnvoyOverloadedValues.True); - } - modify_headers_(headers); - }, - absl::nullopt, details); - } + // This will destroy any created retry timers. + cleanup(); + // sendLocalReply may instead reset the stream if downstream_response_started_ is true. + callbacks_->sendLocalReply( + code, body, + [dropped, this](Http::ResponseHeaderMap& headers) { + if (dropped && !config_.suppress_envoy_headers_) { + headers.addReference(Http::Headers::get().EnvoyOverloaded, + Http::Headers::get().EnvoyOverloadedValues.True); + } + modify_headers_(headers); + }, + absl::nullopt, details); } bool Filter::maybeRetryReset(Http::StreamResetReason reset_reason, diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 9280faf67a0f1..6ab1615cfccb6 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -59,7 +59,6 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.always_apply_route_header_rules", "envoy.reloadable_features.activate_timers_next_event_loop", "envoy.reloadable_features.add_and_validate_scheme_header", - "envoy.reloadable_features.allow_500_after_100", "envoy.reloadable_features.allow_preconnect", "envoy.reloadable_features.allow_response_for_timeout", "envoy.reloadable_features.check_unsupported_typed_per_filter_config", diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 850c5c009494c..0af79a2ec6618 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -2304,23 +2304,4 @@ TEST_P(DownstreamProtocolIntegrationTest, Test100AndDisconnect) { EXPECT_EQ("503", response->headers().getStatusValue()); } -TEST_P(DownstreamProtocolIntegrationTest, Test100AndDisconnectLegacy) { - config_helper_.addRuntimeOverride("envoy.reloadable_features.allow_500_after_100", "false"); - - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); - waitForNextUpstreamRequest(); - upstream_request_->encode100ContinueHeaders(Http::TestResponseHeaderMapImpl{{":status", "100"}}); - ASSERT_TRUE(fake_upstream_connection_->close()); - - if (downstreamProtocol() == Http::CodecClient::Type::HTTP1) { - ASSERT_TRUE(codec_client_->waitForDisconnect()); - EXPECT_FALSE(response->complete()); - } else { - ASSERT_TRUE(response->waitForReset()); - EXPECT_FALSE(response->complete()); - } -} - } // namespace Envoy From 6f6a78be2c2fff5f29b2d3dcd62aaacee45b5d2c Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 26 Apr 2021 14:22:01 -0400 Subject: [PATCH 074/209] http: removing envoy.reloadable_features.unify_grpc_handling (#16173) Risk Level: Low (removing deprecated flag) Testing: n/a Docs Changes: n/a Release Notes: inline Fixes #16014 Signed-off-by: Alyssa Wilk --- docs/root/version_history/current.rst | 1 + source/common/http/filter_manager.cc | 8 +++----- source/common/runtime/runtime_features.cc | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 0e52daef9dcf6..3301626ccd113 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -30,6 +30,7 @@ Removed Config or Runtime * http: removed `envoy.reloadable_features.allow_500_after_100` runtime guard and the legacy code path. * http: removed `envoy.reloadable_features.hcm_stream_error_on_invalid_message` for disabling closing HTTP/1.1 connections on error. Connection-closing can still be disabled by setting the HTTP/1 configuration :ref:`override_stream_error_on_invalid_http_message `. * http: removed `envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2`; Envoy will now always send GOAWAY to HTTP2 downstreams when the :ref:`disable_keepalive ` overload action is active. +* http: removed `envoy.reloadable_features.unify_grpc_handling` runtime guard and legacy code paths. * tls: removed `envoy.reloadable_features.tls_use_io_handle_bio` runtime guard and legacy code path. New Features diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 248cbfa8ce334..8101dc2007cff 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -829,16 +829,14 @@ void FilterManager::onLocalReply(StreamFilterBase::LocalReplyData& data) { state_.under_on_local_reply_ = false; } +// TODO(alyssawilk) update sendLocalReply interface. void FilterManager::sendLocalReply( - bool old_was_grpc_request, Code code, absl::string_view body, + bool, Code code, absl::string_view body, const std::function& modify_headers, const absl::optional grpc_status, absl::string_view details) { ASSERT(!state_.under_on_local_reply_); const bool is_head_request = state_.is_head_request_; - bool is_grpc_request = old_was_grpc_request; - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.unify_grpc_handling")) { - is_grpc_request = state_.is_grpc_request_; - } + const bool is_grpc_request = state_.is_grpc_request_; stream_info_.setResponseCodeDetails(details); diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 6ab1615cfccb6..8ce5103f852f8 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -91,7 +91,6 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.use_observable_cluster_name", "envoy.reloadable_features.vhds_heartbeats", "envoy.reloadable_features.wasm_cluster_name_envoy_grpc", - "envoy.reloadable_features.unify_grpc_handling", "envoy.reloadable_features.upstream_http2_flood_checks", "envoy.restart_features.use_apple_api_for_dns_lookups", "envoy.reloadable_features.header_map_correctly_coalesce_cookies", From 2026543d30272e816bd1b46d1e99d50b2212d80f Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 26 Apr 2021 16:41:31 -0400 Subject: [PATCH 075/209] http3: turning up more tests (#16175) Most of these were fixed by David's "stop writing to packets while under the stack of reading packets" merge, but a couple were fixed up by other PRs. Risk Level: n/a (test only) Testing: yes Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk --- test/integration/multiplexed_upstream_integration_test.cc | 5 +---- test/integration/protocol_integration_test.cc | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/test/integration/multiplexed_upstream_integration_test.cc b/test/integration/multiplexed_upstream_integration_test.cc index 0e1f51812e9ec..db6f3be19216b 100644 --- a/test/integration/multiplexed_upstream_integration_test.cc +++ b/test/integration/multiplexed_upstream_integration_test.cc @@ -47,12 +47,10 @@ TEST_P(Http2UpstreamIntegrationTest, RouterHeaderOnlyRequestAndResponseNoBuffer) } TEST_P(Http2UpstreamIntegrationTest, RouterUpstreamDisconnectBeforeRequestcomplete) { - EXCLUDE_UPSTREAM_HTTP3; // Close loop. testRouterUpstreamDisconnectBeforeRequestComplete(); } TEST_P(Http2UpstreamIntegrationTest, RouterUpstreamDisconnectBeforeResponseComplete) { - EXCLUDE_UPSTREAM_HTTP3; // Close loop. testRouterUpstreamDisconnectBeforeResponseComplete(); } @@ -253,7 +251,6 @@ TEST_P(Http2UpstreamIntegrationTest, SimultaneousRequestAlpn) { } TEST_P(Http2UpstreamIntegrationTest, LargeSimultaneousRequestWithBufferLimitsAlpn) { - EXCLUDE_UPSTREAM_HTTP3; // No H3 support yet. use_alpn_ = true; config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream. simultaneousRequest(1024 * 20, 1024 * 14 + 2, 1024 * 10 + 5, 1024 * 16); @@ -326,7 +323,7 @@ TEST_P(Http2UpstreamIntegrationTest, ManyLargeSimultaneousRequestWithRandomBacku } TEST_P(Http2UpstreamIntegrationTest, UpstreamConnectionCloseWithManyStreams) { - EXCLUDE_UPSTREAM_HTTP3; // Close loop. + EXCLUDE_UPSTREAM_HTTP3; // Times out waiting for reset. config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream. const uint32_t num_requests = 20; std::vector encoders; diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 0af79a2ec6618..1a184f8275100 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -608,7 +608,6 @@ TEST_P(ProtocolIntegrationTest, RetryStreaming) { // sure that Envoy cleans up stream state correctly when doing a retry with // complete response but incomplete request. TEST_P(ProtocolIntegrationTest, RetryStreamingReset) { - EXCLUDE_UPSTREAM_HTTP3; initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); auto encoder_decoder = @@ -728,7 +727,6 @@ TEST_P(ProtocolIntegrationTest, RetryStreamingCancelDueToBufferOverflow) { // Tests that the x-envoy-attempt-count header is properly set on the upstream request and the // downstream response, and updated after the request is retried. TEST_P(DownstreamProtocolIntegrationTest, RetryAttemptCountHeader) { - EXCLUDE_UPSTREAM_HTTP3; auto host = config_helper_.createVirtualHost("host", "/test_retry"); host.set_include_request_attempt_count(true); host.set_include_attempt_count_in_response(true); @@ -778,7 +776,7 @@ TEST_P(DownstreamProtocolIntegrationTest, RetryPriority) { // TODO(alyssawilk) investigate why this combination doesn't work. return; } - EXCLUDE_UPSTREAM_HTTP3; + EXCLUDE_UPSTREAM_HTTP3; // Timed out waiting for new stream. const Upstream::HealthyLoad healthy_priority_load({0u, 100u}); const Upstream::DegradedLoad degraded_priority_load({0u, 100u}); NiceMock retry_priority(healthy_priority_load, @@ -851,7 +849,6 @@ TEST_P(DownstreamProtocolIntegrationTest, RetryPriority) { // the same host. With a total of two upstream hosts, this should result in us continuously sending // requests to the same host. TEST_P(DownstreamProtocolIntegrationTest, RetryHostPredicateFilter) { - EXCLUDE_UPSTREAM_HTTP3; TestHostPredicateFactory predicate_factory; Registry::InjectFactory inject_factory(predicate_factory); From 286ff81935a8a6f68a2ef8fa9d4dac7acb7becee Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Mon, 26 Apr 2021 19:42:50 -0400 Subject: [PATCH 076/209] metric service: add support for sending tags as labels (#16125) Adds a new configuration flag that makes the metrics service use Labels to express tags instead of sending the full stats name that might include embedded tag keys/values. When configured, tags will be sent as labels while the reported name will be the tag extracted name instead of the full name. Risk Level: Low, new configuration flag Testing: UTs Docs Changes: Inline proto docs Release Notes: Added Signed-off-by: Snow Pettersen --- .../config/metrics/v3/metrics_service.proto | 5 ++ .../metrics/v4alpha/metrics_service.proto | 5 ++ docs/root/version_history/current.rst | 2 + .../config/metrics/v3/metrics_service.proto | 5 ++ .../metrics/v4alpha/metrics_service.proto | 5 ++ .../stat_sinks/metrics_service/config.cc | 2 +- .../grpc_metrics_service_impl.cc | 51 +++++++---- .../grpc_metrics_service_impl.h | 15 +++- .../grpc_metrics_service_impl_test.cc | 88 ++++++++++++++++++- 9 files changed, 153 insertions(+), 25 deletions(-) diff --git a/api/envoy/config/metrics/v3/metrics_service.proto b/api/envoy/config/metrics/v3/metrics_service.proto index 4bb6c77e66c23..e5fab870f8ee6 100644 --- a/api/envoy/config/metrics/v3/metrics_service.proto +++ b/api/envoy/config/metrics/v3/metrics_service.proto @@ -38,4 +38,9 @@ message MetricsServiceConfig { // Eventually (https://github.com/envoyproxy/envoy/issues/10968) if this value is not set, the // sink will take updates from the :ref:`MetricsResponse `. google.protobuf.BoolValue report_counters_as_deltas = 2; + + // If true, metrics will have their tags emitted as labels on the metrics objects sent to the MetricsService, + // and the tag extracted name will be used instead of the full name, which may contain values used by the tag + // extractor or additional tags added during stats creation. + bool emit_tags_as_labels = 4; } diff --git a/api/envoy/config/metrics/v4alpha/metrics_service.proto b/api/envoy/config/metrics/v4alpha/metrics_service.proto index edc0fcfc4d6e5..570bc2e9d7162 100644 --- a/api/envoy/config/metrics/v4alpha/metrics_service.proto +++ b/api/envoy/config/metrics/v4alpha/metrics_service.proto @@ -38,4 +38,9 @@ message MetricsServiceConfig { // Eventually (https://github.com/envoyproxy/envoy/issues/10968) if this value is not set, the // sink will take updates from the :ref:`MetricsResponse `. google.protobuf.BoolValue report_counters_as_deltas = 2; + + // If true, metrics will have their tags emitted as labels on the metrics objects sent to the MetricsService, + // and the tag extracted name will be used instead of the full name, which may contain values used by the tag + // extractor or additional tags added during stats creation. + bool emit_tags_as_labels = 4; } diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 3301626ccd113..8a0cf028173d9 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -36,5 +36,7 @@ Removed Config or Runtime New Features ------------ +* metric service: added support for sending metric tags as labels. This can be enabled by setting the :ref:`emit_tags_as_labels ` field to true. + Deprecated ---------- diff --git a/generated_api_shadow/envoy/config/metrics/v3/metrics_service.proto b/generated_api_shadow/envoy/config/metrics/v3/metrics_service.proto index 4bb6c77e66c23..e5fab870f8ee6 100644 --- a/generated_api_shadow/envoy/config/metrics/v3/metrics_service.proto +++ b/generated_api_shadow/envoy/config/metrics/v3/metrics_service.proto @@ -38,4 +38,9 @@ message MetricsServiceConfig { // Eventually (https://github.com/envoyproxy/envoy/issues/10968) if this value is not set, the // sink will take updates from the :ref:`MetricsResponse `. google.protobuf.BoolValue report_counters_as_deltas = 2; + + // If true, metrics will have their tags emitted as labels on the metrics objects sent to the MetricsService, + // and the tag extracted name will be used instead of the full name, which may contain values used by the tag + // extractor or additional tags added during stats creation. + bool emit_tags_as_labels = 4; } diff --git a/generated_api_shadow/envoy/config/metrics/v4alpha/metrics_service.proto b/generated_api_shadow/envoy/config/metrics/v4alpha/metrics_service.proto index edc0fcfc4d6e5..570bc2e9d7162 100644 --- a/generated_api_shadow/envoy/config/metrics/v4alpha/metrics_service.proto +++ b/generated_api_shadow/envoy/config/metrics/v4alpha/metrics_service.proto @@ -38,4 +38,9 @@ message MetricsServiceConfig { // Eventually (https://github.com/envoyproxy/envoy/issues/10968) if this value is not set, the // sink will take updates from the :ref:`MetricsResponse `. google.protobuf.BoolValue report_counters_as_deltas = 2; + + // If true, metrics will have their tags emitted as labels on the metrics objects sent to the MetricsService, + // and the tag extracted name will be used instead of the full name, which may contain values used by the tag + // extractor or additional tags added during stats creation. + bool emit_tags_as_labels = 4; } diff --git a/source/extensions/stat_sinks/metrics_service/config.cc b/source/extensions/stat_sinks/metrics_service/config.cc index 832daa3621016..a17539df4e6fc 100644 --- a/source/extensions/stat_sinks/metrics_service/config.cc +++ b/source/extensions/stat_sinks/metrics_service/config.cc @@ -39,7 +39,7 @@ MetricsServiceSinkFactory::createStatsSink(const Protobuf::Message& config, return std::make_unique>( - grpc_metrics_streamer, + grpc_metrics_streamer, sink_config.emit_tags_as_labels(), PROTOBUF_GET_WRAPPED_OR_DEFAULT(sink_config, report_counters_as_deltas, false)); } diff --git a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc index 11734fe336fe2..14d0d2043bb48 100644 --- a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc +++ b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc @@ -82,10 +82,8 @@ MetricsPtr MetricsFlusher::flush(Stats::MetricSnapshot& snapshot) const { void MetricsFlusher::flushCounter(io::prometheus::client::MetricFamily& metrics_family, const Stats::MetricSnapshot::CounterSnapshot& counter_snapshot, int64_t snapshot_time_ms) const { - metrics_family.set_type(io::prometheus::client::MetricType::COUNTER); - metrics_family.set_name(counter_snapshot.counter_.get().name()); - auto* metric = metrics_family.add_metric(); - metric->set_timestamp_ms(snapshot_time_ms); + auto* metric = populateMetricsFamily(metrics_family, io::prometheus::client::MetricType::COUNTER, + snapshot_time_ms, counter_snapshot.counter_.get()); auto* counter_metric = metric->mutable_counter(); if (report_counters_as_deltas_) { counter_metric->set_value(counter_snapshot.delta_); @@ -96,10 +94,8 @@ void MetricsFlusher::flushCounter(io::prometheus::client::MetricFamily& metrics_ void MetricsFlusher::flushGauge(io::prometheus::client::MetricFamily& metrics_family, const Stats::Gauge& gauge, int64_t snapshot_time_ms) const { - metrics_family.set_type(io::prometheus::client::MetricType::GAUGE); - metrics_family.set_name(gauge.name()); - auto* metric = metrics_family.add_metric(); - metric->set_timestamp_ms(snapshot_time_ms); + auto* metric = populateMetricsFamily(metrics_family, io::prometheus::client::MetricType::GAUGE, + snapshot_time_ms, gauge); auto* gauge_metric = metric->mutable_gauge(); gauge_metric->set_value(gauge.value()); } @@ -113,10 +109,9 @@ void MetricsFlusher::flushHistogram(io::prometheus::client::MetricFamily& summar // performance. // Add summary information for histograms. - summary_metrics_family.set_type(io::prometheus::client::MetricType::SUMMARY); - summary_metrics_family.set_name(envoy_histogram.name()); - auto* summary_metric = summary_metrics_family.add_metric(); - summary_metric->set_timestamp_ms(snapshot_time_ms); + auto* summary_metric = + populateMetricsFamily(summary_metrics_family, io::prometheus::client::MetricType::SUMMARY, + snapshot_time_ms, envoy_histogram); auto* summary = summary_metric->mutable_summary(); const Stats::HistogramStatistics& hist_stats = envoy_histogram.intervalStatistics(); for (size_t i = 0; i < hist_stats.supportedQuantiles().size(); i++) { @@ -126,10 +121,9 @@ void MetricsFlusher::flushHistogram(io::prometheus::client::MetricFamily& summar } // Add bucket information for histograms. - histogram_metrics_family.set_type(io::prometheus::client::MetricType::HISTOGRAM); - histogram_metrics_family.set_name(envoy_histogram.name()); - auto* histogram_metric = histogram_metrics_family.add_metric(); - histogram_metric->set_timestamp_ms(snapshot_time_ms); + auto* histogram_metric = + populateMetricsFamily(histogram_metrics_family, io::prometheus::client::MetricType::HISTOGRAM, + snapshot_time_ms, envoy_histogram); auto* histogram = histogram_metric->mutable_histogram(); histogram->set_sample_count(hist_stats.sampleCount()); histogram->set_sample_sum(hist_stats.sampleSum()); @@ -139,6 +133,31 @@ void MetricsFlusher::flushHistogram(io::prometheus::client::MetricFamily& summar bucket->set_cumulative_count(hist_stats.computedBuckets()[i]); } } + +io::prometheus::client::Metric* +MetricsFlusher::populateMetricsFamily(io::prometheus::client::MetricFamily& metrics_family, + io::prometheus::client::MetricType type, + int64_t snapshot_time_ms, const Stats::Metric& metric) const { + metrics_family.set_type(type); + auto* prometheus_metric = metrics_family.add_metric(); + prometheus_metric->set_timestamp_ms(snapshot_time_ms); + + if (emit_labels_) { + // TODO(snowp): Look into the perf implication of this. We need to take a lock on the symbol + // table to stringify the StatNames, which could result in some lock contention. Consider + // caching the conversion between stat handle to extracted tags. + metrics_family.set_name(metric.tagExtractedName()); + for (const auto& tag : metric.tags()) { + auto* label = prometheus_metric->add_label(); + label->set_name(tag.name_); + label->set_value(tag.value_); + } + } else { + metrics_family.set_name(metric.name()); + } + + return prometheus_metric; +} } // namespace MetricsService } // namespace StatSinks } // namespace Extensions diff --git a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h index 782d887caf7f7..7baef22c5bc07 100644 --- a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h +++ b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h @@ -82,8 +82,8 @@ using GrpcMetricsStreamerImplPtr = std::unique_ptr; class MetricsFlusher { public: - explicit MetricsFlusher(const bool report_counters_as_deltas) - : report_counters_as_deltas_(report_counters_as_deltas) {} + MetricsFlusher(bool report_counters_as_deltas, bool emit_labels) + : report_counters_as_deltas_(report_counters_as_deltas), emit_labels_(emit_labels) {} MetricsPtr flush(Stats::MetricSnapshot& snapshot) const; @@ -98,7 +98,13 @@ class MetricsFlusher { const Stats::ParentHistogram& envoy_histogram, int64_t snapshot_time_ms) const; + io::prometheus::client::Metric* + populateMetricsFamily(io::prometheus::client::MetricFamily& metrics_family, + io::prometheus::client::MetricType type, int64_t snapshot_time_ms, + const Stats::Metric& metric) const; + const bool report_counters_as_deltas_; + const bool emit_labels_; }; /** @@ -109,8 +115,9 @@ template class MetricsServiceSink : pu // MetricsService::Sink MetricsServiceSink( const GrpcMetricsStreamerSharedPtr& grpc_metrics_streamer, - const bool report_counters_as_deltas) - : flusher_(report_counters_as_deltas), grpc_metrics_streamer_(grpc_metrics_streamer) {} + bool report_counters_as_deltas, bool emit_labels) + : flusher_(report_counters_as_deltas, emit_labels), + grpc_metrics_streamer_(grpc_metrics_streamer) {} void flush(Stats::MetricSnapshot& snapshot) override { grpc_metrics_streamer_->send(flusher_.flush(snapshot)); diff --git a/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc b/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc index bafd9bc579606..3b77c71ceaed2 100644 --- a/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc +++ b/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc @@ -111,7 +111,7 @@ class MetricsServiceSinkTest : public testing::Test { TEST_F(MetricsServiceSinkTest, CheckSendCall) { MetricsServiceSink - sink(streamer_, false); + sink(streamer_, false, false); auto counter = std::make_shared>(); counter->name_ = "test_counter"; @@ -137,7 +137,7 @@ TEST_F(MetricsServiceSinkTest, CheckSendCall) { TEST_F(MetricsServiceSinkTest, CheckStatsCount) { MetricsServiceSink - sink(streamer_, false); + sink(streamer_, false, false); auto counter = std::make_shared>(); counter->name_ = "test_counter"; @@ -168,7 +168,7 @@ TEST_F(MetricsServiceSinkTest, CheckStatsCount) { TEST_F(MetricsServiceSinkTest, ReportCountersValues) { MetricsServiceSink - sink(streamer_, false); + sink(streamer_, false, false); auto counter = std::make_shared>(); counter->name_ = "test_counter"; @@ -187,7 +187,7 @@ TEST_F(MetricsServiceSinkTest, ReportCountersValues) { TEST_F(MetricsServiceSinkTest, ReportCountersAsDeltas) { MetricsServiceSink - sink(streamer_, true); + sink(streamer_, true, false); auto counter = std::make_shared>(); counter->name_ = "test_counter"; @@ -202,6 +202,86 @@ TEST_F(MetricsServiceSinkTest, ReportCountersAsDeltas) { sink.flush(snapshot_); } +// Test the behavior of tag emission based on the emit_tags_as_label flag. +TEST_F(MetricsServiceSinkTest, ReportMetricsWithTags) { + auto counter = std::make_shared>(); + counter->name_ = "full-counter-name"; + counter->value_ = 100; + counter->used_ = true; + counter->setTagExtractedName("tag-counter-name"); + counter->setTags({{"a", "b"}}); + snapshot_.counters_.push_back({1, *counter}); + + auto gauge = std::make_shared>(); + gauge->name_ = "full-gauge-name"; + gauge->value_ = 100; + gauge->used_ = true; + gauge->setTagExtractedName("tag-gauge-name"); + gauge->setTags({{"a", "b"}}); + snapshot_.gauges_.push_back({*gauge}); + + auto histogram = std::make_shared>(); + histogram->name_ = "full-histogram-name"; + histogram->used_ = true; + histogram->setTagExtractedName("tag-histogram-name"); + histogram->setTags({{"a", "b"}}); + snapshot_.histograms_.push_back({*histogram}); + + { + // When the emit_tags flag is false, we don't emit the tags and use the full name. + MetricsServiceSink + sink(streamer_, true, false); + + EXPECT_CALL(*streamer_, send(_)).WillOnce(Invoke([](MetricsPtr&& metrics) { + EXPECT_EQ(4, metrics->size()); + + EXPECT_EQ("full-counter-name", (*metrics)[0].name()); + EXPECT_EQ(0, (*metrics)[0].metric(0).label().size()); + + EXPECT_EQ("full-gauge-name", (*metrics)[1].name()); + EXPECT_EQ(0, (*metrics)[1].metric(0).label().size()); + + EXPECT_EQ("full-histogram-name", (*metrics)[2].name()); + EXPECT_EQ(0, (*metrics)[2].metric(0).label().size()); + + EXPECT_EQ("full-histogram-name", (*metrics)[3].name()); + EXPECT_EQ(0, (*metrics)[3].metric(0).label().size()); + })); + sink.flush(snapshot_); + } + + io::prometheus::client::LabelPair expected_label_pair; + expected_label_pair.set_name("a"); + expected_label_pair.set_value("b"); + + // When the emit_tags flag is true, we emit the tags as labels and use the tag extracted name. + MetricsServiceSink + sink(streamer_, true, true); + + EXPECT_CALL(*streamer_, send(_)).WillOnce(Invoke([&expected_label_pair](MetricsPtr&& metrics) { + EXPECT_EQ(4, metrics->size()); + + EXPECT_EQ("tag-counter-name", (*metrics)[0].name()); + EXPECT_EQ(1, (*metrics)[0].metric(0).label().size()); + EXPECT_TRUE(TestUtility::protoEqual(expected_label_pair, (*metrics)[0].metric(0).label()[0])); + + EXPECT_EQ("tag-gauge-name", (*metrics)[1].name()); + EXPECT_EQ(1, (*metrics)[1].metric(0).label().size()); + EXPECT_TRUE(TestUtility::protoEqual(expected_label_pair, (*metrics)[0].metric(0).label()[0])); + + EXPECT_EQ("tag-histogram-name", (*metrics)[2].name()); + EXPECT_EQ(1, (*metrics)[2].metric(0).label().size()); + EXPECT_TRUE(TestUtility::protoEqual(expected_label_pair, (*metrics)[0].metric(0).label()[0])); + + EXPECT_EQ("tag-histogram-name", (*metrics)[3].name()); + EXPECT_EQ(1, (*metrics)[3].metric(0).label().size()); + EXPECT_TRUE(TestUtility::protoEqual(expected_label_pair, (*metrics)[0].metric(0).label()[0])); + })); + sink.flush(snapshot_); +} + } // namespace } // namespace MetricsService } // namespace StatSinks From f40e279f2a30ac80f5c1d21f88302989727eb4cc Mon Sep 17 00:00:00 2001 From: Jose Ulises Nino Rivera Date: Mon, 26 Apr 2021 18:54:23 -0500 Subject: [PATCH 077/209] apple dns: fix crash on invalid dns name (#16028) The release assert in question was ahead of verifying the return code. Risk Level: low - most (possibly all) envoy production usage does not happen on apple hardware. Testing: added new test that repro. Fixes #16021 Signed-off-by: Jose Nino --- source/common/network/apple_dns_impl.cc | 4 ++-- test/common/network/apple_dns_impl_test.cc | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/source/common/network/apple_dns_impl.cc b/source/common/network/apple_dns_impl.cc index 8e8afff94fcf0..45d082ad38ea9 100644 --- a/source/common/network/apple_dns_impl.cc +++ b/source/common/network/apple_dns_impl.cc @@ -302,8 +302,6 @@ void AppleDnsResolverImpl::PendingResolution::onDNSServiceGetAddrInfoReply( "error_code={}, hostname={}", dns_name_, flags, flags & kDNSServiceFlagsMoreComing ? "yes" : "no", flags & kDNSServiceFlagsAdd ? "yes" : "no", interface_index, error_code, hostname); - RELEASE_ASSERT(interface_index == 0, - fmt::format("unexpected interface_index={}", interface_index)); if (!pending_cb_) { pending_cb_ = {ResolutionStatus::Success, {}}; @@ -326,6 +324,8 @@ void AppleDnsResolverImpl::PendingResolution::onDNSServiceGetAddrInfoReply( return; } + ENVOY_BUG(interface_index == 0, fmt::format("unexpected interface_index={}", interface_index)); + // Only add this address to the list if kDNSServiceFlagsAdd is set. Callback targets are only // additive. if (flags & kDNSServiceFlagsAdd) { diff --git a/test/common/network/apple_dns_impl_test.cc b/test/common/network/apple_dns_impl_test.cc index 7cc40baf6f193..1dc149dbf1274 100644 --- a/test/common/network/apple_dns_impl_test.cc +++ b/test/common/network/apple_dns_impl_test.cc @@ -155,6 +155,20 @@ TEST_F(AppleDnsImplTest, DnsIpAddressVersion) { dispatcher_->run(Event::Dispatcher::RunType::Block); } +TEST_F(AppleDnsImplTest, DnsIpAddressVersionInvalid) { + EXPECT_NE(nullptr, resolveWithExpectations("invalidDnsName", DnsLookupFamily::Auto, + DnsResolver::ResolutionStatus::Failure, false)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + + EXPECT_NE(nullptr, resolveWithExpectations("invalidDnsName", DnsLookupFamily::V4Only, + DnsResolver::ResolutionStatus::Failure, false)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + + EXPECT_NE(nullptr, resolveWithExpectations("invalidDnsName", DnsLookupFamily::V6Only, + DnsResolver::ResolutionStatus::Failure, false)); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + TEST_F(AppleDnsImplTest, CallbackException) { EXPECT_NE(nullptr, resolveWithException("google.com", DnsLookupFamily::V4Only)); EXPECT_THROW_WITH_MESSAGE(dispatcher_->run(Event::Dispatcher::RunType::Block), EnvoyException, From 4622fee2cbcc7e5f7e9782d384ccbaae6c715d7e Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 27 Apr 2021 00:59:17 +0100 Subject: [PATCH 078/209] docs: Update refs: v2 -> v3 (#16163) Signed-off-by: Ryan Northey --- docs/root/api/client_features.rst | 6 +++--- .../configuration/http/http_conn_man/stats.rst | 2 +- .../http/http_filters/aws_lambda_filter.rst | 5 ++--- .../http/http_filters/fault_filter.rst | 4 ++-- .../sni_dynamic_forward_proxy_filter.rst | 4 ++-- .../listeners/udp_filters/dns_filter.rst | 2 +- .../observability/access_log/overview.rst | 2 +- .../operations/tools/router_check.rst | 2 +- docs/root/configuration/overview/versioning.rst | 16 +--------------- docs/root/configuration/overview/xds_api.rst | 6 +++--- .../intro/arch_overview/advanced/attributes.rst | 12 ++++++------ .../intro/arch_overview/http/http_routing.rst | 8 ++++---- .../upstream/load_balancing/excluded.rst | 2 +- .../upstream/load_balancing/load_balancers.rst | 8 ++++---- docs/root/operations/certificates.rst | 2 +- docs/root/operations/tools/config_generator.rst | 2 +- .../operations/tools/route_table_check_tool.rst | 2 +- 17 files changed, 35 insertions(+), 50 deletions(-) diff --git a/docs/root/api/client_features.rst b/docs/root/api/client_features.rst index 07f0bd259a53a..67e73283b7eb0 100644 --- a/docs/root/api/client_features.rst +++ b/docs/root/api/client_features.rst @@ -4,8 +4,8 @@ Well Known Client Features ========================== Authoritative list of features that an xDS client may support. An xDS client supplies the list of -features it supports in the :ref:`client_features ` field. -Client features use reverse DNS naming scheme, for example `com.acme.feature`. +features it supports in the :ref:`client_features ` field. +Client features use reverse DNS naming scheme, for example ``com.acme.feature``. Currently Defined Client Features --------------------------------- @@ -17,7 +17,7 @@ Currently Defined Client Features *udpa.type.v1.TypedStruct* only. - **envoy.lb.does_not_support_overprovisioning**: This feature indicates that the client does not support overprovisioning for priority failover and locality weighting as configured by the - :ref:`overprovisioning_factor` + :ref:`overprovisioning_factor ` field. If graceful failover functionality is required, it must be supplied by the management server. - **envoy.lrs.supports_send_all_clusters**: This feature indicates that the client supports diff --git a/docs/root/configuration/http/http_conn_man/stats.rst b/docs/root/configuration/http/http_conn_man/stats.rst index 9c7d441b7fff2..f43cd7812ac0c 100644 --- a/docs/root/configuration/http/http_conn_man/stats.rst +++ b/docs/root/configuration/http/http_conn_man/stats.rst @@ -142,7 +142,7 @@ On the upstream side all http2 statistics are rooted at *cluster..http2.* rx_messaging_error, Counter, Total number of invalid received frames that violated `section 8 `_ of the HTTP/2 spec. This will result in a *tx_reset* rx_reset, Counter, Total number of reset stream frames received by Envoy trailers, Counter, Total number of trailers seen on requests coming from downstream - tx_flush_timeout, Counter, Total number of :ref:`stream idle timeouts ` waiting for open stream window to flush the remainder of a stream + tx_flush_timeout, Counter, Total number of :ref:`stream idle timeouts ` waiting for open stream window to flush the remainder of a stream tx_reset, Counter, Total number of reset stream frames transmitted by Envoy keepalive_timeout, Counter, Total number of connections closed due to :ref:`keepalive timeout ` streams_active, Gauge, Active streams as observed by the codec diff --git a/docs/root/configuration/http/http_filters/aws_lambda_filter.rst b/docs/root/configuration/http/http_filters/aws_lambda_filter.rst index d281de9b0ab7c..38813aaeb8e05 100644 --- a/docs/root/configuration/http/http_filters/aws_lambda_filter.rst +++ b/docs/root/configuration/http/http_filters/aws_lambda_filter.rst @@ -187,13 +187,12 @@ Statistics ---------- The AWS Lambda filter outputs statistics in the *http..aws_lambda.* namespace. The -:ref:`stat prefix ` +| :ref:`stat prefix ` comes from the owning HTTP connection manager. .. csv-table:: :header: Name, Type, Description :widths: 1, 1, 2 - server_error, Counter, Total requests that returned invalid JSON response (see :ref:`payload_passthrough `) + server_error, Counter, Total requests that returned invalid JSON response (see :ref:`payload_passthrough `) upstream_rq_payload_size, Histogram, Size in bytes of the request after JSON-tranformation (if any). - diff --git a/docs/root/configuration/http/http_filters/fault_filter.rst b/docs/root/configuration/http/http_filters/fault_filter.rst index 2dbd48c588467..45b8a0c7866de 100644 --- a/docs/root/configuration/http/http_filters/fault_filter.rst +++ b/docs/root/configuration/http/http_filters/fault_filter.rst @@ -47,7 +47,7 @@ x-envoy-fault-abort-grpc-request the gRPC status code to return in response to a request. Its value range is [0, UInt32.Max] instead of [0, 16] to allow testing even not well-defined gRPC status codes. When this header is set, the HTTP response status code will be set to 200. In order for the header to work, :ref:`header_abort - ` needs to be set. If both + ` needs to be set. If both *x-envoy-fault-abort-request* and *x-envoy-fault-abort-grpc-request* headers are set then *x-envoy-fault-abort-grpc-request* header will be **ignored** and fault response http status code will be set to *x-envoy-fault-abort-request* header value. @@ -163,7 +163,7 @@ fault.http.abort.grpc_status aborted if the headers match. Defaults to the gRPC status code specified in the config. If this field is missing from both the runtime and the config, gRPC status code in the response will be derived from *fault.http.abort.http_status* field. This runtime key is only available when - the filter is :ref:`configured for abort `. + the filter is :ref:`configured for abort `. fault.http.delay.fixed_delay_percent % of requests that will be delayed if the headers match. Defaults to the diff --git a/docs/root/configuration/listeners/network_filters/sni_dynamic_forward_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/sni_dynamic_forward_proxy_filter.rst index ddd9fa7b94899..00635117c1954 100644 --- a/docs/root/configuration/listeners/network_filters/sni_dynamic_forward_proxy_filter.rst +++ b/docs/root/configuration/listeners/network_filters/sni_dynamic_forward_proxy_filter.rst @@ -9,14 +9,14 @@ SNI dynamic forward proxy Through the combination of :ref:`TLS inspector ` listener filter, this network filter and the -:ref:`dynamic forward proxy cluster `, +:ref:`dynamic forward proxy cluster `, Envoy supports SNI based dynamic forward proxy. The implementation works just like the :ref:`HTTP dynamic forward proxy `, but using the value in SNI as target host instead. The following is a complete configuration that configures both this filter as well as the :ref:`dynamic forward proxy cluster -`. Both filter and cluster +`. Both filter and cluster must be configured together and point to the same DNS cache parameters for Envoy to operate as an SNI dynamic forward proxy. diff --git a/docs/root/configuration/listeners/udp_filters/dns_filter.rst b/docs/root/configuration/listeners/udp_filters/dns_filter.rst index ca4bcaa106bb9..d2243351f3403 100644 --- a/docs/root/configuration/listeners/udp_filters/dns_filter.rst +++ b/docs/root/configuration/listeners/udp_filters/dns_filter.rst @@ -123,7 +123,7 @@ adhere to the convention outlined in the RFC. The filter can also consume its domain configuration from an external DNS table. The same entities appearing in the static configuration can be stored as JSON or YAML in a separate file and referenced -using the :ref:`external_dns_table DataSource ` directive: +using the :ref:`external_dns_table DataSource ` directive: Example External DnsTable Configuration --------------------------------------- diff --git a/docs/root/configuration/observability/access_log/overview.rst b/docs/root/configuration/observability/access_log/overview.rst index 33b29018b9127..c8d73cc901ca7 100644 --- a/docs/root/configuration/observability/access_log/overview.rst +++ b/docs/root/configuration/observability/access_log/overview.rst @@ -3,4 +3,4 @@ Overview * Access logging :ref:`architecture overview ` * :ref:`Configuration overview ` -* :ref:`v2 API reference ` +* :ref:`v3 API reference ` diff --git a/docs/root/configuration/operations/tools/router_check.rst b/docs/root/configuration/operations/tools/router_check.rst index 41bfb4d66f496..befaf853c8787 100644 --- a/docs/root/configuration/operations/tools/router_check.rst +++ b/docs/root/configuration/operations/tools/router_check.rst @@ -168,7 +168,7 @@ validate and "date" fields, as well as custom headers set in the input or by the route. The header fields are checked after all other test cases. Thus, the header fields checked will be those of the redirected or rewritten routes when applicable. - - Matchers are specified as :ref:`HeaderMatchers `, and behave the same way. + - Matchers are specified as :ref:`HeaderMatchers `, and behave the same way. Coverage -------- diff --git a/docs/root/configuration/overview/versioning.rst b/docs/root/configuration/overview/versioning.rst index 46c141ff03cce..7bf7510659e3c 100644 --- a/docs/root/configuration/overview/versioning.rst +++ b/docs/root/configuration/overview/versioning.rst @@ -1,25 +1,11 @@ Versioning ---------- -The Envoy xDS APIs follow a well defined :repo:`versioning scheme `. Envoy -supports :ref:`multiple major versions ` at any point in time. The examples -in this section are taken from the v2 xDS API. +The Envoy xDS APIs follow a well defined :repo:`versioning scheme `. Envoy has API versions for both the xDS transport, i.e. the wire protocol for moving resources between a management server and Envoy, and for resources. These are known as the transport and resource API version respectively. -The transport and resource version may be mixed. For example, v3 resources may be transferred over -the v2 transport protocol. In addition, an Envoy may consume mixed resource versions for distinct -resource types. For example, :ref:`v3 Clusters ` may be -used alongside :ref:`v2 Listeners `. - Both the transport and resource API versions follow the API versioning support and deprecation :repo:`policy `. - -.. note:: - - Envoy will internally operate at the latest xDS resource version and all supported versioned - resources will be transparently upgrading to this latest version on configuration ingestion. For - example, v2 and v3 resources, delivered over either a v2 or v3 transport, or any mix thereof, - will be internally converted into v3 resources. diff --git a/docs/root/configuration/overview/xds_api.rst b/docs/root/configuration/overview/xds_api.rst index be4f0ff9aaa5a..dca8e99265c3d 100644 --- a/docs/root/configuration/overview/xds_api.rst +++ b/docs/root/configuration/overview/xds_api.rst @@ -260,7 +260,7 @@ is set in the :ref:`rds .. note:: - The management server responding to these endpoints must respond with a :ref:`DiscoveryResponse ` + The management server responding to these endpoints must respond with a :ref:`DiscoveryResponse ` along with a HTTP status of 200. Additionally, if the configuration that would be supplied has not changed (as indicated by the version supplied by the Envoy client) then the management server can respond with an empty body and a HTTP status of 304. @@ -377,9 +377,9 @@ Currently the behavior when a TTL expires is that the resource is *removed* (as previous version). As such, this feature should primarily be used for use cases where the absence of the resource is preferred instead of the temporary version, e.g. when using RTDS to apply a temporary runtime override. -The TTL is specified on the :ref:`Resource ` proto: for Delta xDS this is specified directly +The TTL is specified on the :ref:`Resource ` proto: for Delta xDS this is specified directly within the response, while for SotW xDS the server may wrap individual resources listed in the response within a -:ref:`Resource ` in order to specify a TTL value. +:ref:`Resource ` in order to specify a TTL value. The server can refresh or modify the TTL by issuing another response for the same version. In this case the resource itself does not have to be included. diff --git a/docs/root/intro/arch_overview/advanced/attributes.rst b/docs/root/intro/arch_overview/advanced/attributes.rst index 767a75b2d4c45..69031b5869f29 100644 --- a/docs/root/intro/arch_overview/advanced/attributes.rst +++ b/docs/root/intro/arch_overview/advanced/attributes.rst @@ -153,7 +153,7 @@ Data exchanged between filters is available as the following attributes: :header: Attribute, Type, Description :widths: 1, 1, 4 - metadata, :ref:`Metadata`, Dynamic request metadata + metadata, :ref:`Metadata`, Dynamic request metadata filter_state, "map", Mapping from a filter state name to its serialized string value Note that these attributes may change during the life of a request as the data can be @@ -171,14 +171,14 @@ In addition to all above, the following extra attributes are available to Wasm e plugin_name, string, Plugin name plugin_root_id, string, Plugin root ID plugin_vm_id, string, Plugin VM ID - node, :ref:`Node`, Local node description + node, :ref:`Node`, Local node description cluster_name, string, Upstream cluster name - cluster_metadata, :ref:`Metadata`, Upstream cluster metadata + cluster_metadata, :ref:`Metadata`, Upstream cluster metadata listener_direction, int, Enumeration value of the :ref:`listener traffic direction` - listener_metadata, :ref:`Metadata`, Listener metadata + listener_metadata, :ref:`Metadata`, Listener metadata route_name, string, Route name - route_metadata, :ref:`Metadata`, Route metadata - upstream_host_metadata, :ref:`Metadata`, Upstream host metadata + route_metadata, :ref:`Metadata`, Route metadata + upstream_host_metadata, :ref:`Metadata`, Upstream host metadata Path expressions ---------------- diff --git a/docs/root/intro/arch_overview/http/http_routing.rst b/docs/root/intro/arch_overview/http/http_routing.rst index 65f66cb622aed..8162187215909 100644 --- a/docs/root/intro/arch_overview/http/http_routing.rst +++ b/docs/root/intro/arch_overview/http/http_routing.rst @@ -53,8 +53,8 @@ Route Scope ----------- Scoped routing enables Envoy to put constraints on search space of domains and route rules. -A :ref:`Route Scope` associates a key with a :ref:`route table `. -For each request, a scope key is computed dynamically by the HTTP connection manager to pick the :ref:`route table`. +A :ref:`Route Scope ` associates a key with a :ref:`route table `. +For each request, a scope key is computed dynamically by the HTTP connection manager to pick the :ref:`route table `. RouteConfiguration associated with scope can be loaded on demand with :ref:`v3 API reference ` configured and on demand filed in protobuf set to true. The Scoped RDS (SRDS) API contains a set of :ref:`Scopes ` resources, each defining independent routing configuration, @@ -110,7 +110,7 @@ headers `. The following configurat * **Retry conditions**: Envoy can retry on different types of conditions depending on application requirements. For example, network failure, all 5xx response codes, idempotent 4xx response codes, etc. -* **Retry budgets**: Envoy can limit the proportion of active requests via :ref:`retry budgets ` that can be retries to +* **Retry budgets**: Envoy can limit the proportion of active requests via :ref:`retry budgets ` that can be retries to prevent their contribution to large increases in traffic volume. * **Host selection retry plugins**: Envoy can be configured to apply additional logic to the host selection logic when selecting hosts for retries. Specifying a @@ -122,7 +122,7 @@ headers `. The following configurat Note that Envoy retries requests when :ref:`x-envoy-overloaded ` is present. It is recommended to either configure -:ref:`retry budgets (preferred) ` or set +:ref:`retry budgets (preferred) ` or set :ref:`maximum active retries circuit breaker ` to an appropriate value to avoid retry storms. .. _arch_overview_http_routing_hedging: diff --git a/docs/root/intro/arch_overview/upstream/load_balancing/excluded.rst b/docs/root/intro/arch_overview/upstream/load_balancing/excluded.rst index 11c7531d0505e..6d025a3a0efbd 100644 --- a/docs/root/intro/arch_overview/upstream/load_balancing/excluded.rst +++ b/docs/root/intro/arch_overview/upstream/load_balancing/excluded.rst @@ -22,7 +22,7 @@ Currently, the following two conditions can lead to a host being excluded when u health checking: * Using the :ref:`ignore_new_hosts_until_first_hc - ` cluster option. + ` cluster option. * Receiving the :ref:`x-envoy-immediate-health-check-fail ` header in a normal routed response or in response to an :ref:`HTTP active health check diff --git a/docs/root/intro/arch_overview/upstream/load_balancing/load_balancers.rst b/docs/root/intro/arch_overview/upstream/load_balancing/load_balancers.rst index ad256fa644134..f3e4a4bd56f84 100644 --- a/docs/root/intro/arch_overview/upstream/load_balancing/load_balancers.rst +++ b/docs/root/intro/arch_overview/upstream/load_balancing/load_balancers.rst @@ -73,7 +73,7 @@ the ring. This technique is also commonly known as `"Ketama" ` e.g.: +in the ``"envoy.lb"`` :ref:`LbEndpoint.Metadata ` e.g.: .. validated-code-block:: yaml :type-name: envoy.config.core.v3.Metadata @@ -82,7 +82,7 @@ in the ``"envoy.lb"`` :ref:`LbEndpoint.Metadata `. +This will override :ref:`use_hostname_for_hashing `. Each host is hashed and placed on the ring some number of times proportional to its weight. For example, if host A has a weight of 1 and host B has a weight of 2, then there might be three entries @@ -114,7 +114,7 @@ any place in which consistent hashing is desired. Like the ring hash load balanc hashing load balancer is only effective when protocol routing is used that specifies a value to hash on. If you want something other than the host's address to be used as the hash key (e.g. the semantic name of your host in a Kubernetes StatefulSet), then you can specify it in the ``"envoy.lb"`` -:ref:`LbEndpoint.Metadata ` e.g.: +:ref:`LbEndpoint.Metadata ` e.g.: .. validated-code-block:: yaml :type-name: envoy.config.core.v3.Metadata @@ -123,7 +123,7 @@ semantic name of your host in a Kubernetes StatefulSet), then you can specify it envoy.lb: hash_key: "YOUR HASH KEY" -This will override :ref:`use_hostname_for_hashing`. +This will override :ref:`use_hostname_for_hashing `. The table construction algorithm places each host in the table some number of times proportional to its weight, until the table is completely filled. For example, if host A has a weight of 1 and diff --git a/docs/root/operations/certificates.rst b/docs/root/operations/certificates.rst index 0dff05aa93602..ad9ae547aebca 100644 --- a/docs/root/operations/certificates.rst +++ b/docs/root/operations/certificates.rst @@ -5,7 +5,7 @@ Certificate Management Envoy provides several mechanisms for cert management. At a high level they can be broken into -1. Static :ref:`CommonTlsContext ` referenced certificates. +1. Static :ref:`CommonTlsContext ` referenced certificates. These will *not* reload automatically, and requires either a restart of the proxy or reloading the clusters/listeners that reference them. :ref:`Hot restarting ` can be used here to pick up the new diff --git a/docs/root/operations/tools/config_generator.rst b/docs/root/operations/tools/config_generator.rst index dfe75bf4ebb32..02bda90aa8a1d 100644 --- a/docs/root/operations/tools/config_generator.rst +++ b/docs/root/operations/tools/config_generator.rst @@ -33,7 +33,7 @@ A few notes about the example configurations: templates for different instances of this. * Tracing is configured for `LightStep `_. To disable this or enable `Zipkin `_ or `Datadog `_ tracing, delete or - change the :ref:`tracing configuration ` accordingly. + change the :ref:`tracing configuration ` accordingly. * The configuration demonstrates the use of a :ref:`global rate limiting service `. To disable this delete the :ref:`rate limit configuration `. diff --git a/docs/root/operations/tools/route_table_check_tool.rst b/docs/root/operations/tools/route_table_check_tool.rst index 2d07c38315279..fb5e6778762b4 100644 --- a/docs/root/operations/tools/route_table_check_tool.rst +++ b/docs/root/operations/tools/route_table_check_tool.rst @@ -19,7 +19,7 @@ Usage -c , --config-path Path to a v2 router config file (YAML or JSON). The router config file schema is found in - :ref:`config ` and the config file extension + :ref:`config ` and the config file extension must reflect its file type (for instance, .json for JSON and .yaml for YAML). -d, --details From 87d5eb7d44e4b24b2d098ce073f8742610c5d03e Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 27 Apr 2021 01:04:17 +0100 Subject: [PATCH 079/209] dependabot: Resolve updates (#16169) Signed-off-by: Ryan Northey Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 4 +- tools/dependency/requirements.txt | 141 +++++++++++++++++++---- tools/deprecate_version/requirements.txt | 87 ++++++++++++-- tools/github/requirements.txt | 6 +- 4 files changed, 202 insertions(+), 36 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 611d0203b3bf0..987496efe1037 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -219,7 +219,9 @@ typing-extensions==3.7.4.3 \ --hash=sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918 \ --hash=sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c \ --hash=sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f - # via gitpython + # via + # -r docs/requirements.txt + # gitpython urllib3==1.26.4 \ --hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \ --hash=sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937 diff --git a/tools/dependency/requirements.txt b/tools/dependency/requirements.txt index 91e76f0911602..311fcbd6d18ee 100644 --- a/tools/dependency/requirements.txt +++ b/tools/dependency/requirements.txt @@ -1,16 +1,105 @@ -PyGithub==1.54.1 \ - --hash=sha256:87afd6a67ea582aa7533afdbf41635725f13d12581faed7e3e04b1579c0c0627 \ - --hash=sha256:300bc16e62886ca6537b0830e8f516ea4bc3ef12d308e0c5aff8bdbd099173d4 -requests==2.25.1 \ - --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e \ - --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 -Deprecated==1.2.12 \ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile --generate-hashes tools/dependency/requirements.txt +# +certifi==2020.12.5 \ + --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c \ + --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 + # via + # -r tools/dependency/requirements.txt + # requests +cffi==1.14.5 \ + --hash=sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813 \ + --hash=sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06 \ + --hash=sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea \ + --hash=sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee \ + --hash=sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396 \ + --hash=sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73 \ + --hash=sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315 \ + --hash=sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1 \ + --hash=sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49 \ + --hash=sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892 \ + --hash=sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482 \ + --hash=sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058 \ + --hash=sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5 \ + --hash=sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53 \ + --hash=sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045 \ + --hash=sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3 \ + --hash=sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5 \ + --hash=sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e \ + --hash=sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c \ + --hash=sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369 \ + --hash=sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827 \ + --hash=sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053 \ + --hash=sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa \ + --hash=sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4 \ + --hash=sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322 \ + --hash=sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132 \ + --hash=sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62 \ + --hash=sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa \ + --hash=sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0 \ + --hash=sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396 \ + --hash=sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e \ + --hash=sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991 \ + --hash=sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6 \ + --hash=sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1 \ + --hash=sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406 \ + --hash=sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d \ + --hash=sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c + # via pynacl +chardet==4.0.0 \ + --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \ + --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 + # via + # -r tools/dependency/requirements.txt + # requests +deprecated==1.2.12 \ --hash=sha256:08452d69b6b5bc66e8330adde0a4f8642e969b9e1702904d137eeb29c8ffc771 \ --hash=sha256:6d2de2de7931a968874481ef30208fd4e08da39177d61d3d4ebdf4366e7dbca1 -PyJWT==1.7.1 \ - --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ - --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 -PyYAML==5.4.1 \ + # via + # -r tools/dependency/requirements.txt + # pygithub +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 + # via + # -r tools/dependency/requirements.txt + # requests +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 + # via cffi +pygithub==1.55 \ + --hash=sha256:1bbfff9372047ff3f21d5cd8e07720f3dbfdaf6462fcaed9d815f528f1ba7283 \ + --hash=sha256:2caf0054ea079b71e539741ae56c5a95e073b81fa472ce222e81667381b9601b + # via -r tools/dependency/requirements.txt +pyjwt==2.0.1 \ + --hash=sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7 \ + --hash=sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847 + # via pygithub +pynacl==1.4.0 \ + --hash=sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4 \ + --hash=sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4 \ + --hash=sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574 \ + --hash=sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d \ + --hash=sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634 \ + --hash=sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25 \ + --hash=sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f \ + --hash=sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505 \ + --hash=sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122 \ + --hash=sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7 \ + --hash=sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420 \ + --hash=sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f \ + --hash=sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96 \ + --hash=sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6 \ + --hash=sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6 \ + --hash=sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514 \ + --hash=sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff \ + --hash=sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80 + # via pygithub +pyyaml==5.4.1 \ --hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \ --hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \ --hash=sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393 \ @@ -40,17 +129,25 @@ PyYAML==5.4.1 \ --hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \ --hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \ --hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0 -certifi==2020.12.5 \ - --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 \ - --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c -chardet==4.0.0 \ - --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \ - --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa -idna==2.10 \ - --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ - --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 -wrapt==1.12.1 \ - --hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7 + # via -r tools/dependency/requirements.txt +requests==2.25.1 \ + --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \ + --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e + # via + # -r tools/dependency/requirements.txt + # pygithub +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced + # via pynacl urllib3==1.26.4 \ --hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \ --hash=sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937 + # via + # -r tools/dependency/requirements.txt + # requests +wrapt==1.12.1 \ + --hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7 + # via + # -r tools/dependency/requirements.txt + # deprecated diff --git a/tools/deprecate_version/requirements.txt b/tools/deprecate_version/requirements.txt index c374c121ce1eb..c49422bdc0fe1 100644 --- a/tools/deprecate_version/requirements.txt +++ b/tools/deprecate_version/requirements.txt @@ -10,6 +10,45 @@ certifi==2020.12.5 \ # via # -r tools/deprecate_version/requirements.txt # requests +cffi==1.14.5 \ + --hash=sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813 \ + --hash=sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06 \ + --hash=sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea \ + --hash=sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee \ + --hash=sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396 \ + --hash=sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73 \ + --hash=sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315 \ + --hash=sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1 \ + --hash=sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49 \ + --hash=sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892 \ + --hash=sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482 \ + --hash=sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058 \ + --hash=sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5 \ + --hash=sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53 \ + --hash=sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045 \ + --hash=sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3 \ + --hash=sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5 \ + --hash=sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e \ + --hash=sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c \ + --hash=sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369 \ + --hash=sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827 \ + --hash=sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053 \ + --hash=sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa \ + --hash=sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4 \ + --hash=sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322 \ + --hash=sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132 \ + --hash=sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62 \ + --hash=sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa \ + --hash=sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0 \ + --hash=sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396 \ + --hash=sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e \ + --hash=sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991 \ + --hash=sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6 \ + --hash=sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1 \ + --hash=sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406 \ + --hash=sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d \ + --hash=sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c + # via pynacl chardet==4.0.0 \ --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \ --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 @@ -38,22 +77,48 @@ idna==2.10 \ # via # -r tools/deprecate_version/requirements.txt # requests -pygithub==1.54.1 \ - --hash=sha256:300bc16e62886ca6537b0830e8f516ea4bc3ef12d308e0c5aff8bdbd099173d4 \ - --hash=sha256:87afd6a67ea582aa7533afdbf41635725f13d12581faed7e3e04b1579c0c0627 +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 + # via cffi +pygithub==1.55 \ + --hash=sha256:1bbfff9372047ff3f21d5cd8e07720f3dbfdaf6462fcaed9d815f528f1ba7283 \ + --hash=sha256:2caf0054ea079b71e539741ae56c5a95e073b81fa472ce222e81667381b9601b # via -r tools/deprecate_version/requirements.txt -pyjwt==1.7.1 \ - --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ - --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 - # via - # -r tools/deprecate_version/requirements.txt - # pygithub +pyjwt==2.0.1 \ + --hash=sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7 \ + --hash=sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847 + # via pygithub +pynacl==1.4.0 \ + --hash=sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4 \ + --hash=sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4 \ + --hash=sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574 \ + --hash=sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d \ + --hash=sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634 \ + --hash=sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25 \ + --hash=sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f \ + --hash=sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505 \ + --hash=sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122 \ + --hash=sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7 \ + --hash=sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420 \ + --hash=sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f \ + --hash=sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96 \ + --hash=sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6 \ + --hash=sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6 \ + --hash=sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514 \ + --hash=sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff \ + --hash=sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80 + # via pygithub requests==2.25.1 \ --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \ --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e # via # -r tools/deprecate_version/requirements.txt # pygithub +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced + # via pynacl smmap==4.0.0 \ --hash=sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182 \ --hash=sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2 @@ -64,7 +129,9 @@ typing-extensions==3.7.4.3 \ --hash=sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918 \ --hash=sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c \ --hash=sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f - # via gitpython + # via + # -r tools/deprecate_version/requirements.txt + # gitpython urllib3==1.26.4 \ --hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \ --hash=sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937 diff --git a/tools/github/requirements.txt b/tools/github/requirements.txt index b38097a0710eb..603347540d225 100644 --- a/tools/github/requirements.txt +++ b/tools/github/requirements.txt @@ -1,3 +1,3 @@ -PyGithub==1.54.1 \ - --hash=sha256:87afd6a67ea582aa7533afdbf41635725f13d12581faed7e3e04b1579c0c0627 \ - --hash=sha256:300bc16e62886ca6537b0830e8f516ea4bc3ef12d308e0c5aff8bdbd099173d4 +PyGithub==1.55 \ + --hash=sha256:2caf0054ea079b71e539741ae56c5a95e073b81fa472ce222e81667381b9601b \ + --hash=sha256:1bbfff9372047ff3f21d5cd8e07720f3dbfdaf6462fcaed9d815f528f1ba7283 From 25d9d30ee9d0e2bbbb836188ae8ef88fccb06446 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Tue, 27 Apr 2021 12:40:26 +0000 Subject: [PATCH 080/209] Fix tests that link all extensions so that gcc can compile them (#16187) Signed-off-by: Yan Avlasov --- test/exe/BUILD | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/exe/BUILD b/test/exe/BUILD index 27038447c8aa7..d0b9fd1939f28 100644 --- a/test/exe/BUILD +++ b/test/exe/BUILD @@ -67,7 +67,7 @@ envoy_cc_test( ], deps = [ "//source/common/api:api_lib", - "//source/exe:main_common_lib", + "//source/exe:envoy_main_common_with_core_extensions_lib", "//source/exe:platform_impl_lib", "//test/mocks/runtime:runtime_mocks", "//test/test_common:contention_lib", @@ -79,10 +79,13 @@ envoy_cc_test( name = "extra_extensions_test", srcs = ["extra_extensions_test.cc"], deps = [ - # This dependency MUST be main_common_lib to meet the purpose of this test - "//source/exe:main_common_lib", "//test/test_common:environment_lib", - ], + ] + select({ + # gcc RBE build has trouble compiling target with all extensions + "//bazel:gcc_build": ["//source/exe:envoy_main_common_with_core_extensions_lib"], + # This dependency MUST be main_common_lib to meet the purpose of this test + "//conditions:default": ["//source/exe:main_common_lib"], + }), ) envoy_cc_test( From d20dbc4be73d2e185ecc3a8159ca95150e204aaa Mon Sep 17 00:00:00 2001 From: Anirudha Pratap Singh Date: Tue, 27 Apr 2021 18:21:54 +0530 Subject: [PATCH 081/209] http: raise max_request_headers_kb limit to 8192 KiB (8MiB) from 96 KiB (#15921) Commit Message: Raise max configurable max_request_headers_kb limit to 8192 KiB (8MiB) from 96 KiB in http connection manager protobuf. Additional Description: Added/Updated relevant unit tests and integration tests. This change will allow increasing max_request_headers_kb to 8MiB from http connection manager's configuration. The change is mainly updating the limit in a validation check in the protobuf. Also, the old (merged) PR #5859 is read, no nghttp2 library-related issues are observed on raising the max_request_headers_kb beyond 96 KiB. Risk Level: Low Testing: Unit, Integration, Manual Docs Changes: Inline in proto file. Signed-off-by: Anirudha Singh --- .../v2/http_connection_manager.proto | 4 +- .../v3/http_connection_manager.proto | 4 +- .../v4alpha/http_connection_manager.proto | 4 +- docs/root/version_history/current.rst | 1 + .../v2/http_connection_manager.proto | 4 +- .../v3/http_connection_manager.proto | 4 +- .../v4alpha/http_connection_manager.proto | 4 +- test/common/http/http1/codec_impl_test.cc | 59 +++++++++++++++++-- test/common/http/http2/codec_impl_test.cc | 9 ++- .../http_connection_manager/config_test.cc | 4 +- test/integration/protocol_integration_test.cc | 18 +++++- 11 files changed, 84 insertions(+), 31 deletions(-) diff --git a/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto b/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto index c05032df21a4d..3e7a4dc17769c 100644 --- a/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +++ b/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto @@ -299,10 +299,8 @@ message HttpConnectionManager { // The maximum request headers size for incoming connections. // If unconfigured, the default max request headers allowed is 60 KiB. // Requests that exceed this limit will receive a 431 response. - // The max configurable limit is 96 KiB, based on current implementation - // constraints. google.protobuf.UInt32Value max_request_headers_kb = 29 - [(validate.rules).uint32 = {lte: 96 gt: 0}]; + [(validate.rules).uint32 = {lte: 8192 gt: 0}]; // The idle timeout for connections managed by the connection manager. The // idle timeout is defined as the period in which there are no active diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 66575c40b1ccc..9ede9a6454351 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -343,10 +343,8 @@ message HttpConnectionManager { // The maximum request headers size for incoming connections. // If unconfigured, the default max request headers allowed is 60 KiB. // Requests that exceed this limit will receive a 431 response. - // The max configurable limit is 96 KiB, based on current implementation - // constraints. google.protobuf.UInt32Value max_request_headers_kb = 29 - [(validate.rules).uint32 = {lte: 96 gt: 0}]; + [(validate.rules).uint32 = {lte: 8192 gt: 0}]; // The stream idle timeout for connections managed by the connection manager. // If not specified, this defaults to 5 minutes. The default value was selected diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index b292548832e13..67cb8194415e0 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -346,10 +346,8 @@ message HttpConnectionManager { // The maximum request headers size for incoming connections. // If unconfigured, the default max request headers allowed is 60 KiB. // Requests that exceed this limit will receive a 431 response. - // The max configurable limit is 96 KiB, based on current implementation - // constraints. google.protobuf.UInt32Value max_request_headers_kb = 29 - [(validate.rules).uint32 = {lte: 96 gt: 0}]; + [(validate.rules).uint32 = {lte: 8192 gt: 0}]; // The stream idle timeout for connections managed by the connection manager. // If not specified, this defaults to 5 minutes. The default value was selected diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 8a0cf028173d9..edcf65e6cf792 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -19,6 +19,7 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* +* http: raise max configurable max_request_headers_kb limit to 8192 KiB (8MiB) from 96 KiB in http connection manager. * validation: fix an issue that causes TAP sockets to panic during config validation mode. * xray: fix the default sampling 'rate' for AWS X-Ray tracer extension to be 5% as opposed to 50%. * zipkin: fix timestamp serializaiton in annotations. A prior bug fix exposed an issue with timestamps being serialized as strings. diff --git a/generated_api_shadow/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto b/generated_api_shadow/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto index c05032df21a4d..3e7a4dc17769c 100644 --- a/generated_api_shadow/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +++ b/generated_api_shadow/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto @@ -299,10 +299,8 @@ message HttpConnectionManager { // The maximum request headers size for incoming connections. // If unconfigured, the default max request headers allowed is 60 KiB. // Requests that exceed this limit will receive a 431 response. - // The max configurable limit is 96 KiB, based on current implementation - // constraints. google.protobuf.UInt32Value max_request_headers_kb = 29 - [(validate.rules).uint32 = {lte: 96 gt: 0}]; + [(validate.rules).uint32 = {lte: 8192 gt: 0}]; // The idle timeout for connections managed by the connection manager. The // idle timeout is defined as the period in which there are no active diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index c9c6ac3a55573..d9343fcb0fbb7 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -349,10 +349,8 @@ message HttpConnectionManager { // The maximum request headers size for incoming connections. // If unconfigured, the default max request headers allowed is 60 KiB. // Requests that exceed this limit will receive a 431 response. - // The max configurable limit is 96 KiB, based on current implementation - // constraints. google.protobuf.UInt32Value max_request_headers_kb = 29 - [(validate.rules).uint32 = {lte: 96 gt: 0}]; + [(validate.rules).uint32 = {lte: 8192 gt: 0}]; // The stream idle timeout for connections managed by the connection manager. // If not specified, this defaults to 5 minutes. The default value was selected diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index b292548832e13..67cb8194415e0 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -346,10 +346,8 @@ message HttpConnectionManager { // The maximum request headers size for incoming connections. // If unconfigured, the default max request headers allowed is 60 KiB. // Requests that exceed this limit will receive a 431 response. - // The max configurable limit is 96 KiB, based on current implementation - // constraints. google.protobuf.UInt32Value max_request_headers_kb = 29 - [(validate.rules).uint32 = {lte: 96 gt: 0}]; + [(validate.rules).uint32 = {lte: 8192 gt: 0}]; // The stream idle timeout for connections managed by the connection manager. // If not specified, this defaults to 5 minutes. The default value was selected diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index 49ab6beb301ab..a05c13d82a0e6 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -48,6 +48,15 @@ std::string createHeaderFragment(int num_headers) { return headers; } +std::string createLargeHeaderFragment(int num_headers) { + // Create a header field with num_headers headers with each header of size ~64 KiB. + std::string headers; + for (int i = 0; i < num_headers; i++) { + headers += "header" + std::to_string(i) + ": " + std::string(64 * 1024, 'q') + "\r\n"; + } + return headers; +} + Buffer::OwnedImpl createBufferWithNByteSlices(absl::string_view input, size_t max_slice_size) { Buffer::OwnedImpl buffer; for (size_t offset = 0; offset < input.size(); offset += max_slice_size) { @@ -2858,6 +2867,12 @@ TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersRejected) { testRequestHeadersExceedLimit(long_string, "headers size exceeds limit", ""); } +TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersRejectedBeyondMaxConfigurable) { + max_request_headers_kb_ = 8192; + std::string long_string = "big: " + std::string(8193 * 1024, 'q') + "\r\n"; + testRequestHeadersExceedLimit(long_string, "headers size exceeds limit", ""); +} + // Tests that the default limit for the number of request headers is 100. TEST_F(Http1ServerConnectionImplTest, ManyRequestHeadersRejected) { // Send a request with 101 headers. @@ -2894,6 +2909,36 @@ TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersSplitRejected) { EXPECT_EQ("http1.headers_too_large", response_encoder->getStream().responseDetails()); } +TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersSplitRejectedMaxConfigurable) { + max_request_headers_kb_ = 8192; + max_request_headers_count_ = 150; + initialize(); + + std::string exception_reason; + NiceMock decoder; + Http::ResponseEncoder* response_encoder = nullptr; + EXPECT_CALL(callbacks_, newStream(_, _)) + .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { + response_encoder = &encoder; + return decoder; + })); + Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\n"); + auto status = codec_->dispatch(buffer); + + std::string long_string = std::string(64 * 1024, 'q'); + for (int i = 0; i < 127; i++) { + buffer = Buffer::OwnedImpl(fmt::format("big: {}\r\n", long_string)); + status = codec_->dispatch(buffer); + } + // the 128th 64kb header should induce overflow + buffer = Buffer::OwnedImpl(fmt::format("big: {}\r\n", long_string)); + EXPECT_CALL(decoder, sendLocalReply(_, _, _, _, _, _)); + status = codec_->dispatch(buffer); + EXPECT_TRUE(isCodecProtocolError(status)); + EXPECT_EQ(status.message(), "headers size exceeds limit"); + EXPECT_EQ("http1.headers_too_large", response_encoder->getStream().responseDetails()); +} + // Tests that the 101th request header causes overflow with the default max number of request // headers. TEST_F(Http1ServerConnectionImplTest, ManyRequestHeadersSplitRejected) { @@ -2924,14 +2969,14 @@ TEST_F(Http1ServerConnectionImplTest, ManyRequestHeadersSplitRejected) { } TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersAccepted) { - max_request_headers_kb_ = 65; - std::string long_string = "big: " + std::string(64 * 1024, 'q') + "\r\n"; + max_request_headers_kb_ = 4096; + std::string long_string = "big: " + std::string(1024 * 1024, 'q') + "\r\n"; testRequestHeadersAccepted(long_string); } TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersAcceptedMaxConfigurable) { - max_request_headers_kb_ = 96; - std::string long_string = "big: " + std::string(95 * 1024, 'q') + "\r\n"; + max_request_headers_kb_ = 8192; + std::string long_string = "big: " + std::string(8191 * 1024, 'q') + "\r\n"; testRequestHeadersAccepted(long_string); } @@ -2942,6 +2987,12 @@ TEST_F(Http1ServerConnectionImplTest, ManyRequestHeadersAccepted) { testRequestHeadersAccepted(createHeaderFragment(150)); } +TEST_F(Http1ServerConnectionImplTest, ManyLargeRequestHeadersAccepted) { + max_request_headers_kb_ = 8192; + // Create a request with 64 headers, each header of size ~64 KiB. Total size ~4MB. + testRequestHeadersAccepted(createLargeHeaderFragment(64)); +} + // Tests that incomplete response headers of 80 kB header value fails. TEST_F(Http1ClientConnectionImplTest, ResponseHeadersWithLargeValueRejected) { initialize(); diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index b6bdf85aa97e8..f6327caaa71bf 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -2176,15 +2176,14 @@ TEST_P(Http2CodecImplTest, ManyLargeRequestHeadersUnderPerHeaderLimit) { } TEST_P(Http2CodecImplTest, LargeRequestHeadersAtMaxConfigurable) { - // Raising the limit past this triggers some unexpected nghttp2 error. - // Further debugging required to increase past ~96 KiB. - max_request_headers_kb_ = 96; + max_request_headers_kb_ = 8192; + max_request_headers_count_ = 150; initialize(); TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); - std::string long_string = std::string(1024, 'q'); - for (int i = 0; i < 95; i++) { + std::string long_string = std::string(63 * 1024, 'q'); + for (int i = 0; i < 129; i++) { request_headers.addCopy(std::to_string(i), long_string); } diff --git a/test/extensions/filters/network/http_connection_manager/config_test.cc b/test/extensions/filters/network/http_connection_manager/config_test.cc index 367f615749be0..89cd004b8dd96 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_test.cc @@ -687,7 +687,7 @@ TEST_F(HttpConnectionManagerConfigTest, MaxRequestHeadersKbConfigured) { TEST_F(HttpConnectionManagerConfigTest, MaxRequestHeadersKbMaxConfigurable) { const std::string yaml_string = R"EOF( stat_prefix: ingress_http - max_request_headers_kb: 96 + max_request_headers_kb: 8192 route_config: name: local_route http_filters: @@ -698,7 +698,7 @@ TEST_F(HttpConnectionManagerConfigTest, MaxRequestHeadersKbMaxConfigurable) { date_provider_, route_config_provider_manager_, scoped_routes_config_provider_manager_, http_tracer_manager_, filter_config_provider_manager_); - EXPECT_EQ(96, config.maxRequestHeadersKb()); + EXPECT_EQ(8192, config.maxRequestHeadersKb()); } // Validated that an explicit zero stream idle timeout disables. diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 1a184f8275100..6228dd40b0a07 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -1573,11 +1573,25 @@ TEST_P(DownstreamProtocolIntegrationTest, LargeRequestHeadersRejected) { testLargeRequestHeaders(95, 1, 60, 100); } +TEST_P(DownstreamProtocolIntegrationTest, VeryLargeRequestHeadersRejected) { + EXCLUDE_DOWNSTREAM_HTTP3 + EXCLUDE_UPSTREAM_HTTP3; + // Send one very large 2048 kB (2 MB) header with limit 1024 kB (1 MB) and 100 headers. + testLargeRequestHeaders(2048, 1, 1024, 100); +} + TEST_P(DownstreamProtocolIntegrationTest, LargeRequestHeadersAccepted) { EXCLUDE_DOWNSTREAM_HTTP3 EXCLUDE_UPSTREAM_HTTP3; // requires configurable header limits. - // Send one 95 kB header with limit 96 kB and 100 headers. - testLargeRequestHeaders(95, 1, 96, 100); + // Send one 100 kB header with limit 8192 kB (8 MB) and 100 headers. + testLargeRequestHeaders(100, 1, 8192, 100); +} + +TEST_P(DownstreamProtocolIntegrationTest, ManyLargeRequestHeadersAccepted) { + EXCLUDE_DOWNSTREAM_HTTP3 + EXCLUDE_UPSTREAM_HTTP3; + // Send 70 headers each of size 100 kB with limit 8192 kB (8 MB) and 100 headers. + testLargeRequestHeaders(100, 70, 8192, 100); } TEST_P(DownstreamProtocolIntegrationTest, ManyRequestHeadersRejected) { From ca4ca983195f337a840618b45b9b33cda82c1224 Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Tue, 27 Apr 2021 06:04:13 -0700 Subject: [PATCH 082/209] grid: Add a new class for tracking alternate protocols for a connection to an origin. (#16127) grid: Add a new class for tracking alternate protocols for a connection to an origin. Create a new AlternateProtocols class which tracks the list of alternate protocols which can be used to make connections to origins. Risk Level: Low Testing: New Unit tests Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Ryan Hamilton --- source/common/http/BUILD | 9 ++ source/common/http/alternate_protocols.cc | 46 ++++++++ source/common/http/alternate_protocols.h | 107 +++++++++++++++++++ test/common/http/BUILD | 11 ++ test/common/http/alternate_protocols_test.cc | 88 +++++++++++++++ 5 files changed, 261 insertions(+) create mode 100644 source/common/http/alternate_protocols.cc create mode 100644 source/common/http/alternate_protocols.h create mode 100644 test/common/http/alternate_protocols_test.cc diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 33cb754e6aa58..94975f555229f 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -149,6 +149,15 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "alternate_protocols", + srcs = ["alternate_protocols.cc"], + hdrs = ["alternate_protocols.h"], + deps = [ + "//include/envoy/common:time_interface", + ], +) + envoy_cc_library( name = "conn_pool_grid", srcs = ["conn_pool_grid.cc"], diff --git a/source/common/http/alternate_protocols.cc b/source/common/http/alternate_protocols.cc new file mode 100644 index 0000000000000..25c7f0860756e --- /dev/null +++ b/source/common/http/alternate_protocols.cc @@ -0,0 +1,46 @@ +#include "common/http/alternate_protocols.h" + +namespace Envoy { +namespace Http { + +AlternateProtocols::AlternateProtocol::AlternateProtocol(absl::string_view alpn, + absl::string_view hostname, int port) + : alpn_(alpn), hostname_(hostname), port_(port) {} + +AlternateProtocols::Origin::Origin(absl::string_view scheme, absl::string_view hostname, int port) + : scheme_(scheme), hostname_(hostname), port_(port) {} + +AlternateProtocols::AlternateProtocols(TimeSource& time_source) : time_source_(time_source) {} + +void AlternateProtocols::setAlternatives(const Origin& origin, + const std::vector& protocols, + const MonotonicTime& expiration) { + Entry& entry = protocols_[origin]; + if (entry.protocols_ != protocols) { + entry.protocols_ = protocols; + } + if (entry.expiration_ != expiration) { + entry.expiration_ = expiration; + } +} + +OptRef> +AlternateProtocols::findAlternatives(const Origin& origin) { + auto entry_it = protocols_.find(origin); + if (entry_it == protocols_.end()) { + return makeOptRefFromPtr>(nullptr); + } + + const Entry& entry = entry_it->second; + if (time_source_.monotonicTime() > entry.expiration_) { + // Expire the entry. + protocols_.erase(entry_it); + return makeOptRefFromPtr>(nullptr); + } + return makeOptRef(entry.protocols_); +} + +size_t AlternateProtocols::size() const { return protocols_.size(); } + +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/alternate_protocols.h b/source/common/http/alternate_protocols.h new file mode 100644 index 0000000000000..39e90c887a4ca --- /dev/null +++ b/source/common/http/alternate_protocols.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include + +#include "envoy/common/optref.h" +#include "envoy/common/time.h" + +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Http { + +// Tracks alternate protocols that can be used to make an HTTP connection to an origin server. +// See https://tools.ietf.org/html/rfc7838 for HTTP Alternate Services and +// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04 for the +// "HTTPS" DNS resource record. +class AlternateProtocols { +public: + // Represents an HTTP origin to be connected too. + struct Origin { + public: + Origin(absl::string_view scheme, absl::string_view hostname, int port); + + bool operator==(const Origin& other) const { + return std::tie(scheme_, hostname_, port_) == + std::tie(other.scheme_, other.hostname_, other.port_); + } + + bool operator!=(const Origin& other) const { return !this->operator==(other); } + + bool operator<(const Origin& other) const { + return std::tie(scheme_, hostname_, port_) < + std::tie(other.scheme_, other.hostname_, other.port_); + } + + bool operator>(const Origin& other) const { + return std::tie(scheme_, hostname_, port_) > + std::tie(other.scheme_, other.hostname_, other.port_); + } + + bool operator<=(const Origin& other) const { + return std::tie(scheme_, hostname_, port_) <= + std::tie(other.scheme_, other.hostname_, other.port_); + } + + bool operator>=(const Origin& other) const { + return std::tie(scheme_, hostname_, port_) >= + std::tie(other.scheme_, other.hostname_, other.port_); + } + + std::string scheme_; + std::string hostname_; + int port_{}; + }; + + // Represents an alternative protocol that can be used to connect to an origin. + struct AlternateProtocol { + public: + AlternateProtocol(absl::string_view alpn, absl::string_view hostname, int port); + + bool operator==(const AlternateProtocol& other) const { + return std::tie(alpn_, hostname_, port_) == + std::tie(other.alpn_, other.hostname_, other.port_); + } + + bool operator!=(const AlternateProtocol& other) const { return !this->operator==(other); } + + std::string alpn_; + std::string hostname_; + int port_; + }; + + explicit AlternateProtocols(TimeSource& time_source); + + // Sets the possible alternative protocols which can be used to connect to the + // specified origin. Expires after the specified expiration time. + void setAlternatives(const Origin& origin, const std::vector& protocols, + const MonotonicTime& expiration); + + // Returns the possible alternative protocols which can be used to connect to the + // specified origin, or nullptr if not alternatives are found. The returned pointer + // is owned by the AlternateProtocols and is valid until the next operation on + // AlternateProtocols. + OptRef> findAlternatives(const Origin& origin); + + // Returns the number of entries in the map. + size_t size() const; + +private: + struct Entry { + std::vector protocols_; + MonotonicTime expiration_; + }; + + // Time source used to check expiration of entries. + TimeSource& time_source_; + + // Map from hostname to list of alternate protocols. + // TODO(RyanTheOptimist): Add a limit to the size of this map and evict based on usage. + std::map protocols_; +}; + +} // namespace Http +} // namespace Envoy diff --git a/test/common/http/BUILD b/test/common/http/BUILD index 953c744753ca8..0e6db1c8eb932 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -440,6 +440,17 @@ envoy_cc_test( ]), ) +envoy_cc_test( + name = "alternate_protocols_test", + srcs = ["alternate_protocols_test.cc"], + deps = [ + ":common_lib", + "//source/common/http:alternate_protocols", + "//test/mocks:common_lib", + "//test/test_common:simulated_time_system_lib", + ], +) + envoy_proto_library( name = "path_utility_fuzz_proto", srcs = ["path_utility_fuzz.proto"], diff --git a/test/common/http/alternate_protocols_test.cc b/test/common/http/alternate_protocols_test.cc new file mode 100644 index 0000000000000..211b2313fb3b0 --- /dev/null +++ b/test/common/http/alternate_protocols_test.cc @@ -0,0 +1,88 @@ +#include "common/http/alternate_protocols.h" + +#include "test/test_common/simulated_time_system.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Http { + +namespace { +class AlternateProtocolsTest : public testing::Test, public Event::TestUsingSimulatedTime { +public: + AlternateProtocolsTest() : protocols_(simTime()) {} + + AlternateProtocols protocols_; + const std::string hostname1_ = "hostname1"; + const std::string hostname2_ = "hostname2"; + const int port1_ = 1; + const int port2_ = 2; + const std::string https_ = "https"; + const std::string http_ = "http"; + + const std::string alpn1_ = "alpn1"; + const std::string alpn2_ = "alpn2"; + + const AlternateProtocols::Origin origin1_ = {https_, hostname1_, port1_}; + const AlternateProtocols::Origin origin2_ = {https_, hostname2_, port2_}; + + const AlternateProtocols::AlternateProtocol protocol1_ = {alpn1_, hostname1_, port1_}; + const AlternateProtocols::AlternateProtocol protocol2_ = {alpn2_, hostname2_, port2_}; + + const std::vector protocols1_ = {protocol1_}; + const std::vector protocols2_ = {protocol2_}; + + const MonotonicTime expiration1_ = simTime().monotonicTime() + Seconds(5); + const MonotonicTime expiration2_ = simTime().monotonicTime() + Seconds(10); +}; + +TEST_F(AlternateProtocolsTest, Init) { EXPECT_EQ(0, protocols_.size()); } + +TEST_F(AlternateProtocolsTest, SetAlternatives) { + EXPECT_EQ(0, protocols_.size()); + protocols_.setAlternatives(origin1_, protocols1_, expiration1_); + EXPECT_EQ(1, protocols_.size()); +} + +TEST_F(AlternateProtocolsTest, FindAlternatives) { + protocols_.setAlternatives(origin1_, protocols1_, expiration1_); + OptRef> protocols = + protocols_.findAlternatives(origin1_); + ASSERT_TRUE(protocols.has_value()); + EXPECT_EQ(protocols1_, protocols.ref()); +} + +TEST_F(AlternateProtocolsTest, FindAlternativesAfterReplacement) { + protocols_.setAlternatives(origin1_, protocols1_, expiration1_); + protocols_.setAlternatives(origin1_, protocols2_, expiration2_); + OptRef> protocols = + protocols_.findAlternatives(origin1_); + ASSERT_TRUE(protocols.has_value()); + EXPECT_EQ(protocols2_, protocols.ref()); + EXPECT_NE(protocols1_, protocols.ref()); +} + +TEST_F(AlternateProtocolsTest, FindAlternativesForMultipleOrigins) { + protocols_.setAlternatives(origin1_, protocols1_, expiration1_); + protocols_.setAlternatives(origin2_, protocols2_, expiration2_); + OptRef> protocols = + protocols_.findAlternatives(origin1_); + ASSERT_TRUE(protocols.has_value()); + EXPECT_EQ(protocols1_, protocols.ref()); + + protocols = protocols_.findAlternatives(origin2_); + EXPECT_EQ(protocols2_, protocols.ref()); +} + +TEST_F(AlternateProtocolsTest, FindAlternativesAfterExpiration) { + protocols_.setAlternatives(origin1_, protocols1_, expiration1_); + simTime().setMonotonicTime(expiration1_ + Seconds(1)); + OptRef> protocols = + protocols_.findAlternatives(origin1_); + ASSERT_FALSE(protocols.has_value()); + EXPECT_EQ(0, protocols_.size()); +} + +} // namespace +} // namespace Http +} // namespace Envoy From 4a0dfd5df93d6aac5c04e869c3cb3b3587767b41 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 27 Apr 2021 17:07:00 +0100 Subject: [PATCH 083/209] docs: Use intersphinx to map old versions and cleanup version history (#16155) This PR - adds a patched version of intersphinx (~from sphinx-doc/sphinx#8981) - uses versioned refs for version history - cleans up inline literals in version history Signed-off-by: Ryan Northey --- .yapfignore | 1 + docs/_ext/intersphinx_custom.py | 435 +++++++++++++++++++++++ docs/conf.py | 40 ++- docs/root/version_history/current.rst | 16 +- docs/root/version_history/v1.1.0.rst | 22 +- docs/root/version_history/v1.10.0.rst | 94 ++--- docs/root/version_history/v1.11.0.rst | 120 +++---- docs/root/version_history/v1.11.1.rst | 26 +- docs/root/version_history/v1.11.2.rst | 12 +- docs/root/version_history/v1.12.0.rst | 194 +++++----- docs/root/version_history/v1.12.2.rst | 2 +- docs/root/version_history/v1.12.3.rst | 6 +- docs/root/version_history/v1.12.4.rst | 2 +- docs/root/version_history/v1.12.5.rst | 6 +- docs/root/version_history/v1.12.6.rst | 2 +- docs/root/version_history/v1.13.0.rst | 96 ++--- docs/root/version_history/v1.13.1.rst | 6 +- docs/root/version_history/v1.13.2.rst | 2 +- docs/root/version_history/v1.13.3.rst | 6 +- docs/root/version_history/v1.13.4.rst | 2 +- docs/root/version_history/v1.14.0.rst | 140 ++++---- docs/root/version_history/v1.14.2.rst | 6 +- docs/root/version_history/v1.14.3.rst | 6 +- docs/root/version_history/v1.14.4.rst | 2 +- docs/root/version_history/v1.14.7.rst | 2 +- docs/root/version_history/v1.15.0.rst | 184 +++++----- docs/root/version_history/v1.15.4.rst | 7 +- docs/root/version_history/v1.16.0.rst | 232 ++++++------ docs/root/version_history/v1.16.3.rst | 7 +- docs/root/version_history/v1.17.0.rst | 138 +++---- docs/root/version_history/v1.17.1.rst | 3 +- docs/root/version_history/v1.17.2.rst | 9 +- docs/root/version_history/v1.18.0.rst | 217 ++++++----- docs/root/version_history/v1.2.0.rst | 20 +- docs/root/version_history/v1.3.0.rst | 48 +-- docs/root/version_history/v1.4.0.rst | 68 ++-- docs/root/version_history/v1.5.0.rst | 94 ++--- docs/root/version_history/v1.6.0.rst | 110 +++--- docs/root/version_history/v1.7.0.rst | 150 ++++---- docs/root/version_history/v1.8.0.rst | 122 +++---- docs/root/version_history/v1.9.0.rst | 116 +++--- docs/root/version_history/v1.9.1.rst | 4 +- tools/code_format/check_format.py | 9 +- tools/code_format/format_python_tools.py | 8 +- 44 files changed, 1630 insertions(+), 1162 deletions(-) create mode 100644 docs/_ext/intersphinx_custom.py diff --git a/.yapfignore b/.yapfignore index 0409026f278ae..f0b729545dc90 100644 --- a/.yapfignore +++ b/.yapfignore @@ -5,3 +5,4 @@ *_pb2.py *tests* *#* +*intersphinx_custom.py diff --git a/docs/_ext/intersphinx_custom.py b/docs/_ext/intersphinx_custom.py new file mode 100644 index 0000000000000..2ce91ca74bdd7 --- /dev/null +++ b/docs/_ext/intersphinx_custom.py @@ -0,0 +1,435 @@ +# +# This module has been copied from sphinx.ext in order to workaround a +# sphinx issue described here: +# https://github.com/sphinx-doc/sphinx/issues/2068 +# +# using the fix from: +# https://github.com/sphinx-doc/sphinx/pull/8981 +# +# Once upstream is fixed this module should be removed. +# +# Envoy tracking bug is here: +# https://github.com/envoyproxy/envoy/issues/16181 +# +""" + sphinx.ext.intersphinx + ~~~~~~~~~~~~~~~~~~~~~~ + + Insert links to objects documented in remote Sphinx documentation. + + This works as follows: + + * Each Sphinx HTML build creates a file named "objects.inv" that contains a + mapping from object names to URIs relative to the HTML set's root. + + * Projects using the Intersphinx extension can specify links to such mapping + files in the `intersphinx_mapping` config value. The mapping will then be + used to resolve otherwise missing references to objects into links to the + other documentation. + + * By default, the mapping file is assumed to be at the same location as the + rest of the documentation; however, the location of the mapping file can + also be specified individually, e.g. if the docs should be buildable + without Internet access. + + :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import concurrent.futures +import functools +import posixpath +import sys +import time +from os import path +from typing import IO, Any, Dict, List, Tuple +from urllib.parse import urlsplit, urlunsplit + +from docutils import nodes +from docutils.nodes import Element, TextElement +from docutils.utils import relative_path + +import sphinx +from sphinx.application import Sphinx +from sphinx.builders.html import INVENTORY_FILENAME +from sphinx.config import Config +from sphinx.environment import BuildEnvironment +from sphinx.locale import _, __ +from sphinx.util import logging, requests +from sphinx.util.inventory import InventoryFile +from sphinx.util.typing import Inventory + +logger = logging.getLogger(__name__) + + +class InventoryAdapter: + """Inventory adapter for environment""" + + def __init__(self, env: BuildEnvironment) -> None: + self.env = env + + if not hasattr(env, 'intersphinx_cache'): + self.env.intersphinx_cache = {} # type: ignore + self.env.intersphinx_inventory = {} # type: ignore + self.env.intersphinx_named_inventory = {} # type: ignore + + @property + def cache(self) -> Dict[str, Tuple[str, int, Inventory]]: + return self.env.intersphinx_cache # type: ignore + + @property + def main_inventory(self) -> Inventory: + return self.env.intersphinx_inventory # type: ignore + + @property + def named_inventory(self) -> Dict[str, Inventory]: + return self.env.intersphinx_named_inventory # type: ignore + + def clear(self) -> None: + self.env.intersphinx_inventory.clear() # type: ignore + self.env.intersphinx_named_inventory.clear() # type: ignore + + +def _strip_basic_auth(url: str) -> str: + """Returns *url* with basic auth credentials removed. Also returns the + basic auth username and password if they're present in *url*. + + E.g.: https://user:pass@example.com => https://example.com + + *url* need not include basic auth credentials. + + :param url: url which may or may not contain basic auth credentials + :type url: ``str`` + + :return: *url* with any basic auth creds removed + :rtype: ``str`` + """ + frags = list(urlsplit(url)) + # swap out "user[:pass]@hostname" for "hostname" + if '@' in frags[1]: + frags[1] = frags[1].split('@')[1] + return urlunsplit(frags) + + +def _read_from_url(url: str, config: Config = None) -> IO: + """Reads data from *url* with an HTTP *GET*. + + This function supports fetching from resources which use basic HTTP auth as + laid out by RFC1738 § 3.1. See § 5 for grammar definitions for URLs. + + .. seealso: + + https://www.ietf.org/rfc/rfc1738.txt + + :param url: URL of an HTTP resource + :type url: ``str`` + + :return: data read from resource described by *url* + :rtype: ``file``-like object + """ + r = requests.get(url, stream=True, config=config, timeout=config.intersphinx_timeout) + r.raise_for_status() + r.raw.url = r.url + # decode content-body based on the header. + # ref: https://github.com/kennethreitz/requests/issues/2155 + r.raw.read = functools.partial(r.raw.read, decode_content=True) + return r.raw + + +def _get_safe_url(url: str) -> str: + """Gets version of *url* with basic auth passwords obscured. This function + returns results suitable for printing and logging. + + E.g.: https://user:12345@example.com => https://user@example.com + + :param url: a url + :type url: ``str`` + + :return: *url* with password removed + :rtype: ``str`` + """ + parts = urlsplit(url) + if parts.username is None: + return url + else: + frags = list(parts) + if parts.port: + frags[1] = '{}@{}:{}'.format(parts.username, parts.hostname, parts.port) + else: + frags[1] = '{}@{}'.format(parts.username, parts.hostname) + + return urlunsplit(frags) + + +def fetch_inventory(app: Sphinx, uri: str, inv: Any) -> Any: + """Fetch, parse and return an intersphinx inventory file.""" + # both *uri* (base URI of the links to generate) and *inv* (actual + # location of the inventory file) can be local or remote URIs + localuri = '://' not in uri + if not localuri: + # case: inv URI points to remote resource; strip any existing auth + uri = _strip_basic_auth(uri) + try: + if '://' in inv: + f = _read_from_url(inv, config=app.config) + else: + f = open(path.join(app.srcdir, inv), 'rb') + except Exception as err: + err.args = ('intersphinx inventory %r not fetchable due to %s: %s', + inv, err.__class__, str(err)) + raise + try: + if hasattr(f, 'url'): + newinv = f.url # type: ignore + if inv != newinv: + logger.info(__('intersphinx inventory has moved: %s -> %s'), inv, newinv) + + if uri in (inv, path.dirname(inv), path.dirname(inv) + '/'): + uri = path.dirname(newinv) + with f: + try: + join = path.join if localuri else posixpath.join + invdata = InventoryFile.load(f, uri, join) + except ValueError as exc: + raise ValueError('unknown or unsupported inventory version: %r' % exc) from exc + except Exception as err: + err.args = ('intersphinx inventory %r not readable due to %s: %s', + inv, err.__class__.__name__, str(err)) + raise + else: + return invdata + + +def fetch_inventory_group( + name: str, uri: str, invs: Any, cache: Any, app: Any, now: float +) -> bool: + cache_time = now - app.config.intersphinx_cache_limit * 86400 + failures = [] + try: + for inv in invs: + if not inv: + inv = posixpath.join(uri, INVENTORY_FILENAME) + # decide whether the inventory must be read: always read local + # files; remote ones only if the cache time is expired + if '://' not in inv or uri not in cache or cache[uri][1] < cache_time: + safe_inv_url = _get_safe_url(inv) + logger.info(__('loading intersphinx inventory from %s...'), safe_inv_url) + try: + invdata = fetch_inventory(app, uri, inv) + except Exception as err: + failures.append(err.args) + continue + if invdata: + cache[uri] = (name, now, invdata) + return True + return False + finally: + if failures == []: + pass + elif len(failures) < len(invs): + logger.info(__("encountered some issues with some of the inventories," + " but they had working alternatives:")) + for fail in failures: + logger.info(*fail) + else: + issues = '\n'.join([f[0] % f[1:] for f in failures]) + logger.warning(__("failed to reach any of the inventories " + "with the following issues:") + "\n" + issues) + + +def load_mappings(app: Sphinx) -> None: + """Load all intersphinx mappings into the environment.""" + now = int(time.time()) + inventories = InventoryAdapter(app.builder.env) + + with concurrent.futures.ThreadPoolExecutor() as pool: + futures = [] + for name, (uri, invs) in app.config.intersphinx_mapping.values(): + futures.append(pool.submit( + fetch_inventory_group, name, uri, invs, inventories.cache, app, now + )) + updated = [f.result() for f in concurrent.futures.as_completed(futures)] + + if any(updated): + inventories.clear() + + # Duplicate values in different inventories will shadow each + # other; which one will override which can vary between builds + # since they are specified using an unordered dict. To make + # it more consistent, we sort the named inventories and then + # add the unnamed inventories last. This means that the + # unnamed inventories will shadow the named ones but the named + # ones can still be accessed when the name is specified. + cached_vals = list(inventories.cache.values()) + named_vals = sorted(v for v in cached_vals if v[0]) + unnamed_vals = [v for v in cached_vals if not v[0]] + for name, _x, invdata in named_vals + unnamed_vals: + if name: + inventories.named_inventory[name] = invdata + for type, objects in invdata.items(): + inventories.main_inventory.setdefault(type, {}).update(objects) + + +def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: TextElement + ) -> nodes.reference: + """Attempt to resolve a missing reference via intersphinx references.""" + target = node['reftarget'] + inventories = InventoryAdapter(env) + objtypes = None # type: List[str] + if node['reftype'] == 'any': + # we search anything! + objtypes = ['%s:%s' % (domain.name, objtype) + for domain in env.domains.values() + for objtype in domain.object_types] + domain = None + else: + domain = node.get('refdomain') + if not domain: + # only objects in domains are in the inventory + return None + objtypes = env.get_domain(domain).objtypes_for_role(node['reftype']) + if not objtypes: + return None + objtypes = ['%s:%s' % (domain, objtype) for objtype in objtypes] + if 'std:cmdoption' in objtypes: + # until Sphinx-1.6, cmdoptions are stored as std:option + objtypes.append('std:option') + if 'py:attribute' in objtypes: + # Since Sphinx-2.1, properties are stored as py:method + objtypes.append('py:method') + to_try = [(inventories.main_inventory, target)] + if domain: + full_qualified_name = env.get_domain(domain).get_full_qualified_name(node) + if full_qualified_name: + to_try.append((inventories.main_inventory, full_qualified_name)) + in_set = None + if ':' in target: + # first part may be the foreign doc set name + setname, newtarget = target.split(':', 1) + if setname in inventories.named_inventory: + in_set = setname + to_try.append((inventories.named_inventory[setname], newtarget)) + if domain: + node['reftarget'] = newtarget + full_qualified_name = env.get_domain(domain).get_full_qualified_name(node) + if full_qualified_name: + to_try.append((inventories.named_inventory[setname], full_qualified_name)) + elif app.config.intersphinx_strict_prefix: + return None + for inventory, target in to_try: + for objtype in objtypes: + if objtype not in inventory or target not in inventory[objtype]: + continue + proj, version, uri, dispname = inventory[objtype][target] + if '://' not in uri and node.get('refdoc'): + # get correct path in case of subdirectories + uri = path.join(relative_path(node['refdoc'], '.'), uri) + if version: + reftitle = _('(in %s v%s)') % (proj, version) + else: + reftitle = _('(in %s)') % (proj,) + newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle) + if node.get('refexplicit'): + # use whatever title was given + newnode.append(contnode) + elif dispname == '-' or \ + (domain == 'std' and node['reftype'] == 'keyword'): + # use whatever title was given, but strip prefix + title = contnode.astext() + if in_set and title.startswith(in_set + ':'): + newnode.append(contnode.__class__(title[len(in_set) + 1:], + title[len(in_set) + 1:])) + else: + newnode.append(contnode) + else: + # else use the given display name (used for :ref:) + newnode.append(contnode.__class__(dispname, dispname)) + return newnode + # at least get rid of the ':' in the target if no explicit title given + if in_set is not None and not node.get('refexplicit', True): + if len(contnode) and isinstance(contnode[0], nodes.Text): + contnode[0] = nodes.Text(newtarget, contnode[0].rawsource) + + return None + + +def normalize_intersphinx_mapping(app: Sphinx, config: Config) -> None: + for key, value in config.intersphinx_mapping.copy().items(): + try: + if isinstance(value, (list, tuple)): + # new format + name, (uri, inv) = key, value + if not isinstance(name, str): + logger.warning(__('intersphinx identifier %r is not string. Ignored'), + name) + config.intersphinx_mapping.pop(key) + continue + else: + # old format, no name + name, uri, inv = None, key, value + + if not isinstance(inv, tuple): + config.intersphinx_mapping[key] = (name, (uri, (inv,))) + else: + config.intersphinx_mapping[key] = (name, (uri, inv)) + except Exception as exc: + logger.warning(__('Failed to read intersphinx_mapping[%s], ignored: %r'), key, exc) + config.intersphinx_mapping.pop(key) + + +def setup(app: Sphinx) -> Dict[str, Any]: + app.add_config_value('intersphinx_mapping', {}, True) + app.add_config_value('intersphinx_cache_limit', 5, False) + app.add_config_value('intersphinx_timeout', None, False) + app.add_config_value('intersphinx_strict_prefix', True, True) + app.connect('config-inited', normalize_intersphinx_mapping, priority=800) + app.connect('builder-inited', load_mappings) + app.connect('missing-reference', missing_reference) + return { + 'version': sphinx.__display_version__, + 'env_version': 1, + 'parallel_read_safe': True + } + + +def inspect_main(argv: List[str]) -> None: + """Debug functionality to print out an inventory""" + if len(argv) < 1: + print("Print out an inventory file.\n" + "Error: must specify local path or URL to an inventory file.", + file=sys.stderr) + sys.exit(1) + + class MockConfig: + intersphinx_timeout = None # type: int + intersphinx_strict_prefix = True + tls_verify = False + user_agent = None + + class MockApp: + srcdir = '' + config = MockConfig() + + def warn(self, msg: str) -> None: + print(msg, file=sys.stderr) + + try: + filename = argv[0] + invdata = fetch_inventory(MockApp(), '', filename) # type: ignore + for key in sorted(invdata or {}): + print(key) + for entry, einfo in sorted(invdata[key].items()): + print('\t%-40s %s%s' % (entry, + '%-40s: ' % einfo[3] if einfo[3] != '-' else '', + einfo[2])) + except ValueError as exc: + print(exc.args[0] % exc.args[1:]) + except Exception as exc: + print('Unknown error: %r' % exc) + + +if __name__ == '__main__': + import logging as _logging + _logging.basicConfig() + + inspect_main(argv=sys.argv[1:]) diff --git a/docs/conf.py b/docs/conf.py index f3eebfd4fb1ba..5fcc251d00603 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -71,8 +71,9 @@ def setup(app): sys.path.append(os.path.abspath("./_ext")) extensions = [ - 'sphinxcontrib.httpdomain', 'sphinx.ext.extlinks', 'sphinx.ext.ifconfig', 'sphinx_tabs.tabs', - 'sphinx_copybutton', 'validating_code_block', 'sphinxext.rediraffe', 'powershell_lexer' + 'sphinxcontrib.httpdomain', 'sphinx.ext.extlinks', 'sphinx.ext.ifconfig', 'intersphinx_custom', + 'sphinx_tabs.tabs', 'sphinx_copybutton', 'validating_code_block', 'sphinxext.rediraffe', + 'powershell_lexer' ] extlinks = { 'repo': ('https://github.com/envoyproxy/envoy/blob/{}/%s'.format(blob_sha), ''), @@ -286,3 +287,38 @@ def setup(app): # - not sure how diffing will work with main merging in PRs - might need # to be injected dynamically, somehow rediraffe_redirects = "redirects.txt" + +intersphinx_mapping = { + 'v1.5.0': ('https://www.envoyproxy.io/docs/envoy/v1.5.0', None), + 'v1.6.0': ('https://www.envoyproxy.io/docs/envoy/v1.6.0', None), + 'v1.7.0': ('https://www.envoyproxy.io/docs/envoy/v1.7.1', None), + 'v1.8.0': ('https://www.envoyproxy.io/docs/envoy/v1.8.0', None), + 'v1.9.0': ('https://www.envoyproxy.io/docs/envoy/v1.9.0', None), + 'v1.9.1': ('https://www.envoyproxy.io/docs/envoy/v1.9.1', None), + 'v1.10.0': ('https://www.envoyproxy.io/docs/envoy/v1.10.0', None), + 'v1.11.0': ('https://www.envoyproxy.io/docs/envoy/v1.11.0', None), + 'v1.11.1': ('https://www.envoyproxy.io/docs/envoy/v1.11.1', None), + 'v1.11.2': ('https://www.envoyproxy.io/docs/envoy/v1.11.2', None), + 'v1.12.0': ('https://www.envoyproxy.io/docs/envoy/v1.12.0', None), + 'v1.12.2': ('https://www.envoyproxy.io/docs/envoy/v1.12.2', None), + 'v1.12.3': ('https://www.envoyproxy.io/docs/envoy/v1.12.3', None), + 'v1.12.4': ('https://www.envoyproxy.io/docs/envoy/v1.12.4', None), + 'v1.12.5': ('https://www.envoyproxy.io/docs/envoy/v1.12.5', None), + 'v1.12.6': ('https://www.envoyproxy.io/docs/envoy/v1.12.6', None), + 'v1.13.0': ('https://www.envoyproxy.io/docs/envoy/v1.13.0', None), + 'v1.13.1': ('https://www.envoyproxy.io/docs/envoy/v1.13.1', None), + 'v1.13.2': ('https://www.envoyproxy.io/docs/envoy/v1.13.2', None), + 'v1.13.3': ('https://www.envoyproxy.io/docs/envoy/v1.13.3', None), + 'v1.14.0': ('https://www.envoyproxy.io/docs/envoy/v1.14.0', None), + 'v1.14.2': ('https://www.envoyproxy.io/docs/envoy/v1.14.2', None), + 'v1.14.3': ('https://www.envoyproxy.io/docs/envoy/v1.14.3', None), + 'v1.14.7': ('https://www.envoyproxy.io/docs/envoy/v1.14.7', None), + 'v1.15.0': ('https://www.envoyproxy.io/docs/envoy/v1.15.0', None), + 'v1.15.4': ('https://www.envoyproxy.io/docs/envoy/v1.15.4', None), + 'v1.16.0': ('https://www.envoyproxy.io/docs/envoy/v1.16.0', None), + 'v1.16.3': ('https://www.envoyproxy.io/docs/envoy/v1.16.3', None), + 'v1.17.0': ('https://www.envoyproxy.io/docs/envoy/v1.17.0', None), + 'v1.17.1': ('https://www.envoyproxy.io/docs/envoy/v1.17.1', None), + 'v1.17.2': ('https://www.envoyproxy.io/docs/envoy/v1.17.2', None), + 'v1.18.0': ('https://www.envoyproxy.io/docs/envoy/v1.18.2', None) +} diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index edcf65e6cf792..634dffe9d624a 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -9,10 +9,10 @@ Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* -* http: replaced setting `envoy.reloadable_features.strict_1xx_and_204_response_headers` with settings - `envoy.reloadable_features.require_strict_1xx_and_204_response_headers` +* http: replaced setting ``envoy.reloadable_features.strict_1xx_and_204_response_headers`` with settings + ``envoy.reloadable_features.require_strict_1xx_and_204_response_headers`` (require upstream 1xx or 204 responses to not have Transfer-Encoding or non-zero Content-Length headers) and - `envoy.reloadable_features.send_strict_1xx_and_204_response_headers` + ``envoy.reloadable_features.send_strict_1xx_and_204_response_headers`` (do not send 1xx or 204 responses with these headers). Both are true by default. Bug Fixes @@ -28,11 +28,11 @@ Removed Config or Runtime ------------------------- *Normally occurs at the end of the* :ref:`deprecation period ` -* http: removed `envoy.reloadable_features.allow_500_after_100` runtime guard and the legacy code path. -* http: removed `envoy.reloadable_features.hcm_stream_error_on_invalid_message` for disabling closing HTTP/1.1 connections on error. Connection-closing can still be disabled by setting the HTTP/1 configuration :ref:`override_stream_error_on_invalid_http_message `. -* http: removed `envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2`; Envoy will now always send GOAWAY to HTTP2 downstreams when the :ref:`disable_keepalive ` overload action is active. -* http: removed `envoy.reloadable_features.unify_grpc_handling` runtime guard and legacy code paths. -* tls: removed `envoy.reloadable_features.tls_use_io_handle_bio` runtime guard and legacy code path. +* http: removed ``envoy.reloadable_features.allow_500_after_100`` runtime guard and the legacy code path. +* http: removed ``envoy.reloadable_features.hcm_stream_error_on_invalid_message`` for disabling closing HTTP/1.1 connections on error. Connection-closing can still be disabled by setting the HTTP/1 configuration :ref:`override_stream_error_on_invalid_http_message `. +* http: removed ``envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2``; Envoy will now always send GOAWAY to HTTP2 downstreams when the :ref:`disable_keepalive ` overload action is active. +* http: removed ``envoy.reloadable_features.unify_grpc_handling`` runtime guard and legacy code paths. +* tls: removed ``envoy.reloadable_features.tls_use_io_handle_bio`` runtime guard and legacy code path. New Features ------------ diff --git a/docs/root/version_history/v1.1.0.rst b/docs/root/version_history/v1.1.0.rst index b477be6fa8495..4ad2763e52a4b 100644 --- a/docs/root/version_history/v1.1.0.rst +++ b/docs/root/version_history/v1.1.0.rst @@ -6,26 +6,26 @@ Changes * Switch from Jannson to RapidJSON for our JSON library (allowing for a configuration schema in 1.2.0). -* Upgrade :ref:`recommended version ` of various other libraries. +* Upgrade :ref:`recommended version ` of various other libraries. * Configurable DNS refresh rate for DNS service discovery types. * Upstream circuit breaker configuration can be :ref:`overridden via runtime - `. -* :ref:`Zone aware routing support `. + `. +* :ref:`Zone aware routing support `. * Generic header matching routing rule. * HTTP/2 graceful connection draining (double GOAWAY). -* DynamoDB filter :ref:`per shard statistics ` (pre-release AWS +* DynamoDB filter :ref:`per shard statistics ` (pre-release AWS feature). -* Initial release of the :ref:`fault injection HTTP filter `. -* HTTP :ref:`rate limit filter ` enhancements (note that the +* Initial release of the :ref:`fault injection HTTP filter `. +* HTTP :ref:`rate limit filter ` enhancements (note that the configuration for HTTP rate limiting is going to be overhauled in 1.2.0). -* Added :ref:`refused-stream retry policy `. -* Multiple :ref:`priority queues ` for upstream clusters +* Added :ref:`refused-stream retry policy `. +* Multiple :ref:`priority queues ` for upstream clusters (configurable on a per route basis, with separate connection pools, circuit breakers, etc.). -* Added max connection circuit breaking to the :ref:`TCP proxy filter `. -* Added :ref:`CLI ` options for setting the logging file flush interval as well +* Added max connection circuit breaking to the :ref:`TCP proxy filter `. +* Added :ref:`CLI ` options for setting the logging file flush interval as well as the drain/shutdown time during hot restart. * A very large number of performance enhancements for core HTTP/TCP proxy flows as well as a few new configuration flags to allow disabling expensive features if they are not needed (specifically request ID generation and dynamic response code stats). -* Support Mongo 3.2 in the :ref:`Mongo sniffing filter `. +* Support Mongo 3.2 in the :ref:`Mongo sniffing filter `. * Lots of other small fixes and enhancements not listed. diff --git a/docs/root/version_history/v1.10.0.rst b/docs/root/version_history/v1.10.0.rst index 27bc0f8555f4e..b8616a86a7c50 100644 --- a/docs/root/version_history/v1.10.0.rst +++ b/docs/root/version_history/v1.10.0.rst @@ -5,97 +5,97 @@ Changes ------- * access log: added a new flag for upstream retry count exceeded. -* access log: added a :ref:`gRPC filter ` to allow filtering on gRPC status. +* access log: added a :ref:`gRPC filter ` to allow filtering on gRPC status. * access log: added a new flag for stream idle timeout. -* access log: added a new field for upstream transport failure reason in :ref:`file access logger` and - :ref:`gRPC access logger` for HTTP access logs. +* access log: added a new field for upstream transport failure reason in :ref:`file access logger ` and + :ref:`gRPC access logger ` for HTTP access logs. * access log: added new fields for downstream x509 information (URI sans and subject) to file and gRPC access logger. * admin: the admin server can now be accessed via HTTP/2 (prior knowledge). * admin: changed HTTP response status code from 400 to 405 when attempting to GET a POST-only route (such as /quitquitquit). * buffer: fix vulnerabilities when allocation fails. * build: releases are built with GCC-7 and linked with LLD. -* build: dev docker images :ref:`have been split ` from tagged images for easier +* build: dev docker images :ref:`have been split ` from tagged images for easier discoverability in Docker Hub. Additionally, we now build images for point releases. * config: added support of using google.protobuf.Any in opaque configs for extensions. * config: logging warnings when deprecated fields are in use. * config: removed deprecated --v2-config-only from command line config. -* config: removed deprecated_v1 sds_config from :ref:`Bootstrap config `. -* config: removed the deprecated_v1 config option from :ref:`ring hash `. -* config: removed REST_LEGACY as a valid :ref:`ApiType `. +* config: removed deprecated_v1 sds_config from :ref:`Bootstrap config `. +* config: removed the deprecated_v1 config option from :ref:`ring hash `. +* config: removed REST_LEGACY as a valid :ref:`ApiType `. * config: finish cluster warming only when a named response i.e. ClusterLoadAssignment associated to the cluster being warmed comes in the EDS response. This is a behavioural change from the current implementation where warming of cluster completes on missing load assignments also. * config: use Envoy cpuset size to set the default number or worker threads if :option:`--cpuset-threads` is enabled. -* config: added support for :ref:`initial_fetch_timeout `. The timeout is disabled by default. -* cors: added :ref:`filter_enabled & shadow_enabled RuntimeFractionalPercent flags ` to filter. -* csrf: added :ref:`CSRF filter `. +* config: added support for :ref:`initial_fetch_timeout `. The timeout is disabled by default. +* cors: added :ref:`filter_enabled & shadow_enabled RuntimeFractionalPercent flags ` to filter. +* csrf: added * ext_authz: added support for buffering request body. * ext_authz: migrated from v2alpha to v2 and improved docs. * ext_authz: added a configurable option to make the gRPC service cross-compatible with V2Alpha. Note that this feature is already deprecated. It should be used for a short time, and only when transitioning from alpha to V2 release version. * ext_authz: migrated from v2alpha to v2 and improved the documentation. * ext_authz: authorization request and response configuration has been separated into two distinct objects: :ref:`authorization request - ` and :ref:`authorization response - `. In addition, :ref:`client headers - ` and :ref:`upstream headers - ` replaces the previous *allowed_authorization_headers* object. - All the control header lists now support :ref:`string matcher ` instead of standard string. + ` and :ref:`authorization response + `. In addition, :ref:`client headers + ` and :ref:`upstream headers + ` replaces the previous *allowed_authorization_headers* object. + All the control header lists now support :ref:`string matcher ` instead of standard string. * fault: added the :ref:`max_active_faults - ` setting, as well as - :ref:`statistics ` for the number of active faults + ` setting, as well as + :ref:`statistics ` for the number of active faults and the number of faults the overflowed. * fault: added :ref:`response rate limit - ` fault injection. + ` fault injection. * fault: added :ref:`HTTP header fault configuration - ` to the HTTP fault filter. + ` to the HTTP fault filter. * governance: extending Envoy deprecation policy from 1 release (0-3 months) to 2 releases (3-6 months). -* health check: expected response codes in http health checks are now :ref:`configurable `. +* health check: expected response codes in http health checks are now :ref:`configurable `. * http: added new grpc_http1_reverse_bridge filter for converting gRPC requests into HTTP/1.1 requests. * http: fixed a bug where Content-Length:0 was added to HTTP/1 204 responses. -* http: added :ref:`max request headers size `. The default behaviour is unchanged. +* http: added :ref:`max request headers size `. The default behaviour is unchanged. * http: added modifyDecodingBuffer/modifyEncodingBuffer to allow modifying the buffered request/response data. * http: added encodeComplete/decodeComplete. These are invoked at the end of the stream, after all data has been encoded/decoded respectively. Default implementation is a no-op. -* outlier_detection: added support for :ref:`outlier detection event protobuf-based logging `. -* mysql: added a MySQL proxy filter that is capable of parsing SQL queries over MySQL wire protocol. Refer to :ref:`MySQL proxy` for more details. +* outlier_detection: added support for :ref:`outlier detection event protobuf-based logging `. +* mysql: added a MySQL proxy filter that is capable of parsing SQL queries over MySQL wire protocol. Refer to :ref:`MySQL proxy ` for more details. * performance: new buffer implementation (disabled by default; to test it, add "--use-libevent-buffers 0" to the command-line arguments when starting Envoy). -* jwt_authn: added :ref:`filter_state_rules ` to allow specifying requirements from filterState by other filters. +* jwt_authn: added :ref:`filter_state_rules ` to allow specifying requirements from filterState by other filters. * ratelimit: removed deprecated rate limit configuration from bootstrap. -* redis: added :ref:`hashtagging ` to guarantee a given key's upstream. -* redis: added :ref:`latency stats ` for commands. -* redis: added :ref:`success and error stats ` for commands. +* redis: added :ref:`hashtagging ` to guarantee a given key's upstream. +* redis: added :ref:`latency stats ` for commands. +* redis: added :ref:`success and error stats ` for commands. * redis: migrate hash function for host selection to `MurmurHash2 `_ from std::hash. MurmurHash2 is compatible with std::hash in GNU libstdc++ 3.4.20 or above. This is typically the case when compiled on Linux and not macOS. -* redis: added :ref:`latency_in_micros ` to specify the redis commands stats time unit in microseconds. -* router: added ability to configure a :ref:`retry policy ` at the +* redis: added :ref:`latency_in_micros ` to specify the redis commands stats time unit in microseconds. +* router: added ability to configure a :ref:`retry policy ` at the virtual host level. * router: added reset reason to response body when upstream reset happens. After this change, the response body will be of the form `upstream connect error or disconnect/reset before headers. reset reason:` -* router: added :ref:`rq_reset_after_downstream_response_started ` counter stat to router stats. -* router: added per-route configuration of :ref:`internal redirects `. +* router: added :ref:`rq_reset_after_downstream_response_started ` counter stat to router stats. +* router: added per-route configuration of :ref:`internal redirects `. * router: removed deprecated route-action level headers_to_add/remove. -* router: made :ref:`max retries header ` take precedence over the number of retries in route and virtual host retry policies. -* router: added support for prefix wildcards in :ref:`virtual host domains` +* router: made :ref:`max retries header ` take precedence over the number of retries in route and virtual host retry policies. +* router: added support for prefix wildcards in :ref:`virtual host domains ` * stats: added support for histograms in prometheus * stats: added usedonly flag to prometheus stats to only output metrics which have been updated at least once. * stats: added gauges tracking remaining resources before circuit breakers open. -* tap: added new alpha :ref:`HTTP tap filter `. +* tap: added new alpha :ref:`HTTP tap filter `. * tls: enabled TLS 1.3 on the server-side (non-FIPS builds). -* upstream: add hash_function to specify the hash function for :ref:`ring hash` as either xxHash or `murmurHash2 `_. MurmurHash2 is compatible with std::hash in GNU libstdc++ 3.4.20 or above. This is typically the case when compiled on Linux and not macOS. -* upstream: added :ref:`degraded health value` which allows +* upstream: add hash_function to specify the hash function for :ref:`ring hash ` as either xxHash or `murmurHash2 `_. MurmurHash2 is compatible with std::hash in GNU libstdc++ 3.4.20 or above. This is typically the case when compiled on Linux and not macOS. +* upstream: added :ref:`degraded health value ` which allows routing to certain hosts only when there are insufficient healthy hosts available. -* upstream: add cluster factory to allow creating and registering :ref:`custom cluster type`. -* upstream: added a :ref:`circuit breaker ` to limit the number of concurrent connection pools in use. -* tracing: added :ref:`verbose ` to support logging annotations on spans. -* upstream: added support for host weighting and :ref:`locality weighting ` in the :ref:`ring hash load balancer `, and added a :ref:`maximum_ring_size` config parameter to strictly bound the ring size. +* upstream: add cluster factory to allow creating and registering :ref:`custom cluster type `. +* upstream: added a :ref:`circuit breaker ` to limit the number of concurrent connection pools in use. +* tracing: added :ref:`verbose ` to support logging annotations on spans. +* upstream: added support for host weighting and :ref:`locality weighting ` in the :ref:`ring hash load balancer `, and added a :ref:`maximum_ring_size ` config parameter to strictly bound the ring size. * zookeeper: added a ZooKeeper proxy filter that parses ZooKeeper messages (requests/responses/events). - Refer to :ref:`ZooKeeper proxy` for more details. + Refer to :ref:`ZooKeeper proxy ` for more details. * upstream: added configuration option to select any host when the fallback policy fails. * upstream: stopped incrementing upstream_rq_total for HTTP/1 conn pool when request is circuit broken. Deprecated ---------- -* Use of `use_alpha` in :ref:`Ext-Authz Authorization Service ` is deprecated. It should be used for a short time, and only when transitioning from alpha to V2 release version. -* Use of `enabled` in `CorsPolicy`, found in - :ref:`route.proto `. - Set the `filter_enabled` field instead. -* Use of the `type` field in the `FaultDelay` message (found in - :ref:`fault.proto `) +* Use of `use_alpha` in :ref:`Ext-Authz Authorization Service ` is deprecated. It should be used for a short time, and only when transitioning from alpha to V2 release version. +* Use of ``enabled`` in ``CorsPolicy``, found in + :ref:`route.proto `. + Set the ``filter_enabled`` field instead. +* Use of the ``type`` field in the ``FaultDelay`` message (found in + :ref:`fault.proto `) has been deprecated. It was never used and setting it has no effect. It will be removed in the following release. diff --git a/docs/root/version_history/v1.11.0.rst b/docs/root/version_history/v1.11.0.rst index 10b48736b2db3..78c9dce6d7c66 100644 --- a/docs/root/version_history/v1.11.0.rst +++ b/docs/root/version_history/v1.11.0.rst @@ -6,117 +6,117 @@ Changes * access log: added a new field for downstream TLS session ID to file and gRPC access logger. * access log: added a new field for route name to file and gRPC access logger. -* access log: added a new field for response code details in :ref:`file access logger` and :ref:`gRPC access logger`. -* access log: added several new variables for exposing information about the downstream TLS connection to :ref:`file access logger` and :ref:`gRPC access logger`. +* access log: added a new field for response code details in :ref:`file access logger ` and :ref:`gRPC access logger `. +* access log: added several new variables for exposing information about the downstream TLS connection to :ref:`file access logger ` and :ref:`gRPC access logger `. * access log: added a new flag for request rejected due to failed strict header check. -* admin: the administration interface now includes a :ref:`/ready endpoint ` for easier readiness checks. -* admin: extend :ref:`/runtime_modify endpoint ` to support parameters within the request body. -* admin: the :ref:`/listener endpoint ` now returns :ref:`listeners.proto` which includes listener names and ports. +* admin: the administration interface now includes a :ref:`/ready endpoint ` for easier readiness checks. +* admin: extend :ref:`/runtime_modify endpoint ` to support parameters within the request body. +* admin: the :ref:`/listener endpoint ` now returns :ref:`listeners.proto ` which includes listener names and ports. * admin: added host priority to :http:get:`/clusters` and :http:get:`/clusters?format=json` endpoint response -* admin: the :ref:`/clusters endpoint ` now shows hostname +* admin: the :ref:`/clusters endpoint ` now shows hostname for each host, useful for DNS based clusters. * api: track and report requests issued since last load report. * build: releases are built with Clang and linked with LLD. -* config: added :ref:stats_server_version_override` ` in bootstrap, that can be used to override :ref:`server.version statistic `. -* control-plane: management servers can respond with HTTP 304 to indicate that config is up to date for Envoy proxies polling a :ref:`REST API Config Type ` +* config: added :ref:`stats_server_version_override ` in bootstrap, that can be used to override :ref:`server.version statistic `. +* control-plane: management servers can respond with HTTP 304 to indicate that config is up to date for Envoy proxies polling a :ref:`REST API Config Type ` * csrf: added support for allowlisting additional source origins. * dns: added support for getting DNS record TTL which is used by STRICT_DNS/LOGICAL_DNS cluster as DNS refresh rate. -* dubbo_proxy: support the :ref:`dubbo proxy filter `. +* dubbo_proxy: support the :ref:`dubbo proxy filter `. * dynamo_request_parser: adding support for transactions. Adds check for new types of dynamodb operations (TransactWriteItems, TransactGetItems) and awareness for new types of dynamodb errors (IdempotentParameterMismatchException, TransactionCanceledException, TransactionInProgressException). -* eds: added support to specify max time for which endpoints can be used :ref:`gRPC filter `. -* eds: removed max limit for `load_balancing_weight`. -* event: added :ref:`loop duration and poll delay statistics `. -* ext_authz: added a `x-envoy-auth-partial-body` metadata header set to `false|true` indicating if there is a partial body sent in the authorization request message. +* eds: added support to specify max time for which endpoints can be used :ref:`gRPC filter `. +* eds: removed max limit for ``load_balancing_weight``. +* event: added :ref:`loop duration and poll delay statistics `. +* ext_authz: added a ``x-envoy-auth-partial-body`` metadata header set to ``false|true`` indicating if there is a partial body sent in the authorization request message. * ext_authz: added configurable status code that allows customizing HTTP responses on filter check status errors. -* ext_authz: added option to `ext_authz` that allows the filter clearing route cache. +* ext_authz: added option to ``ext_authz`` that allows the filter clearing route cache. * grpc-json: added support for :ref:`auto mapping - `. -* health check: added :ref:`initial jitter ` to add jitter to the first health check in order to prevent thundering herd on Envoy startup. + `. +* health check: added :ref:`initial jitter ` to add jitter to the first health check in order to prevent thundering herd on Envoy startup. * hot restart: stats are no longer shared between hot restart parent/child via shared memory, but rather by RPC. Hot restart version incremented to 11. * http: added the ability to pass a URL encoded PEM encoded peer certificate chain in the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header. * http: fixed a bug where large unbufferable responses were not tracked in stats and logs correctly. * http: fixed a crashing bug where gRPC local replies would cause segfaults when upstream access logging was on. -* http: mitigated a race condition with the :ref:`delayed_close_timeout` where it could trigger while actively flushing a pending write buffer for a downstream connection. -* http: added support for :ref:`preserve_external_request_id` that represents whether the x-request-id should not be reset on edge entry inside mesh -* http: changed `sendLocalReply` to send percent-encoded `GrpcMessage`. -* http: added a :ref:header_prefix` ` configuration option to allow Envoy to send and process x-custom- prefixed headers rather than x-envoy. -* http: added :ref:`dynamic forward proxy ` support. -* http: tracking the active stream and dumping state in Envoy crash handlers. This can be disabled by building with `--define disable_object_dump_on_signal_trace=disabled` +* http: mitigated a race condition with the :ref:`delayed_close_timeout ` where it could trigger while actively flushing a pending write buffer for a downstream connection. +* http: added support for :ref:`preserve_external_request_id ` that represents whether the x-request-id should not be reset on edge entry inside mesh +* http: changed ``sendLocalReply`` to send percent-encoded ``GrpcMessage``. +* http: added a :ref:`header_prefix ` configuration option to allow Envoy to send and process x-custom- prefixed headers rather than x-envoy. +* http: added :ref:`dynamic forward proxy ` support. +* http: tracking the active stream and dumping state in Envoy crash handlers. This can be disabled by building with ``--define disable_object_dump_on_signal_trace=disabled`` * jwt_authn: make filter's parsing of JWT more flexible, allowing syntax like ``jwt=eyJhbGciOiJS...ZFnFIw,extra=7,realm=123`` -* listener: added :ref:`source IP ` - and :ref:`source port ` filter +* listener: added :ref:`source IP ` + and :ref:`source port ` filter chain matching. * lua: exposed functions to Lua to verify digital signature. -* original_src filter: added the :ref:`filter`. -* outlier_detector: added configuration :ref:`outlier_detection.split_external_local_origin_errors` to distinguish locally and externally generated errors. See :ref:`arch_overview_outlier_detection` for full details. +* original_src filter: added the :ref:`filter `. +* outlier_detector: added configuration :ref:`outlier_detection.split_external_local_origin_errors ` to distinguish locally and externally generated errors. See :ref:`arch_overview_outlier_detection` for full details. * rbac: migrated from v2alpha to v2. * redis: add support for Redis cluster custom cluster type. * redis: automatically route commands using cluster slots for Redis cluster. -* redis: added :ref:`prefix routing ` to enable routing commands based on their key's prefix to different upstream. -* redis: added :ref:`request mirror policy ` to enable shadow traffic and/or dual writes. +* redis: added :ref:`prefix routing ` to enable routing commands based on their key's prefix to different upstream. +* redis: added :ref:`request mirror policy ` to enable shadow traffic and/or dual writes. * redis: add support for zpopmax and zpopmin commands. * redis: added - :ref:`max_buffer_size_before_flush ` to batch commands together until the encoder buffer hits a certain size, and - :ref:`buffer_flush_timeout ` to control how quickly the buffer is flushed if it is not full. -* redis: added auth support :ref:`downstream_auth_password ` for downstream client authentication, and :ref:`auth_password ` to configure authentication passwords for upstream server clusters. -* retry: added a retry predicate that :ref:`rejects canary hosts. ` -* router: add support for configuring a :ref:`gRPC timeout offset ` on incoming requests. -* router: added ability to control retry back-off intervals via :ref:`retry policy `. -* router: added ability to issue a hedged retry in response to a per try timeout via a :ref:`hedge policy `. + :ref:`max_buffer_size_before_flush ` to batch commands together until the encoder buffer hits a certain size, and + :ref:`buffer_flush_timeout ` to control how quickly the buffer is flushed if it is not full. +* redis: added auth support :ref:`downstream_auth_password ` for downstream client authentication, and :ref:`auth_password ` to configure authentication passwords for upstream server clusters. +* retry: added a retry predicate that :ref:`rejects canary hosts. ` +* router: add support for configuring a :ref:`gRPC timeout offset ` on incoming requests. +* router: added ability to control retry back-off intervals via :ref:`retry policy `. +* router: added ability to issue a hedged retry in response to a per try timeout via a :ref:`hedge policy `. * router: added a route name field to each http route in route.Route list * router: added several new variables for exposing information about the downstream TLS connection via :ref:`header - formatters `. + formatters `. * router: per try timeouts will no longer start before the downstream request has been received in full by the router.This ensures that the per try timeout does not account for slow downstreams and that will not start before the global timeout. -* router: added :ref:`RouteAction's auto_host_rewrite_header ` to allow upstream host header substitution with some other header's value +* router: added :ref:`RouteAction's auto_host_rewrite_header ` to allow upstream host header substitution with some other header's value * router: added support for UPSTREAM_REMOTE_ADDRESS :ref:`header formatter - `. + `. * router: add ability to reject a request that includes invalid values for - headers configured in :ref:`strict_check_headers ` + headers configured in :ref:`strict_check_headers ` * runtime: added support for :ref:`flexible layering configuration - `. + `. * runtime: added support for statically :ref:`specifying the runtime in the bootstrap configuration - `. -* runtime: :ref:`Runtime Discovery Service (RTDS) ` support added to layered runtime configuration. -* sandbox: added :ref:`CSRF sandbox `. + `. +* runtime: :ref:`Runtime Discovery Service (RTDS) ` support added to layered runtime configuration. +* sandbox: added :ref:`CSRF sandbox `. * server: ``--define manual_stamp=manual_stamp`` was added to allow server stamping outside of binary rules. more info in the `bazel docs `_. -* server: added :ref:`server state ` statistic. -* server: added :ref:`initialization_time_ms` statistic. -* subset: added :ref:`list_as_any` option to +* server: added :ref:`server state ` statistic. +* server: added :ref:`initialization_time_ms ` statistic. +* subset: added :ref:`list_as_any ` option to the subset lb which allows matching metadata against any of the values in a list value on the endpoints. -* tools: added :repo:`proto ` support for :ref:`router check tool ` tests. +* tools: added :repo:`proto ` support for :ref:`router check tool ` tests. * tracing: add trace sampling configuration to the route, to override the route level. -* upstream: added :ref:`upstream_cx_pool_overflow ` for the connection pool circuit breaker. +* upstream: added :ref:`upstream_cx_pool_overflow ` for the connection pool circuit breaker. * upstream: an EDS management server can now force removal of a host that is still passing active health checking by first marking the host as failed via EDS health check and subsequently removing it in a future update. This is a mechanism to work around a race condition in which an EDS implementation may remove a host before it has stopped passing active HC, thus causing the host to become stranded until a future update. -* upstream: added :ref:`an option ` +* upstream: added :ref:`an option ` that allows ignoring new hosts for the purpose of load balancing calculations until they have been health checked for the first time. * upstream: added runtime error checking to prevent setting dns type to STRICT_DNS or LOGICAL_DNS when custom resolver name is specified. -* upstream: added possibility to override fallback_policy per specific selector in :ref:`subset load balancer `. -* upstream: the :ref:`logical DNS cluster ` now +* upstream: added possibility to override fallback_policy per specific selector in :ref:`subset load balancer `. +* upstream: the :ref:`logical DNS cluster ` now displays the current resolved IP address in admin output instead of 0.0.0.0. Deprecated ---------- * The --max-stats and --max-obj-name-len flags no longer has any effect. -* Use of :ref:`cluster ` in :ref:`redis_proxy.proto ` is deprecated. Set a :ref:`catch_all_route ` instead. -* Use of :ref:`catch_all_cluster ` in :ref:`redis_proxy.proto ` is deprecated. Set a :ref:`catch_all_route ` instead. -* Use of json based schema in router check tool tests. The tests should follow validation :repo:`schema`. -* Use of the v1 style route configuration for the :ref:`TCP proxy filter ` - is now fully replaced with listener :ref:`filter chain matching `. +* Use of :ref:`cluster ` in :ref:`redis_proxy.proto ` is deprecated. Set a :ref:`catch_all_route ` instead. +* Use of :ref:`catch_all_cluster ` in :ref:`redis_proxy.proto ` is deprecated. Set a :ref:`catch_all_route ` instead. +* Use of json based schema in router check tool tests. The tests should follow validation :repo:`schema `. +* Use of the v1 style route configuration for the :ref:`TCP proxy filter ` + is now fully replaced with listener :ref:`filter chain matching `. Use this instead. -* Use of :ref:`runtime ` in :ref:`Bootstrap - `. Use :ref:`layered_runtime - ` instead. +* Use of :ref:`runtime ` in :ref:`Bootstrap + `. Use :ref:`layered_runtime + ` instead. * Specifying "deprecated_v1: true" in HTTP and network filter configuration to allow loading JSON configuration is now deprecated and will be removed in a following release. Update any custom filters to use protobuf configuration. A struct can be used for a mostly 1:1 conversion if needed. - The `envoy.deprecated_features.v1_filter_json_config` runtime key can be used to temporarily + The ``envoy.deprecated_features.v1_filter_json_config`` runtime key can be used to temporarily enable this feature once the deprecation becomes fail by default. diff --git a/docs/root/version_history/v1.11.1.rst b/docs/root/version_history/v1.11.1.rst index 6e770baa1e8fa..53176eac2b298 100644 --- a/docs/root/version_history/v1.11.1.rst +++ b/docs/root/version_history/v1.11.1.rst @@ -4,16 +4,16 @@ Changes ------- -* http: added mitigation of client initiated attacks that result in flooding of the downstream HTTP/2 connections. Those attacks can be logged at the "warning" level when the runtime feature `http.connection_manager.log_flood_exception` is enabled. The runtime setting defaults to disabled to avoid log spam when under attack. -* http: added :ref:`inbound_empty_frames_flood ` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on consecutive inbound frames with an empty payload and no end stream flag. The limit is configured by setting the :ref:`max_consecutive_inbound_frames_with_empty_payload config setting `. - Runtime feature `envoy.reloadable_features.http2_protocol_options.max_consecutive_inbound_frames_with_empty_payload` overrides :ref:`max_consecutive_inbound_frames_with_empty_payload setting `. Large override value (i.e. 2147483647) effectively disables mitigation of inbound frames with empty payload. -* http: added :ref:`inbound_priority_frames_flood ` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on inbound PRIORITY frames. The limit is configured by setting the :ref:`max_inbound_priority_frames_per_stream config setting `. - Runtime feature `envoy.reloadable_features.http2_protocol_options.max_inbound_priority_frames_per_stream` overrides :ref:`max_inbound_priority_frames_per_stream setting `. Large override value effectively disables flood mitigation of inbound PRIORITY frames. -* http: added :ref:`inbound_window_update_frames_flood ` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on inbound WINDOW_UPDATE frames. The limit is configured by setting the :ref:`max_inbound_window_update_frames_per_data_frame_sent config setting `. - Runtime feature `envoy.reloadable_features.http2_protocol_options.max_inbound_window_update_frames_per_data_frame_sent` overrides :ref:`max_inbound_window_update_frames_per_data_frame_sent setting `. Large override value effectively disables flood mitigation of inbound WINDOW_UPDATE frames. -* http: added :ref:`outbound_flood ` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the outbound queue limit. The limit is configured by setting the :ref:`max_outbound_frames config setting ` - Runtime feature `envoy.reloadable_features.http2_protocol_options.max_outbound_frames` overrides :ref:`max_outbound_frames config setting `. Large override value effectively disables flood mitigation of outbound frames of all types. -* http: added :ref:`outbound_control_flood ` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the outbound queue limit for PING, SETTINGS and RST_STREAM frames. The limit is configured by setting the :ref:`max_outbound_control_frames config setting `. - Runtime feature `envoy.reloadable_features.http2_protocol_options.max_outbound_control_frames` overrides :ref:`max_outbound_control_frames config setting `. Large override value effectively disables flood mitigation of outbound frames of types PING, SETTINGS and RST_STREAM. -* http: enabled strict validation of HTTP/2 messaging. Previous behavior can be restored using :ref:`stream_error_on_invalid_http_messaging config setting `. - Runtime feature `envoy.reloadable_features.http2_protocol_options.stream_error_on_invalid_http_messaging` overrides :ref:`stream_error_on_invalid_http_messaging config setting `. +* http: added mitigation of client initiated attacks that result in flooding of the downstream HTTP/2 connections. Those attacks can be logged at the "warning" level when the runtime feature ``http.connection_manager.log_flood_exception`` is enabled. The runtime setting defaults to disabled to avoid log spam when under attack. +* http: added :ref:`inbound_empty_frames_flood ` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on consecutive inbound frames with an empty payload and no end stream flag. The limit is configured by setting the :ref:`max_consecutive_inbound_frames_with_empty_payload config setting `. + Runtime feature ``envoy.reloadable_features.http2_protocol_options.max_consecutive_inbound_frames_with_empty_payload`` overrides :ref:`max_consecutive_inbound_frames_with_empty_payload setting `. Large override value (i.e. 2147483647) effectively disables mitigation of inbound frames with empty payload. +* http: added :ref:`inbound_priority_frames_flood ` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on inbound PRIORITY frames. The limit is configured by setting the :ref:`max_inbound_priority_frames_per_stream config setting `. + Runtime feature ``envoy.reloadable_features.http2_protocol_options.max_inbound_priority_frames_per_stream`` overrides :ref:`max_inbound_priority_frames_per_stream setting `. Large override value effectively disables flood mitigation of inbound PRIORITY frames. +* http: added :ref:`inbound_window_update_frames_flood ` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on inbound WINDOW_UPDATE frames. The limit is configured by setting the :ref:`max_inbound_window_update_frames_per_data_frame_sent config setting `. + Runtime feature ``envoy.reloadable_features.http2_protocol_options.max_inbound_window_update_frames_per_data_frame_sent`` overrides :ref:`max_inbound_window_update_frames_per_data_frame_sent setting `. Large override value effectively disables flood mitigation of inbound WINDOW_UPDATE frames. +* http: added :ref:`outbound_flood ` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the outbound queue limit. The limit is configured by setting the :ref:`max_outbound_frames config setting ` + Runtime feature ``envoy.reloadable_features.http2_protocol_options.max_outbound_frames`` overrides :ref:`max_outbound_frames config setting `. Large override value effectively disables flood mitigation of outbound frames of all types. +* http: added :ref:`outbound_control_flood ` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the outbound queue limit for PING, SETTINGS and RST_STREAM frames. The limit is configured by setting the :ref:`max_outbound_control_frames config setting `. + Runtime feature ``envoy.reloadable_features.http2_protocol_options.max_outbound_control_frames`` overrides :ref:`max_outbound_control_frames config setting `. Large override value effectively disables flood mitigation of outbound frames of types PING, SETTINGS and RST_STREAM. +* http: enabled strict validation of HTTP/2 messaging. Previous behavior can be restored using :ref:`stream_error_on_invalid_http_messaging config setting `. + Runtime feature ``envoy.reloadable_features.http2_protocol_options.stream_error_on_invalid_http_messaging`` overrides :ref:`stream_error_on_invalid_http_messaging config setting `. diff --git a/docs/root/version_history/v1.11.2.rst b/docs/root/version_history/v1.11.2.rst index 61490a238ef8b..77f6b40f118d9 100644 --- a/docs/root/version_history/v1.11.2.rst +++ b/docs/root/version_history/v1.11.2.rst @@ -5,17 +5,17 @@ Changes ------- * http: fixed CVE-2019-15226 by adding a cached byte size in HeaderMap. -* http: added :ref:`max headers count ` for http connections. The default limit is 100. -* upstream: runtime feature `envoy.reloadable_features.max_response_headers_count` overrides the default limit for upstream :ref:`max headers count ` -* http: added :ref:`common_http_protocol_options ` - Runtime feature `envoy.reloadable_features.max_request_headers_count` overrides the default limit for downstream :ref:`max headers count ` +* http: added :ref:`max headers count ` for http connections. The default limit is 100. +* upstream: runtime feature `envoy.reloadable_features.max_response_headers_count` overrides the default limit for upstream :ref:`max headers count ` +* http: added :ref:`common_http_protocol_options ` + Runtime feature `envoy.reloadable_features.max_request_headers_count` overrides the default limit for downstream :ref:`max headers count ` * regex: backported safe regex matcher fix for CVE-2019-15225. Deprecated ---------- * Use of :ref:`idle_timeout - ` + ` is deprecated. Use :ref:`common_http_protocol_options - ` + ` instead. diff --git a/docs/root/version_history/v1.12.0.rst b/docs/root/version_history/v1.12.0.rst index 9bc7510639ec6..ef1d0050194a7 100644 --- a/docs/root/version_history/v1.12.0.rst +++ b/docs/root/version_history/v1.12.0.rst @@ -4,86 +4,86 @@ Changes ------- -* access log: added a new flag for :ref:`downstream protocol error `. -* access log: added :ref:`buffering ` and :ref:`periodical flushing ` support to gRPC access logger. Defaults to 16KB buffer and flushing every 1 second. -* access log: added DOWNSTREAM_DIRECT_REMOTE_ADDRESS and DOWNSTREAM_DIRECT_REMOTE_ADDRESS_WITHOUT_PORT :ref:`access log formatters ` and gRPC access logger. -* access log: gRPC Access Log Service (ALS) support added for :ref:`TCP access logs `. -* access log: reintroduced :ref:`filesystem ` stats and added the `write_failed` counter to track failed log writes. -* admin: added ability to configure listener :ref:`socket options `. -* admin: added config dump support for Secret Discovery Service :ref:`SecretConfigDump `. -* admin: added support for :ref:`draining ` listeners via admin interface. +* access log: added a new flag for :ref:`downstream protocol error `. +* access log: added :ref:`buffering ` and :ref:`periodical flushing ` support to gRPC access logger. Defaults to 16KB buffer and flushing every 1 second. +* access log: added DOWNSTREAM_DIRECT_REMOTE_ADDRESS and DOWNSTREAM_DIRECT_REMOTE_ADDRESS_WITHOUT_PORT :ref:`access log formatters ` and gRPC access logger. +* access log: gRPC Access Log Service (ALS) support added for :ref:`TCP access logs `. +* access log: reintroduced :ref:`filesystem ` stats and added the `write_failed` counter to track failed log writes. +* admin: added ability to configure listener :ref:`socket options `. +* admin: added config dump support for Secret Discovery Service :ref:`SecretConfigDump `. +* admin: added support for :ref:`draining ` listeners via admin interface. * admin: added :http:get:`/stats/recentlookups`, :http:post:`/stats/recentlookups/clear`, :http:post:`/stats/recentlookups/disable`, and :http:post:`/stats/recentlookups/enable` endpoints. -* api: added :ref:`set_node_on_first_message_only ` option to omit the node identifier from the subsequent discovery requests on the same stream. -* buffer filter: now populates content-length header if not present. This behavior can be temporarily disabled using the runtime feature `envoy.reloadable_features.buffer_filter_populate_content_length`. +* api: added :ref:`set_node_on_first_message_only ` option to omit the node identifier from the subsequent discovery requests on the same stream. +* buffer filter: now populates content-length header if not present. This behavior can be temporarily disabled using the runtime feature ``envoy.reloadable_features.buffer_filter_populate_content_length``. * build: official released binary is now PIE so it can be run with ASLR. -* config: added support for :ref:`delta xDS ` (including ADS) delivery. +* config: added support for :ref:`delta xDS ` (including ADS) delivery. * config: enforcing that terminal filters (e.g. HttpConnectionManager for L4, router for L7) be the last in their respective filter chains. -* config: added access log :ref:`extension filter`. +* config: added access log :ref:`extension filter `. * config: added support for :option:`--reject-unknown-dynamic-fields`, providing independent control over whether unknown fields are rejected in static and dynamic configuration. By default, unknown fields in static configuration are rejected and are allowed in dynamic configuration. Warnings are logged for the first use of any unknown field and these occurrences are counted in the - :ref:`server.static_unknown_fields ` and :ref:`server.dynamic_unknown_fields - ` statistics. + :ref:`server.static_unknown_fields ` and :ref:`server.dynamic_unknown_fields + ` statistics. * config: added async data access for local and remote data sources. -* config: changed the default value of :ref:`initial_fetch_timeout ` from 0s to 15s. This is a change in behaviour in the sense that Envoy will move to the next initialization phase, even if the first config is not delivered in 15s. Refer to :ref:`initialization process ` for more details. -* config: added stat :ref:`init_fetch_timeout `. -* config: tls_context in Cluster and FilterChain are deprecated in favor of transport socket. See :ref:`deprecated documentation` for more information. +* config: changed the default value of :ref:`initial_fetch_timeout ` from 0s to 15s. This is a change in behaviour in the sense that Envoy will move to the next initialization phase, even if the first config is not delivered in 15s. Refer to :ref:`initialization process ` for more details. +* config: added stat :ref:`init_fetch_timeout `. +* config: tls_context in Cluster and FilterChain are deprecated in favor of transport socket. See :ref:`deprecated documentation ` for more information. * csrf: added PATCH to supported methods. -* dns: added support for configuring :ref:`dns_failure_refresh_rate ` to set the DNS refresh rate during failures. -* ext_authz: added :ref:`configurable ability ` to send dynamic metadata to the `ext_authz` service. -* ext_authz: added :ref:`filter_enabled RuntimeFractionalPercent flag ` to filter. +* dns: added support for configuring :ref:`dns_failure_refresh_rate ` to set the DNS refresh rate during failures. +* ext_authz: added :ref:`configurable ability ` to send dynamic metadata to the `ext_authz` service. +* ext_authz: added :ref:`filter_enabled RuntimeFractionalPercent flag ` to filter. * ext_authz: added tracing to the HTTP client. -* ext_authz: deprecated :ref:`cluster scope stats ` in favour of filter scope stats. -* fault: added overrides for default runtime keys in :ref:`HTTPFault ` filter. -* grpc: added :ref:`AWS IAM grpc credentials extension ` for AWS-managed xDS. -* grpc: added :ref:`gRPC stats filter ` for collecting stats about gRPC calls and streaming message counts. -* grpc-json: added support for :ref:`ignoring unknown query parameters`. -* grpc-json: added support for :ref:`the grpc-status-details-bin header`. -* header to metadata: added :ref:`PROTOBUF_VALUE ` and :ref:`ValueEncode ` to support protobuf Value and Base64 encoding. -* http: added a default one hour idle timeout to upstream and downstream connections. HTTP connections with no streams and no activity will be closed after one hour unless the default idle_timeout is overridden. To disable upstream idle timeouts, set the :ref:`idle_timeout ` to zero in Cluster :ref:`http_protocol_options`. To disable downstream idle timeouts, either set :ref:`idle_timeout ` to zero in the HttpConnectionManager :ref:`common_http_protocol_options ` or set the deprecated :ref:`connection manager ` field to zero. -* http: added the ability to format HTTP/1.1 header keys using :ref:`header_key_format `. -* http: added the ability to reject HTTP/1.1 requests with invalid HTTP header values, using the runtime feature `envoy.reloadable_features.strict_header_validation`. -* http: changed Envoy to forward existing x-forwarded-proto from upstream trusted proxies. Guarded by `envoy.reloadable_features.trusted_forwarded_proto` which defaults true. -* http: added the ability to configure the behavior of the server response header, via the :ref:`server_header_transformation` field. -* http: added the ability to :ref:`merge adjacent slashes` in the path. -* http: :ref:`AUTO ` codec protocol inference now requires the H2 magic bytes to be the first bytes transmitted by a downstream client. +* ext_authz: deprecated :ref:`cluster scope stats ` in favour of filter scope stats. +* fault: added overrides for default runtime keys in :ref:`HTTPFault ` filter. +* grpc: added :ref:`AWS IAM grpc credentials extension ` for AWS-managed xDS. +* grpc: added :ref:`gRPC stats filter ` for collecting stats about gRPC calls and streaming message counts. +* grpc-json: added support for :ref:`ignoring unknown query parameters `. +* grpc-json: added support for :ref:`the grpc-status-details-bin header `. +* header to metadata: added :ref:`PROTOBUF_VALUE ` and :ref:`ValueEncode ` to support protobuf Value and Base64 encoding. +* http: added a default one hour idle timeout to upstream and downstream connections. HTTP connections with no streams and no activity will be closed after one hour unless the default idle_timeout is overridden. To disable upstream idle timeouts, set the :ref:`idle_timeout ` to zero in Cluster :ref:`http_protocol_options `. To disable downstream idle timeouts, either set :ref:`idle_timeout ` to zero in the HttpConnectionManager :ref:`common_http_protocol_options ` or set the deprecated :ref:`connection manager ` field to zero. +* http: added the ability to format HTTP/1.1 header keys using :ref:`header_key_format `. +* http: added the ability to reject HTTP/1.1 requests with invalid HTTP header values, using the runtime feature ``envoy.reloadable_features.strict_header_validation``. +* http: changed Envoy to forward existing x-forwarded-proto from upstream trusted proxies. Guarded by ``envoy.reloadable_features.trusted_forwarded_proto`` which defaults true. +* http: added the ability to configure the behavior of the server response header, via the :ref:`server_header_transformation ` field. +* http: added the ability to :ref:`merge adjacent slashes ` in the path. +* http: :ref:`AUTO ` codec protocol inference now requires the H2 magic bytes to be the first bytes transmitted by a downstream client. * http: remove h2c upgrade headers for HTTP/1 as h2c upgrades are currently not supported. -* http: absolute URL support is now on by default. The prior behavior can be reinstated by setting :ref:`allow_absolute_url ` to false. -* http: support :ref:`host rewrite ` in the dynamic forward proxy. -* http: support :ref:`disabling the filter per route ` in the grpc http1 reverse bridge filter. -* http: added the ability to :ref:`configure max connection duration ` for downstream connections. -* listeners: added :ref:`continue_on_listener_filters_timeout ` to configure whether a listener will still create a connection when listener filters time out. -* listeners: added :ref:`HTTP inspector listener filter `. -* listeners: added :ref:`connection balancer ` +* http: absolute URL support is now on by default. The prior behavior can be reinstated by setting :ref:`allow_absolute_url ` to false. +* http: support :ref:`host rewrite ` in the dynamic forward proxy. +* http: support :ref:`disabling the filter per route ` in the grpc http1 reverse bridge filter. +* http: added the ability to :ref:`configure max connection duration ` for downstream connections. +* listeners: added :ref:`continue_on_listener_filters_timeout ` to configure whether a listener will still create a connection when listener filters time out. +* listeners: added :ref:`HTTP inspector listener filter `. +* listeners: added :ref:`connection balancer ` configuration for TCP listeners. * listeners: listeners now close the listening socket as part of the draining stage as soon as workers stop accepting their connections. -* lua: extended `httpCall()` and `respond()` APIs to accept headers with entry values that can be a string or table of strings. -* lua: extended `dynamicMetadata:set()` to allow setting complex values. +* lua: extended ``httpCall()`` and ``respond()`` APIs to accept headers with entry values that can be a string or table of strings. +* lua: extended ``dynamicMetadata:set()`` to allow setting complex values. * metrics_service: added support for flushing histogram buckets. -* outlier_detector: added :ref:`support for the grpc-status response header ` by mapping it to HTTP status. Guarded by envoy.reloadable_features.outlier_detection_support_for_grpc_status which defaults to true. +* outlier_detector: added :ref:`support for the grpc-status response header ` by mapping it to HTTP status. Guarded by envoy.reloadable_features.outlier_detection_support_for_grpc_status which defaults to true. * performance: new buffer implementation enabled by default (to disable add "--use-libevent-buffers 1" to the command-line arguments when starting Envoy). * performance: stats symbol table implementation (disabled by default; to test it, add "--use-fake-symbol-table 0" to the command-line arguments when starting Envoy). -* rbac: added support for DNS SAN as :ref:`principal_name `. -* redis: added :ref:`enable_command_stats ` to enable :ref:`per command statistics ` for upstream clusters. -* redis: added :ref:`read_policy ` to allow reading from redis replicas for Redis Cluster deployments. +* rbac: added support for DNS SAN as :ref:`principal_name `. +* redis: added :ref:`enable_command_stats ` to enable :ref:`per command statistics ` for upstream clusters. +* redis: added :ref:`read_policy ` to allow reading from redis replicas for Redis Cluster deployments. * redis: fixed a bug where the redis health checker ignored the upstream auth password. * redis: enable_hashtaging is always enabled when the upstream uses open source Redis cluster protocol. -* regex: introduced new :ref:`RegexMatcher ` type that +* regex: introduced new :ref:`RegexMatcher ` type that provides a safe regex implementation for untrusted user input. This type is now used in all configuration that processes user provided input. See :ref:`deprecated configuration details - ` for more information. -* rbac: added conditions to the policy, see :ref:`condition `. -* router: added :ref:`rq_retry_skipped_request_not_complete ` counter stat to router stats. -* router: :ref:`scoped routing ` is supported. -* router: added new :ref:`retriable-headers ` retry policy. Retries can now be configured to trigger by arbitrary response header matching. + ` for more information. +* rbac: added conditions to the policy, see :ref:`condition `. +* router: added :ref:`rq_retry_skipped_request_not_complete ` counter stat to router stats. +* router: :ref:`scoped routing ` is supported. +* router: added new :ref:`retriable-headers ` retry policy. Retries can now be configured to trigger by arbitrary response header matching. * router: added ability for most specific header mutations to take precedence, see :ref:`route configuration's most specific - header mutations wins flag `. -* router: added :ref:`respect_expected_rq_timeout ` that instructs ingress Envoy to respect :ref:`config_http_filters_router_x-envoy-expected-rq-timeout-ms` header, populated by egress Envoy, when deriving timeout for upstream cluster. -* router: added new :ref:`retriable request headers ` to route configuration, to allow limiting buffering for retries and shadowing. -* router: added new :ref:`retriable request headers ` to retry policies. Retries can now be configured to only trigger on request header match. + header mutations wins flag `. +* router: added :ref:`respect_expected_rq_timeout ` that instructs ingress Envoy to respect :ref:`config_http_filters_router_x-envoy-expected-rq-timeout-ms` header, populated by egress Envoy, when deriving timeout for upstream cluster. +* router: added new :ref:`retriable request headers ` to route configuration, to allow limiting buffering for retries and shadowing. +* router: added new :ref:`retriable request headers ` to retry policies. Retries can now be configured to only trigger on request header match. * router: added the ability to match a route based on whether a TLS certificate has been - :ref:`presented ` by the + :ref:`presented ` by the downstream connection. * router check tool: added coverage reporting & enforcement. * router check tool: added comprehensive coverage reporting. @@ -93,69 +93,69 @@ Changes * router check tool: added coverage reporting for direct response routes. * runtime: allows for the ability to parse boolean values. * runtime: allows for the ability to parse integers as double values and vice-versa. -* sds: added :ref:`session_ticket_keys_sds_secret_config ` for loading TLS Session Ticket Encryption Keys using SDS API. +* sds: added :ref:`session_ticket_keys_sds_secret_config ` for loading TLS Session Ticket Encryption Keys using SDS API. * server: added a post initialization lifecycle event, in addition to the existing startup and shutdown events. -* server: added :ref:`per-handler listener stats ` and - :ref:`per-worker watchdog stats ` to help diagnosing event +* server: added :ref:`per-handler listener stats ` and + :ref:`per-worker watchdog stats ` to help diagnosing event loop imbalance and general performance issues. * stats: added unit support to histogram. * tcp_proxy: the default :ref:`idle_timeout - ` is now 1 hour. + ` is now 1 hour. * thrift_proxy: fixed crashing bug on invalid transport/protocol framing. * thrift_proxy: added support for stripping service name from method when using the multiplexed protocol. * tls: added verification of IP address SAN fields in certificates against configured SANs in the certificate validation context. * tracing: added support to the Zipkin reporter for sending list of spans as Zipkin JSON v2 and protobuf message over HTTP. certificate validation context. * tracing: added tags for gRPC response status and message. -* tracing: added :ref:`max_path_tag_length ` to support customizing the length of the request path included in the extracted `http.url `_ tag. -* upstream: added :ref:`an option ` that allows draining HTTP, TCP connection pools on cluster membership change. -* upstream: added :ref:`transport_socket_matches `, support using different transport socket config when connecting to different upstream endpoints within a cluster. -* upstream: added network filter chains to upstream connections, see :ref:`filters`. -* upstream: added new :ref:`failure-percentage based outlier detection` mode. +* tracing: added :ref:`max_path_tag_length ` to support customizing the length of the request path included in the extracted `http.url `_ tag. +* upstream: added :ref:`an option ` that allows draining HTTP, TCP connection pools on cluster membership change. +* upstream: added :ref:`transport_socket_matches `, support using different transport socket config when connecting to different upstream endpoints within a cluster. +* upstream: added network filter chains to upstream connections, see :ref:`filters `. +* upstream: added new :ref:`failure-percentage based outlier detection ` mode. * upstream: uses p2c to select hosts for least-requests load balancers if all host weights are the same, even in cases where weights are not equal to 1. -* upstream: added :ref:`fail_traffic_on_panic ` to allow failing all requests to a cluster during panic state. +* upstream: added :ref:`fail_traffic_on_panic ` to allow failing all requests to a cluster during panic state. * zookeeper: parses responses and emits latency stats. Deprecated ---------- -* The ORIGINAL_DST_LB :ref:`load balancing policy ` is +* The ORIGINAL_DST_LB :ref:`load balancing policy ` is deprecated, use CLUSTER_PROVIDED policy instead when configuring an :ref:`original destination - cluster `. -* The `regex` field in :ref:`StringMatcher ` has been - deprecated in favor of the `safe_regex` field. -* The `regex` field in :ref:`RouteMatch ` has been - deprecated in favor of the `safe_regex` field. -* The `allow_origin` and `allow_origin_regex` fields in :ref:`CorsPolicy - ` have been deprecated in favor of the - `allow_origin_string_match` field. -* The `pattern` and `method` fields in :ref:`VirtualCluster ` - have been deprecated in favor of the `headers` field. -* The `regex_match` field in :ref:`HeaderMatcher ` has been - deprecated in favor of the `safe_regex_match` field. -* The `value` and `regex` fields in :ref:`QueryParameterMatcher - ` has been deprecated in favor of the `string_match` - and `present_match` fields. + cluster `. +* The `regex` field in :ref:`StringMatcher ` has been + deprecated in favor of the ``safe_regex`` field. +* The `regex` field in :ref:`RouteMatch ` has been + deprecated in favor of the ``safe_regex`` field. +* The ``allow_origin`` and ``allow_origin_regex`` fields in :ref:`CorsPolicy + ` have been deprecated in favor of the + ``allow_origin_string_match`` field. +* The ``pattern`` and ``method`` fields in :ref:`VirtualCluster ` + have been deprecated in favor of the ``headers`` field. +* The `regex_match` field in :ref:`HeaderMatcher ` has been + deprecated in favor of the ``safe_regex_match`` field. +* The ``value`` and ``regex`` fields in :ref:`QueryParameterMatcher + ` has been deprecated in favor of the ``string_match`` + and ``present_match`` fields. * The :option:`--allow-unknown-fields` command-line option, use :option:`--allow-unknown-static-fields` instead. * The use of HTTP_JSON_V1 :ref:`Zipkin collector endpoint version - ` or not explicitly + ` or not explicitly specifying it is deprecated, use HTTP_JSON or HTTP_PROTO instead. * The `operation_name` field in :ref:`HTTP connection manager - ` - has been deprecated in favor of the `traffic_direction` field in - :ref:`Listener `. The latter takes priority if + ` + has been deprecated in favor of the ``traffic_direction`` field in + :ref:`Listener `. The latter takes priority if specified. -* The `tls_context` field in :ref:`Filter chain ` message - and :ref:`Cluster ` message have been deprecated in favor of - `transport_socket` with name `envoy.transport_sockets.tls`. The latter takes priority if specified. -* The `use_http2` field in - :ref:`HTTP health checker ` has been deprecated in - favor of the `codec_client_type` field. -* The use of :ref:`gRPC bridge filter ` for +* The `tls_context` field in :ref:`Filter chain ` message + and :ref:`Cluster ` message have been deprecated in favor of + ``transport_socket`` with name ``envoy.transport_sockets.tls``. The latter takes priority if specified. +* The ``use_http2`` field in + :ref:`HTTP health checker ` has been deprecated in + favor of the ``codec_client_type`` field. +* The use of :ref:`gRPC bridge filter ` for gRPC stats has been deprecated in favor of the dedicated :ref:`gRPC stats - filter ` -* Ext_authz filter stats `ok`, `error`, `denied`, `failure_mode_allowed` in + filter ` +* Ext_authz filter stats ``ok``, ``error``, ``denied``, ``failure_mode_allowed`` in *cluster..ext_authz.* namespace is deprecated. Use *http..ext_authz.* namespace to access same counters instead. * Use of google.protobuf.Struct for extension opaque configs is deprecated. Use google.protobuf.Any instead or pack diff --git a/docs/root/version_history/v1.12.2.rst b/docs/root/version_history/v1.12.2.rst index 041e9d8429c6d..d3c5183e23cb5 100644 --- a/docs/root/version_history/v1.12.2.rst +++ b/docs/root/version_history/v1.12.2.rst @@ -7,5 +7,5 @@ Changes * http: fixed CVE-2019-18801 by allocating sufficient memory for request headers. * http: fixed CVE-2019-18802 by implementing stricter validation of HTTP/1 headers. * http: trim LWS at the end of header keys, for correct HTTP/1.1 header parsing. -* http: added strict authority checking. This can be reversed temporarily by setting the runtime feature `envoy.reloadable_features.strict_authority_validation` to false. +* http: added strict authority checking. This can be reversed temporarily by setting the runtime feature ``envoy.reloadable_features.strict_authority_validation`` to false. * route config: fixed CVE-2019-18838 by checking for presence of host/path headers. diff --git a/docs/root/version_history/v1.12.3.rst b/docs/root/version_history/v1.12.3.rst index a4e1dff40d27c..53b87280ad7b4 100644 --- a/docs/root/version_history/v1.12.3.rst +++ b/docs/root/version_history/v1.12.3.rst @@ -5,7 +5,7 @@ Changes ------- * buffer: force copy when appending small slices to OwnedImpl buffer to avoid fragmentation. -* http: added HTTP/1.1 flood protection. Can be temporarily disabled using the runtime feature `envoy.reloadable_features.http1_flood_protection`. -* listeners: fixed issue where :ref:`TLS inspector listener filter ` could have been bypassed by a client using only TLS 1.3. -* rbac: added :ref:`url_path ` for matching URL path without the query and fragment string. +* http: added HTTP/1.1 flood protection. Can be temporarily disabled using the runtime feature ``envoy.reloadable_features.http1_flood_protection``. +* listeners: fixed issue where :ref:`TLS inspector listener filter ` could have been bypassed by a client using only TLS 1.3. +* rbac: added :ref:`url_path ` for matching URL path without the query and fragment string. * sds: fixed the SDS vulnerability that TLS validation context (e.g., subject alt name or hash) cannot be effectively validated in some cases. diff --git a/docs/root/version_history/v1.12.4.rst b/docs/root/version_history/v1.12.4.rst index 1635bbb5f0000..7b606d34dbce3 100644 --- a/docs/root/version_history/v1.12.4.rst +++ b/docs/root/version_history/v1.12.4.rst @@ -4,5 +4,5 @@ Changes ------- -* http: added :ref:`headers_with_underscores_action setting ` to control how client requests with header names containing underscore characters are handled. The options are to allow such headers, reject request or drop headers. The default is to allow headers, preserving existing behavior. +* http: added :ref:`headers_with_underscores_action setting ` to control how client requests with header names containing underscore characters are handled. The options are to allow such headers, reject request or drop headers. The default is to allow headers, preserving existing behavior. * http: fixed CVE-2020-11080 by rejecting HTTP/2 SETTINGS frames with too many parameters. diff --git a/docs/root/version_history/v1.12.5.rst b/docs/root/version_history/v1.12.5.rst index b246e20d885b6..dcca35f09aef3 100644 --- a/docs/root/version_history/v1.12.5.rst +++ b/docs/root/version_history/v1.12.5.rst @@ -4,8 +4,8 @@ Changes ------- * buffer: fixed CVE-2020-12603 by avoiding fragmentation, and tracking of HTTP/2 data and control frames in the output buffer. -* http: fixed CVE-2020-12604 by changing :ref:`stream_idle_timeout ` +* http: fixed CVE-2020-12604 by changing :ref:`stream_idle_timeout ` to also defend against an HTTP/2 peer that does not open stream window once an entire response has been buffered to be sent to a downstream client. * http: fixed CVE-2020-12605 by including request URL in request header size computation, and rejecting partial headers that exceed configured limits. -* listener: fixed CVE-2020-8663 by adding runtime support for :ref:`per-listener limits ` on active/accepted connections. -* overload management: fixed CVE-2020-8663 by adding runtime support for :ref:`global limits ` on active/accepted connections. +* listener: fixed CVE-2020-8663 by adding runtime support for :ref:`per-listener limits ` on active/accepted connections. +* overload management: fixed CVE-2020-8663 by adding runtime support for :ref:`global limits ` on active/accepted connections. diff --git a/docs/root/version_history/v1.12.6.rst b/docs/root/version_history/v1.12.6.rst index b4abcd6ee8da8..5d562d5875435 100644 --- a/docs/root/version_history/v1.12.6.rst +++ b/docs/root/version_history/v1.12.6.rst @@ -1,3 +1,3 @@ 1.12.6 (July 7, 2020) ===================== -* tls: fixed a bug where wilcard matching for "\*.foo.com" also matched domains of the form "a.b.foo.com". This behavior can be temporarily reverted by setting runtime feature `envoy.reloadable_features.fix_wildcard_matching` to false. +* tls: fixed a bug where wilcard matching for "\*.foo.com" also matched domains of the form "a.b.foo.com". This behavior can be temporarily reverted by setting runtime feature ``envoy.reloadable_features.fix_wildcard_matching`` to false. diff --git a/docs/root/version_history/v1.13.0.rst b/docs/root/version_history/v1.13.0.rst index 1e0416713c485..b297835547035 100644 --- a/docs/root/version_history/v1.13.0.rst +++ b/docs/root/version_history/v1.13.0.rst @@ -4,82 +4,82 @@ Changes ------- -* access log: added FILTER_STATE :ref:`access log formatters ` and gRPC access logger. -* admin: added the ability to filter :ref:`/config_dump `. -* access log: added a :ref:`typed JSON logging mode ` to output access logs in JSON format with non-string values -* access log: fixed UPSTREAM_LOCAL_ADDRESS :ref:`access log formatters ` to work for http requests +* access log: added FILTER_STATE :ref:`access log formatters ` and gRPC access logger. +* admin: added the ability to filter :ref:`/config_dump `. +* access log: added a :ref:`typed JSON logging mode ` to output access logs in JSON format with non-string values +* access log: fixed UPSTREAM_LOCAL_ADDRESS :ref:`access log formatters ` to work for http requests * access log: added HOSTNAME. * api: remove all support for v1 -* api: added ability to specify `mode` for :ref:`Pipe `. +* api: added ability to specify `mode` for :ref:`Pipe `. * api: support for the v3 xDS API added. See :ref:`api_supported_versions`. -* aws_request_signing: added new alpha :ref:`HTTP AWS request signing filter `. +* aws_request_signing: added new alpha HTTP AWS request signing filter * buffer: remove old implementation * build: official released binary is now built against libc++. -* cluster: added :ref:`aggregate cluster ` that allows load balancing between clusters. +* cluster: added :ref:`aggregate cluster ` that allows load balancing between clusters. * config: all category names of internal envoy extensions are prefixed with the 'envoy.' prefix to follow the reverse DNS naming notation. * decompressor: remove decompressor hard assert failure and replace with an error flag. -* ext_authz: added :ref:`configurable ability` to send the :ref:`certificate` to the `ext_authz` service. -* fault: fixed an issue where the http fault filter would repeatedly check the percentage of abort/delay when the `x-envoy-downstream-service-cluster` header was included in the request to ensure that the actual percentage of abort/delay matches the configuration of the filter. +* ext_authz: added :ref:`configurable ability ` to send the :ref:`certificate ` to the `ext_authz` service. +* fault: fixed an issue where the http fault filter would repeatedly check the percentage of abort/delay when the ``x-envoy-downstream-service-cluster`` header was included in the request to ensure that the actual percentage of abort/delay matches the configuration of the filter. * health check: gRPC health checker sets the gRPC deadline to the configured timeout duration. -* health check: added :ref:`TlsOptions ` to allow TLS configuration overrides. -* health check: added :ref:`service_name_matcher ` to better compare the service name patterns for health check identity. -* http: added strict validation that CONNECT is refused as it is not yet implemented. This can be reversed temporarily by setting the runtime feature `envoy.reloadable_features.strict_method_validation` to false. -* http: added support for http1 trailers. To enable use :ref:`enable_trailers `. -* http: added the ability to sanitize headers nominated by the Connection header. This new behavior is guarded by envoy.reloadable_features.connection_header_sanitization which defaults to true. -* http: blocks unsupported transfer-encodings. Can be reverted temporarily by setting runtime feature `envoy.reloadable_features.reject_unsupported_transfer_encodings` to false. -* http: support :ref:`auto_host_rewrite_header` in the dynamic forward proxy. -* jwt_authn: added :ref:`allow_missing` option that accepts request without token but rejects bad request with bad tokens. -* jwt_authn: added :ref:`bypass_cors_preflight` to allow bypassing the CORS preflight request. -* lb_subset_config: new fallback policy for selectors: :ref:`KEYS_SUBSET` -* listeners: added :ref:`reuse_port` option. -* logger: added :ref:`--log-format-escaped ` command line option to escape newline characters in application logs. -* ratelimit: added :ref:`local rate limit ` network filter. -* rbac: added support for matching all subject alt names instead of first in :ref:`principal_name `. +* health check: added :ref:`TlsOptions ` to allow TLS configuration overrides. +* health check: added :ref:`service_name_matcher ` to better compare the service name patterns for health check identity. +* http: added strict validation that CONNECT is refused as it is not yet implemented. This can be reversed temporarily by setting the runtime feature ``envoy.reloadable_features.strict_method_validation`` to false. +* http: added support for http1 trailers. To enable use :ref:`enable_trailers `. +* http: added the ability to sanitize headers nominated by the Connection header. This new behavior is guarded by ``envoy.reloadable_features.connection_header_sanitization`` which defaults to true. +* http: blocks unsupported transfer-encodings. Can be reverted temporarily by setting runtime feature ``envoy.reloadable_features.reject_unsupported_transfer_encodings`` to false. +* http: support :ref:`auto_host_rewrite_header ` in the dynamic forward proxy. +* jwt_authn: added :ref:`allow_missing ` option that accepts request without token but rejects bad request with bad tokens. +* jwt_authn: added :ref:`bypass_cors_preflight ` to allow bypassing the CORS preflight request. +* lb_subset_config: new fallback policy for selectors: :ref:`KEYS_SUBSET ` +* listeners: added :ref:`reuse_port ` option. +* logger: added :ref:`--log-format-escaped ` command line option to escape newline characters in application logs. +* ratelimit: added :ref:`local rate limit ` network filter. +* rbac: added support for matching all subject alt names instead of first in :ref:`principal_name `. * redis: performance improvement for larger split commands by avoiding string copies. * redis: correctly follow MOVE/ASK redirection for mirrored clusters. -* redis: add :ref:`host_degraded_refresh_threshold ` and :ref:`failure_refresh_threshold ` to refresh topology when nodes are degraded or when requests fails. -* router: added histograms to show timeout budget usage to the :ref:`cluster stats `. +* redis: add :ref:`host_degraded_refresh_threshold ` and :ref:`failure_refresh_threshold ` to refresh topology when nodes are degraded or when requests fails. +* router: added histograms to show timeout budget usage to the :ref:`cluster stats `. * router check tool: added support for testing and marking coverage for routes of runtime fraction 0. -* router: added :ref:`request_mirror_policies` to support sending multiple mirrored requests in one route. -* router: added support for REQ(header-name) :ref:`header formatter `. -* router: added support for percentage-based :ref:`retry budgets ` -* router: allow using a :ref:`query parameter ` for HTTP consistent hashing. +* router: added :ref:`request_mirror_policies ` to support sending multiple mirrored requests in one route. +* router: added support for REQ(header-name) :ref:`header formatter `. +* router: added support for percentage-based :ref:`retry budgets ` +* router: allow using a :ref:`query parameter ` for HTTP consistent hashing. * router: exposed DOWNSTREAM_REMOTE_ADDRESS as custom HTTP request/response headers. -* router: added support for :ref:`max_internal_redirects ` for configurable maximum internal redirect hops. +* router: added support for :ref:`max_internal_redirects ` for configurable maximum internal redirect hops. * router: skip the Location header when the response code is not a 201 or a 3xx. -* router: added :ref:`auto_sni ` to support setting SNI to transport socket for new upstream connections based on the downstream HTTP host/authority header. +* router: added :ref:`auto_sni ` to support setting SNI to transport socket for new upstream connections based on the downstream HTTP host/authority header. * router: added support for HOSTNAME :ref:`header formatter - `. + `. * server: added the :option:`--disable-extensions` CLI option, to disable extensions at startup. * server: fixed a bug in config validation for configs with runtime layers. -* server: added :ref:`workers_started ` that indicates whether listeners have been fully initialized on workers. -* tcp_proxy: added :ref:`ClusterWeight.metadata_match`. -* tcp_proxy: added :ref:`hash_policy`. +* server: added :ref:`workers_started ` that indicates whether listeners have been fully initialized on workers. +* tcp_proxy: added :ref:`ClusterWeight.metadata_match `. +* tcp_proxy: added :ref:`hash_policy `. * thrift_proxy: added support for cluster header based routing. * thrift_proxy: added stats to the router filter. * tls: remove TLS 1.0 and 1.1 from client defaults -* tls: added support for :ref:`generic string matcher ` for subject alternative names. -* tracing: added the ability to set custom tags on both the :ref:`HTTP connection manager` and the :ref:`HTTP route `. +* tls: added support for :ref:`generic string matcher ` for subject alternative names. +* tracing: added the ability to set custom tags on both the :ref:`HTTP connection manager ` and the :ref:`HTTP route `. * tracing: added upstream_address tag. -* tracing: added initial support for AWS X-Ray (local sampling rules only) :ref:`X-Ray Tracing `. +* tracing: added initial support for AWS X-Ray (local sampling rules only) :ref:`X-Ray Tracing `. * tracing: added tags for gRPC request path, authority, content-type and timeout. -* udp: added initial support for :ref:`UDP proxy ` +* udp: added initial support for :ref:`UDP proxy ` Deprecated ---------- * The `request_headers_for_tags` field in :ref:`HTTP connection manager - ` + ` has been deprecated in favor of the :ref:`custom_tags - ` field. + ` field. * The `verify_subject_alt_name` field in :ref:`Certificate Validation Context - ` + ` has been deprecated in favor of the :ref:`match_subject_alt_names - ` field. -* The `request_mirror_policy` field in :ref:`RouteMatch ` has been deprecated in - favor of the `request_mirror_policies` field. -* The `service_name` field in - :ref:`HTTP health checker ` has been deprecated in - favor of the `service_name_matcher` field. + ` field. +* The ``request_mirror_policy`` field in :ref:`RouteMatch ` has been deprecated in + favor of the ``request_mirror_policies`` field. +* The ``service_name`` field in + :ref:`HTTP health checker ` has been deprecated in + favor of the ``service_name_matcher`` field. * The v2 xDS API is deprecated. It will be supported by Envoy until EOY 2020. See :ref:`api_supported_versions`. diff --git a/docs/root/version_history/v1.13.1.rst b/docs/root/version_history/v1.13.1.rst index 379edc6fed474..46d05ebc9d5c9 100644 --- a/docs/root/version_history/v1.13.1.rst +++ b/docs/root/version_history/v1.13.1.rst @@ -5,7 +5,7 @@ Changes ------- * buffer: force copy when appending small slices to OwnedImpl buffer to avoid fragmentation. -* http: added HTTP/1.1 flood protection. Can be temporarily disabled using the runtime feature `envoy.reloadable_features.http1_flood_protection`. -* listeners: fixed issue where :ref:`TLS inspector listener filter ` could have been bypassed by a client using only TLS 1.3. -* rbac: added :ref:`url_path ` for matching URL path without the query and fragment string. +* http: added HTTP/1.1 flood protection. Can be temporarily disabled using the runtime feature ``envoy.reloadable_features.http1_flood_protection``. +* listeners: fixed issue where :ref:`TLS inspector listener filter ` could have been bypassed by a client using only TLS 1.3. +* rbac: added :ref:`url_path ` for matching URL path without the query and fragment string. * sds: fixed the SDS vulnerability that TLS validation context (e.g., subject alt name or hash) cannot be effectively validated in some cases. diff --git a/docs/root/version_history/v1.13.2.rst b/docs/root/version_history/v1.13.2.rst index 641bbaa451d46..5ef942997b7c2 100644 --- a/docs/root/version_history/v1.13.2.rst +++ b/docs/root/version_history/v1.13.2.rst @@ -4,5 +4,5 @@ Changes ------- -* http: added :ref:`headers_with_underscores_action setting ` to control how client requests with header names containing underscore characters are handled. The options are to allow such headers, reject request or drop headers. The default is to allow headers, preserving existing behavior. +* http: added :ref:`headers_with_underscores_action setting ` to control how client requests with header names containing underscore characters are handled. The options are to allow such headers, reject request or drop headers. The default is to allow headers, preserving existing behavior. * http: fixed CVE-2020-11080 by rejecting HTTP/2 SETTINGS frames with too many parameters. diff --git a/docs/root/version_history/v1.13.3.rst b/docs/root/version_history/v1.13.3.rst index 6002a62c496b0..8cdbfe128c931 100644 --- a/docs/root/version_history/v1.13.3.rst +++ b/docs/root/version_history/v1.13.3.rst @@ -5,8 +5,8 @@ Changes ------- * buffer: fixed CVE-2020-12603 by avoiding fragmentation, and tracking of HTTP/2 data and control frames in the output buffer. -* http: fixed CVE-2020-12604 by changing :ref:`stream_idle_timeout ` +* http: fixed CVE-2020-12604 by changing :ref:`stream_idle_timeout ` to also defend against an HTTP/2 peer that does not open stream window once an entire response has been buffered to be sent to a downstream client. * http: fixed CVE-2020-12605 by including request URL in request header size computation, and rejecting partial headers that exceed configured limits. -* listener: fixed CVE-2020-8663 by adding runtime support for :ref:`per-listener limits ` on active/accepted connections. -* overload management: fixed CVE-2020-8663 by adding runtime support for :ref:`global limits ` on active/accepted connections. +* listener: fixed CVE-2020-8663 by adding runtime support for :ref:`per-listener limits ` on active/accepted connections. +* overload management: fixed CVE-2020-8663 by adding runtime support for :ref:`global limits ` on active/accepted connections. diff --git a/docs/root/version_history/v1.13.4.rst b/docs/root/version_history/v1.13.4.rst index 60ca6acece279..03a78e8be6e41 100644 --- a/docs/root/version_history/v1.13.4.rst +++ b/docs/root/version_history/v1.13.4.rst @@ -1,3 +1,3 @@ 1.13.4 (July 7, 2020) ===================== -* tls: fixed a bug where wilcard matching for "\*.foo.com" also matched domains of the form "a.b.foo.com". This behavior can be temporarily reverted by setting runtime feature `envoy.reloadable_features.fix_wildcard_matching` to false. +* tls: fixed a bug where wilcard matching for "\*.foo.com" also matched domains of the form "a.b.foo.com". This behavior can be temporarily reverted by setting runtime feature ``envoy.reloadable_features.fix_wildcard_matching`` to false. diff --git a/docs/root/version_history/v1.14.0.rst b/docs/root/version_history/v1.14.0.rst index 649a34d1cce34..b677dac0f96da 100644 --- a/docs/root/version_history/v1.14.0.rst +++ b/docs/root/version_history/v1.14.0.rst @@ -5,93 +5,93 @@ Changes ------- * access log: access logger extensions use the "envoy.access_loggers" name space. A mapping - of extension names is available in the :ref:`deprecated ` documentation. -* access log: added support for `%DOWNSTREAM_LOCAL_PORT%` :ref:`access log formatters `. -* access log: fixed `%DOWSTREAM_DIRECT_REMOTE_ADDRESS%` when used with PROXY protocol listener filter. -* access log: introduced :ref:`connection-level access loggers`. + of extension names is available in the :ref:`deprecated ` documentation. +* access log: added support for ``%DOWNSTREAM_LOCAL_PORT%`` :ref:`access log formatters `. +* access log: fixed ``%DOWSTREAM_DIRECT_REMOTE_ADDRESS%`` when used with PROXY protocol listener filter. +* access log: introduced :ref:`connection-level access loggers `. * adaptive concurrency: fixed bug that allowed concurrency limits to drop below the configured minimum. * adaptive concurrency: minRTT is now triggered when the minimum concurrency is maintained for 5 consecutive sampling intervals. -* admin: added support for displaying ip address subject alternate names in :ref:`certs` end point. +* admin: added support for displaying ip address subject alternate names in :ref:`certs ` end point. * admin: added :http:post:`/reopen_logs` endpoint to control log rotation. * api: froze v2 xDS API. New feature development in the API should occur in v3 xDS. While the v2 xDS API has been deprecated since 1.13.0, it will continue to be supported by Envoy until EOY 2020. See :ref:`api_supported_versions`. -* aws_lambda: added :ref:`AWS Lambda filter ` that converts HTTP requests to Lambda +* aws_lambda: added :ref:`AWS Lambda filter ` that converts HTTP requests to Lambda invokes. This effectively makes Envoy act as an egress gateway to AWS Lambda. * aws_request_signing: a few fixes so that it works with S3. -* config: added stat :ref:`update_time `. -* config: use type URL to select an extension whenever the config type URL (or its previous versions) uniquely identify a typed extension, see :ref:`extension configuration `. +* config: added stat :ref:`update_time `. +* config: use type URL to select an extension whenever the config type URL (or its previous versions) uniquely identify a typed extension, see :ref:`extension configuration `. * datasource: added retry policy for remote async data source. -* dns: added support for :ref:`dns_failure_refresh_rate ` for the :ref:`dns cache ` to set the DNS refresh rate during failures. +* dns: added support for :ref:`dns_failure_refresh_rate ` for the :ref:`dns cache ` to set the DNS refresh rate during failures. * dns: the STRICT_DNS cluster now only resolves to 0 hosts if DNS resolution successfully returns 0 hosts. -* eds: added :ref:`hostname ` field for endpoints and :ref:`hostname ` field for endpoint's health check config. This enables auto host rewrite and customizing the host header during health checks for eds endpoints. -* ext_authz: disabled the use of lowercase string matcher for headers matching in HTTP-based `ext_authz`. - Can be reverted temporarily by setting runtime feature `envoy.reloadable_features.ext_authz_http_service_enable_case_sensitive_string_matcher` to false. -* fault: added support for controlling abort faults with :ref:`HTTP header fault configuration ` to the HTTP fault filter. +* eds: added :ref:`hostname ` field for endpoints and :ref:`hostname ` field for endpoint's health check config. This enables auto host rewrite and customizing the host header during health checks for eds endpoints. +* ext_authz: disabled the use of lowercase string matcher for headers matching in HTTP-based ``ext_authz``. + Can be reverted temporarily by setting runtime feature ``envoy.reloadable_features.ext_authz_http_service_enable_case_sensitive_string_matcher`` to false. +* fault: added support for controlling abort faults with :ref:`HTTP header fault configuration ` to the HTTP fault filter. * grpc-json: added support for building HTTP request into `google.api.HttpBody `_. * grpc-stats: added option to limit which messages stats are created for. -* http: added HTTP/1.1 flood protection. Can be temporarily disabled using the runtime feature `envoy.reloadable_features.http1_flood_protection`. -* http: added :ref:`headers_with_underscores_action setting ` to control how client requests with header names containing underscore characters are handled. The options are to allow such headers, reject request or drop headers. The default is to allow headers, preserving existing behavior. -* http: added :ref:`max_stream_duration ` to specify the duration of existing streams. See :ref:`connection and stream timeouts `. +* http: added HTTP/1.1 flood protection. Can be temporarily disabled using the runtime feature ``envoy.reloadable_features.http1_flood_protection``. +* http: added :ref:`headers_with_underscores_action setting ` to control how client requests with header names containing underscore characters are handled. The options are to allow such headers, reject request or drop headers. The default is to allow headers, preserving existing behavior. +* http: added :ref:`max_stream_duration ` to specify the duration of existing streams. See :ref:`connection and stream timeouts `. * http: connection header sanitizing has been modified to always sanitize if there is no upgrade, including when an h2c upgrade attempt has been removed. * http: fixed a bug that could send extra METADATA frames and underflow memory when encoding METADATA frames on a connection that was dispatching data. * http: fixing a bug in HTTP/1.0 responses where Connection: keep-alive was not appended for connections which were kept alive. * http: http filter extensions use the "envoy.filters.http" name space. A mapping - of extension names is available in the :ref:`deprecated ` documentation. -* http: the runtime feature `http.connection_manager.log_flood_exception` is removed and replaced with a connection access log response code. + of extension names is available in the :ref:`deprecated ` documentation. +* http: the runtime feature ``http.connection_manager.log_flood_exception`` is removed and replaced with a connection access log response code. * http: upgrade parser library, which removes support for "identity" transfer-encoding value. * listener filters: listener filter extensions use the "envoy.filters.listener" name space. A - mapping of extension names is available in the :ref:`deprecated ` documentation. -* listeners: added :ref:`listener filter matcher api ` to disable individual listener filter on matching downstream connections. -* loadbalancing: added support for using hostname for consistent hash loadbalancing via :ref:`consistent_hash_lb_config `. -* loadbalancing: added support for :ref:`retry host predicates ` in conjunction with consistent hashing load balancers (ring hash and maglev). -* lua: added a parameter to `httpCall` that makes it possible to have the call be asynchronous. + mapping of extension names is available in the :ref:`deprecated ` documentation. +* listeners: added :ref:`listener filter matcher api ` to disable individual listener filter on matching downstream connections. +* loadbalancing: added support for using hostname for consistent hash loadbalancing via :ref:`consistent_hash_lb_config `. +* loadbalancing: added support for :ref:`retry host predicates ` in conjunction with consistent hashing load balancers (ring hash and maglev). +* lua: added a parameter to ``httpCall`` that makes it possible to have the call be asynchronous. * lua: added moonjit support. -* mongo: the stat emitted for queries without a max time set in the :ref:`MongoDB filter` was modified to emit correctly for Mongo v3.2+. -* network filters: added a :ref:`direct response filter `. +* mongo: the stat emitted for queries without a max time set in the :ref:`MongoDB filter ` was modified to emit correctly for Mongo v3.2+. +* network filters: added a :ref:`direct response filter `. * network filters: network filter extensions use the "envoy.filters.network" name space. A mapping - of extension names is available in the :ref:`deprecated ` documentation. -* rbac: added :ref:`remote_ip ` and :ref:`direct_remote_ip ` for matching downstream remote IP address. -* rbac: deprecated :ref:`source_ip ` with :ref:`direct_remote_ip ` and :ref:`remote_ip `. -* request_id_extension: added an ability to extend request ID handling at :ref:`HTTP connection manager`. -* retry: added a retry predicate that :ref:`rejects hosts based on metadata. `. + of extension names is available in the :ref:`deprecated ` documentation. +* rbac: added :ref:`remote_ip ` and :ref:`direct_remote_ip ` for matching downstream remote IP address. +* rbac: deprecated :ref:`source_ip ` with :ref:`direct_remote_ip ` and :ref:`remote_ip `. +* request_id_extension: added an ability to extend request ID handling at :ref:`HTTP connection manager `. +* retry: added a retry predicate that :ref:`rejects hosts based on metadata. `. * router: added ability to set attempt count in downstream response, see :ref:`virtual host's include response - attempt count config `. -* router: added additional stats for :ref:`virtual clusters `. -* router: added :ref:`auto_san_validation ` to support overrriding SAN validation to transport socket for new upstream connections based on the downstream HTTP host/authority header. + attempt count config `. +* router: added additional stats for :ref:`virtual clusters `. +* router: added :ref:`auto_san_validation ` to support overrriding SAN validation to transport socket for new upstream connections based on the downstream HTTP host/authority header. * router: added the ability to match a route based on whether a downstream TLS connection certificate has been - :ref:`validated `. + :ref:`validated `. * router: added support for :ref:`regex_rewrite - ` for path rewriting using regular expressions and capture groups. -* router: added support for `%DOWNSTREAM_LOCAL_PORT%` :ref:`header formatter `. -* router: don't ignore :ref:`per_try_timeout ` when the :ref:`global route timeout ` is disabled. -* router: strip whitespace for :ref:`retry_on `, :ref:`grpc-retry-on header ` and :ref:`retry-on header `. -* runtime: enabling the runtime feature `envoy.deprecated_features.allow_deprecated_extension_names` + ` for path rewriting using regular expressions and capture groups. +* router: added support for `%DOWNSTREAM_LOCAL_PORT%` :ref:`header formatter `. +* router: don't ignore :ref:`per_try_timeout ` when the :ref:`global route timeout ` is disabled. +* router: strip whitespace for :ref:`retry_on `, :ref:`grpc-retry-on header ` and :ref:`retry-on header `. +* runtime: enabling the runtime feature ``envoy.deprecated_features.allow_deprecated_extension_names`` disables the use of deprecated extension names. * runtime: integer values may now be parsed as booleans. -* sds: added :ref:`GenericSecret ` to support secret of generic type. -* sds: added :ref:`certificate rotation ` support for certificates in static resources. +* sds: added :ref:`GenericSecret ` to support secret of generic type. +* sds: added :ref:`certificate rotation ` support for certificates in static resources. * server: the SIGUSR1 access log reopen warning now is logged at info level. * stat sinks: stat sink extensions use the "envoy.stat_sinks" name space. A mapping of extension - names is available in the :ref:`deprecated ` documentation. + names is available in the :ref:`deprecated ` documentation. * thrift_proxy: added router filter stats to docs. -* tls: added configuration to disable stateless TLS session resumption :ref:`disable_stateless_session_resumption `. +* tls: added configuration to disable stateless TLS session resumption :ref:`disable_stateless_session_resumption `. * tracing: added gRPC service configuration to the OpenCensus Stackdriver and OpenCensus Agent tracers. * tracing: tracer extensions use the "envoy.tracers" name space. A mapping of extension names is - available in the :ref:`deprecated ` documentation. -* upstream: added ``upstream_rq_retry_limit_exceeded`` to :ref:`cluster `, and :ref:`virtual cluster ` stats. -* upstream: changed load distribution algorithm when all priorities enter :ref:`panic mode`. + available in the :ref:`deprecated ` documentation. +* upstream: added ``upstream_rq_retry_limit_exceeded`` to :ref:`cluster `, and :ref:`virtual cluster ` stats. +* upstream: changed load distribution algorithm when all priorities enter :ref:`panic mode `. * upstream: combined HTTP/1 and HTTP/2 connection pool code. This means that circuit breaker limits for both requests and connections apply to both pool types. Also, HTTP/2 now has the option to limit concurrent requests on a connection, and allow multiple draining connections. The old behavior is deprecated, but can be used during the deprecation - period by disabling runtime feature `envoy.reloadable_features.new_http1_connection_pool_behavior` or - `envoy.reloadable_features.new_http2_connection_pool_behavior` and then re-configure your clusters or + period by disabling runtime feature ``envoy.reloadable_features.new_http1_connection_pool_behavior`` or + ``envoy.reloadable_features.new_http2_connection_pool_behavior`` and then re-configure your clusters or restart Envoy. The behavior will not switch until the connection pools are recreated. The new - circuit breaker behavior is described :ref:`here `. + circuit breaker behavior is described :ref:`here `. * zlib: by default zlib is initialized to use its default strategy (Z_DEFAULT_STRATEGY) instead of the fixed one (Z_FIXED). The difference is that the use of dynamic Huffman codes is enabled now resulting in better compression ratio for normal data. @@ -101,7 +101,7 @@ Deprecated * The previous behavior for upstream connection pool circuit breaking described `here `_ has - been deprecated in favor of the new behavior described :ref:`here `. + been deprecated in favor of the new behavior described :ref:`here `. * Access Logger, Listener Filter, HTTP Filter, Network Filter, Stats Sink, and Tracer names have been deprecated in favor of the extension name from the envoy build system. Disable the runtime feature "envoy.deprecated_features.allow_deprecated_extension_names" to disallow the deprecated @@ -166,27 +166,27 @@ Deprecated * Tracers * The previous behavior of auto ignoring case in headers matching: - :ref:`allowed_headers `, - :ref:`allowed_upstream_headers `, - and :ref:`allowed_client_headers ` - of HTTP-based `ext_authz` has been deprecated in favor of explicitly setting the - :ref:`ignore_case ` field. -* The `header_fields`, `custom_header_fields`, and `additional_headers` fields for the route checker - tool have been deprecated in favor of `request_header_fields`, `response_header_fields`, - `additional_request_headers`, and `additional_response_headers`. -* The `content_length`, `content_type`, `disable_on_etag_header` and `remove_accept_encoding_header` - fields in :ref:`HTTP Gzip filter config ` have - been deprecated in favor of `compressor`. -* The statistics counter `header_gzip` in :ref:`HTTP Gzip filter ` - has been deprecated in favor of `header_compressor_used`. -* Support for the undocumented HTTP/1.1 `:no-chunks` pseudo-header has been removed. If an extension - was using this it can achieve the same behavior via the new `http1StreamEncoderOptions()` API. + :ref:`allowed_headers `, + :ref:`allowed_upstream_headers `, + and :ref:`allowed_client_headers ` + of HTTP-based ``ext_authz`` has been deprecated in favor of explicitly setting the + :ref:`ignore_case ` field. +* The ``header_fields``, ``custom_header_fields``, and ``additional_headers`` fields for the route checker + tool have been deprecated in favor of ``request_header_fields``, ``response_header_fields``, + ``additional_request_headers``, and ``additional_response_headers``. +* The ``content_length``, ``content_type``, ``disable_on_etag_header`` and ``remove_accept_encoding_header`` + fields in :ref:`HTTP Gzip filter config ` have + been deprecated in favor of ``compressor``. +* The statistics counter ``header_gzip`` in :ref:`HTTP Gzip filter ` + has been deprecated in favor of ``header_compressor_used``. +* Support for the undocumented HTTP/1.1 ``:no-chunks`` pseudo-header has been removed. If an extension + was using this it can achieve the same behavior via the new ``http1StreamEncoderOptions()`` API. * The grpc_stats filter behavior of by default creating a new stat for every message type seen is deprecated. The default will switch to only creating a fixed set of stats. The previous behavior can be enabled by enabling - :ref:`stats_for_all_methods `, + :ref:`stats_for_all_methods `, and the previous default can be enabled until the end of the deprecation period by enabling runtime feature - `envoy.deprecated_features.grpc_stats_filter_enable_stats_for_all_methods_by_default`. -* The :ref:`source_ip ` field in + ``envoy.deprecated_features.grpc_stats_filter_enable_stats_for_all_methods_by_default``. +* The :ref:`source_ip ` field in `RBAC `_ has been deprecated - in favor of :ref:`direct_remote_ip ` and - :ref:`remote_ip `. + in favor of :ref:`direct_remote_ip ` and + :ref:`remote_ip `. diff --git a/docs/root/version_history/v1.14.2.rst b/docs/root/version_history/v1.14.2.rst index c20f93650dcaa..c7d4731d865b4 100644 --- a/docs/root/version_history/v1.14.2.rst +++ b/docs/root/version_history/v1.14.2.rst @@ -5,10 +5,10 @@ Changes ------- * http: fixed CVE-2020-11080 by rejecting HTTP/2 SETTINGS frames with too many parameters. -* http: the :ref:`stream_idle_timeout ` +* http: the :ref:`stream_idle_timeout ` now also defends against an HTTP/2 peer that does not open stream window once an entire response has been buffered to be sent to a downstream client. -* listener: Add runtime support for `per-listener limits ` on +* listener: Add runtime support for :ref:`per-listener limits ` on active/accepted connections. -* overload management: Add runtime support for :ref:`global limits ` +* overload management: Add runtime support for :ref:`global limits ` on active/accepted connections. diff --git a/docs/root/version_history/v1.14.3.rst b/docs/root/version_history/v1.14.3.rst index 8a3a3d91da089..523a4fc9a607e 100644 --- a/docs/root/version_history/v1.14.3.rst +++ b/docs/root/version_history/v1.14.3.rst @@ -4,8 +4,8 @@ Changes ------- * buffer: fixed CVE-2020-12603 by avoiding fragmentation, and tracking of HTTP/2 data and control frames in the output buffer. -* http: fixed CVE-2020-12604 by changing :ref:`stream_idle_timeout ` +* http: fixed CVE-2020-12604 by changing :ref:`stream_idle_timeout ` to also defend against an HTTP/2 peer that does not open stream window once an entire response has been buffered to be sent to a downstream client. * http: fixed CVE-2020-12605 by including request URL in request header size computation, and rejecting partial headers that exceed configured limits. -* listener: fixed CVE-2020-8663 by adding runtime support for :ref:`per-listener limits ` on active/accepted connections. -* overload management: fixed CVE-2020-8663 by adding runtime support for :ref:`global limits ` on active/accepted connections. +* listener: fixed CVE-2020-8663 by adding runtime support for :ref:`per-listener limits ` on active/accepted connections. +* overload management: fixed CVE-2020-8663 by adding runtime support for :ref:`global limits ` on active/accepted connections. diff --git a/docs/root/version_history/v1.14.4.rst b/docs/root/version_history/v1.14.4.rst index bc105781c135e..4b5a8707c577d 100644 --- a/docs/root/version_history/v1.14.4.rst +++ b/docs/root/version_history/v1.14.4.rst @@ -1,3 +1,3 @@ 1.14.4 (July 7, 2020) ===================== -* tls: fixed a bug where wilcard matching for "\*.foo.com" also matched domains of the form "a.b.foo.com". This behavior can be temporarily reverted by setting runtime feature `envoy.reloadable_features.fix_wildcard_matching` to false. +* tls: fixed a bug where wilcard matching for "\*.foo.com" also matched domains of the form "a.b.foo.com". This behavior can be temporarily reverted by setting runtime feature ``envoy.reloadable_features.fix_wildcard_matching`` to false. diff --git a/docs/root/version_history/v1.14.7.rst b/docs/root/version_history/v1.14.7.rst index 3d0584d8ab119..041b5da018f82 100644 --- a/docs/root/version_history/v1.14.7.rst +++ b/docs/root/version_history/v1.14.7.rst @@ -2,7 +2,7 @@ ======================= Changes ------- -* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. * http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. * http: fixed bugs in datadog and squash filter's handling of responses with no bodies. * http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. diff --git a/docs/root/version_history/v1.15.0.rst b/docs/root/version_history/v1.15.0.rst index d97953aac32c2..d565e35bb5cce 100644 --- a/docs/root/version_history/v1.15.0.rst +++ b/docs/root/version_history/v1.15.0.rst @@ -7,31 +7,31 @@ Incompatible Behavior Changes *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* * build: official released binary is now built on Ubuntu 18.04, requires glibc >= 2.27. -* client_ssl_auth: the `auth_ip_white_list` stat has been renamed to - :ref:`auth_ip_allowlist `. +* client_ssl_auth: the ``auth_ip_white_list`` stat has been renamed to + :ref:`auth_ip_allowlist `. * header to metadata: on_header_missing rules with empty values are now rejected (they were skipped before). -* router: path_redirect now keeps query string by default. This behavior may be reverted by setting runtime feature `envoy.reloadable_features.preserve_query_string_in_path_redirects` to false. -* tls: fixed a bug where wilcard matching for "\*.foo.com" also matched domains of the form "a.b.foo.com". This behavior can be temporarily reverted by setting runtime feature `envoy.reloadable_features.fix_wildcard_matching` to false. +* router: path_redirect now keeps query string by default. This behavior may be reverted by setting runtime feature ``envoy.reloadable_features.preserve_query_string_in_path_redirects`` to false. +* tls: fixed a bug where wilcard matching for "\*.foo.com" also matched domains of the form "a.b.foo.com". This behavior can be temporarily reverted by setting runtime feature ``envoy.reloadable_features.fix_wildcard_matching`` to false. Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* -* access loggers: applied existing buffer limits to access logs, as well as :ref:`stats ` for logged / dropped logs. This can be reverted temporarily by setting runtime feature `envoy.reloadable_features.disallow_unbounded_access_logs` to false. -* build: runs as non-root inside Docker containers. Existing behaviour can be restored by setting the environment variable `ENVOY_UID` to `0`. `ENVOY_UID` and `ENVOY_GID` can be used to set the envoy user's `uid` and `gid` respectively. -* health check: in the health check filter the :ref:`percentage of healthy servers in upstream clusters ` is now interpreted as an integer. +* access loggers: applied existing buffer limits to access logs, as well as :ref:`stats ` for logged / dropped logs. This can be reverted temporarily by setting runtime feature ``envoy.reloadable_features.disallow_unbounded_access_logs`` to false. +* build: runs as non-root inside Docker containers. Existing behaviour can be restored by setting the environment variable ``ENVOY_UID`` to ``0``. ``ENVOY_UID`` and ``ENVOY_GID`` can be used to set the envoy user's ``uid`` and ``gid`` respectively. +* health check: in the health check filter the :ref:`percentage of healthy servers in upstream clusters ` is now interpreted as an integer. * hot restart: added the option :option:`--use-dynamic-base-id` to select an unused base ID at startup and the option :option:`--base-id-path` to write the base id to a file (for reuse with later hot restarts). -* http: changed early error path for HTTP/1.1 so that responses consistently flow through the http connection manager, and the http filter chains. This behavior may be temporarily reverted by setting runtime feature `envoy.reloadable_features.early_errors_via_hcm` to false. -* http: fixed several bugs with applying correct connection close behavior across the http connection manager, health checker, and connection pool. This behavior may be temporarily reverted by setting runtime feature `envoy.reloadable_features.fix_connection_close` to false. +* http: changed early error path for HTTP/1.1 so that responses consistently flow through the http connection manager, and the http filter chains. This behavior may be temporarily reverted by setting runtime feature ``envoy.reloadable_features.early_errors_via_hcm`` to false. +* http: fixed several bugs with applying correct connection close behavior across the http connection manager, health checker, and connection pool. This behavior may be temporarily reverted by setting runtime feature ``envoy.reloadable_features.fix_connection_close`` to false. * http: fixed a bug where the upgrade header was not cleared on responses to non-upgrade requests. - Can be reverted temporarily by setting runtime feature `envoy.reloadable_features.fix_upgrade_response` to false. -* http: stopped overwriting `date` response headers. Responses without a `date` header will still have the header properly set. This behavior can be temporarily reverted by setting `envoy.reloadable_features.preserve_upstream_date` to false. -* http: stopped adding a synthetic path to CONNECT requests, meaning unconfigured CONNECT requests will now return 404 instead of 403. This behavior can be temporarily reverted by setting `envoy.reloadable_features.stop_faking_paths` to false. -* http: stopped allowing upstream 1xx or 204 responses with Transfer-Encoding or non-zero Content-Length headers. Content-Length of 0 is allowed, but stripped. This behavior can be temporarily reverted by setting `envoy.reloadable_features.strict_1xx_and_204_response_headers` to false. -* http: upstream connections will now automatically set ALPN when this value is not explicitly set elsewhere (e.g. on the upstream TLS config). This behavior may be temporarily reverted by setting runtime feature `envoy.reloadable_features.http_default_alpn` to false. + Can be reverted temporarily by setting runtime feature ``envoy.reloadable_features.fix_upgrade_response`` to false. +* http: stopped overwriting ``date`` response headers. Responses without a ``date`` header will still have the header properly set. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.preserve_upstream_date`` to false. +* http: stopped adding a synthetic path to CONNECT requests, meaning unconfigured CONNECT requests will now return 404 instead of 403. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.stop_faking_paths`` to false. +* http: stopped allowing upstream 1xx or 204 responses with Transfer-Encoding or non-zero Content-Length headers. Content-Length of 0 is allowed, but stripped. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.strict_1xx_and_204_response_headers`` to false. +* http: upstream connections will now automatically set ALPN when this value is not explicitly set elsewhere (e.g. on the upstream TLS config). This behavior may be temporarily reverted by setting runtime feature ``envoy.reloadable_features.http_default_alpn`` to false. * listener: fixed a bug where when a static listener fails to be added to a worker, the listener was not removed from the active listener list. -* router: extended to allow retries of streaming or incomplete requests. This removes stat `rq_retry_skipped_request_not_complete`. -* router: extended to allow retries by default when upstream responds with :ref:`x-envoy-overloaded `. +* router: extended to allow retries of streaming or incomplete requests. This removes stat ``rq_retry_skipped_request_not_complete``. +* router: extended to allow retries by default when upstream responds with :ref:`x-envoy-overloaded `. Bug Fixes --------- @@ -43,124 +43,124 @@ Bug Fixes * buffer: fixed CVE-2020-12603 by avoiding fragmentation, and tracking of HTTP/2 data and control frames in the output buffer. * grpc-json: fixed a bug when in trailers only gRPC response (e.g. error) HTTP status code is not being re-written. * http: fixed a bug in the grpc_http1_reverse_bridge filter where header-only requests were forwarded with a non-zero content length. -* http: fixed a bug where in some cases slash was moved from path to query string when :ref:`merging of adjacent slashes` is enabled. -* http: fixed CVE-2020-12604 by changing :ref:`stream_idle_timeout ` +* http: fixed a bug where in some cases slash was moved from path to query string when :ref:`merging of adjacent slashes ` is enabled. +* http: fixed CVE-2020-12604 by changing :ref:`stream_idle_timeout ` to also defend against an HTTP/2 peer that does not open stream window once an entire response has been buffered to be sent to a downstream client. * http: fixed CVE-2020-12605 by including request URL in request header size computation, and rejecting partial headers that exceed configured limits. -* http: fixed several bugs with applying correct connection close behavior across the http connection manager, health checker, and connection pool. This behavior may be temporarily reverted by setting runtime feature `envoy.reloadable_features.fix_connection_close` to false. -* listener: fixed CVE-2020-8663 by adding runtime support for :ref:`per-listener limits ` on active/accepted connections. -* overload management: fixed CVE-2020-8663 by adding runtime support for :ref:`global limits ` on active/accepted connections. +* http: fixed several bugs with applying correct connection close behavior across the http connection manager, health checker, and connection pool. This behavior may be temporarily reverted by setting runtime feature ``envoy.reloadable_features.fix_connection_close`` to false. +* listener: fixed CVE-2020-8663 by adding runtime support for :ref:`per-listener limits ` on active/accepted connections. +* overload management: fixed CVE-2020-8663 by adding runtime support for :ref:`global limits ` on active/accepted connections. * prometheus stats: fixed the sort order of output lines to comply with the standard. -* udp: the :ref:`reuse_port ` listener option must now be +* udp: the :ref:`reuse_port ` listener option must now be specified for UDP listeners if concurrency is > 1. This previously crashed so is considered a bug fix. * upstream: fixed a bug where Envoy would panic when receiving a GRPC SERVICE_UNKNOWN status on the health check. Removed Config or Runtime ------------------------- -*Normally occurs at the end of the* :ref:`deprecation period ` +*Normally occurs at the end of the* :ref:`deprecation period ` -* http: removed legacy connection pool code and their runtime features: `envoy.reloadable_features.new_http1_connection_pool_behavior` and - `envoy.reloadable_features.new_http2_connection_pool_behavior`. +* http: removed legacy connection pool code and their runtime features: ``envoy.reloadable_features.new_http1_connection_pool_behavior`` and + ``envoy.reloadable_features.new_http2_connection_pool_behavior``. New Features ------------ -* access loggers: added file access logger config :ref:`log_format `. +* access loggers: added file access logger config :ref:`log_format `. * access loggers: added GRPC_STATUS operator on logging format. -* access loggers: added gRPC access logger config added :ref:`API version ` to explicitly set the version of gRPC service endpoint and message to be used. -* access loggers: extended specifier for FilterStateFormatter to output :ref:`unstructured log string `. -* admin: added support for dumping EDS config at :ref:`/config_dump?include_eds `. -* aggregate cluster: made route :ref:`retry_priority ` predicates work with :ref:`this cluster type `. +* access loggers: added gRPC access logger config added :ref:`API version ` to explicitly set the version of gRPC service endpoint and message to be used. +* access loggers: extended specifier for FilterStateFormatter to output :ref:`unstructured log string `. +* admin: added support for dumping EDS config at :ref:`/config_dump?include_eds `. +* aggregate cluster: made route :ref:`retry_priority ` predicates work with :ref:`this cluster type `. * build: official released binary is now built on Ubuntu 18.04, requires glibc >= 2.27. * build: official released binary is now built with Clang 10.0.0. -* cluster: added an extension point for configurable :ref:`upstreams `. -* compressor: exposed generic :ref:`compressor ` filter to users. -* config: added :ref:`identifier ` stat that reflects control plane identifier. -* config: added :ref:`version_text ` stat that reflects xDS version. -* decompressor: exposed generic :ref:`decompressor ` filter to users. -* dynamic forward proxy: added :ref:`SNI based dynamic forward proxy ` support. -* dynamic forward proxy: added configurable :ref:`circuit breakers ` for resolver on DNS cache. - This behavior can be temporarily disabled by the runtime feature `envoy.reloadable_features.enable_dns_cache_circuit_breakers`. - If this runtime feature is disabled, the upstream circuit breakers for the cluster will be used even if the :ref:`DNS Cache circuit breakers ` are configured. -* dynamic forward proxy: added :ref:`allow_insecure_cluster_options` to allow disabling of auto_san_validation and auto_sni. -* ext_authz filter: added :ref:`v2 deny_at_disable `, :ref:`v3 deny_at_disable `. This allows force denying protected paths while filter gets disabled, by setting this key to true. -* ext_authz filter: added API version field for both :ref:`HTTP ` - and :ref:`Network ` filters to explicitly set the version of gRPC service endpoint and message to be used. -* ext_authz filter: added :ref:`v3 allowed_upstream_headers_to_append ` to allow appending multiple header entries (returned by the authorization server) with the same key to the original request headers. +* cluster: added an extension point for configurable :ref:`upstreams `. +* compressor: exposed generic :ref:`compressor ` filter to users. +* config: added :ref:`identifier ` stat that reflects control plane identifier. +* config: added :ref:`version_text ` stat that reflects xDS version. +* decompressor: exposed generic :ref:`decompressor ` filter to users. +* dynamic forward proxy: added :ref:`SNI based dynamic forward proxy ` support. +* dynamic forward proxy: added configurable :ref:`circuit breakers ` for resolver on DNS cache. + This behavior can be temporarily disabled by the runtime feature ``envoy.reloadable_features.enable_dns_cache_circuit_breakers``. + If this runtime feature is disabled, the upstream circuit breakers for the cluster will be used even if the :ref:`DNS Cache circuit breakers ` are configured. +* dynamic forward proxy: added :ref:`allow_insecure_cluster_options ` to allow disabling of auto_san_validation and auto_sni. +* ext_authz filter: added :ref:`v2 deny_at_disable `, :ref:`v3 deny_at_disable `. This allows force denying protected paths while filter gets disabled, by setting this key to true. +* ext_authz filter: added API version field for both :ref:`HTTP ` + and :ref:`Network ` filters to explicitly set the version of gRPC service endpoint and message to be used. +* ext_authz filter: added :ref:`v3 allowed_upstream_headers_to_append ` to allow appending multiple header entries (returned by the authorization server) with the same key to the original request headers. * fault: added support for controlling the percentage of requests that abort, delay and response rate limits faults - are applied to using :ref:`HTTP headers ` to the HTTP fault filter. + are applied to using :ref:`HTTP headers ` to the HTTP fault filter. * fault: added support for specifying grpc_status code in abort faults using - :ref:`HTTP header ` or abort fault configuration in HTTP fault filter. -* filter: added `upstream_rq_time` stats to the GPRC stats filter. - Disabled by default and can be enabled via :ref:`enable_upstream_stats `. -* grpc: added support for Google gRPC :ref:`custom channel arguments `. + :ref:`HTTP header ` or abort fault configuration in HTTP fault filter. +* filter: added ``upstream_rq_time`` stats to the GPRC stats filter. + Disabled by default and can be enabled via :ref:`enable_upstream_stats `. +* grpc: added support for Google gRPC :ref:`custom channel arguments `. * grpc-json: added support for streaming response using `google.api.HttpBody `_. -* grpc-json: send a `x-envoy-original-method` header to grpc services. +* grpc-json: send a ``x-envoy-original-method`` header to grpc services. * gzip filter: added option to set zlib's next output buffer size. * hds: updated to allow to explicitly set the API version of gRPC service endpoint and message to be used. * header to metadata: added support for regex substitutions on header values. -* health checks: allowed configuring health check transport sockets by specifying :ref:`transport socket match criteria `. -* http: added :ref:`local_reply config ` to http_connection_manager to customize :ref:`local reply `. -* http: added :ref:`stripping port from host header ` support. -* http: added support for proxying CONNECT requests, terminating CONNECT requests, and converting raw TCP streams into HTTP/2 CONNECT requests. See :ref:`upgrade documentation` for details. +* health checks: allowed configuring health check transport sockets by specifying :ref:`transport socket match criteria `. +* http: added :ref:`local_reply config ` to http_connection_manager to customize :ref:`local reply `. +* http: added :ref:`stripping port from host header ` support. +* http: added support for proxying CONNECT requests, terminating CONNECT requests, and converting raw TCP streams into HTTP/2 CONNECT requests. See :ref:`upgrade documentation ` for details. * listener: added in place filter chain update flow for tcp listener update which doesn't close connections if the corresponding network filter chain is equivalent during the listener update. - Can be disabled by setting runtime feature `envoy.reloadable_features.listener_in_place_filterchain_update` to false. - Also added additional draining filter chain stat for :ref:`listener manager ` to track the number of draining filter chains and the number of in place update attempts. -* logger: added `--log-format-prefix-with-location` command line option to prefix '%v' with file path and line number. -* lrs: added new *envoy_api_field_service.load_stats.v2.LoadStatsResponse.send_all_clusters* field + Can be disabled by setting runtime feature ``envoy.reloadable_features.listener_in_place_filterchain_update`` to false. + Also added additional draining filter chain stat for :ref:`listener manager ` to track the number of draining filter chains and the number of in place update attempts. +* logger: added ``--log-format-prefix-with-location`` command line option to prefix '%v' with file path and line number. +* lrs: added new ``envoy_api_field_service.load_stats.v2.LoadStatsResponse.send_all_clusters`` field in LRS response, which allows management servers to avoid explicitly listing all clusters it is - interested in; behavior is allowed based on new "envoy.lrs.supports_send_all_clusters" capability - in :ref:`client_features` field. + interested in; behavior is allowed based on new ``envoy.lrs.supports_send_all_clusters`` capability + in :ref:`client_features ` field. * lrs: updated to allow to explicitly set the API version of gRPC service endpoint and message to be used. -* lua: added :ref:`per route config ` for Lua filter. +* lua: added :ref:`per route config ` for Lua filter. * lua: added tracing to the ``httpCall()`` API. -* metrics service: added :ref:`API version ` to explicitly set the version of gRPC service endpoint and message to be used. -* network filters: added a :ref:`postgres proxy filter `. -* network filters: added a :ref:`rocketmq proxy filter `. +* metrics service: added :ref:`API version ` to explicitly set the version of gRPC service endpoint and message to be used. +* network filters: added a :ref:`postgres proxy filter `. +* network filters: added a :ref:`rocketmq proxy filter `. * performance: enabled stats symbol table implementation by default. To disable it, add - `--use-fake-symbol-table 1` to the command-line arguments when starting Envoy. -* ratelimit: added support for use of dynamic metadata :ref:`dynamic_metadata ` as a ratelimit action. -* ratelimit: added :ref:`API version ` to explicitly set the version of gRPC service endpoint and message to be used. -* ratelimit: support specifying dynamic overrides in rate limit descriptors using :ref:`limit override ` config. -* redis: added acl support :ref:`downstream_auth_username ` for downstream client ACL authentication, and :ref:`auth_username ` to configure authentication usernames for upstream Redis 6+ server clusters with ACL enabled. -* regex: added support for enforcing max program size via runtime and stats to monitor program size for :ref:`Google RE2 `. -* request_id: added to :ref:`always_set_request_id_in_response setting ` - to set :ref:`x-request-id ` header in response even if + ``--use-fake-symbol-table 1`` to the command-line arguments when starting Envoy. +* ratelimit: added support for use of dynamic metadata :ref:`dynamic_metadata ` as a ratelimit action. +* ratelimit: added :ref:`API version ` to explicitly set the version of gRPC service endpoint and message to be used. +* ratelimit: support specifying dynamic overrides in rate limit descriptors using :ref:`limit override ` config. +* redis: added acl support :ref:`downstream_auth_username ` for downstream client ACL authentication, and :ref:`auth_username ` to configure authentication usernames for upstream Redis 6+ server clusters with ACL enabled. +* regex: added support for enforcing max program size via runtime and stats to monitor program size for :ref:`Google RE2 `. +* request_id: added to :ref:`always_set_request_id_in_response setting ` + to set :ref:`x-request-id ` header in response even if tracing is not forced. * router: added more fine grained internal redirect configs to the :ref:`internal_redirect_policy - ` field. + ` field. * router: added regex substitution support for header based hashing. * router: added support for RESPONSE_FLAGS and RESPONSE_CODE_DETAILS :ref:`header formatters - `. -* router: allow Rate Limiting Service to be called in case of missing request header for a descriptor if the :ref:`skip_if_absent ` field is set to true. -* runtime: added new gauge :ref:`deprecated_feature_seen_since_process_start ` that gets reset across hot restarts. + `. +* router: allow Rate Limiting Service to be called in case of missing request header for a descriptor if the :ref:`skip_if_absent ` field is set to true. +* runtime: added new gauge :ref:`deprecated_feature_seen_since_process_start ` that gets reset across hot restarts. * server: added the option :option:`--drain-strategy` to enable different drain strategies for DrainManager::drainClose(). -* server: added :ref:`server.envoy_bug_failures ` statistic to count ENVOY_BUG failures. -* stats: added the option to :ref:`report counters as deltas ` to the metrics service stats sink. +* server: added :ref:`server.envoy_bug_failures ` statistic to count ENVOY_BUG failures. +* stats: added the option to :ref:`report counters as deltas ` to the metrics service stats sink. * tracing: made tracing configuration fully dynamic and every HTTP connection manager - can now have a separate :ref:`tracing provider `. -* udp: upgraded :ref:`udp_proxy ` filter to v3 and promoted it out of alpha. + can now have a separate :ref:`tracing provider `. +* udp: upgraded :ref:`udp_proxy ` filter to v3 and promoted it out of alpha. Deprecated ---------- -* Tracing provider configuration as part of :ref:`bootstrap config ` +* Tracing provider configuration as part of :ref:`bootstrap config ` has been deprecated in favor of configuration as part of :ref:`HTTP connection manager - `. -* The :ref:`HTTP Gzip filter ` has been deprecated in favor of - :ref:`Compressor `. -* The * :ref:`GoogleRE2.max_program_size` + `. +* The :ref:`HTTP Gzip filter ` has been deprecated in favor of + :ref:`Compressor `. +* The * :ref:`GoogleRE2.max_program_size ` field is now deprecated. Management servers are expected to validate regexp program sizes instead of expecting the client to do it. Alternatively, the max program size can be enforced by Envoy via runtime. -* The :ref:`internal_redirect_action ` - field and :ref:`max_internal_redirects ` field +* The :ref:`internal_redirect_action ` + field and :ref:`max_internal_redirects ` field are now deprecated. This changes the implemented default cross scheme redirect behavior. All cross scheme redirects are disallowed by default. To restore the previous behavior, set allow_cross_scheme_redirect=true and use - :ref:`safe_cross_scheme`, - in :ref:`predicates `. -* File access logger fields :ref:`format `, :ref:`json_format ` and :ref:`typed_json_format ` are deprecated in favor of :ref:`log_format `. -* A warning is now logged when v2 xDS api is used. This behavior can be temporarily disabled by setting `envoy.reloadable_features.enable_deprecated_v2_api_warning` to `false`. -* Using cluster circuit breakers for DNS Cache is now deprecated in favor of :ref:`DNS cache circuit breakers `. This behavior can be temporarily disabled by setting `envoy.reloadable_features.enable_dns_cache_circuit_breakers` to `false`. + :ref:`safe_cross_scheme `, + in :ref:`predicates `. +* File access logger fields :ref:`format `, :ref:`json_format ` and :ref:`typed_json_format ` are deprecated in favor of :ref:`log_format `. +* A warning is now logged when v2 xDS api is used. This behavior can be temporarily disabled by setting ``envoy.reloadable_features.enable_deprecated_v2_api_warning`` to ``false``. +* Using cluster circuit breakers for DNS Cache is now deprecated in favor of :ref:`DNS cache circuit breakers `. This behavior can be temporarily disabled by setting ``envoy.reloadable_features.enable_dns_cache_circuit_breakers`` to ``false``. diff --git a/docs/root/version_history/v1.15.4.rst b/docs/root/version_history/v1.15.4.rst index 977e24f54c7be..f40b70a69cfd0 100644 --- a/docs/root/version_history/v1.15.4.rst +++ b/docs/root/version_history/v1.15.4.rst @@ -4,20 +4,19 @@ Changes ------- -* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. * http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. * http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. * http: fixed bugs in datadog and squash filter's handling of responses with no bodies. -* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. +* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting ``envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure`` to false. * tls: fix detection of the upstream connection close event. Removed Config or Runtime ------------------------- -*Normally occurs at the end of the* :ref:`deprecation period ` +*Normally occurs at the end of the* :ref:`deprecation period ` New Features ------------ Deprecated ---------- - diff --git a/docs/root/version_history/v1.16.0.rst b/docs/root/version_history/v1.16.0.rst index d9c2d97d7f25a..32cb2a70dd236 100644 --- a/docs/root/version_history/v1.16.0.rst +++ b/docs/root/version_history/v1.16.0.rst @@ -5,13 +5,13 @@ Incompatible Behavior Changes ----------------------------- *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* -* build: added visibility rules for upstream. If these cause visibility related breakage, see notes in :repo:`BUILD `. -* build: tcmalloc changes require Clang 9. This requirement change can be avoided by building with `--define tcmalloc=gperftools` to use the older tcmalloc code. +* build: added visibility rules for upstream. If these cause visibility related breakage, see notes in :repo:`BUILD `. +* build: tcmalloc changes require Clang 9. This requirement change can be avoided by building with ``--define tcmalloc=gperftools`` to use the older tcmalloc code. * config: additional warnings have been added for the use of v2 APIs. These appear as log messages - and are also captured in the :ref:`deprecated_feature_use ` counter after server + and are also captured in the :ref:`deprecated_feature_use ` counter after server initialization. -* dns: `envoy.restart_features.use_apple_api_for_dns_lookups` is on by default. This flag only affects Apple platforms (macOS, iOS). It is incompatible to have the runtime flag set to true at the same time as specifying the ``use_tcp_for_dns_lookups`` option or custom dns resolvers. Doing so will cause failure. -* watchdog: added two guarddogs, breaking the aggregated stats for the single guarddog system. The aggregated stats for the guarddogs will have the following prefixes: `main_thread` and `workers`. Concretely, anything monitoring `server.watchdog_miss` and `server.watchdog_mega_miss` will need to be updated. +* dns: ``envoy.restart_features.use_apple_api_for_dns_lookups`` is on by default. This flag only affects Apple platforms (macOS, iOS). It is incompatible to have the runtime flag set to true at the same time as specifying the ````use_tcp_for_dns_lookups```` option or custom dns resolvers. Doing so will cause failure. +* watchdog: added two guarddogs, breaking the aggregated stats for the single guarddog system. The aggregated stats for the guarddogs will have the following prefixes: ``main_thread`` and ``workers``. Concretely, anything monitoring ``server.watchdog_miss`` and ``server.watchdog_mega_miss`` will need to be updated. Minor Behavior Changes ---------------------- @@ -19,40 +19,40 @@ Minor Behavior Changes * adaptive concurrency: added a response body / grpc-message header for rejected requests. * async_client: minor change to handling header only responses more similar to header-with-empty-body responses. -* build: an :ref:`Ubuntu based debug image ` is built and published in DockerHub. -* build: the debug information will be generated separately to reduce target size and reduce compilation time when build in compilation mode `dbg` and `opt`. Users will need to build dwp file to debug with gdb. -* compressor: always insert `Vary` headers for compressible resources even if it's decided not to compress a response due to incompatible `Accept-Encoding` value. The `Vary` header needs to be inserted to let a caching proxy in front of Envoy know that the requested resource still can be served with compression applied. +* build: an :ref:`Ubuntu based debug image ` is built and published in DockerHub. +* build: the debug information will be generated separately to reduce target size and reduce compilation time when build in compilation mode ``dbg`` and ``opt``. Users will need to build dwp file to debug with gdb. +* compressor: always insert ``Vary`` headers for compressible resources even if it's decided not to compress a response due to incompatible ``Accept-Encoding`` value. The ``Vary`` header needs to be inserted to let a caching proxy in front of Envoy know that the requested resource still can be served with compression applied. * decompressor: headers-only requests were incorrectly not advertising accept-encoding when configured to do so. This is now fixed. * ext_authz filter: request timeout will now count from the time the check request is created, instead of when it becomes active. This makes sure that the timeout is enforced even if the ext_authz cluster's circuit breaker is engaged. - This behavior can be reverted by setting runtime feature `envoy.reloadable_features.ext_authz_measure_timeout_on_check_created` to false. When enabled, a new `ext_authz.timeout` stat is counted when timeout occurs. See :ref:`stats `. + This behavior can be reverted by setting runtime feature ``envoy.reloadable_features.ext_authz_measure_timeout_on_check_created`` to false. When enabled, a new ``ext_authz.timeout`` stat is counted when timeout occurs. See :ref:`stats `. * grpc reverse bridge: upstream headers will no longer be propagated when the response is missing or contains an unexpected content-type. -* http: added :ref:`contains `, a new string matcher type which matches if the value of the string has the substring mentioned in contains matcher. -* http: added :ref:`contains `, a new header matcher type which matches if the value of the header has the substring mentioned in contains matcher. -* http: added :ref:`headers_to_add ` to :ref:`local reply mapper ` to allow its users to add/append/override response HTTP headers to local replies. -* http: added HCM level configuration of :ref:`error handling on invalid messaging ` which substantially changes Envoy's behavior when encountering invalid HTTP/1.1 defaulting to closing the connection instead of allowing reuse. This can temporarily be reverted by setting `envoy.reloadable_features.hcm_stream_error_on_invalid_message` to false, or permanently reverted by setting the HTTP/1 configuration :ref:`override_stream_error_on_invalid_http_message ` to true to restore prior HTTP/1.1 behavior (i.e. connection isn't terminated) and to retain prior HTTP/2 behavior (i.e. connection is terminated). -* http: added HCM level configuration of :ref:`error handling on invalid messaging ` which substantially changes Envoy's behavior when encountering invalid HTTP/1.1 defaulting to closing the connection instead of allowing reuse. This can temporarily be reverted by setting `envoy.reloadable_features.hcm_stream_error_on_invalid_message` to false, or permanently reverted by setting the :ref:`HCM option ` to true to restore prior HTTP/1.1 beavior and setting the *new* HTTP/2 configuration :ref:`override_stream_error_on_invalid_http_message ` to false to retain prior HTTP/2 behavior. -* http: applying route level header modifications to local replies sent on that route. This behavior may be temporarily reverted by setting `envoy.reloadable_features.always_apply_route_header_rules` to false. -* http: changed Envoy to send GOAWAY to HTTP2 downstreams when the :ref:`disable_keepalive ` overload action is active. This behavior may be temporarily reverted by setting `envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2` to false. -* http: changed Envoy to send error headers and body when possible. This behavior may be temporarily reverted by setting `envoy.reloadable_features.allow_response_for_timeout` to false. -* http: changed empty trailers encoding behavior by sending empty data with ``end_stream`` true (instead of sending empty trailers) for HTTP/2. This behavior can be reverted temporarily by setting runtime feature `envoy.reloadable_features.http2_skip_encoding_empty_trailers` to false. -* http: changed how local replies are processed for requests which transform from grpc to not-grpc, or not-grpc to grpc. Previously the initial generated reply depended on which filter sent the reply, but now the reply is consistently generated the way the downstream expects. This behavior can be temporarily reverted by setting `envoy.reloadable_features.unify_grpc_handling` to false. +* http: added :ref:`contains `, a new string matcher type which matches if the value of the string has the substring mentioned in contains matcher. +* http: added :ref:`contains `, a new header matcher type which matches if the value of the header has the substring mentioned in contains matcher. +* http: added :ref:`headers_to_add ` to :ref:`local reply mapper ` to allow its users to add/append/override response HTTP headers to local replies. +* http: added HCM level configuration of :ref:`error handling on invalid messaging ` which substantially changes Envoy's behavior when encountering invalid HTTP/1.1 defaulting to closing the connection instead of allowing reuse. This can temporarily be reverted by setting ``envoy.reloadable_features.hcm_stream_error_on_invalid_message`` to false, or permanently reverted by setting the HTTP/1 configuration :ref:`override_stream_error_on_invalid_http_message ` to true to restore prior HTTP/1.1 behavior (i.e. connection isn't terminated) and to retain prior HTTP/2 behavior (i.e. connection is terminated). +* http: added HCM level configuration of :ref:`error handling on invalid messaging ` which substantially changes Envoy's behavior when encountering invalid HTTP/1.1 defaulting to closing the connection instead of allowing reuse. This can temporarily be reverted by setting ``envoy.reloadable_features.hcm_stream_error_on_invalid_message`` to false, or permanently reverted by setting the :ref:`HCM option ` to true to restore prior HTTP/1.1 beavior and setting the *new* HTTP/2 configuration :ref:`override_stream_error_on_invalid_http_message ` to false to retain prior HTTP/2 behavior. +* http: applying route level header modifications to local replies sent on that route. This behavior may be temporarily reverted by setting ``envoy.reloadable_features.always_apply_route_header_rules`` to false. +* http: changed Envoy to send GOAWAY to HTTP2 downstreams when the :ref:`disable_keepalive ` overload action is active. This behavior may be temporarily reverted by setting ``envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2`` to false. +* http: changed Envoy to send error headers and body when possible. This behavior may be temporarily reverted by setting ``envoy.reloadable_features.allow_response_for_timeout`` to false. +* http: changed empty trailers encoding behavior by sending empty data with ``end_stream`` true (instead of sending empty trailers) for HTTP/2. This behavior can be reverted temporarily by setting runtime feature ``envoy.reloadable_features.http2_skip_encoding_empty_trailers`` to false. +* http: changed how local replies are processed for requests which transform from grpc to not-grpc, or not-grpc to grpc. Previously the initial generated reply depended on which filter sent the reply, but now the reply is consistently generated the way the downstream expects. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.unify_grpc_handling`` to false. * http: clarified and enforced 1xx handling. Multiple 100-continue headers are coalesced when proxying. 1xx headers other than {100, 101} are dropped. * http: fixed a bug in access logs where early stream termination could be incorrectly tagged as a downstream disconnect, and disconnects after partial response were not flagged. -* http: fixed the 100-continue response path to properly handle upstream failure by sending 5xx responses. This behavior can be temporarily reverted by setting `envoy.reloadable_features.allow_500_after_100` to false. +* http: fixed the 100-continue response path to properly handle upstream failure by sending 5xx responses. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.allow_500_after_100`` to false. * http: the per-stream FilterState maintained by the HTTP connection manager will now provide read/write access to the downstream connection FilterState. As such, code that relies on interacting with this might see a change in behavior. * logging: added fine-grain logging for file level log control with logger management at administration interface. It can be enabled by option :option:`--enable-fine-grain-logging`. -* logging: changed default log format to `"[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v"` and default value of `--log-format-prefix-with-location` to `0`. -* logging: nghttp2 log messages no longer appear at trace level unless `ENVOY_NGHTTP2_TRACE` is set +* logging: changed default log format to ``"[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v"`` and default value of ``--log-format-prefix-with-location`` to ``0``. +* logging: nghttp2 log messages no longer appear at trace level unless ``ENVOY_NGHTTP2_TRACE`` is set in the environment. -* lua: changed the response body returned by `httpCall()` API to raw data. Previously, the returned data was string. -* memory: switched to the `new tcmalloc `_ for linux_x86_64 builds. The `old tcmalloc `_ can still be enabled with the `--define tcmalloc=gperftools` option. +* lua: changed the response body returned by ``httpCall()`` API to raw data. Previously, the returned data was string. +* memory: switched to the `new tcmalloc `_ for linux_x86_64 builds. The `old tcmalloc `_ can still be enabled with the ``--define tcmalloc=gperftools`` option. * postgres: changed log format to tokenize fields of Postgres messages. -* router: added transport failure reason to response body when upstream reset happens. After this change, the response body will be of the form `upstream connect error or disconnect/reset before headers. reset reason:{}, transport failure reason:{}`.This behavior may be reverted by setting runtime feature `envoy.reloadable_features.http_transport_failure_reason_in_body` to false. -* router: now consumes all retry related headers to prevent them from being propagated to the upstream. This behavior may be reverted by setting runtime feature `envoy.reloadable_features.consume_all_retry_headers` to false. -* stats: the fake symbol table implemention has been removed from the binary, and the option `--use-fake-symbol-table` is now a no-op with a warning. +* router: added transport failure reason to response body when upstream reset happens. After this change, the response body will be of the form ``upstream connect error or disconnect/reset before headers. reset reason:{}, transport failure reason:{}``.This behavior may be reverted by setting runtime feature ``envoy.reloadable_features.http_transport_failure_reason_in_body`` to false. +* router: now consumes all retry related headers to prevent them from being propagated to the upstream. This behavior may be reverted by setting runtime feature ``envoy.reloadable_features.consume_all_retry_headers`` to false. +* stats: the fake symbol table implemention has been removed from the binary, and the option ``--use-fake-symbol-table`` is now a no-op with a warning. * thrift_proxy: special characters {'\0', '\r', '\n'} will be stripped from thrift headers. -* watchdog: replaced single watchdog with separate watchdog configuration for worker threads and for the main thread configured via :ref:`Watchdogs`. It works with :ref:`watchdog` by having the worker thread and main thread watchdogs have same config. +* watchdog: replaced single watchdog with separate watchdog configuration for worker threads and for the main thread configured via :ref:`Watchdogs `. It works with :ref:`watchdog ` by having the worker thread and main thread watchdogs have same config. Bug Fixes --------- @@ -60,7 +60,7 @@ Bug Fixes * csrf: fixed issues with regards to origin and host header parsing. * dynamic_forward_proxy: only perform DNS lookups for routes to Dynamic Forward Proxy clusters since other cluster types handle DNS lookup themselves. -* fault: fixed an issue with `active_faults` gauge not being decremented for when abort faults were injected. +* fault: fixed an issue with ``active_faults`` gauge not being decremented for when abort faults were injected. * fault: made the HeaderNameValues::prefix() method const. * grpc-web: fixed an issue with failing HTTP/2 requests on some browsers. Notably, WebKit-based browsers (https://bugs.webkit.org/show_bug.cgi?id=210108), Internet Explorer 11, and Edge (pre-Chromium). * http: fixed CVE-2020-25018 by rolling back the ``GURL`` dependency to previous state (reverted: ``2d69e30``, ``d828958``, and ``c9c4709`` commits) due to potential of crashing when Unicode URIs are present in requests. @@ -74,110 +74,110 @@ Bug Fixes Removed Config or Runtime ------------------------- -*Normally occurs at the end of the* :ref:`deprecation period ` +*Normally occurs at the end of the* :ref:`deprecation period ` -* http: removed legacy header sanitization and the runtime guard `envoy.reloadable_features.strict_header_validation`. -* http: removed legacy transfer-encoding enforcement and runtime guard `envoy.reloadable_features.reject_unsupported_transfer_encodings`. -* http: removed configurable strict host validation and runtime guard `envoy.reloadable_features.strict_authority_validation`. -* http: removed the connection header sanitization runtime guard `envoy.reloadable_features.connection_header_sanitization`. +* http: removed legacy header sanitization and the runtime guard ``envoy.reloadable_features.strict_header_validation``. +* http: removed legacy transfer-encoding enforcement and runtime guard ``envoy.reloadable_features.reject_unsupported_transfer_encodings``. +* http: removed configurable strict host validation and runtime guard ``envoy.reloadable_features.strict_authority_validation``. +* http: removed the connection header sanitization runtime guard ``envoy.reloadable_features.connection_header_sanitization``. New Features ------------ -* access log: added a :ref:`dynamic metadata filter` for access logs, which filters whether to log based on matching dynamic metadata. -* access log: added support for :ref:`%DOWNSTREAM_PEER_FINGERPRINT_1% ` as a response flag. -* access log: added support for :ref:`%CONNECTION_TERMINATION_DETAILS% ` as a log command operator about why the connection is terminated by Envoy. -* access log: added support for nested objects in :ref:`JSON logging mode `. -* access log: added :ref:`omit_empty_values` option to omit unset value from formatted log. -* access log: added support for :ref:`%CONNECTION_ID% ` for the downstream connection identifier. -* admin: added :ref:`circuit breakers settings ` information to GET /clusters?format=json :ref:`cluster status `. -* admin: added :ref:`node ` information to GET /server_info :ref:`response object `. -* admin: added the ability to dump init manager unready targets information :ref:`/init_dump ` and :ref:`/init_dump?mask={} `. -* admission control: added the :ref:`admission control ` filter for client-side request throttling. -* build: enable building envoy :ref:`arm64 images ` by buildx tool in x86 CI platform. -* cluster: added new :ref:`connection_pool_per_downstream_connection ` flag, which enable creation of a new connection pool for each downstream connection. +* access log: added a :ref:`dynamic metadata filter ` for access logs, which filters whether to log based on matching dynamic metadata. +* access log: added support for :ref:`%DOWNSTREAM_PEER_FINGERPRINT_1% ` as a response flag. +* access log: added support for :ref:`%CONNECTION_TERMINATION_DETAILS% ` as a log command operator about why the connection is terminated by Envoy. +* access log: added support for nested objects in :ref:`JSON logging mode `. +* access log: added :ref:`omit_empty_values ` option to omit unset value from formatted log. +* access log: added support for :ref:`%CONNECTION_ID% ` for the downstream connection identifier. +* admin: added :ref:`circuit breakers settings ` information to GET /clusters?format=json :ref:`cluster status `. +* admin: added :ref:`node ` information to GET /server_info :ref:`response object `. +* admin: added the ability to dump init manager unready targets information :ref:`/init_dump ` and :ref:`/init_dump?mask={} `. +* admission control: added the :ref:`admission control ` filter for client-side request throttling. +* build: enable building envoy :ref:`arm64 images ` by buildx tool in x86 CI platform. +* cluster: added new :ref:`connection_pool_per_downstream_connection ` flag, which enable creation of a new connection pool for each downstream connection. * decompressor filter: reports compressed and uncompressed bytes in trailers. -* dns: added support for doing DNS resolution using Apple's DnsService APIs in Apple platforms (macOS, iOS). This feature is ON by default, and is only configurable via the `envoy.restart_features.use_apple_api_for_dns_lookups` runtime key. Note that this value is latched during server startup and changing the runtime key is a no-op during the lifetime of the process. -* dns_filter: added support for answering :ref:`service record` queries. -* dynamic_forward_proxy: added :ref:`use_tcp_for_dns_lookups` option to use TCP for DNS lookups in order to match the DNS options for :ref:`Clusters`. -* ext_authz filter: added support for emitting dynamic metadata for both :ref:`HTTP ` and :ref:`network ` filters. - The emitted dynamic metadata is set by :ref:`dynamic metadata ` field in a returned :ref:`CheckResponse `. -* ext_authz filter: added :ref:`stat_prefix ` as an optional additional prefix for the statistics emitted from `ext_authz` HTTP filter. -* ext_authz filter: added support for enabling the filter based on :ref:`dynamic metadata `. -* ext_authz filter: added support for letting the authorization server instruct Envoy to remove headers from the original request by setting the new field :ref:`headers_to_remove ` before forwarding it to the upstream. -* ext_authz filter: added support for sending :ref:`raw bytes as request body ` of a gRPC check request by setting :ref:`pack_as_bytes ` to true. -* ext_authz_filter: added :ref:`disable_request_body_buffering ` to disable request data buffering per-route. -* grpc-json: support specifying `response_body` field in for `google.api.HttpBody` message. -* hds: added :ref:`cluster_endpoints_health ` to HDS responses, keeping endpoints in the same groupings as they were configured in the HDS specifier by cluster and locality instead of as a flat list. -* hds: added :ref:`transport_socket_matches ` to HDS cluster health check specifier, so the existing match filter :ref:`transport_socket_match_criteria ` in the repeated field :ref:`health_checks ` has context to match against. This unblocks support for health checks over HTTPS and HTTP/2. +* dns: added support for doing DNS resolution using Apple's DnsService APIs in Apple platforms (macOS, iOS). This feature is ON by default, and is only configurable via the ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime key. Note that this value is latched during server startup and changing the runtime key is a no-op during the lifetime of the process. +* dns_filter: added support for answering :ref:`service record ` queries. +* dynamic_forward_proxy: added :ref:`use_tcp_for_dns_lookups ` option to use TCP for DNS lookups in order to match the DNS options for :ref:`Clusters `. +* ext_authz filter: added support for emitting dynamic metadata for both :ref:`HTTP ` and :ref:`network ` filters. + The emitted dynamic metadata is set by :ref:`dynamic metadata ` field in a returned :ref:`CheckResponse `. +* ext_authz filter: added :ref:`stat_prefix ` as an optional additional prefix for the statistics emitted from `ext_authz` HTTP filter. +* ext_authz filter: added support for enabling the filter based on :ref:`dynamic metadata `. +* ext_authz filter: added support for letting the authorization server instruct Envoy to remove headers from the original request by setting the new field :ref:`headers_to_remove ` before forwarding it to the upstream. +* ext_authz filter: added support for sending :ref:`raw bytes as request body ` of a gRPC check request by setting :ref:`pack_as_bytes ` to true. +* ext_authz_filter: added :ref:`disable_request_body_buffering ` to disable request data buffering per-route. +* grpc-json: support specifying ``response_body`` field in for ``google.api.HttpBody`` message. +* hds: added :ref:`cluster_endpoints_health ` to HDS responses, keeping endpoints in the same groupings as they were configured in the HDS specifier by cluster and locality instead of as a flat list. +* hds: added :ref:`transport_socket_matches ` to HDS cluster health check specifier, so the existing match filter :ref:`transport_socket_match_criteria ` in the repeated field :ref:`health_checks ` has context to match against. This unblocks support for health checks over HTTPS and HTTP/2. * hot restart: added :option:`--socket-path` and :option:`--socket-mode` to configure UDS path in the filesystem and set permission to it. -* http: added HTTP/2 support for :ref:`connection keepalive ` via PING. -* http: added support for :ref:`%DOWNSTREAM_PEER_FINGERPRINT_1% ` as custom header. -* http: added :ref:`allow_chunked_length ` configuration option for HTTP/1 codec to allow processing requests/responses with both Content-Length and Transfer-Encoding: chunked headers. If such message is served and option is enabled - per RFC Content-Length is ignored and removed. -* http: added :ref:`CDN Loop filter ` and :ref:`documentation `. -* http: added :ref:`MaxStreamDuration proto ` for configuring per-route downstream duration timeouts. -* http: introduced new HTTP/1 and HTTP/2 codec implementations that will remove the use of exceptions for control flow due to high risk factors and instead use error statuses. The old behavior is used by default for HTTP/1.1 and HTTP/2 server connections. The new codecs can be enabled for testing by setting the runtime feature `envoy.reloadable_features.new_codec_behavior` to true. The new codecs will be in development for one month, and then enabled by default while the old codecs are deprecated. -* http: modified the HTTP header-map data-structure to use an underlying dictionary and a list (no change to the header-map API). To conform with previous versions, the use of a dictionary is currently disabled. It can be enabled by setting the `envoy.http.headermap.lazy_map_min_size` runtime feature to a non-negative number which defines the minimal number of headers in a request/response/trailers required for using a dictionary in addition to the list. Our current benchmarks suggest that the value 3 is a good threshold for most workloads. -* load balancer: added :ref:`RingHashLbConfig` to configure the table size of Maglev consistent hash. -* load balancer: added a :ref:`configuration` option to specify the active request bias used by the least request load balancer. -* load balancer: added an :ref:`option ` to optimize subset load balancing when there is only one host per subset. -* load balancer: added support for bounded load per host for consistent hash load balancers via :ref:`hash_balance_factor `. -* local_reply config: added :ref:`content_type` field to set content-type. -* lua: added Lua APIs to access :ref:`SSL connection info ` object. -* lua: added Lua API for :ref:`base64 escaping a string `. -* lua: added Lua API for :ref:`setting the current buffer content `. -* lua: added new :ref:`source_code ` field to support the dispatching of inline Lua code in per route configuration of Lua filter. -* overload management: add :ref:`scaling ` trigger for OverloadManager actions. -* postgres network filter: :ref:`metadata ` is produced based on SQL query. -* proxy protocol: added support for generating the header upstream using :ref:`Proxy Protocol Transport Socket `. -* ratelimit: added :ref:`enable_x_ratelimit_headers ` option to enable `X-RateLimit-*` headers as defined in `draft RFC `_. -* ratelimit: added :ref:`per route config ` for rate limit filter. -* ratelimit: added support for optional :ref:`descriptor_key ` to Generic Key action. +* http: added HTTP/2 support for :ref:`connection keepalive ` via PING. +* http: added support for :ref:`%DOWNSTREAM_PEER_FINGERPRINT_1% ` as custom header. +* http: added :ref:`allow_chunked_length ` configuration option for HTTP/1 codec to allow processing requests/responses with both Content-Length and Transfer-Encoding: chunked headers. If such message is served and option is enabled - per RFC Content-Length is ignored and removed. +* http: added :ref:`CDN Loop filter ` and :ref:`documentation `. +* http: added :ref:`MaxStreamDuration proto ` for configuring per-route downstream duration timeouts. +* http: introduced new HTTP/1 and HTTP/2 codec implementations that will remove the use of exceptions for control flow due to high risk factors and instead use error statuses. The old behavior is used by default for HTTP/1.1 and HTTP/2 server connections. The new codecs can be enabled for testing by setting the runtime feature ``envoy.reloadable_features.new_codec_behavior`` to true. The new codecs will be in development for one month, and then enabled by default while the old codecs are deprecated. +* http: modified the HTTP header-map data-structure to use an underlying dictionary and a list (no change to the header-map API). To conform with previous versions, the use of a dictionary is currently disabled. It can be enabled by setting the ``envoy.http.headermap.lazy_map_min_size`` runtime feature to a non-negative number which defines the minimal number of headers in a request/response/trailers required for using a dictionary in addition to the list. Our current benchmarks suggest that the value 3 is a good threshold for most workloads. +* load balancer: added :ref:`RingHashLbConfig ` to configure the table size of Maglev consistent hash. +* load balancer: added a :ref:`configuration ` option to specify the active request bias used by the least request load balancer. +* load balancer: added an :ref:`option ` to optimize subset load balancing when there is only one host per subset. +* load balancer: added support for bounded load per host for consistent hash load balancers via :ref:`hash_balance_factor `. +* local_reply config: added :ref:`content_type ` field to set content-type. +* lua: added Lua APIs to access :ref:`SSL connection info ` object. +* lua: added Lua API for :ref:`base64 escaping a string `. +* lua: added Lua API for :ref:`setting the current buffer content `. +* lua: added new :ref:`source_code ` field to support the dispatching of inline Lua code in per route configuration of Lua filter. +* overload management: add :ref:`scaling ` trigger for OverloadManager actions. +* postgres network filter: :ref:`metadata ` is produced based on SQL query. +* proxy protocol: added support for generating the header upstream using :ref:`Proxy Protocol Transport Socket `. +* ratelimit: added :ref:`enable_x_ratelimit_headers ` option to enable `X-RateLimit-*` headers as defined in `draft RFC `_. +* ratelimit: added :ref:`per route config ` for rate limit filter. +* ratelimit: added support for optional :ref:`descriptor_key ` to Generic Key action. * rbac filter: added the name of the matched policy to the response code detail when a request is rejected by the RBAC filter. -* rbac filter: added a log action to the :ref:`RBAC filter ` which sets dynamic metadata to inform access loggers whether to log. -* redis: added fault injection support :ref:`fault injection for redis proxy `, described further in :ref:`configuration documentation `. -* router: added a new :ref:`rate limited retry back off ` strategy that uses headers like `Retry-After` or `X-RateLimit-Reset` to decide the back off interval. +* rbac filter: added a log action to the :ref:`RBAC filter ` which sets dynamic metadata to inform access loggers whether to log. +* redis: added fault injection support :ref:`fault injection for redis proxy `, described further in :ref:`configuration documentation `. +* router: added a new :ref:`rate limited retry back off ` strategy that uses headers like `Retry-After` or `X-RateLimit-Reset` to decide the back off interval. * router: added new - :ref:`envoy-ratelimited` + :ref:`envoy-ratelimited ` retry policy, which allows retrying envoy's own rate limited responses. -* router: added new :ref:`host_rewrite_path_regex ` +* router: added new :ref:`host_rewrite_path_regex ` option, which allows rewriting Host header based on path. -* router: added support for DYNAMIC_METADATA :ref:`header formatter `. -* router_check_tool: added support for `request_header_matches`, `response_header_matches` to :ref:`router check tool `. +* router: added support for DYNAMIC_METADATA :ref:`header formatter `. +* router_check_tool: added support for ``request_header_matches``, ``response_header_matches`` to :ref:`router check tool `. * signal: added support for calling fatal error handlers without envoy's signal handler, via FatalErrorHandler::callFatalErrorHandlers(). -* stats: added optional histograms to :ref:`cluster stats ` +* stats: added optional histograms to :ref:`cluster stats ` that track headers and body sizes of requests and responses. * stats: allow configuring histogram buckets for stats sinks and admin endpoints that support it. -* tap: added :ref:`generic body matcher` to scan http requests and responses for text or hex patterns. -* tcp_proxy: added :ref:`max_downstream_connection_duration` for downstream connection. When max duration is reached the connection will be closed. +* tap: added :ref:`generic body matcher ` to scan http requests and responses for text or hex patterns. +* tcp_proxy: added :ref:`max_downstream_connection_duration ` for downstream connection. When max duration is reached the connection will be closed. * tcp_proxy: allow earlier network filters to set metadataMatchCriteria on the connection StreamInfo to influence load balancing. -* tls: added OCSP stapling support through the :ref:`ocsp_staple ` and :ref:`ocsp_staple_policy ` configuration options. See :ref:`OCSP Stapling ` for usage and runtime flags. -* tls: introduce new :ref:`extension point` for overriding :ref:`TLS handshaker ` behavior. -* tls: switched from using socket BIOs to using custom BIOs that know how to interact with IoHandles. The feature can be disabled by setting runtime feature `envoy.reloadable_features.tls_use_io_handle_bio` to false. -* tracing: added ability to set some :ref:`optional segment fields` in the AWS X-Ray tracer. -* udp_proxy: added :ref:`hash_policies ` to support hash based routing. -* udp_proxy: added :ref:`use_original_src_ip ` option to replicate the downstream remote address of the packets on the upstream side of Envoy. It is similar to :ref:`original source filter `. -* watchdog: support randomizing the watchdog's kill timeout to prevent synchronized kills via a maximium jitter parameter :ref:`max_kill_timeout_jitter`. -* watchdog: supports an extension point where actions can be registered to fire on watchdog events such as miss, megamiss, kill and multikill. See :ref:`watchdog actions`. -* watchdog: watchdog action extension that does cpu profiling. See :ref:`Profile Action `. -* watchdog: watchdog action extension that sends SIGABRT to the stuck thread to terminate the process. See :ref:`Abort Action `. -* xds: added :ref:`extension config discovery` support for HTTP filters. -* xds: added support for mixed v2/v3 discovery response, which enable type url downgrade and upgrade. This feature is disabled by default and is controlled by runtime guard `envoy.reloadable_features.enable_type_url_downgrade_and_upgrade`. +* tls: added OCSP stapling support through the :ref:`ocsp_staple ` and :ref:`ocsp_staple_policy ` configuration options. See :ref:`OCSP Stapling ` for usage and runtime flags. +* tls: introduce new :ref:`extension point ` for overriding :ref:`TLS handshaker ` behavior. +* tls: switched from using socket BIOs to using custom BIOs that know how to interact with IoHandles. The feature can be disabled by setting runtime feature ``envoy.reloadable_features.tls_use_io_handle_bio`` to false. +* tracing: added ability to set some :ref:`optional segment fields ` in the AWS X-Ray tracer. +* udp_proxy: added :ref:`hash_policies ` to support hash based routing. +* udp_proxy: added :ref:`use_original_src_ip ` option to replicate the downstream remote address of the packets on the upstream side of Envoy. It is similar to :ref:`original source filter `. +* watchdog: support randomizing the watchdog's kill timeout to prevent synchronized kills via a maximium jitter parameter :ref:`max_kill_timeout_jitter `. +* watchdog: supports an extension point where actions can be registered to fire on watchdog events such as miss, megamiss, kill and multikill. See :ref:`watchdog actions `. +* watchdog: watchdog action extension that does cpu profiling. See :ref:`Profile Action `. +* watchdog: watchdog action extension that sends SIGABRT to the stuck thread to terminate the process. See :ref:`Abort Action `. +* xds: added :ref:`extension config discovery ` support for HTTP filters. +* xds: added support for mixed v2/v3 discovery response, which enable type url downgrade and upgrade. This feature is disabled by default and is controlled by runtime guard ``envoy.reloadable_features.enable_type_url_downgrade_and_upgrade``. * zlib: added option to use `zlib-ng `_ as zlib library. Deprecated ---------- -* build: alpine based debug image is deprecated in favor of :ref:`Ubuntu based debug image `. -* cluster: the :ref:`track_timeout_budgets ` - field has been deprecated in favor of `timeout_budgets` part of an :ref:`Optional Configuration `. -* ext_authz: the :ref:`dynamic metadata ` field in :ref:`OkHttpResponse ` has been deprecated in favor of :ref:`dynamic metadata ` field in :ref:`CheckResponse `. -* hds: the :ref:`endpoints_health ` - field has been deprecated in favor of :ref:`cluster_endpoints_health ` to maintain +* build: alpine based debug image is deprecated in favor of :ref:`Ubuntu based debug image `. +* cluster: the :ref:`track_timeout_budgets ` + field has been deprecated in favor of `timeout_budgets` part of an :ref:`Optional Configuration `. +* ext_authz: the :ref:`dynamic metadata ` field in :ref:`OkHttpResponse ` has been deprecated in favor of :ref:`dynamic metadata ` field in :ref:`CheckResponse `. +* hds: the :ref:`endpoints_health ` + field has been deprecated in favor of :ref:`cluster_endpoints_health ` to maintain grouping by cluster and locality. -* router: the :ref:`include_vh_rate_limits ` field has been deprecated in favor of :ref:`vh_rate_limits `. -* router: the :ref:`max_grpc_timeout ` field has been deprecated in favor of :ref:`grpc_timeout_header_max `. -* router: the :ref:`grpc_timeout_offset ` field has been deprecated in favor of :ref:`grpc_timeout_header_offset `. -* tap: the :ref:`match_config ` field has been deprecated in favor of - :ref:`match ` field. -* router_check_tool: `request_header_fields`, `response_header_fields` config deprecated in favor of `request_header_matches`, `response_header_matches`. -* watchdog: :ref:`watchdog ` deprecated in favor of :ref:`watchdogs `. +* router: the :ref:`include_vh_rate_limits ` field has been deprecated in favor of :ref:`vh_rate_limits `. +* router: the :ref:`max_grpc_timeout ` field has been deprecated in favor of :ref:`grpc_timeout_header_max `. +* router: the :ref:`grpc_timeout_offset ` field has been deprecated in favor of :ref:`grpc_timeout_header_offset `. +* tap: the :ref:`match_config ` field has been deprecated in favor of + :ref:`match ` field. +* router_check_tool: ``request_header_fields``, ``response_header_fields`` config deprecated in favor of ``request_header_matches``, ``response_header_matches``. +* watchdog: :ref:`watchdog ` deprecated in favor of :ref:`watchdogs `. diff --git a/docs/root/version_history/v1.16.3.rst b/docs/root/version_history/v1.16.3.rst index e40fe3911d4d3..125902e2f67da 100644 --- a/docs/root/version_history/v1.16.3.rst +++ b/docs/root/version_history/v1.16.3.rst @@ -14,9 +14,9 @@ Bug Fixes *Changes expected to improve the state of the world and are unlikely to have negative effects* * aggregate cluster: fixed a crash due to a TLS initialization issue. -* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. * http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. -* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. +* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting ``envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure`` to false. * lua: fixed crash when Lua script contains streamInfo():downstreamSslConnection(). * overload: fix a bug that can cause use-after-free when one scaled timer disables another one with the same duration. * tls: fix a crash when peer sends a TLS Alert with an unknown code. @@ -24,11 +24,10 @@ Bug Fixes Removed Config or Runtime ------------------------- -*Normally occurs at the end of the* :ref:`deprecation period ` +*Normally occurs at the end of the* :ref:`deprecation period ` New Features ------------ Deprecated ---------- - diff --git a/docs/root/version_history/v1.17.0.rst b/docs/root/version_history/v1.17.0.rst index 81059460beb68..ace3ea40fd979 100644 --- a/docs/root/version_history/v1.17.0.rst +++ b/docs/root/version_history/v1.17.0.rst @@ -5,7 +5,7 @@ Incompatible Behavior Changes ----------------------------- *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* -* config: v2 is now fatal-by-default. This may be overridden by setting :option:`--bootstrap-version` 2 on the CLI for a v2 bootstrap file and also enabling the runtime `envoy.reloadable_features.enable_deprecated_v2_api` feature. +* config: v2 is now fatal-by-default. This may be overridden by setting :option:`--bootstrap-version` 2 on the CLI for a v2 bootstrap file and also enabling the runtime ``envoy.reloadable_features.enable_deprecated_v2_api`` feature. Minor Behavior Changes ---------------------- @@ -13,31 +13,31 @@ Minor Behavior Changes * build: the Alpine based debug images are no longer built in CI, use Ubuntu based images instead. * decompressor: set the default value of window_bits of the decompressor to 15 to be able to decompress responses compressed by a compressor with any window size. -* expr filter: added `connection.termination_details` property support. -* formatter: the :ref:`text_format ` field no longer requires at least one byte, and may now be the empty string. It has also become :ref:`deprecated <1_17_deprecated>`. -* grpc_web filter: if a `grpc-accept-encoding` header is present it's passed as-is to the upstream and if it isn't `grpc-accept-encoding:identity` is sent instead. The header was always overwriten with `grpc-accept-encoding:identity,deflate,gzip` before. +* expr filter: added ``connection.termination_details`` property support. +* formatter: the :ref:`text_format ` field no longer requires at least one byte, and may now be the empty string. It has also become :ref:`deprecated `. +* grpc_web filter: if a ``grpc-accept-encoding`` header is present it's passed as-is to the upstream and if it isn't ``grpc-accept-encoding:identity`` is sent instead. The header was always overwriten with ``grpc-accept-encoding:identity,deflate,gzip`` before. * http: upstream protocol will now only be logged if an upstream stream was established. -* jwt_authn filter: added support of JWT time constraint verification with a clock skew (default to 60 seconds) and added a filter config field :ref:`clock_skew_seconds ` to configure it. -* listener: injection of the :ref:`TLS inspector ` has been disabled by default. This feature is controlled by the runtime guard `envoy.reloadable_features.disable_tls_inspector_injection`. -* lua: added `always_wrap_body` argument to `body()` API to always return a :ref:`buffer object ` even if the body is empty. +* jwt_authn filter: added support of JWT time constraint verification with a clock skew (default to 60 seconds) and added a filter config field :ref:`clock_skew_seconds ` to configure it. +* listener: injection of the :ref:`TLS inspector ` has been disabled by default. This feature is controlled by the runtime guard ``envoy.reloadable_features.disable_tls_inspector_injection``. +* lua: added `always_wrap_body` argument to `body()` API to always return a :ref:`buffer object ` even if the body is empty. * memory: enabled new tcmalloc with restartable sequences for aarch64 builds. -* mongo proxy metrics: swapped network connection remote and local closed counters previously set reversed (`cx_destroy_local_with_active_rq` and `cx_destroy_remote_with_active_rq`). -* outlier detection: added :ref:`max_ejection_time ` to limit ejection time growth when a node stays unhealthy for extended period of time. By default :ref:`max_ejection_time ` limits ejection time to 5 minutes. Additionally, when the node stays healthy, ejection time decreases. See :ref:`ejection algorithm` for more info. Previously, ejection time could grow without limit and never decreased. +* mongo proxy metrics: swapped network connection remote and local closed counters previously set reversed (``cx_destroy_local_with_active_rq`` and ``cx_destroy_remote_with_active_rq``). +* outlier detection: added :ref:`max_ejection_time ` to limit ejection time growth when a node stays unhealthy for extended period of time. By default :ref:`max_ejection_time ` limits ejection time to 5 minutes. Additionally, when the node stays healthy, ejection time decreases. See :ref:`ejection algorithm ` for more info. Previously, ejection time could grow without limit and never decreased. * performance: improved performance when handling large HTTP/1 bodies. -* tcp_proxy: now waits for HTTP tunnel to be established before start streaming the downstream data, the runtime guard `envoy.reloadable_features.http_upstream_wait_connect_response` can be set to "false" to disable this behavior. +* tcp_proxy: now waits for HTTP tunnel to be established before start streaming the downstream data, the runtime guard ``envoy.reloadable_features.http_upstream_wait_connect_response`` can be set to "false" to disable this behavior. * tls: removed RSA key transport and SHA-1 cipher suites from the client-side defaults. -* watchdog: the watchdog action :ref:`abort_action ` is now the default action to terminate the process if watchdog kill / multikill is enabled. +* watchdog: the watchdog action :ref:`abort_action ` is now the default action to terminate the process if watchdog kill / multikill is enabled. * xds: to support TTLs, heartbeating has been added to xDS. As a result, responses that contain empty resources without updating the version will no longer be propagated to the - subscribers. To undo this for VHDS (which is the only subscriber that wants empty resources), the `envoy.reloadable_features.vhds_heartbeats` can be set to "false". + subscribers. To undo this for VHDS (which is the only subscriber that wants empty resources), the ``envoy.reloadable_features.vhds_heartbeats`` can be set to "false". Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* -* config: validate that upgrade configs have a non-empty :ref:`upgrade_type `, fixing a bug where an errant "-" could result in unexpected behavior. +* config: validate that upgrade configs have a non-empty :ref:`upgrade_type `, fixing a bug where an errant "-" could result in unexpected behavior. * dns: fixed a bug where custom resolvers provided in configuration were not preserved after network issues. * dns_filter: correctly associate DNS response IDs when multiple queries are received. -* grpc mux: fixed sending node again after stream is reset when :ref:`set_node_on_first_message_only ` is set. +* grpc mux: fixed sending node again after stream is reset when :ref:`set_node_on_first_message_only ` is set. * http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. * http: reject requests with missing required headers after filter chain processing. * http: sending CONNECT_ERROR for HTTP/2 where appropriate during CONNECT requests. @@ -51,70 +51,70 @@ Bug Fixes Removed Config or Runtime ------------------------- -*Normally occurs at the end of the* :ref:`deprecation period ` +*Normally occurs at the end of the* :ref:`deprecation period ` -* dispatcher: removed legacy socket read/write resumption code path and runtime guard `envoy.reloadable_features.activate_fds_next_event_loop`. -* ext_authz: removed auto ignore case in HTTP-based `ext_authz` header matching and the runtime guard `envoy.reloadable_features.ext_authz_http_service_enable_case_sensitive_string_matcher`. To ignore case, set the :ref:`ignore_case ` field to true. -* ext_authz: the deprecated field `use_alpha` is no longer supported and cannot be set anymore. -* http: removed `envoy.reloadable_features.http1_flood_protection` and legacy code path for turning flood protection off. -* http: removed `envoy.reloadable_features.new_codec_behavior` and legacy codecs. +* dispatcher: removed legacy socket read/write resumption code path and runtime guard ``envoy.reloadable_features.activate_fds_next_event_loop``. +* ext_authz: removed auto ignore case in HTTP-based ``ext_authz`` header matching and the runtime guard ``envoy.reloadable_features.ext_authz_http_service_enable_case_sensitive_string_matcher``. To ignore case, set the :ref:`ignore_case ` field to true. +* ext_authz: the deprecated field ``use_alpha`` is no longer supported and cannot be set anymore. +* http: removed ``envoy.reloadable_features.http1_flood_protection`` and legacy code path for turning flood protection off. +* http: removed ``envoy.reloadable_features.new_codec_behavior`` and legacy codecs. New Features ------------ -* compression: the :ref:`compressor ` filter added support for compressing request payloads. Its configuration is unified with the :ref:`decompressor ` filter with two new fields for different directions - :ref:`requests ` and :ref:`responses `. The latter deprecates the old response-specific fields and, if used, roots the response-specific stats in `.compressor...response.*` instead of `.compressor...*`. -* config: added ability to flush stats when the admin's :ref:`/stats endpoint ` is hit instead of on a timer via :ref:`stats_flush_on_admin `. -* config: added new runtime feature `envoy.features.enable_all_deprecated_features` that allows the use of all deprecated features. +* compression: the :ref:`compressor ` filter added support for compressing request payloads. Its configuration is unified with the :ref:`decompressor ` filter with two new fields for different directions - :ref:`requests ` and :ref:`responses `. The latter deprecates the old response-specific fields and, if used, roots the response-specific stats in `.compressor...response.*` instead of `.compressor...*`. +* config: added ability to flush stats when the admin's :ref:`/stats endpoint ` is hit instead of on a timer via :ref:`stats_flush_on_admin `. +* config: added new runtime feature ``envoy.features.enable_all_deprecated_features`` that allows the use of all deprecated features. * crash support: added the ability to dump L4 connection data on crash. -* formatter: added new :ref:`text_format_source ` field to support format strings both inline and from a file. -* formatter: added support for custom date formatting to :ref:`%DOWNSTREAM_PEER_CERT_V_START% ` and :ref:`%DOWNSTREAM_PEER_CERT_V_END% `, similar to :ref:`%START_TIME% `. -* grpc: implemented header value syntax support when defining :ref:`initial metadata ` for gRPC-based `ext_authz` :ref:`HTTP ` and :ref:`network ` filters, and :ref:`ratelimit ` filters. -* grpc-json: added support for configuring :ref:`unescaping behavior ` for path components. -* hds: added support for delta updates in the :ref:`HealthCheckSpecifier `, making only the Endpoints and Health Checkers that changed be reconstructed on receiving a new message, rather than the entire HDS. -* health_check: added option to use :ref:`no_traffic_healthy_interval ` which allows a different no traffic interval when the host is healthy. -* http: added HCM :ref:`request_headers_timeout config field ` to control how long a downstream has to finish sending headers before the stream is cancelled. -* http: added frame flood and abuse checks to the upstream HTTP/2 codec. This check is off by default and can be enabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to true. -* http: added :ref:`stripping any port from host header ` support. -* http: clusters added support for selecting HTTP/1 or HTTP/2 based on ALPN, configurable via :ref:`alpn_config ` in the :ref:`http_protocol_options ` message. -* jwt_authn: added support for :ref:`per-route config `. -* jwt_authn: changed config field :ref:`issuer ` to be optional to comply with JWT `RFC `_ requirements. -* kill_request: added new :ref:`HTTP kill request filter `. -* listener: added an optional :ref:`default filter chain `. If this field is supplied, and none of the :ref:`filter_chains ` matches, this default filter chain is used to serve the connection. -* listener: added back the :ref:`use_original_dst field `. -* listener: added the :ref:`Listener.bind_to_port field `. +* formatter: added new :ref:`text_format_source ` field to support format strings both inline and from a file. +* formatter: added support for custom date formatting to :ref:`%DOWNSTREAM_PEER_CERT_V_START% ` and :ref:`%DOWNSTREAM_PEER_CERT_V_END% `, similar to :ref:`%START_TIME% `. +* grpc: implemented header value syntax support when defining :ref:`initial metadata ` for gRPC-based `ext_authz` :ref:`HTTP ` and :ref:`network ` filters, and :ref:`ratelimit ` filters. +* grpc-json: added support for configuring :ref:`unescaping behavior ` for path components. +* hds: added support for delta updates in the :ref:`HealthCheckSpecifier `, making only the Endpoints and Health Checkers that changed be reconstructed on receiving a new message, rather than the entire HDS. +* health_check: added option to use :ref:`no_traffic_healthy_interval ` which allows a different no traffic interval when the host is healthy. +* http: added HCM :ref:`request_headers_timeout config field ` to control how long a downstream has to finish sending headers before the stream is cancelled. +* http: added frame flood and abuse checks to the upstream HTTP/2 codec. This check is off by default and can be enabled by setting the ``envoy.reloadable_features.upstream_http2_flood_checks`` runtime key to true. +* http: added :ref:`stripping any port from host header ` support. +* http: clusters added support for selecting HTTP/1 or HTTP/2 based on ALPN, configurable via :ref:`alpn_config ` in the :ref:`http_protocol_options ` message. +* jwt_authn: added support for :ref:`per-route config `. +* jwt_authn: changed config field :ref:`issuer ` to be optional to comply with JWT `RFC `_ requirements. +* kill_request: added new :ref:`HTTP kill request filter `. +* listener: added an optional :ref:`default filter chain `. If this field is supplied, and none of the :ref:`filter_chains ` matches, this default filter chain is used to serve the connection. +* listener: added back the :ref:`use_original_dst field `. +* listener: added the :ref:`Listener.bind_to_port field `. * log: added a new custom flag ``%_`` to the log pattern to print the actual message to log, but with escaped newlines. -* lua: added `downstreamDirectRemoteAddress()` and `downstreamLocalAddress()` APIs to :ref:`streamInfo() `. -* mongo_proxy: the list of commands to produce metrics for is now :ref:`configurable `. -* network: added a :ref:`transport_socket_connect_timeout config field ` for incoming connections completing transport-level negotiation, including TLS and ALTS hanshakes. -* overload: added :ref:`envoy.overload_actions.reduce_timeouts ` overload action to enable scaling timeouts down with load. Scaling support :ref:`is limited ` to the HTTP connection and stream idle timeouts. -* ratelimit: added support for use of various :ref:`metadata ` as a ratelimit action. -* ratelimit: added :ref:`disable_x_envoy_ratelimited_header ` option to disable `X-Envoy-RateLimited` header. -* ratelimit: added :ref:`body ` field to support custom response bodies for non-OK responses from the external ratelimit service. -* ratelimit: added :ref:`descriptor extensions `. -* ratelimit: added :ref:`computed descriptors `. -* ratelimit: added :ref:`dynamic_metadata ` field to support setting dynamic metadata from the ratelimit service. -* router: added support for regex rewrites during HTTP redirects using :ref:`regex_rewrite `. -* sds: improved support for atomic :ref:`key rotations ` and added configurable rotation triggers for - :ref:`TlsCertificate ` and - :ref:`CertificateValidationContext `. -* signal: added an extension point for custom actions to run on the thread that has encountered a fatal error. Actions are configurable via :ref:`fatal_actions `. -* start_tls: added new :ref:`transport socket` which starts in clear-text but may programatically be converted to use tls. -* tcp: added a new :ref:`envoy.overload_actions.reject_incoming_connections ` action to reject incoming TCP connections. -* thrift_proxy: added a new :ref:`payload_passthrough ` option to skip decoding body in the Thrift message. +* lua: added `downstreamDirectRemoteAddress()` and `downstreamLocalAddress()` APIs to :ref:`streamInfo() `. +* mongo_proxy: the list of commands to produce metrics for is now :ref:`configurable `. +* network: added a :ref:`transport_socket_connect_timeout config field ` for incoming connections completing transport-level negotiation, including TLS and ALTS hanshakes. +* overload: added :ref:`envoy.overload_actions.reduce_timeouts ` overload action to enable scaling timeouts down with load. Scaling support :ref:`is limited ` to the HTTP connection and stream idle timeouts. +* ratelimit: added support for use of various :ref:`metadata ` as a ratelimit action. +* ratelimit: added :ref:`disable_x_envoy_ratelimited_header ` option to disable `X-Envoy-RateLimited` header. +* ratelimit: added :ref:`body ` field to support custom response bodies for non-OK responses from the external ratelimit service. +* ratelimit: added :ref:`descriptor extensions `. +* ratelimit: added :ref:`computed descriptors `. +* ratelimit: added :ref:`dynamic_metadata ` field to support setting dynamic metadata from the ratelimit service. +* router: added support for regex rewrites during HTTP redirects using :ref:`regex_rewrite `. +* sds: improved support for atomic :ref:`key rotations ` and added configurable rotation triggers for + :ref:`TlsCertificate ` and + :ref:`CertificateValidationContext `. +* signal: added an extension point for custom actions to run on the thread that has encountered a fatal error. Actions are configurable via :ref:`fatal_actions `. +* start_tls: added new :ref:`transport socket ` which starts in clear-text but may programatically be converted to use tls. +* tcp: added a new :ref:`envoy.overload_actions.reject_incoming_connections ` action to reject incoming TCP connections. +* thrift_proxy: added a new :ref:`payload_passthrough ` option to skip decoding body in the Thrift message. * tls: added support for RSA certificates with 4096-bit keys in FIPS mode. -* tracing: added :ref:`SkyWalking tracer `. -* tracing: added support for setting the hostname used when sending spans to a Zipkin collector using the :ref:`collector_hostname ` field. -* xds: added support for resource TTLs. A TTL is specified on the :ref:`Resource `. For SotW, a :ref:`Resource ` can be embedded in the list of resources to specify the TTL. +* tracing: added :ref:`SkyWalking tracer `. +* tracing: added support for setting the hostname used when sending spans to a Zipkin collector using the :ref:`collector_hostname ` field. +* xds: added support for resource TTLs. A TTL is specified on the :ref:`Resource `. For SotW, a :ref:`Resource ` can be embedded in the list of resources to specify the TTL. .. _1_17_deprecated: Deprecated ---------- -* cluster: HTTP configuration for upstream clusters has been reworked. HTTP-specific configuration is now done in the new :ref:`http_protocol_options ` message, configured via the cluster's :ref:`extension_protocol_options`. This replaces explicit HTTP configuration in cluster config, including :ref:`upstream_http_protocol_options` :ref:`common_http_protocol_options` :ref:`http_protocol_options` :ref:`http2_protocol_options` and :ref:`protocol_selection`. Examples of before-and-after configuration can be found in the :ref:`http_protocol_options docs ` and all of Envoy's example configurations have been updated to the new style of config. -* compression: the fields :ref:`content_length `, :ref:`content_type `, :ref:`disable_on_etag_header `, :ref:`remove_accept_encoding_header ` and :ref:`runtime_enabled ` of the :ref:`Compressor ` message have been deprecated in favor of :ref:`response_direction_config `. -* formatter: :ref:`text_format ` is now deprecated in favor of :ref:`text_format_source `. To migrate existing text format strings, use the :ref:`inline_string ` field. -* gzip: :ref:`HTTP Gzip filter ` is rejected now unless explicitly allowed with :ref:`runtime override ` `envoy.deprecated_features.allow_deprecated_gzip_http_filter` set to `true`. Use the :ref:`compressor filter `. -* listener: :ref:`use_proxy_proto ` has been deprecated in favor of adding a :ref:`PROXY protocol listener filter ` explicitly. -* logging: the `--log-format-prefix-with-location` option is removed. -* ratelimit: the :ref:`dynamic metadata ` action is deprecated in favor of the more generic :ref:`metadata ` action. -* stats: the `--use-fake-symbol-table` option is removed. -* tracing: OpenCensus :ref:`Zipkin configuration ` is now deprecated, the preferred Zipkin export is via Envoy's :ref:`native Zipkin tracer `. +* cluster: HTTP configuration for upstream clusters has been reworked. HTTP-specific configuration is now done in the new :ref:`http_protocol_options ` message, configured via the cluster's :ref:`extension_protocol_options `. This replaces explicit HTTP configuration in cluster config, including :ref:`upstream_http_protocol_options ` :ref:`common_http_protocol_options ` :ref:`http_protocol_options ` :ref:`http2_protocol_options ` and :ref:`protocol_selection `. Examples of before-and-after configuration can be found in the :ref:`http_protocol_options docs ` and all of Envoy's example configurations have been updated to the new style of config. +* compression: the fields :ref:`content_length `, :ref:`content_type `, :ref:`disable_on_etag_header `, :ref:`remove_accept_encoding_header ` and :ref:`runtime_enabled ` of the :ref:`Compressor ` message have been deprecated in favor of :ref:`response_direction_config `. +* formatter: :ref:`text_format ` is now deprecated in favor of :ref:`text_format_source `. To migrate existing text format strings, use the :ref:`inline_string ` field. +* gzip: :ref:`HTTP Gzip filter ` is rejected now unless explicitly allowed with :ref:`runtime override ` ``envoy.deprecated_features.allow_deprecated_gzip_http_filter`` set to `true`. Use the :ref:`compressor filter `. +* listener: :ref:`use_proxy_proto ` has been deprecated in favor of adding a :ref:`PROXY protocol listener filter ` explicitly. +* logging: the ``--log-format-prefix-with-location`` option is removed. +* ratelimit: the :ref:`dynamic metadata ` action is deprecated in favor of the more generic :ref:`metadata ` action. +* stats: the ``--use-fake-symbol-table`` option is removed. +* tracing: OpenCensus :ref:`Zipkin configuration ` is now deprecated, the preferred Zipkin export is via Envoy's :ref:`native Zipkin tracer `. diff --git a/docs/root/version_history/v1.17.1.rst b/docs/root/version_history/v1.17.1.rst index 0cafd45771a29..35429d90da7bc 100644 --- a/docs/root/version_history/v1.17.1.rst +++ b/docs/root/version_history/v1.17.1.rst @@ -18,11 +18,10 @@ Bug Fixes Removed Config or Runtime ------------------------- -*Normally occurs at the end of the* :ref:`deprecation period ` +*Normally occurs at the end of the* :ref:`deprecation period ` New Features ------------ Deprecated ---------- - diff --git a/docs/root/version_history/v1.17.2.rst b/docs/root/version_history/v1.17.2.rst index 86e7b5a577967..570447c67a045 100644 --- a/docs/root/version_history/v1.17.2.rst +++ b/docs/root/version_history/v1.17.2.rst @@ -13,19 +13,18 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* -* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. * http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. -* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. +* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting ``envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure`` to false. * tls: fix a crash when peer sends a TLS Alert with an unknown code. Removed Config or Runtime ------------------------- -*Normally occurs at the end of the* :ref:`deprecation period ` +*Normally occurs at the end of the* :ref:`deprecation period ` New Features ------------ -* dispatcher: supports a stack of `Envoy::ScopeTrackedObject` instead of a single tracked object. This will allow Envoy to dump more debug information on crash. +* dispatcher: supports a stack of ``Envoy::ScopeTrackedObject`` instead of a single tracked object. This will allow Envoy to dump more debug information on crash. Deprecated ---------- - diff --git a/docs/root/version_history/v1.18.0.rst b/docs/root/version_history/v1.18.0.rst index d0d33a150f52f..10eaf16302257 100644 --- a/docs/root/version_history/v1.18.0.rst +++ b/docs/root/version_history/v1.18.0.rst @@ -6,70 +6,70 @@ Incompatible Behavior Changes *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* * config: the v2 xDS API is no longer supported by the Envoy binary. -* grpc_stats: the default value for :ref:`stats_for_all_methods ` is switched from true to false, in order to avoid possible memory exhaustion due to an untrusted downstream sending a large number of unique method names. The previous default value was deprecated in version 1.14.0. This only changes the behavior when the value is not set. The previous behavior can be used by setting the value to true. This behavior change by be overridden by setting runtime feature `envoy.deprecated_features.grpc_stats_filter_enable_stats_for_all_methods_by_default`. -* http: fixing a standards compliance issue with :scheme. The :scheme header sent upstream is now based on the original URL scheme, rather than set based on the security of the upstream connection. This behavior can be temporarily reverted by setting `envoy.reloadable_features.preserve_downstream_scheme` to false. -* http: http3 is now enabled/disabled via build option `--define http3=disabled` rather than the extension framework. The behavior is the same, but builds may be affected for platforms or build configurations where http3 is not supported. -* http: resolving inconsistencies between :scheme and X-Forwarded-Proto. :scheme will now be set for all HTTP/1.1 requests. This changes the behavior of the gRPC access logger, Wasm filters, CSRF filter and oath2 filter for HTTP/1 traffic, where :scheme was previously not set. This change also validates that for front-line Envoys (Envoys configured with :ref:`xff_num_trusted_hops ` set to 0 and :ref:`use_remote_address ` set to true) that HTTP/1.1 https schemed requests can not be sent over non-TLS connections. All behavioral changes listed here can be temporarily reverted by setting `envoy.reloadable_features.add_and_validate_scheme_header` to false. -* http: when a protocol error is detected in response from upstream, Envoy sends 502 BadGateway downstream and access log entry contains UPE flag. This behavior change can be overwritten to use error code 503 by setting `envoy.reloadable_features.return_502_for_upstream_protocol_errors` to false. +* grpc_stats: the default value for :ref:`stats_for_all_methods ` is switched from true to false, in order to avoid possible memory exhaustion due to an untrusted downstream sending a large number of unique method names. The previous default value was deprecated in version 1.14.0. This only changes the behavior when the value is not set. The previous behavior can be used by setting the value to true. This behavior change by be overridden by setting runtime feature ``envoy.deprecated_features.grpc_stats_filter_enable_stats_for_all_methods_by_default``. +* http: fixing a standards compliance issue with :scheme. The :scheme header sent upstream is now based on the original URL scheme, rather than set based on the security of the upstream connection. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.preserve_downstream_scheme`` to false. +* http: http3 is now enabled/disabled via build option ``--define http3=disabled`` rather than the extension framework. The behavior is the same, but builds may be affected for platforms or build configurations where http3 is not supported. +* http: resolving inconsistencies between :scheme and X-Forwarded-Proto. :scheme will now be set for all HTTP/1.1 requests. This changes the behavior of the gRPC access logger, Wasm filters, CSRF filter and oath2 filter for HTTP/1 traffic, where :scheme was previously not set. This change also validates that for front-line Envoys (Envoys configured with :ref:`xff_num_trusted_hops ` set to 0 and :ref:`use_remote_address ` set to true) that HTTP/1.1 https schemed requests can not be sent over non-TLS connections. All behavioral changes listed here can be temporarily reverted by setting ``envoy.reloadable_features.add_and_validate_scheme_header`` to false. +* http: when a protocol error is detected in response from upstream, Envoy sends 502 BadGateway downstream and access log entry contains UPE flag. This behavior change can be overwritten to use error code 503 by setting ``envoy.reloadable_features.return_502_for_upstream_protocol_errors`` to false. Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* -* access_logs: change command operator %UPSTREAM_CLUSTER% to resolve to :ref:`alt_stat_name ` if provided. This behavior can be reverted by disabling the runtime feature `envoy.reloadable_features.use_observable_cluster_name`. +* access_logs: change command operator %UPSTREAM_CLUSTER% to resolve to :ref:`alt_stat_name ` if provided. This behavior can be reverted by disabling the runtime feature ``envoy.reloadable_features.use_observable_cluster_name``. * access_logs: fix substition formatter to recognize commands ending with an integer such as DOWNSTREAM_PEER_FINGERPRINT_256. -* access_logs: set the error flag `NC` for `no cluster found` instead of `NR` if the route is found but the corresponding cluster is not available. -* admin: added :ref:`observability_name ` information to GET /clusters?format=json :ref:`cluster status `. -* dns: both the :ref:`strict DNS ` and - :ref:`logical DNS ` cluster types now honor the - :ref:`hostname ` field if not empty. +* access_logs: set the error flag ``NC`` for ``no cluster found`` instead of ``NR`` if the route is found but the corresponding cluster is not available. +* admin: added :ref:`observability_name ` information to GET /clusters?format=json :ref:`cluster status `. +* dns: both the :ref:`strict DNS ` and + :ref:`logical DNS ` cluster types now honor the + :ref:`hostname ` field if not empty. Previously resolved hosts would have their hostname set to the configured DNS address for use with - logging, :ref:`auto_host_rewrite `, etc. + logging, :ref:`auto_host_rewrite `, etc. Setting the hostname manually allows overriding the internal hostname used for such features while still allowing the original DNS resolution name to be used. * grpc_json_transcoder: the filter now adheres to encoder and decoder buffer limits. Requests and responses that require buffering over the limits will be directly rejected. The behavior can be reverted by - disabling runtime feature `envoy.reloadable_features.grpc_json_transcoder_adhere_to_buffer_limits`. - To reduce or increase the buffer limits the filter adheres to, reference the :ref:`flow control documentation `. -* hds: support custom health check port via :ref:`health_check_config `. -* healthcheck: the :ref:`health check filter ` now sends the - :ref:`x-envoy-immediate-health-check-fail ` header + disabling runtime feature ``envoy.reloadable_features.grpc_json_transcoder_adhere_to_buffer_limits``. + To reduce or increase the buffer limits the filter adheres to, reference the :ref:`flow control documentation `. +* hds: support custom health check port via :ref:`health_check_config `. +* healthcheck: the :ref:`health check filter ` now sends the + :ref:`x-envoy-immediate-health-check-fail ` header for all responses when Envoy is in the health check failed state. Additionally, receiving the - :ref:`x-envoy-immediate-health-check-fail ` - header (either in response to normal traffic or in response to an HTTP :ref:`active health check `) will - cause Envoy to immediately :ref:`exclude ` the host from + :ref:`x-envoy-immediate-health-check-fail ` + header (either in response to normal traffic or in response to an HTTP :ref:`active health check `) will + cause Envoy to immediately :ref:`exclude ` the host from load balancing calculations. This has the useful property that such hosts, which are being explicitly told to disable traffic, will not be counted for panic routing calculations. See the excluded documentation for more information. This behavior can be temporarily reverted by setting - the `envoy.reloadable_features.health_check.immediate_failure_exclude_from_cluster` feature flag + the ``envoy.reloadable_features.health_check.immediate_failure_exclude_from_cluster`` feature flag to false. Note that the runtime flag covers *both* the health check filter responding with - `x-envoy-immediate-health-check-fail` in all cases (versus just non-HC requests) as well as - whether receiving `x-envoy-immediate-health-check-fail` will cause exclusion or not. Thus, + ``x-envoy-immediate-health-check-fail`` in all cases (versus just non-HC requests) as well as + whether receiving ``x-envoy-immediate-health-check-fail`` will cause exclusion or not. Thus, depending on the Envoy deployment, the feature flag may need to be flipped on both downstream and upstream instances, depending on the reason. -* http: added support for internal redirects with bodies. This behavior can be disabled temporarily by setting `envoy.reloadable_features.internal_redirects_with_body` to false. +* http: added support for internal redirects with bodies. This behavior can be disabled temporarily by setting ``envoy.reloadable_features.internal_redirects_with_body`` to false. * http: increase the maximum allowed number of initial connection WINDOW_UPDATE frames sent by the peer from 1 to 5. -* http: no longer adding content-length: 0 for requests which should not have bodies. This behavior can be temporarily reverted by setting `envoy.reloadable_features.dont_add_content_length_for_bodiless_requests` false. -* http: switched the path canonicalizer to `googleurl `_ - instead of `//source/common/chromium_url`. The new path canonicalizer is enabled by default. To +* http: no longer adding content-length: 0 for requests which should not have bodies. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.dont_add_content_length_for_bodiless_requests`` false. +* http: switched the path canonicalizer to ``googleurl `_ + instead of ``//source/common/chromium_url``. The new path canonicalizer is enabled by default. To revert to the legacy path canonicalizer, enable the runtime flag - `envoy.reloadable_features.remove_forked_chromium_url`. + ``envoy.reloadable_features.remove_forked_chromium_url``. * http: upstream flood and abuse checks now increment the count of opened HTTP/2 streams when Envoy sends initial HEADERS frame for the new stream. Before the counter was incrementred when Envoy received response HEADERS frame with the END_HEADERS flag set from upstream server. -* lua: added function `timestamp` to provide millisecond resolution timestamps by passing in `EnvoyTimestampResolution.MILLISECOND`. -* oauth filter: added the optional parameter :ref:`auth_scopes ` with default value of 'user' if not provided. This allows this value to be overridden in the Authorization request to the OAuth provider. +* lua: added function ``timestamp`` to provide millisecond resolution timestamps by passing in ``EnvoyTimestampResolution.MILLISECOND``. +* oauth filter: added the optional parameter :ref:`auth_scopes ` with default value of 'user' if not provided. This allows this value to be overridden in the Authorization request to the OAuth provider. * perf: allow reading more bytes per operation from raw sockets to improve performance. -* router: extended custom date formatting to DOWNSTREAM_PEER_CERT_V_START and DOWNSTREAM_PEER_CERT_V_END when using :ref:`custom request/response header formats `. +* router: extended custom date formatting to DOWNSTREAM_PEER_CERT_V_START and DOWNSTREAM_PEER_CERT_V_END when using :ref:`custom request/response header formats `. * router: made the path rewrite available without finalizing headers, so the filter could calculate the current value of the final url. -* tracing: added `upstream_cluster.name` tag that resolves to resolve to :ref:`alt_stat_name ` if provided (and otherwise the cluster name). -* udp: configuration has been added for :ref:`GRO ` +* tracing: added ``upstream_cluster.name`` tag that resolves to resolve to :ref:`alt_stat_name ` if provided (and otherwise the cluster name). +* udp: configuration has been added for :ref:`GRO ` which used to be force enabled if the OS supports it. The default is now disabled for server sockets and enabled for client sockets (see the new features section for links). * upstream: host weight changes now cause a full load balancer rebuild as opposed to happening atomically inline. This change has been made to support load balancer pre-computation of data structures based on host weight, but may have performance implications if host weight changes - are very frequent. This change can be disabled by setting the `envoy.reloadable_features.upstream_host_weight_change_causes_rebuild` + are very frequent. This change can be disabled by setting the ``envoy.reloadable_features.upstream_host_weight_change_causes_rebuild`` feature flag to false. If setting this flag to false is required in a deployment please open an issue against the project. @@ -77,24 +77,24 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* -* active http health checks: properly handles HTTP/2 GOAWAY frames from the upstream. Previously a GOAWAY frame due to a graceful listener drain could cause improper failed health checks due to streams being refused by the upstream on a connection that is going away. To revert to old GOAWAY handling behavior, set the runtime feature `envoy.reloadable_features.health_check.graceful_goaway_handling` to false. +* active http health checks: properly handles HTTP/2 GOAWAY frames from the upstream. Previously a GOAWAY frame due to a graceful listener drain could cause improper failed health checks due to streams being refused by the upstream on a connection that is going away. To revert to old GOAWAY handling behavior, set the runtime feature ``envoy.reloadable_features.health_check.graceful_goaway_handling`` to false. * adaptive concurrency: fixed a bug where concurrent requests on different worker threads could update minRTT back-to-back. * buffer: tighten network connection read and write buffer high watermarks in preparation to more careful enforcement of read limits. Buffer high-watermark is now set to the exact configured value; previously it was set to value + 1. -* cdn_loop: check that the entirety of the :ref:`cdn_id ` field is a valid CDN identifier. +* cdn_loop: check that the entirety of the :ref:`cdn_id ` field is a valid CDN identifier. * cds: fix blocking the update for a warming cluster when the update is the same as the active version. -* ext_authz: emit :ref:`CheckResponse.dynamic_metadata ` when the external authorization response has "Denied" check status. +* ext_authz: emit :ref:`CheckResponse.dynamic_metadata ` when the external authorization response has "Denied" check status. * fault injection: stop counting as active fault after delay elapsed. Previously fault injection filter continues to count the injected delay as an active fault even after it has elapsed. This produces incorrect output statistics and impacts the max number of consecutive faults allowed (e.g., for long-lived streams). This change decreases the active fault count when the delay fault is the only active and has gone finished. * filter_chain: fix filter chain matching with the server name as the case-insensitive way. -* grpc-web: fix local reply and non-proto-encoded gRPC response handling for small response bodies. This fix can be temporarily reverted by setting `envoy.reloadable_features.grpc_web_fix_non_proto_encoded_response_handling` to false. +* grpc-web: fix local reply and non-proto-encoded gRPC response handling for small response bodies. This fix can be temporarily reverted by setting ``envoy.reloadable_features.grpc_web_fix_non_proto_encoded_response_handling`` to false. * grpc_http_bridge: the downstream HTTP status is now correctly set for trailers-only responses from the upstream. -* header map: pick the right delimiter to append multiple header values to the same key. Previouly header with multiple values were coalesced with ",", after this fix cookie headers should be coalesced with " ;". This doesn't affect Http1 or Http2 requests because these 2 codecs coalesce cookie headers before adding it to header map. To revert to the old behavior, set the runtime feature `envoy.reloadable_features.header_map_correctly_coalesce_cookies` to false. +* header map: pick the right delimiter to append multiple header values to the same key. Previouly header with multiple values were coalesced with ",", after this fix cookie headers should be coalesced with " ;". This doesn't affect Http1 or Http2 requests because these 2 codecs coalesce cookie headers before adding it to header map. To revert to the old behavior, set the runtime feature ``envoy.reloadable_features.header_map_correctly_coalesce_cookies`` to false. * http: avoid grpc-status overwrite on when sending local replies if that field has already been set. -* http: disallowing "host:" in request_headers_to_add for behavioral consistency with rejecting :authority header. This behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_host_like_authority` to false. -* http: fixed an issue where Enovy did not handle peer stream limits correctly, and queued streams in nghttp2 rather than establish new connections. This behavior can be temporarily reverted by setting `envoy.reloadable_features.improved_stream_limit_handling` to false. -* http: fixed a bug where setting :ref:`MaxStreamDuration proto ` did not disable legacy timeout defaults. -* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. +* http: disallowing "host:" in request_headers_to_add for behavioral consistency with rejecting :authority header. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.treat_host_like_authority`` to false. +* http: fixed an issue where Enovy did not handle peer stream limits correctly, and queued streams in nghttp2 rather than establish new connections. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.improved_stream_limit_handling`` to false. +* http: fixed a bug where setting :ref:`MaxStreamDuration proto ` did not disable legacy timeout defaults. +* http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. * http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. -* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false. +* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting ``envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure`` to false. * jwt_authn: reject requests with a proper error if JWT has the wrong issuer when allow_missing is used. Before this change, the requests are accepted. * listener: prevent crashing when an unknown listener config proto is received and debug logging is enabled. * mysql_filter: improve the codec ability of mysql filter at connection phase, it can now decode MySQL5.7+ connection phase protocol packet. @@ -109,87 +109,86 @@ Bug Fixes Removed Config or Runtime ------------------------- -*Normally occurs at the end of the* :ref:`deprecation period ` +*Normally occurs at the end of the* :ref:`deprecation period ` -* access_logs: removed legacy unbounded access logs and runtime guard `envoy.reloadable_features.disallow_unbounded_access_logs`. -* dns: removed legacy buggy wildcard matching path and runtime guard `envoy.reloadable_features.fix_wildcard_matching`. -* dynamic_forward_proxy: removed `envoy.reloadable_features.enable_dns_cache_circuit_breakers` and legacy code path. -* http: removed legacy connect behavior and runtime guard `envoy.reloadable_features.stop_faking_paths`. -* http: removed legacy connection close behavior and runtime guard `envoy.reloadable_features.fixed_connection_close`. -* http: removed legacy HTTP/1.1 error reporting path and runtime guard `envoy.reloadable_features.early_errors_via_hcm`. -* http: removed legacy sanitization path for upgrade response headers and runtime guard `envoy.reloadable_features.fix_upgrade_response`. -* http: removed legacy date header overwriting logic and runtime guard `envoy.reloadable_features.preserve_upstream_date deprecation`. -* http: removed legacy ALPN handling and runtime guard `envoy.reloadable_features.http_default_alpn`. -* listener: removed legacy runtime guard `envoy.reloadable_features.listener_in_place_filterchain_update`. -* router: removed `envoy.reloadable_features.consume_all_retry_headers` and legacy code path. -* router: removed `envoy.reloadable_features.preserve_query_string_in_path_redirects` and legacy code path. +* access_logs: removed legacy unbounded access logs and runtime guard ``envoy.reloadable_features.disallow_unbounded_access_logs``. +* dns: removed legacy buggy wildcard matching path and runtime guard ``envoy.reloadable_features.fix_wildcard_matching``. +* dynamic_forward_proxy: removed ``envoy.reloadable_features.enable_dns_cache_circuit_breakers`` and legacy code path. +* http: removed legacy connect behavior and runtime guard ``envoy.reloadable_features.stop_faking_paths``. +* http: removed legacy connection close behavior and runtime guard ``envoy.reloadable_features.fixed_connection_close``. +* http: removed legacy HTTP/1.1 error reporting path and runtime guard ``envoy.reloadable_features.early_errors_via_hcm``. +* http: removed legacy sanitization path for upgrade response headers and runtime guard ``envoy.reloadable_features.fix_upgrade_response``. +* http: removed legacy date header overwriting logic and runtime guard ``envoy.reloadable_features.preserve_upstream_date deprecation``. +* http: removed legacy ALPN handling and runtime guard ``envoy.reloadable_features.http_default_alpn``. +* listener: removed legacy runtime guard ``envoy.reloadable_features.listener_in_place_filterchain_update``. +* router: removed ``envoy.reloadable_features.consume_all_retry_headers`` and legacy code path. +* router: removed ``envoy.reloadable_features.preserve_query_string_in_path_redirects`` and legacy code path. New Features ------------ -* access log: added a new :ref:`OpenTelemetry access logger ` extension, allowing a flexible log structure with native Envoy access log formatting. -* access log: added the new response flag `NC` for upstream cluster not found. The error flag is set when the http or tcp route is found for the request but the cluster is not available. -* access log: added the :ref:`formatters ` extension point for custom formatters (command operators). -* access log: added support for cross platform writing to :ref:`standard output ` and :ref:`standard error `. +* access log: added a new :ref:`OpenTelemetry access logger ` extension, allowing a flexible log structure with native Envoy access log formatting. +* access log: added the new response flag ``NC`` for upstream cluster not found. The error flag is set when the http or tcp route is found for the request but the cluster is not available. +* access log: added the :ref:`formatters ` extension point for custom formatters (command operators). +* access log: added support for cross platform writing to :ref:`standard output ` and :ref:`standard error `. * access log: support command operator: %FILTER_CHAIN_NAME% for the downstream tcp and http request. * access log: support command operator: %REQUEST_HEADERS_BYTES%, %RESPONSE_HEADERS_BYTES%, and %RESPONSE_TRAILERS_BYTES%. -* admin: added support for :ref:`access loggers ` to the admin interface. -* composite filter: added new :ref:`composite filter ` that can be used to instantiate different filter configuratios based on matching incoming data. -* compression: add brotli :ref:`compressor ` and :ref:`decompressor `. -* compression: extended the compression allow compressing when the content length header is not present. This behavior may be temporarily reverted by setting `envoy.reloadable_features.enable_compression_without_content_length_header` to false. -* config: add `envoy.features.fail_on_any_deprecated_feature` runtime key, which matches the behaviour of compile-time flag `ENVOY_DISABLE_DEPRECATED_FEATURES`, i.e. use of deprecated fields will cause a crash. -* config: the ``Node`` :ref:`dynamic context parameters ` are populated in discovery requests when set on the server instance. -* dispatcher: supports a stack of `Envoy::ScopeTrackedObject` instead of a single tracked object. This will allow Envoy to dump more debug information on crash. -* ext_authz: added :ref:`response_headers_to_add ` to support sending response headers to downstream clients on OK authorization checks via gRPC. -* ext_authz: added :ref:`allowed_client_headers_on_success ` to support sending response headers to downstream clients on OK external authorization checks via HTTP. -* grpc_json_transcoder: added :ref:`request_validation_options ` to reject invalid requests early. +* admin: added support for :ref:`access loggers ` to the admin interface. +* composite filter: added new :ref:`composite filter ` that can be used to instantiate different filter configuratios based on matching incoming data. +* compression: add brotli :ref:`compressor ` and :ref:`decompressor `. +* compression: extended the compression allow compressing when the content length header is not present. This behavior may be temporarily reverted by setting ``envoy.reloadable_features.enable_compression_without_content_length_header`` to false. +* config: add ``envoy.features.fail_on_any_deprecated_feature`` runtime key, which matches the behaviour of compile-time flag ``ENVOY_DISABLE_DEPRECATED_FEATURES``, i.e. use of deprecated fields will cause a crash. +* config: the ``Node`` :ref:`dynamic context parameters ` are populated in discovery requests when set on the server instance. +* dispatcher: supports a stack of ``Envoy::ScopeTrackedObject`` instead of a single tracked object. This will allow Envoy to dump more debug information on crash. +* ext_authz: added :ref:`response_headers_to_add ` to support sending response headers to downstream clients on OK authorization checks via gRPC. +* ext_authz: added :ref:`allowed_client_headers_on_success ` to support sending response headers to downstream clients on OK external authorization checks via HTTP. +* grpc_json_transcoder: added :ref:`request_validation_options ` to reject invalid requests early. * grpc_json_transcoder: filter can now be configured on per-route/per-vhost level as well. Leaving empty list of services in the filter configuration disables transcoding on the specific route. -* http: added support for `Envoy::ScopeTrackedObject` for HTTP/1 and HTTP/2 dispatching. Crashes while inside the dispatching loop should dump debug information. Furthermore, HTTP/1 and HTTP/2 clients now dumps the originating request whose response from the upstream caused Envoy to crash. -* http: added support for :ref:`preconnecting `. Preconnecting is off by default, but recommended for clusters serving latency-sensitive traffic, especially if using HTTP/1.1. -* http: added support for stream filters to mutate the cached route set by HCM route resolution. Useful for filters in a filter chain that want to override specific methods/properties of a route. See :ref:`http route mutation ` docs for more information. -* http: added new runtime config `envoy.reloadable_features.check_unsupported_typed_per_filter_config`, the default value is true. When the value is true, envoy will reject virtual host-specific typed per filter config when the filter doesn't support it. -* http: added the ability to preserve HTTP/1 header case across the proxy. See the :ref:`header casing ` documentation for more information. -* http: change frame flood and abuse checks to the upstream HTTP/2 codec to ON by default. It can be disabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to false. -* http: hash multiple header values instead of only hash the first header value. It can be disabled by setting the `envoy.reloadable_features.hash_multiple_header_values` runtime key to false. See the :ref:`HashPolicy's Header configuration ` for more information. -* json: introduced new JSON parser (https://github.com/nlohmann/json) to replace RapidJSON. The new parser is disabled by default. To test the new RapidJSON parser, enable the runtime feature `envoy.reloadable_features.remove_legacy_json`. -* kill_request: :ref:`Kill Request ` now supports bidirection killing. -* listener: added an optional :ref:`stat_prefix `. -* loadbalancer: added the ability to specify the hash_key for a host when using a consistent hashing loadbalancer (ringhash, maglev) using the :ref:`LbEndpoint.Metadata ` e.g.: ``"envoy.lb": {"hash_key": "..."}``. +* http: added support for ``Envoy::ScopeTrackedObject`` for HTTP/1 and HTTP/2 dispatching. Crashes while inside the dispatching loop should dump debug information. Furthermore, HTTP/1 and HTTP/2 clients now dumps the originating request whose response from the upstream caused Envoy to crash. +* http: added support for :ref:`preconnecting `. Preconnecting is off by default, but recommended for clusters serving latency-sensitive traffic, especially if using HTTP/1.1. +* http: added support for stream filters to mutate the cached route set by HCM route resolution. Useful for filters in a filter chain that want to override specific methods/properties of a route. See :ref:`http route mutation ` docs for more information. +* http: added new runtime config ``envoy.reloadable_features.check_unsupported_typed_per_filter_config``, the default value is true. When the value is true, envoy will reject virtual host-specific typed per filter config when the filter doesn't support it. +* http: added the ability to preserve HTTP/1 header case across the proxy. See the :ref:`header casing ` documentation for more information. +* http: change frame flood and abuse checks to the upstream HTTP/2 codec to ON by default. It can be disabled by setting the ``envoy.reloadable_features.upstream_http2_flood_checks`` runtime key to false. +* http: hash multiple header values instead of only hash the first header value. It can be disabled by setting the ``envoy.reloadable_features.hash_multiple_header_values`` runtime key to false. See the :ref:`HashPolicy's Header configuration ` for more information. +* json: introduced new JSON parser (https://github.com/nlohmann/json) to replace RapidJSON. The new parser is disabled by default. To test the new RapidJSON parser, enable the runtime feature ``envoy.reloadable_features.remove_legacy_json``. +* kill_request: :ref:`Kill Request ` now supports bidirection killing. +* listener: added an optional :ref:`stat_prefix `. +* loadbalancer: added the ability to specify the hash_key for a host when using a consistent hashing loadbalancer (ringhash, maglev) using the :ref:`LbEndpoint.Metadata ` e.g.: ``"envoy.lb": {"hash_key": "..."}``. * log: added a new custom flag ``%j`` to the log pattern to print the actual message to log as JSON escaped string. -* oauth filter: added the optional parameter :ref:`resources `. Set this value to add multiple "resource" parameters in the Authorization request sent to the OAuth provider. This acts as an identifier representing the protected resources the client is requesting a token for. -* original_dst: added support for :ref:`Original Destination ` on Windows. This enables the use of Envoy as a sidecar proxy on Windows. -* overload: add support for scaling :ref:`transport connection timeouts`. This can be used to reduce the TLS handshake timeout in response to overload. -* postgres: added ability to :ref:`terminate SSL`. -* rbac: added :ref:`shadow_rules_stat_prefix ` to allow adding custom prefix to the stats emitted by shadow rules. -* route config: added :ref:`allow_post field ` for allowing POST payload as raw TCP. -* route config: added :ref:`max_direct_response_body_size_bytes ` to set maximum :ref:`direct response body ` size in bytes. If not specified the default remains 4096 bytes. -* server: added *fips_mode* to :ref:`server compilation settings ` related statistic. +* oauth filter: added the optional parameter :ref:`resources `. Set this value to add multiple "resource" parameters in the Authorization request sent to the OAuth provider. This acts as an identifier representing the protected resources the client is requesting a token for. +* original_dst: added support for :ref:`Original Destination ` on Windows. This enables the use of Envoy as a sidecar proxy on Windows. +* overload: add support for scaling :ref:`transport connection timeouts `. This can be used to reduce the TLS handshake timeout in response to overload. +* postgres: added ability to :ref:`terminate SSL `. +* rbac: added :ref:`shadow_rules_stat_prefix ` to allow adding custom prefix to the stats emitted by shadow rules. +* route config: added :ref:`allow_post field ` for allowing POST payload as raw TCP. +* route config: added :ref:`max_direct_response_body_size_bytes ` to set maximum :ref:`direct response body ` size in bytes. If not specified the default remains 4096 bytes. +* server: added *fips_mode* to :ref:`server compilation settings ` related statistic. * server: added :option:`--enable-core-dump` flag to enable core dumps via prctl (Linux-based systems only). -* tcp_proxy: add support for converting raw TCP streams into HTTP/1.1 CONNECT requests. See :ref:`upgrade documentation ` for details. -* tcp_proxy: added a :ref:`use_post field ` for using HTTP POST to proxy TCP streams. -* tcp_proxy: added a :ref:`headers_to_add field ` for setting additional headers to the HTTP requests for TCP proxing. -* thrift_proxy: added a :ref:`max_requests_per_connection field ` for setting maximum requests for per downstream connection. -* thrift_proxy: added per upstream metrics within the :ref:`thrift router ` for messagetype counters in request/response. -* thrift_proxy: added per upstream metrics within the :ref:`thrift router ` for request time histograms. -* tls peer certificate validation: added :ref:`SPIFFE validator ` for supporting isolated multiple trust bundles in a single listener or cluster. -* tracing: added the :ref:`pack_trace_reason ` - field as well as explicit configuration for the built-in :ref:`UuidRequestIdConfig ` +* tcp_proxy: add support for converting raw TCP streams into HTTP/1.1 CONNECT requests. See :ref:`upgrade documentation ` for details. +* tcp_proxy: added a :ref:`use_post field ` for using HTTP POST to proxy TCP streams. +* tcp_proxy: added a :ref:`headers_to_add field ` for setting additional headers to the HTTP requests for TCP proxing. +* thrift_proxy: added a :ref:`max_requests_per_connection field ` for setting maximum requests for per downstream connection. +* thrift_proxy: added per upstream metrics within the :ref:`thrift router ` for messagetype counters in request/response. +* thrift_proxy: added per upstream metrics within the :ref:`thrift router ` for request time histograms. +* tls peer certificate validation: added :ref:`SPIFFE validator ` for supporting isolated multiple trust bundles in a single listener or cluster. +* tracing: added the :ref:`pack_trace_reason ` + field as well as explicit configuration for the built-in :ref:`UuidRequestIdConfig ` request ID implementation. See the trace context propagation :ref:`architecture overview - ` for more information. -* udp: added :ref:`downstream ` and - :ref:`upstream ` statistics for dropped datagrams. -* udp: added :ref:`downstream_socket_config ` + ` for more information. +* udp: added :ref:`downstream ` and + :ref:`upstream ` statistics for dropped datagrams. +* udp: added :ref:`downstream_socket_config ` listener configuration to allow configuration of downstream max UDP datagram size. Also added - :ref:`upstream_socket_config ` + :ref:`upstream_socket_config ` UDP proxy configuration to allow configuration of upstream max UDP datagram size. The defaults for both remain 1500 bytes. * udp: added configuration for :ref:`GRO - `. The default is disabled for - :ref:`downstream sockets ` - and enabled for :ref:`upstream sockets `. + `. The default is disabled for + :ref:`downstream sockets ` + and enabled for :ref:`upstream sockets `. Deprecated ---------- -* admin: :ref:`access_log_path ` is deprecated in favor for :ref:`access loggers `. - +* admin: :ref:`access_log_path ` is deprecated in favor for :ref:`access loggers `. diff --git a/docs/root/version_history/v1.2.0.rst b/docs/root/version_history/v1.2.0.rst index 71e91e37f4b88..eb54093f69063 100644 --- a/docs/root/version_history/v1.2.0.rst +++ b/docs/root/version_history/v1.2.0.rst @@ -4,27 +4,27 @@ Changes ------- -* :ref:`Cluster discovery service (CDS) API `. -* :ref:`Outlier detection ` (passive health checking). +* :ref:`Cluster discovery service (CDS) API `. +* :ref:`Outlier detection ` (passive health checking). * Envoy configuration is now checked against a JSON schema. -* :ref:`Ring hash ` consistent load balancer, as well as HTTP +* :ref:`Ring hash ` consistent load balancer, as well as HTTP consistent hash routing based on a policy. -* Vastly :ref:`enhanced global rate limit configuration ` via the HTTP +* Vastly :ref:`enhanced global rate limit configuration ` via the HTTP rate limiting filter. * HTTP routing to a cluster retrieved from a header. * Weighted cluster HTTP routing. * Auto host rewrite during HTTP routing. * Regex header matching during HTTP routing. * HTTP access log runtime filter. -* LightStep tracer :ref:`parent/child span association `. -* :ref:`Route discovery service (RDS) API `. +* LightStep tracer :ref:`parent/child span association `. +* :ref:`Route discovery service (RDS) API `. * HTTP router :ref:`x-envoy-upstream-rq-timeout-alt-response header - ` support. -* *use_original_dst* and *bind_to_port* :ref:`listener options ` (useful for + ` support. +* *use_original_dst* and *bind_to_port* :ref:`listener options ` (useful for iptables based transparent proxy support). -* TCP proxy filter :ref:`route table support `. +* TCP proxy filter :ref:`route table support `. * Configurable stats flush interval. -* Various :ref:`third party library upgrades `, including using BoringSSL as +* Various :ref:`third party library upgrades `, including using BoringSSL as the default SSL provider. * No longer maintain closed HTTP/2 streams for priority calculations. Leads to substantial memory savings for large meshes. diff --git a/docs/root/version_history/v1.3.0.rst b/docs/root/version_history/v1.3.0.rst index 067ea4ee76274..234be4dbacad3 100644 --- a/docs/root/version_history/v1.3.0.rst +++ b/docs/root/version_history/v1.3.0.rst @@ -5,62 +5,62 @@ Changes ------- * As of this release, we now have an official :repo:`breaking change policy - `. Note that there are numerous breaking configuration + `. Note that there are numerous breaking configuration changes in this release. They are not listed here. Future releases will adhere to the policy and have clear documentation on deprecations and changes. * Bazel is now the canonical build system (replacing CMake). There have been a huge number of changes to the development/build/test flow. See :repo:`/bazel/README.md` and :repo:`/ci/README.md` for more information. -* :ref:`Outlier detection ` has been expanded to include success +* :ref:`Outlier detection ` has been expanded to include success rate variance, and all parameters are now configurable in both runtime and in the JSON configuration. * TCP level listener and cluster connections now have configurable receive buffer limits at which point connection level back pressure is applied. Full end to end flow control will be available in a future release. -* :ref:`Redis health checking ` has been added as an active +* :ref:`Redis health checking ` has been added as an active health check type. Full Redis support will be documented/supported in 1.4.0. -* :ref:`TCP health checking ` now supports a +* :ref:`TCP health checking ` now supports a "connect only" mode that only checks if the remote server can be connected to without writing/reading any data. * `BoringSSL `_ is now the only supported TLS provider. The default cipher suites and ECDH curves have been updated with more modern defaults for both listener and cluster connections. -* The `header value match` rate limit action has been expanded to include an `expect - match` parameter. +* The ``header value match`` rate limit action has been expanded to include an ``expect + match`` parameter. * Route level HTTP rate limit configurations now do not inherit the virtual host level - configurations by default. Use `include_vh_rate_limits` to inherit the virtual host + configurations by default. Use ``include_vh_rate_limits`` to inherit the virtual host level options if desired. * HTTP routes can now add request headers on a per route and per virtual host basis via the - :ref:`request_headers_to_add ` option. -* The :ref:`example configurations ` have been refreshed to demonstrate the + :ref:`request_headers_to_add ` option. +* The :ref:`example configurations ` have been refreshed to demonstrate the latest features. -* `per_try_timeout_ms` can now be configured in +* ``per_try_timeout_ms`` can now be configured in a route's retry policy in addition to via the :ref:`x-envoy-upstream-rq-per-try-timeout-ms - ` HTTP header. -* HTTP virtual host matching now includes support for prefix wildcard domains (e.g., `*.lyft.com`). + ` HTTP header. +* HTTP virtual host matching now includes support for prefix wildcard domains (e.g., ``*.lyft.com``). * The default for tracing random sampling has been changed to 100% and is still configurable in - :ref:`runtime `. + :ref:`runtime `. * HTTP tracing configuration has been extended to allow tags to be populated from arbitrary HTTP headers. -* The :ref:`HTTP rate limit filter ` can now be applied to internal, - external, or all requests via the `request_type` option. -* :ref:`Listener binding ` now requires specifying an `address` field. This can be +* The :ref:`HTTP rate limit filter ` can now be applied to internal, + external, or all requests via the ``request_type`` option. +* :ref:`Listener binding ` now requires specifying an `address` field. This can be used to bind a listener to both a specific address as well as a port. -* The :ref:`MongoDB filter ` now emits a stat for queries that - do not have `$maxTimeMS` set. -* The :ref:`MongoDB filter ` now emits logs that are fully valid +* The :ref:`MongoDB filter ` now emits a stat for queries that + do not have ``$maxTimeMS`` set. +* The :ref:`MongoDB filter ` now emits logs that are fully valid JSON. * The CPU profiler output path is now configurable. * A watchdog system has been added that can kill the server if a deadlock is detected. -* A :ref:`route table checking tool ` has been added that can +* A :ref:`route table checking tool ` has been added that can be used to test route tables before use. -* We have added an :ref:`example repo ` that shows how to compile/link a custom filter. +* We have added an :ref:`example repo ` that shows how to compile/link a custom filter. * Added additional cluster wide information related to outlier detection to the :ref:`/clusters - admin endpoint `. -* Multiple SANs can now be verified via the `verify_subject_alt_name` setting. + admin endpoint `. +* Multiple SANs can now be verified via the ``verify_subject_alt_name`` setting. Additionally, URI type SANs can be verified. * HTTP filters can now be passed opaque configuration specified on a per route basis. * By default Envoy now has a built in crash handler that will print a back trace. This behavior can be disabled if desired via the ``--define=signal_trace=disabled`` Bazel option. -* Zipkin has been added as a supported :ref:`tracing provider `. +* Zipkin has been added as a supported :ref:`tracing provider `. * Numerous small changes and fixes not listed here. diff --git a/docs/root/version_history/v1.4.0.rst b/docs/root/version_history/v1.4.0.rst index fb81055386273..3342e4faee549 100644 --- a/docs/root/version_history/v1.4.0.rst +++ b/docs/root/version_history/v1.4.0.rst @@ -4,66 +4,66 @@ Changes ------- -* macOS is :repo:`now supported `. (A few features +* macOS is :repo:`now supported `. (A few features are missing such as hot restart and original destination routing). * YAML is now directly supported for config files. * Added /routes admin endpoint. * End-to-end flow control is now supported for TCP proxy, HTTP/1, and HTTP/2. HTTP flow control that includes filter buffering is incomplete and will be implemented in 1.5.0. -* Log verbosity :repo:`compile time flag ` added. -* Hot restart :repo:`compile time flag ` added. -* Original destination :ref:`cluster ` - and :ref:`load balancer ` added. -* :ref:`WebSocket ` is now supported. +* Log verbosity :repo:`compile time flag ` added. +* Hot restart :repo:`compile time flag ` added. +* Original destination :ref:`cluster ` + and :ref:`load balancer ` added. +* :ref:`WebSocket ` is now supported. * Virtual cluster priorities have been hard removed without deprecation as we are reasonably sure no one is using this feature. -* Route `validate_clusters` option added. -* :ref:`x-envoy-downstream-service-node ` +* Route ``validate_clusters`` option added. +* :ref:`x-envoy-downstream-service-node ` header added. -* :ref:`x-forwarded-client-cert ` header +* :ref:`x-forwarded-client-cert ` header added. * Initial HTTP/1 forward proxy support for absolute URLs has been added. * HTTP/2 codec settings are now configurable. -* gRPC/JSON transcoder :ref:`filter ` added. -* gRPC web :ref:`filter ` added. +* gRPC/JSON transcoder :ref:`filter ` added. +* gRPC web :ref:`filter ` added. * Configurable timeout for the rate limit service call in the :ref:`network - ` and :ref:`HTTP ` rate limit + ` and :ref:`HTTP ` rate limit filters. -* :ref:`x-envoy-retry-grpc-on ` header added. -* :ref:`LDS API ` added. -* TLS :`require_client_certificate` option added. -* :ref:`Configuration check tool ` added. -* :ref:`JSON schema check tool ` added. +* :ref:`x-envoy-retry-grpc-on ` header added. +* :ref:`LDS API ` added. +* TLS :``require_client_certificate`` option added. +* :ref:`Configuration check tool ` added. +* :ref:`JSON schema check tool ` added. * Config validation mode added via the :option:`--mode` option. * :option:`--local-address-ip-version` option added. * IPv6 support is now complete. -* UDP `statsd_ip_address` option added. +* UDP ``statsd_ip_address`` option added. * Per-cluster DNS resolvers added. -* :ref:`Fault filter ` enhancements and fixes. -* Several features are :ref:`deprecated as of the 1.4.0 release `. They +* :ref:`Fault filter ` enhancements and fixes. +* Several features are `deprecated as of the 1.4.0 release `_. They will be removed at the beginning of the 1.5.0 release cycle. We explicitly call out that the - `HttpFilterConfigFactory` filter API has been deprecated in favor of - `NamedHttpFilterConfigFactory`. + ``HttpFilterConfigFactory`` filter API has been deprecated in favor of + ``NamedHttpFilterConfigFactory``. * Many small bug fixes and performance improvements not listed. Deprecated ---------- -* Config option `statsd_local_udp_port` has been deprecated and has been replaced with - `statsd_udp_ip_address`. -* `HttpFilterConfigFactory` filter API has been deprecated in favor of `NamedHttpFilterConfigFactory`. -* Config option `http_codec_options` has been deprecated and has been replaced with `http2_settings`. -* The following log macros have been deprecated: `log_trace`, `log_debug`, `conn_log`, - `conn_log_info`, `conn_log_debug`, `conn_log_trace`, `stream_log`, `stream_log_info`, - `stream_log_debug`, `stream_log_trace`. For replacements, please see +* Config option ``statsd_local_udp_port`` has been deprecated and has been replaced with + ``statsd_udp_ip_address``. +* ``HttpFilterConfigFactory`` filter API has been deprecated in favor of ``NamedHttpFilterConfigFactory``. +* Config option ``http_codec_options`` has been deprecated and has been replaced with ``http2_settings``. +* The following log macros have been deprecated: ``log_trace``, ``log_debug``, ``conn_log``, + ``conn_log_info``, ``conn_log_debug``, ``conn_log_trace``, ``stream_log``, ``stream_log_info``, + ``stream_log_debug``, ``stream_log_trace``. For replacements, please see `logger.h `_. * The connectionId() and ssl() callbacks of StreamFilterCallbacks have been deprecated and replaced with a more general connection() callback, which, when not returning a nullptr, can be used to get the connection id and SSL connection from the returned Connection object pointer. -* The protobuf stub gRPC support via `Grpc::RpcChannelImpl` is now replaced with `Grpc::AsyncClientImpl`. - This no longer uses `protoc` generated stubs but instead utilizes C++ template generation of the - RPC stubs. `Grpc::AsyncClientImpl` supports streaming, in addition to the previous unary, RPCs. +* The protobuf stub gRPC support via ``Grpc::RpcChannelImpl`` is now replaced with ``Grpc::AsyncClientImpl``. + This no longer uses ``protoc`` generated stubs but instead utilizes C++ template generation of the + RPC stubs. ``Grpc::AsyncClientImpl`` supports streaming, in addition to the previous unary, RPCs. * The direction of network and HTTP filters in the configuration will be ignored from 1.4.0 and later removed from the configuration in the v2 APIs. Filter direction is now implied at the C++ type - level. The `type()` methods on the `NamedNetworkFilterConfigFactory` and - `NamedHttpFilterConfigFactory` interfaces have been removed to reflect this. + level. The ``type()`` methods on the ``NamedNetworkFilterConfigFactory`` and + ``NamedHttpFilterConfigFactory`` interfaces have been removed to reflect this. diff --git a/docs/root/version_history/v1.5.0.rst b/docs/root/version_history/v1.5.0.rst index 112019390d037..58e1d3147f7b9 100644 --- a/docs/root/version_history/v1.5.0.rst +++ b/docs/root/version_history/v1.5.0.rst @@ -5,75 +5,75 @@ Changes ------- * access log: added fields for :ref:`UPSTREAM_LOCAL_ADDRESS and DOWNSTREAM_ADDRESS - `. -* admin: added :ref:`JSON output ` for stats admin endpoint. -* admin: added basic :ref:`Prometheus output ` for stats admin + `. +* admin: added :ref:`JSON output ` for stats admin endpoint. +* admin: added basic :ref:`Prometheus output ` for stats admin endpoint. Histograms are not currently output. -* admin: added ``version_info`` to the :ref:`/clusters admin endpoint`. -* config: the :ref:`v2 API ` is now considered production ready. +* admin: added ``version_info`` to the :ref:`/clusters admin endpoint `. +* config: the :ref:`v2 API ` is now considered production ready. * config: added --v2-config-only CLI flag. -* cors: added :ref:`CORS filter `. +* cors: added :ref:`CORS filter `. * health check: added :ref:`x-envoy-immediate-health-check-fail - ` header support. -* health check: added :ref:`reuse_connection ` option. -* http: added :ref:`per-listener stats `. + ` header support. +* health check: added :ref:`reuse_connection ` option. +* http: added :ref:`per-listener stats `. * http: end-to-end HTTP flow control is now complete across both connections, streams, and filters. -* load balancer: added :ref:`subset load balancer `. +* load balancer: added :ref:`subset load balancer `. * load balancer: added ring size and hash :ref:`configuration options - `. This used to be configurable via runtime. The runtime + `. This used to be configurable via runtime. The runtime configuration was deleted without deprecation as we are fairly certain no one is using it. * log: added the ability to optionally log to a file instead of stderr via the :option:`--log-path` option. -* listeners: added :ref:`drain_type ` option. -* lua: added experimental :ref:`Lua filter `. -* mongo filter: added :ref:`fault injection `. -* mongo filter: added :ref:`"drain close" ` support. -* outlier detection: added :ref:`HTTP gateway failure type `. - See :ref:`deprecated log ` +* listeners: added :ref:`drain_type ` option. +* lua: added experimental :ref:`Lua filter `. +* mongo filter: added :ref:`fault injection `. +* mongo filter: added :ref:`"drain close" ` support. +* outlier detection: added :ref:`HTTP gateway failure type `. + See `deprecated log `_ for outlier detection stats deprecations in this release. -* redis: the :ref:`redis proxy filter ` is now considered +* redis: the :ref:`redis proxy filter ` is now considered production ready. -* redis: added :ref:`"drain close" ` functionality. -* router: added :ref:`x-envoy-overloaded ` support. -* router: added :ref:`regex ` route matching. -* router: added :ref:`custom request headers ` +* redis: added :ref:`"drain close" ` functionality. +* router: added :ref:`x-envoy-overloaded ` support. +* router: added :ref:`regex ` route matching. +* router: added :ref:`custom request headers ` for upstream requests. * router: added :ref:`downstream IP hashing - ` for HTTP ketama routing. -* router: added :ref:`cookie hashing `. -* router: added :ref:`start_child_span ` option + ` for HTTP ketama routing. +* router: added :ref:`cookie hashing `. +* router: added :ref:`start_child_span ` option to create child span for egress calls. -* router: added optional :ref:`upstream logs `. +* router: added optional :ref:`upstream logs `. * router: added complete :ref:`custom append/override/remove support - ` of request/response headers. + ` of request/response headers. * router: added support to :ref:`specify response code during redirect - `. -* router: added :ref:`configuration ` + `. +* router: added :ref:`configuration ` to return either a 404 or 503 if the upstream cluster does not exist. -* runtime: added :ref:`comment capability `. -* server: change default log level (:option:`-l`) to `info`. +* runtime: added :ref:`comment capability `. +* server: change default log level (:option:`-l`) to ``info``. * stats: maximum stat/name sizes and maximum number of stats are now variable via the - `--max-obj-name-len` and `--max-stats` options. -* tcp proxy: added :ref:`access logging `. + ``--max-obj-name-len`` and ``--max-stats`` options. +* tcp proxy: added :ref:`access logging `. * tcp proxy: added :ref:`configurable connect retries - `. -* tcp proxy: enable use of :ref:`outlier detector `. -* tls: added :ref:`SNI support `. + `. +* tcp proxy: enable use of :ref:`outlier detector `. +* tls: added :ref:`SNI support `. * tls: added support for specifying :ref:`TLS session ticket keys - `. + `. * tls: allow configuration of the :ref:`min - ` and :ref:`max - ` TLS protocol versions. -* tracing: added :ref:`custom trace span decorators `. + ` and :ref:`max + ` TLS protocol versions. +* tracing: added :ref:`custom trace span decorators `. * Many small bug fixes and performance improvements not listed. Deprecated ---------- -* The outlier detection `ejections_total` stats counter has been deprecated and not replaced. Monitor - the individual `ejections_detected_*` counters for the detectors of interest, or - `ejections_enforced_total` for the total number of ejections that actually occurred. -* The outlier detection `ejections_consecutive_5xx` stats counter has been deprecated in favour of - `ejections_detected_consecutive_5xx` and `ejections_enforced_consecutive_5xx`. -* The outlier detection `ejections_success_rate` stats counter has been deprecated in favour of - `ejections_detected_success_rate` and `ejections_enforced_success_rate`. +* The outlier detection ``ejections_total`` stats counter has been deprecated and not replaced. Monitor + the individual ``ejections_detected_*`` counters for the detectors of interest, or + ``ejections_enforced_total`` for the total number of ejections that actually occurred. +* The outlier detection ``ejections_consecutive_5xx`` stats counter has been deprecated in favour of + ``ejections_detected_consecutive_5xx`` and ``ejections_enforced_consecutive_5xx``. +* The outlier detection ``ejections_success_rate`` stats counter has been deprecated in favour of + ``ejections_detected_success_rate`` and ``ejections_enforced_success_rate``. diff --git a/docs/root/version_history/v1.6.0.rst b/docs/root/version_history/v1.6.0.rst index 879eb2f8df771..406ca33b4da9e 100644 --- a/docs/root/version_history/v1.6.0.rst +++ b/docs/root/version_history/v1.6.0.rst @@ -5,121 +5,121 @@ Changes ------- * access log: added DOWNSTREAM_REMOTE_ADDRESS, DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT, and - DOWNSTREAM_LOCAL_ADDRESS :ref:`access log formatters `. + DOWNSTREAM_LOCAL_ADDRESS :ref:`access log formatters `. DOWNSTREAM_ADDRESS access log formatter has been deprecated. * access log: added less than or equal (LE) :ref:`comparison filter - `. + `. * access log: added configuration to :ref:`runtime filter - ` to set default sampling rate, divisor, + ` to set default sampling rate, divisor, and whether to use independent randomness or not. -* admin: added :ref:`/runtime ` admin endpoint to read the +* admin: added :ref:`/runtime ` admin endpoint to read the current runtime values. * build: added support for :repo:`building Envoy with exported symbols - `. This change allows scripts loaded with the Lua filter to + `. This change allows scripts loaded with the Lua filter to load shared object libraries such as those installed via `LuaRocks `_. * config: added support for sending error details as `grpc.rpc.Status `_ - in :ref:`DiscoveryRequest `. -* config: added support for :ref:`inline delivery ` of TLS + in :ref:`DiscoveryRequest `. +* config: added support for :ref:`inline delivery ` of TLS certificates and private keys. -* config: added restrictions for the backing :ref:`config sources ` +* config: added restrictions for the backing :ref:`config sources ` of xDS resources. For filesystem based xDS the file must exist at configuration time. For cluster based xDS the backing cluster must be statically defined and be of non-EDS type. * grpc: the Google gRPC C++ library client is now supported as specified in the :ref:`gRPC services - overview ` and :ref:`GrpcService `. + overview ` and :ref:`GrpcService `. * grpc-json: added support for :ref:`inline descriptors - `. -* health check: added :ref:`gRPC health check ` + `. +* health check: added :ref:`gRPC health check ` based on `grpc.health.v1.Health `_ service. * health check: added ability to set :ref:`host header value - ` for http health check. + ` for http health check. * health check: extended the health check filter to support computation of the health check response based on the :ref:`percentage of healthy servers in upstream clusters - `. + `. * health check: added setting for :ref:`no-traffic - interval`. + interval `. * http: added idle timeout for :ref:`upstream http connections - `. + `. * http: added support for :ref:`proxying 100-Continue responses - `. + `. * http: added the ability to pass a URL encoded PEM encoded peer certificate in the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header. * http: added support for trusting additional hops in the :ref:`config_http_conn_man_headers_x-forwarded-for` request header. * http: added support for :ref:`incoming HTTP/1.0 - `. + `. * hot restart: added SIGTERM propagation to children to :ref:`hot-restarter.py - `, which enables using it as a parent of containers. -* ip tagging: added :ref:`HTTP IP Tagging filter`. + `, which enables using it as a parent of containers. +* ip tagging: added :ref:`HTTP IP Tagging filter `. * listeners: added support for :ref:`listening for both IPv4 and IPv6 - ` when binding to ::. + ` when binding to ::. * listeners: added support for listening on :ref:`UNIX domain sockets - `. -* listeners: added support for :ref:`abstract unix domain sockets ` on + `. +* listeners: added support for :ref:`abstract unix domain sockets ` on Linux. The abstract namespace can be used by prepending '@' to a socket path. * load balancer: added cluster configuration for :ref:`healthy panic threshold - ` percentage. -* load balancer: added :ref:`Maglev ` consistent hash + ` percentage. +* load balancer: added :ref:`Maglev ` consistent hash load balancer. * load balancer: added support for - :ref:`LocalityLbEndpoints` priorities. -* lua: added headers :ref:`replace() ` API. -* lua: extended to support :ref:`metadata object ` API. -* redis: added local `PING` support to the :ref:`Redis filter `. -* redis: added `GEORADIUS_RO` and `GEORADIUSBYMEMBER_RO` to the :ref:`Redis command splitter - ` allowlist. + :ref:`LocalityLbEndpoints ` priorities. +* lua: added headers :ref:`replace() ` API. +* lua: extended to support :ref:`metadata object ` API. +* redis: added local `PING` support to the :ref:`Redis filter `. +* redis: added ``GEORADIUS_RO`` and ``GEORADIUSBYMEMBER_RO`` to the :ref:`Redis command splitter + ` allowlist. * router: added DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT, DOWNSTREAM_LOCAL_ADDRESS, DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT, PROTOCOL, and UPSTREAM_METADATA :ref:`header - formatters `. The CLIENT_IP header formatter + formatters `. The CLIENT_IP header formatter has been deprecated. -* router: added gateway-error :ref:`retry-on ` policy. +* router: added gateway-error :ref:`retry-on ` policy. * router: added support for route matching based on :ref:`URL query string parameters - `. + `. * router: added support for more granular weighted cluster routing by allowing the :ref:`total_weight - ` to be specified in configuration. + ` to be specified in configuration. * router: added support for :ref:`custom request/response headers - ` with mixed static and dynamic values. -* router: added support for :ref:`direct responses `. + ` with mixed static and dynamic values. +* router: added support for :ref:`direct responses `. I.e., sending a preconfigured HTTP response without proxying anywhere. * router: added support for :ref:`HTTPS redirects - ` on specific routes. + ` on specific routes. * router: added support for :ref:`prefix_rewrite - ` for redirects. + ` for redirects. * router: added support for :ref:`stripping the query string - ` for redirects. + ` for redirects. * router: added support for downstream request/upstream response - :ref:`header manipulation ` in :ref:`weighted - cluster `. + :ref:`header manipulation ` in :ref:`weighted + cluster `. * router: added support for :ref:`range based header matching - ` for request routing. -* squash: added support for the :ref:`Squash microservices debugger `. + ` for request routing. +* squash: added support for the :ref:`Squash microservices debugger `. Allows debugging an incoming request to a microservice in the mesh. * stats: added metrics service API implementation. -* stats: added native :ref:`DogStatsd ` support. +* stats: added native :ref:`DogStatsd ` support. * stats: added support for :ref:`fixed stats tag values - ` which will be added to all metrics. + ` which will be added to all metrics. * tcp proxy: added support for specifying a :ref:`metadata matcher - ` for upstream + ` for upstream clusters in the tcp filter. * tcp proxy: improved TCP proxy to correctly proxy TCP half-close. * tcp proxy: added :ref:`idle timeout - `. + `. * tcp proxy: access logs now bring an IP address without a port when using DOWNSTREAM_ADDRESS. - Use :ref:`DOWNSTREAM_REMOTE_ADDRESS ` instead. + Use :ref:`DOWNSTREAM_REMOTE_ADDRESS ` instead. * tracing: added support for dynamically loading an :ref:`OpenTracing tracer - `. + `. * tracing: when using the Zipkin tracer, it is now possible for clients to specify the sampling - decision (using the :ref:`x-b3-sampled ` header) and + decision (using the :ref:`x-b3-sampled ` header) and have the decision propagated through to subsequently invoked services. * tracing: when using the Zipkin tracer, it is no longer necessary to propagate the - :ref:`x-ot-span-context ` header. - See more on trace context propagation :ref:`here `. + :ref:`x-ot-span-context ` header. + See more on trace context propagation :ref:`here `. * transport sockets: added transport socket interface to allow custom implementations of transport sockets. A transport socket provides read and write logic with buffer encryption and decryption (if applicable). The existing TLS implementation has been refactored with the interface. * upstream: added support for specifying an :ref:`alternate stats name - ` while emitting stats for clusters. + ` while emitting stats for clusters. * Many small bug fixes and performance improvements not listed. Deprecated @@ -130,5 +130,5 @@ Deprecated * CLIENT_IP header formatter is deprecated. Use DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT instead. * 'use_original_dst' field in the v2 LDS API is deprecated. Use listener filters and filter chain matching instead. -* `value` and `regex` fields in the `HeaderMatcher` message is deprecated. Use the `exact_match` - or `regex_match` oneof instead. +* ``value`` and ``regex`` fields in the ``HeaderMatcher`` message is deprecated. Use the ``exact_match`` + or ``regex_match`` oneof instead. diff --git a/docs/root/version_history/v1.7.0.rst b/docs/root/version_history/v1.7.0.rst index 64949cc9e0e93..8be132e66643c 100644 --- a/docs/root/version_history/v1.7.0.rst +++ b/docs/root/version_history/v1.7.0.rst @@ -6,154 +6,154 @@ Changes * access log: added ability to log response trailers. * access log: added ability to format START_TIME. -* access log: added DYNAMIC_METADATA :ref:`access log formatter `. -* access log: added :ref:`HeaderFilter ` +* access log: added DYNAMIC_METADATA :ref:`access log formatter `. +* access log: added :ref:`HeaderFilter ` to filter logs based on request headers. -* access log: added `%([1-9])?f` as one of START_TIME specifiers to render subseconds. +* access log: added ``%([1-9])?f`` as one of START_TIME specifiers to render subseconds. * access log: gRPC Access Log Service (ALS) support added for :ref:`HTTP access logs - `. + `. * access log: improved WebSocket logging. * admin: added :http:get:`/config_dump` for dumping the current configuration and associated xDS version information (if applicable). * admin: added :http:get:`/clusters?format=json` for outputing a JSON-serialized proto detailing the current status of all clusters. * admin: added :http:get:`/stats/prometheus` as an alternative endpoint for getting stats in prometheus format. -* admin: added :ref:`/runtime_modify endpoint ` to add or change runtime values. +* admin: added :ref:`/runtime_modify endpoint ` to add or change runtime values. * admin: mutations must be sent as POSTs, rather than GETs. Mutations include: :http:post:`/cpuprofiler`, :http:post:`/healthcheck/fail`, :http:post:`/healthcheck/ok`, :http:post:`/logging`, :http:post:`/quitquitquit`, :http:post:`/reset_counters`, :http:post:`/runtime_modify?key1=value1&key2=value2&keyN=valueN`. -* admin: removed `/routes` endpoint; route configs can now be found at the :ref:`/config_dump endpoint `. +* admin: removed ``/routes`` endpoint; route configs can now be found at the :ref:`/config_dump endpoint `. * buffer filter: the buffer filter can be optionally - :ref:`disabled ` or - :ref:`overridden ` with + :ref:`disabled ` or + :ref:`overridden ` with route-local configuration. * cli: added --config-yaml flag to the Envoy binary. When set its value is interpreted as a yaml representation of the bootstrap config and overrides --config-path. -* cluster: added :ref:`option ` +* cluster: added :ref:`option ` to close tcp_proxy upstream connections when health checks fail. -* cluster: added :ref:`option ` to drain +* cluster: added :ref:`option ` to drain connections from hosts after they are removed from service discovery, regardless of health status. * cluster: fixed bug preventing the deletion of all endpoints in a priority * debug: added symbolized stack traces (where supported) * ext-authz filter: added support to raw HTTP authorization. * ext-authz filter: added support to gRPC responses to carry HTTP attributes. * grpc: support added for the full set of :ref:`Google gRPC call credentials - `. -* gzip filter: added :ref:`stats ` to the filter. + `. +* gzip filter: added :ref:`stats ` to the filter. * gzip filter: sending *accept-encoding* header as *identity* no longer compresses the payload. * health check: added ability to set :ref:`additional HTTP headers - ` for HTTP health check. + ` for HTTP health check. * health check: added support for EDS delivered :ref:`endpoint health status - `. + `. * health check: added interval overrides for health state transitions from :ref:`healthy to unhealthy - `, :ref:`unhealthy to healthy - ` and for subsequent checks on - :ref:`unhealthy hosts `. -* health check: added support for :ref:`custom health check `. + `, :ref:`unhealthy to healthy + ` and for subsequent checks on + :ref:`unhealthy hosts `. +* health check: added support for :ref:`custom health check `. * health check: health check connections can now be configured to use http/2. * health check http filter: added - :ref:`generic header matching ` + :ref:`generic header matching ` to trigger health check response. Deprecated the endpoint option. * http: filters can now optionally support - :ref:`virtual host `, - :ref:`route `, and - :ref:`weighted cluster ` + :ref:`virtual host `, + :ref:`route `, and + :ref:`weighted cluster ` local configuration. * http: added the ability to pass DNS type Subject Alternative Names of the client certificate in the - :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header. + :ref:`v1.7.0:config_http_conn_man_headers_x-forwarded-client-cert` header. * http: local responses to gRPC requests are now sent as trailers-only gRPC responses instead of plain HTTP responses. Notably the HTTP response code is always "200" in this case, and the gRPC error code is carried in "grpc-status" header, optionally accompanied with a text message in "grpc-message" header. * http: added support for :ref:`via header - ` + ` append. * http: added a :ref:`configuration option - ` + ` to elide *x-forwarded-for* header modifications. * http: fixed a bug in inline headers where addCopy and addViaMove didn't add header values when encountering inline headers with multiple instances. -* listeners: added :ref:`tcp_fast_open_queue_length ` option. -* listeners: added the ability to match :ref:`FilterChain ` using - :ref:`application_protocols ` +* listeners: added :ref:`tcp_fast_open_queue_length ` option. +* listeners: added the ability to match :ref:`FilterChain ` using + :ref:`application_protocols ` (e.g. ALPN for TLS protocol). -* listeners: `sni_domains` has been deprecated/renamed to :ref:`server_names `. +* listeners: ``sni_domains`` has been deprecated/renamed to :ref:`server_names `. * listeners: removed restriction on all filter chains having identical filters. * load balancer: added :ref:`weighted round robin - ` support. The round robin + ` support. The round robin scheduler now respects endpoint weights and also has improved fidelity across picks. * load balancer: :ref:`locality weighted load balancing - ` is now supported. + ` is now supported. * load balancer: ability to configure zone aware load balancer settings :ref:`through the API - `. + `. * load balancer: the :ref:`weighted least request - ` load balancing algorithm has been improved + ` load balancing algorithm has been improved to have better balance when operating in weighted mode. * logger: added the ability to optionally set the log format via the :option:`--log-format` option. -* logger: all :ref:`logging levels ` can be configured +* logger: all :ref:`logging levels ` can be configured at run-time: trace debug info warning error critical. -* rbac http filter: a :ref:`role-based access control http filter ` has been added. +* rbac http filter: a :ref:`role-based access control http filter ` has been added. * router: the behavior of per-try timeouts have changed in the case where a portion of the response has already been proxied downstream when the timeout occurs. Previously, the response would be reset leading to either an HTTP/2 reset or an HTTP/1 closed connection and a partial response. Now, the timeout will be ignored and the response will continue to proxy up to the global request timeout. -* router: changed the behavior of :ref:`source IP routing ` +* router: changed the behavior of :ref:`source IP routing ` to ignore the source port. -* router: added an :ref:`prefix_match ` match type +* router: added an :ref:`prefix_match ` match type to explicitly match based on the prefix of a header value. -* router: added an :ref:`suffix_match ` match type +* router: added an :ref:`suffix_match ` match type to explicitly match based on the suffix of a header value. -* router: added an :ref:`present_match ` match type +* router: added an :ref:`present_match ` match type to explicitly match based on a header's presence. -* router: added an :ref:`invert_match ` config option +* router: added an :ref:`invert_match ` config option which supports inverting all other match types to match based on headers which are not a desired value. -* router: allow :ref:`cookie routing ` to +* router: allow :ref:`cookie routing ` to generate session cookies. * router: added START_TIME as one of supported variables in :ref:`header - formatters `. -* router: added a :ref:`max_grpc_timeout ` + formatters `. +* router: added a :ref:`max_grpc_timeout ` config option to specify the maximum allowable value for timeouts decoded from gRPC header field - `grpc-timeout`. + ``grpc-timeout``. * router: added a :ref:`configuration option - ` to disable *x-envoy-* + ` to disable *x-envoy-* header generation. * router: added 'unavailable' to the retriable gRPC status codes that can be specified - through :ref:`x-envoy-retry-grpc-on `. -* sockets: added :ref:`tap transport socket extension ` to support + through :ref:`x-envoy-retry-grpc-on `. +* sockets: added :ref:`tap transport socket extension ` to support recording plain text traffic and PCAP generation. -* sockets: added `IP_FREEBIND` socket option support for :ref:`listeners - ` and upstream connections via +* sockets: added ``IP_FREEBIND`` socket option support for :ref:`listeners + ` and upstream connections via :ref:`cluster manager wide - ` and - :ref:`cluster specific ` options. -* sockets: added `IP_TRANSPARENT` socket option support for :ref:`listeners - `. -* sockets: added `SO_KEEPALIVE` socket option for upstream connections - :ref:`per cluster `. + ` and + :ref:`cluster specific ` options. +* sockets: added ``IP_TRANSPARENT`` socket option support for :ref:`listeners + `. +* sockets: added ``SO_KEEPALIVE`` socket option for upstream connections + :ref:`per cluster `. * stats: added support for histograms. -* stats: added :ref:`option to configure the statsd prefix`. +* stats: added :ref:`option to configure the statsd prefix `. * stats: updated stats sink interface to flush through a single call. * tls: added support for - :ref:`verify_certificate_spki `. + :ref:`verify_certificate_spki `. * tls: added support for multiple - :ref:`verify_certificate_hash ` + :ref:`verify_certificate_hash ` values. * tls: added support for using - :ref:`verify_certificate_spki ` - and :ref:`verify_certificate_hash ` - without :ref:`trusted_ca `. + :ref:`verify_certificate_spki ` + and :ref:`verify_certificate_hash ` + without :ref:`trusted_ca `. * tls: added support for allowing expired certificates with - :ref:`allow_expired_certificate `. -* tls: added support for :ref:`renegotiation ` + :ref:`allow_expired_certificate `. +* tls: added support for :ref:`renegotiation ` when acting as a client. * tls: removed support for legacy SHA-2 CBC cipher suites. * tracing: the sampling decision is now delegated to the tracers, allowing the tracer to decide when and if - to use it. For example, if the :ref:`x-b3-sampled ` header + to use it. For example, if the :ref:`x-b3-sampled ` header is supplied with the client request, its value will override any sampling decision made by the Envoy proxy. * websocket: support configuring idle_timeout and max_connect_attempts. -* upstream: added support for host override for a request in :ref:`Original destination host request header `. -* header to metadata: added :ref:`HTTP Header to Metadata filter`. +* upstream: added support for host override for a request in :ref:`Original destination host request header `. +* header to metadata: added :ref:`HTTP Header to Metadata filter `. Deprecated ---------- @@ -161,14 +161,14 @@ Deprecated * Admin mutations should be sent as POSTs rather than GETs. HTTP GETs will result in an error status code and will not have their intended effect. Prior to 1.7, GETs can be used for admin mutations, but a warning is logged. -* Rate limit service configuration via the `cluster_name` field is deprecated. Use `grpc_service` +* Rate limit service configuration via the ``cluster_name`` field is deprecated. Use ``grpc_service`` instead. -* gRPC service configuration via the `cluster_names` field in `ApiConfigSource` is deprecated. Use - `grpc_services` instead. Prior to 1.7, a warning is logged. -* Redis health checker configuration via the `redis_health_check` field in `HealthCheck` is - deprecated. Use `custom_health_check` with name `envoy.health_checkers.redis` instead. Prior - to 1.7, `redis_health_check` can be used, but warning is logged. -* `SAN` is replaced by `URI` in the `x-forwarded-client-cert` header. -* The `endpoint` field in the http health check filter is deprecated in favor of the `headers` +* gRPC service configuration via the ``cluster_names`` field in ``ApiConfigSource`` is deprecated. Use + ``grpc_services`` instead. Prior to 1.7, a warning is logged. +* Redis health checker configuration via the ``redis_health_check`` field in ``HealthCheck`` is + deprecated. Use ``custom_health_check`` with name ``envoy.health_checkers.redis`` instead. Prior + to 1.7, ``redis_health_check`` can be used, but warning is logged. +* ``SAN`` is replaced by ``URI`` in the ``x-forwarded-client-cert`` header. +* The ``endpoint`` field in the http health check filter is deprecated in favor of the ``headers`` field where one can specify HeaderMatch objects to match on. -* The `sni_domains` field in the filter chain match was deprecated/renamed to `server_names`. +* The ``sni_domains`` field in the filter chain match was deprecated/renamed to ``server_names``. diff --git a/docs/root/version_history/v1.8.0.rst b/docs/root/version_history/v1.8.0.rst index e99c0e2c459c5..4f1d07b22a87f 100644 --- a/docs/root/version_history/v1.8.0.rst +++ b/docs/root/version_history/v1.8.0.rst @@ -4,126 +4,126 @@ Changes ------- -* access log: added :ref:`response flag filter ` +* access log: added :ref:`response flag filter ` to filter based on the presence of Envoy response flags. * access log: added RESPONSE_DURATION and RESPONSE_TX_DURATION. * access log: added REQUESTED_SERVER_NAME for SNI to tcp_proxy and http * admin: added :http:get:`/hystrix_event_stream` as an endpoint for monitoring envoy's statistics through `Hystrix dashboard `_. -* cli: added support for :ref:`component log level ` command line option for configuring log levels of individual components. -* cluster: added :ref:`option ` to merge +* cli: added support for :ref:`component log level ` command line option for configuring log levels of individual components. +* cluster: added :ref:`option ` to merge health check/weight/metadata updates within the given duration. * config: regex validation added to limit to a maximum of 1024 characters. * config: v1 disabled by default. v1 support remains available until October via flipping --v2-config-only=false. * config: v1 disabled by default. v1 support remains available until October via deprecated flag --allow-deprecated-v1-api. -* config: fixed stat inconsistency between xDS and ADS implementation. :ref:`update_failure ` - stat is incremented in case of network failure and :ref:`update_rejected ` stat is incremented +* config: fixed stat inconsistency between xDS and ADS implementation. :ref:`update_failure ` + stat is incremented in case of network failure and :ref:`update_rejected ` stat is incremented in case of schema/validation error. -* config: added a stat :ref:`connected_state ` that indicates current connected state of Envoy with +* config: added a stat :ref:`connected_state ` that indicates current connected state of Envoy with management server. -* ext_authz: added support for configuring additional :ref:`authorization headers ` +* ext_authz: added support for configuring additional :ref:`authorization headers ` to be sent from Envoy to the authorization service. -* fault: added support for fractional percentages in :ref:`FaultDelay ` - and in :ref:`FaultAbort `. +* fault: added support for fractional percentages in :ref:`FaultDelay ` + and in :ref:`FaultAbort `. * grpc-json: added support for building HTTP response from `google.api.HttpBody `_. -* health check: added support for :ref:`custom health check `. -* health check: added support for :ref:`specifying jitter as a percentage `. -* health_check: added support for :ref:`health check event logging `. -* health_check: added :ref:`timestamp ` - to the :ref:`health check event ` definition. -* health_check: added support for specifying :ref:`custom request headers ` +* health check: added support for :ref:`custom health check `. +* health check: added support for :ref:`specifying jitter as a percentage `. +* health_check: added support for :ref:`health check event logging `. +* health_check: added :ref:`timestamp ` + to the :ref:`health check event ` definition. +* health_check: added support for specifying :ref:`custom request headers ` to HTTP health checker requests. * http: added support for a :ref:`per-stream idle timeout - `. This applies at both :ref:`connection manager - ` - and :ref:`per-route granularity `. The timeout + `. This applies at both :ref:`connection manager + ` + and :ref:`per-route granularity `. The timeout defaults to 5 minutes; if you have other timeouts (e.g. connection idle timeout, upstream response per-retry) that are longer than this in duration, you may want to consider setting a non-default per-stream idle timeout. -* http: added upstream_rq_completed counter for :ref:`total requests completed ` to dynamic HTTP counters. -* http: added downstream_rq_completed counter for :ref:`total requests completed `, including on a :ref:`per-listener basis `. +* http: added upstream_rq_completed counter for :ref:`total requests completed ` to dynamic HTTP counters. +* http: added downstream_rq_completed counter for :ref:`total requests completed `, including on a :ref:`per-listener basis `. * http: added generic :ref:`Upgrade support - `. + `. * http: better handling of HEAD requests. Now sending transfer-encoding: chunked rather than content-length: 0. * http: fixed missing support for appending to predefined inline headers, e.g. *authorization*, in features that interact with request and response headers, e.g. :ref:`request_headers_to_add - `. For example, a + `. For example, a request header *authorization: token1* will appear as *authorization: token1,token2*, after having :ref:`request_headers_to_add - ` with *authorization: + ` with *authorization: token2* applied. * http: response filters not applied to early error paths such as http_parser generated 400s. * http: restrictions added to reject *:*-prefixed pseudo-headers in :ref:`custom - request headers `. -* http: :ref:`hpack_table_size ` now controls + request headers `. +* http: :ref:`hpack_table_size ` now controls dynamic table size of both: encoder and decoder. * http: added support for removing request headers using :ref:`request_headers_to_remove - `. -* http: added support for a :ref:`delayed close timeout` to mitigate race conditions when closing connections to downstream HTTP clients. The timeout defaults to 1 second. + `. +* http: added support for a :ref:`delayed close timeout ` to mitigate race conditions when closing connections to downstream HTTP clients. The timeout defaults to 1 second. * jwt-authn filter: add support for per route JWT requirements. -* listeners: added the ability to match :ref:`FilterChain ` using - :ref:`destination_port ` and - :ref:`prefix_ranges `. -* lua: added :ref:`connection() ` wrapper and *ssl()* API. -* lua: added :ref:`streamInfo() ` wrapper and *protocol()* API. -* lua: added :ref:`streamInfo():dynamicMetadata() ` API. -* network: introduced :ref:`sni_cluster ` network filter that forwards connections to the +* listeners: added the ability to match :ref:`FilterChain ` using + :ref:`destination_port ` and + :ref:`prefix_ranges `. +* lua: added :ref:`connection() ` wrapper and *ssl()* API. +* lua: added :ref:`streamInfo() ` wrapper and *protocol()* API. +* lua: added :ref:`streamInfo():dynamicMetadata() ` API. +* network: introduced :ref:`sni_cluster ` network filter that forwards connections to the upstream cluster specified by the SNI value presented by the client during a TLS handshake. * proxy_protocol: added support for HAProxy Proxy Protocol v2 (AF_INET/AF_INET6 only). * ratelimit: added support for :repo:`api/envoy/service/ratelimit/v2/rls.proto`. Lyft's reference implementation of the `ratelimit `_ service also supports the data-plane-api proto as of v1.1.0. Envoy can use either proto to send client requests to a ratelimit server with the use of the - `use_data_plane_proto` boolean flag in the ratelimit configuration. - Support for the legacy proto `source/common/ratelimit/ratelimit.proto` is deprecated and will be removed at the start of the 1.9.0 release cycle. -* ratelimit: added :ref:`failure_mode_deny ` option to control traffic flow in + ``use_data_plane_proto`` boolean flag in the ratelimit configuration. + Support for the legacy proto ``source/common/ratelimit/ratelimit.proto`` is deprecated and will be removed at the start of the 1.9.0 release cycle. +* ratelimit: added :ref:`failure_mode_deny ` option to control traffic flow in case of rate limit service error. -* rbac config: added a :ref:`principal_name ` field and - removed the old `name` field to give more flexibility for matching certificate identity. -* rbac network filter: a :ref:`role-based access control network filter ` has been added. -* rest-api: added ability to set the :ref:`request timeout ` for REST API requests. +* rbac config: added a :ref:`principal_name ` field and + removed the old ``name`` field to give more flexibility for matching certificate identity. +* rbac network filter: a :ref:`role-based access control network filter ` has been added. +* rest-api: added ability to set the :ref:`request timeout ` for REST API requests. * route checker: added v2 config support and removed support for v1 configs. -* router: added ability to set request/response headers at the :ref:`envoy_api_msg_route.Route` level. -* stats: added :ref:`option to configure the DogStatsD metric name prefix` to DogStatsdSink. -* tcp_proxy: added support for :ref:`weighted clusters `. +* router: added ability to set request/response headers at the :ref:`v1.8.0:envoy_api_msg_route.Route` level. +* stats: added :ref:`option to configure the DogStatsD metric name prefix ` to DogStatsdSink. +* tcp_proxy: added support for :ref:`weighted clusters `. * thrift_proxy: introduced thrift routing, moved configuration to correct location * thrift_proxy: introduced thrift configurable decoder filters -* tls: implemented :ref:`Secret Discovery Service `. +* tls: implemented :ref:`Secret Discovery Service `. * tracing: added support for configuration of :ref:`tracing sampling - `. + `. * upstream: added configuration option to the subset load balancer to take locality weights into account when selecting a host from a subset. -* upstream: require opt-in to use the :ref:`x-envoy-original-dst-host ` header - for overriding destination address when using the :ref:`Original Destination ` +* upstream: require opt-in to use the :ref:`x-envoy-original-dst-host ` header + for overriding destination address when using the :ref:`Original Destination ` load balancing policy. Deprecated ---------- -* Use of the v1 API (including `*.deprecated_v1` fields in the v2 API) is deprecated. +* Use of the v1 API (including ``*.deprecated_v1`` fields in the v2 API) is deprecated. See envoy-announce `email `_. * Use of the legacy `ratelimit.proto `_ is deprecated, in favor of the proto defined in `date-plane-api `_ Prior to 1.8.0, Envoy can use either proto to send client requests to a ratelimit server with the use of the - `use_data_plane_proto` boolean flag in the `ratelimit configuration `_. + ``use_data_plane_proto`` boolean flag in the `ratelimit configuration `_. However, when using the deprecated client a warning is logged. * Use of the --v2-config-only flag. -* Use of both `use_websocket` and `websocket_config` in +* Use of both ``use_websocket`` and ``websocket_config`` in `route.proto `_ - is deprecated. Please use the new `upgrade_configs` in the + is deprecated. Please use the new ``upgrade_configs`` in the `HttpConnectionManager `_ instead. -* Use of the integer `percent` field in `FaultDelay `_ +* Use of the integer ``percent`` field in `FaultDelay `_ and in `FaultAbort `_ is deprecated in favor - of the new `FractionalPercent` based `percentage` field. -* Setting hosts via `hosts` field in `Cluster` is deprecated. Use `load_assignment` instead. -* Use of `response_headers_to_*` and `request_headers_to_add` are deprecated at the `RouteAction` - level. Please use the configuration options at the `Route` level. -* Use of `runtime` in `RouteMatch`, found in + of the new ``FractionalPercent`` based ``percentage`` field. +* Setting hosts via ``hosts`` field in ``Cluster`` is deprecated. Use ``load_assignment`` instead. +* Use of ``response_headers_to_*`` and ``request_headers_to_add`` are deprecated at the ``RouteAction`` + level. Please use the configuration options at the ``Route`` level. +* Use of ``runtime`` in ``RouteMatch``, found in `route.proto `_. - Set the `runtime_fraction` field instead. -* Use of the string `user` field in `Authenticated` in `rbac.proto `_ - is deprecated in favor of the new `StringMatcher` based `principal_name` field. + Set the ``runtime_fraction`` field instead. +* Use of the string ``user`` field in ``Authenticated`` in `rbac.proto `_ + is deprecated in favor of the new ``StringMatcher`` based ``principal_name`` field. diff --git a/docs/root/version_history/v1.9.0.rst b/docs/root/version_history/v1.9.0.rst index 12614ca1497b0..d9056fb3aeb41 100644 --- a/docs/root/version_history/v1.9.0.rst +++ b/docs/root/version_history/v1.9.0.rst @@ -4,110 +4,110 @@ Changes ------- -* access log: added a :ref:`JSON logging mode ` to output access logs in JSON format. +* access log: added a :ref:`JSON logging mode ` to output access logs in JSON format. * access log: added dynamic metadata to access log messages streamed over gRPC. * access log: added DOWNSTREAM_CONNECTION_TERMINATION. * admin: :http:post:`/logging` now responds with 200 while there are no params. -* admin: added support for displaying subject alternate names in :ref:`certs` end point. +* admin: added support for displaying subject alternate names in :ref:`certs ` end point. * admin: added host weight to the :http:get:`/clusters?format=json` end point response. * admin: :http:get:`/server_info` now responds with a JSON object instead of a single string. * admin: :http:get:`/server_info` now exposes what stage of initialization the server is currently in. * admin: added support for displaying command line options in :http:get:`/server_info` end point. * circuit-breaker: added cx_open, rq_pending_open, rq_open and rq_retry_open gauges to expose live - state via :ref:`circuit breakers statistics `. -* cluster: set a default of 1s for :ref:`option `. + state via :ref:`circuit breakers statistics `. +* cluster: set a default of 1s for :ref:`option `. * config: removed support for the v1 API. -* config: added support for :ref:`rate limiting` discovery request calls. -* cors: added :ref:`invalid/valid stats ` to filter. +* config: added support for :ref:`rate limiting ` discovery request calls. +* cors: added :ref:`invalid/valid stats ` to filter. * ext-authz: added support for providing per route config - optionally disable the filter and provide context extensions. * fault: removed integer percentage support. * grpc-json: added support for :ref:`ignoring query parameters - `. -* health check: added :ref:`logging health check failure events `. + `. +* health check: added :ref:`logging health check failure events `. * health check: added ability to set :ref:`authority header value - ` for gRPC health check. -* http: added HTTP/2 WebSocket proxying via :ref:`extended CONNECT `. + ` for gRPC health check. +* http: added HTTP/2 WebSocket proxying via :ref:`extended CONNECT `. * http: added limits to the number and length of header modifications in all fields request_headers_to_add and response_headers_to_add. These limits are very high and should only be used as a last-resort safeguard. -* http: added support for a :ref:`request timeout `. The timeout is disabled by default. +* http: added support for a :ref:`request timeout `. The timeout is disabled by default. * http: no longer adding whitespace when appending X-Forwarded-For headers. **Warning**: this is not compatible with 1.7.0 builds prior to `9d3a4eb4ac44be9f0651fcc7f87ad98c538b01ee `_. See `#3611 `_ for details. -* http: augmented the `sendLocalReply` filter API to accept an optional `GrpcStatus` +* http: augmented the ``sendLocalReply`` filter API to accept an optional ``GrpcStatus`` value to override the default HTTP to gRPC status mapping. * http: no longer close the TCP connection when a HTTP/1 request is retried due to a response with empty body. -* http: added support for more gRPC content-type headers in :ref:`gRPC bridge filter `, like application/grpc+proto. +* http: added support for more gRPC content-type headers in :ref:`gRPC bridge filter `, like application/grpc+proto. * listeners: all listener filters are now governed by the :ref:`listener_filters_timeout - ` setting. The hard coded 15s timeout in - the :ref:`TLS inspector listener filter ` is superseded by + ` setting. The hard coded 15s timeout in + the :ref:`TLS inspector listener filter ` is superseded by this setting. -* listeners: added the ability to match :ref:`FilterChain ` using :ref:`source_type `. -* load balancer: added a `configuration ` option to specify the number of choices made in P2C. +* listeners: added the ability to match :ref:`FilterChain ` using :ref:`source_type `. +* load balancer: added a `configuration ` option to specify the number of choices made in P2C. * logging: added missing [ in log prefix. -* mongo_proxy: added :ref:`dynamic metadata `. -* network: removed the reference to `FilterState` in `Connection` in favor of `StreamInfo`. -* rate-limit: added :ref:`configuration ` - to specify whether the `GrpcStatus` status returned should be `RESOURCE_EXHAUSTED` or - `UNAVAILABLE` when a gRPC call is rate limited. +* mongo_proxy: added :ref:`dynamic metadata `. +* network: removed the reference to ``FilterState`` in ``Connection`` in favor of ``StreamInfo``. +* rate-limit: added :ref:`configuration ` + to specify whether the ``GrpcStatus`` status returned should be ``RESOURCE_EXHAUSTED`` or + ``UNAVAILABLE`` when a gRPC call is rate limited. * rate-limit: removed support for the legacy ratelimit service and made the data-plane-api - :ref:`rls.proto ` based implementation default. -* rate-limit: removed the deprecated cluster_name attribute in :ref:`rate limit service configuration `. -* rate-limit: added :ref:`rate_limit_service ` configuration to filters. + :ref:`rls.proto ` based implementation default. +* rate-limit: removed the deprecated cluster_name attribute in :ref:`rate limit service configuration `. +* rate-limit: added :ref:`rate_limit_service ` configuration to filters. * rbac: added dynamic metadata to the network level filter. -* rbac: added support for permission matching by :ref:`requested server name `. +* rbac: added support for permission matching by :ref:`requested server name `. * redis: static cluster configuration is no longer required. Redis proxy will work with clusters delivered via CDS. -* router: added ability to configure arbitrary :ref:`retriable status codes. ` +* router: added ability to configure arbitrary :ref:`retriable status codes. ` * router: added ability to set attempt count in upstream requests, see :ref:`virtual host's include request - attempt count flag `. -* router: added internal :ref:`grpc-retry-on ` policy. -* router: added :ref:`scheme_redirect ` and - :ref:`port_redirect ` to define the respective + attempt count flag `. +* router: added internal :ref:`grpc-retry-on ` policy. +* router: added :ref:`scheme_redirect ` and + :ref:`port_redirect ` to define the respective scheme and port rewriting RedirectAction. -* router: when :ref:`max_grpc_timeout ` +* router: when :ref:`max_grpc_timeout ` is set, Envoy will now add or update the grpc-timeout header to reflect Envoy's expected timeout. * router: per try timeouts now starts when an upstream stream is ready instead of when the request has been fully decoded by Envoy. -* router: added support for not retrying :ref:`rate limited requests`. Rate limit filter now sets the :ref:`x-envoy-ratelimited` +* router: added support for not retrying :ref:`rate limited requests `. Rate limit filter now sets the :ref:`x-envoy-ratelimited ` header so the rate limited requests that may have been retried earlier will not be retried with this change. -* router: added support for enabling upgrades on a :ref:`per-route ` basis. +* router: added support for enabling upgrades on a :ref:`per-route ` basis. * router: support configuring a default fraction of mirror traffic via - :ref:`runtime_fraction `. -* sandbox: added :ref:`cors sandbox `. -* server: added `SIGINT` (Ctrl-C) handler to gracefully shutdown Envoy like `SIGTERM`. -* stats: added :ref:`stats_matcher ` to the bootstrap config for granular control of stat instantiation. -* stream: renamed the `RequestInfo` namespace to `StreamInfo` to better match + :ref:`runtime_fraction `. +* sandbox: added :ref:`cors sandbox `. +* server: added ``SIGINT`` (Ctrl-C) handler to gracefully shutdown Envoy like ``SIGTERM``. +* stats: added :ref:`stats_matcher ` to the bootstrap config for granular control of stat instantiation. +* stream: renamed the ``RequestInfo`` namespace to ``StreamInfo`` to better match its behaviour within TCP and HTTP implementations. -* stream: renamed `perRequestState` to `filterState` in `StreamInfo`. -* stream: added `downstreamDirectRemoteAddress` to `StreamInfo`. +* stream: renamed ``perRequestState`` to ``filterState`` in ``StreamInfo``. +* stream: added ``downstreamDirectRemoteAddress`` to ``StreamInfo``. * thrift_proxy: introduced thrift rate limiter filter. * tls: added ssl.curves., ssl.sigalgs. and ssl.versions. to - :ref:`listener metrics ` to track TLS algorithms and versions in use. -* tls: added support for :ref:`client-side session resumption `. -* tls: added support for CRLs in :ref:`trusted_ca `. -* tls: added support for :ref:`multiple server TLS certificates `. -* tls: added support for :ref:`password encrypted private keys `. -* tls: added the ability to build :ref:`BoringSSL FIPS ` using ``--define boringssl=fips`` Bazel option. + :ref:`listener metrics ` to track TLS algorithms and versions in use. +* tls: added support for :ref:`client-side session resumption `. +* tls: added support for CRLs in :ref:`trusted_ca `. +* tls: added support for :ref:`multiple server TLS certificates `. +* tls: added support for :ref:`password encrypted private keys `. +* tls: added the ability to build :ref:`BoringSSL FIPS ` using ``--define boringssl=fips`` Bazel option. * tls: removed support for ECDSA certificates with curves other than P-256. * tls: removed support for RSA certificates with keys smaller than 2048-bits. -* tracing: added support to the Zipkin tracer for the :ref:`b3 ` single header format. -* tracing: added support for :ref:`Datadog ` tracer. -* upstream: added :ref:`scale_locality_weight` to enable +* tracing: added support to the Zipkin tracer for the :ref:`b3 ` single header format. +* tracing: added support for :ref:`Datadog ` tracer. +* upstream: added :ref:`scale_locality_weight ` to enable scaling locality weights by number of hosts removed by subset lb predicates. -* upstream: changed how load calculation for :ref:`priority levels` and :ref:`panic thresholds` interact. As long as normalized total health is 100% panic thresholds are disregarded. -* upstream: changed the default hash for :ref:`ring hash ` from std::hash to `xxHash `_. +* upstream: changed how load calculation for :ref:`priority levels ` and :ref:`panic thresholds ` interact. As long as normalized total health is 100% panic thresholds are disregarded. +* upstream: changed the default hash for :ref:`ring hash ` from std::hash to `xxHash `_. * upstream: when using active health checking and STRICT_DNS with several addresses that resolve to the same hosts, Envoy will now health check each host independently. Deprecated ---------- -* Order of execution of the network write filter chain has been reversed. Prior to this release cycle it was incorrect, see `#4599 `_. In the 1.9.0 release cycle we introduced `bugfix_reverse_write_filter_order` in `lds.proto `_ to temporarily support both old and new behaviors. Note this boolean field is deprecated. -* Order of execution of the HTTP encoder filter chain has been reversed. Prior to this release cycle it was incorrect, see `#4599 `_. In the 1.9.0 release cycle we introduced `bugfix_reverse_encode_order` in `http_connection_manager.proto `_ to temporarily support both old and new behaviors. Note this boolean field is deprecated. +* Order of execution of the network write filter chain has been reversed. Prior to this release cycle it was incorrect, see `#4599 `_. In the 1.9.0 release cycle we introduced ``bugfix_reverse_write_filter_order`` in `lds.proto `_ to temporarily support both old and new behaviors. Note this boolean field is deprecated. +* Order of execution of the HTTP encoder filter chain has been reversed. Prior to this release cycle it was incorrect, see `#4599 `_. In the 1.9.0 release cycle we introduced ``bugfix_reverse_encode_order`` in `http_connection_manager.proto `_ to temporarily support both old and new behaviors. Note this boolean field is deprecated. * Use of the v1 REST_LEGACY ApiConfigSource is deprecated. * Use of std::hash in the ring hash load balancer is deprecated. -* Use of `rate_limit_service` configuration in the `bootstrap configuration `_ is deprecated. -* Use of `runtime_key` in `RequestMirrorPolicy`, found in +* Use of ``rate_limit_service`` configuration in the `bootstrap configuration `_ is deprecated. +* Use of ``runtime_key`` in ``RequestMirrorPolicy``, found in `route.proto `_ - is deprecated. Set the `runtime_fraction` field instead. -* Use of buffer filter `max_request_time` is deprecated in favor of the request timeout found in `HttpConnectionManager `_ + is deprecated. Set the ``runtime_fraction`` field instead. +* Use of buffer filter ``max_request_time`` is deprecated in favor of the request timeout found in `HttpConnectionManager `_ diff --git a/docs/root/version_history/v1.9.1.rst b/docs/root/version_history/v1.9.1.rst index 4e8b96be8fe39..7027026e5094f 100644 --- a/docs/root/version_history/v1.9.1.rst +++ b/docs/root/version_history/v1.9.1.rst @@ -7,5 +7,5 @@ Changes * http: fixed CVE-2019-9900 by rejecting HTTP/1.x headers with embedded NUL characters. * http: fixed CVE-2019-9901 by normalizing HTTP paths prior to routing or L7 data plane processing. This defaults off and is configurable via either HTTP connection manager :ref:`normalize_path - ` - or the :ref:`runtime `. + ` + or the :ref:`runtime `. diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index dc3761c05df70..ee89224e16c43 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -164,7 +164,7 @@ PROTO_VALIDATION_STRING = re.compile(r'\bmin_bytes\b') VERSION_HISTORY_NEW_LINE_REGEX = re.compile("\* ([a-z \-_]+): ([a-z:`]+)") VERSION_HISTORY_SECTION_NAME = re.compile("^[A-Z][A-Za-z ]*$") -RELOADABLE_FLAG_REGEX = re.compile(".*(..)(envoy.reloadable_features.[^ ]*)\s.*") +RELOADABLE_FLAG_REGEX = re.compile(".*(...)(envoy.reloadable_features.[^ ]*)\s.*") INVALID_REFLINK = re.compile(".* ref:.*") OLD_MOCK_METHOD_REGEX = re.compile("MOCK_METHOD\d") # C++17 feature, lacks sufficient support across various libraries / compilers. @@ -527,13 +527,12 @@ def report_error(message): if invalid_reflink_match: report_error("Found text \" ref:\". This should probably be \" :ref:\"\n%s" % line) - # make sure flags are surrounded by ``s + # make sure flags are surrounded by ``s (ie "inline literal") flag_match = RELOADABLE_FLAG_REGEX.match(line) if flag_match: - if not flag_match.groups()[0].startswith(' `'): + if not flag_match.groups()[0].startswith(' ``'): report_error( - "Flag `%s` should be enclosed in a single set of back ticks" - % flag_match.groups()[1]) + "Flag %s should be enclosed in double back ticks" % flag_match.groups()[1]) if line.startswith("* "): if not ends_with_period(prior_line): diff --git a/tools/code_format/format_python_tools.py b/tools/code_format/format_python_tools.py index f36c0c80a16f6..d0c5e1e84509d 100644 --- a/tools/code_format/format_python_tools.py +++ b/tools/code_format/format_python_tools.py @@ -23,9 +23,11 @@ def collect_files(): for root, dirnames, filenames in os.walk(dirname): dirnames[:] = [d for d in dirnames if d not in EXCLUDE_LIST] for filename in fnmatch.filter(filenames, '*.py'): - if not filename.endswith('_pb2.py') and not filename.endswith('_pb2_grpc.py'): - if "test" not in root: - matches.append(os.path.join(root, filename)) + ignore_file = ( + "test" in root or filename.endswith('_pb2.py') or filename.endswith('_pb2_grpc.py') + or filename.endswith('intersphinx_custom.py')) + if not ignore_file: + matches.append(os.path.join(root, filename)) return matches From 72de1b204acd2570224417098796c277c66afcbd Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Tue, 27 Apr 2021 10:59:21 -0700 Subject: [PATCH 084/209] Make the members of Upstream::HostDescriptionImpl private. (#16192) Signed-off-by: Ryan Hamilton --- source/common/upstream/logical_host.h | 4 +-- source/common/upstream/upstream_impl.cc | 6 ++-- source/common/upstream/upstream_impl.h | 40 +++++++++++++++++-------- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/source/common/upstream/logical_host.h b/source/common/upstream/logical_host.h index 574728fe92bb0..ae2efb3ebb45a 100644 --- a/source/common/upstream/logical_host.h +++ b/source/common/upstream/logical_host.h @@ -40,8 +40,8 @@ class LogicalHost : public HostImpl { port_value == 0 ? address : Network::Utility::getAddressWithPort(*address, port_value); absl::WriterMutexLock lock(&address_lock_); - address_ = address; - health_check_address_ = health_check_address; + setAddress(address); + setHealthCheckAddress(health_check_address); } // Upstream::Host diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 070063f768589..07db4f18f2ff6 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -284,7 +284,7 @@ Network::TransportSocketFactory& HostDescriptionImpl::resolveTransportSocketFact Host::CreateConnectionData HostImpl::createConnection( Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options, Network::TransportSocketOptionsSharedPtr transport_socket_options) const { - return {createConnection(dispatcher, *cluster_, address_, socket_factory_, options, + return {createConnection(dispatcher, cluster(), address(), transportSocketFactory(), options, transport_socket_options), shared_from_this()}; } @@ -314,8 +314,8 @@ Host::CreateConnectionData HostImpl::createHealthCheckConnection( Network::TransportSocketFactory& factory = (metadata != nullptr) ? resolveTransportSocketFactory(healthCheckAddress(), metadata) - : socket_factory_; - return {createConnection(dispatcher, *cluster_, healthCheckAddress(), factory, nullptr, + : transportSocketFactory(); + return {createConnection(dispatcher, cluster(), healthCheckAddress(), factory, nullptr, transport_socket_options), shared_from_this()}; } diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index ec98ca4146d25..28ff4a7b60218 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -113,20 +113,20 @@ class HostDescriptionImpl : virtual public HostDescription, HealthCheckHostMonitor& healthChecker() const override { if (health_checker_) { return *health_checker_; - } else { - static HealthCheckHostMonitorNullImpl* null_health_checker = - new HealthCheckHostMonitorNullImpl(); - return *null_health_checker; } + + static HealthCheckHostMonitorNullImpl* null_health_checker = + new HealthCheckHostMonitorNullImpl(); + return *null_health_checker; } Outlier::DetectorHostMonitor& outlierDetector() const override { if (outlier_detector_) { return *outlier_detector_; - } else { - static Outlier::DetectorHostMonitorNullImpl* null_outlier_detector = - new Outlier::DetectorHostMonitorNullImpl(); - return *null_outlier_detector; } + + static Outlier::DetectorHostMonitorNullImpl* null_outlier_detector = + new Outlier::DetectorHostMonitorNullImpl(); + return *null_outlier_detector; } HostStats& stats() const override { return stats_; } const std::string& hostnameForHealthChecks() const override { return health_checks_hostname_; } @@ -147,6 +147,21 @@ class HostDescriptionImpl : virtual public HostDescription, MonotonicTime creationTime() const override { return creation_time_; } protected: + void setAddress(Network::Address::InstanceConstSharedPtr address) { address_ = address; } + + void setHealthCheckAddress(Network::Address::InstanceConstSharedPtr address) { + health_check_address_ = address; + } + + void setHealthCheckerImpl(HealthCheckHostMonitorPtr&& health_checker) { + health_checker_ = std::move(health_checker); + } + + void setOutlierDetectorImpl(Outlier::DetectorHostMonitorPtr&& outlier_detector) { + outlier_detector_ = std::move(outlier_detector); + } + +private: ClusterInfoConstSharedPtr cluster_; const std::string hostname_; const std::string health_checks_hostname_; @@ -188,7 +203,7 @@ class HostImpl : public HostDescriptionImpl, // Upstream::Host std::vector> counters() const override { - return stats_.counters(); + return stats().counters(); } CreateConnectionData createConnection( Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options, @@ -200,18 +215,19 @@ class HostImpl : public HostDescriptionImpl, std::vector> gauges() const override { - return stats_.gauges(); + return stats().gauges(); } void healthFlagClear(HealthFlag flag) override { health_flags_ &= ~enumToInt(flag); } bool healthFlagGet(HealthFlag flag) const override { return health_flags_ & enumToInt(flag); } void healthFlagSet(HealthFlag flag) final { health_flags_ |= enumToInt(flag); } void setHealthChecker(HealthCheckHostMonitorPtr&& health_checker) override { - health_checker_ = std::move(health_checker); + setHealthCheckerImpl(std::move(health_checker)); } void setOutlierDetector(Outlier::DetectorHostMonitorPtr&& outlier_detector) override { - outlier_detector_ = std::move(outlier_detector); + setOutlierDetectorImpl(std::move(outlier_detector)); } + Host::Health health() const override { // If any of the unhealthy flags are set, host is unhealthy. if (healthFlagGet(HealthFlag::FAILED_ACTIVE_HC) || From 58e080450a9267fbaaeba2332c4d04f069a109c7 Mon Sep 17 00:00:00 2001 From: Auni Ahsan Date: Tue, 27 Apr 2021 14:00:45 -0400 Subject: [PATCH 085/209] DebugString() -> ShortDebugString() (#16172) Signed-off-by: Auni Ahsan --- source/common/config/grpc_mux_impl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/common/config/grpc_mux_impl.cc b/source/common/config/grpc_mux_impl.cc index 6d562cceb41ca..66352fe9a0a20 100644 --- a/source/common/config/grpc_mux_impl.cc +++ b/source/common/config/grpc_mux_impl.cc @@ -69,7 +69,7 @@ void GrpcMuxImpl::sendDiscoveryRequest(const std::string& type_url) { request.clear_node(); } VersionConverter::prepareMessageForGrpcWire(request, transport_api_version_); - ENVOY_LOG(trace, "Sending DiscoveryRequest for {}: {}", type_url, request.DebugString()); + ENVOY_LOG(trace, "Sending DiscoveryRequest for {}: {}", type_url, request.ShortDebugString()); grpc_stream_.sendMessage(request); first_stream_request_ = false; From aba5650edee58be1c69b249e29221e42f0c9c3ec Mon Sep 17 00:00:00 2001 From: Jared Tan Date: Wed, 28 Apr 2021 02:22:47 +0800 Subject: [PATCH 086/209] update zk filter proxy (#16190) Signed-off-by: JaredTan95 --- .../_include/zookeeper-filter-proxy.yaml | 37 +++++++++++++++++++ .../zookeeper_proxy_filter.rst | 16 +------- 2 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 docs/root/configuration/listeners/network_filters/_include/zookeeper-filter-proxy.yaml diff --git a/docs/root/configuration/listeners/network_filters/_include/zookeeper-filter-proxy.yaml b/docs/root/configuration/listeners/network_filters/_include/zookeeper-filter-proxy.yaml new file mode 100644 index 0000000000000..aafca89351bf5 --- /dev/null +++ b/docs/root/configuration/listeners/network_filters/_include/zookeeper-filter-proxy.yaml @@ -0,0 +1,37 @@ +static_resources: + listeners: + - name: main + address: + socket_address: + address: 127.0.0.1 # Host that zookeeper clients should connect to. + port_value: 10001 # Port that zookeeper clients should connect to. + filter_chains: + - filters: + - name: envoy.filters.network.zookeeper_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.zookeeper_proxy.v3.ZooKeeperProxy + stat_prefix: zookeeper + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: tcp + cluster: local_zk_server + clusters: + - name: local_zk_server + connect_timeout: 120s + type: LOGICAL_DNS + lb_policy: ROUND_ROBIN + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http2_protocol_options: {} + load_assignment: + cluster_name: local_zk_server + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 2181 diff --git a/docs/root/configuration/listeners/network_filters/zookeeper_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/zookeeper_proxy_filter.rst index b0c85ecd7857a..5bd73fc8ea045 100644 --- a/docs/root/configuration/listeners/network_filters/zookeeper_proxy_filter.rst +++ b/docs/root/configuration/listeners/network_filters/zookeeper_proxy_filter.rst @@ -23,20 +23,8 @@ Configuration The ZooKeeper proxy filter should be chained with the TCP proxy filter as shown in the configuration snippet below: -.. code-block:: yaml - - filter_chains: - - filters: - - name: envoy.filters.network.zookeeper_proxy - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.zookeeper_proxy.v3.ZooKeeperProxy - stat_prefix: zookeeper - - name: envoy.filters.network.tcp_proxy - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy - stat_prefix: tcp - cluster: ... - +.. literalinclude:: _include/zookeeper-filter-proxy.yaml + :language: yaml .. _config_network_filters_zookeeper_proxy_stats: From 6c7777c9bff6d9b6e5cd1e52d3baa90776560fdb Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 28 Apr 2021 03:28:55 +0900 Subject: [PATCH 087/209] wasm: update proxy-wasm-cpp-host to use refactored code around runtimes. (#16162) Signed-off-by: Takeshi Yoneda --- bazel/repository_locations.bzl | 6 +++--- test/extensions/common/wasm/wasm_vm_test.cc | 7 ------- test/extensions/filters/http/wasm/config_test.cc | 3 ++- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index d9127ffbea321..98d40cd28bd36 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -948,8 +948,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host", - version = "aba2704bcd7d8adce2ccaf07c4ecbaf0cffeb7ef", - sha256 = "6263b45f87fc0c106eb88f31238553b1bde68f8a5ea25e20e95a5e6624a896d2", + version = "4b33df7638637fcc680291daa9d7a57c59e0411e", + sha256 = "e78507e04dc8b154ced5b19d6175bd1435fd850fdd127bce541d31799db06cd4", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], @@ -964,7 +964,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.wasm.runtime.wavm", "envoy.wasm.runtime.wasmtime", ], - release_date = "2021-04-08", + release_date = "2021-04-20", cpe = "N/A", ), proxy_wasm_rust_sdk = dict( diff --git a/test/extensions/common/wasm/wasm_vm_test.cc b/test/extensions/common/wasm/wasm_vm_test.cc index 43f6f0ac927cf..46edd6dfe3ca2 100644 --- a/test/extensions/common/wasm/wasm_vm_test.cc +++ b/test/extensions/common/wasm/wasm_vm_test.cc @@ -60,7 +60,6 @@ TEST_F(BaseVmTest, NullVmStartup) { EXPECT_TRUE(wasm_vm->cloneable() == Cloneable::InstantiatedModule); auto wasm_vm_clone = wasm_vm->clone(); EXPECT_TRUE(wasm_vm_clone != nullptr); - EXPECT_TRUE(wasm_vm->getCustomSection("user").empty()); EXPECT_EQ(wasm_vm->runtime(), "null"); std::function f; EXPECT_FALSE(wasm_vm->integration()->getNullVmFunction("bad_function", false, 0, nullptr, &f)); @@ -149,12 +148,6 @@ TEST_P(WasmVmTest, V8Code) { "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_rust.wasm")); EXPECT_TRUE(wasm_vm->load(code, GetParam())); - // Sanity checks for the expected test file. - if (!wasm_vm->getPrecompiledSectionName().empty()) { - EXPECT_TRUE(!wasm_vm->getCustomSection(wasm_vm->getPrecompiledSectionName()).empty()); - } - EXPECT_THAT(wasm_vm->getCustomSection("producers"), HasSubstr("rustc")); - EXPECT_TRUE(wasm_vm->cloneable() == Cloneable::CompiledBytecode); EXPECT_TRUE(wasm_vm->clone() != nullptr); } diff --git a/test/extensions/filters/http/wasm/config_test.cc b/test/extensions/filters/http/wasm/config_test.cc index 46c3741c55a69..e80d77ec98928 100644 --- a/test/extensions/filters/http/wasm/config_test.cc +++ b/test/extensions/filters/http/wasm/config_test.cc @@ -840,7 +840,8 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteSuccessBadcode) { EXPECT_CALL(stream_info, setResponseCodeDetails("wasm_fail_stream")); EXPECT_CALL(decoder_callbacks, resetStream()); - EXPECT_EQ(context->onRequestHeaders(10, false), proxy_wasm::FilterHeadersStatus::StopIteration); + EXPECT_EQ(context->onRequestHeaders(10, false), + proxy_wasm::FilterHeadersStatus::StopAllIterationAndWatermark); } TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteSuccessBadcodeFailOpen) { From c8415d61003a7d4bf1a37b5e010f6d80b322717f Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Tue, 27 Apr 2021 19:32:26 +0000 Subject: [PATCH 088/209] Build win32_scm_test with just core extensions (#16193) Signed-off-by: Yan Avlasov --- test/exe/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/exe/BUILD b/test/exe/BUILD index d0b9fd1939f28..279d60ad64e1a 100644 --- a/test/exe/BUILD +++ b/test/exe/BUILD @@ -107,7 +107,7 @@ envoy_cc_test( ], deps = [ "//source/common/api:api_lib", - "//source/exe:main_common_lib", + "//source/exe:envoy_main_common_with_core_extensions_lib", "//test/mocks/runtime:runtime_mocks", "//test/test_common:contention_lib", "//test/test_common:environment_lib", From d7a859be9a90920307955eb1fbe37e6bb55ac444 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 27 Apr 2021 21:05:19 +0100 Subject: [PATCH 089/209] configs: Validate dynamic xds config (#16145) Signed-off-by: Ryan Northey --- docs/BUILD | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/BUILD b/docs/BUILD index 237241dd46280..df6aa85f655f4 100644 --- a/docs/BUILD +++ b/docs/BUILD @@ -16,7 +16,9 @@ filegroup( "root/**/*.yaml", ], exclude = [ - "root/**/envoy-dynamic*.yaml", + "root/**/envoy-dynamic-cds-demo.yaml", + "root/**/envoy-dynamic-lds-demo.yaml", + "root/**/envoy-dynamic-filesystem-demo.yaml", # TODO(phlax/windows-dev): figure out how to get this working on windows # "Error: unable to read file: /etc/ssl/certs/ca-certificates.crt" "root/configuration/http/http_filters/_include/dns-cache-circuit-breaker.yaml", From 152762a0018fefef5643fed22e28d5c49975cdd1 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 28 Apr 2021 08:45:54 -0400 Subject: [PATCH 090/209] http: removing envoy.reloadable_features.http_match_on_all_headers (#16195) Risk Level: low Testing: n/a Docs Changes: n/a Release Notes: inline Part of #16018 Signed-off-by: Alyssa Wilk --- docs/root/version_history/current.rst | 1 + source/common/http/header_utility.cc | 4 +--- source/common/runtime/runtime_features.cc | 1 - test/common/http/header_utility_test.cc | 13 ------------- 4 files changed, 2 insertions(+), 17 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 634dffe9d624a..50b877436139f 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -31,6 +31,7 @@ Removed Config or Runtime * http: removed ``envoy.reloadable_features.allow_500_after_100`` runtime guard and the legacy code path. * http: removed ``envoy.reloadable_features.hcm_stream_error_on_invalid_message`` for disabling closing HTTP/1.1 connections on error. Connection-closing can still be disabled by setting the HTTP/1 configuration :ref:`override_stream_error_on_invalid_http_message `. * http: removed ``envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2``; Envoy will now always send GOAWAY to HTTP2 downstreams when the :ref:`disable_keepalive ` overload action is active. +* http: removed ``envoy.reloadable_features.http_match_on_all_headers`` runtime guard and legacy code paths. * http: removed ``envoy.reloadable_features.unify_grpc_handling`` runtime guard and legacy code paths. * tls: removed ``envoy.reloadable_features.tls_use_io_handle_bio`` runtime guard and legacy code path. diff --git a/source/common/http/header_utility.cc b/source/common/http/header_utility.cc index 5d8dc4f3a8996..a0331c484c288 100644 --- a/source/common/http/header_utility.cc +++ b/source/common/http/header_utility.cc @@ -122,9 +122,7 @@ HeaderUtility::getAllOfHeaderAsString(const HeaderMap& headers, const Http::Lowe if (header_value.empty()) { // Empty for clarity. Avoid handling the empty case in the block below if the runtime feature // is disabled. - } else if (header_value.size() == 1 || - !Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.http_match_on_all_headers")) { + } else if (header_value.size() == 1) { result.result_ = header_value[0]->value().getStringView(); } else { return getAllOfHeaderAsString(header_value, separator); diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 8ce5103f852f8..8240a8b52f5fb 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -71,7 +71,6 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.hash_multiple_header_values", "envoy.reloadable_features.health_check.graceful_goaway_handling", "envoy.reloadable_features.health_check.immediate_failure_exclude_from_cluster", - "envoy.reloadable_features.http_match_on_all_headers", "envoy.reloadable_features.http_set_copy_replace_all_headers", "envoy.reloadable_features.http_transport_failure_reason_in_body", "envoy.reloadable_features.http_upstream_wait_connect_response", diff --git a/test/common/http/header_utility_test.cc b/test/common/http/header_utility_test.cc index ca358e2a7a05d..37b3931295f7f 100644 --- a/test/common/http/header_utility_test.cc +++ b/test/common/http/header_utility_test.cc @@ -287,19 +287,6 @@ exact_match: a,b )EOF")); // Make sure that an exact match on "a,b" does in fact work. EXPECT_TRUE(HeaderUtility::matchHeaders(headers, header_data)); - - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.http_match_on_all_headers", "false"}}); - // Flipping runtime to false should make "a,b" no longer match because we will match on the first - // header only. - EXPECT_FALSE(HeaderUtility::matchHeaders(headers, header_data)); - - header_data[0] = std::make_unique(parseHeaderMatcherFromYaml(R"EOF( -name: match-header -exact_match: a - )EOF")); - // With runtime off, exact match on "a" should pass. - EXPECT_TRUE(HeaderUtility::matchHeaders(headers, header_data)); } TEST(MatchHeadersTest, MustMatchAllHeaderData) { From ec87fdd43e1144d0d0666514dfb10be9027dff92 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 28 Apr 2021 10:20:51 -0400 Subject: [PATCH 091/209] h3 config examples (#15987) Signed-off-by: Alyssa Wilk --- ...xy_io_proxy_http3_downstream.template.yaml | 74 +++++++++++++++++++ configs/google_com_http3_upstream_proxy.yaml | 63 ++++++++++++++++ test/config_test/config_test.cc | 8 ++ 3 files changed, 145 insertions(+) create mode 100644 configs/envoyproxy_io_proxy_http3_downstream.template.yaml create mode 100644 configs/google_com_http3_upstream_proxy.yaml diff --git a/configs/envoyproxy_io_proxy_http3_downstream.template.yaml b/configs/envoyproxy_io_proxy_http3_downstream.template.yaml new file mode 100644 index 0000000000000..2e7ace6c478a0 --- /dev/null +++ b/configs/envoyproxy_io_proxy_http3_downstream.template.yaml @@ -0,0 +1,74 @@ +# An example config which accepts HTTP/3 and forwarads the requests upstream over TCP. +admin: + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 9901 +static_resources: + listeners: + - name: listener_udp + address: + socket_address: + protocol: UDP + address: 0.0.0.0 + port_value: 10000 + reuse_port: true + udp_listener_config: + quic_options: {} + downstream_socket_config: + prefer_gro: true + filter_chains: + transport_socket: + name: envoy.transport_sockets.quic + typed_config: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicDownstreamTransport + downstream_tls_context: + common_tls_context: + alpn_protocols: h3 + tls_certificates: + certificate_chain: + filename: test/config/integration/certs/servercert.pem + private_key: + filename: test/config/integration/certs/serverkey.pem + filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: HTTP3 + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + http3_protocol_options: + http_filters: + - name: envoy.filters.http.router + clusters: + - name: service_envoyproxy_io + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: service_envoyproxy_io + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.envoyproxy.io + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + sni: www.envoyproxy.io diff --git a/configs/google_com_http3_upstream_proxy.yaml b/configs/google_com_http3_upstream_proxy.yaml new file mode 100644 index 0000000000000..1f5e8928eaaf9 --- /dev/null +++ b/configs/google_com_http3_upstream_proxy.yaml @@ -0,0 +1,63 @@ +# An example config which accepts HTTP/1 requests over TCP and forwards them to google using HTTP/3 +admin: + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 9901 +static_resources: + listeners: + - name: listener_0 + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.google.com + cluster: service_google + http_filters: + - name: envoy.filters.http.router + clusters: + - name: service_google + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: service_google + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.google.com + port_value: 443 + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http3_protocol_options: {} + common_http_protocol_options: + idle_timeout: 1s + transport_socket: + name: envoy.transport_sockets.quic + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicUpstreamTransport + upstream_tls_context: + sni: www.google.com diff --git a/test/config_test/config_test.cc b/test/config_test/config_test.cc index f0c7222298754..705811dc29e15 100644 --- a/test/config_test/config_test.cc +++ b/test/config_test/config_test.cc @@ -194,6 +194,14 @@ uint32_t run(const std::string& directory) { uint32_t num_tested = 0; Api::ApiPtr api = Api::createApiForTest(); for (const std::string& filename : TestUtility::listFiles(directory, false)) { +#ifndef ENVOY_ENABLE_QUIC + if (filename.find("http3") != std::string::npos) { + ENVOY_LOG_MISC(info, "Skipping HTTP/3 config {}.\n", filename); + num_tested++; + continue; + } +#endif + ENVOY_LOG_MISC(info, "testing {}.\n", filename); if (std::find_if(unsuported_win32_configs.begin(), unsuported_win32_configs.end(), [filename](const absl::string_view& s) { From c75ac18074825dac56594bf7f082e34336865947 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 28 Apr 2021 23:51:45 +0900 Subject: [PATCH 092/209] wasm: use in_vm_context_created_ flag for http contexts. (#16202) Removed http_request_started_ and instead use the in_vm_context_created_ in Proxy-Wasm C++ host since it has exactly the same meaning (indicating whether onCreate is called or not). Network filter is already using in_vm_context_created_. Signed-off-by: Takeshi Yoneda --- source/extensions/common/wasm/context.cc | 15 +++++++-------- source/extensions/common/wasm/context.h | 1 - 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index fa75e27fa82e8..1ee17e9d72bd1 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -1653,7 +1653,6 @@ WasmResult Context::sendLocalResponse(uint32_t response_code, absl::string_view Http::FilterHeadersStatus Context::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { onCreate(); - http_request_started_ = true; request_headers_ = &headers; end_of_stream_ = end_stream; auto result = convertFilterHeadersStatus(onRequestHeaders(headerSize(&headers), end_stream)); @@ -1664,7 +1663,7 @@ Http::FilterHeadersStatus Context::decodeHeaders(Http::RequestHeaderMap& headers } Http::FilterDataStatus Context::decodeData(::Envoy::Buffer::Instance& data, bool end_stream) { - if (!http_request_started_) { + if (!in_vm_context_created_) { return Http::FilterDataStatus::Continue; } request_body_buffer_ = &data; @@ -1688,7 +1687,7 @@ Http::FilterDataStatus Context::decodeData(::Envoy::Buffer::Instance& data, bool } Http::FilterTrailersStatus Context::decodeTrailers(Http::RequestTrailerMap& trailers) { - if (!http_request_started_) { + if (!in_vm_context_created_) { return Http::FilterTrailersStatus::Continue; } request_trailers_ = &trailers; @@ -1700,7 +1699,7 @@ Http::FilterTrailersStatus Context::decodeTrailers(Http::RequestTrailerMap& trai } Http::FilterMetadataStatus Context::decodeMetadata(Http::MetadataMap& request_metadata) { - if (!http_request_started_) { + if (!in_vm_context_created_) { return Http::FilterMetadataStatus::Continue; } request_metadata_ = &request_metadata; @@ -1721,7 +1720,7 @@ Http::FilterHeadersStatus Context::encode100ContinueHeaders(Http::ResponseHeader Http::FilterHeadersStatus Context::encodeHeaders(Http::ResponseHeaderMap& headers, bool end_stream) { - if (!http_request_started_) { + if (!in_vm_context_created_) { return Http::FilterHeadersStatus::Continue; } response_headers_ = &headers; @@ -1734,7 +1733,7 @@ Http::FilterHeadersStatus Context::encodeHeaders(Http::ResponseHeaderMap& header } Http::FilterDataStatus Context::encodeData(::Envoy::Buffer::Instance& data, bool end_stream) { - if (!http_request_started_) { + if (!in_vm_context_created_) { return Http::FilterDataStatus::Continue; } response_body_buffer_ = &data; @@ -1758,7 +1757,7 @@ Http::FilterDataStatus Context::encodeData(::Envoy::Buffer::Instance& data, bool } Http::FilterTrailersStatus Context::encodeTrailers(Http::ResponseTrailerMap& trailers) { - if (!http_request_started_) { + if (!in_vm_context_created_) { return Http::FilterTrailersStatus::Continue; } response_trailers_ = &trailers; @@ -1770,7 +1769,7 @@ Http::FilterTrailersStatus Context::encodeTrailers(Http::ResponseTrailerMap& tra } Http::FilterMetadataStatus Context::encodeMetadata(Http::MetadataMap& response_metadata) { - if (!http_request_started_) { + if (!in_vm_context_created_) { return Http::FilterMetadataStatus::Continue; } response_metadata_ = &response_metadata; diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index 1db0f8826c25b..5863737695725 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -414,7 +414,6 @@ class Context : public proxy_wasm::ContextBase, ::Envoy::Buffer::Instance* network_upstream_data_buffer_{}; // HTTP filter state. - bool http_request_started_ = false; // When decodeHeaders() is called the request is "started". Http::RequestHeaderMap* request_headers_{}; Http::ResponseHeaderMap* response_headers_{}; ::Envoy::Buffer::Instance* request_body_buffer_{}; From 55e858a56e4fdd69cc6d8ad10f02334dc79c8a9b Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Wed, 28 Apr 2021 20:23:20 +0530 Subject: [PATCH 093/209] Log actual admin port. (#16197) Signed-off-by: Manish Kumar --- source/server/admin/admin.cc | 1 + source/server/server.cc | 1 - test/server/admin/admin_test.cc | 15 +++++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/source/server/admin/admin.cc b/source/server/admin/admin.cc index 6492ee32c661d..fbfe86bd22463 100644 --- a/source/server/admin/admin.cc +++ b/source/server/admin/admin.cc @@ -131,6 +131,7 @@ void AdminImpl::startHttpListener(const std::list& socket_ = std::make_shared(address, socket_options, true); socket_factory_ = std::make_shared(socket_); listener_ = std::make_unique(*this, std::move(listener_scope)); + ENVOY_LOG(info, "admin address: {}", socket().addressProvider().localAddress()->asString()); if (!address_out_path.empty()) { std::ofstream address_out_file(address_out_path); if (!address_out_file) { diff --git a/source/server/server.cc b/source/server/server.cc index 666da1c2ed8b1..1a94860f3b170 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -497,7 +497,6 @@ void InstanceImpl::initialize(const Options& options, } if (initial_config.admin().address()) { - ENVOY_LOG(info, "admin address: {}", initial_config.admin().address()->asString()); admin_->startHttpListener(initial_config.admin().accessLogs(), options.adminAddressPath(), initial_config.admin().address(), initial_config.admin().socketOptions(), diff --git a/test/server/admin/admin_test.cc b/test/server/admin/admin_test.cc index 1646285087bbb..aa915f954ee1f 100644 --- a/test/server/admin/admin_test.cc +++ b/test/server/admin/admin_test.cc @@ -66,6 +66,21 @@ TEST_P(AdminInstanceTest, WriteAddressToFile) { EXPECT_EQ(admin_.socket().addressProvider().localAddress()->asString(), address_from_file); } +TEST_P(AdminInstanceTest, AdminAddress) { + std::string address_out_path = TestEnvironment::temporaryPath("admin.address"); + AdminImpl admin_address_out_path(cpu_profile_path_, server_); + std::list access_logs; + Filesystem::FilePathAndType file_info{Filesystem::DestinationType::File, "/dev/null"}; + access_logs.emplace_back(new Extensions::AccessLoggers::File::FileAccessLog( + file_info, {}, Formatter::SubstitutionFormatUtils::defaultSubstitutionFormatter(), + server_.accessLogManager())); + EXPECT_LOG_CONTAINS("info", "admin address:", + admin_address_out_path.startHttpListener( + access_logs, address_out_path, + Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, + listener_scope_.createScope("listener.admin."))); +} + TEST_P(AdminInstanceTest, AdminBadAddressOutPath) { std::string bad_path = TestEnvironment::temporaryPath("some/unlikely/bad/path/admin.address"); AdminImpl admin_bad_address_out_path(cpu_profile_path_, server_); From f51e927bb8f28dba6b631bebbd2656a1642734df Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Wed, 28 Apr 2021 08:58:40 -0700 Subject: [PATCH 094/209] api: add NonForwardingAction route action type (#16144) Signed-off-by: Mark D. Roth --- .../config/route/v3/route_components.proto | 16 +++++++++++++--- .../route/v4alpha/route_components.proto | 18 +++++++++++++++--- .../config/route/v3/route_components.proto | 16 +++++++++++++--- .../route/v4alpha/route_components.proto | 18 +++++++++++++++--- 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index add42f121abad..9532757cae487 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -202,7 +202,7 @@ message FilterAction { // // Envoy supports routing on HTTP method via :ref:`header matching // `. -// [#next-free-field: 18] +// [#next-free-field: 19] message Route { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.Route"; @@ -229,11 +229,17 @@ message Route { DirectResponseAction direct_response = 7; // [#not-implemented-hide:] - // If true, a filter will define the action (e.g., it could dynamically generate the - // RouteAction). + // A filter-defined action (e.g., it could dynamically generate the RouteAction). // [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when // implemented] FilterAction filter_action = 17; + + // [#not-implemented-hide:] + // An action used when the route will generate a response directly, + // without forwarding to an upstream host. This will be used in non-proxy + // xDS clients like the gRPC server. It could also be used in the future + // in Envoy for a filter that directly generates responses for requests. + NonForwardingAction non_forwarding_action = 18; } // The Metadata field can be used to provide additional information @@ -1469,6 +1475,10 @@ message DirectResponseAction { core.v3.DataSource body = 2; } +// [#not-implemented-hide:] +message NonForwardingAction { +} + message Decorator { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.Decorator"; diff --git a/api/envoy/config/route/v4alpha/route_components.proto b/api/envoy/config/route/v4alpha/route_components.proto index 072b5fb03232a..1d694986ece6f 100644 --- a/api/envoy/config/route/v4alpha/route_components.proto +++ b/api/envoy/config/route/v4alpha/route_components.proto @@ -200,7 +200,7 @@ message FilterAction { // // Envoy supports routing on HTTP method via :ref:`header matching // `. -// [#next-free-field: 18] +// [#next-free-field: 19] message Route { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.Route"; @@ -227,11 +227,17 @@ message Route { DirectResponseAction direct_response = 7; // [#not-implemented-hide:] - // If true, a filter will define the action (e.g., it could dynamically generate the - // RouteAction). + // A filter-defined action (e.g., it could dynamically generate the RouteAction). // [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when // implemented] FilterAction filter_action = 17; + + // [#not-implemented-hide:] + // An action used when the route will generate a response directly, + // without forwarding to an upstream host. This will be used in non-proxy + // xDS clients like the gRPC server. It could also be used in the future + // in Envoy for a filter that directly generates responses for requests. + NonForwardingAction non_forwarding_action = 18; } // The Metadata field can be used to provide additional information @@ -1411,6 +1417,12 @@ message DirectResponseAction { core.v4alpha.DataSource body = 2; } +// [#not-implemented-hide:] +message NonForwardingAction { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.route.v3.NonForwardingAction"; +} + message Decorator { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.Decorator"; diff --git a/generated_api_shadow/envoy/config/route/v3/route_components.proto b/generated_api_shadow/envoy/config/route/v3/route_components.proto index 3072e7445143a..c8644ab050eba 100644 --- a/generated_api_shadow/envoy/config/route/v3/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v3/route_components.proto @@ -204,7 +204,7 @@ message FilterAction { // // Envoy supports routing on HTTP method via :ref:`header matching // `. -// [#next-free-field: 18] +// [#next-free-field: 19] message Route { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.Route"; @@ -229,11 +229,17 @@ message Route { DirectResponseAction direct_response = 7; // [#not-implemented-hide:] - // If true, a filter will define the action (e.g., it could dynamically generate the - // RouteAction). + // A filter-defined action (e.g., it could dynamically generate the RouteAction). // [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when // implemented] FilterAction filter_action = 17; + + // [#not-implemented-hide:] + // An action used when the route will generate a response directly, + // without forwarding to an upstream host. This will be used in non-proxy + // xDS clients like the gRPC server. It could also be used in the future + // in Envoy for a filter that directly generates responses for requests. + NonForwardingAction non_forwarding_action = 18; } // The Metadata field can be used to provide additional information @@ -1493,6 +1499,10 @@ message DirectResponseAction { core.v3.DataSource body = 2; } +// [#not-implemented-hide:] +message NonForwardingAction { +} + message Decorator { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.Decorator"; diff --git a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto index bf4d50ca3b212..edb91bc832e2e 100644 --- a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto @@ -201,7 +201,7 @@ message FilterAction { // // Envoy supports routing on HTTP method via :ref:`header matching // `. -// [#next-free-field: 18] +// [#next-free-field: 19] message Route { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.Route"; @@ -228,11 +228,17 @@ message Route { DirectResponseAction direct_response = 7; // [#not-implemented-hide:] - // If true, a filter will define the action (e.g., it could dynamically generate the - // RouteAction). + // A filter-defined action (e.g., it could dynamically generate the RouteAction). // [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when // implemented] FilterAction filter_action = 17; + + // [#not-implemented-hide:] + // An action used when the route will generate a response directly, + // without forwarding to an upstream host. This will be used in non-proxy + // xDS clients like the gRPC server. It could also be used in the future + // in Envoy for a filter that directly generates responses for requests. + NonForwardingAction non_forwarding_action = 18; } // The Metadata field can be used to provide additional information @@ -1483,6 +1489,12 @@ message DirectResponseAction { core.v4alpha.DataSource body = 2; } +// [#not-implemented-hide:] +message NonForwardingAction { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.route.v3.NonForwardingAction"; +} + message Decorator { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.Decorator"; From 3d77e1839c07cdf51b54fb872f60b1476043845f Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 28 Apr 2021 17:47:31 +0100 Subject: [PATCH 095/209] shellcheck: Enable and cleanup .github file (#16206) Signed-off-by: Ryan Northey --- .github/workflows/get_build_targets.sh | 28 +++++++++++--------- tools/code_format/check_shellcheck_format.sh | 2 +- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.github/workflows/get_build_targets.sh b/.github/workflows/get_build_targets.sh index 874af4aefcc13..8304ef77233a3 100755 --- a/.github/workflows/get_build_targets.sh +++ b/.github/workflows/get_build_targets.sh @@ -5,24 +5,28 @@ readonly SEARCH_FOLDER="//source/common/..." set -e -o pipefail -function get_targets() { - # Comparing the PR HEAD with the upstream main HEAD. - git diff --name-only HEAD FETCH_HEAD | while IFS= read -r line - do - # Only targets under those folders. - case "$line" in - source/*|include/*) - bazel query "rdeps($SEARCH_FOLDER, $line, 1)" 2>/dev/null - ;; - esac +function compare_head () { + # Comparing the PR HEAD with the upstream main HEAD. + git diff --name-only HEAD FETCH_HEAD | while IFS= read -r line; do + # Only targets under those folders. + case "$line" in + source/*|include/*) + bazel query "rdeps($SEARCH_FOLDER, $line, 1)" 2>/dev/null + ;; + esac + done +} + +get_targets () { # This chain of commands from left to right are: # 1. Excluding the redundant .cc/.h targets that bazel query emits. # 2. Storing only the unique output. # 3. Limiting to the first 3 targets. - done | grep -v '\.cc\|\.h' | sort -u | head -n 3 + compare_head | grep -v '\.cc\|\.h' | sort -u | head -n 3 } # Fetching the upstream HEAD to compare with and stored in FETCH_HEAD. git fetch https://github.com/envoyproxy/envoy.git main 2>/dev/null -export BUILD_TARGETS_LOCAL=$(echo $(get_targets)) +BUILD_TARGETS_LOCAL=$(get_targets || :) +export BUILD_TARGETS_LOCAL diff --git a/tools/code_format/check_shellcheck_format.sh b/tools/code_format/check_shellcheck_format.sh index 7352e24c10016..b09ba6cb08f07 100755 --- a/tools/code_format/check_shellcheck_format.sh +++ b/tools/code_format/check_shellcheck_format.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -EXCLUDED_SHELLFILES=${EXCLUDED_SHELLFILES:-"^.github|.rst$|.md$"} +EXCLUDED_SHELLFILES=${EXCLUDED_SHELLFILES:-".rst$|.md$"} SHEBANG_RE='^#!/bin/bash|^#!/bin/sh|^#!/usr/bin/env bash|^#!/usr/bin/env sh' From eed29460a019e5d0fe410244d6b5888209788208 Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Thu, 29 Apr 2021 01:53:37 +0900 Subject: [PATCH 096/209] tracing: bump cpp2sky v0.2.1 (#15782) This PR replaces https://github.com/envoyproxy/envoy/pull/15333/. It upgrades cpp2sky version 0.1.0 -> 0.2.1. It also breaks backward compatibility before SkyWalking 8.3.0 because it uses namespaced-version of data collect protocol Additional Description: Risk Level: Low Testing: Unit Docs Changes: N/A Release Notes: Required Signed-off-by: Shikugawa --- bazel/repository_locations.bzl | 16 ++-- docs/root/version_history/current.rst | 2 + .../skywalking-tracing/docker-compose.yaml | 4 +- .../skywalking/skywalking_tracer_impl.cc | 12 +-- .../skywalking/skywalking_tracer_impl.h | 9 ++- .../skywalking/trace_segment_reporter.cc | 8 +- .../skywalking/trace_segment_reporter.h | 17 ++--- .../extensions/tracers/skywalking/tracer.cc | 23 +++--- source/extensions/tracers/skywalking/tracer.h | 73 +++++++++---------- .../skywalking/skywalking_test_helper.h | 46 ++++++------ .../skywalking/skywalking_tracer_impl_test.cc | 14 ++-- .../skywalking/trace_segment_reporter_test.cc | 18 ++--- .../tracers/skywalking/tracer_test.cc | 4 +- 13 files changed, 125 insertions(+), 121 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 98d40cd28bd36..658f61ac2a74d 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -322,26 +322,26 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_desc = "Data Collect Protocols of Apache SkyWalking", project_url = "https://github.com/apache/skywalking-data-collect-protocol", name = "skywalking_data_collect_protocol", - sha256 = "fa9ac679624217f30b6e8d5c450365386c610e2d08188a20f0340c3b14401833", - urls = ["https://github.com/apache/skywalking-data-collect-protocol/archive/v8.3.0.zip"], - strip_prefix = "skywalking-data-collect-protocol-8.3.0", - version = "8.3.0", + sha256 = "edfa970394511213eacc8055b4c13e4e9773e9196122a49e0db68f6162f67dff", + urls = ["https://github.com/apache/skywalking-data-collect-protocol/archive/v{version}.tar.gz"], + strip_prefix = "skywalking-data-collect-protocol-{version}", + version = "8.4.0", use_category = ["observability_ext"], extensions = ["envoy.tracers.skywalking"], - release_date = "2020-11-20", + release_date = "2021-01-20", cpe = "N/A", ), com_github_skyapm_cpp2sky = dict( project_name = "cpp2sky", project_desc = "C++ SDK for Apache SkyWalking", project_url = "https://github.com/SkyAPM/cpp2sky", - sha256 = "a8d870bb4b1c4a05eae319f689d1948927f3f0a5b5fe524db73a4c04121a339a", - version = "0.1.1", + sha256 = "76117a63cf29355c28a75bc83bd1d7e5bc004039445e7c854ee752dfe66094e6", + version = "0.2.1", strip_prefix = "cpp2sky-{version}", urls = ["https://github.com/SkyAPM/cpp2sky/archive/v{version}.tar.gz"], use_category = ["observability_ext"], extensions = ["envoy.tracers.skywalking"], - release_date = "2021-01-15", + release_date = "2021-03-17", cpe = "N/A", ), com_github_datadog_dd_opentracing_cpp = dict( diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 50b877436139f..9fb6915cf1401 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -5,6 +5,8 @@ Incompatible Behavior Changes ----------------------------- *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* +* tracing: update Apache SkyWalking tracer version to be compatible with 8.4.0 data collect protocol. This change will introduce incompatibility with SkyWalking 8.3.0. + Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/examples/skywalking-tracing/docker-compose.yaml b/examples/skywalking-tracing/docker-compose.yaml index 359f7be74dca0..e09ddb4b141a6 100644 --- a/examples/skywalking-tracing/docker-compose.yaml +++ b/examples/skywalking-tracing/docker-compose.yaml @@ -56,7 +56,7 @@ services: soft: -1 hard: -1 skywalking-oap: - image: apache/skywalking-oap-server:8.2.0-es7 + image: apache/skywalking-oap-server:8.4.0-es7 networks: - envoymesh depends_on: @@ -72,7 +72,7 @@ services: start_period: 40s restart: on-failure skywalking-ui: - image: apache/skywalking-ui:8.2.0 + image: apache/skywalking-ui:8.4.0 networks: - envoymesh depends_on: diff --git a/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc index ce07ab127bd6f..92e17d54966f7 100644 --- a/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc +++ b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc @@ -32,7 +32,7 @@ Driver::Driver(const envoy::config::trace::v3::SkyWalkingConfig& proto_config, POOL_COUNTER_PREFIX(context.serverFactoryContext().scope(), "tracing.skywalking."))}, tls_slot_ptr_(context.serverFactoryContext().threadLocal().allocateSlot()) { loadConfig(proto_config.client_config(), context.serverFactoryContext()); - segment_context_factory_ = createSegmentContextFactory(config_); + tracing_context_factory_ = std::make_unique(config_); auto& factory_context = context.serverFactoryContext(); tls_slot_ptr_->set([proto_config, &factory_context, this](Event::Dispatcher& dispatcher) { TracerPtr tracer = std::make_unique(std::make_unique( @@ -49,28 +49,28 @@ Tracing::SpanPtr Driver::startSpan(const Tracing::Config& config, const std::string& operation_name, Envoy::SystemTime start_time, const Tracing::Decision decision) { auto& tracer = tls_slot_ptr_->getTyped().tracer(); - SegmentContextPtr segment_context; + TracingContextPtr tracing_context; // TODO(shikugawa): support extension span header. auto propagation_header = request_headers.get(skywalkingPropagationHeaderKey()); if (propagation_header.empty()) { - segment_context = segment_context_factory_->create(); + tracing_context = tracing_context_factory_->create(); // Sampling status is always true on SkyWalking. But with disabling skip_analysis, // this span can't be analyzed. if (!decision.traced) { - segment_context->setSkipAnalysis(); + tracing_context->setSkipAnalysis(); } } else { auto header_value_string = propagation_header[0]->value().getStringView(); try { SpanContextPtr span_context = createSpanContext(header_value_string); - segment_context = segment_context_factory_->create(span_context); + tracing_context = tracing_context_factory_->create(span_context); } catch (TracerException& e) { ENVOY_LOG(warn, "New SkyWalking Span/Segment cannot be created for error: {}", e.what()); return std::make_unique(); } } - return tracer.startSpan(config, start_time, operation_name, segment_context, nullptr); + return tracer.startSpan(config, start_time, operation_name, tracing_context, nullptr); } void Driver::loadConfig(const envoy::config::trace::v3::ClientConfig& client_config, diff --git a/source/extensions/tracers/skywalking/skywalking_tracer_impl.h b/source/extensions/tracers/skywalking/skywalking_tracer_impl.h index e6639b5ad36cd..0b64b37efe16c 100644 --- a/source/extensions/tracers/skywalking/skywalking_tracer_impl.h +++ b/source/extensions/tracers/skywalking/skywalking_tracer_impl.h @@ -7,19 +7,20 @@ #include "common/tracing/http_tracer_impl.h" +#include "source/tracing_context_impl.h" + #include "extensions/tracers/skywalking/tracer.h" #include "cpp2sky/exception.h" -#include "cpp2sky/segment_context.h" namespace Envoy { namespace Extensions { namespace Tracers { namespace SkyWalking { -using cpp2sky::SegmentContextFactoryPtr; -using cpp2sky::SegmentContextPtr; using cpp2sky::TracerConfig; +using cpp2sky::TracingContextFactory; +using cpp2sky::TracingContextPtr; class Driver : public Tracing::Driver, public Logger::Loggable { public: @@ -47,7 +48,7 @@ class Driver : public Tracing::Driver, public Logger::Loggable tracing_context_factory_; }; using DriverPtr = std::unique_ptr; diff --git a/source/extensions/tracers/skywalking/trace_segment_reporter.cc b/source/extensions/tracers/skywalking/trace_segment_reporter.cc index eec47fd69c19f..5d81ac68f240f 100644 --- a/source/extensions/tracers/skywalking/trace_segment_reporter.cc +++ b/source/extensions/tracers/skywalking/trace_segment_reporter.cc @@ -21,7 +21,7 @@ TraceSegmentReporter::TraceSegmentReporter(Grpc::AsyncClientFactoryPtr&& factory uint32_t delayed_buffer_size, const std::string& token) : tracing_stats_(stats), client_(factory->create()), service_method_(*Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "TraceSegmentReportService.collect")), + "skywalking.v3.TraceSegmentReportService.collect")), random_generator_(random_generator), token_(token), delayed_buffer_size_(delayed_buffer_size) { @@ -42,9 +42,9 @@ void TraceSegmentReporter::onCreateInitialMetadata(Http::RequestHeaderMap& metad } } -void TraceSegmentReporter::report(SegmentContextPtr segment_context) { - ASSERT(segment_context); - auto request = segment_context->createSegmentObject(); +void TraceSegmentReporter::report(TracingContextPtr tracing_context) { + ASSERT(tracing_context); + auto request = tracing_context->createSegmentObject(); ENVOY_LOG(trace, "Try to report segment to SkyWalking Server:\n{}", request.DebugString()); if (stream_ != nullptr) { diff --git a/source/extensions/tracers/skywalking/trace_segment_reporter.h b/source/extensions/tracers/skywalking/trace_segment_reporter.h index 35e4841fbb028..a54885a756714 100644 --- a/source/extensions/tracers/skywalking/trace_segment_reporter.h +++ b/source/extensions/tracers/skywalking/trace_segment_reporter.h @@ -5,23 +5,22 @@ #include "envoy/config/trace/v3/skywalking.pb.h" #include "envoy/grpc/async_client_manager.h" -#include "common/Common.pb.h" #include "common/common/backoff_strategy.h" #include "common/grpc/async_client_impl.h" #include "extensions/tracers/skywalking/skywalking_stats.h" -#include "cpp2sky/segment_context.h" +#include "cpp2sky/tracing_context.h" namespace Envoy { namespace Extensions { namespace Tracers { namespace SkyWalking { -using cpp2sky::SegmentContextPtr; +using cpp2sky::TracingContextPtr; class TraceSegmentReporter : public Logger::Loggable, - public Grpc::AsyncStreamCallbacks { + public Grpc::AsyncStreamCallbacks { public: explicit TraceSegmentReporter(Grpc::AsyncClientFactoryPtr&& factory, Event::Dispatcher& dispatcher, Random::RandomGenerator& random, @@ -32,11 +31,11 @@ class TraceSegmentReporter : public Logger::Loggable, // Grpc::AsyncStreamCallbacks void onCreateInitialMetadata(Http::RequestHeaderMap& metadata) override; void onReceiveInitialMetadata(Http::ResponseHeaderMapPtr&&) override {} - void onReceiveMessage(std::unique_ptr&&) override {} + void onReceiveMessage(std::unique_ptr&&) override {} void onReceiveTrailingMetadata(Http::ResponseTrailerMapPtr&&) override {} void onRemoteClose(Grpc::Status::GrpcStatus, const std::string&) override; - void report(SegmentContextPtr segment_context); + void report(TracingContextPtr tracing_context); private: /* @@ -49,13 +48,13 @@ class TraceSegmentReporter : public Logger::Loggable, void setRetryTimer(); SkyWalkingTracerStats& tracing_stats_; - Grpc::AsyncClient client_; - Grpc::AsyncStream stream_{}; + Grpc::AsyncClient client_; + Grpc::AsyncStream stream_{}; const Protobuf::MethodDescriptor& service_method_; Random::RandomGenerator& random_generator_; // If the connection is unavailable when reporting data, the created SegmentObject will be cached // in the queue, and when a new connection is established, the cached data will be reported. - std::queue delayed_segments_cache_; + std::queue delayed_segments_cache_; Event::TimerPtr retry_timer_; BackOffStrategyPtr backoff_strategy_; std::string token_; diff --git a/source/extensions/tracers/skywalking/tracer.cc b/source/extensions/tracers/skywalking/tracer.cc index 9c6644ada262d..437a558dccf33 100644 --- a/source/extensions/tracers/skywalking/tracer.cc +++ b/source/extensions/tracers/skywalking/tracer.cc @@ -41,24 +41,26 @@ void Span::log(SystemTime, const std::string& event) { span_entity_->addLog(EMPT void Span::finishSpan() { span_entity_->endSpan(); - parent_tracer_.sendSegment(segment_context_); + parent_tracer_.sendSegment(tracing_context_); } void Span::injectContext(Http::RequestHeaderMap& request_headers) { - request_headers.setReferenceKey( - skywalkingPropagationHeaderKey(), - segment_context_->createSW8HeaderValue(std::string(request_headers.getHostValue()))); + auto sw8_header = + tracing_context_->createSW8HeaderValue(std::string(request_headers.getHostValue())); + if (sw8_header.has_value()) { + request_headers.setReferenceKey(skywalkingPropagationHeaderKey(), sw8_header.value()); + } } Tracing::SpanPtr Span::spawnChild(const Tracing::Config&, const std::string& name, SystemTime) { - auto child_span = segment_context_->createCurrentSegmentSpan(span_entity_); + auto child_span = tracing_context_->createExitSpan(span_entity_); child_span->startSpan(name); - return std::make_unique(child_span, segment_context_, parent_tracer_); + return std::make_unique(child_span, tracing_context_, parent_tracer_); } Tracer::Tracer(TraceSegmentReporterPtr reporter) : reporter_(std::move(reporter)) {} -void Tracer::sendSegment(SegmentContextPtr segment_context) { +void Tracer::sendSegment(TracingContextPtr segment_context) { ASSERT(reporter_); if (segment_context->readyToSend()) { reporter_->report(std::move(segment_context)); @@ -66,11 +68,10 @@ void Tracer::sendSegment(SegmentContextPtr segment_context) { } Tracing::SpanPtr Tracer::startSpan(const Tracing::Config&, SystemTime, const std::string& operation, - SegmentContextPtr segment_context, - CurrentSegmentSpanPtr parent) { + TracingContextPtr segment_context, TracingSpanPtr parent) { Tracing::SpanPtr span; - auto span_entity = parent != nullptr ? segment_context->createCurrentSegmentSpan(parent) - : segment_context->createCurrentSegmentRootSpan(); + auto span_entity = parent != nullptr ? segment_context->createExitSpan(parent) + : segment_context->createEntrySpan(); span_entity->startSpan(operation); span = std::make_unique(span_entity, segment_context, *this); return span; diff --git a/source/extensions/tracers/skywalking/tracer.h b/source/extensions/tracers/skywalking/tracer.h index e55e4cbb6fff7..3852d1718b90a 100644 --- a/source/extensions/tracers/skywalking/tracer.h +++ b/source/extensions/tracers/skywalking/tracer.h @@ -6,8 +6,7 @@ #include "extensions/tracers/skywalking/trace_segment_reporter.h" -#include "cpp2sky/segment_context.h" -#include "cpp2sky/tracer.h" +#include "cpp2sky/tracing_context.h" #include "cpp2sky/well_known_names.h" namespace Envoy { @@ -15,42 +14,12 @@ namespace Extensions { namespace Tracers { namespace SkyWalking { -using cpp2sky::CurrentSegmentSpanPtr; -using cpp2sky::SegmentContextPtr; -using SkywalkingTracer = cpp2sky::Tracer; +using cpp2sky::TracingContextPtr; +using cpp2sky::TracingSpanPtr; const Http::LowerCaseString& skywalkingPropagationHeaderKey(); -class Span : public Tracing::Span { -public: - Span(CurrentSegmentSpanPtr span_entity, SegmentContextPtr segment_context, - SkywalkingTracer& parent_tracer) - : parent_tracer_(parent_tracer), span_entity_(span_entity), - segment_context_(segment_context) {} - - // Tracing::Span - void setOperation(absl::string_view) override {} - void setTag(absl::string_view name, absl::string_view value) override; - void log(SystemTime timestam, const std::string& event) override; - void finishSpan() override; - void injectContext(Http::RequestHeaderMap& request_headers) override; - Tracing::SpanPtr spawnChild(const Tracing::Config& config, const std::string& name, - SystemTime start_time) override; - void setSampled(bool do_sample) override; - std::string getBaggage(absl::string_view) override { return EMPTY_STRING; } - void setBaggage(absl::string_view, absl::string_view) override {} - std::string getTraceIdAsHex() const override { return EMPTY_STRING; } - - const SegmentContextPtr segmentContext() { return segment_context_; } - const CurrentSegmentSpanPtr spanEntity() { return span_entity_; } - -private: - SkywalkingTracer& parent_tracer_; - CurrentSegmentSpanPtr span_entity_; - SegmentContextPtr segment_context_; -}; - -class Tracer : public SkywalkingTracer { +class Tracer { public: Tracer(TraceSegmentReporterPtr reporter); @@ -59,7 +28,7 @@ class Tracer : public SkywalkingTracer { * * @param segment_context The segment context. */ - void sendSegment(SegmentContextPtr segment_context) override; + void sendSegment(TracingContextPtr tracing_context); /* * Create a new span based on the segment context and parent span. @@ -75,8 +44,8 @@ class Tracer : public SkywalkingTracer { * @return The unique ptr to the newly created span. */ Tracing::SpanPtr startSpan(const Tracing::Config& config, SystemTime start_time, - const std::string& operation, SegmentContextPtr segment_context, - CurrentSegmentSpanPtr parent); + const std::string& operation, TracingContextPtr tracing_context, + TracingSpanPtr parent); private: TraceSegmentReporterPtr reporter_; @@ -84,6 +53,34 @@ class Tracer : public SkywalkingTracer { using TracerPtr = std::unique_ptr; +class Span : public Tracing::Span { +public: + Span(TracingSpanPtr span_entity, TracingContextPtr tracing_context, Tracer& parent_tracer) + : parent_tracer_(parent_tracer), span_entity_(span_entity), + tracing_context_(tracing_context) {} + + // Tracing::Span + void setOperation(absl::string_view) override {} + void setTag(absl::string_view name, absl::string_view value) override; + void log(SystemTime timestamp, const std::string& event) override; + void finishSpan() override; + void injectContext(Http::RequestHeaderMap& request_headers) override; + Tracing::SpanPtr spawnChild(const Tracing::Config& config, const std::string& name, + SystemTime start_time) override; + void setSampled(bool do_sample) override; + std::string getBaggage(absl::string_view) override { return EMPTY_STRING; } + void setBaggage(absl::string_view, absl::string_view) override {} + std::string getTraceIdAsHex() const override { return EMPTY_STRING; } + + const TracingContextPtr tracingContext() { return tracing_context_; } + const TracingSpanPtr spanEntity() { return span_entity_; } + +private: + Tracer& parent_tracer_; + TracingSpanPtr span_entity_; + TracingContextPtr tracing_context_; +}; + } // namespace SkyWalking } // namespace Tracers } // namespace Extensions diff --git a/test/extensions/tracers/skywalking/skywalking_test_helper.h b/test/extensions/tracers/skywalking/skywalking_test_helper.h index e49ac3b3bd77f..51b25531d2d0d 100644 --- a/test/extensions/tracers/skywalking/skywalking_test_helper.h +++ b/test/extensions/tracers/skywalking/skywalking_test_helper.h @@ -3,23 +3,25 @@ #include "common/common/base64.h" #include "common/common/hex.h" +#include "source/tracing_context_impl.h" + #include "test/test_common/utility.h" #include "cpp2sky/config.pb.h" #include "cpp2sky/propagation.h" -#include "cpp2sky/segment_context.h" +#include "cpp2sky/tracing_context.h" namespace Envoy { namespace Extensions { namespace Tracers { namespace SkyWalking { -using cpp2sky::createSegmentContextFactory; using cpp2sky::createSpanContext; -using cpp2sky::CurrentSegmentSpanPtr; -using cpp2sky::SegmentContextPtr; using cpp2sky::SpanContextPtr; using cpp2sky::TracerConfig; +using cpp2sky::TracingContextFactory; +using cpp2sky::TracingContextPtr; +using cpp2sky::TracingSpanPtr; /* * A simple helper class for auxiliary testing. Contains some simple static functions, such as @@ -39,10 +41,12 @@ class SkyWalkingTestHelper { TracerConfig config; config.set_service_name(seed + "#SERVICE"); config.set_instance_name(seed + "#INSTANCE"); - auto segment_context_factory = createSegmentContextFactory(config); - auto segment_context = segment_context_factory->create(); - auto span = segment_context->createCurrentSegmentRootSpan(); + auto tracing_context_factory = std::make_unique(config); + auto tracing_context = tracing_context_factory->create(); + + auto entry_span = tracing_context->createEntrySpan(); + auto span = tracing_context->createExitSpan(entry_span); span->startSpan(seed + "#OPERATION"); span->setPeer(seed + "#ADDRESS"); @@ -51,16 +55,17 @@ class SkyWalkingTestHelper { } span->endSpan(); + entry_span->endSpan(); - return segment_context->createSW8HeaderValue(seed + "#ENDPOINT"); + return tracing_context->createSW8HeaderValue(seed + "#ENDPOINT").value(); } - static SegmentContextPtr createSegmentContext(bool sampled, std::string seed, + static TracingContextPtr createSegmentContext(bool sampled, std::string seed, std::string prev_seed) { TracerConfig config; config.set_service_name(seed + "#SERVICE"); config.set_instance_name(seed + "#INSTANCE"); - auto segment_context_factory = createSegmentContextFactory(config); + auto tracing_context_factory = std::make_unique(config); SpanContextPtr previous_span_context; if (!prev_seed.empty()) { @@ -69,25 +74,24 @@ class SkyWalkingTestHelper { ASSERT(previous_span_context); } - SegmentContextPtr segment_context; + TracingContextPtr tracing_context; if (previous_span_context) { - segment_context = segment_context_factory->create(previous_span_context); + tracing_context = tracing_context_factory->create(previous_span_context); } else { - segment_context = segment_context_factory->create(); + tracing_context = tracing_context_factory->create(); if (!sampled) { - segment_context->setSkipAnalysis(); + tracing_context->setSkipAnalysis(); } } - return segment_context; + return tracing_context; } - static CurrentSegmentSpanPtr createSpanStore(SegmentContextPtr segment_context, - CurrentSegmentSpanPtr parent_span_store, - std::string seed, bool sample = true) { - auto span_store = parent_span_store - ? segment_context->createCurrentSegmentSpan(parent_span_store) - : segment_context->createCurrentSegmentRootSpan(); + static TracingSpanPtr createSpanStore(TracingContextPtr tracing_context, + TracingSpanPtr parent_span_store, std::string seed, + bool sample = true) { + auto span_store = parent_span_store ? tracing_context->createExitSpan(parent_span_store) + : tracing_context->createEntrySpan(); span_store->startSpan(seed + "#OPERATION"); span_store->setPeer("0.0.0.0"); diff --git a/test/extensions/tracers/skywalking/skywalking_tracer_impl_test.cc b/test/extensions/tracers/skywalking/skywalking_tracer_impl_test.cc index 7fe3e637b6c9d..985c366ea1b92 100644 --- a/test/extensions/tracers/skywalking/skywalking_tracer_impl_test.cc +++ b/test/extensions/tracers/skywalking/skywalking_tracer_impl_test.cc @@ -88,11 +88,11 @@ TEST_F(SkyWalkingDriverTest, SkyWalkingDriverStartSpanTestWithClientConfig) { Span* span = dynamic_cast(org_span.get()); ASSERT(span); - EXPECT_EQ("FAKE_FAKE_FAKE", span->segmentContext()->service()); - EXPECT_EQ("FAKE_FAKE_FAKE", span->segmentContext()->serviceInstance()); + EXPECT_EQ("FAKE_FAKE_FAKE", span->tracingContext()->service()); + EXPECT_EQ("FAKE_FAKE_FAKE", span->tracingContext()->serviceInstance()); // Tracing decision will be overwrite by skip analysis flag in propagation headers. - EXPECT_FALSE(span->segmentContext()->skipAnalysis()); + EXPECT_FALSE(span->tracingContext()->skipAnalysis()); // Since the sampling flag is false, no segment data is reported. EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)); @@ -112,7 +112,7 @@ TEST_F(SkyWalkingDriverTest, SkyWalkingDriverStartSpanTestWithClientConfig) { Span* span = dynamic_cast(org_span.get()); ASSERT(span); - EXPECT_FALSE(span->segmentContext()->skipAnalysis()); + EXPECT_FALSE(span->tracingContext()->skipAnalysis()); EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)); span->finishSpan(); @@ -147,7 +147,7 @@ TEST_F(SkyWalkingDriverTest, SkyWalkingDriverStartSpanTestWithClientConfig) { Span* new_span = dynamic_cast(span.get()); ASSERT(new_span); - EXPECT_TRUE(new_span->segmentContext()->skipAnalysis()); + EXPECT_TRUE(new_span->tracingContext()->skipAnalysis()); EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)); span->finishSpan(); @@ -175,8 +175,8 @@ TEST_F(SkyWalkingDriverTest, SkyWalkingDriverStartSpanTestNoClientConfig) { Span* span = dynamic_cast(org_span.get()); ASSERT(span); - EXPECT_EQ(test_string, span->segmentContext()->service()); - EXPECT_EQ(test_string, span->segmentContext()->serviceInstance()); + EXPECT_EQ(test_string, span->tracingContext()->service()); + EXPECT_EQ(test_string, span->tracingContext()->serviceInstance()); } } // namespace diff --git a/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc b/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc index 67c3c6c2ce2d9..8026d8d6d7af6 100644 --- a/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc +++ b/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc @@ -97,13 +97,13 @@ TEST_F(TraceSegmentReporterTest, TraceSegmentReporterReportTraceSegment) { setupTraceSegmentReporter("{}"); ON_CALL(mock_random_generator_, random()).WillByDefault(Return(23333)); - SegmentContextPtr segment_context = + TracingContextPtr segment_context = SkyWalkingTestHelper::createSegmentContext(true, "NEW", "PRE"); - CurrentSegmentSpanPtr parent_store = + TracingSpanPtr parent_store = SkyWalkingTestHelper::createSpanStore(segment_context, nullptr, "PARENT"); // Skip reporting the first child span. - CurrentSegmentSpanPtr first_child_sptore = + TracingSpanPtr first_child_sptore = SkyWalkingTestHelper::createSpanStore(segment_context, parent_store, "CHILD", false); // Create second child span. @@ -119,7 +119,7 @@ TEST_F(TraceSegmentReporterTest, TraceSegmentReporterReportTraceSegment) { EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); // Create a segment context with no previous span context. - SegmentContextPtr second_segment_context = + TracingContextPtr second_segment_context = SkyWalkingTestHelper::createSegmentContext(true, "SECOND_SEGMENT", ""); SkyWalkingTestHelper::createSpanStore(second_segment_context, nullptr, "PARENT"); @@ -136,9 +136,9 @@ TEST_F(TraceSegmentReporterTest, TraceSegmentReporterReportWithDefaultCache) { setupTraceSegmentReporter("{}"); ON_CALL(mock_random_generator_, random()).WillByDefault(Return(23333)); - SegmentContextPtr segment_context = + TracingContextPtr segment_context = SkyWalkingTestHelper::createSegmentContext(true, "NEW", "PRE"); - CurrentSegmentSpanPtr parent_store = + TracingSpanPtr parent_store = SkyWalkingTestHelper::createSpanStore(segment_context, nullptr, "PARENT"); SkyWalkingTestHelper::createSpanStore(segment_context, parent_store, "CHILD"); @@ -186,9 +186,9 @@ TEST_F(TraceSegmentReporterTest, TraceSegmentReporterReportWithCacheConfig) { ON_CALL(mock_random_generator_, random()).WillByDefault(Return(23333)); - SegmentContextPtr segment_context = + TracingContextPtr segment_context = SkyWalkingTestHelper::createSegmentContext(true, "NEW", "PRE"); - CurrentSegmentSpanPtr parent_store = + TracingSpanPtr parent_store = SkyWalkingTestHelper::createSpanStore(segment_context, nullptr, "PARENT"); SkyWalkingTestHelper::createSpanStore(segment_context, parent_store, "CHILD"); @@ -231,7 +231,7 @@ TEST_F(TraceSegmentReporterTest, CallAsyncCallbackAndNothingTodo) { setupTraceSegmentReporter("{}"); reporter_->onReceiveInitialMetadata(std::make_unique()); reporter_->onReceiveTrailingMetadata(std::make_unique()); - reporter_->onReceiveMessage(std::make_unique()); + reporter_->onReceiveMessage(std::make_unique()); } } // namespace diff --git a/test/extensions/tracers/skywalking/tracer_test.cc b/test/extensions/tracers/skywalking/tracer_test.cc index aee24271dfe96..75522ab296b61 100644 --- a/test/extensions/tracers/skywalking/tracer_test.cc +++ b/test/extensions/tracers/skywalking/tracer_test.cc @@ -81,7 +81,7 @@ TEST_F(TracerTest, TracerTestCreateNewSpanWithNoPropagationHeaders) { { Span* span = dynamic_cast(org_span.get()); - EXPECT_TRUE(span->spanEntity()->spanType() == SpanType::Entry); + EXPECT_TRUE(span->spanEntity()->spanType() == skywalking::v3::SpanType::Entry); EXPECT_EQ("", span->getBaggage("FakeStringAndNothingToDo")); span->setOperation("FakeStringAndNothingToDo"); span->setBaggage("FakeStringAndNothingToDo", "FakeStringAndNothingToDo"); @@ -130,7 +130,7 @@ TEST_F(TracerTest, TracerTestCreateNewSpanWithNoPropagationHeaders) { Span* first_child_span = dynamic_cast(org_first_child_span.get()); - EXPECT_TRUE(first_child_span->spanEntity()->spanType() == SpanType::Exit); + EXPECT_TRUE(first_child_span->spanEntity()->spanType() == skywalking::v3::SpanType::Exit); EXPECT_FALSE(first_child_span->spanEntity()->skipAnalysis()); EXPECT_EQ(1, first_child_span->spanEntity()->spanId()); From aa3d8a1fcb598ac968fc5f805911ae7cf00628d6 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 28 Apr 2021 13:28:01 -0400 Subject: [PATCH 097/209] http: removing envoy.reloadable_features.http_set_copy_replace_all_headers (#16209) Risk Level: low Testing: n/a Docs Changes: n/a Release Notes: inline Fixes #16018 Signed-off-by: Alyssa Wilk --- docs/root/version_history/current.rst | 1 + source/common/http/header_map_impl.cc | 15 +------ source/common/runtime/runtime_features.cc | 1 - test/common/http/header_map_impl_test.cc | 50 +---------------------- 4 files changed, 4 insertions(+), 63 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 9fb6915cf1401..eb17d1e04c264 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -32,6 +32,7 @@ Removed Config or Runtime * http: removed ``envoy.reloadable_features.allow_500_after_100`` runtime guard and the legacy code path. * http: removed ``envoy.reloadable_features.hcm_stream_error_on_invalid_message`` for disabling closing HTTP/1.1 connections on error. Connection-closing can still be disabled by setting the HTTP/1 configuration :ref:`override_stream_error_on_invalid_http_message `. +* http: removed ``envoy.reloadable_features.http_set_copy_replace_all_headers`` runtime guard and legacy code paths. * http: removed ``envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2``; Envoy will now always send GOAWAY to HTTP2 downstreams when the :ref:`disable_keepalive ` overload action is active. * http: removed ``envoy.reloadable_features.http_match_on_all_headers`` runtime guard and legacy code paths. * http: removed ``envoy.reloadable_features.unify_grpc_handling`` runtime guard and legacy code paths. diff --git a/source/common/http/header_map_impl.cc b/source/common/http/header_map_impl.cc index 4dd22ee29b560..cc83f950e7d27 100644 --- a/source/common/http/header_map_impl.cc +++ b/source/common/http/header_map_impl.cc @@ -449,19 +449,8 @@ void HeaderMapImpl::setReferenceKey(const LowerCaseString& key, absl::string_vie } void HeaderMapImpl::setCopy(const LowerCaseString& key, absl::string_view value) { - if (!Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.http_set_copy_replace_all_headers")) { - auto entry = getExisting(key); - if (!entry.empty()) { - updateSize(entry[0]->value().size(), value.size()); - entry[0]->value(value); - } else { - addCopy(key, value); - } - } else { - remove(key); - addCopy(key, value); - } + remove(key); + addCopy(key, value); } uint64_t HeaderMapImpl::byteSize() const { return cached_byte_size_; } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 8240a8b52f5fb..57d21153afd21 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -71,7 +71,6 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.hash_multiple_header_values", "envoy.reloadable_features.health_check.graceful_goaway_handling", "envoy.reloadable_features.health_check.immediate_failure_exclude_from_cluster", - "envoy.reloadable_features.http_set_copy_replace_all_headers", "envoy.reloadable_features.http_transport_failure_reason_in_body", "envoy.reloadable_features.http_upstream_wait_connect_response", "envoy.reloadable_features.http2_skip_encoding_empty_trailers", diff --git a/test/common/http/header_map_impl_test.cc b/test/common/http/header_map_impl_test.cc index 581d636e61cbc..9a1fef6241458 100644 --- a/test/common/http/header_map_impl_test.cc +++ b/test/common/http/header_map_impl_test.cc @@ -794,55 +794,7 @@ TEST_P(HeaderMapImplTest, SetReferenceKey) { EXPECT_EQ("monde", headers.get(foo)[0]->value().getStringView()); } -TEST_P(HeaderMapImplTest, SetCopyOldBehavior) { - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.http_set_copy_replace_all_headers", "false"}}); - - TestRequestHeaderMapImpl headers; - LowerCaseString foo("hello"); - headers.setCopy(foo, "world"); - EXPECT_EQ("world", headers.get(foo)[0]->value().getStringView()); - - // Overwrite value. - headers.setCopy(foo, "monde"); - EXPECT_EQ("monde", headers.get(foo)[0]->value().getStringView()); - - // Add another foo header. - headers.addCopy(foo, "monde2"); - EXPECT_EQ(headers.size(), 2); - - // Only the first foo header is overridden. - headers.setCopy(foo, "override-monde"); - EXPECT_EQ(headers.size(), 2); - - HeaderAndValueCb cb; - - InSequence seq; - EXPECT_CALL(cb, Call("hello", "override-monde")); - EXPECT_CALL(cb, Call("hello", "monde2")); - headers.iterate(cb.asIterateCb()); - - // Test setting an empty string and then overriding. - EXPECT_EQ(2UL, headers.remove(foo)); - EXPECT_EQ(headers.size(), 0); - const std::string empty; - headers.setCopy(foo, empty); - EXPECT_EQ(headers.size(), 1); - headers.setCopy(foo, "not-empty"); - EXPECT_EQ(headers.get(foo)[0]->value().getStringView(), "not-empty"); - - // Use setCopy with inline headers both indirectly and directly. - headers.clear(); - EXPECT_EQ(headers.size(), 0); - headers.setCopy(Headers::get().Path, "/"); - EXPECT_EQ(headers.size(), 1); - EXPECT_EQ(headers.getPathValue(), "/"); - headers.setPath("/foo"); - EXPECT_EQ(headers.size(), 1); - EXPECT_EQ(headers.getPathValue(), "/foo"); -} - -TEST_P(HeaderMapImplTest, SetCopyNewBehavior) { +TEST_P(HeaderMapImplTest, SetCopy) { TestRequestHeaderMapImpl headers; LowerCaseString foo("hello"); headers.setCopy(foo, "world"); From 6d4b75f87e90670e9310f3feb57bf61a0e00be66 Mon Sep 17 00:00:00 2001 From: htuch Date: Wed, 28 Apr 2021 14:09:55 -0400 Subject: [PATCH 098/209] owners: add @phlax as maintainer. (#16212) Welcome! Signed-off-by: Harvey Tuch --- OWNERS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OWNERS.md b/OWNERS.md index 5bd6d9adedcf7..c897e25414399 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -37,6 +37,8 @@ routing PRs, questions, etc. to the right place. * Lua, access logging, and general miscellany. * Joshua Marantz ([jmarantz](https://github.com/jmarantz)) (jmarantz@google.com) * Stats, abseil, scalability, and performance. +* Ryan Northey ([phlax](https://github.com/phlax)) (ryan@synca.io) + * Docs, tooling, CI, containers and sandbox examples * William A Rowe Jr ([wrowe](https://github.com/wrowe)) (wrowe@vmware.com) * Windows port and CI build, `bazel/foreign_cc` build and dependencies liason. * Antonio Vicente ([antoniovicente](https://github.com/antoniovicente)) (avd@google.com) From d86bd1140a53a32d7d5558af5be4ed778eaae179 Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Wed, 28 Apr 2021 14:39:15 -0400 Subject: [PATCH 099/209] delta-xds: avoid sending resource names for wildcard requests on stream reconnect (#16153) Signed-off-by: Adi Suissa-Peleg --- api/xds_protocol.rst | 11 ++- .../common/config/delta_subscription_state.cc | 17 ++-- .../common/config/delta_subscription_state.h | 5 +- source/common/config/new_grpc_mux_impl.cc | 12 +-- source/common/config/new_grpc_mux_impl.h | 9 +- .../config/delta_subscription_state_test.cc | 59 ++++++++++--- test/common/config/new_grpc_mux_impl_test.cc | 86 ++++++++++++++++++- test/integration/ads_integration_test.cc | 39 ++++++++- 8 files changed, 205 insertions(+), 33 deletions(-) diff --git a/api/xds_protocol.rst b/api/xds_protocol.rst index 46c7dacfb9661..edd12f6bb5c39 100644 --- a/api/xds_protocol.rst +++ b/api/xds_protocol.rst @@ -771,10 +771,13 @@ Later the xDS client spontaneously requests the "wc" resource. :alt: Incremental session example On reconnect the Incremental xDS client may tell the server of its known -resources to avoid resending them over the network. Because no state is -assumed to be preserved from the previous stream, the reconnecting -client must provide the server with all resource names it is interested -in. +resources to avoid resending them over the network by sending them in +:ref:`initial_resource_versions `. +Because no state is assumed to be preserved from the previous stream, the reconnecting +client must provide the server with all resource names it is interested in. Note that for wildcard +requests (CDS/LDS/SRDS), the request must have no resources in both +:ref:`resource_names_subscribe ` and +:ref:`resource_names_unsubscribe `. .. figure:: diagrams/incremental-reconnect.svg :alt: Incremental reconnect example diff --git a/source/common/config/delta_subscription_state.cc b/source/common/config/delta_subscription_state.cc index c667833a19620..215dc55602fbc 100644 --- a/source/common/config/delta_subscription_state.cc +++ b/source/common/config/delta_subscription_state.cc @@ -14,7 +14,7 @@ namespace Config { DeltaSubscriptionState::DeltaSubscriptionState(std::string type_url, UntypedConfigUpdateCallbacks& watch_map, const LocalInfo::LocalInfo& local_info, - Event::Dispatcher& dispatcher) + Event::Dispatcher& dispatcher, const bool wildcard) // TODO(snowp): Hard coding VHDS here is temporary until we can move it away from relying on // empty resources as updates. : supports_heartbeats_(type_url != "envoy.config.route.v3.VirtualHost"), @@ -29,8 +29,8 @@ DeltaSubscriptionState::DeltaSubscriptionState(std::string type_url, watch_map_.onConfigUpdate({}, removed_resources, ""); }, dispatcher, dispatcher.timeSource()), - type_url_(std::move(type_url)), watch_map_(watch_map), local_info_(local_info), - dispatcher_(dispatcher) {} + type_url_(std::move(type_url)), wildcard_(wildcard), watch_map_(watch_map), + local_info_(local_info), dispatcher_(dispatcher) {} void DeltaSubscriptionState::updateSubscriptionInterest( const absl::flat_hash_set& cur_added, @@ -178,8 +178,15 @@ DeltaSubscriptionState::getNextRequestAckless() { (*request.mutable_initial_resource_versions())[resource_name] = resource_state.version(); } // As mentioned above, fill resource_names_subscribe with everything, including names we - // have yet to receive any resource for. - names_added_.insert(resource_name); + // have yet to receive any resource for unless this is a wildcard subscription, for which + // the first request on a stream must be without any resource names. + if (!wildcard_) { + names_added_.insert(resource_name); + } + } + // Wildcard subscription initial requests must have no resource_names_subscribe. + if (wildcard_) { + names_added_.clear(); } names_removed_.clear(); } diff --git a/source/common/config/delta_subscription_state.h b/source/common/config/delta_subscription_state.h index 2913479d40d77..7c478002ce379 100644 --- a/source/common/config/delta_subscription_state.h +++ b/source/common/config/delta_subscription_state.h @@ -26,7 +26,8 @@ namespace Config { class DeltaSubscriptionState : public Logger::Loggable { public: DeltaSubscriptionState(std::string type_url, UntypedConfigUpdateCallbacks& watch_map, - const LocalInfo::LocalInfo& local_info, Event::Dispatcher& dispatcher); + const LocalInfo::LocalInfo& local_info, Event::Dispatcher& dispatcher, + const bool wildcard); // Update which resources we're interested in subscribing to. void updateSubscriptionInterest(const absl::flat_hash_set& cur_added, @@ -103,6 +104,8 @@ class DeltaSubscriptionState : public Logger::Loggable { absl::flat_hash_set resource_names_; const std::string type_url_; + // Is the subscription is for a wildcard request. + const bool wildcard_; UntypedConfigUpdateCallbacks& watch_map_; const LocalInfo::LocalInfo& local_info_; Event::Dispatcher& dispatcher_; diff --git a/source/common/config/new_grpc_mux_impl.cc b/source/common/config/new_grpc_mux_impl.cc index 0a5e6997bc6a2..95d38e7822cf6 100644 --- a/source/common/config/new_grpc_mux_impl.cc +++ b/source/common/config/new_grpc_mux_impl.cc @@ -153,7 +153,8 @@ GrpcMuxWatchPtr NewGrpcMuxImpl::addWatch(const std::string& type_url, if (enable_type_url_downgrade_and_upgrade_) { registerVersionedTypeUrl(type_url); } - addSubscription(type_url, options.use_namespace_matching_); + // No resources implies that this is a wildcard request subscription. + addSubscription(type_url, options.use_namespace_matching_, resources.empty()); return addWatch(type_url, resources, callbacks, resource_decoder, options); } @@ -225,10 +226,11 @@ void NewGrpcMuxImpl::removeWatch(const std::string& type_url, Watch* watch) { entry->second->watch_map_.removeWatch(watch); } -void NewGrpcMuxImpl::addSubscription(const std::string& type_url, - const bool use_namespace_matching) { - subscriptions_.emplace(type_url, std::make_unique( - type_url, local_info_, use_namespace_matching, dispatcher_)); +void NewGrpcMuxImpl::addSubscription(const std::string& type_url, const bool use_namespace_matching, + const bool wildcard) { + subscriptions_.emplace(type_url, std::make_unique(type_url, local_info_, + use_namespace_matching, + dispatcher_, wildcard)); subscription_ordering_.emplace_back(type_url); } diff --git a/source/common/config/new_grpc_mux_impl.h b/source/common/config/new_grpc_mux_impl.h index 002f10eb14988..71664cb30a13c 100644 --- a/source/common/config/new_grpc_mux_impl.h +++ b/source/common/config/new_grpc_mux_impl.h @@ -75,9 +75,10 @@ class NewGrpcMuxImpl struct SubscriptionStuff { SubscriptionStuff(const std::string& type_url, const LocalInfo::LocalInfo& local_info, - const bool use_namespace_matching, Event::Dispatcher& dispatcher) + const bool use_namespace_matching, Event::Dispatcher& dispatcher, + const bool wildcard) : watch_map_(use_namespace_matching), - sub_state_(type_url, watch_map_, local_info, dispatcher) {} + sub_state_(type_url, watch_map_, local_info, dispatcher, wildcard) {} WatchMap watch_map_; DeltaSubscriptionState sub_state_; @@ -129,7 +130,9 @@ class NewGrpcMuxImpl const absl::flat_hash_set& resources, const SubscriptionOptions& options); - void addSubscription(const std::string& type_url, bool use_namespace_matching); + // Adds a subscription for the type_url to the subscriptions map and order list. + void addSubscription(const std::string& type_url, bool use_namespace_matching, + const bool wildcard); void trySendDiscoveryRequests(); diff --git a/test/common/config/delta_subscription_state_test.cc b/test/common/config/delta_subscription_state_test.cc index 92193cbe143ce..cc33dc4a5758f 100644 --- a/test/common/config/delta_subscription_state_test.cc +++ b/test/common/config/delta_subscription_state_test.cc @@ -19,6 +19,7 @@ using testing::NiceMock; using testing::Throw; using testing::UnorderedElementsAre; +using testing::UnorderedElementsAreArray; namespace Envoy { namespace Config { @@ -28,14 +29,17 @@ const char TypeUrl[] = "type.googleapis.com/envoy.api.v2.Cluster"; class DeltaSubscriptionStateTestBase : public testing::Test { protected: - DeltaSubscriptionStateTestBase(const std::string& type_url) + DeltaSubscriptionStateTestBase( + const std::string& type_url, const bool wildcard, + const absl::flat_hash_set initial_resources = {"name1", "name2", "name3"}) : timer_(new Event::MockTimer(&dispatcher_)), - state_(type_url, callbacks_, local_info_, dispatcher_) { - state_.updateSubscriptionInterest({"name1", "name2", "name3"}, {}); + state_(type_url, callbacks_, local_info_, dispatcher_, wildcard) { + state_.updateSubscriptionInterest(initial_resources, {}); envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = state_.getNextRequestAckless(); EXPECT_THAT(cur_request.resource_names_subscribe(), - UnorderedElementsAre("name1", "name2", "name3")); + // UnorderedElementsAre("name1", "name2", "name3")); + UnorderedElementsAreArray(initial_resources.cbegin(), initial_resources.cend())); } UpdateAck deliverDiscoveryResponse( @@ -94,7 +98,13 @@ populateRepeatedResource(std::vector> items) class DeltaSubscriptionStateTest : public DeltaSubscriptionStateTestBase { public: - DeltaSubscriptionStateTest() : DeltaSubscriptionStateTestBase(TypeUrl) {} + DeltaSubscriptionStateTest() : DeltaSubscriptionStateTestBase(TypeUrl, false) {} +}; + +// Delta subscription state of a wildcard subscription request. +class WildcardDeltaSubscriptionStateTest : public DeltaSubscriptionStateTestBase { +public: + WildcardDeltaSubscriptionStateTest() : DeltaSubscriptionStateTestBase(TypeUrl, true, {}) {} }; // Basic gaining/losing interest in resources should lead to subscription updates. @@ -307,9 +317,10 @@ TEST_F(DeltaSubscriptionStateTest, ResourceGoneLeadsToBlankInitialVersion) { } } -// Upon a reconnection, the server is supposed to assume a blank slate for the Envoy's state -// (hence the need for initial_resource_versions). The resource_names_subscribe of the first -// message must therefore be every resource the Envoy is interested in. +// For non-wildcard subscription, upon a reconnection, the server is supposed to assume a +// blank slate for the Envoy's state (hence the need for initial_resource_versions). +// The resource_names_subscribe of the first message must therefore be every resource the +// Envoy is interested in. // // resource_names_unsubscribe, on the other hand, is always blank in the first request - even if, // in between the last request of the last stream and the first request of the new stream, Envoy @@ -326,16 +337,40 @@ TEST_F(DeltaSubscriptionStateTest, SubscribeAndUnsubscribeAfterReconnect) { envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = state_.getNextRequestAckless(); // Regarding the resource_names_subscribe field: // name1: do not include: we lost interest. - // name2: yes do include: we're interested and we have a version of it. + // name2: yes do include: we are interested, its non-wildcard, and we have a version of it. // name3: yes do include: even though we don't have a version of it, we are interested. // name4: yes do include: we are newly interested. (If this wasn't a stream reconnect, only - // name4 - // would belong in this subscribe field). + // name4 would belong in this subscribe field). EXPECT_THAT(cur_request.resource_names_subscribe(), UnorderedElementsAre("name2", "name3", "name4")); EXPECT_TRUE(cur_request.resource_names_unsubscribe().empty()); } +// For wildcard subscription, upon a reconnection, the server is supposed to assume a +// blank slate for the Envoy's state (hence the need for initial_resource_versions), and +// the resource_names_subscribe and resource_names_unsubscribe must be empty (as is expected +// of every wildcard first message). This is true even if in between the last request of the +// last stream and the first request of the new stream, Envoy gained or lost interest in a +// resource. The subscription & unsubscription implicitly takes effect by simply requesting a +// wildcard subscription in the newly reconnected stream. +TEST_F(WildcardDeltaSubscriptionStateTest, SubscribeAndUnsubscribeAfterReconnect) { + Protobuf::RepeatedPtrField add1_2 = + populateRepeatedResource({{"name1", "version1A"}, {"name2", "version2A"}}); + EXPECT_CALL(*timer_, disableTimer()); + deliverDiscoveryResponse(add1_2, {}, "debugversion1"); + + state_.updateSubscriptionInterest({"name3"}, {"name1"}); + state_.markStreamFresh(); // simulate a stream reconnection + envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = state_.getNextRequestAckless(); + // Regarding the resource_names_subscribe field: + // name1: do not include: we lost interest. + // name2: do not include: we are interested, but for wildcard it shouldn't be provided. + // name4: do not include: although we are newly interested, an initial wildcard request + // must be with no resources. + EXPECT_TRUE(cur_request.resource_names_subscribe().empty()); + EXPECT_TRUE(cur_request.resource_names_unsubscribe().empty()); +} + // initial_resource_versions should not be present on messages after the first in a stream. TEST_F(DeltaSubscriptionStateTest, InitialVersionMapFirstMessageOnly) { // First, verify that the first message of a new stream sends initial versions. @@ -484,7 +519,7 @@ TEST_F(DeltaSubscriptionStateTest, ResourceTTL) { class VhdsDeltaSubscriptionStateTest : public DeltaSubscriptionStateTestBase { public: VhdsDeltaSubscriptionStateTest() - : DeltaSubscriptionStateTestBase("envoy.config.route.v3.VirtualHost") {} + : DeltaSubscriptionStateTestBase("envoy.config.route.v3.VirtualHost", false) {} }; TEST_F(VhdsDeltaSubscriptionStateTest, ResourceTTL) { diff --git a/test/common/config/new_grpc_mux_impl_test.cc b/test/common/config/new_grpc_mux_impl_test.cc index d7e43ed2ad0f0..37256ee412f6b 100644 --- a/test/common/config/new_grpc_mux_impl_test.cc +++ b/test/common/config/new_grpc_mux_impl_test.cc @@ -164,7 +164,7 @@ TEST_F(NewGrpcMuxImplTest, ReconnectionResetsNonceAndAcks) { add_response_resource("x", "2000", *response); add_response_resource("y", "3000", *response); // Pause EDS to allow the ACK to be cached. - auto resume_cds = grpc_mux_->pause(type_url); + auto resume_eds = grpc_mux_->pause(type_url); grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); // Now disconnect. // Grpc stream retry timer will kick in and reconnection will happen. @@ -179,6 +179,90 @@ TEST_F(NewGrpcMuxImplTest, ReconnectionResetsNonceAndAcks) { expectSendMessage(type_url, {}, {"x", "y"}); } +// Validate resources are not sent on wildcard watch reconnection. +// Regression test of https://github.com/envoyproxy/envoy/issues/16063. +TEST_F(NewGrpcMuxImplTest, ReconnectionResetsWildcardSubscription) { + Event::MockTimer* grpc_stream_retry_timer{new Event::MockTimer()}; + Event::MockTimer* ttl_mgr_timer{new NiceMock()}; + Event::TimerCb grpc_stream_retry_timer_cb; + EXPECT_CALL(dispatcher_, createTimer_(_)) + .WillOnce( + testing::DoAll(SaveArg<0>(&grpc_stream_retry_timer_cb), Return(grpc_stream_retry_timer))) + // Happens when adding a type url watch. + .WillRepeatedly(Return(ttl_mgr_timer)); + setup(); + InSequence s; + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + auto foo_sub = grpc_mux_->addWatch(type_url, {}, callbacks_, resource_decoder_, {}); + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + // Send a wildcard request on new connection. + expectSendMessage(type_url, {}, {}); + grpc_mux_->start(); + + // An helper function to create a response with a single load_assignment resource + // (load_assignment's cluster_name will be updated). + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + auto create_response = [&load_assignment, &type_url](const std::string& name, + const std::string& version, + const std::string& nonce) + -> std::unique_ptr { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_system_version_info(version); + response->set_nonce(nonce); + auto res = response->add_resources(); + res->set_name(name); + res->set_version(version); + load_assignment.set_cluster_name(name); + res->mutable_resource()->PackFrom(load_assignment); + return response; + }; + + // Send a response with a single resource that should be received by Envoy, + // followed by an ack with the nonce. + { + auto response = create_response("x", "1000", "111"); + EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "1000")) + .WillOnce(Invoke([&load_assignment](const std::vector& added_resources, + const Protobuf::RepeatedPtrField&, + const std::string&) { + EXPECT_EQ(1, added_resources.size()); + EXPECT_TRUE( + TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + })); + // Expect an ack with the nonce. + expectSendMessage(type_url, {}, {}, "111"); + grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); + } + // Send another response with a different resource, but where EDS is paused. + auto resume_eds = grpc_mux_->pause(type_url); + { + auto response = create_response("y", "2000", "222"); + EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "2000")) + .WillOnce(Invoke([&load_assignment](const std::vector& added_resources, + const Protobuf::RepeatedPtrField&, + const std::string&) { + EXPECT_EQ(1, added_resources.size()); + EXPECT_TRUE( + TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + })); + // No ack reply is expected in this case, as EDS is suspended. + grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); + } + + // Now disconnect. + // Grpc stream retry timer will kick in and reconnection will happen. + EXPECT_CALL(*grpc_stream_retry_timer, enableTimer(_, _)) + .WillOnce(Invoke(grpc_stream_retry_timer_cb)); + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + // initial_resource_versions should contain client side all resource:version info, and no + // added resources because this is a wildcard request. + expectSendMessage(type_url, {}, {}, "", Grpc::Status::WellKnownGrpcStatus::Ok, "", + {{"x", "1000"}, {"y", "2000"}}); + grpc_mux_->grpcStreamForTest().onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Canceled, ""); + // Destruction of wildcard will not issue unsubscribe requests for the resources. +} + // Test that we simply ignore a message for an unknown type_url, with no ill effects. TEST_F(NewGrpcMuxImplTest, DiscoveryResponseNonexistentSub) { setup(); diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index 84e69912c209a..c577f065af332 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -329,8 +329,43 @@ TEST_P(AdsIntegrationTest, ResendNodeOnStreamReset) { RELEASE_ASSERT(result, result.message()); xds_stream_->startGrpcStream(); - EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "1", {"cluster_0"}, - {"cluster_0"}, {}, true)); + // In SotW cluster_0 will be in the resource_names, but in delta-xDS + // resource_names_subscribe and resource_names_unsubscribe must be empty for + // a wildcard request (cluster_0 will appear in initial_resource_versions). + EXPECT_TRUE( + compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "1", {"cluster_0"}, {}, {}, true)); +} + +// Verifies that upon stream reconnection: +// - Non-wildcard requests contain all known resources. +// - Wildcard requests contain all known resources in SotW, but no resources in delta-xDS. +// Regression test for https://github.com/envoyproxy/envoy/issues/16063. +TEST_P(AdsIntegrationTest, ResourceNamesOnStreamReset) { + initialize(); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "", {}, {}, {}, true)); + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, + {buildCluster("cluster_0")}, + {buildCluster("cluster_0")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "", + {"cluster_0"}, {"cluster_0"}, {})); + sendDiscoveryResponse( + Config::TypeUrl::get().ClusterLoadAssignment, {buildClusterLoadAssignment("cluster_0")}, + {buildClusterLoadAssignment("cluster_0")}, {}, "1"); + + // A second CDS request should be sent so that the node is cleared in the cached request. + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "1", {}, {}, {})); + + xds_stream_->finishGrpcStream(Grpc::Status::Internal); + AssertionResult result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + RELEASE_ASSERT(result, result.message()); + xds_stream_->startGrpcStream(); + + // In SotW cluster_0 will be in the resource_names, but in delta-xDS + // resource_names_subscribe and resource_names_unsubscribe must be empty for + // a wildcard request (cluster_0 will appear in initial_resource_versions). + EXPECT_TRUE( + compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "1", {"cluster_0"}, {}, {}, true)); } // Validate that xds can support a mix of v2 and v3 type url. From 73ebf87618574eb2e88a5d9874d5243b5927aae1 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 28 Apr 2021 15:28:24 -0400 Subject: [PATCH 100/209] http: removing envoy.reloadable features.always apply route header rules (#16191) Risk Level: Low Testing: n/a Docs Changes: n/a Release Notes: inline Fixes #16002 Signed-off-by: Alyssa Wilk --- docs/root/version_history/current.rst | 1 + source/common/http/filter_manager.cc | 9 ++------- source/common/runtime/runtime_features.cc | 1 - 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index eb17d1e04c264..2a465da670274 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -31,6 +31,7 @@ Removed Config or Runtime *Normally occurs at the end of the* :ref:`deprecation period ` * http: removed ``envoy.reloadable_features.allow_500_after_100`` runtime guard and the legacy code path. +* http: removed ``envoy.reloadable_features.always_apply_route_header_rules`` runtime guard and legacy code path. * http: removed ``envoy.reloadable_features.hcm_stream_error_on_invalid_message`` for disabling closing HTTP/1.1 connections on error. Connection-closing can still be disabled by setting the HTTP/1 configuration :ref:`override_stream_error_on_invalid_http_message `. * http: removed ``envoy.reloadable_features.http_set_copy_replace_all_headers`` runtime guard and legacy code paths. * http: removed ``envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2``; Envoy will now always send GOAWAY to HTTP2 downstreams when the :ref:`disable_keepalive ` overload action is active. diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 8101dc2007cff..378419207b30a 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -9,7 +9,6 @@ #include "common/http/header_map_impl.h" #include "common/http/header_utility.h" #include "common/http/utility.h" -#include "common/runtime/runtime_features.h" namespace Envoy { namespace Http { @@ -889,9 +888,7 @@ void FilterManager::sendLocalReplyViaFilterChain( state_.destroyed_, Utility::EncodeFunctions{ [this, modify_headers](ResponseHeaderMap& headers) -> void { - if (streamInfo().route_entry_ && - Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.always_apply_route_header_rules")) { + if (streamInfo().route_entry_) { streamInfo().route_entry_->finalizeResponseHeaders(headers, streamInfo()); } if (modify_headers) { @@ -930,9 +927,7 @@ void FilterManager::sendDirectLocalReply( state_.destroyed_, Utility::EncodeFunctions{ [this, modify_headers](ResponseHeaderMap& headers) -> void { - if (streamInfo().route_entry_ && - Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.always_apply_route_header_rules")) { + if (streamInfo().route_entry_) { streamInfo().route_entry_->finalizeResponseHeaders(headers, streamInfo()); } if (modify_headers) { diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 57d21153afd21..139913ab1fa6a 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -56,7 +56,6 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.test_feature_true", // Begin alphabetically sorted section. "envoy.deprecated_features.allow_deprecated_extension_names", - "envoy.reloadable_features.always_apply_route_header_rules", "envoy.reloadable_features.activate_timers_next_event_loop", "envoy.reloadable_features.add_and_validate_scheme_header", "envoy.reloadable_features.allow_preconnect", From 3756e5b00ec8bba404688400e08d7302a247f951 Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Wed, 28 Apr 2021 12:38:39 -0700 Subject: [PATCH 101/209] grid: Add a new class for tracking HTTP/3 status (#16067) grid: Add a new class for tracking HTTP/3 status. Create a new Http3StatusTracker class which can mark HTTP/3 as broken for a period of time, subject to exponential backoff. Use this in ConnectivityGrid. Risk Level: Low Testing: New unit tests Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Ryan Hamilton --- source/common/http/BUILD | 11 ++ source/common/http/conn_pool_grid.cc | 18 +- source/common/http/conn_pool_grid.h | 16 +- source/common/http/http3_status_tracker.cc | 49 +++++ source/common/http/http3_status_tracker.h | 42 +++++ test/common/http/BUILD | 10 + test/common/http/conn_pool_grid_test.cc | 2 +- test/common/http/http3_status_tracker_test.cc | 177 ++++++++++++++++++ test/mocks/event/mocks.cc | 3 - test/mocks/event/mocks.h | 4 + 10 files changed, 321 insertions(+), 11 deletions(-) create mode 100644 source/common/http/http3_status_tracker.cc create mode 100644 source/common/http/http3_status_tracker.h create mode 100644 test/common/http/http3_status_tracker_test.cc diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 94975f555229f..a8056973014fe 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -149,6 +149,16 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "http3_status_tracker", + srcs = ["http3_status_tracker.cc"], + hdrs = ["http3_status_tracker.h"], + deps = [ + "//include/envoy/event:dispatcher_interface", + "//include/envoy/event:timer_interface", + ], +) + envoy_cc_library( name = "alternate_protocols", srcs = ["alternate_protocols.cc"], @@ -163,6 +173,7 @@ envoy_cc_library( srcs = ["conn_pool_grid.cc"], hdrs = ["conn_pool_grid.h"], deps = [ + ":http3_status_tracker", ":mixed_conn_pool", "//source/common/http/http3:conn_pool_lib", ], diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc index 995e0dc0c7f72..521b3a2e362d0 100644 --- a/source/common/http/conn_pool_grid.cc +++ b/source/common/http/conn_pool_grid.cc @@ -110,8 +110,11 @@ void ConnectivityGrid::WrapperCallbacks::onConnectionAttemptReady( host->hostname()); if (!grid_.isPoolHttp3(attempt->pool())) { tcp_attempt_succeeded_ = true; + maybeMarkHttp3Broken(); + } else { + ENVOY_LOG(trace, "Marking HTTP/3 confirmed for host '{}'.", grid_.host_->hostname()); + grid_.markHttp3Confirmed(); } - maybeMarkHttp3Broken(); auto delete_this_on_return = attempt->removeFromList(connection_attempts_); ConnectionPool::Callbacks* callbacks = inner_callbacks_; @@ -133,7 +136,7 @@ void ConnectivityGrid::WrapperCallbacks::onConnectionAttemptReady( void ConnectivityGrid::WrapperCallbacks::maybeMarkHttp3Broken() { if (http3_attempt_failed_ && tcp_attempt_succeeded_) { ENVOY_LOG(trace, "Marking HTTP/3 broken for host '{}'.", grid_.host_->hostname()); - grid_.setIsHttp3Broken(true); + grid_.markHttp3Broken(); } } @@ -190,7 +193,8 @@ ConnectivityGrid::ConnectivityGrid( std::chrono::milliseconds next_attempt_duration, ConnectivityOptions connectivity_options) : dispatcher_(dispatcher), random_generator_(random_generator), host_(host), priority_(priority), options_(options), transport_socket_options_(transport_socket_options), - state_(state), next_attempt_duration_(next_attempt_duration), time_source_(time_source) { + state_(state), next_attempt_duration_(next_attempt_duration), time_source_(time_source), + http3_status_tracker_(dispatcher_) { // ProdClusterManagerFactory::allocateConnPool verifies the protocols are HTTP/1, HTTP/2 and // HTTP/3. // TODO(#15649) support v6/v4, WiFi/cellular. @@ -243,7 +247,7 @@ ConnectionPool::Cancellable* ConnectivityGrid::newStream(Http::ResponseDecoder& createNextPool(); } PoolIterator pool = pools_.begin(); - if (is_http3_broken_) { + if (http3_status_tracker_.isHttp3Broken()) { ENVOY_LOG(trace, "HTTP/3 is broken to host '{}', skipping.", describePool(**pool), host_->hostname()); // Since HTTP/3 is broken, presumably both pools have already been created so this @@ -306,6 +310,12 @@ bool ConnectivityGrid::isPoolHttp3(const ConnectionPool::Instance& pool) { return &pool == pools_.begin()->get(); } +bool ConnectivityGrid::isHttp3Broken() const { return http3_status_tracker_.isHttp3Broken(); } + +void ConnectivityGrid::markHttp3Broken() { http3_status_tracker_.markHttp3Broken(); } + +void ConnectivityGrid::markHttp3Confirmed() { http3_status_tracker_.markHttp3Confirmed(); } + void ConnectivityGrid::onDrainReceived() { // Don't do any work under the stack of ~ConnectivityGrid() if (destroying_) { diff --git a/source/common/http/conn_pool_grid.h b/source/common/http/conn_pool_grid.h index d4d2ae2fc92db..69c2a303c6c3a 100644 --- a/source/common/http/conn_pool_grid.h +++ b/source/common/http/conn_pool_grid.h @@ -1,6 +1,7 @@ #pragma once #include "common/http/conn_pool_base.h" +#include "common/http/http3_status_tracker.h" #include "absl/container/flat_hash_map.h" @@ -150,8 +151,17 @@ class ConnectivityGrid : public ConnectionPool::Instance, // Returns true if pool is the grid's HTTP/3 connection pool. bool isPoolHttp3(const ConnectionPool::Instance& pool); - bool isHttp3Broken() const { return is_http3_broken_; } - void setIsHttp3Broken(bool is_http3_broken) { is_http3_broken_ = is_http3_broken; } + // Returns true if HTTP/3 is currently broken. While HTTP/3 is broken the grid will not + // attempt to make new HTTP/3 connections. + bool isHttp3Broken() const; + + // Marks HTTP/3 broken for a period of time subject to exponential backoff. While HTTP/3 + // is broken the grid will not attempt to make new HTTP/3 connections. + void markHttp3Broken(); + + // Marks that HTTP/3 is working, which resets the exponential backoff counter in the + // event that HTTP/3 is marked broken again. + void markHttp3Confirmed(); private: friend class ConnectivityGridForTest; @@ -174,7 +184,7 @@ class ConnectivityGrid : public ConnectionPool::Instance, Upstream::ClusterConnectivityState& state_; std::chrono::milliseconds next_attempt_duration_; TimeSource& time_source_; - bool is_http3_broken_{}; + Http3StatusTracker http3_status_tracker_; // Tracks how many drains are needed before calling drain callbacks. This is // set to the number of pools when the first drain callbacks are added, and diff --git a/source/common/http/http3_status_tracker.cc b/source/common/http/http3_status_tracker.cc new file mode 100644 index 0000000000000..472d0672ed5b5 --- /dev/null +++ b/source/common/http/http3_status_tracker.cc @@ -0,0 +1,49 @@ +#include "common/http/http3_status_tracker.h" + +namespace Envoy { +namespace Http { + +namespace { + +// Initially, HTTP/3 is be marked broken for 5 minutes. +const std::chrono::minutes DefaultExpirationTime{5}; +// Cap the broken period at just under 1 day. +const int MaxConsecutiveBrokenCount = 8; +} // namespace + +Http3StatusTracker::Http3StatusTracker(Event::Dispatcher& dispatcher) + : expiration_timer_(dispatcher.createTimer([this]() -> void { onExpirationTimeout(); })) {} + +bool Http3StatusTracker::isHttp3Broken() const { return state_ == State::Broken; } + +bool Http3StatusTracker::isHttp3Confirmed() const { return state_ == State::Confirmed; } + +void Http3StatusTracker::markHttp3Broken() { + state_ = State::Broken; + if (!expiration_timer_->enabled()) { + expiration_timer_->enableTimer(std::chrono::duration_cast( + DefaultExpirationTime * (1 << consecutive_broken_count_))); + if (consecutive_broken_count_ < MaxConsecutiveBrokenCount) { + ++consecutive_broken_count_; + } + } +} + +void Http3StatusTracker::markHttp3Confirmed() { + state_ = State::Confirmed; + consecutive_broken_count_ = 0; + if (expiration_timer_->enabled()) { + expiration_timer_->disableTimer(); + } +} + +void Http3StatusTracker::onExpirationTimeout() { + if (state_ != State::Broken) { + return; + } + + state_ = State::Pending; +} + +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/http3_status_tracker.h b/source/common/http/http3_status_tracker.h new file mode 100644 index 0000000000000..16a9acdd01656 --- /dev/null +++ b/source/common/http/http3_status_tracker.h @@ -0,0 +1,42 @@ +#pragma once + +#include "envoy/event/dispatcher.h" +#include "envoy/event/timer.h" + +namespace Envoy { +namespace Http { + +// Tracks the status of HTTP/3 being broken for a period of time +// subject to exponential backoff. +class Http3StatusTracker { +public: + explicit Http3StatusTracker(Event::Dispatcher& dispatcher); + + // Returns true if HTTP/3 is broken. + bool isHttp3Broken() const; + // Returns true if HTTP/3 is confirmed to be working. + bool isHttp3Confirmed() const; + // Marks HTTP/3 broken for a period of time, subject to backoff. + void markHttp3Broken(); + // Marks HTTP/3 as confirmed to be working and resets the backoff timeout. + void markHttp3Confirmed(); + +private: + enum class State { + Pending, + Broken, + Confirmed, + }; + + // Called when the expiration timer fires. + void onExpirationTimeout(); + + State state_{State::Pending}; + // The number of consecutive times HTTP/3 has been marked broken. + int consecutive_broken_count_{}; + // The timer which tracks when HTTP/3 broken status should expire + Event::TimerPtr expiration_timer_; +}; + +} // namespace Http +} // namespace Envoy diff --git a/test/common/http/BUILD b/test/common/http/BUILD index 0e6db1c8eb932..04d0fa0b10ec3 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -440,6 +440,16 @@ envoy_cc_test( ]), ) +envoy_cc_test( + name = "http3_status_tracker_test", + srcs = ["http3_status_tracker_test.cc"], + deps = [ + ":common_lib", + "//source/common/http:http3_status_tracker", + "//test/mocks:common_lib", + ], +) + envoy_cc_test( name = "alternate_protocols_test", srcs = ["alternate_protocols_test.cc"], diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index d4a2e2097ebc3..8b588626ae3c5 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -424,7 +424,7 @@ TEST_F(ConnectivityGridTest, NoDrainOnTeardown) { // Test that when HTTP/3 is broken then the HTTP/3 pool is skipped. TEST_F(ConnectivityGridTest, SuccessAfterBroken) { - grid_.setIsHttp3Broken(true); + grid_.markHttp3Broken(); EXPECT_EQ(grid_.first(), nullptr); EXPECT_LOG_CONTAINS("trace", "HTTP/3 is broken to host 'first', skipping.", diff --git a/test/common/http/http3_status_tracker_test.cc b/test/common/http/http3_status_tracker_test.cc new file mode 100644 index 0000000000000..0fce5f482172d --- /dev/null +++ b/test/common/http/http3_status_tracker_test.cc @@ -0,0 +1,177 @@ +#include "common/http/http3_status_tracker.h" + +#include "test/mocks/common.h" +#include "test/mocks/event/mocks.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using Envoy::Event::MockTimer; +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Http { + +namespace { +class Http3StatusTrackerTest : public testing::Test { +public: + Http3StatusTrackerTest() : timer_(new MockTimer(&dispatcher_)), tracker_(dispatcher_) {} + + NiceMock dispatcher_; + MockTimer* timer_; // Owned by tracker_; + Http3StatusTracker tracker_; +}; + +TEST_F(Http3StatusTrackerTest, Initialized) { + EXPECT_FALSE(tracker_.isHttp3Broken()); + EXPECT_FALSE(tracker_.isHttp3Confirmed()); +} + +TEST_F(Http3StatusTrackerTest, MarkBroken) { + EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + EXPECT_TRUE(tracker_.isHttp3Broken()); + EXPECT_FALSE(tracker_.isHttp3Confirmed()); +} + +TEST_F(Http3StatusTrackerTest, MarkBrokenRepeatedly) { + EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + EXPECT_TRUE(tracker_.isHttp3Broken()); + EXPECT_FALSE(tracker_.isHttp3Confirmed()); + + EXPECT_CALL(*timer_, enabled()).WillOnce(Return(true)); + tracker_.markHttp3Broken(); + EXPECT_TRUE(tracker_.isHttp3Broken()); + EXPECT_FALSE(tracker_.isHttp3Confirmed()); +} + +TEST_F(Http3StatusTrackerTest, MarkBrokenThenExpires) { + EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + + timer_->invokeCallback(); + EXPECT_FALSE(tracker_.isHttp3Broken()); + EXPECT_FALSE(tracker_.isHttp3Confirmed()); +} + +TEST_F(Http3StatusTrackerTest, MarkBrokenWithBackoff) { + EXPECT_CALL(*timer_, enabled()).WillRepeatedly(Return(false)); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + + timer_->invokeCallback(); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(10 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + + timer_->invokeCallback(); + EXPECT_FALSE(tracker_.isHttp3Broken()); + EXPECT_FALSE(tracker_.isHttp3Confirmed()); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(20 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + EXPECT_TRUE(tracker_.isHttp3Broken()); + EXPECT_FALSE(tracker_.isHttp3Confirmed()); + + timer_->invokeCallback(); + EXPECT_FALSE(tracker_.isHttp3Broken()); + EXPECT_FALSE(tracker_.isHttp3Confirmed()); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(40 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + EXPECT_TRUE(tracker_.isHttp3Broken()); + EXPECT_FALSE(tracker_.isHttp3Confirmed()); + + timer_->invokeCallback(); + EXPECT_FALSE(tracker_.isHttp3Broken()); + EXPECT_FALSE(tracker_.isHttp3Confirmed()); +} + +TEST_F(Http3StatusTrackerTest, MarkBrokenWithBackoffMax) { + EXPECT_CALL(*timer_, enabled()).WillRepeatedly(Return(false)); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + timer_->invokeCallback(); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(10 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + timer_->invokeCallback(); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(20 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + timer_->invokeCallback(); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(40 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + timer_->invokeCallback(); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(80 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + timer_->invokeCallback(); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(160 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + timer_->invokeCallback(); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(320 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + timer_->invokeCallback(); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(640 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + timer_->invokeCallback(); + + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1280 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + timer_->invokeCallback(); + + // Broken period no longer increases. + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1280 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + timer_->invokeCallback(); +} + +TEST_F(Http3StatusTrackerTest, MarkBrokenThenExpiresThenConfirmedThenBroken) { + EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + + timer_->invokeCallback(); + + EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); + tracker_.markHttp3Confirmed(); + EXPECT_FALSE(tracker_.isHttp3Broken()); + EXPECT_TRUE(tracker_.isHttp3Confirmed()); + + // markConfirmed will have reset the timeout back to the initial value. + EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + + EXPECT_TRUE(tracker_.isHttp3Broken()); + EXPECT_FALSE(tracker_.isHttp3Confirmed()); +} + +TEST_F(Http3StatusTrackerTest, MarkBrokenThenConfirmed) { + EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + tracker_.markHttp3Broken(); + + timer_->invokeCallback(); + + EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); + tracker_.markHttp3Confirmed(); + EXPECT_FALSE(tracker_.isHttp3Broken()); + EXPECT_TRUE(tracker_.isHttp3Confirmed()); +} + +} // namespace +} // namespace Http +} // namespace Envoy diff --git a/test/mocks/event/mocks.cc b/test/mocks/event/mocks.cc index c3f2c766e2028..ab47d2fc9b5ea 100644 --- a/test/mocks/event/mocks.cc +++ b/test/mocks/event/mocks.cc @@ -49,9 +49,6 @@ MockTimer::MockTimer() { ON_CALL(*this, enabled()).WillByDefault(ReturnPointee(&enabled_)); } -// Ownership of each MockTimer instance is transferred to the (caller of) dispatcher's -// createTimer_(), so to avoid destructing it twice, the MockTimer must have been dynamically -// allocated and must not be deleted by it's creator. MockTimer::MockTimer(MockDispatcher* dispatcher) : MockTimer() { dispatcher_ = dispatcher; EXPECT_CALL(*dispatcher, createTimer_(_)) diff --git a/test/mocks/event/mocks.h b/test/mocks/event/mocks.h index dfb3e6e37a8ed..31ac32d7b0a6f 100644 --- a/test/mocks/event/mocks.h +++ b/test/mocks/event/mocks.h @@ -173,6 +173,10 @@ class MockDispatcher : public Dispatcher { class MockTimer : public Timer { public: MockTimer(); + + // Ownership of each MockTimer instance is transferred to the (caller of) dispatcher's + // createTimer_(), so to avoid destructing it twice, the MockTimer must have been dynamically + // allocated and must not be deleted by it's creator. MockTimer(MockDispatcher* dispatcher); ~MockTimer() override; From eb18d71972e1f8af714d7843eb206108aad153c7 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 29 Apr 2021 00:10:46 +0100 Subject: [PATCH 102/209] docs: Improve style for inline literals and update contrib guidance (#16194) Signed-off-by: Ryan Northey --- api/CONTRIBUTING.md | 3 +-- docs/root/_static/css/envoy.css | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/api/CONTRIBUTING.md b/api/CONTRIBUTING.md index 847cdf74dc55e..a1e61a7072c45 100644 --- a/api/CONTRIBUTING.md +++ b/api/CONTRIBUTING.md @@ -72,7 +72,6 @@ The following are some general guidelines around documentation. // [#comment:TODO(mattklein123): Do something cooler] string foo_field = 3; ``` - -* Prefer *italics* for emphasis as `backtick` emphasis is somewhat jarring in our Sphinx theme. +* Please use *italics* (enclosed in asterisks `*emphasized word*`) for emphasis and `inline literals` for code quotation (enclosed in *double* backticks ` ``code`` `). * All documentation is expected to use proper English grammar with proper punctuation. If you are not a fluent English speaker please let us know and we will help out. diff --git a/docs/root/_static/css/envoy.css b/docs/root/_static/css/envoy.css index ade3cb1b16a12..4cc8d11cfdf3a 100644 --- a/docs/root/_static/css/envoy.css +++ b/docs/root/_static/css/envoy.css @@ -28,3 +28,11 @@ table.docutils div.line-block { .rst-content .sidebar { clear: right; } + +/* make code.literals more muted - dont use red! */ +.rst-content code.literal { + color: #555; + background-color: rgba(27, 31, 35, 0.05); + padding: 2px 2px; + border: solid #eee 1px; +} From 29eb17096e50fc9385edbf260c03da7adc4ca24c Mon Sep 17 00:00:00 2001 From: Greg Brail Date: Wed, 28 Apr 2021 17:28:33 -0700 Subject: [PATCH 103/209] ext_proc: Support trailer callbacks (#16102) Existing trailers will be sent to the processing server if the processing mode is set to enable them. If the processing mode is set to sent trailers, but there are no trailers present, then empty trailers will be sent to the server for modification. However, trailers may only be added in the end of the data callback in Envoy, which may come in before a previous gRPC reply returns. Filters that need to be able to consistently add trailers where none existed should enable trailer processing in the Envoy filter configuration instead of relying on being able to turn it on dynamically. Risk Level: Low. Trailers only enabled if a service called by the filter is configured to ask for them. Testing: New integration and unit tests added. Docs Changes: API docs updated in .proto files. Release Notes: When the processing mode is changed to SEND for request or response trailers, a corresponding message will be sent to the server, which can respond with trailer mutations as desired. In addition, if trailer processing is enabled in the filter configuration, then trailer messages will be sent to the server even if trailers are not present. This makes it possible for the server to add trailers where none exist. Finally, at the moment Envoy only implements trailers for the HTTP/2 protocol. Nothing will happen if trailer processing is enabled and Envoy is using HTTP/1 until Envoy implements trailers for HTTP/1. Signed-off-by: Gregory Brail --- .../http/ext_proc/v3alpha/ext_proc.proto | 9 +- .../ext_proc/v3alpha/external_processor.proto | 56 +++-- .../http/ext_proc/v3alpha/ext_proc.proto | 9 +- .../ext_proc/v3alpha/external_processor.proto | 56 +++-- source/common/http/filter_manager.cc | 5 +- .../filters/http/ext_proc/ext_proc.cc | 179 +++++++++++--- .../filters/http/ext_proc/ext_proc.h | 24 +- .../filters/http/ext_proc/processor_state.cc | 67 ++++- .../filters/http/ext_proc/processor_state.h | 108 ++++++-- test/extensions/filters/http/ext_proc/BUILD | 6 + .../ext_proc/ext_proc_integration_test.cc | 168 ++++++++++++- .../filters/http/ext_proc/ordering_test.cc | 234 +++++++++++++++--- 12 files changed, 771 insertions(+), 150 deletions(-) diff --git a/api/envoy/extensions/filters/http/ext_proc/v3alpha/ext_proc.proto b/api/envoy/extensions/filters/http/ext_proc/v3alpha/ext_proc.proto index c4175d5d5b69e..76fa69198dfe4 100644 --- a/api/envoy/extensions/filters/http/ext_proc/v3alpha/ext_proc.proto +++ b/api/envoy/extensions/filters/http/ext_proc/v3alpha/ext_proc.proto @@ -26,7 +26,8 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // The filter will send the "request_headers" and "response_headers" messages by default. // In addition, if the "processing mode" is set , the "request_body" and "response_body" // messages will be sent if the corresponding fields of the "processing_mode" are -// set to BUFFERED. The other body processing modes, and trailer processing, are not +// set to BUFFERED, and trailers will be sent if the corresponding fields are set +// to SEND. The other body processing modes are not // implemented yet. The filter will also respond to "immediate_response" messages // at any point in the stream. @@ -34,10 +35,10 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // process of being implemented: // * Request headers: IMPLEMENTED // * Request body: Only BUFFERED mode is implemented -// * Request trailers: NOT IMPLEMENTED +// * Request trailers: IMPLEMENTED // * Response headers: IMPLEMENTED // * Response body: Only BUFFERED mode is implemented -// * Response trailers: NOT IMPLEMENTED +// * Response trailers: IMPLEMENTED // The filter communicates with an external gRPC service that can use it to do a variety of things // with the request and response: @@ -57,6 +58,8 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // * Whether it receives the message body at all, in separate chunks, or as a single buffer // * Whether subsequent HTTP requests are transmitted synchronously or whether they are // sent asynchronously. +// * To modify request or response trailers if they already exist +// * To add request or response trailers where they are not present // // All of this together allows a server to process the filter traffic in fairly // sophisticated ways. For example: diff --git a/api/envoy/service/ext_proc/v3alpha/external_processor.proto b/api/envoy/service/ext_proc/v3alpha/external_processor.proto index 5b0696bfc3b0d..3246a3c3bdab3 100644 --- a/api/envoy/service/ext_proc/v3alpha/external_processor.proto +++ b/api/envoy/service/ext_proc/v3alpha/external_processor.proto @@ -59,12 +59,16 @@ service ExternalProcessor { // [#next-free-field: 8] message ProcessingRequest { // Specify whether the filter that sent this request is running in synchronous - // or asynchronous mode. If false, then the server must either respond - // with exactly one ProcessingResponse message or close the stream. - // If true, however, then the server must not respond with - // an additional message, although it may still close the stream. - // The choice of synchronous or asynchronous mode can be chosen in the - // filter configuration. + // or asynchronous mode. The choice of synchronous or asynchronous mode + // can be set in the filter configuration, and defaults to false. + // + // * A value of "false" indicates that the server must respond + // to this message by either sending back a matching ProcessingResponse message, + // or by closing the stream. + // * A value of "true" indicates that the server must not respond to this + // message, although it may still close the stream to indicate that no more messages + // are needed. + // bool async_mode = 1; // Each request message will include one of the following sub-messages. Which @@ -74,29 +78,41 @@ message ProcessingRequest { option (validate.required) = true; // Information about the HTTP request headers, as well as peer info and additional - // properties. If "response_required" is set, the server must send back a + // properties. Unless "async_mode" is true, the server must send back a // HeaderResponse message, an ImmediateResponse message, or close the stream. HttpHeaders request_headers = 2; // Information about the HTTP response headers, as well as peer info and additional - // properties. If "response_required" is set, the server must send back a + // properties. Unless "async_mode" is true, the server must send back a // HeaderResponse message or close the stream. HttpHeaders response_headers = 3; - // A chunk of the HTTP request body. If "response_required" is set, the server must send back + // A chunk of the HTTP request body. Unless "async_mode" is true, the server must send back // a BodyResponse message, an ImmediateResponse message, or close the stream. HttpBody request_body = 4; - // A chunk of the HTTP request body. If "response_required" is set, the server must send back + // A chunk of the HTTP request body. Unless "async_mode" is true, the server must send back // a BodyResponse message or close the stream. HttpBody response_body = 5; - // The HTTP trailers for the request path. If "response_required" is set, the server + // The HTTP trailers for the request path. Unless "async_mode" is true, the server // must send back a TrailerResponse message or close the stream. + // + // This message is only sent if the trailers processing mode is set to "SEND". + // If there are no trailers on the original downstream request, then this message + // will only be sent (with empty trailers waiting to be populated) if the + // processing mode is set before the request headers are sent, such as + // in the filter configuration. HttpTrailers request_trailers = 6; - // The HTTP trailers for the response path. If "response_required" is set, the server + // The HTTP trailers for the response path. Unless "async_mode" is true, the server // must send back a TrailerResponse message or close the stream. + // + // This message is only sent if the trailers processing mode is set to "SEND". + // If there are no trailers on the original downstream request, then this message + // will only be sent (with empty trailers waiting to be populated) if the + // processing mode is set before the request headers are sent, such as + // in the filter configuration. HttpTrailers response_trailers = 7; } } @@ -142,6 +158,7 @@ message ProcessingResponse { ImmediateResponse immediate_response = 7; } + // [#not-implemented-hide:] // Optional metadata that will be emitted as dynamic metadata to be consumed by the next // filter. This metadata will be placed in the namespace "envoy.filters.http.ext_proc". google.protobuf.Struct dynamic_metadata = 8; @@ -162,6 +179,7 @@ message HttpHeaders { // lower-cased, because HTTP header keys are case-insensitive. config.core.v3.HeaderMap headers = 1; + // [#not-implemented-hide:] // The values of properties selected by the "request_attributes" // or "response_attributes" list in the configuration. Each entry // in the list is populated @@ -213,6 +231,7 @@ message CommonResponse { // stream as normal. This is the default. CONTINUE = 0; + // [#not-implemented-hide:] // Replace the request or response with the contents // of this message. If header_mutation is set, apply it to the // headers. If body_mutation is set and contains a body, then add that @@ -229,24 +248,23 @@ message CommonResponse { ResponseStatus status = 1 [(validate.rules).enum = {defined_only: true}]; // Instructions on how to manipulate the headers. When responding to an - // HttpBody request, header mutations will only take effect if the - // headers were not already sent further on the filter chain, which - // happens only if the current processing mode for the body is BUFFERED - // or BUFFERED_PARTIAL. + // HttpBody request, header mutations will only take effect if + // the current processing mode for the body is BUFFERED. HeaderMutation header_mutation = 2; // Replace the body of the last message sent to the remote server on this // stream. If responding to an HttpBody request, simply replace or clear - // the body chunk that was sent with that request. If responding to an - // HttpHeaders request, then a new body may be added to the request if this - // message is returned along with the CONTINUE_AND_REPLACE status. + // the body chunk that was sent with that request. Body mutations only take + // effect in response to "body" messages and are ignored otherwise. BodyMutation body_mutation = 3; + // [#not-implemented-hide:] // Add new trailers to the message. This may be used when responding to either a // HttpHeaders or HttpBody message, but only if this message is returned // along with the CONTINUE_AND_REPLACE status. config.core.v3.HeaderMap trailers = 4; + // [#not-implemented-hide:] // Clear the route cache for the current request. // This is necessary if the remote server // modified headers that are used to calculate the route. diff --git a/generated_api_shadow/envoy/extensions/filters/http/ext_proc/v3alpha/ext_proc.proto b/generated_api_shadow/envoy/extensions/filters/http/ext_proc/v3alpha/ext_proc.proto index c4175d5d5b69e..76fa69198dfe4 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/ext_proc/v3alpha/ext_proc.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/ext_proc/v3alpha/ext_proc.proto @@ -26,7 +26,8 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // The filter will send the "request_headers" and "response_headers" messages by default. // In addition, if the "processing mode" is set , the "request_body" and "response_body" // messages will be sent if the corresponding fields of the "processing_mode" are -// set to BUFFERED. The other body processing modes, and trailer processing, are not +// set to BUFFERED, and trailers will be sent if the corresponding fields are set +// to SEND. The other body processing modes are not // implemented yet. The filter will also respond to "immediate_response" messages // at any point in the stream. @@ -34,10 +35,10 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // process of being implemented: // * Request headers: IMPLEMENTED // * Request body: Only BUFFERED mode is implemented -// * Request trailers: NOT IMPLEMENTED +// * Request trailers: IMPLEMENTED // * Response headers: IMPLEMENTED // * Response body: Only BUFFERED mode is implemented -// * Response trailers: NOT IMPLEMENTED +// * Response trailers: IMPLEMENTED // The filter communicates with an external gRPC service that can use it to do a variety of things // with the request and response: @@ -57,6 +58,8 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // * Whether it receives the message body at all, in separate chunks, or as a single buffer // * Whether subsequent HTTP requests are transmitted synchronously or whether they are // sent asynchronously. +// * To modify request or response trailers if they already exist +// * To add request or response trailers where they are not present // // All of this together allows a server to process the filter traffic in fairly // sophisticated ways. For example: diff --git a/generated_api_shadow/envoy/service/ext_proc/v3alpha/external_processor.proto b/generated_api_shadow/envoy/service/ext_proc/v3alpha/external_processor.proto index 5b0696bfc3b0d..3246a3c3bdab3 100644 --- a/generated_api_shadow/envoy/service/ext_proc/v3alpha/external_processor.proto +++ b/generated_api_shadow/envoy/service/ext_proc/v3alpha/external_processor.proto @@ -59,12 +59,16 @@ service ExternalProcessor { // [#next-free-field: 8] message ProcessingRequest { // Specify whether the filter that sent this request is running in synchronous - // or asynchronous mode. If false, then the server must either respond - // with exactly one ProcessingResponse message or close the stream. - // If true, however, then the server must not respond with - // an additional message, although it may still close the stream. - // The choice of synchronous or asynchronous mode can be chosen in the - // filter configuration. + // or asynchronous mode. The choice of synchronous or asynchronous mode + // can be set in the filter configuration, and defaults to false. + // + // * A value of "false" indicates that the server must respond + // to this message by either sending back a matching ProcessingResponse message, + // or by closing the stream. + // * A value of "true" indicates that the server must not respond to this + // message, although it may still close the stream to indicate that no more messages + // are needed. + // bool async_mode = 1; // Each request message will include one of the following sub-messages. Which @@ -74,29 +78,41 @@ message ProcessingRequest { option (validate.required) = true; // Information about the HTTP request headers, as well as peer info and additional - // properties. If "response_required" is set, the server must send back a + // properties. Unless "async_mode" is true, the server must send back a // HeaderResponse message, an ImmediateResponse message, or close the stream. HttpHeaders request_headers = 2; // Information about the HTTP response headers, as well as peer info and additional - // properties. If "response_required" is set, the server must send back a + // properties. Unless "async_mode" is true, the server must send back a // HeaderResponse message or close the stream. HttpHeaders response_headers = 3; - // A chunk of the HTTP request body. If "response_required" is set, the server must send back + // A chunk of the HTTP request body. Unless "async_mode" is true, the server must send back // a BodyResponse message, an ImmediateResponse message, or close the stream. HttpBody request_body = 4; - // A chunk of the HTTP request body. If "response_required" is set, the server must send back + // A chunk of the HTTP request body. Unless "async_mode" is true, the server must send back // a BodyResponse message or close the stream. HttpBody response_body = 5; - // The HTTP trailers for the request path. If "response_required" is set, the server + // The HTTP trailers for the request path. Unless "async_mode" is true, the server // must send back a TrailerResponse message or close the stream. + // + // This message is only sent if the trailers processing mode is set to "SEND". + // If there are no trailers on the original downstream request, then this message + // will only be sent (with empty trailers waiting to be populated) if the + // processing mode is set before the request headers are sent, such as + // in the filter configuration. HttpTrailers request_trailers = 6; - // The HTTP trailers for the response path. If "response_required" is set, the server + // The HTTP trailers for the response path. Unless "async_mode" is true, the server // must send back a TrailerResponse message or close the stream. + // + // This message is only sent if the trailers processing mode is set to "SEND". + // If there are no trailers on the original downstream request, then this message + // will only be sent (with empty trailers waiting to be populated) if the + // processing mode is set before the request headers are sent, such as + // in the filter configuration. HttpTrailers response_trailers = 7; } } @@ -142,6 +158,7 @@ message ProcessingResponse { ImmediateResponse immediate_response = 7; } + // [#not-implemented-hide:] // Optional metadata that will be emitted as dynamic metadata to be consumed by the next // filter. This metadata will be placed in the namespace "envoy.filters.http.ext_proc". google.protobuf.Struct dynamic_metadata = 8; @@ -162,6 +179,7 @@ message HttpHeaders { // lower-cased, because HTTP header keys are case-insensitive. config.core.v3.HeaderMap headers = 1; + // [#not-implemented-hide:] // The values of properties selected by the "request_attributes" // or "response_attributes" list in the configuration. Each entry // in the list is populated @@ -213,6 +231,7 @@ message CommonResponse { // stream as normal. This is the default. CONTINUE = 0; + // [#not-implemented-hide:] // Replace the request or response with the contents // of this message. If header_mutation is set, apply it to the // headers. If body_mutation is set and contains a body, then add that @@ -229,24 +248,23 @@ message CommonResponse { ResponseStatus status = 1 [(validate.rules).enum = {defined_only: true}]; // Instructions on how to manipulate the headers. When responding to an - // HttpBody request, header mutations will only take effect if the - // headers were not already sent further on the filter chain, which - // happens only if the current processing mode for the body is BUFFERED - // or BUFFERED_PARTIAL. + // HttpBody request, header mutations will only take effect if + // the current processing mode for the body is BUFFERED. HeaderMutation header_mutation = 2; // Replace the body of the last message sent to the remote server on this // stream. If responding to an HttpBody request, simply replace or clear - // the body chunk that was sent with that request. If responding to an - // HttpHeaders request, then a new body may be added to the request if this - // message is returned along with the CONTINUE_AND_REPLACE status. + // the body chunk that was sent with that request. Body mutations only take + // effect in response to "body" messages and are ignored otherwise. BodyMutation body_mutation = 3; + // [#not-implemented-hide:] // Add new trailers to the message. This may be used when responding to either a // HttpHeaders or HttpBody message, but only if this message is returned // along with the CONTINUE_AND_REPLACE status. config.core.v3.HeaderMap trailers = 4; + // [#not-implemented-hide:] // Clear the route cache for the current request. // This is necessary if the remote server // modified headers that are used to calculate the route. diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 378419207b30a..b03a9694986eb 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -206,7 +206,10 @@ bool ActiveStreamFilterBase::commonHandleAfterTrailersCallback(FilterTrailersSta } else { ASSERT(headers_continued_); } - } else { + } else if (status == FilterTrailersStatus::StopIteration) { + if (canIterate()) { + iteration_state_ = IterationState::StopSingleIteration; + } return false; } diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 25751c158b0d7..941ecf4c1b1c1 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -17,8 +17,11 @@ using envoy::service::ext_proc::v3alpha::ProcessingResponse; using Http::FilterDataStatus; using Http::FilterHeadersStatus; +using Http::FilterTrailersStatus; using Http::RequestHeaderMap; +using Http::RequestTrailerMap; using Http::ResponseHeaderMap; +using Http::ResponseTrailerMap; static const std::string kErrorPrefix = "ext_proc error"; @@ -59,9 +62,6 @@ void Filter::onDestroy() { FilterHeadersStatus Filter::onHeaders(ProcessorState& state, Http::HeaderMap& headers, bool end_stream) { - ENVOY_BUG(state.callbacksIdle(), "Invalid filter state"); - - // We're at the start, so start the stream and send a headers message switch (openStream()) { case StreamOpenState::Error: return FilterHeadersStatus::StopIteration; @@ -77,7 +77,7 @@ FilterHeadersStatus Filter::onHeaders(ProcessorState& state, Http::HeaderMap& he auto* headers_req = state.mutableHeaders(req); MutationUtils::buildHttpHeaders(headers, *headers_req->mutable_headers()); headers_req->set_end_of_stream(end_stream); - state.setCallbackState(ProcessorState::CallbackState::Headers); + state.setCallbackState(ProcessorState::CallbackState::HeadersCallback); state.startMessageTimer(std::bind(&Filter::onMessageTimeout, this), config_->messageTimeout()); ENVOY_LOG(debug, "Sending headers message"); stream_->send(std::move(req), false); @@ -87,8 +87,11 @@ FilterHeadersStatus Filter::onHeaders(ProcessorState& state, Http::HeaderMap& he FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool end_stream) { ENVOY_LOG(trace, "decodeHeaders: end_stream = {}", end_stream); + if (end_stream) { + decoding_state_.setCompleteBodyAvailable(true); + } - if (processing_mode_.request_header_mode() == ProcessingMode::SKIP) { + if (!decoding_state_.sendHeaders()) { ENVOY_LOG(trace, "decodeHeaders: Skipped"); return FilterHeadersStatus::Continue; } @@ -98,15 +101,30 @@ FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool end_st return status; } -Http::FilterDataStatus Filter::onData(ProcessorState& state, ProcessingMode::BodySendMode body_mode, - Buffer::Instance& data, bool end_stream) { - if (state.callbackState() == ProcessorState::CallbackState::Headers) { +Http::FilterDataStatus Filter::onData(ProcessorState& state, Buffer::Instance& data, + bool end_stream) { + if (end_stream) { + state.setCompleteBodyAvailable(true); + } + + bool just_added_trailers = false; + Http::HeaderMap* new_trailers = nullptr; + if (end_stream && state.sendTrailers()) { + // We're at the end of the stream, but the filter wants to process trailers. + // According to the filter contract, this is the only place where we can + // add trailers, even if we will return right after this and process them + // later. + ENVOY_LOG(trace, "Creating new, empty trailers"); + new_trailers = state.addTrailers(); + state.setTrailersAvailable(true); + just_added_trailers = true; + } + + if (state.callbackState() == ProcessorState::CallbackState::HeadersCallback) { ENVOY_LOG(trace, "Header processing still in progress -- holding body data"); // We don't know what to do with the body until the response comes back. // We must buffer it in case we need it when that happens. if (end_stream) { - // Indicate to continue processing when the response returns. - state.setBodySendDeferred(true); return FilterDataStatus::StopIterationAndBuffer; } else { // Raise a watermark to prevent a buffer overflow until the response comes back. @@ -115,10 +133,10 @@ Http::FilterDataStatus Filter::onData(ProcessorState& state, ProcessingMode::Bod } } - switch (body_mode) { + FilterDataStatus result; + switch (state.bodyMode()) { case ProcessingMode::BUFFERED: if (end_stream) { - ENVOY_BUG(state.callbacksIdle(), "Invalid filter state"); switch (openStream()) { case StreamOpenState::Error: return FilterDataStatus::StopIterationNoBuffer; @@ -132,26 +150,44 @@ Http::FilterDataStatus Filter::onData(ProcessorState& state, ProcessingMode::Bod // The body has been buffered and we need to send the buffer ENVOY_LOG(debug, "Sending request body message"); state.addBufferedData(data); - state.setCallbackState(ProcessorState::CallbackState::BufferedBody); - state.startMessageTimer(std::bind(&Filter::onMessageTimeout, this), - config_->messageTimeout()); sendBodyChunk(state, *state.bufferedData(), true); // Since we just just moved the data into the buffer, return NoBuffer // so that we do not buffer this chunk twice. - return FilterDataStatus::StopIterationNoBuffer; + result = FilterDataStatus::StopIterationNoBuffer; + break; } ENVOY_LOG(trace, "onData: Buffering"); - return FilterDataStatus::StopIterationAndBuffer; + result = FilterDataStatus::StopIterationAndBuffer; + break; case ProcessingMode::BUFFERED_PARTIAL: case ProcessingMode::STREAMED: ENVOY_LOG(debug, "Ignoring unimplemented request body processing mode"); - return FilterDataStatus::Continue; + result = FilterDataStatus::Continue; + break; case ProcessingMode::NONE: default: - return FilterDataStatus::Continue; + result = FilterDataStatus::Continue; + break; + } + + if (just_added_trailers) { + // If we get here, then we need to send the trailers message now + switch (openStream()) { + case StreamOpenState::Error: + return FilterDataStatus::StopIterationNoBuffer; + case StreamOpenState::IgnoreError: + return FilterDataStatus::Continue; + case StreamOpenState::Ok: + // Fall through + break; + } + + sendTrailers(state, *new_trailers); + return FilterDataStatus::StopIterationAndBuffer; } + return result; } FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) { @@ -161,16 +197,69 @@ FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) { return FilterDataStatus::Continue; } - const auto status = - onData(decoding_state_, processing_mode_.request_body_mode(), data, end_stream); + const auto status = onData(decoding_state_, data, end_stream); ENVOY_LOG(trace, "decodeData returning {}", status); return status; } +FilterTrailersStatus Filter::onTrailers(ProcessorState& state, Http::HeaderMap& trailers) { + bool body_delivered = state.completeBodyAvailable(); + state.setCompleteBodyAvailable(true); + state.setTrailersAvailable(true); + state.setTrailers(&trailers); + + if (state.callbackState() == ProcessorState::CallbackState::HeadersCallback || + state.callbackState() == ProcessorState::CallbackState::BufferedBodyCallback) { + ENVOY_LOG(trace, "Previous callback still executing -- holding header iteration"); + return FilterTrailersStatus::StopIteration; + } + + if (!body_delivered && state.bodyMode() == ProcessingMode::BUFFERED) { + // We would like to process the body in a buffered way, but until now the complete + // body has not arrived. With the arrival of trailers, we now know that the body + // has arrived. + sendBufferedData(state, true); + return FilterTrailersStatus::StopIteration; + } + + if (!state.sendTrailers()) { + ENVOY_LOG(trace, "Skipped trailer processing"); + return FilterTrailersStatus::Continue; + } + + switch (openStream()) { + case StreamOpenState::Error: + return FilterTrailersStatus::StopIteration; + case StreamOpenState::IgnoreError: + return FilterTrailersStatus::Continue; + case StreamOpenState::Ok: + // Fall through + break; + } + + sendTrailers(state, trailers); + return FilterTrailersStatus::StopIteration; +} + +FilterTrailersStatus Filter::decodeTrailers(RequestTrailerMap& trailers) { + ENVOY_LOG(trace, "decodeTrailers"); + if (processing_complete_) { + ENVOY_LOG(trace, "decodeTrailers: Continue"); + return FilterTrailersStatus::Continue; + } + + const auto status = onTrailers(decoding_state_, trailers); + ENVOY_LOG(trace, "encodeTrailers returning {}", status); + return status; +} + FilterHeadersStatus Filter::encodeHeaders(ResponseHeaderMap& headers, bool end_stream) { ENVOY_LOG(trace, "encodeHeaders end_stream = {}", end_stream); + if (end_stream) { + encoding_state_.setCompleteBodyAvailable(true); + } - if (processing_complete_ || processing_mode_.response_header_mode() == ProcessingMode::SKIP) { + if (processing_complete_ || !encoding_state_.sendHeaders()) { ENVOY_LOG(trace, "encodeHeaders: Continue"); return FilterHeadersStatus::Continue; } @@ -187,15 +276,27 @@ FilterDataStatus Filter::encodeData(Buffer::Instance& data, bool end_stream) { return FilterDataStatus::Continue; } - const auto status = - onData(encoding_state_, processing_mode_.response_body_mode(), data, end_stream); + const auto status = onData(encoding_state_, data, end_stream); ENVOY_LOG(trace, "encodeData returning {}", status); return status; } -void Filter::sendBodyChunk(const ProcessorState& state, const Buffer::Instance& data, - bool end_stream) { +FilterTrailersStatus Filter::encodeTrailers(ResponseTrailerMap& trailers) { + ENVOY_LOG(trace, "encodeTrailers"); + if (processing_complete_) { + ENVOY_LOG(trace, "encodeTrailers: Continue"); + return FilterTrailersStatus::Continue; + } + + const auto status = onTrailers(encoding_state_, trailers); + ENVOY_LOG(trace, "encodeTrailers returning {}", status); + return status; +} + +void Filter::sendBodyChunk(ProcessorState& state, const Buffer::Instance& data, bool end_stream) { ENVOY_LOG(debug, "Sending a body chunk of {} bytes", data.length()); + state.setCallbackState(ProcessorState::CallbackState::BufferedBodyCallback); + state.startMessageTimer(std::bind(&Filter::onMessageTimeout, this), config_->messageTimeout()); ProcessingRequest req; auto* body_req = state.mutableBody(req); body_req->set_end_of_stream(end_stream); @@ -204,6 +305,17 @@ void Filter::sendBodyChunk(const ProcessorState& state, const Buffer::Instance& stats_.stream_msgs_sent_.inc(); } +void Filter::sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers) { + ProcessingRequest req; + auto* trailers_req = state.mutableTrailers(req); + MutationUtils::buildHttpHeaders(trailers, *trailers_req->mutable_trailers()); + state.setCallbackState(ProcessorState::CallbackState::TrailersCallback); + state.startMessageTimer(std::bind(&Filter::onMessageTimeout, this), config_->messageTimeout()); + ENVOY_LOG(debug, "Sending trailers message"); + stream_->send(std::move(req), false); + stats_.stream_msgs_sent_.inc(); +} + void Filter::onReceiveMessage(std::unique_ptr&& r) { if (processing_complete_) { // Ignore additional messages after we decided we were done with the stream @@ -218,17 +330,16 @@ void Filter::onReceiveMessage(std::unique_ptr&& r) { // being invoked in line. if (response->has_mode_override()) { ENVOY_LOG(debug, "Processing mode overridden by server for this request"); - processing_mode_ = response->mode_override(); + decoding_state_.setProcessingMode(response->mode_override()); + encoding_state_.setProcessingMode(response->mode_override()); } switch (response->response_case()) { case ProcessingResponse::ResponseCase::kRequestHeaders: - message_handled = decoding_state_.handleHeadersResponse(response->request_headers(), - processing_mode_.request_body_mode()); + message_handled = decoding_state_.handleHeadersResponse(response->request_headers()); break; case ProcessingResponse::ResponseCase::kResponseHeaders: - message_handled = encoding_state_.handleHeadersResponse(response->response_headers(), - processing_mode_.response_body_mode()); + message_handled = encoding_state_.handleHeadersResponse(response->response_headers()); break; case ProcessingResponse::ResponseCase::kRequestBody: message_handled = decoding_state_.handleBodyResponse(response->request_body()); @@ -236,6 +347,12 @@ void Filter::onReceiveMessage(std::unique_ptr&& r) { case ProcessingResponse::ResponseCase::kResponseBody: message_handled = encoding_state_.handleBodyResponse(response->response_body()); break; + case ProcessingResponse::ResponseCase::kRequestTrailers: + message_handled = decoding_state_.handleTrailersResponse(response->request_trailers()); + break; + case ProcessingResponse::ResponseCase::kResponseTrailers: + message_handled = encoding_state_.handleTrailersResponse(response->response_trailers()); + break; case ProcessingResponse::ResponseCase::kImmediateResponse: // We won't be sending anything more to the stream after we // receive this message. diff --git a/source/extensions/filters/http/ext_proc/ext_proc.h b/source/extensions/filters/http/ext_proc/ext_proc.h index 716d7bb3b558d..37dc63d646ae5 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.h +++ b/source/extensions/filters/http/ext_proc/ext_proc.h @@ -91,10 +91,8 @@ class Filter : public Logger::Loggable, public: Filter(const FilterConfigSharedPtr& config, ExternalProcessorClientPtr&& client) : config_(config), client_(std::move(client)), stats_(config->stats()), - decoding_state_(*this), encoding_state_(*this), processing_mode_(config->processingMode()) { - } - - const FilterConfig& config() const { return *config_; } + decoding_state_(*this, config->processingMode()), + encoding_state_(*this, config->processingMode()) {} void onDestroy() override; void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override; @@ -103,10 +101,12 @@ class Filter : public Logger::Loggable, Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) override; Http::FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override; + Http::FilterTrailersStatus decodeTrailers(Http::RequestTrailerMap& trailers) override; Http::FilterHeadersStatus encodeHeaders(Http::ResponseHeaderMap& headers, bool end_stream) override; Http::FilterDataStatus encodeData(Buffer::Instance& data, bool end_stream) override; + Http::FilterTrailersStatus encodeTrailers(Http::ResponseTrailerMap& trailers) override; // ExternalProcessorCallbacks @@ -119,24 +119,24 @@ class Filter : public Logger::Loggable, void onMessageTimeout(); - void sendBufferedData(const ProcessorState& state, bool end_stream) { + void sendBufferedData(ProcessorState& state, bool end_stream) { sendBodyChunk(state, *state.bufferedData(), end_stream); } + void sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers); + private: StreamOpenState openStream(); void cleanUpTimers(); void clearAsyncState(); void sendImmediateResponse(const envoy::service::ext_proc::v3alpha::ImmediateResponse& response); - void sendBodyChunk(const ProcessorState& state, const Buffer::Instance& data, bool end_stream); + void sendBodyChunk(ProcessorState& state, const Buffer::Instance& data, bool end_stream); Http::FilterHeadersStatus onHeaders(ProcessorState& state, Http::HeaderMap& headers, bool end_stream); - Http::FilterDataStatus onData( - ProcessorState& state, - envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode::BodySendMode body_mode, - Buffer::Instance& data, bool end_stream); + Http::FilterDataStatus onData(ProcessorState& state, Buffer::Instance& data, bool end_stream); + Http::FilterTrailersStatus onTrailers(ProcessorState& state, Http::HeaderMap& trailers); const FilterConfigSharedPtr config_; const ExternalProcessorClientPtr client_; @@ -158,10 +158,6 @@ class Filter : public Logger::Loggable, // Set to true when an "immediate response" has been delivered. This helps us // know what response to return from certain failures. bool sent_immediate_response_ = false; - - // The processing mode. May be locally overridden by any response, - // So every instance of the filter has a copy. - envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode processing_mode_; }; } // namespace ExternalProcessing diff --git a/source/extensions/filters/http/ext_proc/processor_state.cc b/source/extensions/filters/http/ext_proc/processor_state.cc index d085b5815fce2..3d60779a42295 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.cc +++ b/source/extensions/filters/http/ext_proc/processor_state.cc @@ -9,10 +9,10 @@ namespace HttpFilters { namespace ExternalProcessing { using envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode; -using envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode_BodySendMode; using envoy::service::ext_proc::v3alpha::BodyResponse; using envoy::service::ext_proc::v3alpha::HeadersResponse; +using envoy::service::ext_proc::v3alpha::TrailersResponse; void ProcessorState::startMessageTimer(Event::TimerCb cb, std::chrono::milliseconds timeout) { if (!message_timer_) { @@ -21,24 +21,20 @@ void ProcessorState::startMessageTimer(Event::TimerCb cb, std::chrono::milliseco message_timer_->enableTimer(timeout); } -bool ProcessorState::handleHeadersResponse(const HeadersResponse& response, - ProcessingMode_BodySendMode body_mode) { - if (callback_state_ == CallbackState::Headers) { +bool ProcessorState::handleHeadersResponse(const HeadersResponse& response) { + if (callback_state_ == CallbackState::HeadersCallback) { ENVOY_LOG(debug, "applying headers response"); MutationUtils::applyCommonHeaderResponse(response, *headers_); callback_state_ = CallbackState::Idle; clearWatermark(); message_timer_->disableTimer(); - if (body_mode == ProcessingMode::BUFFERED) { - if (body_send_deferred_) { + if (body_mode_ == ProcessingMode::BUFFERED) { + if (complete_body_available_) { // If we get here, then all the body data came in before the header message // was complete, and the server wants the body. So, don't continue filter // processing, but send the buffered request body now. ENVOY_LOG(debug, "Sending buffered request body message"); - callback_state_ = CallbackState::BufferedBody; - startMessageTimer(std::bind(&Filter::onMessageTimeout, &filter_), - filter_.config().messageTimeout()); filter_.sendBufferedData(*this, true); } @@ -48,7 +44,15 @@ bool ProcessorState::handleHeadersResponse(const HeadersResponse& response, return true; } - // If we got here, then the processor doesn't care about the body, so we can just continue. + if (send_trailers_ && trailers_available_) { + // Trailers came in while we were waiting for this response, and the server + // is not interested in the body, so send them now. + filter_.sendTrailers(*this, *trailers_); + return true; + } + + // If we got here, then the processor doesn't care about the body or is not ready for + // trailers, so we can just continue. headers_ = nullptr; continueProcessing(); return true; @@ -57,7 +61,7 @@ bool ProcessorState::handleHeadersResponse(const HeadersResponse& response, } bool ProcessorState::handleBodyResponse(const BodyResponse& response) { - if (callback_state_ == CallbackState::BufferedBody) { + if (callback_state_ == CallbackState::BufferedBodyCallback) { ENVOY_LOG(debug, "Applying body response to buffered data"); modifyBufferedData([this, &response](Buffer::Instance& data) { MutationUtils::applyCommonBodyResponse(response, headers_, data); @@ -65,6 +69,29 @@ bool ProcessorState::handleBodyResponse(const BodyResponse& response) { headers_ = nullptr; callback_state_ = CallbackState::Idle; message_timer_->disableTimer(); + + if (send_trailers_ && trailers_available_) { + // Trailers came in while we were waiting for this response, and the server + // asked to see them -- send them now. + filter_.sendTrailers(*this, *trailers_); + return true; + } + + continueProcessing(); + return true; + } + return false; +} + +bool ProcessorState::handleTrailersResponse(const TrailersResponse& response) { + if (callback_state_ == CallbackState::TrailersCallback) { + ENVOY_LOG(debug, "Applying response to buffered trailers"); + if (response.has_header_mutation()) { + MutationUtils::applyHeaderMutations(response.header_mutation(), *trailers_); + } + trailers_ = nullptr; + callback_state_ = CallbackState::Idle; + message_timer_->disableTimer(); continueProcessing(); return true; } @@ -74,8 +101,8 @@ bool ProcessorState::handleBodyResponse(const BodyResponse& response) { void ProcessorState::clearAsyncState() { cleanUpTimer(); if (callback_state_ != CallbackState::Idle) { - callback_state_ = CallbackState::Idle; continueProcessing(); + callback_state_ = CallbackState::Idle; } } @@ -85,6 +112,14 @@ void ProcessorState::cleanUpTimer() const { } } +void DecodingProcessorState::setProcessingModeInternal(const ProcessingMode& mode) { + // Account for the different default behaviors of headers and trailers -- + // headers are sent by default and trailers are not. + send_headers_ = mode.request_header_mode() != ProcessingMode::SKIP; + send_trailers_ = mode.request_trailer_mode() == ProcessingMode::SEND; + body_mode_ = mode.request_body_mode(); +} + void DecodingProcessorState::requestWatermark() { if (!watermark_requested_) { ENVOY_LOG(debug, "Watermark raised on decoding"); @@ -101,6 +136,14 @@ void DecodingProcessorState::clearWatermark() { } } +void EncodingProcessorState::setProcessingModeInternal(const ProcessingMode& mode) { + // Account for the different default behaviors of headers and trailers -- + // headers are sent by default and trailers are not. + send_headers_ = mode.response_header_mode() != ProcessingMode::SKIP; + send_trailers_ = mode.response_trailer_mode() == ProcessingMode::SEND; + body_mode_ = mode.response_body_mode(); +} + void EncodingProcessorState::requestWatermark() { if (!watermark_requested_) { ENVOY_LOG(debug, "Watermark raised on encoding"); diff --git a/source/extensions/filters/http/ext_proc/processor_state.h b/source/extensions/filters/http/ext_proc/processor_state.h index aafb2d73f81bd..61473e03c0bcd 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.h +++ b/source/extensions/filters/http/ext_proc/processor_state.h @@ -18,26 +18,43 @@ class Filter; class ProcessorState : public Logger::Loggable { public: + // This describes whether the filter is waiting for a response to a gRPC message enum class CallbackState { + // Not waiting for anything Idle, // Waiting for a "headers" response - Headers, - // Waiting for a "body" response - BufferedBody, + HeadersCallback, + // Waiting for a "body" response in buffered mode + BufferedBodyCallback, + // and waiting for a "trailers" response + TrailersCallback, }; - explicit ProcessorState(Filter& filter) : filter_(filter) {} + explicit ProcessorState(Filter& filter) + : filter_(filter), watermark_requested_(false), complete_body_available_(false), + trailers_available_(false) {} ProcessorState(const ProcessorState&) = delete; virtual ~ProcessorState() = default; ProcessorState& operator=(const ProcessorState&) = delete; CallbackState callbackState() const { return callback_state_; } void setCallbackState(CallbackState state) { callback_state_ = state; } - bool callbacksIdle() const { return callback_state_ == CallbackState::Idle; } - void setBodySendDeferred(bool deferred) { body_send_deferred_ = deferred; } + bool completeBodyAvailable() const { return complete_body_available_; } + void setCompleteBodyAvailable(bool d) { complete_body_available_ = d; } + void setTrailersAvailable(bool d) { trailers_available_ = d; } + + virtual void setProcessingMode( + const envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode& mode) PURE; + bool sendHeaders() const { return send_headers_; } + bool sendTrailers() const { return send_trailers_; } + envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode_BodySendMode + bodyMode() const { + return body_mode_; + } void setHeaders(Http::HeaderMap* headers) { headers_ = headers; } + void setTrailers(Http::HeaderMap* trailers) { trailers_ = trailers; } void startMessageTimer(Event::TimerCb cb, std::chrono::milliseconds timeout); void cleanUpTimer() const; @@ -46,15 +63,16 @@ class ProcessorState : public Logger::Loggable { virtual void requestWatermark() PURE; virtual void clearWatermark() PURE; - bool handleHeadersResponse( - const envoy::service::ext_proc::v3alpha::HeadersResponse& response, - envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode_BodySendMode body_mode); + bool handleHeadersResponse(const envoy::service::ext_proc::v3alpha::HeadersResponse& response); bool handleBodyResponse(const envoy::service::ext_proc::v3alpha::BodyResponse& response); + bool handleTrailersResponse(const envoy::service::ext_proc::v3alpha::TrailersResponse& response); virtual const Buffer::Instance* bufferedData() const PURE; virtual void addBufferedData(Buffer::Instance& data) const PURE; virtual void modifyBufferedData(std::function cb) const PURE; + virtual Http::HeaderMap* addTrailers() PURE; + virtual void continueProcessing() const PURE; void clearAsyncState(); @@ -62,22 +80,43 @@ class ProcessorState : public Logger::Loggable { mutableHeaders(envoy::service::ext_proc::v3alpha::ProcessingRequest& request) const PURE; virtual envoy::service::ext_proc::v3alpha::HttpBody* mutableBody(envoy::service::ext_proc::v3alpha::ProcessingRequest& request) const PURE; + virtual envoy::service::ext_proc::v3alpha::HttpTrailers* + mutableTrailers(envoy::service::ext_proc::v3alpha::ProcessingRequest& request) const PURE; protected: Filter& filter_; Http::StreamFilterCallbacks* filter_callbacks_; CallbackState callback_state_ = CallbackState::Idle; - // Keep track of whether we must send the body when the header processing callback is done. - bool body_send_deferred_ = false; + // Keep track of whether we requested a watermark. - bool watermark_requested_ = false; + bool watermark_requested_ : 1; + + // If true, then the filter received the complete body + bool complete_body_available_ : 1; + // If true, then the filter received the trailers + bool trailers_available_ : 1; + + // If true, the server wants to see the headers + bool send_headers_ : 1; + // If true, the server wants to see the trailers + bool send_trailers_ : 1; + + // The specific mode for body handling + envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode_BodySendMode body_mode_; + Http::HeaderMap* headers_ = nullptr; + Http::HeaderMap* trailers_ = nullptr; Event::TimerPtr message_timer_; }; class DecodingProcessorState : public ProcessorState { public: - explicit DecodingProcessorState(Filter& filter) : ProcessorState(filter) {} + explicit DecodingProcessorState( + Filter& filter, + const envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode& mode) + : ProcessorState(filter) { + setProcessingModeInternal(mode); + } DecodingProcessorState(const DecodingProcessorState&) = delete; DecodingProcessorState& operator=(const DecodingProcessorState&) = delete; @@ -98,6 +137,11 @@ class DecodingProcessorState : public ProcessorState { decoder_callbacks_->modifyDecodingBuffer(cb); } + Http::HeaderMap* addTrailers() override { + trailers_ = &decoder_callbacks_->addDecodedTrailers(); + return trailers_; + } + void continueProcessing() const override { decoder_callbacks_->continueDecoding(); } envoy::service::ext_proc::v3alpha::HttpHeaders* @@ -110,16 +154,34 @@ class DecodingProcessorState : public ProcessorState { return request.mutable_request_body(); } + envoy::service::ext_proc::v3alpha::HttpTrailers* + mutableTrailers(envoy::service::ext_proc::v3alpha::ProcessingRequest& request) const override { + return request.mutable_request_trailers(); + } + + void setProcessingMode( + const envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode& mode) override { + setProcessingModeInternal(mode); + } + void requestWatermark() override; void clearWatermark() override; private: + void setProcessingModeInternal( + const envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode& mode); + Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; }; class EncodingProcessorState : public ProcessorState { public: - explicit EncodingProcessorState(Filter& filter) : ProcessorState(filter) {} + explicit EncodingProcessorState( + Filter& filter, + const envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode& mode) + : ProcessorState(filter) { + setProcessingModeInternal(mode); + } EncodingProcessorState(const EncodingProcessorState&) = delete; EncodingProcessorState& operator=(const EncodingProcessorState&) = delete; @@ -140,6 +202,11 @@ class EncodingProcessorState : public ProcessorState { encoder_callbacks_->modifyEncodingBuffer(cb); } + Http::HeaderMap* addTrailers() override { + trailers_ = &encoder_callbacks_->addEncodedTrailers(); + return trailers_; + } + void continueProcessing() const override { encoder_callbacks_->continueEncoding(); } envoy::service::ext_proc::v3alpha::HttpHeaders* @@ -152,10 +219,23 @@ class EncodingProcessorState : public ProcessorState { return request.mutable_response_body(); } + envoy::service::ext_proc::v3alpha::HttpTrailers* + mutableTrailers(envoy::service::ext_proc::v3alpha::ProcessingRequest& request) const override { + return request.mutable_response_trailers(); + } + + void setProcessingMode( + const envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode& mode) override { + setProcessingModeInternal(mode); + } + void requestWatermark() override; void clearWatermark() override; private: + void setProcessingModeInternal( + const envoy::extensions::filters::http::ext_proc::v3alpha::ProcessingMode& mode); + Http::StreamEncoderFilterCallbacks* encoder_callbacks_{}; }; diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index 153d5f85bca0f..ae3eeeeb6e90e 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -14,6 +14,7 @@ envoy_package() envoy_extension_cc_test( name = "config_test", + size = "small", srcs = ["config_test.cc"], extension_name = "envoy.filters.http.ext_proc", deps = [ @@ -25,6 +26,7 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "filter_test", + size = "small", srcs = ["filter_test.cc"], extension_name = "envoy.filters.http.ext_proc", deps = [ @@ -40,6 +42,7 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "ordering_test", + size = "small", srcs = ["ordering_test.cc"], extension_name = "envoy.filters.http.ext_proc", deps = [ @@ -54,6 +57,7 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "client_test", + size = "small", srcs = ["client_test.cc"], extension_name = "envoy.filters.http.ext_proc", deps = [ @@ -68,6 +72,7 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "mutation_utils_test", + size = "small", srcs = ["mutation_utils_test.cc"], extension_name = "envoy.filters.http.ext_proc", deps = [ @@ -79,6 +84,7 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "ext_proc_integration_test", + size = "medium", srcs = ["ext_proc_integration_test.cc"], extension_name = "envoy.filters.http.ext_proc", deps = [ diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index 4f37f47e91250..41c432e1dd70d 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -20,9 +20,11 @@ using envoy::service::ext_proc::v3alpha::BodyResponse; using envoy::service::ext_proc::v3alpha::HeadersResponse; using envoy::service::ext_proc::v3alpha::HttpBody; using envoy::service::ext_proc::v3alpha::HttpHeaders; +using envoy::service::ext_proc::v3alpha::HttpTrailers; using envoy::service::ext_proc::v3alpha::ImmediateResponse; using envoy::service::ext_proc::v3alpha::ProcessingRequest; using envoy::service::ext_proc::v3alpha::ProcessingResponse; +using envoy::service::ext_proc::v3alpha::TrailersResponse; using Extensions::HttpFilters::ExternalProcessing::HasNoHeader; using Extensions::HttpFilters::ExternalProcessing::HeaderProtosEqual; using Extensions::HttpFilters::ExternalProcessing::SingleHeaderValueIs; @@ -38,7 +40,7 @@ using namespace std::chrono_literals; class ExtProcIntegrationTest : public HttpIntegrationTest, public Grpc::GrpcClientIntegrationParamTest { protected: - ExtProcIntegrationTest() : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, ipVersion()) {} + ExtProcIntegrationTest() : HttpIntegrationTest(Http::CodecClient::Type::HTTP2, ipVersion()) {} void createUpstreams() override { // Need to create a separate "upstream" for the gRPC server @@ -61,6 +63,10 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, server_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); server_cluster->set_name("ext_proc_server"); server_cluster->mutable_load_assignment()->set_cluster_name("ext_proc_server"); + + // Ensure "HTTP2 with no prior knowledge." Necessary for gRPC and for headers + ConfigHelper::setHttp2( + *(bootstrap.mutable_static_resources()->mutable_clusters()->Mutable(0))); ConfigHelper::setHttp2(*server_cluster); // Load configuration of the server from YAML and use a helper to add a grpc_service @@ -75,6 +81,7 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, ext_proc_filter.mutable_typed_config()->PackFrom(proto_config_); config_helper_.addFilter(MessageUtil::getJsonStringFromMessageOrDie(ext_proc_filter)); }); + setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); setDownstreamProtocol(Http::CodecClient::Type::HTTP2); } @@ -117,6 +124,15 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, upstream_request_->encodeData(100, true); } + void handleUpstreamRequestWithTrailer() { + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForEndStream(*dispatcher_)); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + upstream_request_->encodeData(100, false); + upstream_request_->encodeTrailers(Http::TestResponseTrailerMapImpl{{"x-test-trailers", "Yes"}}); + } + void handleUpstreamRequestWithResponse(const Buffer::Instance& all_data, uint64_t chunk_size) { ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); @@ -227,6 +243,28 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, } } + void processResponseTrailersMessage( + bool first_message, + absl::optional> cb) { + ProcessingRequest request; + if (first_message) { + ASSERT_TRUE( + fake_upstreams_.back()->waitForHttpConnection(*dispatcher_, processor_connection_)); + ASSERT_TRUE(processor_connection_->waitForNewStream(*dispatcher_, processor_stream_)); + } + ASSERT_TRUE(processor_stream_->waitForGrpcMessage(*dispatcher_, request)); + ASSERT_TRUE(request.has_response_trailers()); + if (first_message) { + processor_stream_->startGrpcStream(); + } + ProcessingResponse response; + auto* body = response.mutable_response_trailers(); + const bool sendReply = !cb || (*cb)(request.response_trailers(), *body); + if (sendReply) { + processor_stream_->sendGrpcMessage(response); + } + } + void processAndRespondImmediately(bool first_message, absl::optional> cb) { ProcessingRequest request; @@ -444,6 +482,60 @@ TEST_P(ExtProcIntegrationTest, GetAndSetHeadersOnResponse) { EXPECT_THAT(response->headers(), SingleHeaderValueIs("x-response-processed", "1")); } +// Test the filter using the default configuration by connecting to +// an ext_proc server that responds to the response_headers message +// by checking the headers and modifying the trailers +TEST_P(ExtProcIntegrationTest, GetAndSetHeadersAndTrailersOnResponse) { + proto_config_.mutable_processing_mode()->set_response_trailer_mode(ProcessingMode::SEND); + initializeConfig(); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + processRequestHeadersMessage(true, absl::nullopt); + handleUpstreamRequestWithTrailer(); + + processResponseHeadersMessage(false, absl::nullopt); + processResponseTrailersMessage(false, [](const HttpTrailers& trailers, TrailersResponse& resp) { + Http::TestResponseTrailerMapImpl expected_trailers{{"x-test-trailers", "Yes"}}; + EXPECT_THAT(trailers.trailers(), HeaderProtosEqual(expected_trailers)); + auto* trailer_mut = resp.mutable_header_mutation(); + auto* trailer_add = trailer_mut->add_set_headers(); + trailer_add->mutable_header()->set_key("x-modified-trailers"); + trailer_add->mutable_header()->set_value("xxx"); + return true; + }); + + verifyDownstreamResponse(*response, 200); + ASSERT_TRUE(response->trailers()); + EXPECT_THAT(*(response->trailers()), SingleHeaderValueIs("x-test-trailers", "Yes")); + EXPECT_THAT(*(response->trailers()), SingleHeaderValueIs("x-modified-trailers", "xxx")); +} + +// Test the filter configured to only send the response trailers message +TEST_P(ExtProcIntegrationTest, GetAndSetOnlyTrailersOnResponse) { + auto* mode = proto_config_.mutable_processing_mode(); + mode->set_request_header_mode(ProcessingMode::SKIP); + mode->set_response_header_mode(ProcessingMode::SKIP); + mode->set_response_trailer_mode(ProcessingMode::SEND); + initializeConfig(); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + handleUpstreamRequestWithTrailer(); + processResponseTrailersMessage(true, [](const HttpTrailers& trailers, TrailersResponse& resp) { + Http::TestResponseTrailerMapImpl expected_trailers{{"x-test-trailers", "Yes"}}; + EXPECT_THAT(trailers.trailers(), HeaderProtosEqual(expected_trailers)); + auto* trailer_mut = resp.mutable_header_mutation(); + auto* trailer_add = trailer_mut->add_set_headers(); + trailer_add->mutable_header()->set_key("x-modified-trailers"); + trailer_add->mutable_header()->set_value("xxx"); + return true; + }); + + verifyDownstreamResponse(*response, 200); + ASSERT_TRUE(response->trailers()); + EXPECT_THAT(*(response->trailers()), SingleHeaderValueIs("x-test-trailers", "Yes")); + EXPECT_THAT(*(response->trailers()), SingleHeaderValueIs("x-modified-trailers", "xxx")); +} + // Test the filter with a response body callback enabled using an // an ext_proc server that responds to the response_body message // by requesting to modify the response body and headers. @@ -481,6 +573,80 @@ TEST_P(ExtProcIntegrationTest, GetAndSetBodyAndHeadersOnResponse) { EXPECT_EQ("Hello, World!", response->body()); } +// Test the filter with a response body callback enabled using an +// an ext_proc server that responds to the response_body message +// by requesting to modify the response body and headers. +TEST_P(ExtProcIntegrationTest, GetAndSetBodyAndHeadersAndTrailersOnResponse) { + auto* mode = proto_config_.mutable_processing_mode(); + mode->set_response_body_mode(ProcessingMode::BUFFERED); + mode->set_response_trailer_mode(ProcessingMode::SEND); + initializeConfig(); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + processRequestHeadersMessage(true, absl::nullopt); + handleUpstreamRequestWithTrailer(); + processResponseHeadersMessage(false, absl::nullopt); + + // Should get just one message with the body + processResponseBodyMessage(false, [](const HttpBody& body, BodyResponse&) { + EXPECT_TRUE(body.end_of_stream()); + return true; + }); + + processResponseTrailersMessage(false, [](const HttpTrailers& trailers, TrailersResponse& resp) { + Http::TestResponseTrailerMapImpl expected_trailers{{"x-test-trailers", "Yes"}}; + EXPECT_THAT(trailers.trailers(), HeaderProtosEqual(expected_trailers)); + auto* trailer_mut = resp.mutable_header_mutation(); + auto* trailer_add = trailer_mut->add_set_headers(); + trailer_add->mutable_header()->set_key("x-modified-trailers"); + trailer_add->mutable_header()->set_value("xxx"); + return true; + }); + + verifyDownstreamResponse(*response, 200); + ASSERT_TRUE(response->trailers()); + EXPECT_THAT(*(response->trailers()), SingleHeaderValueIs("x-test-trailers", "Yes")); + EXPECT_THAT(*(response->trailers()), SingleHeaderValueIs("x-modified-trailers", "xxx")); +} + +// Test the filter using a configuration that processes response trailers, and process an upstream +// response that has no trailers, so add them. +TEST_P(ExtProcIntegrationTest, AddTrailersOnResponse) { + proto_config_.mutable_processing_mode()->set_response_trailer_mode(ProcessingMode::SEND); + initializeConfig(); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + processRequestHeadersMessage(true, absl::nullopt); + handleUpstreamRequest(); + processResponseHeadersMessage(false, absl::nullopt); + processResponseTrailersMessage(false, [](const HttpTrailers&, TrailersResponse& resp) { + auto* trailer_mut = resp.mutable_header_mutation(); + auto* trailer_add = trailer_mut->add_set_headers(); + trailer_add->mutable_header()->set_key("x-modified-trailers"); + trailer_add->mutable_header()->set_value("xxx"); + return true; + }); + + verifyDownstreamResponse(*response, 200); + ASSERT_TRUE(response->trailers()); + EXPECT_THAT(*(response->trailers()), SingleHeaderValueIs("x-modified-trailers", "xxx")); +} + +// Test the filter using a configuration that processes response trailers, and process an upstream +// response that has no trailers, but when the time comes, don't actually add anything. +TEST_P(ExtProcIntegrationTest, AddTrailersOnResponseJustKidding) { + proto_config_.mutable_processing_mode()->set_response_trailer_mode(ProcessingMode::SEND); + initializeConfig(); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + processRequestHeadersMessage(true, absl::nullopt); + handleUpstreamRequest(); + processResponseHeadersMessage(false, absl::nullopt); + processResponseTrailersMessage(false, absl::nullopt); + + verifyDownstreamResponse(*response, 200); +} + // Test the filter with a response body callback enabled using an // an ext_proc server that responds to the response_body message // by requesting to modify the response body and headers, using a response diff --git a/test/extensions/filters/http/ext_proc/ordering_test.cc b/test/extensions/filters/http/ext_proc/ordering_test.cc index 3d111e7e778dc..0ae4543296dfb 100644 --- a/test/extensions/filters/http/ext_proc/ordering_test.cc +++ b/test/extensions/filters/http/ext_proc/ordering_test.cc @@ -34,6 +34,7 @@ using Http::LowerCaseString; using testing::Invoke; using testing::Return; +using testing::ReturnRef; using testing::Unused; using namespace std::chrono_literals; @@ -105,12 +106,16 @@ class OrderingTest : public testing::Test { filter_->encodeHeaders(response_headers_, false)); } - void sendRequestTrailers() { - EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + void sendRequestTrailers(bool expect_callback) { + EXPECT_EQ(expect_callback ? FilterTrailersStatus::StopIteration + : FilterTrailersStatus::Continue, + filter_->decodeTrailers(request_trailers_)); } - void sendResponseTrailers() { - EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + void sendResponseTrailers(bool expect_callback) { + EXPECT_EQ(expect_callback ? FilterTrailersStatus::StopIteration + : FilterTrailersStatus::Continue, + filter_->encodeTrailers(response_trailers_)); } // Make it easier to send responses from the external processor @@ -139,6 +144,12 @@ class OrderingTest : public testing::Test { stream_callbacks_->onReceiveMessage(std::move(reply)); } + void sendRequestTrailersReply() { + auto reply = std::make_unique(); + reply->mutable_request_trailers(); + stream_callbacks_->onReceiveMessage(std::move(reply)); + } + void sendResponseTrailersReply() { auto reply = std::make_unique(); reply->mutable_response_trailers(); @@ -207,6 +218,23 @@ class FastFailOrderingTest : public OrderingTest { // *** Tests for the normal processing path *** +// A call with a totally crazy response +TEST_F(OrderingTest, TotallyInvalidResponse) { + initialize(absl::nullopt); + + EXPECT_CALL(stream_delegate_, send(_, false)); + sendRequestHeadersGet(true); + EXPECT_CALL(decoder_callbacks_, continueDecoding()); + auto reply = std::make_unique(); + // Totally empty response is spurious -- we should ignore the server for + // all subsequent callbacks as we do for other spurious messages. + stream_callbacks_->onReceiveMessage(std::move(reply)); + + sendResponseHeaders(false); + Buffer::OwnedImpl req_body("Hello!"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(req_body, true)); +} + // A normal call with the default configuration TEST_F(OrderingTest, DefaultOrderingGet) { initialize(absl::nullopt); @@ -215,7 +243,6 @@ TEST_F(OrderingTest, DefaultOrderingGet) { sendRequestHeadersGet(true); EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestHeadersReply(); - sendRequestTrailers(); EXPECT_CALL(stream_delegate_, send(_, false)); sendResponseHeaders(true); @@ -223,7 +250,6 @@ TEST_F(OrderingTest, DefaultOrderingGet) { sendResponseHeadersReply(); Buffer::OwnedImpl req_body("Hello!"); EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(req_body, true)); - sendResponseTrailers(); } // A normal call with the default configuration, with a mock timer so that we can @@ -241,7 +267,6 @@ TEST_F(OrderingTest, DefaultOrderingGetWithTimer) { EXPECT_CALL(decoder_callbacks_, continueDecoding()); EXPECT_CALL(*request_timer, disableTimer()); sendRequestHeadersReply(); - sendRequestTrailers(); MockTimer* response_timer = new MockTimer(&dispatcher_); EXPECT_CALL(*response_timer, enableTimer(kMessageTimeout, nullptr)); @@ -252,11 +277,10 @@ TEST_F(OrderingTest, DefaultOrderingGetWithTimer) { sendResponseHeadersReply(); Buffer::OwnedImpl req_body("Hello!"); EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(req_body, true)); - sendResponseTrailers(); } // A normal call with all supported callbacks turned on -TEST_F(OrderingTest, DefaultOrderingAllCallbacks) { +TEST_F(OrderingTest, DefaultOrderingHeadersBody) { initialize([](ExternalProcessor& cfg) { auto* pm = cfg.mutable_processing_mode(); pm->set_request_body_mode(ProcessingMode::BUFFERED); @@ -281,7 +305,6 @@ TEST_F(OrderingTest, DefaultOrderingAllCallbacks) { EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_body_2, true)); EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestBodyReply(); - sendRequestTrailers(); Buffer::OwnedImpl resp_body_1("Dummy response"); Buffer::OwnedImpl resp_buffer; @@ -291,10 +314,64 @@ TEST_F(OrderingTest, DefaultOrderingAllCallbacks) { sendResponseHeaders(true); sendResponseHeadersReply(); EXPECT_CALL(stream_delegate_, send(_, false)); - EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_body_1, true)); + // Remember that end_stream is false if there will be headers + EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->encodeData(resp_body_1, false)); + resp_buffer.add(resp_body_1); + sendResponseTrailers(true); EXPECT_CALL(encoder_callbacks_, continueEncoding()); sendResponseBodyReply(); - sendResponseTrailers(); +} + +// A normal call with all supported callbacks turned on +TEST_F(OrderingTest, DefaultOrderingEverything) { + initialize([](ExternalProcessor& cfg) { + auto* pm = cfg.mutable_processing_mode(); + pm->set_request_body_mode(ProcessingMode::BUFFERED); + pm->set_response_body_mode(ProcessingMode::BUFFERED); + pm->set_request_trailer_mode(ProcessingMode::SEND); + pm->set_response_trailer_mode(ProcessingMode::SEND); + }); + + EXPECT_CALL(stream_delegate_, send(_, false)); + sendRequestHeadersPost(true); + sendRequestHeadersReply(); + + Buffer::OwnedImpl req_body_1; + req_body_1.add("Dummy data 1"); + Buffer::OwnedImpl req_body_2; + req_body_2.add("Dummy data 2"); + Buffer::OwnedImpl req_buffer; + expectBufferedRequest(req_buffer, true); + + EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_body_1, false)); + req_buffer.add(req_body_1); + EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_body_2, false)); + req_buffer.add(req_body_2); + + EXPECT_CALL(stream_delegate_, send(_, false)); + sendRequestTrailers(true); + + EXPECT_CALL(stream_delegate_, send(_, false)); + sendRequestBodyReply(); + EXPECT_CALL(decoder_callbacks_, continueDecoding()); + sendRequestTrailersReply(); + + Buffer::OwnedImpl resp_body_1("Dummy response"); + Buffer::OwnedImpl resp_buffer; + expectBufferedResponse(resp_buffer, true); + + EXPECT_CALL(stream_delegate_, send(_, false)); + sendResponseHeaders(true); + sendResponseHeadersReply(); + EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->encodeData(resp_body_1, false)); + resp_buffer.add(resp_body_1); + + EXPECT_CALL(stream_delegate_, send(_, false)); + sendResponseTrailers(true); + EXPECT_CALL(stream_delegate_, send(_, false)); + sendResponseBodyReply(); + EXPECT_CALL(encoder_callbacks_, continueEncoding()); + sendResponseTrailersReply(); } // A normal call with all supported callbacks turned on, @@ -341,14 +418,11 @@ TEST_F(OrderingTest, DefaultOrderingAllCallbacksInterleaved) { EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestBodyReply(); - sendRequestTrailers(); - EXPECT_CALL(stream_delegate_, send(_, false)); expectBufferedResponse(resp_buffer, true); EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_body_2, true)); EXPECT_CALL(encoder_callbacks_, continueEncoding()); sendResponseBodyReply(); - sendResponseTrailers(); } // A normal call with response buffering on. All response data comes back before the @@ -416,6 +490,44 @@ TEST_F(OrderingTest, ResponseSomeDataComesFast) { sendResponseBodyReply(); } +// Add trailers to the request path +TEST_F(OrderingTest, AddRequestTrailers) { + initialize([](ExternalProcessor& cfg) { + auto* pm = cfg.mutable_processing_mode(); + pm->set_request_trailer_mode(ProcessingMode::SEND); + }); + + EXPECT_CALL(stream_delegate_, send(_, false)); + sendRequestHeadersPost(true); + EXPECT_CALL(decoder_callbacks_, continueDecoding()); + sendRequestHeadersReply(); + + Buffer::OwnedImpl req_body_1; + req_body_1.add("Dummy data 1"); + Buffer::OwnedImpl req_buffer; + expectBufferedRequest(req_buffer, false); + + Http::TestRequestTrailerMapImpl response_trailers; + + // Expect the trailers callback to be sent in line with decodeData + EXPECT_CALL(stream_delegate_, send(_, false)); + EXPECT_CALL(decoder_callbacks_, addDecodedTrailers()).WillOnce(ReturnRef(response_trailers)); + EXPECT_EQ(FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(req_body_1, true)); + req_buffer.add(req_body_1); + EXPECT_CALL(decoder_callbacks_, continueDecoding()); + sendRequestTrailersReply(); + + Buffer::OwnedImpl resp_body_1("Dummy response"); + Buffer::OwnedImpl resp_buffer; + expectBufferedResponse(resp_buffer, false); + + EXPECT_CALL(stream_delegate_, send(_, false)); + sendResponseHeaders(true); + EXPECT_CALL(encoder_callbacks_, continueEncoding()); + sendResponseHeadersReply(); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body_1, true)); +} + // An immediate response on the request path TEST_F(OrderingTest, ImmediateResponseOnRequest) { initialize(absl::nullopt); @@ -435,7 +547,6 @@ TEST_F(OrderingTest, ImmediateResponseOnResponse) { sendRequestHeadersGet(true); EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestHeadersReply(); - sendRequestTrailers(); EXPECT_CALL(stream_delegate_, send(_, false)); sendResponseHeaders(true); @@ -443,7 +554,6 @@ TEST_F(OrderingTest, ImmediateResponseOnResponse) { sendImmediateResponse500(); Buffer::OwnedImpl resp_body("Hello!"); EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); - sendResponseTrailers(); } // *** Tests of out-of-order messages *** @@ -460,13 +570,11 @@ TEST_F(OrderingTest, IncorrectRequestHeadersReply) { sendRequestHeadersGet(true); EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendResponseHeadersReply(); // Wrong message here - sendRequestTrailers(); // Expect us to go on from here normally but send no more stream messages sendResponseHeaders(false); Buffer::OwnedImpl resp_body("Hello!"); EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); - sendResponseTrailers(); } // Receive a response trailers reply in response to the request @@ -479,13 +587,11 @@ TEST_F(OrderingTest, IncorrectRequestHeadersReply2) { sendRequestHeadersGet(true); EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendResponseTrailersReply(); // Wrong message here - sendRequestTrailers(); // Expect us to go on from here normally but send no more stream messages sendResponseHeaders(false); Buffer::OwnedImpl resp_body("Hello!"); EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); - sendResponseTrailers(); } // Receive a response body reply in response to the request @@ -512,13 +618,11 @@ TEST_F(OrderingTest, IncorrectRequestBodyReply) { EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_body_1, true)); EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendResponseBodyReply(); // Wrong message here - sendRequestTrailers(); // Expect us to go on from here normally but send no more stream messages sendResponseHeaders(false); Buffer::OwnedImpl resp_body("Hello!"); EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); - sendResponseTrailers(); } // Receive a request headers reply in response to the response @@ -530,7 +634,6 @@ TEST_F(OrderingTest, IncorrectResponseHeadersReply) { sendRequestHeadersGet(true); EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestHeadersReply(); - sendRequestTrailers(); EXPECT_CALL(stream_delegate_, send(_, false)); sendResponseHeaders(true); @@ -539,7 +642,6 @@ TEST_F(OrderingTest, IncorrectResponseHeadersReply) { // Still should ignore the message and go on but send no more stream messages Buffer::OwnedImpl resp_body("Hello!"); EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); - sendResponseTrailers(); } // Receive an extra message -- we should ignore it @@ -556,11 +658,9 @@ TEST_F(OrderingTest, ExtraReply) { sendRequestHeadersReply(); // After this we are ignoring the processor - sendRequestTrailers(); sendResponseHeaders(false); Buffer::OwnedImpl resp_body("Hello!"); EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); - sendResponseTrailers(); } // Receive an extra message after the immediate response -- it should @@ -613,11 +713,9 @@ TEST_F(OrderingTest, GrpcErrorInlineIgnored) { sendGrpcError(); // After that we ignore the processor - sendRequestTrailers(); sendResponseHeaders(false); Buffer::OwnedImpl resp_body("Hello!"); EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); - sendResponseTrailers(); } // gRPC error in between calls should still be delivered @@ -633,7 +731,6 @@ TEST_F(OrderingTest, GrpcErrorOutOfLine) { EXPECT_CALL(decoder_callbacks_, continueDecoding()); EXPECT_CALL(*request_timer, disableTimer()); sendRequestHeadersReply(); - sendRequestTrailers(); EXPECT_CALL(encoder_callbacks_, sendLocalReply(Http::Code::InternalServerError, _, _, _, _)); EXPECT_CALL(*request_timer, enabled()).WillOnce(Return(false)); @@ -651,11 +748,9 @@ TEST_F(OrderingTest, GrpcCloseAfter) { closeGrpcStream(); // After that we ignore the processor - sendRequestTrailers(); sendResponseHeaders(false); Buffer::OwnedImpl resp_body("Hello!"); EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); - sendResponseTrailers(); } // gRPC error might be received after a message timeout has fired @@ -701,7 +796,6 @@ TEST_F(OrderingTest, TimeoutOnResponseBody) { EXPECT_CALL(*request_timer, disableTimer()); EXPECT_CALL(decoder_callbacks_, continueDecoding()); sendRequestBodyReply(); - sendRequestTrailers(); Buffer::OwnedImpl resp_body("Dummy response"); Buffer::OwnedImpl buffered_response; @@ -878,6 +972,80 @@ TEST_F(FastFailOrderingTest, GrpcErrorIgnoredOnStartResponseBody) { EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); } +// gRPC failure while opening stream with only response body enabled +TEST_F(FastFailOrderingTest, GrpcErrorOnStartResponseTrailers) { + initialize([](ExternalProcessor& cfg) { + auto* pm = cfg.mutable_processing_mode(); + pm->set_request_header_mode(ProcessingMode::SKIP); + pm->set_response_header_mode(ProcessingMode::SKIP); + pm->set_response_trailer_mode(ProcessingMode::SEND); + }); + + sendRequestHeadersGet(false); + sendResponseHeaders(false); + Buffer::OwnedImpl resp_body("Hello!"); + Buffer::OwnedImpl resp_buf; + expectBufferedResponse(resp_buf, false); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, false)); + EXPECT_CALL(encoder_callbacks_, sendLocalReply(Http::Code::InternalServerError, _, _, _, _)); + EXPECT_EQ(FilterTrailersStatus::StopIteration, filter_->encodeTrailers(response_trailers_)); +} + +// gRPC failure while opening stream with only response body enabled but errors ignored +TEST_F(FastFailOrderingTest, GrpcErrorIgnoredOnStartResponseTrailers) { + initialize([](ExternalProcessor& cfg) { + cfg.set_failure_mode_allow(true); + auto* pm = cfg.mutable_processing_mode(); + pm->set_request_header_mode(ProcessingMode::SKIP); + pm->set_response_header_mode(ProcessingMode::SKIP); + pm->set_response_trailer_mode(ProcessingMode::SEND); + }); + + sendRequestHeadersGet(false); + sendResponseHeaders(false); + Buffer::OwnedImpl resp_body("Hello!"); + Buffer::OwnedImpl resp_buf; + expectBufferedResponse(resp_buf, false); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, false)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); +} + +// gRPC failure while opening stream with only response body enabled +TEST_F(FastFailOrderingTest, GrpcErrorOnStartAddResponseTrailers) { + initialize([](ExternalProcessor& cfg) { + auto* pm = cfg.mutable_processing_mode(); + pm->set_request_header_mode(ProcessingMode::SKIP); + pm->set_response_header_mode(ProcessingMode::SKIP); + pm->set_response_trailer_mode(ProcessingMode::SEND); + }); + + sendRequestHeadersGet(false); + sendResponseHeaders(false); + Buffer::OwnedImpl resp_body("Hello!"); + EXPECT_CALL(encoder_callbacks_, sendLocalReply(Http::Code::InternalServerError, _, _, _, _)); + Http::TestResponseTrailerMapImpl response_trailers; + EXPECT_CALL(encoder_callbacks_, addEncodedTrailers()).WillOnce(ReturnRef(response_trailers)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_body, true)); +} + +// gRPC failure while opening stream with only response body enabled but errors ignored +TEST_F(FastFailOrderingTest, GrpcErrorIgnoredOnStartAddResponseTrailers) { + initialize([](ExternalProcessor& cfg) { + cfg.set_failure_mode_allow(true); + auto* pm = cfg.mutable_processing_mode(); + pm->set_request_header_mode(ProcessingMode::SKIP); + pm->set_response_header_mode(ProcessingMode::SKIP); + pm->set_response_trailer_mode(ProcessingMode::SEND); + }); + + sendRequestHeadersGet(false); + sendResponseHeaders(false); + Buffer::OwnedImpl resp_body("Hello!"); + Http::TestResponseTrailerMapImpl response_trailers; + EXPECT_CALL(encoder_callbacks_, addEncodedTrailers()).WillOnce(ReturnRef(response_trailers)); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_body, true)); +} + } // namespace } // namespace ExternalProcessing } // namespace HttpFilters From ce1cb0ed18b6a6b8f6ddecb820424cec63b1cc31 Mon Sep 17 00:00:00 2001 From: williamsfu99 <32112201+williamsfu99@users.noreply.github.com> Date: Wed, 28 Apr 2021 17:51:35 -0700 Subject: [PATCH 104/209] http local_ratelimit: add request_headers_to_add option (#16178) Introduce the ability to add request headers to forwarded upstream requests when the local HTTP rate limit filter determines (through its configured ruleset) that a request should be rate limited, but is not hard enforcing the incoming traffic. Additional Description: This is useful when a backend service should take customized action when a particular traffic volume is reached, rather than forcing request shedding by the proxy. Risk Level: Low, adding a new feature Testing: Modified unit tests Docs Changes: Updated associated doc Signed-off-by: William Fu --- .../local_ratelimit/v3/local_rate_limit.proto | 10 +++++++-- .../http_filters/local_rate_limit_filter.rst | 11 +++++++--- .../local_ratelimit/v3/local_rate_limit.proto | 10 +++++++-- .../http/local_ratelimit/local_ratelimit.cc | 3 +++ .../http/local_ratelimit/local_ratelimit.h | 2 ++ .../http/local_ratelimit/filter_test.cc | 21 +++++++++++++++---- 6 files changed, 46 insertions(+), 11 deletions(-) diff --git a/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto b/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto index bc2e42fced1be..59c61d281aa0b 100644 --- a/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto +++ b/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Local Rate limit :ref:`configuration overview `. // [#extension: envoy.filters.http.local_ratelimit] -// [#next-free-field: 10] +// [#next-free-field: 11] message LocalRateLimit { // The human readable prefix to use when emitting stats. string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; @@ -63,8 +63,14 @@ message LocalRateLimit { // Defaults to 0% of requests for safety. config.core.v3.RuntimeFractionalPercent filter_enforced = 5; + // Specifies a list of HTTP headers that should be added to each request that + // has been rate limited and is also forwarded upstream. This can only occur when the + // filter is enabled but not enforced. + repeated config.core.v3.HeaderValueOption request_headers_to_add_when_not_enforced = 10 + [(validate.rules).repeated = {max_items: 10}]; + // Specifies a list of HTTP headers that should be added to each response for requests that - // have been rate limited. + // have been rate limited. This occurs when the filter is either enabled or fully enforced. repeated config.core.v3.HeaderValueOption response_headers_to_add = 6 [(validate.rules).repeated = {max_items: 10}]; diff --git a/docs/root/configuration/http/http_filters/local_rate_limit_filter.rst b/docs/root/configuration/http/http_filters/local_rate_limit_filter.rst index 7933b04a320ec..4467ba080a41f 100644 --- a/docs/root/configuration/http/http_filters/local_rate_limit_filter.rst +++ b/docs/root/configuration/http/http_filters/local_rate_limit_filter.rst @@ -13,9 +13,14 @@ limit when the request's route or virtual host has a per filter :ref:`local rate limit configuration `. If the local rate limit token bucket is checked, and there are no tokens available, a 429 response is returned -(the response is configurable). The local rate limit filter also sets the -:ref:`x-envoy-ratelimited` header. Additional response -headers may be configured. +(the response is configurable). The local rate limit filter then sets the +:ref:`x-envoy-ratelimited` response header. :ref:`Additional response headers +` can be +configured to be returned. + +:ref:`Request headers +` can be +configured to be added to forwarded requests to the upstream when the local rate limit filter is enabled but not enforced. .. note:: The token bucket is shared across all workers, thus the rate limits are applied per Envoy process. diff --git a/generated_api_shadow/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto b/generated_api_shadow/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto index bc2e42fced1be..59c61d281aa0b 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Local Rate limit :ref:`configuration overview `. // [#extension: envoy.filters.http.local_ratelimit] -// [#next-free-field: 10] +// [#next-free-field: 11] message LocalRateLimit { // The human readable prefix to use when emitting stats. string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; @@ -63,8 +63,14 @@ message LocalRateLimit { // Defaults to 0% of requests for safety. config.core.v3.RuntimeFractionalPercent filter_enforced = 5; + // Specifies a list of HTTP headers that should be added to each request that + // has been rate limited and is also forwarded upstream. This can only occur when the + // filter is enabled but not enforced. + repeated config.core.v3.HeaderValueOption request_headers_to_add_when_not_enforced = 10 + [(validate.rules).repeated = {max_items: 10}]; + // Specifies a list of HTTP headers that should be added to each response for requests that - // have been rate limited. + // have been rate limited. This occurs when the filter is either enabled or fully enforced. repeated config.core.v3.HeaderValueOption response_headers_to_add = 6 [(validate.rules).repeated = {max_items: 10}]; diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc index 263d77849dc21..30ed854930e51 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc @@ -38,6 +38,8 @@ FilterConfig::FilterConfig( : absl::nullopt), response_headers_parser_( Envoy::Router::HeaderParser::configure(config.response_headers_to_add())), + request_headers_parser_(Envoy::Router::HeaderParser::configure( + config.request_headers_to_add_when_not_enforced())), stage_(static_cast(config.stage())), has_descriptors_(!config.descriptors().empty()) { // Note: no token bucket is fine for the global config, which would be the case for enabling @@ -90,6 +92,7 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, config->stats().rate_limited_.inc(); if (!config->enforced()) { + config->requestHeadersParser().evaluateHeaders(headers, decoder_callbacks_->streamInfo()); return Http::FilterHeadersStatus::Continue; } diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.h b/source/extensions/filters/http/local_ratelimit/local_ratelimit.h index cffbc399c05d5..af27e23efe4ef 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.h +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.h @@ -58,6 +58,7 @@ class FilterConfig : public Router::RouteSpecificFilterConfig { bool enforced() const; LocalRateLimitStats& stats() const { return stats_; } const Router::HeaderParser& responseHeadersParser() const { return *response_headers_parser_; } + const Router::HeaderParser& requestHeadersParser() const { return *request_headers_parser_; } Http::Code status() const { return status_; } uint64_t stage() const { return stage_; } bool hasDescriptors() const { return has_descriptors_; } @@ -83,6 +84,7 @@ class FilterConfig : public Router::RouteSpecificFilterConfig { const absl::optional filter_enabled_; const absl::optional filter_enforced_; Router::HeaderParserPtr response_headers_parser_; + Router::HeaderParserPtr request_headers_parser_; const uint64_t stage_; const bool has_descriptors_; }; diff --git a/test/extensions/filters/http/local_ratelimit/filter_test.cc b/test/extensions/filters/http/local_ratelimit/filter_test.cc index 0c9f2507e016b..cd9b6ec84f7b0 100644 --- a/test/extensions/filters/http/local_ratelimit/filter_test.cc +++ b/test/extensions/filters/http/local_ratelimit/filter_test.cc @@ -34,6 +34,11 @@ stat_prefix: test header: key: x-test-rate-limit value: 'true' +request_headers_to_add_when_not_enforced: + - append: false + header: + key: x-local-ratelimited + value: 'true' )"; class FilterTest : public testing::Test { @@ -128,8 +133,12 @@ TEST_F(FilterTest, RequestRateLimited) { EXPECT_EQ(details, "local_rate_limited"); })); - auto headers = Http::TestRequestHeaderMapImpl(); - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); + auto request_headers = Http::TestRequestHeaderMapImpl(); + auto expected_headers = Http::TestRequestHeaderMapImpl(); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(request_headers, expected_headers); EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.enabled")); EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.enforced")); EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.rate_limited")); @@ -140,8 +149,12 @@ TEST_F(FilterTest, RequestRateLimitedButNotEnforced) { EXPECT_CALL(decoder_callbacks_, sendLocalReply(Http::Code::TooManyRequests, _, _, _, _)).Times(0); - auto headers = Http::TestRequestHeaderMapImpl(); - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + auto request_headers = Http::TestRequestHeaderMapImpl(); + Http::TestRequestHeaderMapImpl expected_headers{ + {"x-local-ratelimited", "true"}, + }; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(request_headers, expected_headers); EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.enabled")); EXPECT_EQ(0U, findCounter("test.http_local_rate_limit.enforced")); EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.rate_limited")); From 3e9678019e4d6244d9d066f1e6c8336360281f77 Mon Sep 17 00:00:00 2001 From: Yuchen Dai Date: Wed, 28 Apr 2021 17:55:04 -0700 Subject: [PATCH 105/209] Listener: respect the connection balancer of the redirected listener (#15842) If listener1 redirects the connection to listener2, the balancer field in listener2 decides whether to rebalance. Previously we rely on the rebalancing at listener1, however, the rebalance is weak because listener1 is likely to not own any connection and the rebalance is no-op. Risk Level: MID. Rebalance may introduce latency. User needs to clear rebalancer field of listener2 to recover the original behavior. Fix #15146 #16113 Signed-off-by: Yuchen Dai --- api/envoy/config/listener/v3/listener.proto | 6 + .../config/listener/v4alpha/listener.proto | 6 + docs/root/version_history/current.rst | 3 + .../envoy/config/listener/v3/listener.proto | 6 + .../config/listener/v4alpha/listener.proto | 6 + source/server/active_tcp_listener.cc | 15 +- source/server/active_tcp_listener.h | 8 +- test/common/quic/BUILD | 1 + test/integration/BUILD | 3 + test/integration/filters/BUILD | 19 ++ .../address_restore_listener_filter.cc | 55 ++++++ .../listener_lds_integration_test.cc | 101 ++++++++++ test/mocks/network/mocks.cc | 12 +- test/mocks/network/mocks.h | 4 +- test/server/BUILD | 19 ++ test/server/active_tcp_listener_test.cc | 181 ++++++++++++++++++ 16 files changed, 431 insertions(+), 14 deletions(-) create mode 100644 test/integration/filters/address_restore_listener_filter.cc create mode 100644 test/server/active_tcp_listener_test.cc diff --git a/api/envoy/config/listener/v3/listener.proto b/api/envoy/config/listener/v3/listener.proto index 4e0a857ce256a..5461318ada01c 100644 --- a/api/envoy/config/listener/v3/listener.proto +++ b/api/envoy/config/listener/v3/listener.proto @@ -247,6 +247,12 @@ message Listener { // The listener's connection balancer configuration, currently only applicable to TCP listeners. // If no configuration is specified, Envoy will not attempt to balance active connections between // worker threads. + // + // In the scenario that the listener X redirects all the connections to the listeners Y1 and Y2 + // by setting :ref:`use_original_dst ` in X + // and :ref:`bind_to_port ` to false in Y1 and Y2, + // it is recommended to disable the balance config in listener X to avoid the cost of balancing, and + // enable the balance config in Y1 and Y2 to balance the connections among the workers. ConnectionBalanceConfig connection_balance_config = 20; // When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and diff --git a/api/envoy/config/listener/v4alpha/listener.proto b/api/envoy/config/listener/v4alpha/listener.proto index e2eb3a1e60655..e40dbf9058af9 100644 --- a/api/envoy/config/listener/v4alpha/listener.proto +++ b/api/envoy/config/listener/v4alpha/listener.proto @@ -249,6 +249,12 @@ message Listener { // The listener's connection balancer configuration, currently only applicable to TCP listeners. // If no configuration is specified, Envoy will not attempt to balance active connections between // worker threads. + // + // In the scenario that the listener X redirects all the connections to the listeners Y1 and Y2 + // by setting :ref:`use_original_dst ` in X + // and :ref:`bind_to_port ` to false in Y1 and Y2, + // it is recommended to disable the balance config in listener X to avoid the cost of balancing, and + // enable the balance config in Y1 and Y2 to balance the connections among the workers. ConnectionBalanceConfig connection_balance_config = 20; // When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 2a465da670274..19db50652b02c 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -16,6 +16,9 @@ Minor Behavior Changes (require upstream 1xx or 204 responses to not have Transfer-Encoding or non-zero Content-Length headers) and ``envoy.reloadable_features.send_strict_1xx_and_204_response_headers`` (do not send 1xx or 204 responses with these headers). Both are true by default. +* listener: respect the :ref:`connection balance config ` + defined within the listener where the sockets are redirected to. Clear that field to restore the previous behavior. + Bug Fixes --------- diff --git a/generated_api_shadow/envoy/config/listener/v3/listener.proto b/generated_api_shadow/envoy/config/listener/v3/listener.proto index 4e0a857ce256a..5461318ada01c 100644 --- a/generated_api_shadow/envoy/config/listener/v3/listener.proto +++ b/generated_api_shadow/envoy/config/listener/v3/listener.proto @@ -247,6 +247,12 @@ message Listener { // The listener's connection balancer configuration, currently only applicable to TCP listeners. // If no configuration is specified, Envoy will not attempt to balance active connections between // worker threads. + // + // In the scenario that the listener X redirects all the connections to the listeners Y1 and Y2 + // by setting :ref:`use_original_dst ` in X + // and :ref:`bind_to_port ` to false in Y1 and Y2, + // it is recommended to disable the balance config in listener X to avoid the cost of balancing, and + // enable the balance config in Y1 and Y2 to balance the connections among the workers. ConnectionBalanceConfig connection_balance_config = 20; // When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and diff --git a/generated_api_shadow/envoy/config/listener/v4alpha/listener.proto b/generated_api_shadow/envoy/config/listener/v4alpha/listener.proto index cad42c77a1ff6..47611a615efbc 100644 --- a/generated_api_shadow/envoy/config/listener/v4alpha/listener.proto +++ b/generated_api_shadow/envoy/config/listener/v4alpha/listener.proto @@ -252,6 +252,12 @@ message Listener { // The listener's connection balancer configuration, currently only applicable to TCP listeners. // If no configuration is specified, Envoy will not attempt to balance active connections between // worker threads. + // + // In the scenario that the listener X redirects all the connections to the listeners Y1 and Y2 + // by setting :ref:`use_original_dst ` in X + // and :ref:`bind_to_port ` to false in Y1 and Y2, + // it is recommended to disable the balance config in listener X to avoid the cost of balancing, and + // enable the balance config in Y1 and Y2 to balance the connections among the workers. ConnectionBalanceConfig connection_balance_config = 20; // When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and diff --git a/source/server/active_tcp_listener.cc b/source/server/active_tcp_listener.cc index 7a5f0a64edccc..4146ed86b39ac 100644 --- a/source/server/active_tcp_listener.cc +++ b/source/server/active_tcp_listener.cc @@ -69,7 +69,8 @@ ActiveTcpListener::~ActiveTcpListener() { // for now. If it becomes a problem (developers hitting this assert when using debug builds) we // can revisit. This case, if it happens, should be benign on production builds. This case is // covered in ConnectionHandlerTest::RemoveListenerDuringRebalance. - ASSERT(num_listener_connections_ == 0); + ASSERT(num_listener_connections_ == 0, fmt::format("destroyed listener {} has {} connections", + config_->name(), numConnections())); } void ActiveTcpListener::removeConnection(ActiveTcpConnection& connection) { @@ -188,14 +189,12 @@ void ActiveTcpSocket::newConnection() { if (new_listener.has_value()) { // Hands off connections redirected by iptables to the listener associated with the // original destination address. Pass 'hand_off_restored_destination_connections' as false to - // prevent further redirection as well as 'rebalanced' as true since the connection has - // already been balanced if applicable inside onAcceptWorker() when the connection was - // initially accepted. Note also that we must account for the number of connections properly - // across both listeners. + // prevent further redirection. + // Leave the new listener to decide whether to execute re-balance. + // Note also that we must account for the number of connections properly across both listeners. // TODO(mattklein123): See note in ~ActiveTcpSocket() related to making this accounting better. listener_.decNumConnections(); - new_listener.value().get().incNumConnections(); - new_listener.value().get().onAcceptWorker(std::move(socket_), false, true); + new_listener.value().get().onAcceptWorker(std::move(socket_), false, false); } else { // Set default transport protocol if none of the listener filters did it. if (socket_->detectedTransportProtocol().empty()) { @@ -250,7 +249,7 @@ void ActiveTcpListener::onAcceptWorker(Network::ConnectionSocketPtr&& socket, auto active_socket = std::make_unique(*this, std::move(socket), hand_off_restored_destination_connections); - // Create and run the filters + // Create and run the filters. config_->filterChainFactory().createListenerFilterChain(*active_socket); active_socket->continueFilterChain(true); diff --git a/source/server/active_tcp_listener.h b/source/server/active_tcp_listener.h index 01cf11b72408a..c698faaa06058 100644 --- a/source/server/active_tcp_listener.h +++ b/source/server/active_tcp_listener.h @@ -30,10 +30,10 @@ using RebalancedSocketSharedPtr = std::shared_ptr; /** * Wrapper for an active tcp listener owned by this handler. */ -class ActiveTcpListener : public Network::TcpListenerCallbacks, - public ActiveListenerImplBase, - public Network::BalancedConnectionHandler, - Logger::Loggable { +class ActiveTcpListener final : public Network::TcpListenerCallbacks, + public ActiveListenerImplBase, + public Network::BalancedConnectionHandler, + Logger::Loggable { public: ActiveTcpListener(Network::TcpConnectionHandler& parent, Network::ListenerConfig& config); ActiveTcpListener(Network::TcpConnectionHandler& parent, Network::ListenerPtr&& listener, diff --git a/test/common/quic/BUILD b/test/common/quic/BUILD index 38d7fa11a2526..b5ed243f8bbba 100644 --- a/test/common/quic/BUILD +++ b/test/common/quic/BUILD @@ -79,6 +79,7 @@ envoy_cc_test( "//source/common/quic:envoy_quic_connection_helper_lib", "//source/common/quic:envoy_quic_server_connection_lib", "//source/common/quic:envoy_quic_server_session_lib", + "//source/server:active_listener_base", "//test/mocks/http:http_mocks", "//test/mocks/http:stream_decoder_mock", "//test/mocks/network:network_mocks", diff --git a/test/integration/BUILD b/test/integration/BUILD index 8ab9096c0c194..7d0ed082a0216 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1547,13 +1547,16 @@ envoy_cc_test( "//source/common/network:connection_lib", "//source/common/network:utility_lib", "//source/extensions/filters/http/health_check:config", + "//source/extensions/filters/network/tcp_proxy:config", "//test/common/grpc:grpc_client_integration_lib", + "//test/integration/filters:address_restore_listener_filter_lib", "//test/test_common:resources_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", ], ) diff --git a/test/integration/filters/BUILD b/test/integration/filters/BUILD index 5d8c073a5bb76..4722b15a3730b 100644 --- a/test/integration/filters/BUILD +++ b/test/integration/filters/BUILD @@ -467,6 +467,25 @@ envoy_cc_test_library( ], ) +envoy_cc_test_library( + name = "address_restore_listener_filter_lib", + srcs = [ + "address_restore_listener_filter.cc", + ], + deps = [ + ":common_lib", + "//include/envoy/network:filter_interface", + "//include/envoy/network:listen_socket_interface", + "//include/envoy/registry", + "//include/envoy/server:filter_config_interface", + "//source/common/common:assert_lib", + "//source/common/common:minimal_logger_lib", + "//source/common/network:address_lib", + "//source/common/network:upstream_socket_options_filter_state_lib", + "//source/common/network:utility_lib", + ], +) + envoy_cc_test_library( name = "set_route_filter_lib", srcs = [ diff --git a/test/integration/filters/address_restore_listener_filter.cc b/test/integration/filters/address_restore_listener_filter.cc new file mode 100644 index 0000000000000..c769994b9644d --- /dev/null +++ b/test/integration/filters/address_restore_listener_filter.cc @@ -0,0 +1,55 @@ + + +#include "envoy/network/filter.h" +#include "envoy/network/listen_socket.h" +#include "envoy/server/filter_config.h" + +#include "common/network/address_impl.h" +#include "common/network/utility.h" + +namespace Envoy { + +// The FakeOriginalDstListenerFilter restore desired local address without the dependency of OS. +class FakeOriginalDstListenerFilter : public Network::ListenerFilter { +public: + // Network::ListenerFilter + Network::FilterStatus onAccept(Network::ListenerFilterCallbacks& cb) override { + FANCY_LOG(debug, "in FakeOriginalDstListenerFilter::onAccept"); + Network::ConnectionSocket& socket = cb.socket(); + socket.addressProvider().restoreLocalAddress( + std::make_shared("127.0.0.2", 80)); + FANCY_LOG(debug, "current local socket address is {} restored = {}", + socket.addressProvider().localAddress()->asString(), + socket.addressProvider().localAddressRestored()); + return Network::FilterStatus::Continue; + } +}; + +class FakeOriginalDstListenerFilterConfigFactory + : public Server::Configuration::NamedListenerFilterConfigFactory { +public: + // NamedListenerFilterConfigFactory + Network::ListenerFilterFactoryCb createListenerFilterFactoryFromProto( + const Protobuf::Message&, + const Network::ListenerFilterMatcherSharedPtr& listener_filter_matcher, + Server::Configuration::ListenerFactoryContext&) override { + return [listener_filter_matcher](Network::ListenerFilterManager& filter_manager) -> void { + filter_manager.addAcceptFilter(listener_filter_matcher, + std::make_unique()); + }; + } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return ProtobufTypes::MessagePtr{new Envoy::ProtobufWkt::Struct()}; + } + + std::string name() const override { + // This fake original_dest should be used only in integration test! + return "envoy.filters.listener.original_dst"; + } +}; + +static Registry::RegisterFactory + register_; +} // namespace Envoy diff --git a/test/integration/listener_lds_integration_test.cc b/test/integration/listener_lds_integration_test.cc index e256e8a09275c..2179d9b3c3e5c 100644 --- a/test/integration/listener_lds_integration_test.cc +++ b/test/integration/listener_lds_integration_test.cc @@ -4,6 +4,8 @@ #include "envoy/config/route/v3/route.pb.h" #include "envoy/config/route/v3/scoped_route.pb.h" #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" +#include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" +#include "envoy/network/connection.h" #include "envoy/service/discovery/v3/discovery.pb.h" #include "common/config/api_version.h" @@ -409,5 +411,104 @@ TEST_P(ListenerIntegrationTest, MultipleLdsUpdatesSharingListenSocketFactory) { } } +class RebalancerTest : public testing::TestWithParam, + public BaseIntegrationTest { +public: + RebalancerTest() + : BaseIntegrationTest(GetParam(), ConfigHelper::baseConfig() + R"EOF( + filter_chains: + - filters: + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: tcp_stats + cluster: cluster_0 +)EOF") {} + + void initialize() override { + config_helper_.renameListener("tcp"); + config_helper_.addConfigModifier( + [&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto& src_listener_config = *bootstrap.mutable_static_resources()->mutable_listeners(0); + src_listener_config.mutable_use_original_dst()->set_value(true); + // Note that the below original_dst is replaced by FakeOriginalDstListenerFilter at the + // link time. + src_listener_config.add_listener_filters()->set_name( + "envoy.filters.listener.original_dst"); + auto& virtual_listener_config = *bootstrap.mutable_static_resources()->add_listeners(); + virtual_listener_config = src_listener_config; + virtual_listener_config.mutable_use_original_dst()->set_value(false); + virtual_listener_config.clear_listener_filters(); + virtual_listener_config.mutable_bind_to_port()->set_value(false); + virtual_listener_config.set_name("balanced_target_listener"); + virtual_listener_config.mutable_connection_balance_config()->mutable_exact_balance(); + + // 127.0.0.2 is defined in FakeOriginalDstListenerFilter. This virtual listener does not + // listen on a passive socket so it's safe to use any ip address. + *virtual_listener_config.mutable_address()->mutable_socket_address()->mutable_address() = + "127.0.0.2"; + virtual_listener_config.mutable_address()->mutable_socket_address()->set_port_value(80); + }); + BaseIntegrationTest::initialize(); + } + + std::unique_ptr createConnectionAndWrite(const std::string& request, + std::string& response) { + Buffer::OwnedImpl buffer(request); + return std::make_unique( + lookupPort("tcp"), buffer, + [&response](Network::ClientConnection&, const Buffer::Instance& data) -> void { + response.append(data.toString()); + }, + version_, *dispatcher_); + } +}; + +struct PerConnection { + std::string response_; + std::unique_ptr client_conn_; + FakeRawConnectionPtr upstream_conn_; +}; + +// Verify the connections are distributed evenly on the 2 worker threads of the redirected +// listener. +TEST_P(RebalancerTest, RedirectConnectionIsBalancedOnDestinationListener) { + concurrency_ = 2; + int repeats = 10; + initialize(); + + // The balancer is balanced as per active connection instead of total connection. + // The below vector maintains all the connections alive. + std::vector connections; + for (uint32_t i = 0; i < repeats * concurrency_; ++i) { + connections.emplace_back(); + connections.back().client_conn_ = + createConnectionAndWrite("dummy", connections.back().response_); + connections.back().client_conn_->waitForConnection(); + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(connections.back().upstream_conn_)); + } + for (auto& conn : connections) { + conn.client_conn_->close(); + while (!conn.client_conn_->closed()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + } + + ASSERT_EQ(TestUtility::findCounter(test_server_->statStore(), + "listener.127.0.0.2_80.worker_0.downstream_cx_total") + + ->value(), + repeats); + ASSERT_EQ(TestUtility::findCounter(test_server_->statStore(), + "listener.127.0.0.2_80.worker_1.downstream_cx_total") + + ->value(), + repeats); +} + +INSTANTIATE_TEST_SUITE_P(IpVersions, RebalancerTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + } // namespace } // namespace Envoy diff --git a/test/mocks/network/mocks.cc b/test/mocks/network/mocks.cc index 7a2ff7458a5cc..5575eeb722bb6 100644 --- a/test/mocks/network/mocks.cc +++ b/test/mocks/network/mocks.cc @@ -171,7 +171,17 @@ MockListener::MockListener() = default; MockListener::~MockListener() { onDestroy(); } -MockConnectionHandler::MockConnectionHandler() = default; +MockConnectionHandler::MockConnectionHandler() { + ON_CALL(*this, incNumConnections()).WillByDefault(Invoke([this]() { + ++num_handler_connections_; + })); + ON_CALL(*this, decNumConnections()).WillByDefault(Invoke([this]() { + --num_handler_connections_; + })); + ON_CALL(*this, numConnections()).WillByDefault(Invoke([this]() { + return num_handler_connections_; + })); +} MockConnectionHandler::~MockConnectionHandler() = default; MockIp::MockIp() = default; diff --git a/test/mocks/network/mocks.h b/test/mocks/network/mocks.h index 63c4c5e7c2ff8..c102194d40169 100644 --- a/test/mocks/network/mocks.h +++ b/test/mocks/network/mocks.h @@ -421,7 +421,7 @@ class MockListener : public Listener { MOCK_METHOD(void, setRejectFraction, (UnitFloat)); }; -class MockConnectionHandler : public ConnectionHandler { +class MockConnectionHandler : public virtual ConnectionHandler { public: MockConnectionHandler(); ~MockConnectionHandler() override; @@ -441,6 +441,8 @@ class MockConnectionHandler : public ConnectionHandler { MOCK_METHOD(void, enableListeners, ()); MOCK_METHOD(void, setListenerRejectFraction, (UnitFloat), (override)); MOCK_METHOD(const std::string&, statPrefix, (), (const)); + + uint64_t num_handler_connections_{}; }; class MockIp : public Address::Ip { diff --git a/test/server/BUILD b/test/server/BUILD index 7a591987be6b2..498bc7ecef0bb 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -92,6 +92,25 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "active_tcp_listener_test", + srcs = ["active_tcp_listener_test.cc"], + deps = [ + "//source/common/common:utility_lib", + "//source/common/config:utility_lib", + "//source/common/network:address_lib", + "//source/common/network:connection_balancer_lib", + "//source/common/stats:stats_lib", + "//source/server:active_raw_udp_listener_config", + "//source/server:connection_handler_lib", + "//test/mocks/access_log:access_log_mocks", + "//test/mocks/api:api_mocks", + "//test/mocks/network:network_mocks", + "//test/test_common:network_utility_lib", + "//test/test_common:threadsafe_singleton_injector_lib", + ], +) + envoy_cc_test( name = "drain_manager_impl_test", srcs = ["drain_manager_impl_test.cc"], diff --git a/test/server/active_tcp_listener_test.cc b/test/server/active_tcp_listener_test.cc new file mode 100644 index 0000000000000..cc1e2710f9f2d --- /dev/null +++ b/test/server/active_tcp_listener_test.cc @@ -0,0 +1,181 @@ +#include + +#include "envoy/network/filter.h" +#include "envoy/network/listener.h" +#include "envoy/stats/scope.h" + +#include "common/network/address_impl.h" +#include "common/network/connection_balancer_impl.h" +#include "common/network/raw_buffer_socket.h" +#include "common/network/utility.h" + +#include "server/active_tcp_listener.h" + +#include "test/mocks/api/mocks.h" +#include "test/mocks/common.h" +#include "test/mocks/network/mocks.h" +#include "test/test_common/network_utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Server { +namespace { + +class MockTcpConnectionHandler : public Network::TcpConnectionHandler, + public Network::MockConnectionHandler { +public: + MOCK_METHOD(Event::Dispatcher&, dispatcher, ()); + MOCK_METHOD(Network::BalancedConnectionHandlerOptRef, getBalancedHandlerByTag, + (uint64_t listener_tag)); + MOCK_METHOD(Network::BalancedConnectionHandlerOptRef, getBalancedHandlerByAddress, + (const Network::Address::Instance& address)); +}; +class ActiveTcpListenerTest : public testing::Test, protected Logger::Loggable { +public: + ActiveTcpListenerTest() { + EXPECT_CALL(conn_handler_, dispatcher()).WillRepeatedly(ReturnRef(dispatcher_)); + EXPECT_CALL(conn_handler_, numConnections()).Times(testing::AnyNumber()); + EXPECT_CALL(conn_handler_, statPrefix()).WillRepeatedly(ReturnRef(listener_stat_prefix_)); + listener_filter_matcher_ = std::make_shared>(); + } + + std::string listener_stat_prefix_{"listener_stat_prefix"}; + std::shared_ptr socket_factory_{ + std::make_shared()}; + NiceMock dispatcher_{"test"}; + BasicResourceLimitImpl resource_limit_; + NiceMock conn_handler_; + Network::MockListener* generic_listener_; + Network::MockListenerConfig listener_config_; + NiceMock manager_; + NiceMock filter_chain_factory_; + std::shared_ptr filter_chain_; + std::shared_ptr> listener_filter_matcher_; +}; + +// Verify that the server connection with recovered address is rebalanced at redirected listener. +TEST_F(ActiveTcpListenerTest, RedirectedRebalancer) { + NiceMock listener_config1; + NiceMock balancer1; + EXPECT_CALL(balancer1, registerHandler(_)); + EXPECT_CALL(balancer1, unregisterHandler(_)); + + Network::Address::InstanceConstSharedPtr normal_address( + new Network::Address::Ipv4Instance("127.0.0.1", 10001)); + EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(normal_address)); + EXPECT_CALL(listener_config1, connectionBalancer()).WillRepeatedly(ReturnRef(balancer1)); + EXPECT_CALL(listener_config1, listenerScope).Times(testing::AnyNumber()); + EXPECT_CALL(listener_config1, listenerFiltersTimeout()); + EXPECT_CALL(listener_config1, continueOnListenerFiltersTimeout()); + EXPECT_CALL(listener_config1, filterChainManager()).WillRepeatedly(ReturnRef(manager_)); + EXPECT_CALL(listener_config1, openConnections()).WillRepeatedly(ReturnRef(resource_limit_)); + EXPECT_CALL(listener_config1, handOffRestoredDestinationConnections()) + .WillRepeatedly(Return(true)); + + auto mock_listener_will_be_moved1 = std::make_unique(); + auto& listener1 = *mock_listener_will_be_moved1; + auto active_listener1 = std::make_unique( + conn_handler_, std::move(mock_listener_will_be_moved1), listener_config1); + + NiceMock listener_config2; + Network::MockConnectionBalancer balancer2; + EXPECT_CALL(balancer2, registerHandler(_)); + EXPECT_CALL(balancer2, unregisterHandler(_)); + + Network::Address::InstanceConstSharedPtr alt_address( + new Network::Address::Ipv4Instance("127.0.0.2", 20002)); + EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(alt_address)); + EXPECT_CALL(listener_config2, listenerFiltersTimeout()); + EXPECT_CALL(listener_config2, connectionBalancer()).WillRepeatedly(ReturnRef(balancer2)); + EXPECT_CALL(listener_config2, listenerScope).Times(testing::AnyNumber()); + EXPECT_CALL(listener_config2, handOffRestoredDestinationConnections()) + .WillRepeatedly(Return(false)); + EXPECT_CALL(listener_config2, continueOnListenerFiltersTimeout()); + EXPECT_CALL(listener_config2, filterChainManager()).WillRepeatedly(ReturnRef(manager_)); + EXPECT_CALL(listener_config2, openConnections()).WillRepeatedly(ReturnRef(resource_limit_)); + auto mock_listener_will_be_moved2 = std::make_unique(); + auto& listener2 = *mock_listener_will_be_moved2; + auto active_listener2 = std::make_shared( + conn_handler_, std::move(mock_listener_will_be_moved2), listener_config2); + + auto* test_filter = new NiceMock(); + EXPECT_CALL(*test_filter, destroy_()); + Network::MockConnectionSocket* accepted_socket = new NiceMock(); + bool redirected = false; + + // 1. Listener1 re-balance. Set the balance target to the the active listener itself. + EXPECT_CALL(balancer1, pickTargetHandler(_)) + .WillOnce(testing::DoAll( + testing::WithArg<0>(Invoke([](auto& target) { target.incNumConnections(); })), + ReturnRef(*active_listener1))); + + EXPECT_CALL(listener_config1, filterChainFactory()) + .WillRepeatedly(ReturnRef(filter_chain_factory_)); + + // Listener1 has a listener filter in the listener filter chain. + EXPECT_CALL(filter_chain_factory_, createListenerFilterChain(_)) + .WillRepeatedly(Invoke([&](Network::ListenerFilterManager& manager) -> bool { + // Insert the Mock filter. + if (!redirected) { + manager.addAcceptFilter(nullptr, Network::ListenerFilterPtr{test_filter}); + redirected = true; + } + return true; + })); + EXPECT_CALL(*test_filter, onAccept(_)) + .WillOnce(Invoke([&](Network::ListenerFilterCallbacks& cb) -> Network::FilterStatus { + cb.socket().addressProvider().restoreLocalAddress(alt_address); + return Network::FilterStatus::Continue; + })); + // Verify that listener1 hands off the connection by not creating network filter chain. + EXPECT_CALL(manager_, findFilterChain(_)).Times(0); + + // 2. Redirect to Listener2. + EXPECT_CALL(conn_handler_, getBalancedHandlerByAddress(_)) + .WillOnce(Return(Network::BalancedConnectionHandlerOptRef(*active_listener2))); + + // 3. Listener2 re-balance. Set the balance target to the the active listener itself. + EXPECT_CALL(balancer2, pickTargetHandler(_)) + .WillOnce(testing::DoAll( + testing::WithArg<0>(Invoke([](auto& target) { target.incNumConnections(); })), + ReturnRef(*active_listener2))); + + auto filter_factory_callback = std::make_shared>(); + auto transport_socket_factory = Network::Test::createRawBufferSocketFactory(); + filter_chain_ = std::make_shared>(); + + EXPECT_CALL(conn_handler_, incNumConnections()); + EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(filter_chain_.get())); + EXPECT_CALL(*filter_chain_, transportSocketFactory) + .WillOnce(testing::ReturnRef(*transport_socket_factory)); + EXPECT_CALL(*filter_chain_, networkFilterFactories).WillOnce(ReturnRef(*filter_factory_callback)); + EXPECT_CALL(listener_config2, filterChainFactory()) + .WillRepeatedly(ReturnRef(filter_chain_factory_)); + + auto* connection = new NiceMock(); + EXPECT_CALL(dispatcher_, createServerConnection_()).WillOnce(Return(connection)); + EXPECT_CALL(filter_chain_factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); + active_listener1->onAccept(Network::ConnectionSocketPtr{accepted_socket}); + + // Verify per-listener connection stats. + EXPECT_EQ(1UL, conn_handler_.numConnections()); + + EXPECT_CALL(conn_handler_, decNumConnections()); + connection->close(Network::ConnectionCloseType::NoFlush); + + EXPECT_CALL(listener1, onDestroy()); + active_listener1.reset(); + EXPECT_CALL(listener2, onDestroy()); + active_listener2.reset(); +} +} // namespace +} // namespace Server +} // namespace Envoy From 265275ebf486b81cc59fe6d90735478e3ddc5bfd Mon Sep 17 00:00:00 2001 From: Ravindra Akella Date: Thu, 29 Apr 2021 17:41:00 +0530 Subject: [PATCH 106/209] server: fix fips_mode stat (#16140) Commit Message: Fix fips_mode stat by using a static variable to check if the ssl version is fips compliant or not. Additional Description: Originally added as part of #14719 Risk Level: Low Testing: Updated unit tests Docs Changes: None. Already documented Release Notes: Platform Specific Features: Signed-off-by: Ravindra Akella --- source/common/version/BUILD | 5 ++++- source/common/version/version.cc | 8 ++++---- test/common/common/BUILD | 2 ++ test/common/common/version_test.cc | 11 +++++++++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/source/common/version/BUILD b/source/common/version/BUILD index 99300df7e6518..6626e2da9e229 100644 --- a/source/common/version/BUILD +++ b/source/common/version/BUILD @@ -59,7 +59,10 @@ envoy_cc_library( name = "version_lib", srcs = ["version.cc"], copts = envoy_select_boringssl( - ["-DENVOY_SSL_VERSION=\\\"BoringSSL-FIPS\\\""], + [ + "-DENVOY_SSL_VERSION=\\\"BoringSSL-FIPS\\\"", + "-DENVOY_SSL_FIPS", + ], ["-DENVOY_SSL_VERSION=\\\"BoringSSL\\\""], ), deps = [ diff --git a/source/common/version/version.cc b/source/common/version/version.cc index 8b013dde01cbe..f1d80c8e19e4b 100644 --- a/source/common/version/version.cc +++ b/source/common/version/version.cc @@ -37,11 +37,11 @@ const envoy::config::core::v3::BuildVersion& VersionInfo::buildVersion() { } bool VersionInfo::sslFipsCompliant() { - bool fipsCompliant = false; -#ifdef BORINGSSL_FIPS - fipsCompliant = true; +#ifdef ENVOY_SSL_FIPS + return true; +#else + return false; #endif - return fipsCompliant; } const std::string& VersionInfo::buildType() { diff --git a/test/common/common/BUILD b/test/common/common/BUILD index b210f21c49eca..da737aa053bd5 100644 --- a/test/common/common/BUILD +++ b/test/common/common/BUILD @@ -5,6 +5,7 @@ load( "envoy_cc_fuzz_test", "envoy_cc_test", "envoy_package", + "envoy_select_boringssl", ) licenses(["notice"]) # Apache 2 @@ -389,6 +390,7 @@ envoy_cc_test( envoy_cc_test( name = "version_test", srcs = ["version_test.cc"], + copts = envoy_select_boringssl(["-DENVOY_SSL_FIPS"]), external_deps = [ "abseil_strings", ], diff --git a/test/common/common/version_test.cc b/test/common/common/version_test.cc index 5177f5ac1661f..379d539b85ddb 100644 --- a/test/common/common/version_test.cc +++ b/test/common/common/version_test.cc @@ -11,6 +11,7 @@ class VersionInfoTestPeer { public: static const std::string& buildType() { return VersionInfo::buildType(); } static const std::string& sslVersion() { return VersionInfo::sslVersion(); } + static bool sslFipsCompliant() { return VersionInfo::sslFipsCompliant(); } static envoy::config::core::v3::BuildVersion makeBuildVersion(const char* version) { return VersionInfo::makeBuildVersion(version); } @@ -34,6 +35,11 @@ TEST(VersionTest, BuildVersion) { fields.at(BuildVersionMetadataKeys::get().RevisionStatus).string_value()); EXPECT_EQ(VersionInfoTestPeer::buildType(), fields.at(BuildVersionMetadataKeys::get().BuildType).string_value()); +#ifdef ENVOY_SSL_FIPS + EXPECT_TRUE(VersionInfoTestPeer::sslFipsCompliant()); +#else + EXPECT_FALSE(VersionInfoTestPeer::sslFipsCompliant()); +#endif EXPECT_EQ(VersionInfoTestPeer::sslVersion(), fields.at(BuildVersionMetadataKeys::get().SslVersion).string_value()); } @@ -45,6 +51,11 @@ TEST(VersionTest, MakeBuildVersionWithLabel) { EXPECT_EQ(3, build_version.version().patch()); const auto& fields = build_version.metadata().fields(); EXPECT_GE(fields.size(), 1); +#ifdef ENVOY_SSL_FIPS + EXPECT_TRUE(VersionInfoTestPeer::sslFipsCompliant()); +#else + EXPECT_FALSE(VersionInfoTestPeer::sslFipsCompliant()); +#endif EXPECT_EQ("foo-bar", fields.at(BuildVersionMetadataKeys::get().BuildLabel).string_value()); } From 8c714008ed9d98c15ee18d645fdd59e18db2d9c2 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 29 Apr 2021 08:24:31 -0400 Subject: [PATCH 107/209] http: port stripping for CONNECT (#15975) Port stripping the host for CONNECT requests for the duration of the filter chain (reinstating before sending upstream) Signed-off-by: Alyssa Wilk --- .../v3/http_connection_manager.proto | 9 +- .../v4alpha/http_connection_manager.proto | 9 +- docs/root/version_history/current.rst | 1 + .../v3/http_connection_manager.proto | 9 +- .../v4alpha/http_connection_manager.proto | 9 +- source/common/http/conn_manager_impl.cc | 11 +- source/common/http/conn_manager_utility.cc | 11 +- source/common/http/conn_manager_utility.h | 5 +- source/common/http/header_utility.cc | 54 +++++---- source/common/http/header_utility.h | 12 +- source/common/router/config_impl.cc | 14 +++ source/common/router/config_impl.h | 13 +++ source/common/runtime/runtime_features.cc | 1 + test/common/http/header_utility_test.cc | 35 +++++- test/common/router/config_impl_test.cc | 21 ++++ .../tcp_tunneling_integration_test.cc | 104 ++++++++++++++++++ .../integration/websocket_integration_test.cc | 33 +++--- 17 files changed, 290 insertions(+), 61 deletions(-) diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 9ede9a6454351..87d826262b941 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -586,7 +586,9 @@ message HttpConnectionManager { // Determines if the port part should be removed from host/authority header before any processing // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` - // local port and request method is not CONNECT. This affects the upstream host header as well. + // local port. This affects the upstream host header unless the method is + // CONNECT in which case if no filter adds a port the original port will be restored before headers are + // sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. @@ -596,8 +598,9 @@ message HttpConnectionManager { oneof strip_port_mode { // Determines if the port part should be removed from host/authority header before any processing - // of request by HTTP filters or routing. The port would be removed only if request method is not CONNECT. - // This affects the upstream host header as well. + // of request by HTTP filters or routing. + // This affects the upstream host header unless the method is CONNECT in + // which case if no filter adds a port the original port will be restored before headers are sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index 67cb8194415e0..ba9b3c461b58b 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -590,7 +590,9 @@ message HttpConnectionManager { oneof strip_port_mode { // Determines if the port part should be removed from host/authority header before any processing // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` - // local port and request method is not CONNECT. This affects the upstream host header as well. + // local port. This affects the upstream host header unless the method is + // CONNECT in which case if no filter adds a port the original port will be restored before headers are + // sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. @@ -598,8 +600,9 @@ message HttpConnectionManager { bool strip_matching_host_port = 39; // Determines if the port part should be removed from host/authority header before any processing - // of request by HTTP filters or routing. The port would be removed only if request method is not CONNECT. - // This affects the upstream host header as well. + // of request by HTTP filters or routing. + // This affects the upstream host header unless the method is CONNECT in + // which case if no filter adds a port the original port will be restored before headers are sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 19db50652b02c..39ffddd2085f4 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -24,6 +24,7 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* +* http: port stripping now works for CONNECT requests, though the port will be restored if the CONNECT request is sent upstream. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.strip_port_from_connect`` to false. * http: raise max configurable max_request_headers_kb limit to 8192 KiB (8MiB) from 96 KiB in http connection manager. * validation: fix an issue that causes TAP sockets to panic during config validation mode. * xray: fix the default sampling 'rate' for AWS X-Ray tracer extension to be 5% as opposed to 50%. diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index d9343fcb0fbb7..da461ab777d48 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -592,7 +592,9 @@ message HttpConnectionManager { // Determines if the port part should be removed from host/authority header before any processing // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` - // local port and request method is not CONNECT. This affects the upstream host header as well. + // local port. This affects the upstream host header unless the method is + // CONNECT in which case if no filter adds a port the original port will be restored before headers are + // sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. @@ -602,8 +604,9 @@ message HttpConnectionManager { oneof strip_port_mode { // Determines if the port part should be removed from host/authority header before any processing - // of request by HTTP filters or routing. The port would be removed only if request method is not CONNECT. - // This affects the upstream host header as well. + // of request by HTTP filters or routing. + // This affects the upstream host header unless the method is CONNECT in + // which case if no filter adds a port the original port will be restored before headers are sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index 67cb8194415e0..ba9b3c461b58b 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -590,7 +590,9 @@ message HttpConnectionManager { oneof strip_port_mode { // Determines if the port part should be removed from host/authority header before any processing // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` - // local port and request method is not CONNECT. This affects the upstream host header as well. + // local port. This affects the upstream host header unless the method is + // CONNECT in which case if no filter adds a port the original port will be restored before headers are + // sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. @@ -598,8 +600,9 @@ message HttpConnectionManager { bool strip_matching_host_port = 39; // Determines if the port part should be removed from host/authority header before any processing - // of request by HTTP filters or routing. The port would be removed only if request method is not CONNECT. - // This affects the upstream host header as well. + // of request by HTTP filters or routing. + // This affects the upstream host header unless the method is CONNECT in + // which case if no filter adds a port the original port will be restored before headers are sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index e85b4b07b3c4e..44b9016c2ee8e 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -973,8 +973,15 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapPtr&& he return; } - ConnectionManagerUtility::maybeNormalizeHost(*request_headers_, connection_manager_.config_, - localPort()); + auto optional_port = ConnectionManagerUtility::maybeNormalizeHost( + *request_headers_, connection_manager_.config_, localPort()); + if (optional_port.has_value() && + requestWasConnect(request_headers_, connection_manager_.codec_->protocol())) { + filter_manager_.streamInfo().filterState()->setData( + Router::OriginalConnectPort::key(), + std::make_unique(optional_port.value()), + StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Request); + } if (!state_.is_internally_created_) { // Only sanitize headers on first pass. // Modify the downstream remote address depending on configuration and headers. diff --git a/source/common/http/conn_manager_utility.cc b/source/common/http/conn_manager_utility.cc index 1d9d008a3bccc..edfc6dc2a9b47 100644 --- a/source/common/http/conn_manager_utility.cc +++ b/source/common/http/conn_manager_utility.cc @@ -449,14 +449,15 @@ bool ConnectionManagerUtility::maybeNormalizePath(RequestHeaderMap& request_head return is_valid_path; } -void ConnectionManagerUtility::maybeNormalizeHost(RequestHeaderMap& request_headers, - const ConnectionManagerConfig& config, - uint32_t port) { +absl::optional +ConnectionManagerUtility::maybeNormalizeHost(RequestHeaderMap& request_headers, + const ConnectionManagerConfig& config, uint32_t port) { if (config.stripPortType() == Http::StripPortType::Any) { - HeaderUtility::stripPortFromHost(request_headers, absl::nullopt); + return HeaderUtility::stripPortFromHost(request_headers, absl::nullopt); } else if (config.stripPortType() == Http::StripPortType::MatchingHost) { - HeaderUtility::stripPortFromHost(request_headers, port); + return HeaderUtility::stripPortFromHost(request_headers, port); } + return absl::nullopt; } } // namespace Http diff --git a/source/common/http/conn_manager_utility.h b/source/common/http/conn_manager_utility.h index 1234e171cd274..c952b99c2fbb8 100644 --- a/source/common/http/conn_manager_utility.h +++ b/source/common/http/conn_manager_utility.h @@ -74,8 +74,9 @@ class ConnectionManagerUtility { static bool maybeNormalizePath(RequestHeaderMap& request_headers, const ConnectionManagerConfig& config); - static void maybeNormalizeHost(RequestHeaderMap& request_headers, - const ConnectionManagerConfig& config, uint32_t port); + static absl::optional maybeNormalizeHost(RequestHeaderMap& request_headers, + const ConnectionManagerConfig& config, + uint32_t port); /** * Mutate request headers if request needs to be traced. diff --git a/source/common/http/header_utility.cc b/source/common/http/header_utility.cc index a0331c484c288..527bd05f52af1 100644 --- a/source/common/http/header_utility.cc +++ b/source/common/http/header_utility.cc @@ -227,38 +227,48 @@ bool HeaderUtility::isEnvoyInternalRequest(const RequestHeaderMap& headers) { internal_request_header->value() == Headers::get().EnvoyInternalRequestValues.True; } -void HeaderUtility::stripPortFromHost(RequestHeaderMap& headers, - absl::optional listener_port) { - - if (headers.getMethodValue() == Http::Headers::get().MethodValues.Connect) { +absl::optional HeaderUtility::stripPortFromHost(RequestHeaderMap& headers, + absl::optional listener_port) { + if (headers.getMethodValue() == Http::Headers::get().MethodValues.Connect && + !Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strip_port_from_connect")) { // According to RFC 2817 Connect method should have port part in host header. // In this case we won't strip it even if configured to do so. - return; + return absl::nullopt; } const absl::string_view original_host = headers.getHostValue(); - const absl::string_view::size_type port_start = original_host.rfind(':'); + const absl::string_view::size_type port_start = getPortStart(original_host); if (port_start == absl::string_view::npos) { - return; + return absl::nullopt; + } + const absl::string_view port_str = original_host.substr(port_start + 1); + uint32_t port = 0; + if (!absl::SimpleAtoi(port_str, &port)) { + return absl::nullopt; + } + if (listener_port.has_value() && port != listener_port) { + // We would strip ports only if it is specified and they are the same, as local port of the + // listener. + return absl::nullopt; + } + const absl::string_view host = original_host.substr(0, port_start); + headers.setHost(host); + return port; +} + +absl::string_view::size_type HeaderUtility::getPortStart(absl::string_view host) { + const absl::string_view::size_type port_start = host.rfind(':'); + if (port_start == absl::string_view::npos) { + return absl::string_view::npos; } // According to RFC3986 v6 address is always enclosed in "[]". section 3.2.2. - const auto v6_end_index = original_host.rfind("]"); + const auto v6_end_index = host.rfind(']'); if (v6_end_index == absl::string_view::npos || v6_end_index < port_start) { - if ((port_start + 1) > original_host.size()) { - return; - } - const absl::string_view port_str = original_host.substr(port_start + 1); - uint32_t port = 0; - if (!absl::SimpleAtoi(port_str, &port)) { - return; - } - if (listener_port.has_value() && port != listener_port) { - // We would strip ports only if it is specified and they are the same, as local port of the - // listener. - return; + if ((port_start + 1) > host.size()) { + return absl::string_view::npos; } - const absl::string_view host = original_host.substr(0, port_start); - headers.setHost(host); + return port_start; } + return absl::string_view::npos; } absl::optional> diff --git a/source/common/http/header_utility.h b/source/common/http/header_utility.h index e5d58f39ca8e4..e84f38ed5347b 100644 --- a/source/common/http/header_utility.h +++ b/source/common/http/header_utility.h @@ -185,9 +185,19 @@ class HeaderUtility { /** * @brief Remove the port part from host/authority header if it is equal to provided port. + * @return absl::optional containing the port, if removed, else absl::nullopt. * If port is not passed, port part from host/authority header is removed. */ - static void stripPortFromHost(RequestHeaderMap& headers, absl::optional listener_port); + static absl::optional stripPortFromHost(RequestHeaderMap& headers, + absl::optional listener_port); + + /** + * @brief Return the index of the port, or npos if the host has no port + * + * Note this does not do validity checks on the port, it just finds the + * trailing : which is not a part of an IP address. + */ + static absl::string_view::size_type getPortStart(absl::string_view host); /* Does a common header check ensuring required headers are present. * Required request headers include :method header, :path for non-CONNECT requests, and diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 33692173e474a..4994bc5d070e7 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -63,6 +63,10 @@ void mergeTransforms(Http::HeaderTransforms& dest, const Http::HeaderTransforms& } // namespace +const std::string& OriginalConnectPort::key() { + CONSTRUCT_ON_FIRST_USE(std::string, "envoy.router.original_connect_port"); +} + std::string SslRedirector::newPath(const Http::RequestHeaderMap& headers) const { return Http::Utility::createSslRedirectPath(headers); } @@ -567,6 +571,16 @@ void RouteEntryImplBase::finalizeRequestHeaders(Http::RequestHeaderMap& headers, request_headers_parser_->evaluateHeaders(headers, stream_info); } + // Restore the port if this was a CONNECT request. + // Note this will restore the port for HTTP/2 CONNECT-upgrades as well as as HTTP/1.1 style + // CONNECT requests. + if (stream_info.filterState().hasData(OriginalConnectPort::key()) && + Http::HeaderUtility::getPortStart(headers.getHostValue()) == absl::string_view::npos) { + const OriginalConnectPort& original_port = + stream_info.filterState().getDataReadOnly(OriginalConnectPort::key()); + headers.setHost(absl::StrCat(headers.getHostValue(), ":", original_port.value())); + } + if (!host_rewrite_.empty()) { headers.setHost(host_rewrite_); } else if (auto_host_rewrite_header_) { diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index 9472dc2e84f4d..b88f1932666d5 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -37,6 +37,19 @@ namespace Envoy { namespace Router { +/** + * Original port from the authority header. + */ +class OriginalConnectPort : public StreamInfo::FilterState::Object { +public: + explicit OriginalConnectPort(uint32_t port) : port_(port) {} + const uint32_t& value() const { return port_; } + static const std::string& key(); + +private: + const uint32_t port_; +}; + /** * Base interface for something that matches a header. */ diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 139913ab1fa6a..cbabc221f4b39 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -82,6 +82,7 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.require_strict_1xx_and_204_response_headers", "envoy.reloadable_features.return_502_for_upstream_protocol_errors", "envoy.reloadable_features.send_strict_1xx_and_204_response_headers", + "envoy.reloadable_features.strip_port_from_connect", "envoy.reloadable_features.treat_host_like_authority", "envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure", "envoy.reloadable_features.upstream_host_weight_change_causes_rebuild", diff --git a/test/common/http/header_utility_test.cc b/test/common/http/header_utility_test.cc index 37b3931295f7f..7664aaee4c80a 100644 --- a/test/common/http/header_utility_test.cc +++ b/test/common/http/header_utility_test.cc @@ -34,6 +34,25 @@ class HeaderUtilityTest : public testing::Test { TestRequestHeaderMapImpl headers_; }; +TEST_F(HeaderUtilityTest, HasHost) { + const std::vector> host_headers{ + {"localhost", false}, // w/o port part + {"localhost:443", true}, // name w/ port + {"", false}, // empty + {":443", true}, // just port + {"192.168.1.1", false}, // ipv4 + {"192.168.1.1:443", true}, // ipv4 w/ port + {"[fc00::1]:443", true}, // ipv6 w/ port + {"[fc00::1]", false}, // ipv6 + }; + + for (const auto& host_pair : host_headers) { + EXPECT_EQ(HeaderUtility::getPortStart(host_pair.first) != absl::string_view::npos, + host_pair.second) + << host_pair.first; + } +} + // Port's part from host header get removed TEST_F(HeaderUtilityTest, RemovePortsFromHost) { const std::vector> host_headers{ @@ -69,8 +88,22 @@ TEST_F(HeaderUtilityTest, RemovePortsFromHost) { } } -// Port's part from host header won't be removed if method is "connect" TEST_F(HeaderUtilityTest, RemovePortsFromHostConnect) { + const std::vector> host_headers{ + {"localhost:443", "localhost"}, + }; + for (const auto& host_pair : host_headers) { + auto& host_header = hostHeaderEntry(host_pair.first, true); + HeaderUtility::stripPortFromHost(headers_, 443); + EXPECT_EQ(host_header.value().getStringView(), host_pair.second); + } +} + +// Port's part from host header won't be removed if method is "connect" +TEST_F(HeaderUtilityTest, RemovePortsFromHostConnectLegacy) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.strip_port_from_connect", "false"}}); const std::vector> host_headers{ {"localhost:443", "localhost:443"}, }; diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 75ec953c963bc..c465afe150e6a 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -583,6 +583,7 @@ TEST_F(RouteMatcherTest, TestConnectRoutes) { EXPECT_EQ("/rewrote?works=true", route->currentUrlPathAfterRewrite(headers)); route->finalizeRequestHeaders(headers, stream_info, true); EXPECT_EQ("/rewrote?works=true", headers.get_(Http::Headers::get().Path)); + EXPECT_EQ("bat3.com", headers.get_(Http::Headers::get().Host)); } // Prefix rewrite for CONNECT without path (for non-crashing) { @@ -593,6 +594,26 @@ TEST_F(RouteMatcherTest, TestConnectRoutes) { EXPECT_EQ("http://bat4.com/new_path", redirect->newPath(headers)); } + stream_info.filterState()->setData( + Router::OriginalConnectPort::key(), std::make_unique(10), + StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Request); + // Port addition for CONNECT without port + { + Http::TestRequestHeaderMapImpl headers = + genHeaders("bat3.com", "/api/locations?works=true", "CONNECT"); + const RouteEntry* route = config.route(headers, 0)->routeEntry(); + route->finalizeRequestHeaders(headers, stream_info, true); + EXPECT_EQ("bat3.com:10", headers.get_(Http::Headers::get().Host)); + } + // No port addition for CONNECT with port + { + Http::TestRequestHeaderMapImpl headers = + genHeaders("bat3.com:20", "/api/locations?works=true", "CONNECT"); + const RouteEntry* route = config.route(headers, 0)->routeEntry(); + route->finalizeRequestHeaders(headers, stream_info, true); + EXPECT_EQ("bat3.com:20", headers.get_(Http::Headers::get().Host)); + } + // Header matching (for HTTP/1.1) EXPECT_EQ( "connect_header_match", diff --git a/test/integration/tcp_tunneling_integration_test.cc b/test/integration/tcp_tunneling_integration_test.cc index 93ace7f7f52f1..42d70385524ef 100644 --- a/test/integration/tcp_tunneling_integration_test.cc +++ b/test/integration/tcp_tunneling_integration_test.cc @@ -299,6 +299,110 @@ TEST_P(ProxyingConnectIntegrationTest, ProxyConnect) { cleanupUpstreamAndDownstream(); } +TEST_P(ProxyingConnectIntegrationTest, ProxyConnectWithPortStrippingLegacy) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.strip_port_from_connect", "false"); + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { hcm.set_strip_any_host_port(true); }); + + initialize(); + + // Send request headers. + codec_client_ = makeHttpConnection(lookupPort("http")); + auto encoder_decoder = codec_client_->startRequest(connect_headers_); + request_encoder_ = &encoder_decoder.first; + response_ = std::move(encoder_decoder.second); + + // Wait for them to arrive upstream. + AssertionResult result = + fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_); + RELEASE_ASSERT(result, result.message()); + result = fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_); + RELEASE_ASSERT(result, result.message()); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + EXPECT_EQ(upstream_request_->headers().getMethodValue(), "CONNECT"); + EXPECT_EQ(upstream_request_->headers().getHostValue(), "host:80"); + if (upstreamProtocol() == FakeHttpConnection::Type::HTTP1) { + EXPECT_TRUE(upstream_request_->headers().get(Http::Headers::get().Protocol).empty()); + } else { + EXPECT_EQ(upstream_request_->headers().get(Http::Headers::get().Protocol)[0]->value(), + "bytestream"); + } + + // Send response headers + upstream_request_->encodeHeaders(default_response_headers_, false); + + // Wait for them to arrive downstream. + response_->waitForHeaders(); + EXPECT_EQ("200", response_->headers().getStatusValue()); + + // Make sure that even once the response has started, that data can continue to go upstream. + codec_client_->sendData(*request_encoder_, "hello", false); + ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, 5)); + + // Also test upstream to downstream data. + upstream_request_->encodeData(12, false); + response_->waitForBodyData(12); + + cleanupUpstreamAndDownstream(); +} + +TEST_P(ProxyingConnectIntegrationTest, ProxyConnectWithPortStripping) { + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + hcm.set_strip_any_host_port(true); + auto* route_config = hcm.mutable_route_config(); + auto* header_value_option = route_config->mutable_request_headers_to_add()->Add(); + auto* mutable_header = header_value_option->mutable_header(); + mutable_header->set_key("Host-In-Envoy"); + mutable_header->set_value("%REQ(:AUTHORITY)%"); + }); + + initialize(); + + // Send request headers. + codec_client_ = makeHttpConnection(lookupPort("http")); + auto encoder_decoder = codec_client_->startRequest(connect_headers_); + request_encoder_ = &encoder_decoder.first; + response_ = std::move(encoder_decoder.second); + + // Wait for them to arrive upstream. + AssertionResult result = + fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_); + RELEASE_ASSERT(result, result.message()); + result = fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_); + RELEASE_ASSERT(result, result.message()); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + EXPECT_EQ(upstream_request_->headers().getMethodValue(), "CONNECT"); + EXPECT_EQ(upstream_request_->headers().getHostValue(), "host:80"); + if (upstreamProtocol() == FakeHttpConnection::Type::HTTP1) { + EXPECT_TRUE(upstream_request_->headers().getProtocolValue().empty()); + } else { + EXPECT_EQ(upstream_request_->headers().getProtocolValue(), "bytestream"); + } + auto stripped_host = upstream_request_->headers().get(Http::LowerCaseString("host-in-envoy")); + ASSERT_EQ(stripped_host.size(), 1); + EXPECT_EQ(stripped_host[0]->value(), "host"); + + // Send response headers + upstream_request_->encodeHeaders(default_response_headers_, false); + + // Wait for them to arrive downstream. + response_->waitForHeaders(); + EXPECT_EQ("200", response_->headers().getStatusValue()); + + // Make sure that even once the response has started, that data can continue to go upstream. + codec_client_->sendData(*request_encoder_, "hello", false); + ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, 5)); + + // Also test upstream to downstream data. + upstream_request_->encodeData(12, false); + response_->waitForBodyData(12); + + cleanupUpstreamAndDownstream(); +} + TEST_P(ProxyingConnectIntegrationTest, ProxyConnectWithIP) { initialize(); diff --git a/test/integration/websocket_integration_test.cc b/test/integration/websocket_integration_test.cc index 5e67c7a5c772b..ef6cab3ed57ce 100644 --- a/test/integration/websocket_integration_test.cc +++ b/test/integration/websocket_integration_test.cc @@ -21,7 +21,7 @@ namespace { Http::TestRequestHeaderMapImpl upgradeRequestHeaders(const char* upgrade_type = "websocket", uint32_t content_length = 0) { - return Http::TestRequestHeaderMapImpl{{":authority", "host"}, + return Http::TestRequestHeaderMapImpl{{":authority", "host:80"}, {"content-length", fmt::format("{}", content_length)}, {":path", "/websocket/test"}, {":method", "GET"}, @@ -194,25 +194,26 @@ TEST_P(WebsocketIntegrationTest, WebSocketConnectionDownstreamDisconnect) { test_server_->waitForGaugeEq("http.config_test.downstream_cx_upgrades_active", 0); } -TEST_P(WebsocketIntegrationTest, WebSocketConnectionUpstreamDisconnect) { +TEST_P(WebsocketIntegrationTest, PortStrippingForHttp2) { + if (downstreamProtocol() != Http::CodecClient::Type::HTTP2) { + return; + } + + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { hcm.set_strip_any_host_port(true); }); + config_helper_.addConfigModifier(setRouteUsingWebsocket()); initialize(); performUpgrade(upgradeRequestHeaders(), upgradeResponseHeaders()); + ASSERT_EQ(upstream_request_->headers().getHostValue(), "host:80"); - // Standard TCP proxy semantics post upgrade - codec_client_->sendData(*request_encoder_, "hello", false); - ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, "hello")); - - // Send data downstream and disconnect immediately. - upstream_request_->encodeData("world", false); - ASSERT_TRUE(fake_upstream_connection_->close()); - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - - // Verify both the data and the disconnect went through. - response_->waitForBodyData(5); - EXPECT_EQ("world", response_->body()); - waitForClientDisconnectOrReset(Http::StreamResetReason::ConnectError); + codec_client_->sendData(*request_encoder_, "bye!", false); + codec_client_->close(); + // Verify the final data was received and that the connection is torn down. + ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, "bye!")); + ASSERT_TRUE(waitForUpstreamDisconnectOrReset()); } TEST_P(WebsocketIntegrationTest, EarlyData) { @@ -322,7 +323,7 @@ TEST_P(WebsocketIntegrationTest, RouteSpecificUpgrade) { foo_upgrade->set_upgrade_type("foo"); foo_upgrade->mutable_enabled()->set_value(false); }); - auto host = config_helper_.createVirtualHost("host", "/websocket/test"); + auto host = config_helper_.createVirtualHost("host:80", "/websocket/test"); host.mutable_routes(0)->mutable_route()->add_upgrade_configs()->set_upgrade_type("foo"); config_helper_.addVirtualHost(host); initialize(); From 26a60c2c71f8af4cbaa95f59706817174c24e735 Mon Sep 17 00:00:00 2001 From: danzh Date: Thu, 29 Apr 2021 09:21:05 -0400 Subject: [PATCH 108/209] quiche: use max header size configured in HCM (#15912) Commit Message: use max header size in HCM config to initialize quic session. Additional Description: change QuicSession::Initialize() and filter chain creation order to set max header list size for each session before Initialize(). Move Initialize() of Http3 connection from CodecClient to CodecClientProd. Added CodecClient::connect() interface. Change EnvoyQuicConnection not to directly inherit from QuicConnection. And renamed it. Fix a use-after-free bug in FakeUpstream which causes ASAN failure. Risk Level: low Testing: more protocol H3 tests Part of #2557 #12930 #14829 Signed-off-by: Dan Zhang --- source/common/http/codec_client.cc | 37 +++-- source/common/http/codec_client.h | 9 +- source/common/quic/BUILD | 25 ++-- source/common/quic/active_quic_listener.cc | 3 + .../quic/client_connection_factory_impl.cc | 5 +- source/common/quic/codec_impl.cc | 6 +- .../quic/envoy_quic_client_connection.cc | 12 +- .../quic/envoy_quic_client_connection.h | 19 ++- .../common/quic/envoy_quic_client_session.cc | 23 ++-- .../common/quic/envoy_quic_client_session.h | 3 + source/common/quic/envoy_quic_connection.cc | 27 ---- source/common/quic/envoy_quic_dispatcher.cc | 22 ++- source/common/quic/envoy_quic_proof_source.cc | 14 +- .../quic/envoy_quic_server_connection.cc | 25 ++-- .../quic/envoy_quic_server_connection.h | 21 ++- .../common/quic/envoy_quic_server_session.cc | 37 +++-- .../common/quic/envoy_quic_server_session.h | 13 +- source/common/quic/envoy_quic_utils.cc | 20 +++ source/common/quic/envoy_quic_utils.h | 9 ++ .../common/quic/platform/quiche_flags_impl.cc | 5 +- .../quic_filter_manager_connection_impl.cc | 34 +++-- .../quic_filter_manager_connection_impl.h | 41 ++++-- source/common/quic/quic_network_connection.cc | 14 ++ ...connection.h => quic_network_connection.h} | 33 +---- test/common/http/common.h | 1 + test/common/quic/BUILD | 2 + test/common/quic/active_quic_listener_test.cc | 15 ++- .../common/quic/envoy_quic_dispatcher_test.cc | 126 +++++++++++++----- .../quic/envoy_quic_proof_source_test.cc | 4 +- .../quic/envoy_quic_server_session_test.cc | 56 +------- .../quic/envoy_quic_server_stream_test.cc | 15 ++- test/common/quic/test_utils.h | 65 ++++++++- test/integration/fake_upstream.cc | 1 + test/integration/fake_upstream.h | 2 +- test/integration/protocol_integration_test.cc | 53 +++----- .../integration/quic_http_integration_test.cc | 1 - .../quic_protocol_integration_test.cc | 8 +- tools/spelling/spelling_dictionary.txt | 1 + 38 files changed, 484 insertions(+), 323 deletions(-) delete mode 100644 source/common/quic/envoy_quic_connection.cc create mode 100644 source/common/quic/quic_network_connection.cc rename source/common/quic/{envoy_quic_connection.h => quic_network_connection.h} (54%) diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index 9110962936cd8..7ea1916839b6d 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -34,16 +34,6 @@ CodecClient::CodecClient(Type type, Network::ClientConnectionPtr&& connection, connection_->addConnectionCallbacks(*this); connection_->addReadFilter(Network::ReadFilterSharedPtr{new CodecReadFilter(*this)}); - // In general, codecs are handed new not-yet-connected connections, but in the - // case of ALPN, the codec may be handed an already connected connection. - if (!connection_->connecting()) { - ASSERT(connection_->state() == Network::Connection::State::Open); - connected_ = true; - } else { - ENVOY_CONN_LOG(debug, "connecting", *connection_); - connection_->connect(); - } - if (idle_timeout_) { idle_timer_ = dispatcher.createTimer([this]() -> void { onIdleTimeout(); }); enableIdleTimer(); @@ -54,7 +44,23 @@ CodecClient::CodecClient(Type type, Network::ClientConnectionPtr&& connection, connection_->noDelay(true); } -CodecClient::~CodecClient() = default; +CodecClient::~CodecClient() { + ASSERT(connect_called_, "CodecClient::connect() is not called through out the life time."); +} + +void CodecClient::connect() { + connect_called_ = true; + ASSERT(codec_ != nullptr); + // In general, codecs are handed new not-yet-connected connections, but in the + // case of ALPN, the codec may be handed an already connected connection. + if (!connection_->connecting()) { + ASSERT(connection_->state() == Network::Connection::State::Open); + connected_ = true; + } else { + ENVOY_CONN_LOG(debug, "connecting", *connection_); + connection_->connect(); + } +} void CodecClient::close() { connection_->close(Network::ConnectionCloseType::NoFlush); } @@ -168,7 +174,6 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator) : CodecClient(type, std::move(connection), host, dispatcher) { - switch (type) { case Type::HTTP1: { codec_ = std::make_unique( @@ -185,10 +190,13 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne } case Type::HTTP3: { #ifdef ENVOY_ENABLE_QUIC + auto& quic_session = dynamic_cast(*connection_); codec_ = std::make_unique( - dynamic_cast(*connection_), *this, - host->cluster().http3CodecStats(), host->cluster().http3Options(), + quic_session, *this, host->cluster().http3CodecStats(), host->cluster().http3Options(), Http::DEFAULT_MAX_REQUEST_HEADERS_KB); + // Initialize the session after max request header size is changed in above http client + // connection creation. + quic_session.Initialize(); break; #else // Should be blocked by configuration checking at an earlier point. @@ -196,6 +204,7 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne #endif } } + connect(); } } // namespace Http diff --git a/source/common/http/codec_client.h b/source/common/http/codec_client.h index d0c5e486f0765..07eb9724d607a 100644 --- a/source/common/http/codec_client.h +++ b/source/common/http/codec_client.h @@ -47,7 +47,7 @@ class CodecClientCallbacks { * This is an HTTP client that multiple stream management and underlying connection management * across multiple HTTP codec types. */ -class CodecClient : Logger::Loggable, +class CodecClient : protected Logger::Loggable, public Http::ConnectionCallbacks, public Network::ConnectionCallbacks, public Event::DeferredDeletable { @@ -140,6 +140,12 @@ class CodecClient : Logger::Loggable, CodecClient(Type type, Network::ClientConnectionPtr&& connection, Upstream::HostDescriptionConstSharedPtr host, Event::Dispatcher& dispatcher); + /** + * Connect to the host. + * Needs to be called after codec_ is instantiated. + */ + void connect(); + // Http::ConnectionCallbacks void onGoAway(GoAwayErrorCode error_code) override { if (codec_callbacks_) { @@ -257,6 +263,7 @@ class CodecClient : Logger::Loggable, bool connected_{}; bool remote_closed_{}; bool protocol_error_{false}; + bool connect_called_{false}; }; using CodecClientPtr = std::unique_ptr; diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index d46b4b3bc28c8..9e4dae98192f9 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -180,8 +180,8 @@ envoy_cc_library( hdrs = ["quic_filter_manager_connection_impl.h"], tags = ["nofips"], deps = [ - ":envoy_quic_connection_lib", ":envoy_quic_simulated_watermark_buffer_lib", + ":quic_network_connection_lib", ":send_buffer_monitor_lib", "//include/envoy/event:dispatcher_interface", "//include/envoy/network:connection_interface", @@ -192,6 +192,7 @@ envoy_cc_library( "//source/common/http/http3:codec_stats_lib", "//source/common/network:connection_base_lib", "//source/common/stream_info:stream_info_lib", + "@com_googlesource_quiche//:quic_core_connection_lib", ], ) @@ -208,6 +209,7 @@ envoy_cc_library( tags = ["nofips"], deps = [ ":envoy_quic_proof_source_lib", + ":envoy_quic_server_connection_lib", ":envoy_quic_stream_lib", ":envoy_quic_utils_lib", ":quic_filter_manager_connection_lib", @@ -255,17 +257,13 @@ envoy_cc_library( ) envoy_cc_library( - name = "envoy_quic_connection_lib", - srcs = ["envoy_quic_connection.cc"], - hdrs = ["envoy_quic_connection.h"], + name = "quic_network_connection_lib", + srcs = ["quic_network_connection.cc"], + hdrs = ["quic_network_connection.h"], tags = ["nofips"], deps = [ - ":quic_io_handle_wrapper_lib", "//include/envoy/network:connection_interface", "//source/common/network:listen_socket_lib", - "//source/common/quic:envoy_quic_utils_lib", - "//source/extensions/transport_sockets:well_known_names", - "@com_googlesource_quiche//:quic_core_connection_lib", ], ) @@ -275,8 +273,12 @@ envoy_cc_library( hdrs = ["envoy_quic_server_connection.h"], tags = ["nofips"], deps = [ - ":envoy_quic_connection_lib", + ":quic_io_handle_wrapper_lib", + ":quic_network_connection_lib", + "//source/common/quic:envoy_quic_utils_lib", + "//source/extensions/transport_sockets:well_known_names", "//source/server:connection_handler_lib", + "@com_googlesource_quiche//:quic_core_connection_lib", ], ) @@ -286,11 +288,12 @@ envoy_cc_library( hdrs = ["envoy_quic_client_connection.h"], tags = ["nofips"], deps = [ - ":envoy_quic_connection_lib", ":envoy_quic_packet_writer_lib", + ":quic_network_connection_lib", "//include/envoy/event:dispatcher_interface", "//source/common/network:socket_option_factory_lib", "//source/common/network:udp_packet_writer_handler_lib", + "@com_googlesource_quiche//:quic_core_connection_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) @@ -354,6 +357,8 @@ envoy_cc_library( "//source/common/network:address_lib", "//source/common/network:listen_socket_lib", "//source/common/network:socket_option_factory_lib", + "//source/common/quic:quic_io_handle_wrapper_lib", + "//source/extensions/transport_sockets:well_known_names", "@com_googlesource_quiche//:quic_core_http_header_list_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], diff --git a/source/common/quic/active_quic_listener.cc b/source/common/quic/active_quic_listener.cc index ba987c33ae8a2..6fe1af4b38d84 100644 --- a/source/common/quic/active_quic_listener.cc +++ b/source/common/quic/active_quic_listener.cc @@ -46,6 +46,9 @@ ActiveQuicListener::ActiveQuicListener( kernel_worker_routing_(kernel_worker_routing) { // This flag fix a QUICHE issue which may crash Envoy during connection close. SetQuicReloadableFlag(quic_single_ack_in_packet2, true); + // Do not include 32-byte per-entry overhead while counting header size. + quiche::FlagRegistry::getInstance(); + ASSERT(!GetQuicFlag(FLAGS_quic_header_size_limit_includes_overhead)); if (Runtime::LoaderSingleton::getExisting()) { enabled_.emplace(Runtime::FeatureFlag(enabled, Runtime::LoaderSingleton::get())); diff --git a/source/common/quic/client_connection_factory_impl.cc b/source/common/quic/client_connection_factory_impl.cc index 8034c74e47411..ffb6951237f9a 100644 --- a/source/common/quic/client_connection_factory_impl.cc +++ b/source/common/quic/client_connection_factory_impl.cc @@ -22,7 +22,9 @@ PersistentQuicInfoImpl::PersistentQuicInfoImpl( static_cast(server_addr->ip()->port()), false}, crypto_config_( std::make_unique(std::make_unique( - stats_scope, getConfig(transport_socket_factory), time_source))) {} + stats_scope, getConfig(transport_socket_factory), time_source))) { + quiche::FlagRegistry::getInstance(); +} namespace { // TODO(alyssawilk, danzh2010): This is mutable static info that is required for the QUICHE code. @@ -53,7 +55,6 @@ createQuicNetworkConnection(Http::PersistentQuicInfo& info, Event::Dispatcher& d static_info.quic_config_, info_impl->supported_versions_, std::move(connection), info_impl->server_id_, info_impl->crypto_config_.get(), &static_info.push_promise_index_, dispatcher, 0); - ret->Initialize(); return ret; } diff --git a/source/common/quic/codec_impl.cc b/source/common/quic/codec_impl.cc index bbabca605cd27..5cdae4dd8e962 100644 --- a/source/common/quic/codec_impl.cc +++ b/source/common/quic/codec_impl.cc @@ -22,7 +22,7 @@ QuicHttpServerConnectionImpl::QuicHttpServerConnectionImpl( EnvoyQuicServerSession& quic_session, Http::ServerConnectionCallbacks& callbacks, Http::Http3::CodecStats& stats, const envoy::config::core::v3::Http3ProtocolOptions& http3_options, - const uint32_t /*max_request_headers_kb*/, + const uint32_t max_request_headers_kb, envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction headers_with_underscores_action) : QuicHttpConnectionImplBase(quic_session, stats), quic_server_session_(quic_session) { @@ -30,6 +30,7 @@ QuicHttpServerConnectionImpl::QuicHttpServerConnectionImpl( quic_session.setHttp3Options(http3_options); quic_session.setHeadersWithUnderscoreAction(headers_with_underscores_action); quic_session.setHttpConnectionCallbacks(callbacks); + quic_session.set_max_inbound_header_list_size(max_request_headers_kb * 1024u); } void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { @@ -68,11 +69,12 @@ QuicHttpClientConnectionImpl::QuicHttpClientConnectionImpl( EnvoyQuicClientSession& session, Http::ConnectionCallbacks& callbacks, Http::Http3::CodecStats& stats, const envoy::config::core::v3::Http3ProtocolOptions& http3_options, - const uint32_t /*max_request_headers_kb*/) + const uint32_t max_request_headers_kb) : QuicHttpConnectionImplBase(session, stats), quic_client_session_(session) { session.setCodecStats(stats); session.setHttp3Options(http3_options); session.setHttpConnectionCallbacks(callbacks); + session.set_max_inbound_header_list_size(max_request_headers_kb * 1024); } Http::RequestEncoder& diff --git a/source/common/quic/envoy_quic_client_connection.cc b/source/common/quic/envoy_quic_client_connection.cc index 3b914b91f6300..158fa4d2244b3 100644 --- a/source/common/quic/envoy_quic_client_connection.cc +++ b/source/common/quic/envoy_quic_client_connection.cc @@ -42,12 +42,12 @@ EnvoyQuicClientConnection::EnvoyQuicClientConnection( quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, bool owns_writer, const quic::ParsedQuicVersionVector& supported_versions, Event::Dispatcher& dispatcher, Network::ConnectionSocketPtr&& connection_socket) - : EnvoyQuicConnection(server_connection_id, quic::QuicSocketAddress(), - envoyIpAddressToQuicSocketAddress( - connection_socket->addressProvider().remoteAddress()->ip()), - helper, alarm_factory, writer, owns_writer, quic::Perspective::IS_CLIENT, - supported_versions, std::move(connection_socket)), - dispatcher_(dispatcher) {} + : quic::QuicConnection(server_connection_id, quic::QuicSocketAddress(), + envoyIpAddressToQuicSocketAddress( + connection_socket->addressProvider().remoteAddress()->ip()), + &helper, &alarm_factory, writer, owns_writer, + quic::Perspective::IS_CLIENT, supported_versions), + QuicNetworkConnection(std::move(connection_socket)), dispatcher_(dispatcher) {} void EnvoyQuicClientConnection::processPacket( Network::Address::InstanceConstSharedPtr local_address, diff --git a/source/common/quic/envoy_quic_client_connection.h b/source/common/quic/envoy_quic_client_connection.h index 8fa3b18100e75..dc3587a3b05c4 100644 --- a/source/common/quic/envoy_quic_client_connection.h +++ b/source/common/quic/envoy_quic_client_connection.h @@ -3,13 +3,28 @@ #include "envoy/event/dispatcher.h" #include "common/network/utility.h" -#include "common/quic/envoy_quic_connection.h" +#include "common/quic/envoy_quic_utils.h" +#include "common/quic/quic_network_connection.h" + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + +#include "quiche/quic/core/quic_connection.h" + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif namespace Envoy { namespace Quic { // A client QuicConnection instance managing its own file events. -class EnvoyQuicClientConnection : public EnvoyQuicConnection, public Network::UdpPacketProcessor { +class EnvoyQuicClientConnection : public quic::QuicConnection, + public QuicNetworkConnection, + public Network::UdpPacketProcessor { public: // A connection socket will be created with given |local_addr|. If binding // port not provided in |local_addr|, pick up a random port. diff --git a/source/common/quic/envoy_quic_client_session.cc b/source/common/quic/envoy_quic_client_session.cc index 7c969926fa1e7..6f0484c9535e1 100644 --- a/source/common/quic/envoy_quic_client_session.cc +++ b/source/common/quic/envoy_quic_client_session.cc @@ -11,23 +11,21 @@ EnvoyQuicClientSession::EnvoyQuicClientSession( quic::QuicCryptoClientConfig* crypto_config, quic::QuicClientPushPromiseIndex* push_promise_index, Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) - : QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), + : QuicFilterManagerConnectionImpl(*connection, connection->connection_id(), dispatcher, + send_buffer_limit), quic::QuicSpdyClientSession(config, supported_versions, connection.release(), server_id, crypto_config, push_promise_index), - host_name_(server_id.host()) { - // HTTP/3 header limits should be configurable, but for now hard-code to Envoy defaults. - set_max_inbound_header_list_size(Http::DEFAULT_MAX_REQUEST_HEADERS_KB * 1000); -} + host_name_(server_id.host()) {} EnvoyQuicClientSession::~EnvoyQuicClientSession() { ASSERT(!connection()->connected()); - quic_connection_ = nullptr; + network_connection_ = nullptr; } absl::string_view EnvoyQuicClientSession::requestedServerName() const { return host_name_; } void EnvoyQuicClientSession::connect() { - dynamic_cast(quic_connection_)->setUpConnectionSocket(); + dynamic_cast(network_connection_)->setUpConnectionSocket(); // Start version negotiation and crypto handshake during which the connection may fail if server // doesn't support the one and only supported version. CryptoConnect(); @@ -41,7 +39,8 @@ void EnvoyQuicClientSession::OnConnectionClosed(const quic::QuicConnectionCloseF void EnvoyQuicClientSession::Initialize() { quic::QuicSpdyClientSession::Initialize(); - quic_connection_->setEnvoyConnection(*this); + initialized_ = true; + network_connection_->setEnvoyConnection(*this); } void EnvoyQuicClientSession::OnCanWrite() { @@ -102,6 +101,14 @@ EnvoyQuicClientSession::CreateIncomingStream(quic::PendingStream* /*pending*/) { bool EnvoyQuicClientSession::hasDataToWrite() { return HasDataToWrite(); } +const quic::QuicConnection* EnvoyQuicClientSession::quicConnection() const { + return initialized_ ? connection() : nullptr; +} + +quic::QuicConnection* EnvoyQuicClientSession::quicConnection() { + return initialized_ ? connection() : nullptr; +} + void EnvoyQuicClientSession::OnTlsHandshakeComplete() { raiseConnectionEvent(Network::ConnectionEvent::Connected); } diff --git a/source/common/quic/envoy_quic_client_session.h b/source/common/quic/envoy_quic_client_session.h index 54398175b83c9..638d99ffcb809 100644 --- a/source/common/quic/envoy_quic_client_session.h +++ b/source/common/quic/envoy_quic_client_session.h @@ -77,6 +77,9 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, // QuicFilterManagerConnectionImpl bool hasDataToWrite() override; + // Used by base class to access quic connection after initialization. + const quic::QuicConnection* quicConnection() const override; + quic::QuicConnection* quicConnection() override; private: // These callbacks are owned by network filters and quic session should outlive diff --git a/source/common/quic/envoy_quic_connection.cc b/source/common/quic/envoy_quic_connection.cc deleted file mode 100644 index 5a51ada8cc19d..0000000000000 --- a/source/common/quic/envoy_quic_connection.cc +++ /dev/null @@ -1,27 +0,0 @@ -#include "common/quic/envoy_quic_connection.h" - -#include "common/quic/envoy_quic_utils.h" - -namespace Envoy { -namespace Quic { - -EnvoyQuicConnection::EnvoyQuicConnection(const quic::QuicConnectionId& server_connection_id, - quic::QuicSocketAddress initial_self_address, - quic::QuicSocketAddress initial_peer_address, - quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, - quic::QuicPacketWriter* writer, bool owns_writer, - quic::Perspective perspective, - const quic::ParsedQuicVersionVector& supported_versions, - Network::ConnectionSocketPtr&& connection_socket) - : quic::QuicConnection(server_connection_id, initial_self_address, initial_peer_address, - &helper, &alarm_factory, writer, owns_writer, perspective, - supported_versions), - connection_socket_(std::move(connection_socket)) {} - -EnvoyQuicConnection::~EnvoyQuicConnection() { connection_socket_->close(); } - -uint64_t EnvoyQuicConnection::id() const { return envoy_connection_->id(); } - -} // namespace Quic -} // namespace Envoy diff --git a/source/common/quic/envoy_quic_dispatcher.cc b/source/common/quic/envoy_quic_dispatcher.cc index 6a7caeba272d0..8626830194043 100644 --- a/source/common/quic/envoy_quic_dispatcher.cc +++ b/source/common/quic/envoy_quic_dispatcher.cc @@ -3,6 +3,7 @@ #include "common/http/utility.h" #include "common/quic/envoy_quic_server_connection.h" #include "common/quic/envoy_quic_server_session.h" +#include "common/quic/envoy_quic_utils.h" namespace Envoy { namespace Quic { @@ -48,21 +49,34 @@ void EnvoyQuicDispatcher::OnConnectionClosed(quic::QuicConnectionId connection_i std::unique_ptr EnvoyQuicDispatcher::CreateQuicSession( quic::QuicConnectionId server_connection_id, const quic::QuicSocketAddress& self_address, - const quic::QuicSocketAddress& peer_address, absl::string_view /*alpn*/, - const quic::ParsedQuicVersion& version, absl::string_view /*sni*/) { + const quic::QuicSocketAddress& peer_address, absl::string_view alpn, + const quic::ParsedQuicVersion& version, absl::string_view sni) { quic::QuicConfig quic_config = config(); // TODO(danzh) setup flow control window via config. quic_config.SetInitialStreamFlowControlWindowToSend( Http2::Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE); quic_config.SetInitialSessionFlowControlWindowToSend( 1.5 * Http2::Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE); + + Network::ConnectionSocketPtr connection_socket = createServerConnectionSocket( + listen_socket_.ioHandle(), self_address, peer_address, std::string(sni), alpn); + const Network::FilterChain* filter_chain = + listener_config_.filterChainManager().findFilterChain(*connection_socket); + auto quic_connection = std::make_unique( server_connection_id, self_address, peer_address, *helper(), *alarm_factory(), writer(), - /*owns_writer=*/false, quic::ParsedQuicVersionVector{version}, listen_socket_); + /*owns_writer=*/false, quic::ParsedQuicVersionVector{version}, std::move(connection_socket)); auto quic_session = std::make_unique( quic_config, quic::ParsedQuicVersionVector{version}, std::move(quic_connection), this, session_helper(), crypto_config(), compressed_certs_cache(), dispatcher_, - listener_config_.perConnectionBufferLimitBytes(), listener_config_); + listener_config_.perConnectionBufferLimitBytes()); + if (filter_chain != nullptr) { + const bool has_filter_initialized = + listener_config_.filterChainFactory().createNetworkFilterChain( + *quic_session, filter_chain->networkFilterFactories()); + // QUIC listener must have HCM filter configured. Otherwise, stream creation later will fail. + ASSERT(has_filter_initialized); + } quic_session->Initialize(); // Filter chain can't be retrieved here as self address is unknown at this // point. diff --git a/source/common/quic/envoy_quic_proof_source.cc b/source/common/quic/envoy_quic_proof_source.cc index bc0dfb8c8de8d..fd9554f1a7a8b 100644 --- a/source/common/quic/envoy_quic_proof_source.cc +++ b/source/common/quic/envoy_quic_proof_source.cc @@ -82,16 +82,12 @@ EnvoyQuicProofSource::getTlsCertConfigAndFilterChain(const quic::QuicSocketAddre const quic::QuicSocketAddress& client_address, const std::string& hostname) { ENVOY_LOG(trace, "Getting cert chain for {}", hostname); - Network::ConnectionSocketImpl connection_socket( - std::make_unique(listen_socket_.ioHandle()), - quicAddressToEnvoyAddressInstance(server_address), - quicAddressToEnvoyAddressInstance(client_address)); - connection_socket.setDetectedTransportProtocol( - Extensions::TransportSockets::TransportProtocolNames::get().Quic); - connection_socket.setRequestedServerName(hostname); - connection_socket.setRequestedApplicationProtocols({"h2"}); + // TODO(danzh) modify QUICHE to make quic session or ALPN accessible to avoid hard-coded ALPN. + Network::ConnectionSocketPtr connection_socket = createServerConnectionSocket( + listen_socket_.ioHandle(), server_address, client_address, hostname, "h3-29"); const Network::FilterChain* filter_chain = - filter_chain_manager_.findFilterChain(connection_socket); + filter_chain_manager_.findFilterChain(*connection_socket); + if (filter_chain == nullptr) { listener_stats_.no_filter_chain_match_.inc(); ENVOY_LOG(warn, "No matching filter chain found for handshake."); diff --git a/source/common/quic/envoy_quic_server_connection.cc b/source/common/quic/envoy_quic_server_connection.cc index 290b60e0fbb12..b17a9a88663f3 100644 --- a/source/common/quic/envoy_quic_server_connection.cc +++ b/source/common/quic/envoy_quic_server_connection.cc @@ -14,29 +14,26 @@ EnvoyQuicServerConnection::EnvoyQuicServerConnection( quic::QuicSocketAddress initial_self_address, quic::QuicSocketAddress initial_peer_address, quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, bool owns_writer, - const quic::ParsedQuicVersionVector& supported_versions, Network::Socket& listen_socket) - : EnvoyQuicConnection(server_connection_id, initial_self_address, initial_peer_address, helper, - alarm_factory, writer, owns_writer, quic::Perspective::IS_SERVER, - supported_versions, - std::make_unique( - // Wraps the real IoHandle instance so that if the connection socket - // gets closed, the real IoHandle won't be affected. - std::make_unique(listen_socket.ioHandle()), - nullptr, quicAddressToEnvoyAddressInstance(initial_peer_address))) {} + const quic::ParsedQuicVersionVector& supported_versions, + Network::ConnectionSocketPtr connection_socket) + : quic::QuicConnection(server_connection_id, initial_self_address, initial_peer_address, + &helper, &alarm_factory, writer, owns_writer, + quic::Perspective::IS_SERVER, supported_versions), + QuicNetworkConnection(std::move(connection_socket)) {} bool EnvoyQuicServerConnection::OnPacketHeader(const quic::QuicPacketHeader& header) { - if (!EnvoyQuicConnection::OnPacketHeader(header)) { + quic::QuicSocketAddress old_self_address = self_address(); + if (!quic::QuicConnection::OnPacketHeader(header)) { return false; } - if (connectionSocket()->addressProvider().localAddress() != nullptr) { + if (old_self_address == self_address()) { return true; } + // Update local address if QUICHE has updated the self address. ASSERT(self_address().IsInitialized()); - // Self address should be initialized by now. connectionSocket()->addressProvider().setLocalAddress( quicAddressToEnvoyAddressInstance(self_address())); - connectionSocket()->setDetectedTransportProtocol( - Extensions::TransportSockets::TransportProtocolNames::get().Quic); + return true; } diff --git a/source/common/quic/envoy_quic_server_connection.h b/source/common/quic/envoy_quic_server_connection.h index 62903793d985a..04c1a4b8f80d9 100644 --- a/source/common/quic/envoy_quic_server_connection.h +++ b/source/common/quic/envoy_quic_server_connection.h @@ -2,14 +2,27 @@ #include "envoy/network/listener.h" -#include "common/quic/envoy_quic_connection.h" +#include "common/quic/envoy_quic_utils.h" +#include "common/quic/quic_network_connection.h" #include "server/connection_handler_impl.h" +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + +#include "quiche/quic/core/quic_connection.h" + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + namespace Envoy { namespace Quic { -class EnvoyQuicServerConnection : public EnvoyQuicConnection { +class EnvoyQuicServerConnection : public quic::QuicConnection, public QuicNetworkConnection { public: EnvoyQuicServerConnection(const quic::QuicConnectionId& server_connection_id, quic::QuicSocketAddress initial_self_address, @@ -18,9 +31,9 @@ class EnvoyQuicServerConnection : public EnvoyQuicConnection { quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, bool owns_writer, const quic::ParsedQuicVersionVector& supported_versions, - Network::Socket& listen_socket); + Network::ConnectionSocketPtr connection_socket); - // EnvoyQuicConnection + // QuicNetworkConnection // Overridden to set connection_socket_ with initialized self address and retrieve filter chain. bool OnPacketHeader(const quic::QuicPacketHeader& header) override; }; diff --git a/source/common/quic/envoy_quic_server_session.cc b/source/common/quic/envoy_quic_server_session.cc index d13da33e4110a..44208d4fb3f34 100644 --- a/source/common/quic/envoy_quic_server_session.cc +++ b/source/common/quic/envoy_quic_server_session.cc @@ -11,21 +11,19 @@ namespace Quic { EnvoyQuicServerSession::EnvoyQuicServerSession( const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, - std::unique_ptr connection, quic::QuicSession::Visitor* visitor, + std::unique_ptr connection, quic::QuicSession::Visitor* visitor, quic::QuicCryptoServerStream::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, quic::QuicCompressedCertsCache* compressed_certs_cache, Event::Dispatcher& dispatcher, - uint32_t send_buffer_limit, Network::ListenerConfig& listener_config) + uint32_t send_buffer_limit) : quic::QuicServerSessionBase(config, supported_versions, connection.get(), visitor, helper, crypto_config, compressed_certs_cache), - QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), - quic_connection_(std::move(connection)), listener_config_(listener_config) { - // HTTP/3 header limits should be configurable, but for now hard-code to Envoy defaults. - set_max_inbound_header_list_size(Http::DEFAULT_MAX_REQUEST_HEADERS_KB * 1000); -} + QuicFilterManagerConnectionImpl(*connection, connection->connection_id(), dispatcher, + send_buffer_limit), + quic_connection_(std::move(connection)) {} EnvoyQuicServerSession::~EnvoyQuicServerSession() { ASSERT(!quic_connection_->connected()); - QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; + QuicFilterManagerConnectionImpl::network_connection_ = nullptr; } absl::string_view EnvoyQuicServerSession::requestedServerName() const { @@ -89,6 +87,7 @@ void EnvoyQuicServerSession::OnConnectionClosed(const quic::QuicConnectionCloseF void EnvoyQuicServerSession::Initialize() { quic::QuicServerSessionBase::Initialize(); + initialized_ = true; quic_connection_->setEnvoyConnection(*this); } @@ -109,29 +108,23 @@ void EnvoyQuicServerSession::SetDefaultEncryptionLevel(quic::EncryptionLevel lev if (level != quic::ENCRYPTION_FORWARD_SECURE) { return; } - maybeCreateNetworkFilters(); // This is only reached once, when handshake is done. raiseConnectionEvent(Network::ConnectionEvent::Connected); } bool EnvoyQuicServerSession::hasDataToWrite() { return HasDataToWrite(); } -void EnvoyQuicServerSession::OnTlsHandshakeComplete() { - quic::QuicServerSessionBase::OnTlsHandshakeComplete(); - maybeCreateNetworkFilters(); - raiseConnectionEvent(Network::ConnectionEvent::Connected); +const quic::QuicConnection* EnvoyQuicServerSession::quicConnection() const { + return initialized_ ? connection() : nullptr; } -void EnvoyQuicServerSession::maybeCreateNetworkFilters() { - auto proof_source_details = - dynamic_cast(GetCryptoStream()->ProofSourceDetails()); - ASSERT(proof_source_details != nullptr, - "ProofSource didn't provide ProofSource::Details. No filter chain will be installed."); +quic::QuicConnection* EnvoyQuicServerSession::quicConnection() { + return initialized_ ? connection() : nullptr; +} - const bool has_filter_initialized = - listener_config_.filterChainFactory().createNetworkFilterChain( - *this, proof_source_details->filterChain().networkFilterFactories()); - ASSERT(has_filter_initialized); +void EnvoyQuicServerSession::OnTlsHandshakeComplete() { + quic::QuicServerSessionBase::OnTlsHandshakeComplete(); + raiseConnectionEvent(Network::ConnectionEvent::Connected); } size_t EnvoyQuicServerSession::WriteHeadersOnHeadersStream( diff --git a/source/common/quic/envoy_quic_server_session.h b/source/common/quic/envoy_quic_server_session.h index e80e42c9e62fc..1e4900e5632af 100644 --- a/source/common/quic/envoy_quic_server_session.h +++ b/source/common/quic/envoy_quic_server_session.h @@ -19,6 +19,7 @@ #include "common/quic/send_buffer_monitor.h" #include "common/quic/quic_filter_manager_connection_impl.h" +#include "common/quic/envoy_quic_server_connection.h" #include "common/quic/envoy_quic_server_stream.h" namespace Envoy { @@ -33,13 +34,12 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, public: EnvoyQuicServerSession(const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, - std::unique_ptr connection, + std::unique_ptr connection, quic::QuicSession::Visitor* visitor, quic::QuicCryptoServerStreamBase::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, quic::QuicCompressedCertsCache* compressed_certs_cache, - Event::Dispatcher& dispatcher, uint32_t send_buffer_limit, - Network::ListenerConfig& listener_config); + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); ~EnvoyQuicServerSession() override; @@ -87,13 +87,14 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, // QuicFilterManagerConnectionImpl bool hasDataToWrite() override; + // Used by base class to access quic connection after initialization. + const quic::QuicConnection* quicConnection() const override; + quic::QuicConnection* quicConnection() override; private: void setUpRequestDecoder(EnvoyQuicServerStream& stream); - void maybeCreateNetworkFilters(); - std::unique_ptr quic_connection_; - Network::ListenerConfig& listener_config_; + std::unique_ptr quic_connection_; // These callbacks are owned by network filters and quic session should out live // them. Http::ServerConnectionCallbacks* http_connection_callbacks_{nullptr}; diff --git a/source/common/quic/envoy_quic_utils.cc b/source/common/quic/envoy_quic_utils.cc index 3da9c12702ea9..8f9460db3e6ce 100644 --- a/source/common/quic/envoy_quic_utils.cc +++ b/source/common/quic/envoy_quic_utils.cc @@ -1,11 +1,15 @@ #include "common/quic/envoy_quic_utils.h" +#include + #include "envoy/common/platform.h" #include "envoy/config/core/v3/base.pb.h" #include "common/network/socket_option_factory.h" #include "common/network/utility.h" +#include "extensions/transport_sockets/well_known_names.h" + namespace Envoy { namespace Quic { @@ -223,5 +227,21 @@ int deduceSignatureAlgorithmFromPublicKey(const EVP_PKEY* public_key, std::strin return sign_alg; } +Network::ConnectionSocketPtr +createServerConnectionSocket(Network::IoHandle& io_handle, + const quic::QuicSocketAddress& self_address, + const quic::QuicSocketAddress& peer_address, + const std::string& hostname, absl::string_view alpn) { + auto connection_socket = std::make_unique( + std::make_unique(io_handle), + quicAddressToEnvoyAddressInstance(self_address), + quicAddressToEnvoyAddressInstance(peer_address)); + connection_socket->setDetectedTransportProtocol( + Extensions::TransportSockets::TransportProtocolNames::get().Quic); + connection_socket->setRequestedServerName(hostname); + connection_socket->setRequestedApplicationProtocols({alpn}); + return connection_socket; +} + } // namespace Quic } // namespace Envoy diff --git a/source/common/quic/envoy_quic_utils.h b/source/common/quic/envoy_quic_utils.h index 8898bd1e4971d..f30c582b7d534 100644 --- a/source/common/quic/envoy_quic_utils.h +++ b/source/common/quic/envoy_quic_utils.h @@ -7,6 +7,7 @@ #include "common/http/header_map_impl.h" #include "common/network/address_impl.h" #include "common/network/listen_socket_impl.h" +#include "common/quic/quic_io_handle_wrapper.h" #if defined(__GNUC__) #pragma GCC diagnostic push @@ -125,5 +126,13 @@ bssl::UniquePtr parseDERCertificate(const std::string& der_bytes, std::str // not supported, return 0 with error_details populated correspondingly. int deduceSignatureAlgorithmFromPublicKey(const EVP_PKEY* public_key, std::string* error_details); +// Return a connection socket which read and write via io_handle, but doesn't close it when the +// socket gets closed nor set options on the socket. +Network::ConnectionSocketPtr +createServerConnectionSocket(Network::IoHandle& io_handle, + const quic::QuicSocketAddress& self_address, + const quic::QuicSocketAddress& peer_address, + const std::string& hostname, absl::string_view alpn); + } // namespace Quic } // namespace Envoy diff --git a/source/common/quic/platform/quiche_flags_impl.cc b/source/common/quic/platform/quiche_flags_impl.cc index 6b2f9a638df43..641f31ae0b2f0 100644 --- a/source/common/quic/platform/quiche_flags_impl.cc +++ b/source/common/quic/platform/quiche_flags_impl.cc @@ -33,9 +33,8 @@ absl::flat_hash_map makeFlagMap() { #define QUIC_PROTOCOL_FLAG(type, flag, ...) flags.emplace(FLAGS_##flag->name(), FLAGS_##flag); #include "quiche/quic/core/quic_protocol_flags_list.h" #undef QUIC_PROTOCOL_FLAG - - // TODO(danzh) Re-enable TLS resumption after #15912 is checked in. - FLAGS_quic_disable_server_tls_resumption->setValue(true); + // Do not include 32-byte per-entry overhead while counting header size. + FLAGS_quic_header_size_limit_includes_overhead->setValue(false); return flags; } diff --git a/source/common/quic/quic_filter_manager_connection_impl.cc b/source/common/quic/quic_filter_manager_connection_impl.cc index b58b0a4a25a53..e47c27080d1d2 100644 --- a/source/common/quic/quic_filter_manager_connection_impl.cc +++ b/source/common/quic/quic_filter_manager_connection_impl.cc @@ -1,17 +1,18 @@ #include "common/quic/quic_filter_manager_connection_impl.h" +#include #include namespace Envoy { namespace Quic { -QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl(EnvoyQuicConnection& connection, - Event::Dispatcher& dispatcher, - uint32_t send_buffer_limit) +QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl( + QuicNetworkConnection& connection, const quic::QuicConnectionId& connection_id, + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) // Using this for purpose other than logging is not safe. Because QUIC connection id can be // 18 bytes, so there might be collision when it's hashed to 8 bytes. - : Network::ConnectionImplBase(dispatcher, /*id=*/connection.connection_id().Hash()), - quic_connection_(&connection), filter_manager_(*this, *connection.connectionSocket()), + : Network::ConnectionImplBase(dispatcher, /*id=*/connection_id.Hash()), + network_connection_(&connection), filter_manager_(*this, *connection.connectionSocket()), stream_info_(dispatcher.timeSource(), connection.connectionSocket()->addressProviderSharedPtr()), write_buffer_watermark_simulation_( @@ -61,10 +62,15 @@ bool QuicFilterManagerConnectionImpl::aboveHighWatermark() const { } void QuicFilterManagerConnectionImpl::close(Network::ConnectionCloseType type) { - if (quic_connection_ == nullptr) { + if (network_connection_ == nullptr) { // Already detached from quic connection. return; } + if (!initialized_) { + // Delay close till the first OnCanWrite() call. + close_type_during_initialize_ = type; + return; + } const bool delayed_close_timeout_configured = delayed_close_timeout_.count() > 0; if (hasDataToWrite() && type != Network::ConnectionCloseType::NoFlush) { if (delayed_close_timeout_configured) { @@ -105,7 +111,7 @@ void QuicFilterManagerConnectionImpl::close(Network::ConnectionCloseType type) { const Network::ConnectionSocket::OptionsSharedPtr& QuicFilterManagerConnectionImpl::socketOptions() const { - return quic_connection_->connectionSocket()->options(); + return network_connection_->connectionSocket()->options(); } Ssl::ConnectionInfoConstSharedPtr QuicFilterManagerConnectionImpl::ssl() const { @@ -134,6 +140,10 @@ void QuicFilterManagerConnectionImpl::updateBytesBuffered(size_t old_buffered_by void QuicFilterManagerConnectionImpl::maybeApplyDelayClosePolicy() { if (!inDelayedClose()) { + if (close_type_during_initialize_.has_value()) { + close(close_type_during_initialize_.value()); + close_type_during_initialize_ = absl::nullopt; + } return; } if (hasDataToWrite() || delayed_close_state_ == DelayedCloseState::CloseAfterFlushAndWait) { @@ -151,21 +161,21 @@ void QuicFilterManagerConnectionImpl::onConnectionCloseEvent( const quic::QuicConnectionCloseFrame& frame, quic::ConnectionCloseSource source) { transport_failure_reason_ = absl::StrCat(quic::QuicErrorCodeToString(frame.quic_error_code), " with details: ", frame.error_details); - if (quic_connection_ != nullptr) { + if (network_connection_ != nullptr) { // Tell network callbacks about connection close if not detached yet. raiseConnectionEvent(source == quic::ConnectionCloseSource::FROM_PEER ? Network::ConnectionEvent::RemoteClose : Network::ConnectionEvent::LocalClose); - ASSERT(quic_connection_ != nullptr); - quic_connection_ = nullptr; + ASSERT(network_connection_ != nullptr); + network_connection_ = nullptr; } } void QuicFilterManagerConnectionImpl::closeConnectionImmediately() { - if (quic_connection_ == nullptr) { + if (quicConnection() == nullptr) { return; } - quic_connection_->CloseConnection(quic::QUIC_NO_ERROR, "Closed by application", + quicConnection()->CloseConnection(quic::QUIC_NO_ERROR, "Closed by application", quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); } diff --git a/source/common/quic/quic_filter_manager_connection_impl.h b/source/common/quic/quic_filter_manager_connection_impl.h index a1532222ddb53..91cfd98b8e238 100644 --- a/source/common/quic/quic_filter_manager_connection_impl.h +++ b/source/common/quic/quic_filter_manager_connection_impl.h @@ -5,11 +5,23 @@ #include "envoy/event/dispatcher.h" #include "envoy/network/connection.h" +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + +#include "quiche/quic/core/quic_connection.h" + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + #include "common/common/empty_string.h" #include "common/common/logger.h" #include "common/http/http3/codec_stats.h" #include "common/network/connection_impl_base.h" -#include "common/quic/envoy_quic_connection.h" +#include "common/quic/quic_network_connection.h" #include "common/quic/envoy_quic_simulated_watermark_buffer.h" #include "common/quic/send_buffer_monitor.h" #include "common/stream_info/stream_info_impl.h" @@ -24,8 +36,9 @@ namespace Quic { class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, public SendBufferMonitor { public: - QuicFilterManagerConnectionImpl(EnvoyQuicConnection& connection, Event::Dispatcher& dispatcher, - uint32_t send_buffer_limit); + QuicFilterManagerConnectionImpl(QuicNetworkConnection& connection, + const quic::QuicConnectionId& connection_id, + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); ~QuicFilterManagerConnectionImpl() override = default; // Network::FilterManager @@ -56,10 +69,10 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, void detectEarlyCloseWhenReadDisabled(bool /*value*/) override { ASSERT(false); } bool readEnabled() const override { return true; } const Network::SocketAddressSetter& addressProvider() const override { - return quic_connection_->connectionSocket()->addressProvider(); + return network_connection_->connectionSocket()->addressProvider(); } Network::SocketAddressProviderSharedPtr addressProviderSharedPtr() const override { - return quic_connection_->connectionSocket()->addressProviderSharedPtr(); + return network_connection_->connectionSocket()->addressProviderSharedPtr(); } absl::optional unixSocketPeerCredentials() const override { @@ -69,20 +82,20 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, void setConnectionStats(const Network::Connection::ConnectionStats& stats) override { // TODO(danzh): populate stats. Network::ConnectionImplBase::setConnectionStats(stats); - quic_connection_->setConnectionStats(stats); + network_connection_->setConnectionStats(stats); } Ssl::ConnectionInfoConstSharedPtr ssl() const override; Network::Connection::State state() const override { - if (quic_connection_ != nullptr && quic_connection_->connected()) { + if (!initialized_ || (quicConnection() != nullptr && quicConnection()->connected())) { return Network::Connection::State::Open; } return Network::Connection::State::Closed; } bool connecting() const override { - if (quic_connection_ != nullptr && !quic_connection_->IsHandshakeComplete()) { - return true; + if (initialized_ && (quicConnection() == nullptr || quicConnection()->IsHandshakeComplete())) { + return false; } - return false; + return true; } void write(Buffer::Instance& /*data*/, bool /*end_stream*/) override { // All writes should be handled by Quic internally. @@ -138,11 +151,16 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, virtual bool hasDataToWrite() PURE; - EnvoyQuicConnection* quic_connection_{nullptr}; + // Returns a QuicConnection interface if initialized_ is true, otherwise nullptr. + virtual const quic::QuicConnection* quicConnection() const = 0; + virtual quic::QuicConnection* quicConnection() = 0; + + QuicNetworkConnection* network_connection_{nullptr}; absl::optional> codec_stats_; absl::optional> http3_options_; + bool initialized_{false}; private: friend class Envoy::TestPauseFilterForQuic; @@ -167,6 +185,7 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, // send buffer. EnvoyQuicSimulatedWatermarkBuffer write_buffer_watermark_simulation_; Buffer::OwnedImpl empty_buffer_; + absl::optional close_type_during_initialize_; }; } // namespace Quic diff --git a/source/common/quic/quic_network_connection.cc b/source/common/quic/quic_network_connection.cc new file mode 100644 index 0000000000000..577178c9cc320 --- /dev/null +++ b/source/common/quic/quic_network_connection.cc @@ -0,0 +1,14 @@ +#include "common/quic/quic_network_connection.h" + +namespace Envoy { +namespace Quic { + +QuicNetworkConnection::QuicNetworkConnection(Network::ConnectionSocketPtr&& connection_socket) + : connection_socket_(std::move(connection_socket)) {} + +QuicNetworkConnection::~QuicNetworkConnection() { connection_socket_->close(); } + +uint64_t QuicNetworkConnection::id() const { return envoy_connection_->id(); } + +} // namespace Quic +} // namespace Envoy diff --git a/source/common/quic/envoy_quic_connection.h b/source/common/quic/quic_network_connection.h similarity index 54% rename from source/common/quic/envoy_quic_connection.h rename to source/common/quic/quic_network_connection.h index d853252e777ff..66c51a7d52150 100644 --- a/source/common/quic/envoy_quic_connection.h +++ b/source/common/quic/quic_network_connection.h @@ -1,40 +1,21 @@ #pragma once -#if defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Winvalid-offsetof" -#endif - -#include "quiche/quic/core/quic_connection.h" - -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif - #include -#include "common/common/logger.h" #include "envoy/network/connection.h" +#include "common/common/logger.h" + namespace Envoy { namespace Quic { -// Derived for network filter chain, stats and QoS. This is used on both client -// and server side. -class EnvoyQuicConnection : public quic::QuicConnection, - protected Logger::Loggable { +// A base class of both the client and server connections which keeps stats and +// connection socket. +class QuicNetworkConnection : protected Logger::Loggable { public: - EnvoyQuicConnection(const quic::QuicConnectionId& server_connection_id, - quic::QuicSocketAddress initial_self_address, - quic::QuicSocketAddress initial_peer_address, - quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, - bool owns_writer, quic::Perspective perspective, - const quic::ParsedQuicVersionVector& supported_versions, - Network::ConnectionSocketPtr&& connection_socket); + QuicNetworkConnection(Network::ConnectionSocketPtr&& connection_socket); - ~EnvoyQuicConnection() override; + virtual ~QuicNetworkConnection(); // Called by EnvoyQuicSession::setConnectionStats(). void setConnectionStats(const Network::Connection::ConnectionStats& stats) { diff --git a/test/common/http/common.h b/test/common/http/common.h index c031767ef3474..3a28e9b369b87 100644 --- a/test/common/http/common.h +++ b/test/common/http/common.h @@ -22,6 +22,7 @@ class CodecClientForTest : public Http::CodecClient { Upstream::HostDescriptionConstSharedPtr host, Event::Dispatcher& dispatcher) : CodecClient(type, std::move(connection), host, dispatcher), destroy_cb_(destroy_cb) { codec_.reset(codec); + connect(); } ~CodecClientForTest() override { if (destroy_cb_) { diff --git a/test/common/quic/BUILD b/test/common/quic/BUILD index b5ed243f8bbba..53967319be888 100644 --- a/test/common/quic/BUILD +++ b/test/common/quic/BUILD @@ -275,6 +275,8 @@ envoy_cc_test_library( external_deps = ["bazel_runfiles"], tags = ["nofips"], deps = [ + "//source/common/quic:envoy_quic_client_connection_lib", + "//source/common/quic:envoy_quic_server_connection_lib", "//source/common/quic:quic_filter_manager_connection_lib", "//test/test_common:environment_lib", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", diff --git a/test/common/quic/active_quic_listener_test.cc b/test/common/quic/active_quic_listener_test.cc index 6ed403a415fd7..d4cfe8a54231a 100644 --- a/test/common/quic/active_quic_listener_test.cc +++ b/test/common/quic/active_quic_listener_test.cc @@ -126,7 +126,8 @@ class ActiveQuicListenerTest : public QuicMultiVersionTest { })); listener_factory_ = createQuicListenerFactory(yamlForQuicConfig()); - EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager_)); + EXPECT_CALL(listener_config_, filterChainManager()) + .WillRepeatedly(ReturnRef(filter_chain_manager_)); quic_listener_ = staticUniquePointerCast(listener_factory_->createActiveUdpListener( 0, connection_handler_, *dispatcher_, listener_config_)); @@ -155,9 +156,9 @@ class ActiveQuicListenerTest : public QuicMultiVersionTest { } void maybeConfigureMocks(int connection_count) { - if (quic_version_.UsesTls()) { - return; - } + EXPECT_CALL(filter_chain_manager_, findFilterChain(_)) + .Times(connection_count) + .WillRepeatedly(Return(filter_chain_)); EXPECT_CALL(listener_config_, filterChainFactory()).Times(connection_count); EXPECT_CALL(listener_config_.filter_chain_factory_, createNetworkFilterChain(_, _)) .Times(connection_count) @@ -167,8 +168,10 @@ class ActiveQuicListenerTest : public QuicMultiVersionTest { Server::Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); return true; })); - EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::Connected)) - .Times(connection_count); + if (!quic_version_.UsesTls()) { + EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::Connected)) + .Times(connection_count); + } EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose)) .Times(connection_count); diff --git a/test/common/quic/envoy_quic_dispatcher_test.cc b/test/common/quic/envoy_quic_dispatcher_test.cc index 0a3e6a0ce492c..5b4f7b4b76b63 100644 --- a/test/common/quic/envoy_quic_dispatcher_test.cc +++ b/test/common/quic/envoy_quic_dispatcher_test.cc @@ -109,22 +109,7 @@ class EnvoyQuicDispatcherTest : public QuicMultiVersionTest, dispatcher_->run(Event::Dispatcher::RunType::NonBlock); } - void processValidChloPacketAndCheckStatus(bool should_buffer) { - quic::QuicSocketAddress peer_addr(version_ == Network::Address::IpVersion::v4 - ? quic::QuicIpAddress::Loopback4() - : quic::QuicIpAddress::Loopback6(), - 54321); - quic::QuicBufferedPacketStore* buffered_packets = - quic::test::QuicDispatcherPeer::GetBufferedPackets(&envoy_quic_dispatcher_); - if (!should_buffer) { - // Set QuicDispatcher::new_sessions_allowed_per_event_loop_ to - // |kNumSessionsToCreatePerLoopForTests| so that received CHLOs can be - // processed immediately. - envoy_quic_dispatcher_.ProcessBufferedChlos(kNumSessionsToCreatePerLoopForTests); - EXPECT_FALSE(buffered_packets->HasChlosBuffered()); - EXPECT_FALSE(buffered_packets->HasBufferedPackets(connection_id_)); - } - + void processValidChloPacket(const quic::QuicSocketAddress& peer_addr) { // Create a Quic Crypto or TLS1.3 CHLO packet. EnvoyQuicClock clock(*dispatcher_); Buffer::OwnedImpl payload = generateChloPacketToSend( @@ -142,7 +127,25 @@ class EnvoyQuicDispatcherTest : public QuicMultiVersionTest, envoy_quic_dispatcher_.ProcessPacket( envoyIpAddressToQuicSocketAddress(listen_socket_->addressProvider().localAddress()->ip()), peer_addr, *received_packet); + } + void processValidChloPacketAndCheckStatus(bool should_buffer) { + quic::QuicSocketAddress peer_addr(version_ == Network::Address::IpVersion::v4 + ? quic::QuicIpAddress::Loopback4() + : quic::QuicIpAddress::Loopback6(), + 54321); + quic::QuicBufferedPacketStore* buffered_packets = + quic::test::QuicDispatcherPeer::GetBufferedPackets(&envoy_quic_dispatcher_); + if (!should_buffer) { + // Set QuicDispatcher::new_sessions_allowed_per_event_loop_ to + // |kNumSessionsToCreatePerLoopForTests| so that received CHLOs can be + // processed immediately. + envoy_quic_dispatcher_.ProcessBufferedChlos(kNumSessionsToCreatePerLoopForTests); + EXPECT_FALSE(buffered_packets->HasChlosBuffered()); + EXPECT_FALSE(buffered_packets->HasBufferedPackets(connection_id_)); + } + + processValidChloPacket(peer_addr); if (should_buffer) { // Incoming CHLO packet is buffered, because ProcessPacket() is called before // ProcessBufferedChlos(). @@ -170,6 +173,7 @@ class EnvoyQuicDispatcherTest : public QuicMultiVersionTest, ASSERT(envoy_connection->addressProvider().localAddress() != nullptr); EXPECT_EQ(*listen_socket_->addressProvider().localAddress(), *envoy_connection->addressProvider().localAddress()); + EXPECT_EQ(64 * 1024, envoy_connection->max_inbound_header_list_size()); } void processValidChloPacketAndInitializeFilters(bool should_buffer) { @@ -189,6 +193,23 @@ class EnvoyQuicDispatcherTest : public QuicMultiVersionTest, read_filter->callbacks_->connection().setConnectionStats( {read_total, read_current, write_total, write_current, nullptr, nullptr}); }}); + EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager)); + EXPECT_CALL(filter_chain_manager, findFilterChain(_)) + .WillOnce(Invoke([this](const Network::ConnectionSocket& socket) { + switch (GetParam().second) { + case QuicVersionType::GquicQuicCrypto: + EXPECT_EQ("", socket.requestedApplicationProtocols()[0]); + break; + case QuicVersionType::GquicTls: + EXPECT_EQ("h3-T051", socket.requestedApplicationProtocols()[0]); + break; + case QuicVersionType::Iquic: + EXPECT_EQ("h3-29", socket.requestedApplicationProtocols()[0]); + break; + } + EXPECT_EQ("test.example.org", socket.requestedServerName()); + return &proof_source_->filterChain(); + })); EXPECT_CALL(proof_source_->filterChain(), networkFilterFactories()) .WillOnce(ReturnRef(filter_factory)); EXPECT_CALL(listener_config_, filterChainFactory()); @@ -197,12 +218,17 @@ class EnvoyQuicDispatcherTest : public QuicMultiVersionTest, const std::vector& filter_factories) { EXPECT_EQ(1u, filter_factories.size()); Server::Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); + dynamic_cast(connection) + .set_max_inbound_header_list_size(64 * 1024); return true; })); EXPECT_CALL(*read_filter, onNewConnection()) // Stop iteration to avoid calling getRead/WriteBuffer(). .WillOnce(Return(Network::FilterStatus::StopIteration)); - EXPECT_CALL(network_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); + if (!quicVersionUsesTls()) { + // The test utility can't generate 0-RTT packet for Quic TLS handshake yet. + EXPECT_CALL(network_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); + } processValidChloPacketAndCheckStatus(should_buffer); EXPECT_CALL(network_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); @@ -236,24 +262,62 @@ INSTANTIATE_TEST_SUITE_P(EnvoyQuicDispatcherTests, EnvoyQuicDispatcherTest, testing::ValuesIn(generateTestParam()), testParamsToString); TEST_P(EnvoyQuicDispatcherTest, CreateNewConnectionUponCHLO) { - if (quicVersionUsesTls()) { - // QUICHE doesn't support 0-RTT TLS1.3 handshake yet. - processValidChloPacketAndCheckStatus(false); - // Shutdown() to close the connection. - envoy_quic_dispatcher_.Shutdown(); - return; - } processValidChloPacketAndInitializeFilters(false); } -TEST_P(EnvoyQuicDispatcherTest, CreateNewConnectionUponBufferedCHLO) { - if (quicVersionUsesTls()) { - // QUICHE doesn't support 0-RTT TLS1.3 handshake yet. - processValidChloPacketAndCheckStatus(true); - // Shutdown() to close the connection. - envoy_quic_dispatcher_.Shutdown(); - return; +TEST_P(EnvoyQuicDispatcherTest, CloseConnectionDuringFilterInstallation) { + Network::MockFilterChainManager filter_chain_manager; + std::shared_ptr read_filter(new Network::MockReadFilter()); + Network::MockConnectionCallbacks network_connection_callbacks; + testing::StrictMock read_total; + testing::StrictMock read_current; + testing::StrictMock write_total; + testing::StrictMock write_current; + + std::vector filter_factory( + {[&](Network::FilterManager& filter_manager) { + filter_manager.addReadFilter(read_filter); + read_filter->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks); + read_filter->callbacks_->connection().setConnectionStats( + {read_total, read_current, write_total, write_current, nullptr, nullptr}); + // This will not close connection right away, but after it processes the first packet. + read_filter->callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + }}); + EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager)); + EXPECT_CALL(filter_chain_manager, findFilterChain(_)) + .WillOnce(Return(&proof_source_->filterChain())); + EXPECT_CALL(proof_source_->filterChain(), networkFilterFactories()) + .WillOnce(ReturnRef(filter_factory)); + EXPECT_CALL(listener_config_, filterChainFactory()); + EXPECT_CALL(listener_config_.filter_chain_factory_, createNetworkFilterChain(_, _)) + .WillOnce(Invoke([](Network::Connection& connection, + const std::vector& filter_factories) { + EXPECT_EQ(1u, filter_factories.size()); + Server::Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); + return true; + })); + EXPECT_CALL(*read_filter, onNewConnection()) + // Stop iteration to avoid calling getRead/WriteBuffer(). + .WillOnce(Return(Network::FilterStatus::StopIteration)); + + if (!quicVersionUsesTls()) { + EXPECT_CALL(network_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); } + + EXPECT_CALL(network_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); + quic::QuicSocketAddress peer_addr(version_ == Network::Address::IpVersion::v4 + ? quic::QuicIpAddress::Loopback4() + : quic::QuicIpAddress::Loopback6(), + 54321); + // Set QuicDispatcher::new_sessions_allowed_per_event_loop_ to + // |kNumSessionsToCreatePerLoopForTests| so that received CHLOs can be + // processed immediately. + envoy_quic_dispatcher_.ProcessBufferedChlos(kNumSessionsToCreatePerLoopForTests); + + processValidChloPacket(peer_addr); +} + +TEST_P(EnvoyQuicDispatcherTest, CreateNewConnectionUponBufferedCHLO) { processValidChloPacketAndInitializeFilters(true); } diff --git a/test/common/quic/envoy_quic_proof_source_test.cc b/test/common/quic/envoy_quic_proof_source_test.cc index 898678ff374fc..bd41d2991544a 100644 --- a/test/common/quic/envoy_quic_proof_source_test.cc +++ b/test/common/quic/envoy_quic_proof_source_test.cc @@ -155,7 +155,7 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { *connection_socket.addressProvider().remoteAddress()); EXPECT_EQ(Extensions::TransportSockets::TransportProtocolNames::get().Quic, connection_socket.detectedTransportProtocol()); - EXPECT_EQ("h2", connection_socket.requestedApplicationProtocols()[0]); + EXPECT_EQ("h3-29", connection_socket.requestedApplicationProtocols()[0]); return &filter_chain_; })); EXPECT_CALL(filter_chain_, transportSocketFactory()) @@ -244,7 +244,7 @@ TEST_F(EnvoyQuicProofSourceTest, GetProofFailNoCertConfig) { *connection_socket.addressProvider().remoteAddress()); EXPECT_EQ(Extensions::TransportSockets::TransportProtocolNames::get().Quic, connection_socket.detectedTransportProtocol()); - EXPECT_EQ("h2", connection_socket.requestedApplicationProtocols()[0]); + EXPECT_EQ("h3-29", connection_socket.requestedApplicationProtocols()[0]); return &filter_chain_; })); EXPECT_CALL(filter_chain_, transportSocketFactory()) diff --git a/test/common/quic/envoy_quic_server_session_test.cc b/test/common/quic/envoy_quic_server_session_test.cc index c2c93111a1ddf..fe441dbc25cbe 100644 --- a/test/common/quic/envoy_quic_server_session_test.cc +++ b/test/common/quic/envoy_quic_server_session_test.cc @@ -55,28 +55,6 @@ using testing::ReturnRef; namespace Envoy { namespace Quic { -class TestEnvoyQuicServerConnection : public EnvoyQuicServerConnection { -public: - TestEnvoyQuicServerConnection(quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, - quic::QuicPacketWriter& writer, - const quic::ParsedQuicVersionVector& supported_versions, - Network::Socket& listen_socket) - : EnvoyQuicServerConnection(quic::test::TestConnectionId(), - quic::QuicSocketAddress(quic::QuicIpAddress::Any4(), 12345), - quic::QuicSocketAddress(quic::QuicIpAddress::Loopback4(), 12345), - helper, alarm_factory, &writer, /*owns_writer=*/false, - supported_versions, listen_socket) {} - - Network::Connection::ConnectionStats& connectionStats() const { - return EnvoyQuicConnection::connectionStats(); - } - - MOCK_METHOD(void, SendConnectionClosePacket, - (quic::QuicErrorCode, quic::QuicIetfTransportErrorCodes, const std::string&)); - MOCK_METHOD(bool, SendControlFrame, (const quic::QuicFrame& frame)); -}; - // Derive to have simpler priority mechanism. class TestEnvoyQuicServerSession : public EnvoyQuicServerSession { public: @@ -154,16 +132,15 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { SetQuicReloadableFlag(quic_disable_version_draft_29, !GetParam()); return quic::ParsedVersionOfIndex(quic::CurrentSupportedVersions(), 0); }()), - quic_connection_(new TestEnvoyQuicServerConnection( + quic_connection_(new MockEnvoyQuicServerConnection( connection_helper_, alarm_factory_, writer_, quic_version_, *listener_config_.socket_)), crypto_config_(quic::QuicCryptoServerConfig::TESTING, quic::QuicRandom::GetInstance(), std::make_unique(), quic::KeyExchangeSource::Default()), envoy_quic_session_(quic_config_, quic_version_, - std::unique_ptr(quic_connection_), + std::unique_ptr(quic_connection_), /*visitor=*/nullptr, &crypto_stream_helper_, &crypto_config_, &compressed_certs_cache_, *dispatcher_, - /*send_buffer_limit*/ quic::kDefaultFlowControlSendWindow * 1.5, - listener_config_), + /*send_buffer_limit*/ quic::kDefaultFlowControlSendWindow * 1.5), stats_({ALL_HTTP3_CODEC_STATS( POOL_COUNTER_PREFIX(listener_config_.listenerScope(), "http3."), POOL_GAUGE_PREFIX(listener_config_.listenerScope(), "http3."))}) { @@ -270,7 +247,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { quic::ParsedQuicVersionVector quic_version_; testing::NiceMock writer_; testing::NiceMock listener_config_; - TestEnvoyQuicServerConnection* quic_connection_; + MockEnvoyQuicServerConnection* quic_connection_; quic::QuicConfig quic_config_; quic::QuicCryptoServerConfig crypto_config_; testing::NiceMock crypto_stream_helper_; @@ -800,29 +777,8 @@ TEST_P(EnvoyQuicServerSessionTest, GoAway) { http_connection_->goAway(); } -TEST_P(EnvoyQuicServerSessionTest, InitializeFilterChain) { - read_filter_ = std::make_shared(); - Network::MockFilterChain filter_chain; - crypto_stream_->setProofSourceDetails( - std::make_unique(filter_chain)); - std::vector filter_factory{[this]( - Network::FilterManager& filter_manager) { - filter_manager.addReadFilter(read_filter_); - read_filter_->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks_); - read_filter_->callbacks_->connection().setConnectionStats( - {read_total_, read_current_, write_total_, write_current_, nullptr, nullptr}); - }}; - EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); - EXPECT_CALL(*read_filter_, onNewConnection()) - // Stop iteration to avoid calling getRead/WriteBuffer(). - .WillOnce(Return(Network::FilterStatus::StopIteration)); - EXPECT_CALL(listener_config_.filter_chain_factory_, createNetworkFilterChain(_, _)) - .WillOnce(Invoke([](Network::Connection& connection, - const std::vector& filter_factories) { - EXPECT_EQ(1u, filter_factories.size()); - Server::Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); - return true; - })); +TEST_P(EnvoyQuicServerSessionTest, ConnectedAfterHandshake) { + installReadFilter(); EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::Connected)); if (!quic_version_[0].UsesTls()) { envoy_quic_session_.SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); diff --git a/test/common/quic/envoy_quic_server_stream_test.cc b/test/common/quic/envoy_quic_server_stream_test.cc index 3ec95ee9e2c67..d9a5643f6f0a7 100644 --- a/test/common/quic/envoy_quic_server_stream_test.cc +++ b/test/common/quic/envoy_quic_server_stream_test.cc @@ -50,11 +50,8 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), POOL_GAUGE(listener_config_.listenerScope()), POOL_HISTOGRAM(listener_config_.listenerScope()))}), - quic_connection_(quic::test::TestConnectionId(), - quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 123), - quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), - connection_helper_, alarm_factory_, &writer_, - /*owns_writer=*/false, {quic_version_}, *listener_config_.socket_), + quic_connection_(connection_helper_, alarm_factory_, writer_, + quic::ParsedQuicVersionVector{quic_version_}, *listener_config_.socket_), quic_session_(quic_config_, {quic_version_}, &quic_connection_, *dispatcher_, quic_config_.GetInitialStreamFlowControlWindowToSend() * 2), stream_id_(VersionUsesHttp3(quic_version_.transport_version) ? 4u : 5u), @@ -111,6 +108,8 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { EXPECT_CALL(quic_session_, MaybeSendRstStreamFrame(_, _, _)).Times(testing::AtMost(1u)); EXPECT_CALL(quic_session_, MaybeSendStopSendingFrame(_, quic::QUIC_STREAM_NO_ERROR)) .Times(testing::AtMost(1u)); + EXPECT_CALL(quic_connection_, + SendConnectionClosePacket(_, quic::NO_IETF_QUIC_ERROR, "Closed by application")); quic_session_.close(Network::ConnectionCloseType::NoFlush); } } @@ -166,7 +165,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic::QuicConfig quic_config_; testing::NiceMock listener_config_; Server::ListenerStats listener_stats_; - EnvoyQuicServerConnection quic_connection_; + testing::NiceMock quic_connection_; MockEnvoyQuicSession quic_session_; quic::QuicStreamId stream_id_; Http::Http3::CodecStats stats_; @@ -605,6 +604,8 @@ TEST_P(EnvoyQuicServerStreamTest, ConnectionCloseDuringEncoding) { receiveRequest(request_body_, true, request_body_.size() * 2); quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/false); std::string response(16 * 1024 + 1, 'a'); + EXPECT_CALL(quic_connection_, + SendConnectionClosePacket(_, quic::NO_IETF_QUIC_ERROR, "Closed in WriteHeaders")); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) .Times(testing::AtLeast(1u)) .WillRepeatedly( @@ -645,6 +646,8 @@ TEST_P(EnvoyQuicServerStreamTest, ConnectionCloseDuringEncoding) { // onResetStream() callbacks. TEST_P(EnvoyQuicServerStreamTest, ConnectionCloseAfterEndStreamEncoded) { receiveRequest(request_body_, true, request_body_.size() * 2); + EXPECT_CALL(quic_connection_, + SendConnectionClosePacket(_, quic::NO_IETF_QUIC_ERROR, "Closed in WriteHeaders")); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) .WillOnce( Invoke([this](quic::QuicStreamId, size_t, quic::QuicStreamOffset, diff --git a/test/common/quic/test_utils.h b/test/common/quic/test_utils.h index 4650eeb9f6dd7..0da7ee95ba927 100644 --- a/test/common/quic/test_utils.h +++ b/test/common/quic/test_utils.h @@ -1,5 +1,7 @@ #pragma once +#include "common/quic/envoy_quic_client_connection.h" +#include "common/quic/envoy_quic_server_connection.h" #include "common/quic/quic_filter_manager_connection_impl.h" #if defined(__GNUC__) @@ -28,17 +30,58 @@ namespace Envoy { namespace Quic { +class MockEnvoyQuicServerConnection : public EnvoyQuicServerConnection { +public: + MockEnvoyQuicServerConnection(quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, + quic::QuicPacketWriter& writer, + const quic::ParsedQuicVersionVector& supported_versions, + Network::Socket& listen_socket) + : MockEnvoyQuicServerConnection( + helper, alarm_factory, writer, + quic::QuicSocketAddress(quic::QuicIpAddress::Any4(), 12345), + quic::QuicSocketAddress(quic::QuicIpAddress::Loopback4(), 12345), supported_versions, + listen_socket) {} + + MockEnvoyQuicServerConnection(quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, + quic::QuicPacketWriter& writer, + quic::QuicSocketAddress self_address, + quic::QuicSocketAddress peer_address, + const quic::ParsedQuicVersionVector& supported_versions, + Network::Socket& listen_socket) + : EnvoyQuicServerConnection( + quic::test::TestConnectionId(), self_address, peer_address, helper, alarm_factory, + &writer, /*owns_writer=*/false, supported_versions, + createServerConnectionSocket(listen_socket.ioHandle(), self_address, peer_address, + "example.com", "h3-29")) {} + + Network::Connection::ConnectionStats& connectionStats() const { + return QuicNetworkConnection::connectionStats(); + } + + MOCK_METHOD(void, SendConnectionClosePacket, + (quic::QuicErrorCode, quic::QuicIetfTransportErrorCodes, const std::string&)); + MOCK_METHOD(bool, SendControlFrame, (const quic::QuicFrame& frame)); +}; + class MockEnvoyQuicSession : public quic::QuicSpdySession, public QuicFilterManagerConnectionImpl { public: MockEnvoyQuicSession(const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, - EnvoyQuicConnection* connection, Event::Dispatcher& dispatcher, + EnvoyQuicServerConnection* connection, Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) : quic::QuicSpdySession(connection, /*visitor=*/nullptr, config, supported_versions), - QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit) { + QuicFilterManagerConnectionImpl(*connection, connection->connection_id(), dispatcher, + send_buffer_limit) { crypto_stream_ = std::make_unique(this); } + void Initialize() override { + quic::QuicSpdySession::Initialize(); + initialized_ = true; + } + // From QuicSession. MOCK_METHOD(quic::QuicSpdyStream*, CreateIncomingStream, (quic::QuicStreamId id)); MOCK_METHOD(quic::QuicSpdyStream*, CreateIncomingStream, (quic::PendingStream * pending)); @@ -70,6 +113,10 @@ class MockEnvoyQuicSession : public quic::QuicSpdySession, public QuicFilterMana protected: bool hasDataToWrite() override { return HasDataToWrite(); } + const quic::QuicConnection* quicConnection() const override { + return initialized_ ? connection() : nullptr; + } + quic::QuicConnection* quicConnection() override { return initialized_ ? connection() : nullptr; } private: std::unique_ptr crypto_stream_; @@ -80,14 +127,20 @@ class MockEnvoyQuicClientSession : public quic::QuicSpdyClientSession, public: MockEnvoyQuicClientSession(const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, - EnvoyQuicConnection* connection, Event::Dispatcher& dispatcher, + EnvoyQuicClientConnection* connection, Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) : quic::QuicSpdyClientSession(config, supported_versions, connection, quic::QuicServerId("example.com", 443, false), &crypto_config_, nullptr), - QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), + QuicFilterManagerConnectionImpl(*connection, connection->connection_id(), dispatcher, + send_buffer_limit), crypto_config_(quic::test::crypto_test_utils::ProofVerifierForTesting()) {} + void Initialize() override { + quic::QuicSpdyClientSession::Initialize(); + initialized_ = true; + } + // From QuicSession. MOCK_METHOD(quic::QuicSpdyClientStream*, CreateIncomingStream, (quic::QuicStreamId id)); MOCK_METHOD(quic::QuicSpdyClientStream*, CreateIncomingStream, (quic::PendingStream * pending)); @@ -110,6 +163,10 @@ class MockEnvoyQuicClientSession : public quic::QuicSpdyClientSession, protected: bool hasDataToWrite() override { return HasDataToWrite(); } + const quic::QuicConnection* quicConnection() const override { + return initialized_ ? connection() : nullptr; + } + quic::QuicConnection* quicConnection() override { return initialized_ ? connection() : nullptr; } private: quic::QuicCryptoClientConfig crypto_config_; diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index f0a67fe7a6cb7..94ab19fd19694 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -589,6 +589,7 @@ void FakeUpstream::threadRoutine() { { absl::MutexLock lock(&lock_); new_connections_.clear(); + quic_connections_.clear(); consumed_connections_.clear(); } } diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index ce92bcb06f5ee..04bccb733b66a 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -805,12 +805,12 @@ class FakeUpstream : Logger::Loggable, Event::DispatcherPtr dispatcher_; Network::ConnectionHandlerPtr handler_; std::list new_connections_ ABSL_GUARDED_BY(lock_); - std::list quic_connections_ ABSL_GUARDED_BY(lock_); // When a QueuedConnectionWrapper is popped from new_connections_, ownership is transferred to // consumed_connections_. This allows later the Connection destruction (when the FakeUpstream is // deleted) on the same thread that allocated the connection. std::list consumed_connections_ ABSL_GUARDED_BY(lock_); + std::list quic_connections_ ABSL_GUARDED_BY(lock_); const FakeUpstreamConfig config_; bool read_disable_on_new_connection_; const bool enable_half_close_; diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 6228dd40b0a07..03cd14677936e 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -771,11 +771,6 @@ TEST_P(DownstreamProtocolIntegrationTest, RetryAttemptCountHeader) { // The retry priority will always target P1, which would otherwise never be hit due to P0 being // healthy. TEST_P(DownstreamProtocolIntegrationTest, RetryPriority) { - if (upstreamProtocol() == FakeHttpConnection::Type::HTTP2 && - downstreamProtocol() == Http::CodecClient::Type::HTTP3) { - // TODO(alyssawilk) investigate why this combination doesn't work. - return; - } EXCLUDE_UPSTREAM_HTTP3; // Timed out waiting for new stream. const Upstream::HealthyLoad healthy_priority_load({0u, 100u}); const Upstream::DegradedLoad degraded_priority_load({0u, 100u}); @@ -1325,9 +1320,18 @@ TEST_P(ProtocolIntegrationTest, MissingStatus) { // Validate that lots of tiny cookies doesn't cause a DoS (single cookie header). TEST_P(DownstreamProtocolIntegrationTest, LargeCookieParsingConcatenated) { - // TODO(danzh) re-enable this test after quic headers size become configurable. - EXCLUDE_DOWNSTREAM_HTTP3 - EXCLUDE_UPSTREAM_HTTP3; // QUIC_STREAM_EXCESSIVE_LOAD + if (downstreamProtocol() == Http::CodecClient::Type::HTTP3) { + // QUICHE Qpack splits concatenated cookies into crumbs to increase + // compression ratio. On the receiver side, the total size of these crumbs + // may be larger than coalesced cookie headers. Increase the max request + // header size to avoid QUIC_HEADERS_TOO_LARGE stream error. + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { hcm.mutable_max_request_headers_kb()->set_value(96); }); + } + if (upstreamProtocol() == FakeHttpConnection::Type::HTTP3) { + setMaxRequestHeadersKb(96); + } initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -1350,9 +1354,6 @@ TEST_P(DownstreamProtocolIntegrationTest, LargeCookieParsingConcatenated) { // Validate that lots of tiny cookies doesn't cause a DoS (many cookie headers). TEST_P(DownstreamProtocolIntegrationTest, LargeCookieParsingMany) { - // TODO(danzh) re-enable this test after quic headers size become configurable. - EXCLUDE_DOWNSTREAM_HTTP3 - EXCLUDE_UPSTREAM_HTTP3; // QUIC_STREAM_EXCESSIVE_LOAD // Set header count limit to 2010. uint32_t max_count = 2010; config_helper_.addConfigModifier( @@ -1561,9 +1562,6 @@ TEST_P(DownstreamProtocolIntegrationTest, LargeRequestUrlRejected) { } TEST_P(DownstreamProtocolIntegrationTest, LargeRequestUrlAccepted) { - // TODO(danzh) re-enable this test after quic headers size become configurable. - EXCLUDE_DOWNSTREAM_HTTP3 - EXCLUDE_UPSTREAM_HTTP3; // requires configurable header limits. // Send one 95 kB URL with limit 96 kB headers. testLargeRequestUrl(95, 96); } @@ -1574,22 +1572,22 @@ TEST_P(DownstreamProtocolIntegrationTest, LargeRequestHeadersRejected) { } TEST_P(DownstreamProtocolIntegrationTest, VeryLargeRequestHeadersRejected) { - EXCLUDE_DOWNSTREAM_HTTP3 + EXCLUDE_DOWNSTREAM_HTTP3; EXCLUDE_UPSTREAM_HTTP3; // Send one very large 2048 kB (2 MB) header with limit 1024 kB (1 MB) and 100 headers. testLargeRequestHeaders(2048, 1, 1024, 100); } TEST_P(DownstreamProtocolIntegrationTest, LargeRequestHeadersAccepted) { - EXCLUDE_DOWNSTREAM_HTTP3 - EXCLUDE_UPSTREAM_HTTP3; // requires configurable header limits. // Send one 100 kB header with limit 8192 kB (8 MB) and 100 headers. testLargeRequestHeaders(100, 1, 8192, 100); } TEST_P(DownstreamProtocolIntegrationTest, ManyLargeRequestHeadersAccepted) { - EXCLUDE_DOWNSTREAM_HTTP3 - EXCLUDE_UPSTREAM_HTTP3; + // Fail under TSAN. Quic blackhole detection fired and closed the connection with + // QUIC_TOO_MANY_RTOS while waiting for upstream finishing transferring the large header. Observed + // long event loop. + EXCLUDE_DOWNSTREAM_HTTP3; // Send 70 headers each of size 100 kB with limit 8192 kB (8 MB) and 100 headers. testLargeRequestHeaders(100, 70, 8192, 100); } @@ -1609,8 +1607,7 @@ TEST_P(DownstreamProtocolIntegrationTest, ManyRequestHeadersAccepted) { TEST_P(DownstreamProtocolIntegrationTest, ManyRequestTrailersRejected) { // QUICHE doesn't limit number of headers. EXCLUDE_DOWNSTREAM_HTTP3 - EXCLUDE_UPSTREAM_HTTP3; // CI asan use-after-free - // Default header (and trailer) count limit is 100. + // The default configured header (and trailer) count limit is 100. config_helper_.addConfigModifier(setEnableDownstreamTrailersHttp1()); config_helper_.addConfigModifier(setEnableUpstreamTrailersHttp1()); Http::TestRequestTrailerMapImpl request_trailers; @@ -1637,7 +1634,6 @@ TEST_P(DownstreamProtocolIntegrationTest, ManyRequestTrailersRejected) { } TEST_P(DownstreamProtocolIntegrationTest, ManyRequestTrailersAccepted) { - EXCLUDE_UPSTREAM_HTTP3; // assert failure: validHeaderString // Set header (and trailer) count limit to 200. uint32_t max_count = 200; config_helper_.addConfigModifier( @@ -1670,25 +1666,16 @@ TEST_P(DownstreamProtocolIntegrationTest, ManyRequestTrailersAccepted) { // This test uses an Http::HeaderMapImpl instead of an Http::TestHeaderMapImpl to avoid // time-consuming byte size validations that will cause this test to timeout. TEST_P(DownstreamProtocolIntegrationTest, ManyRequestHeadersTimeout) { - // TODO(danzh) re-enable this test after quic headers size become configurable. - EXCLUDE_DOWNSTREAM_HTTP3 - EXCLUDE_UPSTREAM_HTTP3; // Set timeout for 5 seconds, and ensure that a request with 10k+ headers can be sent. testManyRequestHeaders(std::chrono::milliseconds(5000)); } TEST_P(DownstreamProtocolIntegrationTest, LargeRequestTrailersAccepted) { - // TODO(danzh) re-enable this test after quic headers size become configurable. - EXCLUDE_DOWNSTREAM_HTTP3 - EXCLUDE_UPSTREAM_HTTP3; config_helper_.addConfigModifier(setEnableDownstreamTrailersHttp1()); testLargeRequestTrailers(60, 96); } TEST_P(DownstreamProtocolIntegrationTest, LargeRequestTrailersRejected) { - // TODO(danzh) investigate why it failed for H3. - EXCLUDE_DOWNSTREAM_HTTP3 - EXCLUDE_UPSTREAM_HTTP3; config_helper_.addConfigModifier(setEnableDownstreamTrailersHttp1()); testLargeRequestTrailers(66, 60); } @@ -1696,10 +1683,6 @@ TEST_P(DownstreamProtocolIntegrationTest, LargeRequestTrailersRejected) { // This test uses an Http::HeaderMapImpl instead of an Http::TestHeaderMapImpl to avoid // time-consuming byte size verification that will cause this test to timeout. TEST_P(DownstreamProtocolIntegrationTest, ManyTrailerHeaders) { - // Enable after setting QUICHE max_inbound_header_list_size_ from HCM - // config. - EXCLUDE_DOWNSTREAM_HTTP3 - EXCLUDE_UPSTREAM_HTTP3; setMaxRequestHeadersKb(96); setMaxRequestHeadersCount(20005); diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index d1eb590cd64e9..5aeef19daf495 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -123,7 +123,6 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers quic_config_, supported_versions_, std::move(connection), persistent_info.server_id_, persistent_info.crypto_config_.get(), &push_promise_index_, *dispatcher_, /*send_buffer_limit=*/2 * Http2::Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE); - session->Initialize(); return session; } diff --git a/test/integration/quic_protocol_integration_test.cc b/test/integration/quic_protocol_integration_test.cc index 4d94b6783cadb..197eb3761c7f2 100644 --- a/test/integration/quic_protocol_integration_test.cc +++ b/test/integration/quic_protocol_integration_test.cc @@ -2,13 +2,13 @@ namespace Envoy { -// These will run with HTTP/3 downstream, and Http and HTTP/2 upstream. -INSTANTIATE_TEST_SUITE_P(Protocols, DownstreamProtocolIntegrationTest, +// These will run with HTTP/3 downstream, and Http upstream. +INSTANTIATE_TEST_SUITE_P(DownstreamProtocols, DownstreamProtocolIntegrationTest, testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( - {Http::CodecClient::Type::HTTP3}, - {FakeHttpConnection::Type::HTTP1, FakeHttpConnection::Type::HTTP2})), + {Http::CodecClient::Type::HTTP3}, {FakeHttpConnection::Type::HTTP1})), HttpProtocolIntegrationTest::protocolTestParamsToString); +// These will run with HTTP/3 downstream, and Http and HTTP/2 upstream. INSTANTIATE_TEST_SUITE_P(DownstreamProtocols, ProtocolIntegrationTest, testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( {Http::CodecClient::Type::HTTP3}, diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 52b0f16228c8e..16b5fac738701 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -273,6 +273,7 @@ RPC RSA RST RTDS +RTOS RTTI RUNDIR RW From 9e263d8d636fb0b52da99a524b906cfc62190ae0 Mon Sep 17 00:00:00 2001 From: danzh Date: Thu, 29 Apr 2021 09:29:57 -0400 Subject: [PATCH 109/209] quiche: handle stream blockage during decodeHeaders and decodeTrailers (#16128) Commit Message: use posted callback to block/unblock quic stream in EnvoyQuicStream::readDisable() with weak_ptr to stream to handle stream life time issue. Additional Description: currently if readDisabled() is called in decodeHeaders|Trailers(), the stream will be blocked right away which breaks the assumption QUICHE has that a stream shouldn't be blocked during OnInitialHeadersComplete() and OnTrailingHeadersComplete(). This change makes the stream state change completely outside of QUICHE call stack. (The unblocking is already outside of QUICHE call stack in existing implementation.) Also simplify the blockage state change logic. Risk Level: low Testing: added more unit tests and enabled Http2UpstreamIntegrationTest::ManyLargeSimultaneousRequestWithBufferLimits which was flaky. Part of #2557 #14829 Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- .../common/quic/envoy_quic_client_stream.cc | 21 ++-- source/common/quic/envoy_quic_client_stream.h | 2 +- .../common/quic/envoy_quic_server_stream.cc | 23 ++-- source/common/quic/envoy_quic_server_stream.h | 2 +- source/common/quic/envoy_quic_stream.h | 60 ++++----- source/docs/quiche_integration.md | 2 +- .../quic/envoy_quic_client_stream_test.cc | 29 +++++ .../quic/envoy_quic_server_stream_test.cc | 118 ++++++++++++++---- test/common/quic/test_utils.h | 1 + .../multiplexed_upstream_integration_test.cc | 1 - .../integration/quic_http_integration_test.cc | 1 + 11 files changed, 164 insertions(+), 96 deletions(-) diff --git a/source/common/quic/envoy_quic_client_stream.cc b/source/common/quic/envoy_quic_client_stream.cc index 8c503832d816a..b107850ee230b 100644 --- a/source/common/quic/envoy_quic_client_stream.cc +++ b/source/common/quic/envoy_quic_client_stream.cc @@ -113,11 +113,16 @@ void EnvoyQuicClientStream::resetStream(Http::StreamResetReason reason) { Reset(envoyResetReasonToQuicRstError(reason)); } -void EnvoyQuicClientStream::switchStreamBlockState(bool should_block) { - if (should_block) { +void EnvoyQuicClientStream::switchStreamBlockState() { + // From when the callback got scheduled till now, readDisable() might have blocked and unblocked + // the stream multiple times, but those actions haven't taken any effect yet, and only the last + // state of read_disable_counter_ determines whether to unblock or block the quic stream. Unlike + // Envoy readDisable() the quic stream gets blocked/unblocked based on the most recent call. So a + // stream will be blocked upon SetBlockedUntilFlush() no matter how many times SetUnblocked() was + // called before, and vice versa. + if (read_disable_counter_ > 0) { sequencer()->SetBlockedUntilFlush(); } else { - ASSERT(read_disable_counter_ == 0, "readDisable called in between."); sequencer()->SetUnblocked(); } } @@ -176,12 +181,9 @@ void EnvoyQuicClientStream::OnInitialHeadersComplete(bool fin, size_t frame_len, void EnvoyQuicClientStream::OnBodyAvailable() { ASSERT(FinishedReadingHeaders()); - ASSERT(read_disable_counter_ == 0); - ASSERT(!in_decode_data_callstack_); if (read_side_closed()) { return; } - in_decode_data_callstack_ = true; Buffer::InstancePtr buffer = std::make_unique(); // TODO(danzh): check Envoy per stream buffer limit. @@ -210,12 +212,6 @@ void EnvoyQuicClientStream::OnBodyAvailable() { } if (!sequencer()->IsClosed() || read_side_closed()) { - in_decode_data_callstack_ = false; - if (read_disable_counter_ > 0) { - // If readDisable() was ever called during decodeData() and it meant to disable - // reading from downstream, the call must have been deferred. Call it now. - switchStreamBlockState(true); - } return; } @@ -224,7 +220,6 @@ void EnvoyQuicClientStream::OnBodyAvailable() { maybeDecodeTrailers(); OnFinRead(); - in_decode_data_callstack_ = false; } void EnvoyQuicClientStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, diff --git a/source/common/quic/envoy_quic_client_stream.h b/source/common/quic/envoy_quic_client_stream.h index 73b162feabe85..48e4940b53acf 100644 --- a/source/common/quic/envoy_quic_client_stream.h +++ b/source/common/quic/envoy_quic_client_stream.h @@ -59,7 +59,7 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, protected: // EnvoyQuicStream - void switchStreamBlockState(bool should_block) override; + void switchStreamBlockState() override; uint32_t streamId() override; Network::Connection* connection() override; diff --git a/source/common/quic/envoy_quic_server_stream.cc b/source/common/quic/envoy_quic_server_stream.cc index ef37d6b53df1a..e6a2197a3be4a 100644 --- a/source/common/quic/envoy_quic_server_stream.cc +++ b/source/common/quic/envoy_quic_server_stream.cc @@ -128,13 +128,16 @@ void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { } } -void EnvoyQuicServerStream::switchStreamBlockState(bool should_block) { - ASSERT(FinishedReadingHeaders(), - "Upperstream buffer limit is reached before request body is delivered."); - if (should_block) { +void EnvoyQuicServerStream::switchStreamBlockState() { + // From when the callback got scheduled till now, readDisable() might have blocked and unblocked + // the stream multiple times, but those actions haven't taken any effect yet, and only the last + // state of read_disable_counter_ determines whether to unblock or block the quic stream. + // Unlike Envoy readDisable() the quic stream gets blocked/unblocked based on the most recent + // call. So a stream will be blocked upon SetBlockedUntilFlush() no matter how many times + // SetUnblocked() was called before, and vice versa. + if (read_disable_counter_ > 0) { sequencer()->SetBlockedUntilFlush(); } else { - ASSERT(read_disable_counter_ == 0, "readDisable called in between."); sequencer()->SetUnblocked(); } } @@ -174,12 +177,9 @@ void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, void EnvoyQuicServerStream::OnBodyAvailable() { ASSERT(FinishedReadingHeaders()); - ASSERT(read_disable_counter_ == 0); - ASSERT(!in_decode_data_callstack_); if (read_side_closed()) { return; } - in_decode_data_callstack_ = true; Buffer::InstancePtr buffer = std::make_unique(); // TODO(danzh): check Envoy per stream buffer limit. @@ -209,12 +209,6 @@ void EnvoyQuicServerStream::OnBodyAvailable() { } if (!sequencer()->IsClosed() || read_side_closed()) { - in_decode_data_callstack_ = false; - if (read_disable_counter_ > 0) { - // If readDisable() was ever called during decodeData() and it meant to disable - // reading from downstream, the call must have been deferred. Call it now. - switchStreamBlockState(true); - } return; } @@ -223,7 +217,6 @@ void EnvoyQuicServerStream::OnBodyAvailable() { maybeDecodeTrailers(); OnFinRead(); - in_decode_data_callstack_ = false; } void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, diff --git a/source/common/quic/envoy_quic_server_stream.h b/source/common/quic/envoy_quic_server_stream.h index e9059861c5e28..f86857cc800df 100644 --- a/source/common/quic/envoy_quic_server_stream.h +++ b/source/common/quic/envoy_quic_server_stream.h @@ -72,7 +72,7 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, protected: // EnvoyQuicStream - void switchStreamBlockState(bool should_block) override; + void switchStreamBlockState() override; uint32_t streamId() override; Network::Connection* connection() override; diff --git a/source/common/quic/envoy_quic_stream.h b/source/common/quic/envoy_quic_stream.h index 53e2623291b07..bc8c9d0e7d986 100644 --- a/source/common/quic/envoy_quic_stream.h +++ b/source/common/quic/envoy_quic_stream.h @@ -47,7 +47,10 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, : stats_(stats), http3_options_(http3_options), send_buffer_simulation_(buffer_limit / 2, buffer_limit, std::move(below_low_watermark), std::move(above_high_watermark), ENVOY_LOGGER()), - filter_manager_connection_(filter_manager_connection) {} + filter_manager_connection_(filter_manager_connection), + async_stream_blockage_change_( + filter_manager_connection.dispatcher().createSchedulableCallback( + [this]() { switchStreamBlockState(); })) {} ~EnvoyQuicStream() override = default; @@ -70,31 +73,15 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, } } - if (status_changed && !in_decode_data_callstack_) { - // Avoid calling this while decoding data because transient disabling and - // enabling reading may trigger another decoding data inside the - // callstack which messes up stream state. - if (disable) { - // Block QUIC stream right away. And if there are queued switching - // state callback, update the desired state as well. - switchStreamBlockState(true); - if (unblock_posted_) { - should_block_ = true; - } - } else { - should_block_ = false; - if (!unblock_posted_) { - // If this is the first time unblocking stream is desired, post a - // callback to do it in next loop. This is because unblocking QUIC - // stream can lead to immediate upstream encoding. - unblock_posted_ = true; - connection()->dispatcher().post([this] { - unblock_posted_ = false; - switchStreamBlockState(should_block_); - }); - } - } + if (!status_changed) { + return; } + + // If the status transiently changed from unblocked to blocked and then unblocked, the quic + // stream will be spuriously unblocked and call OnDataAvailable(). This call shouldn't take any + // effect because any available data should have been processed already upon arrival or they + // were blocked by some condition other than flow control, i.e. Qpack decoding. + async_stream_blockage_change_->scheduleCallbackNextIteration(); } void addCallbacks(Http::StreamCallbacks& callbacks) override { @@ -139,7 +126,7 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, absl::string_view responseDetails() override { return details_; } protected: - virtual void switchStreamBlockState(bool should_block) PURE; + virtual void switchStreamBlockState() PURE; // Needed for ENVOY_STREAM_LOG. virtual uint32_t streamId() PURE; @@ -149,10 +136,12 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, // notified more than once about end of stream. So once this is true, no need // to set it in the callback to Envoy stream any more. bool end_stream_decoded_{false}; + // The latest state a QUIC stream blockage state change callback should look at. As + // more readDisable() calls may happen between the callback is posted and it's + // executed, the stream might be unblocked and blocked several times. If this + // counter is 0, the callback should unblock the stream. Otherwise it should + // block the stream. uint32_t read_disable_counter_{0u}; - // If true, switchStreamBlockState() should be deferred till this variable - // becomes false. - bool in_decode_data_callstack_{false}; Http::Http3::CodecStats& stats_; const envoy::config::core::v3::Http3ProtocolOptions& http3_options_; @@ -169,16 +158,11 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, // directly and buffers them in filters if needed. Itself doesn't buffer request data. EnvoyQuicSimulatedWatermarkBuffer send_buffer_simulation_; - // True if there is posted unblocking QUIC stream callback. There should be - // only one such callback no matter how many times readDisable() is called. - bool unblock_posted_{false}; - // The latest state an unblocking QUIC stream callback should look at. As - // more readDisable() calls may happen between the callback is posted and it's - // executed, the stream might be unblocked and blocked several times. Only the - // latest desired state should be considered by the callback. - bool should_block_{false}; - QuicFilterManagerConnectionImpl& filter_manager_connection_; + // Used to block or unblock stream in the next event loop. QUICHE doesn't like stream blockage + // state change in its own call stack. And Envoy upstream doesn't like quic stream to be unblocked + // in its callstack either because the stream will push data right away. + Event::SchedulableCallbackPtr async_stream_blockage_change_; }; } // namespace Quic diff --git a/source/docs/quiche_integration.md b/source/docs/quiche_integration.md index fef0ee6aee9b5..f0164ad0fc29e 100644 --- a/source/docs/quiche_integration.md +++ b/source/docs/quiche_integration.md @@ -20,7 +20,7 @@ The HCM will call encoder's encodeHeaders() to write response headers, and then All arrived out-of-order data is buffered in QUICHE stream. This buffer is capped by max stream flow control window in QUICHE which is 16MB. Once bytes are put in sequence and ready to be used, OnBodyDataAvailable() is called. The stream implementation overrides this call and calls StreamDecoder::decodeData() in it. Request and response body are buffered in each L7 filter if desired, and the stream itself doesn't buffer any of them unless set as read blocked. -When upstream or any L7 filter reaches its buffer limit, it will call Http::Stream::readDisable() with false to set QUIC stream to be read blocked. In this state, even if more request/response body is available to be delivered, OnBodyDataAvailable() will not be called. As a result, downstream flow control will not shift as no data will be consumed. As both filters and upstream buffers can call readDisable(), each stream has a counter of how many times the HCM blocks the stream. When the counter is cleared, the stream will set its state to unblocked and thus deliver any new and existing available data buffered in the QUICHE stream. +When upstream or any L7 filter reaches its buffer limit, it will call Http::Stream::readDisable() with false to set QUIC stream to be read blocked. This takes effect in the next event loop. Once the stream is read blocked, even if more request/response body is available to be delivered, OnBodyDataAvailable() will not be called. As a result, downstream flow control will not shift as no data will be consumed. As both filters and upstream buffers can call readDisable(), each stream has a counter of how many times the HCM blocks the stream. When the counter is cleared, the stream will set its state to unblocked also in the next event loop and thus deliver any new and existing available data buffered in the QUICHE stream object. readDisable() can be called to block and unblock stream multiple times within one event loop, the stream blockage state is determined by the final state of the counter. #### Send buffer diff --git a/test/common/quic/envoy_quic_client_stream_test.cc b/test/common/quic/envoy_quic_client_stream_test.cc index a07b58dd36b30..3cda1218570aa 100644 --- a/test/common/quic/envoy_quic_client_stream_test.cc +++ b/test/common/quic/envoy_quic_client_stream_test.cc @@ -64,6 +64,7 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { void SetUp() override { quic_session_.Initialize(); + quic_connection_->setEnvoyConnection(quic_session_); setQuicConfigWithDefaultValues(quic_session_.config()); quic_session_.OnConfigNegotiated(); quic_connection_->setUpConnectionSocket(); @@ -576,5 +577,33 @@ TEST_P(EnvoyQuicClientStreamTest, CloseConnectionDuringDecodingTrailer) { } } +// Tests that posted stream block callback won't cause use-after-free crash. +TEST_P(EnvoyQuicClientStreamTest, ReadDisabledBeforeClose) { + const auto result = quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/true); + EXPECT_TRUE(result.ok()); + + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/!quic::VersionUsesHttp3( + quic_version_.transport_version))) + .WillOnce(Invoke([this](const Http::ResponseHeaderMapPtr& headers, bool) { + EXPECT_EQ("200", headers->getStatusValue()); + quic_stream_->readDisable(true); + })); + if (quic_version_.UsesHttp3()) { + EXPECT_CALL(stream_decoder_, decodeData(BufferStringEqual(""), /*end_stream=*/true)); + std::string payload = spdyHeaderToHttp3StreamPayload(spdy_response_headers_); + quic::QuicStreamFrame frame(stream_id_, true, 0, payload); + quic_stream_->OnStreamFrame(frame); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/true, response_headers_.uncompressed_header_bytes(), + response_headers_); + } + // Reset to close the stream. + EXPECT_CALL(stream_callbacks_, onResetStream(Http::StreamResetReason::LocalReset, _)); + quic_stream_->resetStream(Http::StreamResetReason::LocalReset); + EXPECT_EQ(1u, quic_session_.closed_streams()->size()); + quic_session_.closed_streams()->clear(); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); +} + } // namespace Quic } // namespace Envoy diff --git a/test/common/quic/envoy_quic_server_stream_test.cc b/test/common/quic/envoy_quic_server_stream_test.cc index d9a5643f6f0a7..b73e685f5c249 100644 --- a/test/common/quic/envoy_quic_server_stream_test.cc +++ b/test/common/quic/envoy_quic_server_stream_test.cc @@ -1,3 +1,4 @@ +#include #include #if defined(__GNUC__) @@ -94,13 +95,6 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { spdy_request_headers_[":authority"] = host_; spdy_request_headers_[":method"] = "POST"; spdy_request_headers_[":path"] = "/"; - - trailers_.OnHeaderBlockStart(); - trailers_.OnHeader("key1", "value1"); - // ":final-offset" is required and stripped off by quic. - trailers_.OnHeader(":final-offset", absl::StrCat("", request_body_.length())); - trailers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); - spdy_trailers_["key1"] = "value1"; } void TearDown() override { @@ -155,6 +149,23 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { return payload.length(); } + void receiveTrailers(size_t offset) { + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + spdy_trailers_["key1"] = "value1"; + std::string payload = spdyHeaderToHttp3StreamPayload(spdy_trailers_); + quic::QuicStreamFrame frame(stream_id_, true, offset, payload); + quic_stream_->OnStreamFrame(frame); + } else { + trailers_.OnHeaderBlockStart(); + trailers_.OnHeader("key1", "value1"); + // ":final-offset" is required and stripped off by quic. + trailers_.OnHeader(":final-offset", absl::StrCat("", offset)); + trailers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), + trailers_); + } + } + protected: Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; @@ -248,14 +259,7 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_EQ("value1", headers->get(key1)[0]->value().getStringView()); EXPECT_TRUE(headers->get(key2).empty()); })); - if (quic::VersionUsesHttp3(quic_version_.transport_version)) { - std::string payload = spdyHeaderToHttp3StreamPayload(spdy_trailers_); - quic::QuicStreamFrame frame(stream_id_, true, offset, payload); - quic_stream_->OnStreamFrame(frame); - } else { - quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), - trailers_); - } + receiveTrailers(offset); EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); } @@ -275,7 +279,7 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); // Trailer should be delivered to HCM later after body arrives. - quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + receiveTrailers(request_body_.length()); quic::QuicStreamFrame frame(stream_id_, false, 0, request_body_); EXPECT_CALL(stream_decoder_, decodeData(_, _)) @@ -329,11 +333,16 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponLargePost) { // Disable reading one more time. quic_stream_->readDisable(true); std::string second_part_request = bodyToStreamPayload("bbb"); - // Receiving more data shouldn't push the receiving pipe line as the stream - // should have been marked blocked. + // Receiving more data in the same event loop will push the receiving pipe line. + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(3u, buffer.length()); + EXPECT_EQ("bbb", buffer.toString()); + EXPECT_FALSE(finished_reading); + })); quic::QuicStreamFrame frame(stream_id_, false, payload_offset, second_part_request); - EXPECT_CALL(stream_decoder_, decodeData(_, _)).Times(0); quic_stream_->OnStreamFrame(frame); + payload_offset += second_part_request.length(); // Re-enable reading just once shouldn't unblock stream. quic_stream_->readDisable(false); @@ -341,26 +350,30 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponLargePost) { // This data frame should also be buffered. std::string last_part_request = bodyToStreamPayload("ccc"); - quic::QuicStreamFrame frame2(stream_id_, true, payload_offset + second_part_request.length(), - last_part_request); + quic::QuicStreamFrame frame2(stream_id_, false, payload_offset, last_part_request); quic_stream_->OnStreamFrame(frame2); + payload_offset += last_part_request.length(); + + // Trailers should also be buffered. + EXPECT_CALL(stream_decoder_, decodeTrailers_(_)).Times(0); + receiveTrailers(payload_offset); // Unblock stream now. The remaining data in the receiving buffer should be // pushed to upstream. EXPECT_CALL(stream_decoder_, decodeData(_, _)) .WillOnce(Invoke([](Buffer::Instance& buffer, bool finished_reading) { - std::string rest_request = "bbbccc"; - EXPECT_EQ(rest_request.size(), buffer.length()); - EXPECT_EQ(rest_request, buffer.toString()); - EXPECT_TRUE(finished_reading); + EXPECT_EQ(3u, buffer.length()); + EXPECT_EQ("ccc", buffer.toString()); + EXPECT_FALSE(finished_reading); })); + EXPECT_CALL(stream_decoder_, decodeTrailers_(_)); quic_stream_->readDisable(false); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); } -// Tests that ReadDisable() doesn't cause re-entry of OnBodyAvailable(). +// Tests that readDisable() doesn't cause re-entry of OnBodyAvailable(). TEST_P(EnvoyQuicServerStreamTest, ReadDisableAndReEnableImmediately) { EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) .WillOnce(Invoke([this](const Http::RequestHeaderMapPtr& headers, bool) { @@ -384,7 +397,9 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableAndReEnableImmediately) { std::string data = bodyToStreamPayload(payload); quic::QuicStreamFrame frame(stream_id_, false, 0, data); quic_stream_->OnStreamFrame(frame); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + // The stream shouldn't be blocked in the next event loop. std::string last_part_request = bodyToStreamPayload("bbb"); quic::QuicStreamFrame frame2(stream_id_, true, data.length(), last_part_request); EXPECT_CALL(stream_decoder_, decodeData(_, _)) @@ -394,6 +409,57 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableAndReEnableImmediately) { })); quic_stream_->OnStreamFrame(frame2); + // The posted unblock callback shouldn't trigger another decodeData. + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); +} + +TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponHeaders) { + std::string payload(1024, 'a'); + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + .WillOnce(Invoke( + [this](const Http::RequestHeaderMapPtr&, bool) { quic_stream_->readDisable(true); })); + EXPECT_CALL(stream_decoder_, decodeData(_, _)); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string data = absl::StrCat(spdyHeaderToHttp3StreamPayload(spdy_request_headers_), + bodyToStreamPayload(payload)); + quic::QuicStreamFrame frame(stream_id_, false, 0, data); + quic_stream_->OnStreamFrame(frame); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), + request_headers_); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + + quic::QuicStreamFrame frame(stream_id_, false, 0, payload); + quic_stream_->OnStreamFrame(frame); + } + // Stream should be blocked in the next event loop. + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + // Receiving more date shouldn't trigger decoding. + EXPECT_CALL(stream_decoder_, decodeData(_, _)).Times(0); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string data = bodyToStreamPayload(payload); + quic::QuicStreamFrame frame(stream_id_, false, 0, data); + quic_stream_->OnStreamFrame(frame); + } else { + quic::QuicStreamFrame frame(stream_id_, false, 0, payload); + quic_stream_->OnStreamFrame(frame); + } + + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); +} + +TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponTrailers) { + size_t payload_offset = receiveRequest(request_body_, false, request_body_.length() * 2); + EXPECT_FALSE(quic_stream_->HasBytesToRead()); + + EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) + .WillOnce( + Invoke([this](const Http::RequestTrailerMapPtr&) { quic_stream_->readDisable(true); })); + receiveTrailers(payload_offset); + + EXPECT_TRUE(quic_stream_->IsDoneReading()); EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); } diff --git a/test/common/quic/test_utils.h b/test/common/quic/test_utils.h index 0da7ee95ba927..987b5af219a1d 100644 --- a/test/common/quic/test_utils.h +++ b/test/common/quic/test_utils.h @@ -159,6 +159,7 @@ class MockEnvoyQuicClientSession : public quic::QuicSpdyClientSession, return {GetCryptoStream()->crypto_negotiated_params().sni}; } + using quic::QuicSession::closed_streams; using quic::QuicSpdySession::ActivateStream; protected: diff --git a/test/integration/multiplexed_upstream_integration_test.cc b/test/integration/multiplexed_upstream_integration_test.cc index db6f3be19216b..88bd515b1679f 100644 --- a/test/integration/multiplexed_upstream_integration_test.cc +++ b/test/integration/multiplexed_upstream_integration_test.cc @@ -305,7 +305,6 @@ TEST_P(Http2UpstreamIntegrationTest, ManySimultaneousRequest) { } TEST_P(Http2UpstreamIntegrationTest, ManyLargeSimultaneousRequestWithBufferLimits) { - EXCLUDE_UPSTREAM_HTTP3; // quic_stream_sequencer.cc:235 CHECK failed: !blocked_. config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream. manySimultaneousRequests(1024 * 20, 1024 * 20); } diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 5aeef19daf495..a3d3910d7a179 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -320,6 +320,7 @@ TEST_P(QuicHttpIntegrationTest, UpstreamReadDisabledOnGiantResponseBody) { } TEST_P(QuicHttpIntegrationTest, DownstreamReadDisabledOnGiantPost) { + config_helper_.addConfigModifier(setUpstreamTimeout); config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); testRouterRequestAndResponseWithBody(/*request_size=*/10 * 1024 * 1024, /*response_size=*/1024, false); From 82e33f6ee0607698f6ed357cf2ec2375949c76b9 Mon Sep 17 00:00:00 2001 From: RenjieTang Date: Thu, 29 Apr 2021 09:22:02 -0700 Subject: [PATCH 110/209] runtime: refresh QUIC flags when a new runtime config is loaded. (#16151) Commit Message: When a new runtime config is loaded and a new Snapshot is created, search for quiche reloadable flags and override their values. Additional Description: Risk Level: Low Testing: unit test Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Fixes #9435 Signed-off-by: Renjie Tang Co-authored-by: Renjie Tang --- source/common/quic/platform/BUILD | 1 + .../common/quic/platform/quiche_flags_impl.cc | 21 ++++++++++-- .../common/quic/platform/quiche_flags_impl.h | 34 ++++++++++++++++--- source/common/runtime/BUILD | 6 ++-- source/common/runtime/runtime_features.h | 1 - source/common/runtime/runtime_impl.cc | 25 ++++++++++++++ source/common/runtime/runtime_impl.h | 2 ++ test/common/runtime/BUILD | 5 ++- test/common/runtime/runtime_impl_test.cc | 32 +++++++++++++++++ 9 files changed, 115 insertions(+), 12 deletions(-) diff --git a/source/common/quic/platform/BUILD b/source/common/quic/platform/BUILD index b704b29fd961d..0d9edee9c2334 100644 --- a/source/common/quic/platform/BUILD +++ b/source/common/quic/platform/BUILD @@ -43,6 +43,7 @@ envoy_cc_library( ], visibility = ["//visibility:public"], deps = [ + "//source/common/common:assert_lib", "@com_googlesource_quiche//:quic_core_flags_list_lib", "@com_googlesource_quiche//:quic_core_protocol_flags_list_lib", ], diff --git a/source/common/quic/platform/quiche_flags_impl.cc b/source/common/quic/platform/quiche_flags_impl.cc index 641f31ae0b2f0..5f937e3b73396 100644 --- a/source/common/quic/platform/quiche_flags_impl.cc +++ b/source/common/quic/platform/quiche_flags_impl.cc @@ -8,15 +8,18 @@ #include +#include "common/common/assert.h" + #include "absl/strings/ascii.h" +#include "absl/strings/match.h" #include "absl/strings/numbers.h" namespace quiche { namespace { -absl::flat_hash_map makeFlagMap() { - absl::flat_hash_map flags; +absl::flat_hash_map makeFlagMap() { + absl::flat_hash_map flags; #define QUIC_FLAG(flag, ...) flags.emplace(flag->name(), flag); #include "quiche/quic/core/quic_flags_list.h" @@ -54,11 +57,23 @@ void FlagRegistry::resetFlags() const { } } -Flag* FlagRegistry::findFlag(const std::string& name) const { +Flag* FlagRegistry::findFlag(absl::string_view name) const { auto it = flags_.find(name); return (it != flags_.end()) ? it->second : nullptr; } +void FlagRegistry::updateReloadableFlags( + const absl::flat_hash_map& quiche_flags_override) { + for (auto& kv : flags_) { + const auto it = quiche_flags_override.find(kv.first); + if (it != quiche_flags_override.end()) { + static_cast*>(kv.second)->setReloadedValue(it->second); + } else { + kv.second->resetReloadedValue(); + } + } +} + template <> bool TypedFlag::setValueFromString(const std::string& value_str) { static const auto* kTrueValues = new std::set({"1", "t", "true", "y", "yes"}); static const auto* kFalseValues = new std::set({"0", "f", "false", "n", "no"}); diff --git a/source/common/quic/platform/quiche_flags_impl.h b/source/common/quic/platform/quiche_flags_impl.h index db4a51e0ec694..6df7fee972c07 100644 --- a/source/common/quic/platform/quiche_flags_impl.h +++ b/source/common/quic/platform/quiche_flags_impl.h @@ -13,6 +13,10 @@ namespace quiche { +const std::string EnvoyQuicheReloadableFlagPrefix = + "envoy.reloadable_features.FLAGS_quic_reloadable_flag_"; +const std::string EnvoyFeaturePrefix = "envoy.reloadable_features."; + class Flag; // TODO: modify flags implementation to be backed by @@ -32,12 +36,14 @@ class FlagRegistry { void resetFlags() const; // Look up a flag by name. - Flag* findFlag(const std::string& name) const; + Flag* findFlag(absl::string_view name) const; + + void updateReloadableFlags(const absl::flat_hash_map& quiche_flags_override); private: FlagRegistry(); - const absl::flat_hash_map flags_; + const absl::flat_hash_map flags_; }; // Abstract class for QUICHE protocol and feature flags. @@ -53,11 +59,13 @@ class Flag { // Reset flag to default value. virtual void resetValue() = 0; + virtual void resetReloadedValue() = 0; + // Return flag name. - std::string name() const { return name_; } + absl::string_view name() const { return name_; } // Return flag help string. - std::string help() const { return help_; } + absl::string_view help() const { return help_; } private: std::string name_; @@ -86,13 +94,29 @@ template class TypedFlag : public Flag { // Return flag value. T value() const { absl::MutexLock lock(&mutex_); + if (has_reloaded_value_) { + return reloaded_value_; + } return value_; } + void setReloadedValue(T value) { + absl::MutexLock lock(&mutex_); + has_reloaded_value_ = true; + reloaded_value_ = value; + } + + void resetReloadedValue() override { + absl::MutexLock lock(&mutex_); + has_reloaded_value_ = false; + } + private: mutable absl::Mutex mutex_; T value_ ABSL_GUARDED_BY(mutex_); - T default_value_; + const T default_value_; + bool has_reloaded_value_ ABSL_GUARDED_BY(mutex_) = false; + T reloaded_value_ ABSL_GUARDED_BY(mutex_); }; // SetValueFromString specializations diff --git a/source/common/runtime/BUILD b/source/common/runtime/BUILD index d010148200223..6b2f326d2cb64 100644 --- a/source/common/runtime/BUILD +++ b/source/common/runtime/BUILD @@ -2,6 +2,7 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", + "envoy_select_enable_http3", ) licenses(["notice"]) # Apache 2 @@ -23,7 +24,6 @@ envoy_cc_library( "//include/envoy/runtime:runtime_interface", "//source/common/common:hash_lib", "//source/common/singleton:const_singleton", - "//source/common/singleton:threadsafe_singleton", ], ) @@ -78,5 +78,7 @@ envoy_cc_library( "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", "@envoy_api//envoy/service/runtime/v3:pkg_cc_proto", "@envoy_api//envoy/type/v3:pkg_cc_proto", - ], + ] + envoy_select_enable_http3([ + "//source/common/quic/platform:quiche_flags_impl_lib", + ]), ) diff --git a/source/common/runtime/runtime_features.h b/source/common/runtime/runtime_features.h index b7d57ded38b7e..4760d218504c2 100644 --- a/source/common/runtime/runtime_features.h +++ b/source/common/runtime/runtime_features.h @@ -5,7 +5,6 @@ #include "envoy/runtime/runtime.h" #include "common/singleton/const_singleton.h" -#include "common/singleton/threadsafe_singleton.h" #include "absl/container/flat_hash_set.h" diff --git a/source/common/runtime/runtime_impl.cc b/source/common/runtime/runtime_impl.cc index fb789c82725fa..e41a19217f9cb 100644 --- a/source/common/runtime/runtime_impl.cc +++ b/source/common/runtime/runtime_impl.cc @@ -27,6 +27,10 @@ #include "absl/strings/match.h" #include "absl/strings/numbers.h" +#ifdef ENVOY_ENABLE_QUIC +#include "common/quic/platform/quiche_flags_impl.h" +#endif + namespace Envoy { namespace Runtime { @@ -38,6 +42,21 @@ void countDeprecatedFeatureUseInternal(const RuntimeStats& stats) { stats.deprecated_feature_seen_since_process_start_.inc(); } +// TODO(12923): Document the Quiche reloadable flag setup. +#ifdef ENVOY_ENABLE_QUIC +void refreshQuicheReloadableFlags(const Snapshot::EntryMap& flag_map) { + absl::flat_hash_map quiche_flags_override; + for (const auto& it : flag_map) { + if (absl::StartsWith(it.first, quiche::EnvoyQuicheReloadableFlagPrefix) && + it.second.bool_value_.has_value()) { + quiche_flags_override[it.first.substr(quiche::EnvoyFeaturePrefix.length())] = + it.second.bool_value_.value(); + } + } + quiche::FlagRegistry::getInstance().updateReloadableFlags(quiche_flags_override); +} +#endif + } // namespace bool SnapshotImpl::deprecatedFeatureEnabled(absl::string_view key, bool default_value) const { @@ -178,6 +197,8 @@ const std::vector& SnapshotImpl::getLayers() co return layers_; } +const Snapshot::EntryMap& SnapshotImpl::values() const { return values_; } + SnapshotImpl::SnapshotImpl(Random::RandomGenerator& generator, RuntimeStats& stats, std::vector&& layers) : layers_{std::move(layers)}, generator_{generator}, stats_{stats} { @@ -513,6 +534,10 @@ void LoaderImpl::loadNewSnapshot() { return std::static_pointer_cast(ptr); }); +#ifdef ENVOY_ENABLE_QUIC + refreshQuicheReloadableFlags(ptr->values()); +#endif + { absl::MutexLock lock(&snapshot_mutex_); thread_safe_snapshot_ = ptr; diff --git a/source/common/runtime/runtime_impl.h b/source/common/runtime/runtime_impl.h index d8422b6dc41ce..3d99376369677 100644 --- a/source/common/runtime/runtime_impl.h +++ b/source/common/runtime/runtime_impl.h @@ -85,6 +85,8 @@ class SnapshotImpl : public Snapshot, Logger::Loggable { bool getBoolean(absl::string_view key, bool value) const override; const std::vector& getLayers() const override; + const EntryMap& values() const; + static Entry createEntry(const std::string& value); static Entry createEntry(const ProtobufWkt::Value& value); diff --git a/test/common/runtime/BUILD b/test/common/runtime/BUILD index 4bfdbe0a4b41b..1aea7b96e9176 100644 --- a/test/common/runtime/BUILD +++ b/test/common/runtime/BUILD @@ -3,6 +3,7 @@ load( "envoy_cc_test", "envoy_cc_test_library", "envoy_package", + "envoy_select_enable_http3", ) licenses(["notice"]) # Apache 2 @@ -63,7 +64,9 @@ envoy_cc_test( "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", "@envoy_api//envoy/service/runtime/v3:pkg_cc_proto", "@envoy_api//envoy/type/v3:pkg_cc_proto", - ], + ] + envoy_select_enable_http3([ + "//source/common/quic:envoy_quic_utils_lib", + ]), ) envoy_cc_test( diff --git a/test/common/runtime/runtime_impl_test.cc b/test/common/runtime/runtime_impl_test.cc index 65acd33bc6e5c..0f220455ef218 100644 --- a/test/common/runtime/runtime_impl_test.cc +++ b/test/common/runtime/runtime_impl_test.cc @@ -28,6 +28,10 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#ifdef ENVOY_ENABLE_QUIC +#include "common/quic/envoy_quic_utils.h" +#endif + using testing::_; using testing::Invoke; using testing::InvokeWithoutArgs; @@ -541,6 +545,34 @@ TEST_F(StaticLoaderImplTest, All) { testNewOverrides(*loader_, store_); } +#ifdef ENVOY_ENABLE_QUIC +TEST_F(StaticLoaderImplTest, QuicheReloadableFlags) { + // Test that Quiche flags can be overwritten via Envoy runtime config. + base_ = TestUtility::parseYaml(R"EOF( + envoy.reloadable_features.FLAGS_quic_reloadable_flag_quic_testonly_default_false: true + envoy.reloadable_features.FLAGS_quic_reloadable_flag_quic_testonly_default_true: false + envoy.reloadable_features.FLAGS_quic_reloadable_flag_spdy_testonly_default_false: false + )EOF"); + SetQuicReloadableFlag(spdy_testonly_default_false, true); + EXPECT_EQ(true, GetQuicReloadableFlag(spdy_testonly_default_false)); + setup(); + EXPECT_EQ(true, GetQuicReloadableFlag(quic_testonly_default_false)); + EXPECT_EQ(false, GetQuicReloadableFlag(quic_testonly_default_true)); + EXPECT_EQ(false, GetQuicReloadableFlag(spdy_testonly_default_false)); + + // Test 2 behaviors: + // 1. Removing overwritten config will make the flag fallback to default value. + // 2. Quiche flags can be overwritten again. + base_ = TestUtility::parseYaml(R"EOF( + envoy.reloadable_features.FLAGS_quic_reloadable_flag_quic_testonly_default_true: true + )EOF"); + setup(); + EXPECT_EQ(false, GetQuicReloadableFlag(quic_testonly_default_false)); + EXPECT_EQ(true, GetQuicReloadableFlag(quic_testonly_default_true)); + EXPECT_EQ(true, GetQuicReloadableFlag(spdy_testonly_default_false)); +} +#endif + // Validate proto parsing sanity. TEST_F(StaticLoaderImplTest, ProtoParsing) { base_ = TestUtility::parseYaml(R"EOF( From 8b9b87702885beb324dadb349cbcb06d037c956e Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Thu, 29 Apr 2021 23:23:24 +0000 Subject: [PATCH 111/209] test: Deflake tsan //test/integration:integration_test (#16238) Signed-off-by: Yan Avlasov --- test/integration/integration_test.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index 071e9e5ff06e2..e6481e7e89613 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -1918,6 +1918,12 @@ TEST_P(IntegrationTest, Preconnect) { clients.front()->close(); clients.pop_front(); } + + for (auto& connection : fake_connections) { + ASSERT_TRUE(connection->close()); + ASSERT_TRUE(connection->waitForDisconnect()); + connection.reset(); + } } TEST_P(IntegrationTest, RandomPreconnect) { From 9ae397ea6fb0d27fcb2d62beef268a6e01bb1298 Mon Sep 17 00:00:00 2001 From: OutOfControl Date: Fri, 30 Apr 2021 07:53:50 +0800 Subject: [PATCH 112/209] add %REQUEST_TX_DURATION% to the access log (#16207) Signed-off-by: gaoweiwen --- .../observability/access_log/usage.rst | 9 ++++++++ docs/root/version_history/current.rst | 1 + .../formatter/substitution_formatter.cc | 5 ++++ .../substitution_formatter_fuzz_test.dict | 1 + .../formatter/substitution_formatter_test.cc | 23 +++++++++++++++++++ 5 files changed, 39 insertions(+) diff --git a/docs/root/configuration/observability/access_log/usage.rst b/docs/root/configuration/observability/access_log/usage.rst index 4eb2b0b8e7b5a..22817c3db4d14 100644 --- a/docs/root/configuration/observability/access_log/usage.rst +++ b/docs/root/configuration/observability/access_log/usage.rst @@ -262,6 +262,15 @@ The following command operators are supported: Renders a numeric value in typed JSON logs. +%REQUEST_TX_DURATION% + HTTP + Total duration in milliseconds of the request from the start time to the last byte sent upstream. + + TCP + Not implemented ("-"). + + Renders a numeric value in typed JSON logs. + %RESPONSE_DURATION% HTTP Total duration in milliseconds of the request from the start time to the first byte read from the diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 39ffddd2085f4..3f6a6f1c0cfbb 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -11,6 +11,7 @@ Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* +* access_log: add new access_log command operator ``%REQUEST_TX_DURATION%``. * http: replaced setting ``envoy.reloadable_features.strict_1xx_and_204_response_headers`` with settings ``envoy.reloadable_features.require_strict_1xx_and_204_response_headers`` (require upstream 1xx or 204 responses to not have Transfer-Encoding or non-zero Content-Length headers) and diff --git a/source/common/formatter/substitution_formatter.cc b/source/common/formatter/substitution_formatter.cc index 4a700e1b41a70..9360e62709609 100644 --- a/source/common/formatter/substitution_formatter.cc +++ b/source/common/formatter/substitution_formatter.cc @@ -703,6 +703,11 @@ StreamInfoFormatter::StreamInfoFormatter(const std::string& field_name) { [](const StreamInfo::StreamInfo& stream_info) { return stream_info.lastDownstreamRxByteReceived(); }); + } else if (field_name == "REQUEST_TX_DURATION") { + field_extractor_ = std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.lastUpstreamTxByteSent(); + }); } else if (field_name == "RESPONSE_DURATION") { field_extractor_ = std::make_unique( [](const StreamInfo::StreamInfo& stream_info) { diff --git a/test/common/formatter/substitution_formatter_fuzz_test.dict b/test/common/formatter/substitution_formatter_fuzz_test.dict index ee785e3354ba3..e6598ba54888b 100644 --- a/test/common/formatter/substitution_formatter_fuzz_test.dict +++ b/test/common/formatter/substitution_formatter_fuzz_test.dict @@ -7,6 +7,7 @@ "%RESPONSE_CODE_DETAILS%" "%BYTES_SENT%" "%DURATION%" +"%REQUEST_TX_DURATION%" "%RESPONSE_DURATION%" "%RESPONSE_FLAGS%" "%RESPONSE_TX_DURATION%" diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index dbec222a08dee..e1c77c75dc186 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -163,6 +163,29 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { ProtoEq(ValueUtil::nullValue())); } + { + StreamInfoFormatter request_tx_duration_format("REQUEST_TX_DURATION"); + absl::optional dur = std::chrono::nanoseconds(15000000); + EXPECT_CALL(stream_info, lastUpstreamTxByteSent()).WillRepeatedly(Return(dur)); + EXPECT_EQ("15", request_tx_duration_format.format(request_headers, response_headers, + response_trailers, stream_info, body)); + EXPECT_THAT(request_tx_duration_format.formatValue(request_headers, response_headers, + response_trailers, stream_info, body), + ProtoEq(ValueUtil::numberValue(15.0))); + } + + { + StreamInfoFormatter request_tx_duration_format("REQUEST_TX_DURATION"); + absl::optional dur; + EXPECT_CALL(stream_info, lastUpstreamTxByteSent()).WillRepeatedly(Return(dur)); + EXPECT_EQ(absl::nullopt, + request_tx_duration_format.format(request_headers, response_headers, + response_trailers, stream_info, body)); + EXPECT_THAT(request_tx_duration_format.formatValue(request_headers, response_headers, + response_trailers, stream_info, body), + ProtoEq(ValueUtil::nullValue())); + } + { StreamInfoFormatter response_duration_format("RESPONSE_DURATION"); absl::optional dur = std::chrono::nanoseconds(10000000); From 5e35ac69762db03af32b5d6e5e78421b2075da93 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 29 Apr 2021 19:56:48 -0400 Subject: [PATCH 113/209] tools: (mostly) enforcing flag alpha order (#16182) Signed-off-by: Alyssa Wilk --- tools/code_format/check_format.py | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index ee89224e16c43..c03697fec7aca 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -173,6 +173,7 @@ # :ref:`panic mode. ` REF_WITH_PUNCTUATION_REGEX = re.compile(".*\. <[^<]*>`\s*") DOT_MULTI_SPACE_REGEX = re.compile("\\. +") +FLAG_REGEX = re.compile(" \"(.*)\",") # yapf: disable PROTOBUF_TYPE_ERRORS = { @@ -250,6 +251,15 @@ "extensions/filters/network/common", "extensions/filters/network/common/redis", } + +UNSORTED_FLAGS = { + "envoy.reloadable_features.activate_timers_next_event_loop", + "envoy.reloadable_features.check_ocsp_policy", + "envoy.reloadable_features.grpc_json_transcoder_adhere_to_buffer_limits", + "envoy.reloadable_features.http2_skip_encoding_empty_trailers", + "envoy.reloadable_features.upstream_http2_flood_checks", + "envoy.reloadable_features.header_map_correctly_coalesce_cookies", +} # yapf: enable @@ -494,6 +504,30 @@ def has_invalid_angle_bracket_directory(self, line): subdir = path[0:slash] return subdir in SUBDIR_SET + # simple check that all flags between "Begin alphabetically sorted section." + # and the end of the struct are in order (except the ones that already aren't) + def check_runtime_flags(self, file_path, error_messages): + in_flag_block = False + previous_flag = "" + for line_number, line in enumerate(self.read_lines(file_path)): + if "Begin alphabetically" in line: + in_flag_block = True + continue + if not in_flag_block: + continue + if "}" in line: + break + + match = FLAG_REGEX.match(line) + if not match: + error_messages.append("%s does not look like a reloadable flag" % line) + break + + if previous_flag: + if line < previous_flag and match.groups()[0] not in UNSORTED_FLAGS: + error_messages.append("%s and %s are out of order\n" % (line, previous_flag)) + previous_flag = line + def check_current_release_notes(self, file_path, error_messages): first_word_of_prior_line = '' next_word_to_check = '' # first word after : @@ -580,6 +614,9 @@ def check_file_contents(self, file_path, checker): # This only validates entries for the current release as very old release # notes have a different format. self.check_current_release_notes(file_path, error_messages) + if file_path.endswith("source/common/runtime/runtime_features.cc"): + # Do runtime alphabetical order checks. + self.check_runtime_flags(file_path, error_messages) def check_format_errors(line, line_number): From ba2b536d790899710ca76e0e359f7ea43431982b Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 30 Apr 2021 01:00:32 +0100 Subject: [PATCH 114/209] python: Revert buggy gitpython version (#16156) Signed-off-by: Ryan Northey --- docs/requirements.txt | 13 +++---------- tools/deprecate_version/requirements.txt | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 987496efe1037..376c7c4fbb809 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -41,9 +41,9 @@ gitdb==4.0.7 \ # via # -r docs/requirements.txt # gitpython -gitpython==3.1.15 \ - --hash=sha256:05af150f47a5cca3f4b0af289b73aef8cf3c4fe2385015b06220cbcdee48bb6e \ - --hash=sha256:a77824e516d3298b04fb36ec7845e92747df8fcfee9cacc32dd6239f9652f867 +gitpython==3.1.14 \ + --hash=sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b \ + --hash=sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61 # via -r docs/requirements.txt idna==2.10 \ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ @@ -215,13 +215,6 @@ sphinxext-rediraffe==0.2.7 \ --hash=sha256:651dcbfae5ffda9ffd534dfb8025f36120e5efb6ea1a33f5420023862b9f725d \ --hash=sha256:9e430a52d4403847f4ffb3a8dd6dfc34a9fe43525305131f52ed899743a5fd8c # via -r docs/requirements.txt -typing-extensions==3.7.4.3 \ - --hash=sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918 \ - --hash=sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c \ - --hash=sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f - # via - # -r docs/requirements.txt - # gitpython urllib3==1.26.4 \ --hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \ --hash=sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937 diff --git a/tools/deprecate_version/requirements.txt b/tools/deprecate_version/requirements.txt index c49422bdc0fe1..e616d1bc66eff 100644 --- a/tools/deprecate_version/requirements.txt +++ b/tools/deprecate_version/requirements.txt @@ -67,9 +67,9 @@ gitdb==4.0.7 \ # via # -r tools/deprecate_version/requirements.txt # gitpython -gitpython==3.1.15 \ - --hash=sha256:05af150f47a5cca3f4b0af289b73aef8cf3c4fe2385015b06220cbcdee48bb6e \ - --hash=sha256:a77824e516d3298b04fb36ec7845e92747df8fcfee9cacc32dd6239f9652f867 +gitpython==3.1.14 \ + --hash=sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b \ + --hash=sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61 # via -r tools/deprecate_version/requirements.txt idna==2.10 \ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ @@ -125,13 +125,6 @@ smmap==4.0.0 \ # via # -r tools/deprecate_version/requirements.txt # gitdb -typing-extensions==3.7.4.3 \ - --hash=sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918 \ - --hash=sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c \ - --hash=sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f - # via - # -r tools/deprecate_version/requirements.txt - # gitpython urllib3==1.26.4 \ --hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \ --hash=sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937 From 89248b358d44b090439ad5f93a65c2d28a067a74 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Thu, 29 Apr 2021 18:23:32 -0700 Subject: [PATCH 115/209] wasm: update V8 to v9.1.269.18. (#16220) Signed-off-by: Piotr Sikora --- bazel/external/wee8.BUILD | 5 ++++- bazel/external/wee8.genrule_cmd | 13 +++++++------ bazel/repository_locations.bzl | 6 +++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/bazel/external/wee8.BUILD b/bazel/external/wee8.BUILD index cccd57e6d0649..625e80ce500b7 100644 --- a/bazel/external/wee8.BUILD +++ b/bazel/external/wee8.BUILD @@ -18,7 +18,10 @@ cc_library( copts = [ "-Wno-range-loop-analysis", ], - defines = ["ENVOY_WASM_V8"], + defines = [ + "ENVOY_WASM_V8", + "V8_ENABLE_WEBASSEMBLY", + ], includes = [ "wee8", "wee8/include", diff --git a/bazel/external/wee8.genrule_cmd b/bazel/external/wee8.genrule_cmd index 70bc936d65a79..11fb9c6ed8d9a 100644 --- a/bazel/external/wee8.genrule_cmd +++ b/bazel/external/wee8.genrule_cmd @@ -95,14 +95,15 @@ WEE8_BUILD_ARGS+=" v8_use_external_startup_data=false" # TODO(PiotrSikora): remove when fixed upstream. WEE8_BUILD_ARGS+=" v8_enable_shared_ro_heap=false" -# Support arm64. -if [[ $${ARCH} == "aarch64" || $${ARCH} == "arm64" ]]; then +# Set target architecture. +if [[ $${ARCH} == "x86_64" ]]; then + WEE8_BUILD_ARGS+=" target_cpu=\"x64\"" +elif [[ $${ARCH} == "aarch64" || $${ARCH} == "arm64" ]]; then WEE8_BUILD_ARGS+=" target_cpu=\"arm64\"" -fi - -# Support ppc64le. -if [[ $${ARCH} == "ppc64le" ]]; then +elif [[ $${ARCH} == "ppc64le" ]]; then WEE8_BUILD_ARGS+=" target_cpu=\"ppc64\"" +elif [[ $${ARCH} == "s390x" ]]; then + WEE8_BUILD_ARGS+=" target_cpu=\"s390x\"" fi # Select gn tool for the current platform. diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 658f61ac2a74d..2f3f6c884bcd6 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -768,14 +768,14 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "V8", project_desc = "Google’s open source high-performance JavaScript and WebAssembly engine, written in C++", project_url = "https://v8.dev", - version = "9.0.257.17", + version = "9.1.269.18", # This archive was created using https://storage.googleapis.com/envoyproxy-wee8/wee8-archive.sh # and contains complete checkout of V8 with all dependencies necessary to build wee8. - sha256 = "0eaf060eae4907f7d961fc31b0692175003f56cee320c7e4da4d19b47c2557f3", + sha256 = "3c4f0827f38b49c11a8a3a20cf897ce14dc8a7a7b999f8b0ee913211c6ab3d8b", urls = ["https://storage.googleapis.com/envoyproxy-wee8/wee8-{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.wasm.runtime.v8"], - release_date = "2021-04-12", + release_date = "2021-04-27", cpe = "cpe:2.3:a:google:v8:*", ), com_googlesource_quiche = dict( From 96f85719740e08a03c8f1a490655fb791f15e5b3 Mon Sep 17 00:00:00 2001 From: Auni Ahsan Date: Thu, 29 Apr 2021 21:59:50 -0400 Subject: [PATCH 116/209] hcm config: Reject filterchains with unmet decode dependencies (#15462) - Modify filterfactory processing to record FilterDependencies with a DependencyManager - Call DependencyManager::validDecodeDependencies() and reject config in the case of a dependency violation - This will be modified to support decode+encode path once support for that is added to DependencyManager - This also affects any upgrade filter chain Risk Level: High, any bug can cause config rejections. Testing: Unit testing. Signed-off-by: Auni Ahsan --- .../network/http_connection_manager/BUILD | 1 + .../network/http_connection_manager/config.cc | 27 +- .../network/http_connection_manager/config.h | 6 +- .../network/http_connection_manager/BUILD | 16 +- .../http_connection_manager/config.proto | 3 + .../config_filter_dependencies_test.cc | 258 ++++++++++++++++++ .../config_test_base.h | 19 ++ 7 files changed, 322 insertions(+), 8 deletions(-) create mode 100644 test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc diff --git a/source/extensions/filters/network/http_connection_manager/BUILD b/source/extensions/filters/network/http_connection_manager/BUILD index 590a4a10a44a3..fd9234f3ea93a 100644 --- a/source/extensions/filters/network/http_connection_manager/BUILD +++ b/source/extensions/filters/network/http_connection_manager/BUILD @@ -23,6 +23,7 @@ envoy_cc_extension( # This is core Envoy config. visibility = ["//visibility:public"], deps = [ + ":dependency_manager", "//include/envoy/config:config_provider_manager_interface", "//include/envoy/filesystem:filesystem_interface", "//include/envoy/http:codec_interface", diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 4b6b0ba70c64a..70f4d08f29700 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -474,8 +474,15 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( } const auto& filters = config.http_filters(); + DependencyManager dependency_manager; for (int32_t i = 0; i < filters.size(); i++) { - processFilter(filters[i], i, "http", filter_factories_, "http", i == filters.size() - 1); + processFilter(filters[i], i, "http", "http", i == filters.size() - 1, filter_factories_, + dependency_manager); + } + // TODO(auni53): Validate encode dependencies too. + auto status = dependency_manager.validDecodeDependencies(); + if (!status.ok()) { + throw EnvoyException(std::string(status.message())); } for (const auto& upgrade_config : config.upgrade_configs()) { @@ -488,10 +495,18 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( } if (!upgrade_config.filters().empty()) { std::unique_ptr factories = std::make_unique(); + DependencyManager upgrade_dependency_manager; for (int32_t j = 0; j < upgrade_config.filters().size(); j++) { - processFilter(upgrade_config.filters(j), j, name, *factories, "http upgrade", - j == upgrade_config.filters().size() - 1); + processFilter(upgrade_config.filters(j), j, name, "http upgrade", + j == upgrade_config.filters().size() - 1, *factories, + upgrade_dependency_manager); + } + // TODO(auni53): Validate encode dependencies too. + auto status = upgrade_dependency_manager.validDecodeDependencies(); + if (!status.ok()) { + throw EnvoyException(std::string(status.message())); } + upgrade_filter_factories_.emplace( std::make_pair(name, FilterConfig{std::move(factories), enabled})); } else { @@ -505,8 +520,9 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( void HttpConnectionManagerConfig::processFilter( const envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter& proto_config, - int i, const std::string& prefix, FilterFactoriesList& filter_factories, - const std::string& filter_chain_type, bool last_filter_in_current_config) { + int i, const std::string& prefix, const std::string& filter_chain_type, + bool last_filter_in_current_config, FilterFactoriesList& filter_factories, + DependencyManager& dependency_manager) { ENVOY_LOG(debug, " {} filter #{}", prefix, i); if (proto_config.config_type_case() == envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter::ConfigTypeCase:: @@ -524,6 +540,7 @@ void HttpConnectionManagerConfig::processFilter( proto_config, context_.messageValidationVisitor(), factory); Http::FilterFactoryCb callback = factory.createFilterFactoryFromProto(*message, stats_prefix_, context_); + dependency_manager.registerFilter(factory.name(), *factory.dependencies()); bool is_terminal = factory.isTerminalFilterByProto(*message, context_); Config::Utility::validateTerminalFilters(proto_config.name(), factory.name(), filter_chain_type, is_terminal, last_filter_in_current_config); diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index f6685a644fb69..fdabd465a6fc7 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -31,6 +31,7 @@ #include "common/tracing/http_tracer_impl.h" #include "extensions/filters/network/common/factory_base.h" +#include "extensions/filters/network/http_connection_manager/dependency_manager.h" #include "extensions/filters/network/well_known_names.h" namespace Envoy { @@ -186,8 +187,9 @@ class HttpConnectionManagerConfig : Logger::Loggable, void processFilter(const envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter& proto_config, - int i, const std::string& prefix, FilterFactoriesList& filter_factories, - const std::string& filter_chain_type, bool last_filter_in_current_config); + int i, const std::string& prefix, const std::string& filter_chain_type, + bool last_filter_in_current_config, FilterFactoriesList& filter_factories, + DependencyManager& dependency_manager); void processDynamicFilterConfig(const std::string& name, const envoy::config::core::v3::ExtensionConfigSource& config_discovery, diff --git a/test/extensions/filters/network/http_connection_manager/BUILD b/test/extensions/filters/network/http_connection_manager/BUILD index cf1a8ea1827ce..5124dc2fa9bbc 100644 --- a/test/extensions/filters/network/http_connection_manager/BUILD +++ b/test/extensions/filters/network/http_connection_manager/BUILD @@ -27,6 +27,8 @@ envoy_extension_cc_test_library( ":config_cc_proto", "//source/common/filter/http:filter_config_discovery_lib", "//source/common/network:address_lib", + "//source/extensions/filters/http/common:factory_base_lib", + "//source/extensions/filters/http/common:pass_through_filter_lib", "//source/extensions/filters/http/health_check:config", "//source/extensions/filters/http/router:config", "//source/extensions/filters/network/http_connection_manager:config", @@ -34,6 +36,7 @@ envoy_extension_cc_test_library( "//test/integration/filters:encoder_decoder_buffer_filter_lib", "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", + "//test/test_common:registry_lib", ], ) @@ -50,7 +53,6 @@ envoy_extension_cc_test( "//test/integration/filters:encoder_decoder_buffer_filter_lib", "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", - "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", @@ -70,6 +72,18 @@ envoy_extension_cc_test( ], ) +envoy_extension_cc_test( + name = "config_filter_dependencies_test", + srcs = ["config_filter_dependencies_test.cc"], + extension_name = "envoy.filters.network.http_connection_manager", + deps = [ + ":config_cc_proto", + ":config_test_base", + "//source/extensions/filters/network/http_connection_manager:config", + "@envoy_api//envoy/extensions/filters/common/dependency/v3:pkg_cc_proto", + ], +) + envoy_cc_test( name = "dependency_manager_test", srcs = ["dependency_manager_test.cc"], diff --git a/test/extensions/filters/network/http_connection_manager/config.proto b/test/extensions/filters/network/http_connection_manager/config.proto index c04622285decb..dd4e890d57911 100644 --- a/test/extensions/filters/network/http_connection_manager/config.proto +++ b/test/extensions/filters/network/http_connection_manager/config.proto @@ -8,3 +8,6 @@ message CustomRequestIDExtension { message UnknownRequestIDExtension { } + +message FilterDependencyTestFilter { +} diff --git a/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc b/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc new file mode 100644 index 0000000000000..e9bc5386ede34 --- /dev/null +++ b/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc @@ -0,0 +1,258 @@ +#include "envoy/extensions/filters/common/dependency/v3/dependency.pb.h" + +#include "extensions/filters/network/http_connection_manager/config.h" + +#include "test/extensions/filters/network/http_connection_manager/config_test_base.h" +#include "test/mocks/config/mocks.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/factory_context.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using envoy::extensions::filters::common::dependency::v3::Dependency; +using envoy::extensions::filters::common::dependency::v3::FilterDependencies; +using Envoy::Server::Configuration::FilterDependenciesPtr; +using Envoy::Server::Configuration::NamedHttpFilterConfigFactory; +using testing::Eq; + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace HttpConnectionManager { +namespace { + +const std::string ConfigTemplate = R"EOF( +codec_type: http1 +server_name: foo +stat_prefix: router +route_config: + virtual_hosts: + - name: service + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: cluster +)EOF"; + +/** + * Testing scenario for testing filter dependencies. + * ChefFilter requires a "potato" dependency, which PantryFilter provides. + */ + +Dependency potato() { + Dependency d; + d.set_name("potato"); + d.set_type(Dependency::FILTER_STATE_KEY); + return d; +} + +// Test filter that provides a potato. +class PantryFilterFactory : public PassThroughFilterFactory { +public: + PantryFilterFactory() : PassThroughFilterFactory("test.pantry") {} + + FilterDependenciesPtr dependencies() override { + FilterDependencies d; + *d.add_decode_provided() = potato(); + return std::make_unique(d); + } +}; + +// Test filter that requires a potato. +class ChefFilterFactory : public PassThroughFilterFactory { +public: + ChefFilterFactory() : PassThroughFilterFactory("test.chef") {} + + FilterDependenciesPtr dependencies() override { + FilterDependencies d; + *d.add_decode_required() = potato(); + return std::make_unique(d); + } +}; + +TEST_F(HttpConnectionManagerConfigTest, UnregisteredFilterException) { + auto hcm_config = parseHttpConnectionManagerFromYaml(ConfigTemplate); + hcm_config.add_http_filters()->set_name("test.pantry"); + hcm_config.add_http_filters()->set_name("envoy.filters.http.router"); + + EXPECT_THROW_WITH_MESSAGE( + HttpConnectionManagerConfig(hcm_config, context_, date_provider_, + route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_), + EnvoyException, "Didn't find a registered implementation for name: 'test.pantry'"); +} + +// ChefFilter requires a potato, and PantryFilter provides it. +TEST_F(HttpConnectionManagerConfigTest, AllDependenciesSatisfiedOk) { + auto hcm_config = parseHttpConnectionManagerFromYaml(ConfigTemplate); + hcm_config.add_http_filters()->set_name("test.pantry"); + hcm_config.add_http_filters()->set_name("test.chef"); + hcm_config.add_http_filters()->set_name("envoy.filters.http.router"); + + PantryFilterFactory pf; + Registry::InjectFactory rf(pf); + ChefFilterFactory cf; + Registry::InjectFactory rc(cf); + + HttpConnectionManagerConfig(hcm_config, context_, date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); +} + +// PantryFilter provides a potato, which is not required by any other filter. +TEST_F(HttpConnectionManagerConfigTest, UnusedProvidencyOk) { + auto hcm_config = parseHttpConnectionManagerFromYaml(ConfigTemplate); + hcm_config.add_http_filters()->set_name("test.pantry"); + hcm_config.add_http_filters()->set_name("envoy.filters.http.router"); + + PantryFilterFactory pf; + Registry::InjectFactory rf(pf); + + HttpConnectionManagerConfig(hcm_config, context_, date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); +} + +// ChefFilter requires a potato, but no filter provides it. +TEST_F(HttpConnectionManagerConfigTest, UnmetDependencyError) { + auto hcm_config = parseHttpConnectionManagerFromYaml(ConfigTemplate); + hcm_config.add_http_filters()->set_name("test.chef"); + hcm_config.add_http_filters()->set_name("envoy.filters.http.router"); + + ChefFilterFactory cf; + Registry::InjectFactory rc(cf); + + EXPECT_THROW_WITH_MESSAGE( + HttpConnectionManagerConfig(hcm_config, context_, date_provider_, + route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_), + EnvoyException, + "Dependency violation: filter 'test.chef' requires a FILTER_STATE_KEY named 'potato'"); +} + +// ChefFilter requires a potato, but no preceding filter provides it. +TEST_F(HttpConnectionManagerConfigTest, MisorderedDependenciesError) { + auto hcm_config = parseHttpConnectionManagerFromYaml(ConfigTemplate); + hcm_config.add_http_filters()->set_name("test.chef"); + hcm_config.add_http_filters()->set_name("test.pantry"); + hcm_config.add_http_filters()->set_name("envoy.filters.http.router"); + + // Registration order does not matter. + PantryFilterFactory pf; + Registry::InjectFactory rf(pf); + ChefFilterFactory cf; + Registry::InjectFactory rc(cf); + + EXPECT_THROW_WITH_MESSAGE( + HttpConnectionManagerConfig(hcm_config, context_, date_provider_, + route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_), + EnvoyException, + "Dependency violation: filter 'test.chef' requires a FILTER_STATE_KEY named 'potato'"); +} + +TEST_F(HttpConnectionManagerConfigTest, UpgradeUnmetDependencyError) { + auto hcm_config = parseHttpConnectionManagerFromYaml(ConfigTemplate); + auto upgrade_config = hcm_config.add_upgrade_configs(); + upgrade_config->set_upgrade_type("websocket"); + + upgrade_config->add_filters()->set_name("test.chef"); + upgrade_config->add_filters()->set_name("envoy.filters.http.router"); + + ChefFilterFactory cf; + Registry::InjectFactory rc(cf); + + EXPECT_THROW_WITH_MESSAGE( + HttpConnectionManagerConfig(hcm_config, context_, date_provider_, + route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_), + EnvoyException, + "Dependency violation: filter 'test.chef' requires a FILTER_STATE_KEY named 'potato'"); +} + +TEST_F(HttpConnectionManagerConfigTest, UpgradeDependencyOK) { + auto hcm_config = parseHttpConnectionManagerFromYaml(ConfigTemplate); + auto upgrade_config = hcm_config.add_upgrade_configs(); + upgrade_config->set_upgrade_type("websocket"); + upgrade_config->add_filters()->set_name("test.pantry"); + upgrade_config->add_filters()->set_name("test.chef"); + upgrade_config->add_filters()->set_name("envoy.filters.http.router"); + + PantryFilterFactory pf; + Registry::InjectFactory rf(pf); + ChefFilterFactory cf; + Registry::InjectFactory rc(cf); + + HttpConnectionManagerConfig(hcm_config, context_, date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); +} + +// Dependencies provided in the HCM config filter chain do not satisfy +// requirements in the upgrade filter chain. +TEST_F(HttpConnectionManagerConfigTest, UpgradeFilterChainDependenciesIsolatedFromHcmConfig) { + auto hcm_config = parseHttpConnectionManagerFromYaml(ConfigTemplate); + hcm_config.add_http_filters()->set_name("test.pantry"); + hcm_config.add_http_filters()->set_name("envoy.filters.http.router"); + + auto upgrade_config = hcm_config.add_upgrade_configs(); + upgrade_config->set_upgrade_type("websocket"); + upgrade_config->add_filters()->set_name("test.chef"); + upgrade_config->add_filters()->set_name("envoy.filters.http.router"); + + PantryFilterFactory pf; + Registry::InjectFactory rf(pf); + ChefFilterFactory cf; + Registry::InjectFactory rc(cf); + + EXPECT_THROW_WITH_MESSAGE( + HttpConnectionManagerConfig(hcm_config, context_, date_provider_, + route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_), + EnvoyException, + "Dependency violation: filter 'test.chef' requires a FILTER_STATE_KEY named 'potato'"); +} + +// Dependencies provided in one upgrade filter chain do not satisfy +// requirements in another. +TEST_F(HttpConnectionManagerConfigTest, UpgradeFilterChainDependenciesIsolatedFromOtherUpgrades) { + auto hcm_config = parseHttpConnectionManagerFromYaml(ConfigTemplate); + auto upgrade_config1 = hcm_config.add_upgrade_configs(); + upgrade_config1->set_upgrade_type("websocket"); + upgrade_config1->add_filters()->set_name("test.pantry"); + upgrade_config1->add_filters()->set_name("envoy.filters.http.router"); + + auto upgrade_config2 = hcm_config.add_upgrade_configs(); + upgrade_config2->set_upgrade_type("CONNECT"); + upgrade_config2->add_filters()->set_name("test.chef"); + upgrade_config2->add_filters()->set_name("envoy.filters.http.router"); + + PantryFilterFactory pf; + Registry::InjectFactory rf(pf); + ChefFilterFactory cf; + Registry::InjectFactory rc(cf); + + EXPECT_THROW_WITH_MESSAGE( + HttpConnectionManagerConfig(hcm_config, context_, date_provider_, + route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_), + EnvoyException, + "Dependency violation: filter 'test.chef' requires a FILTER_STATE_KEY named 'potato'"); +} + +} // namespace +} // namespace HttpConnectionManager +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/http_connection_manager/config_test_base.h b/test/extensions/filters/network/http_connection_manager/config_test_base.h index a0ce9792af31d..a4554a42d6703 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test_base.h +++ b/test/extensions/filters/network/http_connection_manager/config_test_base.h @@ -4,13 +4,17 @@ #include "common/http/date_provider_impl.h" #include "common/network/address_impl.h" +#include "extensions/filters/http/common/factory_base.h" +#include "extensions/filters/http/common/pass_through_filter.h" #include "extensions/filters/network/http_connection_manager/config.h" #include "test/extensions/filters/network/http_connection_manager/config.pb.h" +#include "test/extensions/filters/network/http_connection_manager/config.pb.validate.h" #include "test/mocks/config/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/factory_context.h" +#include "test/test_common/registry.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -46,6 +50,21 @@ class HttpConnectionManagerConfigTest : public testing::Test { } }; +class PassThroughFilterFactory : public Extensions::HttpFilters::Common::FactoryBase< + test::http_connection_manager::FilterDependencyTestFilter> { +public: + PassThroughFilterFactory(std::string name) : FactoryBase(name) {} + +private: + Http::FilterFactoryCb createFilterFactoryFromProtoTyped( + const test::http_connection_manager::FilterDependencyTestFilter&, const std::string&, + Server::Configuration::FactoryContext&) override { + return [](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamDecoderFilter(std::make_shared()); + }; + } +}; + } // namespace HttpConnectionManager } // namespace NetworkFilters } // namespace Extensions From abe2f9b4af4b3c697ee8b3fda934aa2e181bc5ef Mon Sep 17 00:00:00 2001 From: tavishvaidya Date: Thu, 29 Apr 2021 19:10:08 -0700 Subject: [PATCH 117/209] Config proto for Secure Session Agent (S2A) transport socket extension. (#16183) Config proto for Secure Session Agent (S2A) transport socket extension (#16110). Signed-off-by: Tavish Vaidya --- api/BUILD | 1 + .../transport_sockets/s2a/v3alpha/BUILD | 9 ++++++++ .../transport_sockets/s2a/v3alpha/s2a.proto | 22 +++++++++++++++++++ api/versioning/BUILD | 1 + generated_api_shadow/BUILD | 1 + .../transport_sockets/s2a/v3alpha/BUILD | 9 ++++++++ .../transport_sockets/s2a/v3alpha/s2a.proto | 22 +++++++++++++++++++ 7 files changed, 65 insertions(+) create mode 100644 api/envoy/extensions/transport_sockets/s2a/v3alpha/BUILD create mode 100644 api/envoy/extensions/transport_sockets/s2a/v3alpha/s2a.proto create mode 100644 generated_api_shadow/envoy/extensions/transport_sockets/s2a/v3alpha/BUILD create mode 100644 generated_api_shadow/envoy/extensions/transport_sockets/s2a/v3alpha/s2a.proto diff --git a/api/BUILD b/api/BUILD index 409498da974cf..9506cb8d0254c 100644 --- a/api/BUILD +++ b/api/BUILD @@ -263,6 +263,7 @@ proto_library( "//envoy/extensions/transport_sockets/proxy_protocol/v3:pkg", "//envoy/extensions/transport_sockets/quic/v3:pkg", "//envoy/extensions/transport_sockets/raw_buffer/v3:pkg", + "//envoy/extensions/transport_sockets/s2a/v3alpha:pkg", "//envoy/extensions/transport_sockets/starttls/v3:pkg", "//envoy/extensions/transport_sockets/tap/v3:pkg", "//envoy/extensions/transport_sockets/tls/v3:pkg", diff --git a/api/envoy/extensions/transport_sockets/s2a/v3alpha/BUILD b/api/envoy/extensions/transport_sockets/s2a/v3alpha/BUILD new file mode 100644 index 0000000000000..ee92fb652582e --- /dev/null +++ b/api/envoy/extensions/transport_sockets/s2a/v3alpha/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/envoy/extensions/transport_sockets/s2a/v3alpha/s2a.proto b/api/envoy/extensions/transport_sockets/s2a/v3alpha/s2a.proto new file mode 100644 index 0000000000000..b32b84653e690 --- /dev/null +++ b/api/envoy/extensions/transport_sockets/s2a/v3alpha/s2a.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package envoy.extensions.transport_sockets.s2a.v3alpha; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.transport_sockets.s2a.v3alpha"; +option java_outer_classname = "S2aProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).work_in_progress = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#not-implemented-hide:] +// Configuration for S2A transport socket. This allows Envoy clients to +// configure how to offload mTLS handshakes to the S2A service. +// https://github.com/google/s2a-core#readme +message S2AConfiguration { + // The address of the S2A. This can be an IP address or a hostname, + // followed by a port number. + string s2a_address = 1 [(validate.rules).string = {min_len: 1}]; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index e48ac24faf07c..822e07f07ca84 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -146,6 +146,7 @@ proto_library( "//envoy/extensions/transport_sockets/proxy_protocol/v3:pkg", "//envoy/extensions/transport_sockets/quic/v3:pkg", "//envoy/extensions/transport_sockets/raw_buffer/v3:pkg", + "//envoy/extensions/transport_sockets/s2a/v3alpha:pkg", "//envoy/extensions/transport_sockets/starttls/v3:pkg", "//envoy/extensions/transport_sockets/tap/v3:pkg", "//envoy/extensions/transport_sockets/tls/v3:pkg", diff --git a/generated_api_shadow/BUILD b/generated_api_shadow/BUILD index 409498da974cf..9506cb8d0254c 100644 --- a/generated_api_shadow/BUILD +++ b/generated_api_shadow/BUILD @@ -263,6 +263,7 @@ proto_library( "//envoy/extensions/transport_sockets/proxy_protocol/v3:pkg", "//envoy/extensions/transport_sockets/quic/v3:pkg", "//envoy/extensions/transport_sockets/raw_buffer/v3:pkg", + "//envoy/extensions/transport_sockets/s2a/v3alpha:pkg", "//envoy/extensions/transport_sockets/starttls/v3:pkg", "//envoy/extensions/transport_sockets/tap/v3:pkg", "//envoy/extensions/transport_sockets/tls/v3:pkg", diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/s2a/v3alpha/BUILD b/generated_api_shadow/envoy/extensions/transport_sockets/s2a/v3alpha/BUILD new file mode 100644 index 0000000000000..ee92fb652582e --- /dev/null +++ b/generated_api_shadow/envoy/extensions/transport_sockets/s2a/v3alpha/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/s2a/v3alpha/s2a.proto b/generated_api_shadow/envoy/extensions/transport_sockets/s2a/v3alpha/s2a.proto new file mode 100644 index 0000000000000..b32b84653e690 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/transport_sockets/s2a/v3alpha/s2a.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package envoy.extensions.transport_sockets.s2a.v3alpha; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.transport_sockets.s2a.v3alpha"; +option java_outer_classname = "S2aProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).work_in_progress = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#not-implemented-hide:] +// Configuration for S2A transport socket. This allows Envoy clients to +// configure how to offload mTLS handshakes to the S2A service. +// https://github.com/google/s2a-core#readme +message S2AConfiguration { + // The address of the S2A. This can be an IP address or a hostname, + // followed by a port number. + string s2a_address = 1 [(validate.rules).string = {min_len: 1}]; +} From 32638592df5188ccbdd9f8e2fb9daba8525531b1 Mon Sep 17 00:00:00 2001 From: YaoLe Date: Fri, 30 Apr 2021 21:40:14 +0800 Subject: [PATCH 118/209] Typo in test comment and release note (#16245) Fix the typo in test comment and release not Signed-off-by: Le Yao --- docs/root/version_history/v1.18.0.rst | 2 +- test/per_file_coverage.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/root/version_history/v1.18.0.rst b/docs/root/version_history/v1.18.0.rst index 10eaf16302257..70b32883e77c9 100644 --- a/docs/root/version_history/v1.18.0.rst +++ b/docs/root/version_history/v1.18.0.rst @@ -90,7 +90,7 @@ Bug Fixes * header map: pick the right delimiter to append multiple header values to the same key. Previouly header with multiple values were coalesced with ",", after this fix cookie headers should be coalesced with " ;". This doesn't affect Http1 or Http2 requests because these 2 codecs coalesce cookie headers before adding it to header map. To revert to the old behavior, set the runtime feature ``envoy.reloadable_features.header_map_correctly_coalesce_cookies`` to false. * http: avoid grpc-status overwrite on when sending local replies if that field has already been set. * http: disallowing "host:" in request_headers_to_add for behavioral consistency with rejecting :authority header. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.treat_host_like_authority`` to false. -* http: fixed an issue where Enovy did not handle peer stream limits correctly, and queued streams in nghttp2 rather than establish new connections. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.improved_stream_limit_handling`` to false. +* http: fixed an issue where Envoy did not handle peer stream limits correctly, and queued streams in nghttp2 rather than establish new connections. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.improved_stream_limit_handling`` to false. * http: fixed a bug where setting :ref:`MaxStreamDuration proto ` did not disable legacy timeout defaults. * http: fixed a crash upon receiving empty HTTP/2 metadata frames. Received empty metadata frames are now counted in the HTTP/2 codec stat :ref:`metadata_empty_frames `. * http: fixed a remotely exploitable integer overflow via a very large grpc-timeout value causes undefined behavior. diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index 682b1d8070690..0c2278641e766 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -58,7 +58,7 @@ declare -a KNOWN_LOW_COVERAGE=( "source/extensions/transport_sockets/tls:95.0" "source/extensions/wasm_runtime:50.0" "source/extensions/wasm_runtime/wasmtime:0.0" # Not enabled in coverage build -"source/extensions/wasm_runtime/wavm:0.0" # Noe enabled in coverage build +"source/extensions/wasm_runtime/wavm:0.0" # Not enabled in coverage build "source/extensions/watchdog:85.7" # Death tests within extensions "source/extensions/watchdog/profile_action:85.7" "source/server:94.4" # flaky: be careful adjusting. See https://github.com/envoyproxy/envoy/issues/15239 From 2971bf44160abdd515b11ca9b16306403f8b5ef3 Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Fri, 30 Apr 2021 06:42:08 -0700 Subject: [PATCH 119/209] Fix warning in the docs (#16242) Signed-off-by: Sotiris Nanopoulos --- docs/_ext/powershell_lexer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_ext/powershell_lexer.py b/docs/_ext/powershell_lexer.py index 74a077708245d..8551d61291e47 100644 --- a/docs/_ext/powershell_lexer.py +++ b/docs/_ext/powershell_lexer.py @@ -1,5 +1,5 @@ -from pygments.lexers import get_lexer_by_name +from pygments.lexers import PowerShellLexer def setup(app): - app.add_lexer('powershell', get_lexer_by_name('powershell')) + app.add_lexer('powershell', PowerShellLexer) From 5886f03b04519a00c1b7045db3361b0c68e8a1e6 Mon Sep 17 00:00:00 2001 From: Adrien Guinet Date: Fri, 30 Apr 2021 15:49:03 +0200 Subject: [PATCH 120/209] Matcher: add a "not" matcher (#16149) Signed-off-by: Adrien Guinet --- .../config/common/matcher/v3/matcher.proto | 3 ++ .../common/matcher/v4alpha/matcher.proto | 3 ++ .../config/common/matcher/v3/matcher.proto | 3 ++ .../common/matcher/v4alpha/matcher.proto | 3 ++ source/common/matcher/field_matcher.h | 20 ++++++++++ source/common/matcher/matcher.h | 4 ++ test/common/matcher/field_matcher_test.cc | 17 ++++++++ test/common/matcher/matcher_test.cc | 40 +++++++++++++++++++ 8 files changed, 93 insertions(+) diff --git a/api/envoy/config/common/matcher/v3/matcher.proto b/api/envoy/config/common/matcher/v3/matcher.proto index f9a4aafa945ed..d09dcbd0f62cd 100644 --- a/api/envoy/config/common/matcher/v3/matcher.proto +++ b/api/envoy/config/common/matcher/v3/matcher.proto @@ -81,6 +81,9 @@ message Matcher { // A list of predicates to be AND-ed together. PredicateList and_matcher = 3; + + // The invert of a predicate + Predicate not_matcher = 4; } } diff --git a/api/envoy/config/common/matcher/v4alpha/matcher.proto b/api/envoy/config/common/matcher/v4alpha/matcher.proto index 7747157a24dc1..fd5928e210c25 100644 --- a/api/envoy/config/common/matcher/v4alpha/matcher.proto +++ b/api/envoy/config/common/matcher/v4alpha/matcher.proto @@ -100,6 +100,9 @@ message Matcher { // A list of predicates to be AND-ed together. PredicateList and_matcher = 3; + + // The invert of a predicate + Predicate not_matcher = 4; } } diff --git a/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto b/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto index f9a4aafa945ed..d09dcbd0f62cd 100644 --- a/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto +++ b/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto @@ -81,6 +81,9 @@ message Matcher { // A list of predicates to be AND-ed together. PredicateList and_matcher = 3; + + // The invert of a predicate + Predicate not_matcher = 4; } } diff --git a/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto b/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto index 7747157a24dc1..fd5928e210c25 100644 --- a/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto +++ b/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto @@ -100,6 +100,9 @@ message Matcher { // A list of predicates to be AND-ed together. PredicateList and_matcher = 3; + + // The invert of a predicate + Predicate not_matcher = 4; } } diff --git a/source/common/matcher/field_matcher.h b/source/common/matcher/field_matcher.h index a4eec335e3f6e..c1145d7321e9c 100644 --- a/source/common/matcher/field_matcher.h +++ b/source/common/matcher/field_matcher.h @@ -113,6 +113,26 @@ template class AnyFieldMatcher : public FieldMatcher const std::vector> matchers_; }; +/** + * A FieldMatcher that returns the invert of a FieldMatcher. + */ +template class NotFieldMatcher : public FieldMatcher { +public: + explicit NotFieldMatcher(FieldMatcherPtr matcher) : matcher_(std::move(matcher)) {} + + FieldMatchResult match(const DataType& data) override { + const auto result = matcher_->match(data); + if (result.match_state_ == MatchState::UnableToMatch) { + return result; + } + + return {MatchState::MatchComplete, !result.result()}; + } + +private: + const FieldMatcherPtr matcher_; +}; + /** * Implementation of a FieldMatcher that extracts an input value from the provided data and attempts * to match using an InputMatcher. absl::nullopt is returned whenever the data is not available or diff --git a/source/common/matcher/matcher.h b/source/common/matcher/matcher.h index 76978a3ba5a1a..511e844287e92 100644 --- a/source/common/matcher/matcher.h +++ b/source/common/matcher/matcher.h @@ -118,6 +118,10 @@ template class MatchTreeFactory { return std::make_unique>(std::move(sub_matchers)); } + case (envoy::config::common::matcher::v3::Matcher::MatcherList::Predicate::kNotMatcher): { + return std::make_unique>( + createFieldMatcher(field_predicate.not_matcher())); + } default: NOT_REACHED_GCOVR_EXCL_LINE; } diff --git a/test/common/matcher/field_matcher_test.cc b/test/common/matcher/field_matcher_test.cc index f75e895f4db50..133da5ae8a4ff 100644 --- a/test/common/matcher/field_matcher_test.cc +++ b/test/common/matcher/field_matcher_test.cc @@ -106,5 +106,22 @@ TEST_F(FieldMatcherTest, AllMatcher) { MatchState::UnableToMatch); } +TEST_F(FieldMatcherTest, NotMatcher) { + EXPECT_TRUE(NotFieldMatcher( + std::make_unique>(createMatchers({true, false}))) + .match(TestData()) + .result()); + + EXPECT_EQ( + NotFieldMatcher( + std::make_unique>(createMatchers( + {std::make_pair(false, + DataInputGetResult::DataAvailability::MoreDataMightBeAvailable), + std::make_pair(false, DataInputGetResult::DataAvailability::AllDataAvailable)}))) + .match(TestData()) + .match_state_, + MatchState::UnableToMatch); +} + } // namespace Matcher } // namespace Envoy diff --git a/test/common/matcher/matcher_test.cc b/test/common/matcher/matcher_test.cc index 5ebb556b7e317..1bbc395806788 100644 --- a/test/common/matcher/matcher_test.cc +++ b/test/common/matcher/matcher_test.cc @@ -273,6 +273,46 @@ TEST_F(MatcherTest, TestOrMatcher) { EXPECT_NE(result.on_match_->action_cb_, nullptr); } +TEST_F(MatcherTest, TestNotMatcher) { + const std::string yaml = R"EOF( +matcher_list: + matchers: + - on_match: + action: + name: test_action + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + value: match!! + predicate: + not_matcher: + single_predicate: + input: + name: inner_input + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + value_match: + exact: foo + )EOF"; + + envoy::config::common::matcher::v3::Matcher matcher; + MessageUtil::loadFromYaml(yaml, matcher, ProtobufMessage::getStrictValidationVisitor()); + + TestUtility::validate(matcher); + + MatchTreeFactory factory("", factory_context_, validation_visitor_); + + auto inner_factory = TestDataInputFactory("inner_input", "foo"); + NeverMatchFactory match_factory; + + EXPECT_CALL(validation_visitor_, + performDataInputValidation(_, "type.googleapis.com/google.protobuf.StringValue")); + auto match_tree = factory.create(matcher); + + const auto result = match_tree->match(TestData()); + EXPECT_EQ(result.match_state_, MatchState::MatchComplete); + EXPECT_FALSE(result.on_match_.has_value()); +} + TEST_F(MatcherTest, TestRecursiveMatcher) { const std::string yaml = R"EOF( matcher_list: From 63307f55889d35ce98a34afe7a9aaaf0277304d3 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 30 Apr 2021 14:49:53 +0100 Subject: [PATCH 121/209] wasm: Simplify example config and fix docs (#16142) Signed-off-by: Ryan Northey --- .../http/http_filters/wasm_filter.rst | 21 ++++------- examples/wasm-cc/envoy.yaml | 35 ++++++++++--------- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/docs/root/configuration/http/http_filters/wasm_filter.rst b/docs/root/configuration/http/http_filters/wasm_filter.rst index ac3b6d2eb1326..8b10dbaf7acdf 100644 --- a/docs/root/configuration/http/http_filters/wasm_filter.rst +++ b/docs/root/configuration/http/http_filters/wasm_filter.rst @@ -21,20 +21,11 @@ Example configuration Example filter configuration: -.. code-block:: yaml - - name: envoy.filters.http.wasm - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm - config: - config: - name: "my_plugin" - vm_config: - runtime: "envoy.wasm.runtime.v8" - code: - local: - filename: "/etc/envoy_filter_http_wasm_example.wasm" - allow_precompiled: true - +.. literalinclude:: ../../../start/sandboxes/_include/wasm-cc/envoy.yaml + :language: yaml + :lines: 24-49 + :emphasize-lines: 4-21 + :linenos: + :caption: :download:`wasm envoy.yaml <../../../start/sandboxes/_include/wasm-cc/envoy.yaml>` The preceding snippet configures a filter from a Wasm binary on local disk. diff --git a/examples/wasm-cc/envoy.yaml b/examples/wasm-cc/envoy.yaml index a6697ae1c8dd1..1d04c7a26f62c 100644 --- a/examples/wasm-cc/envoy.yaml +++ b/examples/wasm-cc/envoy.yaml @@ -22,28 +22,29 @@ static_resources: prefix: "/" route: cluster: web_service + http_filters: - name: envoy.filters.http.wasm typed_config: - "@type": type.googleapis.com/udpa.type.v1.TypedStruct - type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm - value: - config: - name: "my_plugin" - root_id: "my_root_id" - configuration: - "@type": "type.googleapis.com/google.protobuf.StringValue" - value: | - {} - vm_config: - runtime: "envoy.wasm.runtime.v8" - vm_id: "my_vm_id" - code: - local: - filename: "lib/envoy_filter_http_wasm_example.wasm" - configuration: {} + "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm + config: + name: "my_plugin" + root_id: "my_root_id" + # if your wasm filter requires custom configuration you can add + # as follows + configuration: + "@type": "type.googleapis.com/google.protobuf.StringValue" + value: | + {} + vm_config: + runtime: "envoy.wasm.runtime.v8" + vm_id: "my_vm_id" + code: + local: + filename: "lib/envoy_filter_http_wasm_example.wasm" - name: envoy.filters.http.router typed_config: {} + clusters: - name: web_service connect_timeout: 0.25s From dede3aace85684d732758dab02806f773bd56846 Mon Sep 17 00:00:00 2001 From: phlax Date: Sun, 2 May 2021 15:44:48 +0100 Subject: [PATCH 122/209] protos: Move simple_http_cache config proto to api (#16230) Signed-off-by: Ryan Northey --- api/BUILD | 1 + .../cache/simple_http_cache/v3alpha/BUILD | 9 +++++++++ .../cache/simple_http_cache/v3alpha/config.proto | 16 ++++++++++++++++ .../filters/http/cache/v3alpha/cache.proto | 3 ++- .../filters/http/cache/v4alpha/cache.proto | 3 ++- api/versioning/BUILD | 1 + bazel/envoy_library.bzl | 1 + .../http/http_filters/http_filters.rst | 1 + examples/cache/front-envoy.yaml | 2 +- generated_api_shadow/BUILD | 1 + .../cache/simple_http_cache/v3alpha/BUILD | 9 +++++++++ .../cache/simple_http_cache/v3alpha/config.proto | 16 ++++++++++++++++ .../filters/http/cache/v3alpha/cache.proto | 3 ++- .../filters/http/cache/v4alpha/cache.proto | 3 ++- source/BUILD | 16 ---------------- source/extensions/extensions_build_config.bzl | 3 +-- .../filters/http/cache/simple_http_cache/BUILD | 12 +++--------- .../http/cache/simple_http_cache/config.proto | 9 --------- .../cache/simple_http_cache/simple_http_cache.cc | 5 ++--- .../cache/simple_http_cache/simple_http_cache.h | 3 +++ test/extensions/filters/http/cache/BUILD | 11 ++++++----- .../http/cache/cache_filter_integration_test.cc | 2 +- .../extensions/filters/http/cache/config_test.cc | 4 ++-- .../filters/http/cache/simple_http_cache/BUILD | 4 ++-- .../simple_http_cache/simple_http_cache_test.cc | 2 +- tools/type_whisperer/BUILD | 1 - 26 files changed, 85 insertions(+), 56 deletions(-) create mode 100644 api/envoy/extensions/cache/simple_http_cache/v3alpha/BUILD create mode 100644 api/envoy/extensions/cache/simple_http_cache/v3alpha/config.proto create mode 100644 generated_api_shadow/envoy/extensions/cache/simple_http_cache/v3alpha/BUILD create mode 100644 generated_api_shadow/envoy/extensions/cache/simple_http_cache/v3alpha/config.proto delete mode 100644 source/BUILD delete mode 100644 source/extensions/filters/http/cache/simple_http_cache/config.proto diff --git a/api/BUILD b/api/BUILD index 9506cb8d0254c..7a6671dd681f9 100644 --- a/api/BUILD +++ b/api/BUILD @@ -158,6 +158,7 @@ proto_library( "//envoy/extensions/access_loggers/open_telemetry/v3alpha:pkg", "//envoy/extensions/access_loggers/stream/v3:pkg", "//envoy/extensions/access_loggers/wasm/v3:pkg", + "//envoy/extensions/cache/simple_http_cache/v3alpha:pkg", "//envoy/extensions/clusters/aggregate/v3:pkg", "//envoy/extensions/clusters/dynamic_forward_proxy/v3:pkg", "//envoy/extensions/clusters/redis/v3:pkg", diff --git a/api/envoy/extensions/cache/simple_http_cache/v3alpha/BUILD b/api/envoy/extensions/cache/simple_http_cache/v3alpha/BUILD new file mode 100644 index 0000000000000..ee92fb652582e --- /dev/null +++ b/api/envoy/extensions/cache/simple_http_cache/v3alpha/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/envoy/extensions/cache/simple_http_cache/v3alpha/config.proto b/api/envoy/extensions/cache/simple_http_cache/v3alpha/config.proto new file mode 100644 index 0000000000000..1b42e9b3f93d4 --- /dev/null +++ b/api/envoy/extensions/cache/simple_http_cache/v3alpha/config.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package envoy.extensions.cache.simple_http_cache.v3alpha; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.cache.simple_http_cache.v3alpha"; +option java_outer_classname = "ConfigProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: SimpleHttpCache CacheFilter storage plugin] + +// [#extension: envoy.cache.simple_http_cache] +message SimpleHttpCacheConfig { +} diff --git a/api/envoy/extensions/filters/http/cache/v3alpha/cache.proto b/api/envoy/extensions/filters/http/cache/v3alpha/cache.proto index 9e140a38a902e..5f0a5befa4bb3 100644 --- a/api/envoy/extensions/filters/http/cache/v3alpha/cache.proto +++ b/api/envoy/extensions/filters/http/cache/v3alpha/cache.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: HTTP Cache Filter] -// [#extension: envoy.filters.http.cache.simple_http_cache] +// [#extension: envoy.filters.http.cache] message CacheConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.cache.v2alpha.CacheConfig"; @@ -50,6 +50,7 @@ message CacheConfig { } // Config specific to the cache storage implementation. + // [#extension-category: envoy.filters.http.cache] google.protobuf.Any typed_config = 1 [(validate.rules).any = {required: true}]; // List of matching rules that defines allowed *Vary* headers. diff --git a/api/envoy/extensions/filters/http/cache/v4alpha/cache.proto b/api/envoy/extensions/filters/http/cache/v4alpha/cache.proto index 283e8f53aebb2..5297a3d15ef89 100644 --- a/api/envoy/extensions/filters/http/cache/v4alpha/cache.proto +++ b/api/envoy/extensions/filters/http/cache/v4alpha/cache.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: HTTP Cache Filter] -// [#extension: envoy.filters.http.cache.simple_http_cache] +// [#extension: envoy.filters.http.cache] message CacheConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.cache.v3alpha.CacheConfig"; @@ -50,6 +50,7 @@ message CacheConfig { } // Config specific to the cache storage implementation. + // [#extension-category: envoy.filters.http.cache] google.protobuf.Any typed_config = 1 [(validate.rules).any = {required: true}]; // List of matching rules that defines allowed *Vary* headers. diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 822e07f07ca84..338a8cdb80f2a 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -41,6 +41,7 @@ proto_library( "//envoy/extensions/access_loggers/open_telemetry/v3alpha:pkg", "//envoy/extensions/access_loggers/stream/v3:pkg", "//envoy/extensions/access_loggers/wasm/v3:pkg", + "//envoy/extensions/cache/simple_http_cache/v3alpha:pkg", "//envoy/extensions/clusters/aggregate/v3:pkg", "//envoy/extensions/clusters/dynamic_forward_proxy/v3:pkg", "//envoy/extensions/clusters/redis/v3:pkg", diff --git a/bazel/envoy_library.bzl b/bazel/envoy_library.bzl index f239dd3487ec2..7f9b745a504bc 100644 --- a/bazel/envoy_library.bzl +++ b/bazel/envoy_library.bzl @@ -74,6 +74,7 @@ EXTENSION_CATEGORIES = [ "envoy.compression.compressor", "envoy.compression.decompressor", "envoy.filters.http", + "envoy.filters.http.cache", "envoy.filters.listener", "envoy.filters.network", "envoy.filters.udp_listener", diff --git a/docs/root/configuration/http/http_filters/http_filters.rst b/docs/root/configuration/http/http_filters/http_filters.rst index b8102656bd4b2..5bd61f14dc44b 100644 --- a/docs/root/configuration/http/http_filters/http_filters.rst +++ b/docs/root/configuration/http/http_filters/http_filters.rst @@ -55,4 +55,5 @@ HTTP filters ../../../api-v3/service/ext_proc/v3alpha/external_processor.proto ../../../api-v3/extensions/filters/http/oauth2/v3alpha/oauth.proto ../../../api-v3/extensions/filters/http/cache/v3alpha/cache.proto + ../../../api-v3/extensions/cache/simple_http_cache/v3alpha/config.proto ../../../api-v3/extensions/filters/http/cdn_loop/v3alpha/cdn_loop.proto diff --git a/examples/cache/front-envoy.yaml b/examples/cache/front-envoy.yaml index 15177c651aa94..345e96f4f7346 100644 --- a/examples/cache/front-envoy.yaml +++ b/examples/cache/front-envoy.yaml @@ -31,7 +31,7 @@ static_resources: typed_config: "@type": "type.googleapis.com/envoy.extensions.filters.http.cache.v3alpha.CacheConfig" typed_config: - "@type": "type.googleapis.com/envoy.source.extensions.filters.http.cache.SimpleHttpCacheConfig" + "@type": "type.googleapis.com/envoy.extensions.cache.simple_http_cache.v3alpha.SimpleHttpCacheConfig" - name: envoy.filters.http.router typed_config: {} diff --git a/generated_api_shadow/BUILD b/generated_api_shadow/BUILD index 9506cb8d0254c..7a6671dd681f9 100644 --- a/generated_api_shadow/BUILD +++ b/generated_api_shadow/BUILD @@ -158,6 +158,7 @@ proto_library( "//envoy/extensions/access_loggers/open_telemetry/v3alpha:pkg", "//envoy/extensions/access_loggers/stream/v3:pkg", "//envoy/extensions/access_loggers/wasm/v3:pkg", + "//envoy/extensions/cache/simple_http_cache/v3alpha:pkg", "//envoy/extensions/clusters/aggregate/v3:pkg", "//envoy/extensions/clusters/dynamic_forward_proxy/v3:pkg", "//envoy/extensions/clusters/redis/v3:pkg", diff --git a/generated_api_shadow/envoy/extensions/cache/simple_http_cache/v3alpha/BUILD b/generated_api_shadow/envoy/extensions/cache/simple_http_cache/v3alpha/BUILD new file mode 100644 index 0000000000000..ee92fb652582e --- /dev/null +++ b/generated_api_shadow/envoy/extensions/cache/simple_http_cache/v3alpha/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/generated_api_shadow/envoy/extensions/cache/simple_http_cache/v3alpha/config.proto b/generated_api_shadow/envoy/extensions/cache/simple_http_cache/v3alpha/config.proto new file mode 100644 index 0000000000000..1b42e9b3f93d4 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/cache/simple_http_cache/v3alpha/config.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package envoy.extensions.cache.simple_http_cache.v3alpha; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.cache.simple_http_cache.v3alpha"; +option java_outer_classname = "ConfigProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: SimpleHttpCache CacheFilter storage plugin] + +// [#extension: envoy.cache.simple_http_cache] +message SimpleHttpCacheConfig { +} diff --git a/generated_api_shadow/envoy/extensions/filters/http/cache/v3alpha/cache.proto b/generated_api_shadow/envoy/extensions/filters/http/cache/v3alpha/cache.proto index 9e140a38a902e..5f0a5befa4bb3 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/cache/v3alpha/cache.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/cache/v3alpha/cache.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: HTTP Cache Filter] -// [#extension: envoy.filters.http.cache.simple_http_cache] +// [#extension: envoy.filters.http.cache] message CacheConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.cache.v2alpha.CacheConfig"; @@ -50,6 +50,7 @@ message CacheConfig { } // Config specific to the cache storage implementation. + // [#extension-category: envoy.filters.http.cache] google.protobuf.Any typed_config = 1 [(validate.rules).any = {required: true}]; // List of matching rules that defines allowed *Vary* headers. diff --git a/generated_api_shadow/envoy/extensions/filters/http/cache/v4alpha/cache.proto b/generated_api_shadow/envoy/extensions/filters/http/cache/v4alpha/cache.proto index 283e8f53aebb2..5297a3d15ef89 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/cache/v4alpha/cache.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/cache/v4alpha/cache.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: HTTP Cache Filter] -// [#extension: envoy.filters.http.cache.simple_http_cache] +// [#extension: envoy.filters.http.cache] message CacheConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.cache.v3alpha.CacheConfig"; @@ -50,6 +50,7 @@ message CacheConfig { } // Config specific to the cache storage implementation. + // [#extension-category: envoy.filters.http.cache] google.protobuf.Any typed_config = 1 [(validate.rules).any = {required: true}]; // List of matching rules that defines allowed *Vary* headers. diff --git a/source/BUILD b/source/BUILD deleted file mode 100644 index 27485e6a99f16..0000000000000 --- a/source/BUILD +++ /dev/null @@ -1,16 +0,0 @@ -load("@rules_proto//proto:defs.bzl", "proto_library") -load( - "//bazel:envoy_build_system.bzl", - "envoy_package", -) - -licenses(["notice"]) # Apache 2 - -envoy_package() - -proto_library( - name = "protos", - deps = [ - "//source/extensions/filters/http/cache/simple_http_cache:config", - ], -) diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 937994626eb5c..616d7714ef79a 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -214,8 +214,7 @@ EXTENSIONS = { # # CacheFilter plugins # - - "envoy.filters.http.cache.simple_http_cache": "//source/extensions/filters/http/cache/simple_http_cache:simple_http_cache_lib", + "envoy.cache.simple_http_cache": "//source/extensions/filters/http/cache/simple_http_cache:config", # # Internal redirect predicates diff --git a/source/extensions/filters/http/cache/simple_http_cache/BUILD b/source/extensions/filters/http/cache/simple_http_cache/BUILD index 65901a682ee5d..a9a500e1f9b8e 100644 --- a/source/extensions/filters/http/cache/simple_http_cache/BUILD +++ b/source/extensions/filters/http/cache/simple_http_cache/BUILD @@ -2,7 +2,6 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_extension", "envoy_extension_package", - "envoy_proto_library", ) licenses(["notice"]) # Apache 2 @@ -12,14 +11,13 @@ licenses(["notice"]) # Apache 2 envoy_extension_package() envoy_cc_extension( - name = "simple_http_cache_lib", + name = "config", srcs = ["simple_http_cache.cc"], hdrs = ["simple_http_cache.h"], - category = "envoy.filters.http", + category = "envoy.filters.http.cache", security_posture = "robust_to_untrusted_downstream_and_upstream", status = "wip", deps = [ - ":config_cc_proto", "//include/envoy/registry", "//include/envoy/runtime:runtime_interface", "//source/common/buffer:buffer_lib", @@ -29,10 +27,6 @@ envoy_cc_extension( "//source/common/protobuf", "//source/extensions/filters/http/cache:http_cache_lib", "//source/extensions/filters/http/common:pass_through_filter_lib", + "@envoy_api//envoy/extensions/cache/simple_http_cache/v3alpha:pkg_cc_proto", ], ) - -envoy_proto_library( - name = "config", - srcs = ["config.proto"], -) diff --git a/source/extensions/filters/http/cache/simple_http_cache/config.proto b/source/extensions/filters/http/cache/simple_http_cache/config.proto deleted file mode 100644 index 313b07a7f92ae..0000000000000 --- a/source/extensions/filters/http/cache/simple_http_cache/config.proto +++ /dev/null @@ -1,9 +0,0 @@ -syntax = "proto3"; - -package envoy.source.extensions.filters.http.cache; - -// [#protodoc-title: SimpleHttpCache CacheFilter storage plugin] -// [#extension: envoy.extensions.http.cache] - -message SimpleHttpCacheConfig { -} diff --git a/source/extensions/filters/http/cache/simple_http_cache/simple_http_cache.cc b/source/extensions/filters/http/cache/simple_http_cache/simple_http_cache.cc index 08de8b4fccbb6..7a7f3170504e9 100644 --- a/source/extensions/filters/http/cache/simple_http_cache/simple_http_cache.cc +++ b/source/extensions/filters/http/cache/simple_http_cache/simple_http_cache.cc @@ -1,12 +1,11 @@ #include "extensions/filters/http/cache/simple_http_cache/simple_http_cache.h" +#include "envoy/extensions/cache/simple_http_cache/v3alpha/config.pb.h" #include "envoy/registry/registry.h" #include "common/buffer/buffer_impl.h" #include "common/http/header_map_impl.h" -#include "source/extensions/filters/http/cache/simple_http_cache/config.pb.h" - namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -215,7 +214,7 @@ class SimpleHttpCacheFactory : public HttpCacheFactory { // From TypedFactory ProtobufTypes::MessagePtr createEmptyConfigProto() override { return std::make_unique< - envoy::source::extensions::filters::http::cache::SimpleHttpCacheConfig>(); + envoy::extensions::cache::simple_http_cache::v3alpha::SimpleHttpCacheConfig>(); } // From HttpCacheFactory HttpCache& diff --git a/source/extensions/filters/http/cache/simple_http_cache/simple_http_cache.h b/source/extensions/filters/http/cache/simple_http_cache/simple_http_cache.h index 4c2c1a85d0e04..03ee72e51e613 100644 --- a/source/extensions/filters/http/cache/simple_http_cache/simple_http_cache.h +++ b/source/extensions/filters/http/cache/simple_http_cache/simple_http_cache.h @@ -8,6 +8,9 @@ #include "absl/container/flat_hash_map.h" #include "absl/synchronization/mutex.h" +// included to make code_format happy +#include "envoy/extensions/cache/simple_http_cache/v3alpha/config.pb.h" + namespace Envoy { namespace Extensions { namespace HttpFilters { diff --git a/test/extensions/filters/http/cache/BUILD b/test/extensions/filters/http/cache/BUILD index 49d694720dec1..5b5398ab23aac 100644 --- a/test/extensions/filters/http/cache/BUILD +++ b/test/extensions/filters/http/cache/BUILD @@ -14,7 +14,7 @@ envoy_cc_test_library( deps = [ "//source/extensions/filters/http/cache:cache_headers_utils_lib", "//source/extensions/filters/http/cache:http_cache_lib", - "//source/extensions/filters/http/cache/simple_http_cache:simple_http_cache_lib", + "//source/extensions/filters/http/cache/simple_http_cache:config", ], ) @@ -39,7 +39,7 @@ envoy_extension_cc_test( deps = [ ":common", "//source/extensions/filters/http/cache:http_cache_lib", - "//source/extensions/filters/http/cache/simple_http_cache:simple_http_cache_lib", + "//source/extensions/filters/http/cache/simple_http_cache:config", "//test/mocks/http:http_mocks", "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", @@ -53,7 +53,7 @@ envoy_extension_cc_test( deps = [ ":common", "//source/extensions/filters/http/cache:cache_filter_lib", - "//source/extensions/filters/http/cache/simple_http_cache:simple_http_cache_lib", + "//source/extensions/filters/http/cache/simple_http_cache:config", "//test/mocks/server:factory_context_mocks", "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", @@ -76,10 +76,11 @@ envoy_extension_cc_test( extension_name = "envoy.filters.http.cache", deps = [ "//source/extensions/filters/http/cache:config", - "//source/extensions/filters/http/cache/simple_http_cache:simple_http_cache_lib", + "//source/extensions/filters/http/cache/simple_http_cache:config", "//test/mocks/http:http_mocks", "//test/mocks/server:factory_context_mocks", "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/cache/simple_http_cache/v3alpha:pkg_cc_proto", ], ) @@ -92,7 +93,7 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/cache:config", "//source/extensions/filters/http/cache:http_cache_lib", - "//source/extensions/filters/http/cache/simple_http_cache:simple_http_cache_lib", + "//source/extensions/filters/http/cache/simple_http_cache:config", "//test/integration:http_protocol_integration_lib", "//test/test_common:simulated_time_system_lib", ], diff --git a/test/extensions/filters/http/cache/cache_filter_integration_test.cc b/test/extensions/filters/http/cache/cache_filter_integration_test.cc index 4e4403a5fe452..e72bff19e7d8e 100644 --- a/test/extensions/filters/http/cache/cache_filter_integration_test.cc +++ b/test/extensions/filters/http/cache/cache_filter_integration_test.cc @@ -31,7 +31,7 @@ class CacheIntegrationTest : public Event::TestUsingSimulatedTime, typed_config: "@type": "type.googleapis.com/envoy.extensions.filters.http.cache.v3alpha.CacheConfig" typed_config: - "@type": "type.googleapis.com/envoy.source.extensions.filters.http.cache.SimpleHttpCacheConfig" + "@type": "type.googleapis.com/envoy.extensions.cache.simple_http_cache.v3alpha.SimpleHttpCacheConfig" )EOF"}; DateFormatter formatter_{"%a, %d %b %Y %H:%M:%S GMT"}; }; diff --git a/test/extensions/filters/http/cache/config_test.cc b/test/extensions/filters/http/cache/config_test.cc index 83459d8328eff..e254044455a00 100644 --- a/test/extensions/filters/http/cache/config_test.cc +++ b/test/extensions/filters/http/cache/config_test.cc @@ -1,4 +1,4 @@ -#include "source/extensions/filters/http/cache/simple_http_cache/config.pb.h" +#include "envoy/extensions/cache/simple_http_cache/v3alpha/config.pb.h" #include "extensions/filters/http/cache/cache_filter.h" #include "extensions/filters/http/cache/config.h" @@ -24,7 +24,7 @@ class CacheFilterFactoryTest : public ::testing::Test { TEST_F(CacheFilterFactoryTest, Basic) { config_.mutable_typed_config()->PackFrom( - envoy::source::extensions::filters::http::cache::SimpleHttpCacheConfig()); + envoy::extensions::cache::simple_http_cache::v3alpha::SimpleHttpCacheConfig()); Http::FilterFactoryCb cb = factory_.createFilterFactoryFromProto(config_, "stats", context_); Http::StreamFilterSharedPtr filter; EXPECT_CALL(filter_callback_, addStreamFilter(_)).WillOnce(::testing::SaveArg<0>(&filter)); diff --git a/test/extensions/filters/http/cache/simple_http_cache/BUILD b/test/extensions/filters/http/cache/simple_http_cache/BUILD index c5f39562f6e24..1e41993d7eb9f 100644 --- a/test/extensions/filters/http/cache/simple_http_cache/BUILD +++ b/test/extensions/filters/http/cache/simple_http_cache/BUILD @@ -11,9 +11,9 @@ envoy_package() envoy_extension_cc_test( name = "simple_http_cache_test", srcs = ["simple_http_cache_test.cc"], - extension_name = "envoy.filters.http.cache.simple_http_cache", + extension_name = "envoy.cache.simple_http_cache", deps = [ - "//source/extensions/filters/http/cache/simple_http_cache:simple_http_cache_lib", + "//source/extensions/filters/http/cache/simple_http_cache:config", "//test/extensions/filters/http/cache:common", "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/filters/http/cache/simple_http_cache/simple_http_cache_test.cc b/test/extensions/filters/http/cache/simple_http_cache/simple_http_cache_test.cc index 1717e24997545..cd534b1c6fe3f 100644 --- a/test/extensions/filters/http/cache/simple_http_cache/simple_http_cache_test.cc +++ b/test/extensions/filters/http/cache/simple_http_cache/simple_http_cache_test.cc @@ -220,7 +220,7 @@ TEST_F(SimpleHttpCacheTest, StreamingPut) { TEST(Registration, GetFactory) { HttpCacheFactory* factory = Registry::FactoryRegistry::getFactoryByType( - "envoy.source.extensions.filters.http.cache.SimpleHttpCacheConfig"); + "envoy.extensions.cache.simple_http_cache.v3alpha.SimpleHttpCacheConfig"); ASSERT_NE(factory, nullptr); envoy::extensions::filters::http::cache::v3alpha::CacheConfig config; config.mutable_typed_config()->PackFrom(*factory->createEmptyConfigProto()); diff --git a/tools/type_whisperer/BUILD b/tools/type_whisperer/BUILD index 43f317031f8df..e7d9f455e7fbe 100644 --- a/tools/type_whisperer/BUILD +++ b/tools/type_whisperer/BUILD @@ -75,7 +75,6 @@ file_descriptor_set_text( name = "all_protos_with_ext_pb_text", with_external_deps = True, deps = [ - "//source:protos", "@envoy_api_canonical//:all_protos", ], ) From 3910848dbdd299e166e654bc98f88879dad9ae12 Mon Sep 17 00:00:00 2001 From: DavidKorczynski Date: Sun, 2 May 2021 21:25:30 +0100 Subject: [PATCH 123/209] docs: add fuzzing improvement report. (#16232) Adds the fuzzing work recently carried out on the Envoy project. Signed-off-by: davkor --- README.md | 4 +++- .../audit_cure53_2018.pdf} | Bin docs/security/audit_fuzzer_adalogics_2021.pdf | Bin 0 -> 659628 bytes 3 files changed, 3 insertions(+), 1 deletion(-) rename docs/{SECURITY_AUDIT.pdf => security/audit_cure53_2018.pdf} (100%) create mode 100644 docs/security/audit_fuzzer_adalogics_2021.pdf diff --git a/README.md b/README.md index 27b25254b9cf9..8826efb9e4c3d 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,9 @@ Google calendar is here: https://goo.gl/PkDijT ### Security Audit -A third party security audit was performed by Cure53, you can see the full report [here](docs/SECURITY_AUDIT.pdf). +There has been several third party engagements focused on Envoy security: +* In 2018 Cure53 performed a security audit, [full report](docs/security/audit_cure53_2018.pdf). +* In 2021 Ada Logics performed an audit on our fuzzing infrastructure with recommendations for improvements, [full report](docs/security/audit_fuzzer_adalogics_2021.pdf). ### Reporting security vulnerabilities diff --git a/docs/SECURITY_AUDIT.pdf b/docs/security/audit_cure53_2018.pdf similarity index 100% rename from docs/SECURITY_AUDIT.pdf rename to docs/security/audit_cure53_2018.pdf diff --git a/docs/security/audit_fuzzer_adalogics_2021.pdf b/docs/security/audit_fuzzer_adalogics_2021.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c0dfe56fa3590c0160566af8bafac16863976dbe GIT binary patch literal 659628 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 z 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 GT`r?dXeehakACM5Mr6YZLb*~-p`y9JW^|1 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%0D}pJN=qR2b*A#p;l1q;3FW>$Z_kb~ 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 z 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ⅅnOor$=R` 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 Date: Sun, 2 May 2021 13:45:57 -0700 Subject: [PATCH 124/209] tls: update BoringSSL to c5ad6dcb (4491). (#16104) Fixes #16105. Signed-off-by: Piotr Sikora --- bazel/repository_locations.bzl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 2f3f6c884bcd6..eaf034c2bf7de 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -77,17 +77,17 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_desc = "Minimal OpenSSL fork", project_url = "https://github.com/google/boringssl", # To update BoringSSL, which tracks Chromium releases: - # 1. Open https://omahaproxy.appspot.com/ and note of linux/stable release. + # 1. Open https://omahaproxy.appspot.com/ and note of linux/dev release. # 2. Open https://chromium.googlesource.com/chromium/src/+/refs/tags//DEPS and note . # 3. Find a commit in BoringSSL's "master-with-bazel" branch that merges . # - # chromium-90.0.4395.0(linux/dev) - version = "b049eae83d25977661556dcd913b35fbafb3a93a", - sha256 = "d78f7b11b8665feea1b6def8e6f235ad8671db8de950f5429f1bf2b3503b3894", + # chromium-92.0.4491.6 (linux/dev) + version = "c5ad6dcb65e532589e8acb9e9adbde62463af13d", + sha256 = "6b4674999af85c4a19f2b51132db3507520070923cd967bb1cd157d43b3f68d9", strip_prefix = "boringssl-{version}", urls = ["https://github.com/google/boringssl/archive/{version}.tar.gz"], use_category = ["controlplane", "dataplane_core"], - release_date = "2021-01-25", + release_date = "2021-04-22", cpe = "cpe:2.3:a:google:boringssl:*", ), boringssl_fips = dict( From e22e4a1011cf02003adea4736b0bbc35d9758db8 Mon Sep 17 00:00:00 2001 From: phlax Date: Sun, 2 May 2021 21:51:20 +0100 Subject: [PATCH 125/209] docs: Remove api v2 (#16077) Signed-off-by: Ryan Northey --- api/BUILD | 6 ------ api/xds_protocol.rst | 6 +++--- docs/build.sh | 17 +++-------------- docs/conf.py | 2 -- .../api-v3/config/filter/thrift/thrift.rst | 1 - .../config/health_checker/health_checker.rst | 1 - .../resource_monitor/resource_monitor.rst | 1 - docs/root/api-v3/config/retry/retry.rst | 1 - .../configuration/http/http_conn_man/rds.rst | 2 +- .../configuration/http/http_conn_man/vhds.rst | 2 +- docs/root/configuration/listeners/lds.rst | 2 +- .../dubbo_filters/router_filter.rst | 2 +- docs/root/configuration/overview/xds_api.rst | 18 +++--------------- .../upstream/cluster_manager/cds.rst | 2 +- docs/v2-api-header.rst | 5 ----- 15 files changed, 14 insertions(+), 54 deletions(-) delete mode 100644 docs/v2-api-header.rst diff --git a/api/BUILD b/api/BUILD index 7a6671dd681f9..d8782fb96b230 100644 --- a/api/BUILD +++ b/api/BUILD @@ -133,18 +133,12 @@ proto_library( "//envoy/config/common/matcher/v3:pkg", "//envoy/config/core/v3:pkg", "//envoy/config/endpoint/v3:pkg", - "//envoy/config/filter/thrift/router/v2alpha1:pkg", "//envoy/config/grpc_credential/v3:pkg", - "//envoy/config/health_checker/redis/v2:pkg", "//envoy/config/listener/v3:pkg", "//envoy/config/metrics/v3:pkg", "//envoy/config/overload/v3:pkg", "//envoy/config/ratelimit/v3:pkg", "//envoy/config/rbac/v3:pkg", - "//envoy/config/resource_monitor/fixed_heap/v2alpha:pkg", - "//envoy/config/resource_monitor/injected_resource/v2alpha:pkg", - "//envoy/config/retry/omit_canary_hosts/v2:pkg", - "//envoy/config/retry/previous_hosts/v2:pkg", "//envoy/config/route/v3:pkg", "//envoy/config/tap/v3:pkg", "//envoy/config/trace/v3:pkg", diff --git a/api/xds_protocol.rst b/api/xds_protocol.rst index edd12f6bb5c39..71c5f646627bb 100644 --- a/api/xds_protocol.rst +++ b/api/xds_protocol.rst @@ -772,12 +772,12 @@ Later the xDS client spontaneously requests the "wc" resource. On reconnect the Incremental xDS client may tell the server of its known resources to avoid resending them over the network by sending them in -:ref:`initial_resource_versions `. +:ref:`initial_resource_versions `. Because no state is assumed to be preserved from the previous stream, the reconnecting client must provide the server with all resource names it is interested in. Note that for wildcard requests (CDS/LDS/SRDS), the request must have no resources in both -:ref:`resource_names_subscribe ` and -:ref:`resource_names_unsubscribe `. +:ref:`resource_names_subscribe ` and +:ref:`resource_names_unsubscribe `. .. figure:: diagrams/incremental-reconnect.svg :alt: Incremental reconnect example diff --git a/docs/build.sh b/docs/build.sh index 7b6eee6ba76d4..fc3e89d117bdc 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -97,11 +97,8 @@ function generate_api_rst() { # Fill in boiler plate for extensions that have google.protobuf.Empty as their # config. We only have v2 support here for version history anchors, which don't point at any empty # configs. - if [[ "${API_VERSION}" != "v2" ]] - then - bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/protodoc:generate_empty \ - "${PWD}"/docs/empty_extensions.json "${GENERATED_RST_DIR}/api-${API_VERSION}"/config - fi + bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/protodoc:generate_empty \ + "${PWD}"/docs/empty_extensions.json "${GENERATED_RST_DIR}/api-${API_VERSION}"/config # We do ** matching below to deal with Bazel cache blah (source proto artifacts # are nested inside source package targets). @@ -129,18 +126,10 @@ function generate_api_rst() { declare DST="${GENERATED_RST_DIR}/api-${API_VERSION}/${PROTO_FILE_CANONICAL#envoy/}".rst mkdir -p "$(dirname "${DST}")" - if [[ "${API_VERSION}" == "v2" ]] - then - cat docs/v2-api-header.rst "${SRC}" > "$(dirname "${DST}")/$(basename "${SRC}")" - else - cp -f "${SRC}" "$(dirname "${DST}")" - fi + cp -f "${SRC}" "$(dirname "${DST}")" done } -# TODO(htuch): remove v2 support once we have a good story for version history RST links that refer -# to v2 APIs. -generate_api_rst v2 generate_api_rst v3 # Fixup anchors and references in v3 so they form a distinct namespace. diff --git a/docs/conf.py b/docs/conf.py index 5fcc251d00603..6524873d43aa9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -147,8 +147,6 @@ def setup(app): '_venv', 'Thumbs.db', '.DS_Store', - 'api-v2/api/v2/endpoint/load_report.proto.rst', - 'api-v2/service/discovery/v2/hds.proto.rst', ] # The reST default role (used for this markup: `text`) to use for all diff --git a/docs/root/api-v3/config/filter/thrift/thrift.rst b/docs/root/api-v3/config/filter/thrift/thrift.rst index 7e40e6f951942..688961fd16c1d 100644 --- a/docs/root/api-v3/config/filter/thrift/thrift.rst +++ b/docs/root/api-v3/config/filter/thrift/thrift.rst @@ -5,5 +5,4 @@ Thrift filters :glob: :maxdepth: 2 - router/v2alpha1/* ../../../extensions/filters/network/thrift_proxy/**/v3/* diff --git a/docs/root/api-v3/config/health_checker/health_checker.rst b/docs/root/api-v3/config/health_checker/health_checker.rst index b533d675ad7b1..4f4230a6137f5 100644 --- a/docs/root/api-v3/config/health_checker/health_checker.rst +++ b/docs/root/api-v3/config/health_checker/health_checker.rst @@ -5,5 +5,4 @@ Health checkers :glob: :maxdepth: 2 - */v2/* ../../extensions/health_checkers/*/v3/* diff --git a/docs/root/api-v3/config/resource_monitor/resource_monitor.rst b/docs/root/api-v3/config/resource_monitor/resource_monitor.rst index c755d656cc8b3..767f4ffeb1827 100644 --- a/docs/root/api-v3/config/resource_monitor/resource_monitor.rst +++ b/docs/root/api-v3/config/resource_monitor/resource_monitor.rst @@ -8,4 +8,3 @@ Resource monitors :maxdepth: 2 ../../extensions/resource_monitors/*/v3/* - */v2alpha/* diff --git a/docs/root/api-v3/config/retry/retry.rst b/docs/root/api-v3/config/retry/retry.rst index 2ba4572ab2edc..b26870b1e9567 100644 --- a/docs/root/api-v3/config/retry/retry.rst +++ b/docs/root/api-v3/config/retry/retry.rst @@ -6,5 +6,4 @@ Retry Predicates :maxdepth: 2 */empty/* - */v2/* ../../extensions/retry/**/v3/* diff --git a/docs/root/configuration/http/http_conn_man/rds.rst b/docs/root/configuration/http/http_conn_man/rds.rst index d124d694c50b9..06990756ae337 100644 --- a/docs/root/configuration/http/http_conn_man/rds.rst +++ b/docs/root/configuration/http/http_conn_man/rds.rst @@ -11,7 +11,7 @@ fetch its own route configuration via the API. Optionally, the :ref:`virtual host discovery service ` can be used to fetch virtual hosts separately from the route configuration. -* :ref:`v2 API reference ` +* :ref:`v3 API reference ` Statistics ---------- diff --git a/docs/root/configuration/http/http_conn_man/vhds.rst b/docs/root/configuration/http/http_conn_man/vhds.rst index 6a92c94d13191..45ed3ea7d8a5a 100644 --- a/docs/root/configuration/http/http_conn_man/vhds.rst +++ b/docs/root/configuration/http/http_conn_man/vhds.rst @@ -69,7 +69,7 @@ on-demand :ref:`scoped RDS ` +* :ref:`v3 API reference ` Statistics ---------- diff --git a/docs/root/configuration/listeners/lds.rst b/docs/root/configuration/listeners/lds.rst index f5b8c778e95e3..21560e958c6f2 100644 --- a/docs/root/configuration/listeners/lds.rst +++ b/docs/root/configuration/listeners/lds.rst @@ -36,7 +36,7 @@ The semantics of listener updates are as follows: Configuration ------------- -* :ref:`v3 LDS API ` +* :ref:`v3 LDS API ` Statistics ---------- diff --git a/docs/root/configuration/other_protocols/dubbo_filters/router_filter.rst b/docs/root/configuration/other_protocols/dubbo_filters/router_filter.rst index 615a0b03da271..02e3a1fe264f9 100644 --- a/docs/root/configuration/other_protocols/dubbo_filters/router_filter.rst +++ b/docs/root/configuration/other_protocols/dubbo_filters/router_filter.rst @@ -7,5 +7,5 @@ The router filter implements Dubbo forwarding. It will be used in almost all Dub scenarios. The filter's main job is to follow the instructions specified in the configured :ref:`route table `. -* :ref:`v3 API reference ` +* :ref:`v3 API reference ` * This filter should be configured with the name *envoy.filters.dubbo.router*. diff --git a/docs/root/configuration/overview/xds_api.rst b/docs/root/configuration/overview/xds_api.rst index dca8e99265c3d..919e4ec4a3af4 100644 --- a/docs/root/configuration/overview/xds_api.rst +++ b/docs/root/configuration/overview/xds_api.rst @@ -9,14 +9,13 @@ REST-JSON cases, a :ref:`DiscoveryRequest ` received following the :ref:`xDS protocol `. -Below we describe endpoints for the v2 and v3 transport API versions. +Below we describe endpoints for the v3 transport API. -.. _v2_grpc_streaming_endpoints: +.. _v3_grpc_streaming_endpoints: gRPC streaming endpoints ^^^^^^^^^^^^^^^^^^^^^^^^ -.. http:post:: /envoy.api.v2.ClusterDiscoveryService/StreamClusters .. http:post:: /envoy.service.cluster.v3.ClusterDiscoveryService/StreamClusters See :repo:`cds.proto ` for the service definition. This is used by Envoy @@ -37,7 +36,6 @@ is set in the :ref:`dynamic_resources ` of the :ref:`Bootstrap ` config. -.. http:post:: /envoy.api.v2.EndpointDiscoveryService/StreamEndpoints .. http:post:: /envoy.service.endpoint.v3.EndpointDiscoveryService/StreamEndpoints See :repo:`eds.proto @@ -59,7 +57,6 @@ is set in the :ref:`eds_cluster_config ` field of the :ref:`Cluster ` config. -.. http:post:: /envoy.api.v2.ListenerDiscoveryService/StreamListeners .. http:post:: /envoy.service.listener.v3.ListenerDiscoveryService/StreamListeners See :repo:`lds.proto @@ -81,7 +78,6 @@ is set in the :ref:`dynamic_resources ` of the :ref:`Bootstrap ` config. -.. http:post:: /envoy.api.v2.RouteDiscoveryService/StreamRoutes .. http:post:: /envoy.service.route.v3.RouteDiscoveryService/StreamRoutes See :repo:`rds.proto @@ -105,7 +101,6 @@ is set in the :ref:`rds of the :ref:`HttpConnectionManager ` config. -.. http:post:: /envoy.api.v2.ScopedRoutesDiscoveryService/StreamScopedRoutes .. http:post:: /envoy.service.route.v3.ScopedRoutesDiscoveryService/StreamScopedRoutes See :repo:`srds.proto @@ -130,7 +125,6 @@ is set in the :ref:`scoped_routes field of the :ref:`HttpConnectionManager ` config. -.. http:post:: /envoy.service.discovery.v2.SecretDiscoveryService/StreamSecrets .. http:post:: /envoy.service.secret.v3.SecretDiscoveryService/StreamSecrets See :repo:`sds.proto @@ -152,7 +146,6 @@ for the service definition. This is used by Envoy as a client when is set inside a :ref:`SdsSecretConfig ` message. This message is used in various places such as the :ref:`CommonTlsContext `. -.. http:post:: /envoy.service.discovery.v2.RuntimeDiscoveryService/StreamRuntime .. http:post:: /envoy.service.runtime.v3.RuntimeDiscoveryService/StreamRuntime See :repo:`rtds.proto @@ -177,7 +170,6 @@ field. REST endpoints ^^^^^^^^^^^^^^ -.. http:post:: /v2/discovery:clusters .. http:post:: /v3/discovery:clusters See :repo:`cds.proto @@ -197,7 +189,6 @@ is set in the :ref:`dynamic_resources ` of the :ref:`Bootstrap ` config. -.. http:post:: /v2/discovery:endpoints .. http:post:: /v3/discovery:endpoints See :repo:`eds.proto @@ -217,7 +208,6 @@ is set in the :ref:`eds_cluster_config ` field of the :ref:`Cluster ` config. -.. http:post:: /v2/discovery:listeners .. http:post:: /v3/discovery:listeners See :repo:`lds.proto @@ -237,7 +227,6 @@ is set in the :ref:`dynamic_resources ` of the :ref:`Bootstrap ` config. -.. http:post:: /v2/discovery:routes .. http:post:: /v3/discovery:routes See :repo:`rds.proto @@ -295,7 +284,6 @@ ADS is only available for gRPC streaming (not REST) and is described more fully in :ref:`xDS ` document. The gRPC endpoint is: -.. http:post:: /envoy.service.discovery.v2.AggregatedDiscoveryService/StreamAggregatedResources .. http:post:: /envoy.service.discovery.v3.AggregatedDiscoveryService/StreamAggregatedResources See :repo:`discovery.proto @@ -315,7 +303,7 @@ is set in the :ref:`dynamic_resources ` of the :ref:`Bootstrap ` config. -When this is set, any of the configuration sources :ref:`above ` can +When this is set, any of the configuration sources :ref:`above ` can be set to use the ADS channel. For example, a LDS config could be changed from .. code-block:: yaml diff --git a/docs/root/configuration/upstream/cluster_manager/cds.rst b/docs/root/configuration/upstream/cluster_manager/cds.rst index 9d747f4c8349c..2449f4ae52342 100644 --- a/docs/root/configuration/upstream/cluster_manager/cds.rst +++ b/docs/root/configuration/upstream/cluster_manager/cds.rst @@ -12,7 +12,7 @@ clusters depending on what is required. Any clusters that are statically defined within the Envoy configuration cannot be modified or removed via the CDS API. -* :ref:`v3 CDS API ` +* :ref:`v3 CDS API ` Statistics ---------- diff --git a/docs/v2-api-header.rst b/docs/v2-api-header.rst deleted file mode 100644 index 2d23070961264..0000000000000 --- a/docs/v2-api-header.rst +++ /dev/null @@ -1,5 +0,0 @@ -:orphan: - -.. warning:: - The v2 xDS API is not supported in Envoy v1.18.0 and above. - From a058c6efe427a4d804d1924ae0daa3f6cbab633c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Korn=C3=A9l=20D=C3=A1vid?= <47784437+davidkornel@users.noreply.github.com> Date: Mon, 3 May 2021 05:37:53 +0200 Subject: [PATCH 126/209] udp: add new key based hash policy (#15967) Signed-off-by: Kornel David --- .../filters/udp/udp_proxy/v3/udp_proxy.proto | 7 +++ docs/root/version_history/current.rst | 1 + .../filters/udp/udp_proxy/v3/udp_proxy.proto | 7 +++ .../filters/udp/udp_proxy/hash_policy_impl.cc | 20 +++++++ .../udp/udp_proxy/hash_policy_impl_test.cc | 21 +++++++ .../udp/udp_proxy/udp_proxy_filter_test.cc | 55 +++++++++++++++++++ tools/spelling/spelling_dictionary.txt | 2 + 7 files changed, 113 insertions(+) diff --git a/api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto b/api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto index cb28ea189a281..9d410e28afe3d 100644 --- a/api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto +++ b/api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto @@ -33,6 +33,13 @@ message UdpProxyConfig { // The source IP will be used to compute the hash used by hash-based load balancing algorithms. bool source_ip = 1 [(validate.rules).bool = {const: true}]; + + // A given key will be used to compute the hash used by hash-based load balancing algorithms. + // In certain cases there is a need to direct different UDP streams jointly towards the selected set of endpoints. + // A possible use-case is VoIP telephony, where media (RTP) and its corresponding control (RTCP) belong to the same logical session, + // although they travel in separate streams. To ensure that these pair of streams are load-balanced on session level + // (instead of individual stream level), dynamically created listeners can use the same hash key for each stream in the session. + string key = 2 [(validate.rules).string = {min_len: 1}]; } } diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 3f6a6f1c0cfbb..732d412372c50 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -48,6 +48,7 @@ New Features ------------ * metric service: added support for sending metric tags as labels. This can be enabled by setting the :ref:`emit_tags_as_labels ` field to true. +* udp_proxy: added :ref:`key ` as another hash policy to support hash based routing on any given key. Deprecated ---------- diff --git a/generated_api_shadow/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto b/generated_api_shadow/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto index cb28ea189a281..9d410e28afe3d 100644 --- a/generated_api_shadow/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto +++ b/generated_api_shadow/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto @@ -33,6 +33,13 @@ message UdpProxyConfig { // The source IP will be used to compute the hash used by hash-based load balancing algorithms. bool source_ip = 1 [(validate.rules).bool = {const: true}]; + + // A given key will be used to compute the hash used by hash-based load balancing algorithms. + // In certain cases there is a need to direct different UDP streams jointly towards the selected set of endpoints. + // A possible use-case is VoIP telephony, where media (RTP) and its corresponding control (RTCP) belong to the same logical session, + // although they travel in separate streams. To ensure that these pair of streams are load-balanced on session level + // (instead of individual stream level), dynamically created listeners can use the same hash key for each stream in the session. + string key = 2 [(validate.rules).string = {min_len: 1}]; } } diff --git a/source/extensions/filters/udp/udp_proxy/hash_policy_impl.cc b/source/extensions/filters/udp/udp_proxy/hash_policy_impl.cc index e131a19833cdb..5f6551c521e41 100644 --- a/source/extensions/filters/udp/udp_proxy/hash_policy_impl.cc +++ b/source/extensions/filters/udp/udp_proxy/hash_policy_impl.cc @@ -1,6 +1,7 @@ #include "extensions/filters/udp/udp_proxy/hash_policy_impl.h" #include "common/common/assert.h" +#include "common/common/macros.h" namespace Envoy { namespace Extensions { @@ -20,6 +21,22 @@ class SourceIpHashMethod : public HashPolicyImpl::HashMethod { } }; +class KeyHashMethod : public HashPolicyImpl::HashMethod { +public: + explicit KeyHashMethod(const std::string& key) : hash_{HashUtil::xxHash64(key)} { + ASSERT(!key.empty()); + } + + absl::optional + evaluate(const Network::Address::Instance& downstream_addr) const override { + UNREFERENCED_PARAMETER(downstream_addr); + return hash_; + } + +private: + const uint64_t hash_; +}; + HashPolicyImpl::HashPolicyImpl( const absl::Span& hash_policies) { ASSERT(hash_policies.size() == 1); @@ -27,6 +44,9 @@ HashPolicyImpl::HashPolicyImpl( case UdpProxyConfig::HashPolicy::PolicySpecifierCase::kSourceIp: hash_impl_ = std::make_unique(); break; + case UdpProxyConfig::HashPolicy::PolicySpecifierCase::kKey: + hash_impl_ = std::make_unique(hash_policies[0]->key()); + break; default: NOT_REACHED_GCOVR_EXCL_LINE; } diff --git a/test/extensions/filters/udp/udp_proxy/hash_policy_impl_test.cc b/test/extensions/filters/udp/udp_proxy/hash_policy_impl_test.cc index 98fbbae740785..5090f38f09efa 100644 --- a/test/extensions/filters/udp/udp_proxy/hash_policy_impl_test.cc +++ b/test/extensions/filters/udp/udp_proxy/hash_policy_impl_test.cc @@ -1,3 +1,5 @@ +#include + #include "envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.pb.h" #include "common/common/hash.h" @@ -50,6 +52,15 @@ class HashPolicyImplSourceIpTest : public HashPolicyImplBaseTest { const Network::Address::InstanceConstSharedPtr pipe_address_; }; +class HashPolicyImplKeyTest : public HashPolicyImplBaseTest { +public: + HashPolicyImplKeyTest() : key_("key") {} + + void additionalSetup() override { hash_policy_config_->set_key(key_); } + + const std::string key_; +}; + // Check invalid policy type TEST_F(HashPolicyImplBaseTest, NotSupportedPolicy) { EXPECT_DEATH(setup(), ".*panic: not reached.*"); @@ -74,6 +85,16 @@ TEST_F(HashPolicyImplSourceIpTest, SourceIpWithUnixDomainSocketType) { EXPECT_FALSE(hash.has_value()); } +// Check if generate correct hash +TEST_F(HashPolicyImplKeyTest, KeyHash) { + setup(); + + auto generated_hash = HashUtil::xxHash64(key_); + auto hash = hash_policy_->generateHash(*peer_address_); + + EXPECT_EQ(generated_hash, hash.value()); +} + } // namespace } // namespace UdpProxy } // namespace UdpFilters diff --git a/test/extensions/filters/udp/udp_proxy/udp_proxy_filter_test.cc b/test/extensions/filters/udp/udp_proxy/udp_proxy_filter_test.cc index 618b07a82967b..ddcb11b891572 100644 --- a/test/extensions/filters/udp/udp_proxy/udp_proxy_filter_test.cc +++ b/test/extensions/filters/udp/udp_proxy/udp_proxy_filter_test.cc @@ -796,6 +796,61 @@ cluster: fake_cluster test_sessions_[0].recvDataFromUpstream("world"); } +// Make sure hash policy with key is created. +TEST_F(UdpProxyFilterTest, HashPolicyWithKey) { + InSequence s; + + setup(R"EOF( +stat_prefix: foo +cluster: fake_cluster +hash_policies: +- key: "key" + )EOF"); + + EXPECT_NE(nullptr, config_->hashPolicy()); +} + +// Make sure validation fails if key is an empty string. +TEST_F(UdpProxyFilterTest, ValidateHashPolicyWithKey) { + InSequence s; + auto config = R"EOF( +stat_prefix: foo +cluster: fake_cluster +hash_policies: +- key: "" + )EOF"; + + EXPECT_THROW_WITH_REGEX(setup(config), EnvoyException, + "caused by HashPolicyValidationError\\.Key"); +} + +// Expect correct hash is created if hash_policy with key is mentioned. +TEST_F(UdpProxyFilterTest, HashWithKey) { + InSequence s; + + setup(R"EOF( +stat_prefix: foo +cluster: fake_cluster +hash_policies: +- key: "key" + )EOF"); + + auto host = createHost(upstream_address_); + auto generated_hash = HashUtil::xxHash64("key"); + EXPECT_CALL(cluster_manager_.thread_local_cluster_.lb_, chooseHost(_)) + .WillOnce(Invoke([host, generated_hash]( + Upstream::LoadBalancerContext* context) -> Upstream::HostConstSharedPtr { + auto hash = context->computeHashKey(); + EXPECT_TRUE(hash.has_value()); + EXPECT_EQ(generated_hash, hash.value()); + return host; + })); + expectSessionCreate(upstream_address_); + test_sessions_[0].expectWriteToUpstream("hello"); + recvDataFromDownstream("10.0.0.1:1000", "10.0.0.2:80", "hello"); + test_sessions_[0].recvDataFromUpstream("world"); +} + } // namespace } // namespace UdpProxy } // namespace UdpFilters diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 16b5fac738701..878eeb1d81fe5 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -34,6 +34,8 @@ HEXDIG HEXDIGIT OWS Preconnecting +RTCP +RTP STATNAME SkyWalking TIDs From c86cfb55352ab8c7b0e513962ab84bb99a13c989 Mon Sep 17 00:00:00 2001 From: Oleksiy Pylypenko Date: Mon, 3 May 2021 10:20:31 +0200 Subject: [PATCH 127/209] Adding distroless image to Envoy CI pipeline (#16268) * ci: Adding distroless image Signed-off-by: Oleksiy Pylypenko --- ci/Dockerfile-envoy-distroless | 10 ++++++++++ ci/docker_ci.sh | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 ci/Dockerfile-envoy-distroless diff --git a/ci/Dockerfile-envoy-distroless b/ci/Dockerfile-envoy-distroless new file mode 100644 index 0000000000000..98bc2cfca8a97 --- /dev/null +++ b/ci/Dockerfile-envoy-distroless @@ -0,0 +1,10 @@ +FROM gcr.io/distroless/base-debian10:nonroot + +ADD configs/envoyproxy_io_proxy.yaml /etc/envoy/envoy.yaml + +ARG ENVOY_BINARY_SUFFIX=_stripped +ADD linux/amd64/build_release${ENVOY_BINARY_SUFFIX}/* /usr/local/bin/ + +EXPOSE 10000 + +CMD ["envoy", "-c", "/etc/envoy/envoy.yaml"] diff --git a/ci/docker_ci.sh b/ci/docker_ci.sh index 439647ebe779b..ff75b56518a24 100755 --- a/ci/docker_ci.sh +++ b/ci/docker_ci.sh @@ -125,7 +125,7 @@ if is_windows; then BUILD_COMMAND=("build") else # "-google-vrp" must come afer "" to ensure we rebuild the local base image dependency. - BUILD_TYPES=("" "-debug" "-alpine" "-google-vrp") + BUILD_TYPES=("" "-debug" "-alpine" "-distroless" "-google-vrp") # Configure docker-buildx tools BUILD_COMMAND=("buildx" "build") From 6a2e11235bba6812d67a59e9d54e61521f25c40e Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 3 May 2021 09:14:48 -0400 Subject: [PATCH 128/209] http3: respecting header number limits (#15970) Respecting upstream and downstream caps on number of headers for HTTP/3 Risk Level: n/a (http/3) Testing: turned up integration tests Docs Changes: n/a Release Notes: n/a #14829 among others. Signed-off-by: Alyssa Wilk --- .../http_conn_man/response_code_details.rst | 3 ++ source/common/http/codec_client.cc | 2 +- source/common/quic/codec_impl.cc | 6 ++-- source/common/quic/codec_impl.h | 5 +-- .../common/quic/envoy_quic_client_stream.cc | 21 ++++++++---- source/common/quic/envoy_quic_client_stream.h | 3 +- .../common/quic/envoy_quic_server_stream.cc | 8 ++++- source/common/quic/envoy_quic_stream.h | 17 ---------- source/common/quic/envoy_quic_utils.h | 32 +++++++++++++++++-- .../quic_filter_manager_connection_impl.h | 7 ++++ .../network/http_connection_manager/config.cc | 2 +- .../quic/envoy_quic_client_session_test.cc | 2 +- .../quic/envoy_quic_server_session_test.cc | 2 +- test/common/quic/envoy_quic_utils_test.cc | 7 ++-- test/integration/fake_upstream.cc | 3 +- test/integration/http_integration.cc | 2 +- .../multiplexed_upstream_integration_test.cc | 6 ++-- test/integration/protocol_integration_test.cc | 19 +++++++---- 18 files changed, 97 insertions(+), 50 deletions(-) diff --git a/docs/root/configuration/http/http_conn_man/response_code_details.rst b/docs/root/configuration/http/http_conn_man/response_code_details.rst index ebca3a321f2e7..24d94164ca78f 100644 --- a/docs/root/configuration/http/http_conn_man/response_code_details.rst +++ b/docs/root/configuration/http/http_conn_man/response_code_details.rst @@ -112,3 +112,6 @@ All http3 details are rooted at *http3.* http3.invalid_header_field, One of the HTTP/3 headers was invalid http3.headers_too_large, The size of headers (or trailers) exceeded the configured limits http3.unexpected_underscore, Envoy was configured to drop or reject requests with header keys beginning with underscores. + http3.too_many_headers, Either incoming request or response headers contained too many headers. + http3.too_many_trailers, Either incoming request or response trailers contained too many entries. + diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index 7ea1916839b6d..3353ff81d0904 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -193,7 +193,7 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne auto& quic_session = dynamic_cast(*connection_); codec_ = std::make_unique( quic_session, *this, host->cluster().http3CodecStats(), host->cluster().http3Options(), - Http::DEFAULT_MAX_REQUEST_HEADERS_KB); + Http::DEFAULT_MAX_REQUEST_HEADERS_KB, host->cluster().maxResponseHeadersCount()); // Initialize the session after max request header size is changed in above http client // connection creation. quic_session.Initialize(); diff --git a/source/common/quic/codec_impl.cc b/source/common/quic/codec_impl.cc index 5cdae4dd8e962..6ec3c355e0232 100644 --- a/source/common/quic/codec_impl.cc +++ b/source/common/quic/codec_impl.cc @@ -22,7 +22,7 @@ QuicHttpServerConnectionImpl::QuicHttpServerConnectionImpl( EnvoyQuicServerSession& quic_session, Http::ServerConnectionCallbacks& callbacks, Http::Http3::CodecStats& stats, const envoy::config::core::v3::Http3ProtocolOptions& http3_options, - const uint32_t max_request_headers_kb, + const uint32_t max_request_headers_kb, const uint32_t max_request_headers_count, envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction headers_with_underscores_action) : QuicHttpConnectionImplBase(quic_session, stats), quic_server_session_(quic_session) { @@ -30,6 +30,7 @@ QuicHttpServerConnectionImpl::QuicHttpServerConnectionImpl( quic_session.setHttp3Options(http3_options); quic_session.setHeadersWithUnderscoreAction(headers_with_underscores_action); quic_session.setHttpConnectionCallbacks(callbacks); + quic_session.setMaxIncomingHeadersCount(max_request_headers_count); quic_session.set_max_inbound_header_list_size(max_request_headers_kb * 1024u); } @@ -69,11 +70,12 @@ QuicHttpClientConnectionImpl::QuicHttpClientConnectionImpl( EnvoyQuicClientSession& session, Http::ConnectionCallbacks& callbacks, Http::Http3::CodecStats& stats, const envoy::config::core::v3::Http3ProtocolOptions& http3_options, - const uint32_t max_request_headers_kb) + const uint32_t max_request_headers_kb, const uint32_t max_response_headers_count) : QuicHttpConnectionImplBase(session, stats), quic_client_session_(session) { session.setCodecStats(stats); session.setHttp3Options(http3_options); session.setHttpConnectionCallbacks(callbacks); + session.setMaxIncomingHeadersCount(max_response_headers_count); session.set_max_inbound_header_list_size(max_request_headers_kb * 1024); } diff --git a/source/common/quic/codec_impl.h b/source/common/quic/codec_impl.h index efd38ea18c429..1dca7b65931ab 100644 --- a/source/common/quic/codec_impl.h +++ b/source/common/quic/codec_impl.h @@ -43,7 +43,7 @@ class QuicHttpServerConnectionImpl : public QuicHttpConnectionImplBase, EnvoyQuicServerSession& quic_session, Http::ServerConnectionCallbacks& callbacks, Http::Http3::CodecStats& stats, const envoy::config::core::v3::Http3ProtocolOptions& http3_options, - const uint32_t max_request_headers_kb, + const uint32_t max_request_headers_kb, const uint32_t max_request_headers_count, envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction headers_with_underscores_action); @@ -63,7 +63,8 @@ class QuicHttpClientConnectionImpl : public QuicHttpConnectionImplBase, QuicHttpClientConnectionImpl(EnvoyQuicClientSession& session, Http::ConnectionCallbacks& callbacks, Http::Http3::CodecStats& stats, const envoy::config::core::v3::Http3ProtocolOptions& http3_options, - const uint32_t max_request_headers_kb); + const uint32_t max_request_headers_kb, + const uint32_t max_response_headers_count); // Http::ClientConnection Http::RequestEncoder& newStream(Http::ResponseDecoder& response_decoder) override; diff --git a/source/common/quic/envoy_quic_client_stream.cc b/source/common/quic/envoy_quic_client_stream.cc index b107850ee230b..185c7bc588d22 100644 --- a/source/common/quic/envoy_quic_client_stream.cc +++ b/source/common/quic/envoy_quic_client_stream.cc @@ -134,7 +134,8 @@ void EnvoyQuicClientStream::OnInitialHeadersComplete(bool fin, size_t frame_len, } quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); if (!headers_decompressed() || header_list.empty()) { - onStreamError(!http3_options_.override_stream_error_on_invalid_http_message().value()); + onStreamError(!http3_options_.override_stream_error_on_invalid_http_message().value(), + quic::QUIC_BAD_APPLICATION_PAYLOAD); return; } @@ -143,16 +144,18 @@ void EnvoyQuicClientStream::OnInitialHeadersComplete(bool fin, size_t frame_len, end_stream_decoded_ = true; } std::unique_ptr headers = - quicHeadersToEnvoyHeaders(header_list, *this); + quicHeadersToEnvoyHeaders( + header_list, *this, filterManagerConnection()->maxIncomingHeadersCount(), details_); if (headers == nullptr) { - onStreamError(close_connection_upon_invalid_header_); + onStreamError(close_connection_upon_invalid_header_, quic::QUIC_STREAM_EXCESSIVE_LOAD); return; } const absl::optional optional_status = Http::Utility::getResponseStatusNoThrow(*headers); if (!optional_status.has_value()) { details_ = Http3ResponseCodeDetailValues::invalid_http_header; - onStreamError(!http3_options_.override_stream_error_on_invalid_http_message().value()); + onStreamError(!http3_options_.override_stream_error_on_invalid_http_message().value(), + quic::QUIC_BAD_APPLICATION_PAYLOAD); return; } const uint64_t status = optional_status.value(); @@ -239,6 +242,11 @@ void EnvoyQuicClientStream::maybeDecodeTrailers() { if (sequencer()->IsClosed() && !FinishedReadingTrailers()) { // Only decode trailers after finishing decoding body. end_stream_decoded_ = true; + if (received_trailers().size() > filterManagerConnection()->maxIncomingHeadersCount()) { + details_ = Http3ResponseCodeDetailValues::too_many_trailers; + onStreamError(close_connection_upon_invalid_header_, quic::QUIC_STREAM_EXCESSIVE_LOAD); + return; + } response_decoder_->decodeTrailers( spdyHeaderBlockToEnvoyHeaders(received_trailers())); MarkTrailersConsumed(); @@ -299,7 +307,8 @@ QuicFilterManagerConnectionImpl* EnvoyQuicClientStream::filterManagerConnection( return dynamic_cast(session()); } -void EnvoyQuicClientStream::onStreamError(absl::optional should_close_connection) { +void EnvoyQuicClientStream::onStreamError(absl::optional should_close_connection, + quic::QuicRstStreamErrorCode rst_code) { if (details_.empty()) { details_ = Http3ResponseCodeDetailValues::invalid_http_header; } @@ -313,7 +322,7 @@ void EnvoyQuicClientStream::onStreamError(absl::optional should_close_conn if (close_connection_upon_invalid_header) { stream_delegate()->OnStreamError(quic::QUIC_HTTP_FRAME_ERROR, "Invalid headers"); } else { - Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD); + Reset(rst_code); } } diff --git a/source/common/quic/envoy_quic_client_stream.h b/source/common/quic/envoy_quic_client_stream.h index 48e4940b53acf..a722accc6bfc2 100644 --- a/source/common/quic/envoy_quic_client_stream.h +++ b/source/common/quic/envoy_quic_client_stream.h @@ -78,7 +78,8 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, // Either reset the stream or close the connection according to // should_close_connection and configured http3 options. - void onStreamError(absl::optional should_close_connection); + void onStreamError(absl::optional should_close_connection, + quic::QuicRstStreamErrorCode rst_code); Http::ResponseDecoder* response_decoder_{nullptr}; diff --git a/source/common/quic/envoy_quic_server_stream.cc b/source/common/quic/envoy_quic_server_stream.cc index e6a2197a3be4a..094b1069fb05b 100644 --- a/source/common/quic/envoy_quic_server_stream.cc +++ b/source/common/quic/envoy_quic_server_stream.cc @@ -160,7 +160,8 @@ void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, end_stream_decoded_ = true; } std::unique_ptr headers = - quicHeadersToEnvoyHeaders(header_list, *this); + quicHeadersToEnvoyHeaders( + header_list, *this, filterManagerConnection()->maxIncomingHeadersCount(), details_); if (headers == nullptr) { onStreamError(close_connection_upon_invalid_header_); return; @@ -242,6 +243,11 @@ void EnvoyQuicServerStream::maybeDecodeTrailers() { if (sequencer()->IsClosed() && !FinishedReadingTrailers()) { // Only decode trailers after finishing decoding body. end_stream_decoded_ = true; + if (received_trailers().size() > filterManagerConnection()->maxIncomingHeadersCount()) { + details_ = Http3ResponseCodeDetailValues::too_many_trailers; + onStreamError(close_connection_upon_invalid_header_); + return; + } request_decoder_->decodeTrailers( spdyHeaderBlockToEnvoyHeaders(received_trailers())); MarkTrailersConsumed(); diff --git a/source/common/quic/envoy_quic_stream.h b/source/common/quic/envoy_quic_stream.h index bc8c9d0e7d986..56e744d56d80b 100644 --- a/source/common/quic/envoy_quic_stream.h +++ b/source/common/quic/envoy_quic_stream.h @@ -13,23 +13,6 @@ namespace Envoy { namespace Quic { -// Changes or additions to details should be reflected in -// docs/root/configuration/http/http_conn_man/response_code_details_details.rst -class Http3ResponseCodeDetailValues { -public: - // Invalid HTTP header field was received and stream is going to be - // closed. - static constexpr absl::string_view invalid_http_header = "http3.invalid_header_field"; - // The size of headers (or trailers) exceeded the configured limits. - static constexpr absl::string_view headers_too_large = "http3.headers_too_large"; - // Envoy was configured to drop requests with header keys beginning with underscores. - static constexpr absl::string_view invalid_underscore = "http3.unexpected_underscore"; - // The peer refused the stream. - static constexpr absl::string_view remote_refused = "http3.remote_refuse"; - // The peer reset the stream. - static constexpr absl::string_view remote_reset = "http3.remote_reset"; -}; - // Base class for EnvoyQuicServer|ClientStream. class EnvoyQuicStream : public virtual Http::StreamEncoder, public Http::Stream, diff --git a/source/common/quic/envoy_quic_utils.h b/source/common/quic/envoy_quic_utils.h index f30c582b7d534..f71d2fd0c58fb 100644 --- a/source/common/quic/envoy_quic_utils.h +++ b/source/common/quic/envoy_quic_utils.h @@ -32,6 +32,27 @@ namespace Envoy { namespace Quic { +// Changes or additions to details should be reflected in +// docs/root/configuration/http/http_conn_man/response_code_details.rst +class Http3ResponseCodeDetailValues { +public: + // Invalid HTTP header field was received and stream is going to be + // closed. + static constexpr absl::string_view invalid_http_header = "http3.invalid_header_field"; + // The size of headers (or trailers) exceeded the configured limits. + static constexpr absl::string_view headers_too_large = "http3.headers_too_large"; + // Envoy was configured to drop requests with header keys beginning with underscores. + static constexpr absl::string_view invalid_underscore = "http3.unexpected_underscore"; + // The peer refused the stream. + static constexpr absl::string_view remote_refused = "http3.remote_refuse"; + // The peer reset the stream. + static constexpr absl::string_view remote_reset = "http3.remote_reset"; + // Too many trailers were sent. + static constexpr absl::string_view too_many_trailers = "http3.too_many_trailers"; + // Too many headers were sent. + static constexpr absl::string_view too_many_headers = "http3.too_many_headers"; +}; + // TODO(danzh): this is called on each write. Consider to return an address instance on the stack if // the heap allocation is too expensive. Network::Address::InstanceConstSharedPtr @@ -48,14 +69,21 @@ class HeaderValidator { // The returned header map has all keys in lower case. template -std::unique_ptr quicHeadersToEnvoyHeaders(const quic::QuicHeaderList& header_list, - HeaderValidator& validator) { +std::unique_ptr +quicHeadersToEnvoyHeaders(const quic::QuicHeaderList& header_list, HeaderValidator& validator, + uint32_t max_headers_allowed, absl::string_view& details) { auto headers = T::create(); for (const auto& entry : header_list) { + if (max_headers_allowed == 0) { + details = Http3ResponseCodeDetailValues::too_many_headers; + return nullptr; + } + max_headers_allowed--; Http::HeaderUtility::HeaderValidationResult result = validator.validateHeader(entry.first, entry.second); switch (result) { case Http::HeaderUtility::HeaderValidationResult::REJECT: + // The validator sets the details to Http3ResponseCodeDetailValues::invalid_underscore return nullptr; case Http::HeaderUtility::HeaderValidationResult::DROP: continue; diff --git a/source/common/quic/quic_filter_manager_connection_impl.h b/source/common/quic/quic_filter_manager_connection_impl.h index 91cfd98b8e238..c33bc5959d7f7 100644 --- a/source/common/quic/quic_filter_manager_connection_impl.h +++ b/source/common/quic/quic_filter_manager_connection_impl.h @@ -142,6 +142,12 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, codec_stats_ = std::reference_wrapper(stats); } + uint32_t maxIncomingHeadersCount() { return max_headers_count_; } + + void setMaxIncomingHeadersCount(uint32_t max_headers_count) { + max_headers_count_ = max_headers_count; + } + protected: // Propagate connection close to network_connection_callbacks_. void onConnectionCloseEvent(const quic::QuicConnectionCloseFrame& frame, @@ -179,6 +185,7 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, StreamInfo::StreamInfoImpl stream_info_; std::string transport_failure_reason_; uint32_t bytes_to_send_{0}; + uint32_t max_headers_count_{std::numeric_limits::max()}; // Keeps the buffer state of the connection, and react upon the changes of how many bytes are // buffered cross all streams' send buffer. The state is evaluated and may be changed upon each // stream write. QUICHE doesn't buffer data in connection, all the data is buffered in stream's diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 70f4d08f29700..0cf301d93ead1 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -606,7 +606,7 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, return std::make_unique( dynamic_cast(connection), callbacks, Http::Http3::CodecStats::atomicGet(http3_codec_stats_, context_.scope()), http3_options_, - maxRequestHeadersKb(), headersWithUnderscoresAction()); + maxRequestHeadersKb(), maxRequestHeadersCount(), headersWithUnderscoresAction()); #else // Should be blocked by configuration checking at an earlier point. NOT_REACHED_GCOVR_EXCL_LINE; diff --git a/test/common/quic/envoy_quic_client_session_test.cc b/test/common/quic/envoy_quic_client_session_test.cc index 8617d8953e83a..9facb2058a790 100644 --- a/test/common/quic/envoy_quic_client_session_test.cc +++ b/test/common/quic/envoy_quic_client_session_test.cc @@ -118,7 +118,7 @@ class EnvoyQuicClientSessionTest : public testing::TestWithParam { stats_({ALL_HTTP3_CODEC_STATS(POOL_COUNTER_PREFIX(scope_, "http3."), POOL_GAUGE_PREFIX(scope_, "http3."))}), http_connection_(envoy_quic_session_, http_connection_callbacks_, stats_, http3_options_, - 64 * 1024) { + 64 * 1024, 100) { EXPECT_EQ(time_system_.systemTime(), envoy_quic_session_.streamInfo().startTime()); EXPECT_EQ(EMPTY_STRING, envoy_quic_session_.nextProtocol()); EXPECT_EQ(Http::Protocol::Http3, http_connection_.protocol()); diff --git a/test/common/quic/envoy_quic_server_session_test.cc b/test/common/quic/envoy_quic_server_session_test.cc index fe441dbc25cbe..415b5c609823d 100644 --- a/test/common/quic/envoy_quic_server_session_test.cc +++ b/test/common/quic/envoy_quic_server_session_test.cc @@ -204,7 +204,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { EXPECT_CALL(*read_filter_, onNewConnection()).WillOnce(Invoke([this]() { // Create ServerConnection instance and setup callbacks for it. http_connection_ = std::make_unique( - envoy_quic_session_, http_connection_callbacks_, stats_, http3_options_, 64 * 1024, + envoy_quic_session_, http_connection_callbacks_, stats_, http3_options_, 64 * 1024, 100, envoy::config::core::v3::HttpProtocolOptions::ALLOW); EXPECT_EQ(Http::Protocol::Http3, http_connection_->protocol()); // Stop iteration to avoid calling getRead/WriteBuffer(). diff --git a/test/common/quic/envoy_quic_utils_test.cc b/test/common/quic/envoy_quic_utils_test.cc index 2ba153ba9a507..7d032b4253717 100644 --- a/test/common/quic/envoy_quic_utils_test.cc +++ b/test/common/quic/envoy_quic_utils_test.cc @@ -87,8 +87,9 @@ TEST(EnvoyQuicUtilsTest, HeadersConversion) { } return Http::HeaderUtility::HeaderValidationResult::ACCEPT; }); + absl::string_view details; auto envoy_headers2 = - quicHeadersToEnvoyHeaders(quic_headers, validator); + quicHeadersToEnvoyHeaders(quic_headers, validator, 100, details); EXPECT_EQ(*envoy_headers, *envoy_headers2); quic::QuicHeaderList quic_headers2; @@ -105,8 +106,8 @@ TEST(EnvoyQuicUtilsTest, HeadersConversion) { } return Http::HeaderUtility::HeaderValidationResult::ACCEPT; }); - EXPECT_EQ(nullptr, - quicHeadersToEnvoyHeaders(quic_headers2, validator)); + EXPECT_EQ(nullptr, quicHeadersToEnvoyHeaders(quic_headers2, validator, + 100, details)); } } // namespace Quic diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index 94ab19fd19694..3364b23afbad9 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -346,7 +346,8 @@ FakeHttpConnection::FakeHttpConnection( Http::Http3::CodecStats& stats = fake_upstream.http3CodecStats(); codec_ = std::make_unique( dynamic_cast(shared_connection_.connection()), *this, stats, - fake_upstream.http3Options(), max_request_headers_kb, headers_with_underscores_action); + fake_upstream.http3Options(), max_request_headers_kb, max_request_headers_count, + headers_with_underscores_action); #else ASSERT(false, "running a QUIC integration test without compiling QUIC"); #endif diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 99827c4020d8d..ea2bd3eb32bd0 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -1251,7 +1251,7 @@ void HttpIntegrationTest::testManyRequestHeaders(std::chrono::milliseconds time) // This test uses an Http::HeaderMapImpl instead of an Http::TestHeaderMapImpl to avoid // time-consuming asserts when using a large number of headers. setMaxRequestHeadersKb(96); - setMaxRequestHeadersCount(10005); + setMaxRequestHeadersCount(10010); config_helper_.addConfigModifier( [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& diff --git a/test/integration/multiplexed_upstream_integration_test.cc b/test/integration/multiplexed_upstream_integration_test.cc index 88bd515b1679f..107f67b1e39b9 100644 --- a/test/integration/multiplexed_upstream_integration_test.cc +++ b/test/integration/multiplexed_upstream_integration_test.cc @@ -322,7 +322,6 @@ TEST_P(Http2UpstreamIntegrationTest, ManyLargeSimultaneousRequestWithRandomBacku } TEST_P(Http2UpstreamIntegrationTest, UpstreamConnectionCloseWithManyStreams) { - EXCLUDE_UPSTREAM_HTTP3; // Times out waiting for reset. config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream. const uint32_t num_requests = 20; std::vector encoders; @@ -368,6 +367,9 @@ TEST_P(Http2UpstreamIntegrationTest, UpstreamConnectionCloseWithManyStreams) { ASSERT_TRUE(upstream_requests[i]->waitForEndStream(*dispatcher_)); upstream_requests[i]->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); upstream_requests[i]->encodeData(100, false); + // Make sure at least the headers go through, to ensure stream reset rather + // than disconnect. + responses[i]->waitForHeaders(); } // Close the connection. ASSERT_TRUE(fake_upstream_connection_->close()); @@ -441,8 +443,6 @@ name: router // Tests the default limit for the number of response headers is 100. Results in a stream reset if // exceeds. TEST_P(Http2UpstreamIntegrationTest, TestManyResponseHeadersRejected) { - EXCLUDE_UPSTREAM_HTTP3; // no 503. - // Default limit for response headers is 100. initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 03cd14677936e..3e34c3e2c9a5f 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -1327,10 +1327,14 @@ TEST_P(DownstreamProtocolIntegrationTest, LargeCookieParsingConcatenated) { // header size to avoid QUIC_HEADERS_TOO_LARGE stream error. config_helper_.addConfigModifier( [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& - hcm) -> void { hcm.mutable_max_request_headers_kb()->set_value(96); }); + hcm) -> void { + hcm.mutable_max_request_headers_kb()->set_value(96); + hcm.mutable_common_http_protocol_options()->mutable_max_headers_count()->set_value(8000); + }); } if (upstreamProtocol() == FakeHttpConnection::Type::HTTP3) { setMaxRequestHeadersKb(96); + setMaxRequestHeadersCount(8000); } initialize(); @@ -1593,8 +1597,6 @@ TEST_P(DownstreamProtocolIntegrationTest, ManyLargeRequestHeadersAccepted) { } TEST_P(DownstreamProtocolIntegrationTest, ManyRequestHeadersRejected) { - // QUICHE doesn't limit number of headers. - EXCLUDE_DOWNSTREAM_HTTP3 // Send 101 empty headers with limit 60 kB and 100 headers. testLargeRequestHeaders(0, 101, 60, 80); } @@ -1605,14 +1607,15 @@ TEST_P(DownstreamProtocolIntegrationTest, ManyRequestHeadersAccepted) { } TEST_P(DownstreamProtocolIntegrationTest, ManyRequestTrailersRejected) { - // QUICHE doesn't limit number of headers. - EXCLUDE_DOWNSTREAM_HTTP3 - // The default configured header (and trailer) count limit is 100. + // Default header (and trailer) count limit is 100. config_helper_.addConfigModifier(setEnableDownstreamTrailersHttp1()); config_helper_.addConfigModifier(setEnableUpstreamTrailersHttp1()); Http::TestRequestTrailerMapImpl request_trailers; for (int i = 0; i < 150; i++) { - request_trailers.addCopy("trailer", std::string(1, 'a')); + // TODO(alyssawilk) QUIC fails without the trailers being distinct because + // the checks are done before transformation. Either make the transformation + // use commas, or do QUIC checks before and after. + request_trailers.addCopy(absl::StrCat("trailer", i), std::string(1, 'a')); } initialize(); @@ -1620,6 +1623,8 @@ TEST_P(DownstreamProtocolIntegrationTest, ManyRequestTrailersRejected) { auto encoder_decoder = codec_client_->startRequest(default_request_headers_); request_encoder_ = &encoder_decoder.first; auto response = std::move(encoder_decoder.second); + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + EXPECT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); codec_client_->sendData(*request_encoder_, 1, false); codec_client_->sendTrailers(*request_encoder_, request_trailers); From fc6099e488671ff5ec6d7c259c180890a59a9afd Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Mon, 3 May 2021 07:01:49 -0700 Subject: [PATCH 129/209] dns_cache: Remove getCacheManager() (#16273) dns_cache: Remove getCacheManager() Remove the function getCacheManager() which was declared in dns_cache.h, defined in dns_cache_manager_impl.cc and called in dns_cache_manager_impl.h. Instead, simply inline this method into DnsCacheManagerFactoryImpl::get() which was the only place it was called. Risk Level: Low Testing: N/A - no code behavior change Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Ryan Hamilton --- .../common/dynamic_forward_proxy/dns_cache.h | 9 --------- .../dns_cache_manager_impl.cc | 15 +++++---------- .../dns_cache_manager_impl.h | 4 +--- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache.h b/source/extensions/common/dynamic_forward_proxy/dns_cache.h index 70b98ea34511b..218c1044a0793 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache.h +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache.h @@ -219,15 +219,6 @@ class DnsCacheManager { using DnsCacheManagerSharedPtr = std::shared_ptr; -/** - * Get the singleton cache manager for the entire server. - */ -DnsCacheManagerSharedPtr getCacheManager(Singleton::Manager& manager, - Event::Dispatcher& main_thread_dispatcher, - ThreadLocal::SlotAllocator& tls, - Random::RandomGenerator& random, Runtime::Loader& loader, - Stats::Scope& root_scope); - /** * Factory for getting a DNS cache manager. */ diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_manager_impl.cc b/source/extensions/common/dynamic_forward_proxy/dns_cache_manager_impl.cc index ff2eb6add5e87..c758d416d6fa8 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_manager_impl.cc +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_manager_impl.cc @@ -33,16 +33,11 @@ DnsCacheSharedPtr DnsCacheManagerImpl::getCache( return new_cache; } -DnsCacheManagerSharedPtr getCacheManager(Singleton::Manager& singleton_manager, - Event::Dispatcher& main_thread_dispatcher, - ThreadLocal::SlotAllocator& tls, - Random::RandomGenerator& random, Runtime::Loader& loader, - Stats::Scope& root_scope) { - return singleton_manager.getTyped( - SINGLETON_MANAGER_REGISTERED_NAME(dns_cache_manager), - [&main_thread_dispatcher, &tls, &random, &loader, &root_scope] { - return std::make_shared(main_thread_dispatcher, tls, random, loader, - root_scope); +DnsCacheManagerSharedPtr DnsCacheManagerFactoryImpl::get() { + return singleton_manager_.getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(dns_cache_manager), [this] { + return std::make_shared(dispatcher_, tls_, random_, loader_, + root_scope_); }); } diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_manager_impl.h b/source/extensions/common/dynamic_forward_proxy/dns_cache_manager_impl.h index 15db6a928a0be..3a8c5d26013be 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_manager_impl.h +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_manager_impl.h @@ -49,9 +49,7 @@ class DnsCacheManagerFactoryImpl : public DnsCacheManagerFactory { : singleton_manager_(singleton_manager), dispatcher_(dispatcher), tls_(tls), random_(random), loader_(loader), root_scope_(root_scope) {} - DnsCacheManagerSharedPtr get() override { - return getCacheManager(singleton_manager_, dispatcher_, tls_, random_, loader_, root_scope_); - } + DnsCacheManagerSharedPtr get() override; private: Singleton::Manager& singleton_manager_; From 174e45c9a902bd297dd29fa9f895be1f4ebcb916 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 3 May 2021 16:21:46 +0100 Subject: [PATCH 130/209] protos: Update style guide to try and prevent redundant imports (#16231) Signed-off-by: Ryan Northey --- api/STYLE.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/STYLE.md b/api/STYLE.md index 64668ac7528d8..30599e194a6f2 100644 --- a/api/STYLE.md +++ b/api/STYLE.md @@ -122,8 +122,7 @@ To add an extension config to the API, the steps below should be followed: deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], ) ``` -1. Add to the v3 extension config proto `import "udpa/annotations/migrate.proto";` - and `import "udpa/annotations/status.proto";` +1. Add to the v3 extension config proto `import "udpa/annotations/status.proto";` 1. If this is still WiP and subject to breaking changes, set `option (udpa.annotations.file_status).work_in_progress = true;`. 1. Add to the v3 extension config proto a file level From 6fdcb5c1c5fd6a25f8b3106828fcbe51da832c74 Mon Sep 17 00:00:00 2001 From: williamsfu99 <32112201+williamsfu99@users.noreply.github.com> Date: Mon, 3 May 2021 09:17:55 -0700 Subject: [PATCH 131/209] thrift_proxy router: fix bug when charging upstream rq_time before (#16261) We charge upstream_rq_time latencies after responses are received but also when an upstreamRequest destroys early, to submit proper data points during events such as timeouts. However if we experience a stream reset before requestCompletes calls, we will be recording a latency value that is calculated against an initial value of time_after_epoch() = 0. This will occasionally cause extreme outliers in the histogram. Risk Level: Low, fixing a bug Testing: Updated existing unit test that handled onEvent Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: William Fu --- .../filters/network/thrift_proxy/router/router_impl.cc | 2 +- test/extensions/filters/network/thrift_proxy/router_test.cc | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/source/extensions/filters/network/thrift_proxy/router/router_impl.cc b/source/extensions/filters/network/thrift_proxy/router/router_impl.cc index f239463073bfb..498a86bbe61c0 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/router/router_impl.cc @@ -584,7 +584,7 @@ void Router::UpstreamRequest::onResetStream(ConnectionPool::PoolFailureReason re } void Router::UpstreamRequest::chargeResponseTiming() { - if (charged_response_timing_) { + if (charged_response_timing_ || !request_complete_) { return; } charged_response_timing_ = true; diff --git a/test/extensions/filters/network/thrift_proxy/router_test.cc b/test/extensions/filters/network/thrift_proxy/router_test.cc index 72410c685d493..03448be78b93a 100644 --- a/test/extensions/filters/network/thrift_proxy/router_test.cc +++ b/test/extensions/filters/network/thrift_proxy/router_test.cc @@ -953,10 +953,12 @@ TEST_F(ThriftRouterTest, PoolTimeoutUpstreamTimeMeasurement) { dispatcher_.time_system_.advanceTimeWait(std::chrono::milliseconds(500)); EXPECT_CALL(cluster_scope, - histogram("thrift.upstream_rq_time", Stats::Histogram::Unit::Milliseconds)); + histogram("thrift.upstream_rq_time", Stats::Histogram::Unit::Milliseconds)) + .Times(0); EXPECT_CALL(cluster_scope, deliverHistogramToSinks( - testing::Property(&Stats::Metric::name, "thrift.upstream_rq_time"), 500)); + testing::Property(&Stats::Metric::name, "thrift.upstream_rq_time"), 500)) + .Times(0); EXPECT_CALL(callbacks_, sendLocalReply(_, _)) .WillOnce(Invoke([&](const DirectResponse& response, bool end_stream) -> void { auto& app_ex = dynamic_cast(response); From 1ca5ca25a25f23e7d215e7c387faa4d8e19efa38 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 3 May 2021 20:10:02 +0100 Subject: [PATCH 132/209] docs: Cleanup inline literals (#16280) Signed-off-by: Ryan Northey --- .../advanced/well_known_dynamic_metadata.rst | 4 +- .../http/http_conn_man/local_reply.rst | 10 ++--- .../http/http_conn_man/stats.rst | 4 +- .../adaptive_concurrency_filter.rst | 10 ++--- .../http/http_filters/composite_filter.rst | 6 +-- .../http/http_filters/ext_authz_filter.rst | 2 +- .../grpc_json_transcoder_filter.rst | 20 +++++----- .../http/http_filters/grpc_stats_filter.rst | 4 +- .../header_to_metadata_filter.rst | 2 +- .../http/http_filters/kill_request_filter.rst | 5 +-- .../http/http_filters/lua_filter.rst | 14 +++---- .../http/http_filters/oauth2_filter.rst | 6 +-- .../http/http_filters/rbac_filter.rst | 10 ++--- .../network_filters/postgres_proxy_filter.rst | 6 +-- .../listeners/network_filters/rbac_filter.rst | 10 ++--- .../network_filters/redis_proxy_filter.rst | 2 +- .../network_filters/sni_cluster_filter.rst | 4 +- .../network_filters/tcp_proxy_filter.rst | 6 +-- .../network_filters/thrift_proxy_filter.rst | 14 +++---- .../observability/access_log/usage.rst | 2 +- .../observability/statistics.rst | 4 +- .../overload_manager/overload_manager.rst | 14 +++---- .../root/configuration/operations/runtime.rst | 4 +- .../operations/tools/router_check.rst | 4 +- .../thrift_filters/router_filter.rst | 2 +- docs/root/configuration/overview/examples.rst | 2 +- .../root/configuration/overview/extension.rst | 6 +-- docs/root/faq/api/why_versioning.rst | 18 ++++----- docs/root/faq/configuration/sni.rst | 4 +- .../disable_circuit_breaking.rst | 4 +- .../performance/how_to_benchmark_envoy.rst | 4 +- .../arch_overview/advanced/attributes.rst | 38 +++++++++---------- .../advanced/data_sharing_between_filters.rst | 30 +++++++-------- .../advanced/matching/matching_api.rst | 12 +++--- .../intro/arch_overview/http/upgrades.rst | 12 +++--- .../arch_overview/security/google_vrp.rst | 24 ++++++------ .../arch_overview/security/rbac_filter.rst | 5 +-- .../root/intro/arch_overview/security/ssl.rst | 2 +- .../arch_overview/security/threat_model.rst | 2 +- .../upstream/circuit_breaking.rst | 4 +- .../load_balancing/load_balancers.rst | 4 +- docs/root/intro/life_of_a_request.rst | 20 +++++----- docs/root/operations/admin.rst | 26 ++++++------- docs/root/operations/cli.rst | 12 +++--- docs/root/operations/hot_restarter.rst | 4 +- .../operations/tools/config_generator.rst | 10 ++--- docs/root/operations/traffic_tapping.rst | 8 ++-- docs/root/start/building.rst | 2 +- docs/root/start/quick-start/run-envoy.rst | 2 +- 49 files changed, 211 insertions(+), 213 deletions(-) diff --git a/docs/root/configuration/advanced/well_known_dynamic_metadata.rst b/docs/root/configuration/advanced/well_known_dynamic_metadata.rst index 4d7f8ed3872c6..f6b596441eab1 100644 --- a/docs/root/configuration/advanced/well_known_dynamic_metadata.rst +++ b/docs/root/configuration/advanced/well_known_dynamic_metadata.rst @@ -32,13 +32,13 @@ The following Envoy filters can be configured to consume dynamic metadata emitte Shared Dynamic Metadata ----------------------- -Dynamic metadata that is set by multiple filters is placed in the common key namespace `envoy.common`. Refer to the corresponding rules when setting this metadata. +Dynamic metadata that is set by multiple filters is placed in the common key namespace ``envoy.common``. Refer to the corresponding rules when setting this metadata. .. csv-table:: :header: Name, Type, Description, Rules :widths: 1, 1, 3, 3 - access_log_hint, boolean, Whether access loggers should log the request., "When this metadata is already set: A `true` value should not be overwritten by a `false` value, while a `false` value can be overwritten by a `true` value." + access_log_hint, boolean, Whether access loggers should log the request., "When this metadata is already set: A ``true`` value should not be overwritten by a ``false`` value, while a ``false`` value can be overwritten by a ``true`` value." The following Envoy filters emit shared dynamic metadata. diff --git a/docs/root/configuration/http/http_conn_man/local_reply.rst b/docs/root/configuration/http/http_conn_man/local_reply.rst index d7649f0f4eaaf..0009ca39c3442 100644 --- a/docs/root/configuration/http/http_conn_man/local_reply.rst +++ b/docs/root/configuration/http/http_conn_man/local_reply.rst @@ -15,7 +15,7 @@ Features: Local reply content modification -------------------------------- -The local response content returned by Envoy can be customized. A list of :ref:`mappers ` can be specified. Each mapper must have a :ref:`filter `. It may have following rewrite rules; a :ref:`status_code ` rule to rewrite response code, a :ref:`headers_to_add ` rule to add/override/append response HTTP headers, a :ref:`body ` rule to rewrite the local reply body and a :ref:`body_format_override ` to specify the response body format. Envoy checks each `mapper` according to the specified order until the first one is matched. If a `mapper` is matched, all its rewrite rules will apply. +The local response content returned by Envoy can be customized. A list of :ref:`mappers ` can be specified. Each mapper must have a :ref:`filter `. It may have following rewrite rules; a :ref:`status_code ` rule to rewrite response code, a :ref:`headers_to_add ` rule to add/override/append response HTTP headers, a :ref:`body ` rule to rewrite the local reply body and a :ref:`body_format_override ` to specify the response body format. Envoy checks each ``mapper`` according to the specified order until the first one is matched. If a ``mapper`` is matched, all its rewrite rules will apply. Example of a LocalReplyConfig @@ -45,13 +45,13 @@ In above example, if the status_code is 400, it will be rewritten to 401, the r Local reply format modification ------------------------------- -The response body content type can be customized. If not specified, the content type is plain/text. There are two `body_format` fields; one is the :ref:`body_format ` field in the :ref:`LocalReplyConfig ` message and the other :ref:`body_format_override ` field in the `mapper`. The latter is only used when its mapper is matched. The former is used if there is no any matched mappers, or the matched mapper doesn't have the `body_format` specified. +The response body content type can be customized. If not specified, the content type is plain/text. There are two ``body_format`` fields; one is the :ref:`body_format ` field in the :ref:`LocalReplyConfig ` message and the other :ref:`body_format_override ` field in the ``mapper``. The latter is only used when its mapper is matched. The former is used if there is no any matched mappers, or the matched mapper doesn't have the ``body_format`` specified. Local reply format can be specified as :ref:`SubstitutionFormatString `. It supports :ref:`text_format ` and :ref:`json_format `. -Optionally, content-type can be modified further via :ref:`content_type ` field. If not specified, default content-type is `text/plain` for :ref:`text_format ` and `application/json` for :ref:`json_format `. +Optionally, content-type can be modified further via :ref:`content_type ` field. If not specified, default content-type is ``text/plain`` for :ref:`text_format ` and ``application/json`` for :ref:`json_format `. -Example of a LocalReplyConfig with `body_format` field. +Example of a LocalReplyConfig with ``body_format`` field. .. code-block:: @@ -78,4 +78,4 @@ Example of a LocalReplyConfig with `body_format` field. body_format: text_format: "%LOCAL_REPLY_BODY% %RESPONSE_CODE%" -In above example, there is a `body_format_override` inside the first `mapper` with a filter matching `status_code == 400`. It generates the response body in plain text format by concatenating %LOCAL_REPLY_BODY% with the `:path` request header. It is only used when the first mapper is matched. There is a `body_format` at the bottom of the config and at the same level as field `mappers`. It is used when non of the mappers is matched or the matched mapper doesn't have its own `body_format_override` specified. +In above example, there is a ``body_format_override`` inside the first ``mapper`` with a filter matching ``status_code == 400``. It generates the response body in plain text format by concatenating %LOCAL_REPLY_BODY% with the ``:path`` request header. It is only used when the first mapper is matched. There is a ``body_format`` at the bottom of the config and at the same level as field ``mappers``. It is used when non of the mappers is matched or the matched mapper doesn't have its own ``body_format_override`` specified. diff --git a/docs/root/configuration/http/http_conn_man/stats.rst b/docs/root/configuration/http/http_conn_man/stats.rst index f43cd7812ac0c..bbabd97d60e99 100644 --- a/docs/root/configuration/http/http_conn_man/stats.rst +++ b/docs/root/configuration/http/http_conn_man/stats.rst @@ -150,8 +150,8 @@ On the upstream side all http2 statistics are rooted at *cluster..http2.* .. attention:: - The HTTP/2 `streams_active` gauge may be greater than the HTTP connection manager - `downstream_rq_active` gauge due to differences in stream accounting between the codec and the + The HTTP/2 ``streams_active`` gauge may be greater than the HTTP connection manager + ``downstream_rq_active`` gauge due to differences in stream accounting between the codec and the HTTP connection manager. Tracing statistics diff --git a/docs/root/configuration/http/http_filters/adaptive_concurrency_filter.rst b/docs/root/configuration/http/http_filters/adaptive_concurrency_filter.rst index 19c7d0c588721..429c1f0c88acd 100644 --- a/docs/root/configuration/http/http_filters/adaptive_concurrency_filter.rst +++ b/docs/root/configuration/http/http_filters/adaptive_concurrency_filter.rst @@ -7,7 +7,7 @@ Adaptive Concurrency The adaptive concurrency filter is experimental and is currently under active development. -This filter should be configured with the name `envoy.filters.http.adaptive_concurrency`. +This filter should be configured with the name ``envoy.filters.http.adaptive_concurrency``. See the :ref:`v3 API reference ` for details on each configuration parameter. @@ -156,8 +156,8 @@ The adaptive concurrency filter supports the following runtime settings: adaptive_concurrency.enabled Overrides whether the adaptive concurrency filter will use the concurrency controller for - forwarding decisions. If set to `false`, the filter will be a no-op. Defaults to what is - specified for `enabled` in the filter configuration. + forwarding decisions. If set to ``false``, the filter will be a no-op. Defaults to what is + specified for ``enabled`` in the filter configuration. adaptive_concurrency.gradient_controller.min_rtt_calc_interval_ms Overrides the interval in which the ideal round-trip time (minRTT) will be recalculated. @@ -166,7 +166,7 @@ adaptive_concurrency.gradient_controller.min_rtt_aggregate_request_count Overrides the number of requests sampled for calculation of the minRTT. adaptive_concurrency.gradient_controller.jitter - Overrides the random delay introduced to the minRTT calculation start time. A value of `10` + Overrides the random delay introduced to the minRTT calculation start time. A value of ``10`` indicates a random delay of 10% of the configured interval. The runtime value specified is clamped to the range [0,100]. @@ -181,7 +181,7 @@ adaptive_concurrency.gradient_controller.min_rtt_buffer adaptive_concurrency.gradient_controller.sample_aggregate_percentile Overrides the percentile value used to represent the collection of latency samples in - calculations. A value of `95` indicates the 95th percentile. The runtime value specified is + calculations. A value of ``95`` indicates the 95th percentile. The runtime value specified is clamped to the range [0,100]. adaptive_concurrency.gradient_controller.min_concurrency diff --git a/docs/root/configuration/http/http_filters/composite_filter.rst b/docs/root/configuration/http/http_filters/composite_filter.rst index bf3b9bd0bd409..9f4814857bd98 100644 --- a/docs/root/configuration/http/http_filters/composite_filter.rst +++ b/docs/root/configuration/http/http_filters/composite_filter.rst @@ -15,7 +15,7 @@ this, in order to delegate all the data to the specified filter, the decision mu on just the request headers. Delegation can fail if the filter factory attempted to use a callback not supported by the -composite filter. In either case, the `.composite.delegation_error` stat will be +composite filter. In either case, the ``.composite.delegation_error`` stat will be incremented. Sample Envoy configuration @@ -23,8 +23,8 @@ Sample Envoy configuration Here's a sample Envoy configuration that makes use of the composite filter to inject a different latency via the :ref:`fault filter `. It uses the header -`x-fault-category` to determine which fault configuration to use: if the header is equal to the -string `huge fault`, a 10s latency is injected while if the header contains `tiny string` a 1s +``x-fault-category`` to determine which fault configuration to use: if the header is equal to the +string ``huge fault``, a 10s latency is injected while if the header contains ``tiny string`` a 1s latency is injected. If the header is absent or contains a different value, no filter is instantiated. diff --git a/docs/root/configuration/http/http_filters/ext_authz_filter.rst b/docs/root/configuration/http/http_filters/ext_authz_filter.rst index 015e9ae94d633..94414daa0e285 100644 --- a/docs/root/configuration/http/http_filters/ext_authz_filter.rst +++ b/docs/root/configuration/http/http_filters/ext_authz_filter.rst @@ -133,7 +133,7 @@ Per-Route Configuration ----------------------- A sample virtual host and route filter configuration. -In this example we add additional context on the virtual host, and disabled the filter for `/static` prefixed routes. +In this example we add additional context on the virtual host, and disabled the filter for ``/static`` prefixed routes. .. code-block:: yaml diff --git a/docs/root/configuration/http/http_filters/grpc_json_transcoder_filter.rst b/docs/root/configuration/http/http_filters/grpc_json_transcoder_filter.rst index c89093b846580..6d42810ed621a 100644 --- a/docs/root/configuration/http/http_filters/grpc_json_transcoder_filter.rst +++ b/docs/root/configuration/http/http_filters/grpc_json_transcoder_filter.rst @@ -48,14 +48,14 @@ Route configs for transcoded requests ------------------------------------- The route configs to be used with the gRPC-JSON transcoder should be identical to the gRPC route. -The requests processed by the transcoder filter will have `/./` path and -`POST` method. The route configs for those requests should match on `/./`, +The requests processed by the transcoder filter will have ``/./`` path and +``POST`` method. The route configs for those requests should match on ``/./``, not the incoming request path. This allows the routes to be used for both gRPC requests and gRPC-JSON transcoded requests. -For example, with the following proto example, the router will process `/helloworld.Greeter/SayHello` -as the path, so the route config prefix `/say` won't match requests to `SayHello`. If you want to -match the incoming request path, set `match_incoming_request_route` to true. +For example, with the following proto example, the router will process ``/helloworld.Greeter/SayHello`` +as the path, so the route config prefix ``/say`` won't match requests to ``SayHello``. If you want to +match the incoming request path, set ``match_incoming_request_route`` to true. .. literalinclude:: _include/helloworld.proto :language: proto @@ -73,17 +73,17 @@ Sending arbitrary content ------------------------- By default, when transcoding occurs, gRPC-JSON encodes the message output of a gRPC service method into -JSON and sets the HTTP response `Content-Type` header to `application/json`. To send arbitrary content, +JSON and sets the HTTP response ``Content-Type`` header to ``application/json``. To send arbitrary content, a gRPC service method can use `google.api.HttpBody `_ as its output message type. The implementation needs to set `content_type `_ -(which sets the value of the HTTP response `Content-Type` header) and +(which sets the value of the HTTP response ``Content-Type`` header) and `data `_ (which sets the HTTP response body) accordingly. Multiple `google.api.HttpBody `_ can be send by the gRPC server in the server streaming case. -In this case, HTTP response header `Content-Type` will use the `content-type` from the first +In this case, HTTP response header ``Content-Type`` will use the ``content-type`` from the first `google.api.HttpBody `_. Headers @@ -91,8 +91,8 @@ Headers gRPC-JSON forwards the following headers to the gRPC server: -* `x-envoy-original-path`, containing the value of the original path of HTTP request -* `x-envoy-original-method`, containing the value of the original method of HTTP request +* ``x-envoy-original-path``, containing the value of the original path of HTTP request +* ``x-envoy-original-method``, containing the value of the original method of HTTP request Sample Envoy configuration diff --git a/docs/root/configuration/http/http_filters/grpc_stats_filter.rst b/docs/root/configuration/http/http_filters/grpc_stats_filter.rst index 4f133ccd3afd8..6546b74b501d6 100644 --- a/docs/root/configuration/http/http_filters/grpc_stats_filter.rst +++ b/docs/root/configuration/http/http_filters/grpc_stats_filter.rst @@ -8,7 +8,7 @@ gRPC Statistics * This filter should be configured with the name *envoy.filters.http.grpc_stats*. * This filter can be enabled to emit a :ref:`filter state object ` -* The filter state object textual representation is `request_message_count,response_message_count`. +* The filter state object textual representation is ``request_message_count,response_message_count``. This filter enables telemetry of gRPC calls. It counts the number of successful and failed calls, optionally grouping them by the gRPC method name. @@ -18,7 +18,7 @@ emits the message counts for both uni-directional and bi-directional calls. See more info on the wire format in `gRPC over HTTP/2 `_. The filter emits statistics in the *cluster..grpc.* namespace. Depending on the -configuration, the stats may be prefixed with `..`; the stats in the table below +configuration, the stats may be prefixed with ``..``; the stats in the table below are shown in this form. See the documentation for :ref:`individual_method_stats_allowlist ` and :ref:`stats_for_all_methods `. diff --git a/docs/root/configuration/http/http_filters/header_to_metadata_filter.rst b/docs/root/configuration/http/http_filters/header_to_metadata_filter.rst index fb98f97c21ace..e5b4cc86bb65a 100644 --- a/docs/root/configuration/http/http_filters/header_to_metadata_filter.rst +++ b/docs/root/configuration/http/http_filters/header_to_metadata_filter.rst @@ -85,7 +85,7 @@ A corresponding upstream cluster configuration could be: - keys: - version -This would then allow requests with the `x-version` header set to be matched against +This would then allow requests with the ``x-version`` header set to be matched against endpoints with the corresponding version. Whereas requests with that header missing would be matched with the default endpoints. diff --git a/docs/root/configuration/http/http_filters/kill_request_filter.rst b/docs/root/configuration/http/http_filters/kill_request_filter.rst index 6c85c28ccb8b7..7b7f595dc412a 100644 --- a/docs/root/configuration/http/http_filters/kill_request_filter.rst +++ b/docs/root/configuration/http/http_filters/kill_request_filter.rst @@ -1,11 +1,11 @@ .. _config_http_filters_kill_request: Kill Request -=============== +============ The KillRequest filter can be used to crash Envoy when receiving a Kill request. By default, KillRequest filter is not built into Envoy binary. If you want to use this extension, -build Envoy with `--//source/extensions/filters/http/kill_request:enabled`. +build Envoy with ``--//source/extensions/filters/http/kill_request:enabled``. Configuration ------------- @@ -40,4 +40,3 @@ The following is an example configuration: "@type": type.googleapis.com/envoy.extensions.filters.http.kill_request.v3.KillRequest probability: numerator: 100 - diff --git a/docs/root/configuration/http/http_filters/lua_filter.rst b/docs/root/configuration/http/http_filters/lua_filter.rst index 119086b7acd43..32180e901451c 100644 --- a/docs/root/configuration/http/http_filters/lua_filter.rst +++ b/docs/root/configuration/http/http_filters/lua_filter.rst @@ -87,7 +87,7 @@ The Lua HTTP filter also can be disabled or overridden on a per-route basis by p :ref:`LuaPerRoute ` configuration on the virtual host, route, or weighted cluster. -LuaPerRoute provides two ways of overriding the `GLOBAL` Lua script: +LuaPerRoute provides two ways of overriding the ``GLOBAL`` Lua script: * By providing a name reference to the defined :ref:`named Lua source codes map `. @@ -145,7 +145,7 @@ The ``GLOBAL`` Lua script will be overridden by the referenced script: `. Therefore, do not use ``GLOBAL`` as name for other Lua scripts. -Or we can define a new Lua script in the LuaPerRoute configuration directly to override the `GLOBAL` +Or we can define a new Lua script in the LuaPerRoute configuration directly to override the ``GLOBAL`` Lua script as follows: .. code-block:: yaml @@ -243,7 +243,7 @@ more details on the supported API. A common use-case is to rewrite upstream response body, for example: an upstream sends non-2xx response with JSON data, but the application requires HTML page to be sent to browsers. -There are two ways of doing this, the first one is via the `body()` API. +There are two ways of doing this, the first one is via the ``body()`` API. .. code-block:: lua @@ -254,7 +254,7 @@ There are two ways of doing this, the first one is via the `body()` API. end -Or, through `bodyChunks()` API, which let Envoy to skip buffering the upstream response data. +Or, through ``bodyChunks()`` API, which let Envoy to skip buffering the upstream response data. .. code-block:: lua @@ -334,8 +334,8 @@ the entire body has been received in a buffer. Note that all buffering must adhe flow-control policies in place. Envoy will not buffer more data than is allowed by the connection manager. -An optional boolean argument `always_wrap_body` can be used to require Envoy always returns a -`body` object even if the body is empty. Therefore we can modify the body regardless of whether the +An optional boolean argument ``always_wrap_body`` can be used to require Envoy always returns a +``body`` object even if the body is empty. Therefore we can modify the body regardless of whether the original body exists or not. Returns a :ref:`buffer object `. @@ -561,7 +561,7 @@ that supplies the header value. In the current implementation, headers cannot be modified during iteration. Additionally, if it is necessary to modify headers after an iteration, the iteration must first be completed. This means that - `break` or any other way to exit the loop early must not be used. This may be more flexible in the future. + ``break`` or any other way to exit the loop early must not be used. This may be more flexible in the future. remove() ^^^^^^^^ diff --git a/docs/root/configuration/http/http_filters/oauth2_filter.rst b/docs/root/configuration/http/http_filters/oauth2_filter.rst index 11d79c0386661..0ea1b97c95d00 100644 --- a/docs/root/configuration/http/http_filters/oauth2_filter.rst +++ b/docs/root/configuration/http/http_filters/oauth2_filter.rst @@ -33,7 +33,7 @@ When the authn server validates the client and returns an authorization token ba no matter what format that token is, if :ref:`forward_bearer_token ` is set to true the filter will send over a -cookie named `BearerToken` to the upstream. Additionally, the `Authorization` header will be populated +cookie named ``BearerToken`` to the upstream. Additionally, the ``Authorization`` header will be populated with the same value. .. attention:: @@ -202,7 +202,7 @@ cached authentication (in the form of cookies). It is recommended to pair this filter with the :ref:`CSRF Filter ` to prevent malicious social engineering. -The service must be served over HTTPS for this filter to work properly, as the cookies use `;secure`. Without https, your +The service must be served over HTTPS for this filter to work properly, as the cookies use ``;secure``. Without https, your :ref:`authorization_endpoint ` provider will likely reject the incoming request, and your access cookies will not be cached to bypass future logins. @@ -212,7 +212,7 @@ sending the user to the configured auth endpoint. :ref:`pass_through_matcher ` provides an interface for users to provide specific header matching criteria such that, when applicable, the OAuth flow is entirely skipped. -When this occurs, the `oauth_success` metric is still incremented. +When this occurs, the ``oauth_success`` metric is still incremented. Generally, allowlisting is inadvisable from a security standpoint. diff --git a/docs/root/configuration/http/http_filters/rbac_filter.rst b/docs/root/configuration/http/http_filters/rbac_filter.rst index a41d033e071bc..819964e6211ed 100644 --- a/docs/root/configuration/http/http_filters/rbac_filter.rst +++ b/docs/root/configuration/http/http_filters/rbac_filter.rst @@ -12,8 +12,8 @@ and shadow mode, shadow mode won't effect real users, it is used to test that a work before rolling out to production. When a request is denied, the :ref:`RESPONSE_CODE_DETAILS` -will include the name of the matched policy that caused the deny in the format of `rbac_access_denied_matched_policy[policy_name]` -(policy_name will be `none` if no policy matched), this helps to distinguish the deny from Envoy RBAC +will include the name of the matched policy that caused the deny in the format of ``rbac_access_denied_matched_policy[policy_name]`` +(policy_name will be ``none`` if no policy matched), this helps to distinguish the deny from Envoy RBAC filter and the upstream backend. * :ref:`v3 API reference ` @@ -33,7 +33,7 @@ The RBAC filter outputs statistics in the *http..rbac.* namespace. ` comes from the owning HTTP connection manager. -For the shadow rule statistics `shadow_allowed` and `shadow_denied`, the :ref:`shadow_rules_stat_prefix ` +For the shadow rule statistics ``shadow_allowed`` and ``shadow_denied``, the :ref:`shadow_rules_stat_prefix ` can be used to add an extra prefix to output the statistics in the *http..rbac..* namespace. .. csv-table:: @@ -54,7 +54,7 @@ Dynamic Metadata The RBAC filter emits the following dynamic metadata. -For the shadow rules dynamic metadata `shadow_effective_policy_id` and `shadow_engine_result`, the :ref:`shadow_rules_stat_prefix ` +For the shadow rules dynamic metadata ``shadow_effective_policy_id`` and ``shadow_engine_result``, the :ref:`shadow_rules_stat_prefix ` can be used to add an extra prefix to the corresponding dynamic metadata key. .. csv-table:: @@ -62,5 +62,5 @@ can be used to add an extra prefix to the corresponding dynamic metadata key. :widths: 1, 1, 2 shadow_effective_policy_id, string, The effective shadow policy ID matching the action (if any). - shadow_engine_result, string, The engine result for the shadow rules (i.e. either `allowed` or `denied`). + shadow_engine_result, string, The engine result for the shadow rules (i.e. either ``allowed`` or ``denied``). access_log_hint, boolean, Whether the request should be logged. This metadata is shared and set under the key namespace 'envoy.common' (See :ref:`Shared Dynamic Metadata`). diff --git a/docs/root/configuration/listeners/network_filters/postgres_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/postgres_proxy_filter.rst index c0623c5894d4f..5e4834c17662e 100644 --- a/docs/root/configuration/listeners/network_filters/postgres_proxy_filter.rst +++ b/docs/root/configuration/listeners/network_filters/postgres_proxy_filter.rst @@ -1,7 +1,7 @@ .. _config_network_filters_postgres_proxy: Postgres proxy -================ +============== The Postgres proxy filter decodes the wire protocol between a Postgres client (downstream) and a Postgres server (upstream). The decoded information is used to produce Postgres level statistics like sessions, @@ -15,13 +15,13 @@ place. More information: .. attention:: - The `postgres_proxy` filter is experimental and is currently under active development. + The ``postgres_proxy`` filter is experimental and is currently under active development. Capabilities will be expanded over time and the configuration structures are likely to change. .. warning:: - The `postgreql_proxy` filter was tested only with + The ``postgreql_proxy`` filter was tested only with `Postgres frontend/backend protocol version 3.0`_, which was introduced in Postgres 7.4. Earlier versions are thus not supported. Testing is limited anyway to not EOL-ed versions. diff --git a/docs/root/configuration/listeners/network_filters/rbac_filter.rst b/docs/root/configuration/listeners/network_filters/rbac_filter.rst index 3fb15d98d4ff0..ee8338c06041b 100644 --- a/docs/root/configuration/listeners/network_filters/rbac_filter.rst +++ b/docs/root/configuration/listeners/network_filters/rbac_filter.rst @@ -11,8 +11,8 @@ This filter also supports policy in both enforcement and shadow modes. Shadow mo users, it is used to test that a new set of policies work before rolling out to production. When a request is denied, the :ref:`CONNECTION_TERMINATION_DETAILS` -will include the name of the matched policy that caused the deny in the format of `rbac_access_denied_matched_policy[policy_name]` -(policy_name will be `none` if no policy matched), this helps to distinguish the deny from Envoy +will include the name of the matched policy that caused the deny in the format of ``rbac_access_denied_matched_policy[policy_name]`` +(policy_name will be ``none`` if no policy matched), this helps to distinguish the deny from Envoy RBAC filter and the upstream backend. * :ref:`v3 API reference ` @@ -23,7 +23,7 @@ Statistics The RBAC network filter outputs statistics in the *.rbac.* namespace. -For the shadow rule statistics `shadow_allowed` and `shadow_denied`, the :ref:`shadow_rules_stat_prefix ` +For the shadow rule statistics ``shadow_allowed`` and ``shadow_denied``, the :ref:`shadow_rules_stat_prefix ` can be used to add an extra prefix to output the statistics in the *.rbac..* namespace. .. csv-table:: @@ -44,7 +44,7 @@ Dynamic Metadata The RBAC filter emits the following dynamic metadata. -For the shadow rules dynamic metadata `shadow_effective_policy_id` and `shadow_engine_result`, the :ref:`shadow_rules_stat_prefix ` +For the shadow rules dynamic metadata ``shadow_effective_policy_id`` and ``shadow_engine_result``, the :ref:`shadow_rules_stat_prefix ` can be used to add an extra prefix to the corresponding dynamic metadata key. .. csv-table:: @@ -52,5 +52,5 @@ can be used to add an extra prefix to the corresponding dynamic metadata key. :widths: 1, 1, 2 shadow_effective_policy_id, string, The effective shadow policy ID matching the action (if any). - shadow_engine_result, string, The engine result for the shadow rules (i.e. either `allowed` or `denied`). + shadow_engine_result, string, The engine result for the shadow rules (i.e. either ``allowed`` or ``denied``). access_log_hint, boolean, Whether the request should be logged. This metadata is shared and set under the key namespace 'envoy.common' (See :ref:`Shared Dynamic Metadata`). diff --git a/docs/root/configuration/listeners/network_filters/redis_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/redis_proxy_filter.rst index d02ec47746090..60ac24097afb3 100644 --- a/docs/root/configuration/listeners/network_filters/redis_proxy_filter.rst +++ b/docs/root/configuration/listeners/network_filters/redis_proxy_filter.rst @@ -94,7 +94,7 @@ is injected, then the total request latency is 500ms. Also, due to implementatio a delayed request will delay everything that comes in after it, due to the proxy's need to respect the order of commands it receives. -Note that faults must have a `fault_enabled` field, and are not enabled by default (if no default value +Note that faults must have a ``fault_enabled`` field, and are not enabled by default (if no default value or runtime key are set). Example configuration: diff --git a/docs/root/configuration/listeners/network_filters/sni_cluster_filter.rst b/docs/root/configuration/listeners/network_filters/sni_cluster_filter.rst index 32d2c38a2cd3c..b63af5a691fa8 100644 --- a/docs/root/configuration/listeners/network_filters/sni_cluster_filter.rst +++ b/docs/root/configuration/listeners/network_filters/sni_cluster_filter.rst @@ -3,10 +3,10 @@ Upstream Cluster from SNI ========================= -The `sni_cluster` is a network filter that uses the SNI value in a TLS +The ``sni_cluster`` is a network filter that uses the SNI value in a TLS connection as the upstream cluster name. The filter will not modify the upstream cluster for non-TLS connections. This filter should be configured -with the name *envoy.filters.network.sni_cluster*. +with the name ``envoy.filters.network.sni_cluster``. This filter has no configuration. It must be installed before the :ref:`tcp_proxy ` filter. diff --git a/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst index 0004b3757c51f..478ea5567c02c 100644 --- a/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst +++ b/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst @@ -14,7 +14,7 @@ Dynamic cluster selection The upstream cluster used by the TCP proxy filter can be dynamically set by other network filters on a per-connection basis by setting a per-connection -state object under the key `envoy.tcp_proxy.cluster`. See the +state object under the key ``envoy.tcp_proxy.cluster``. See the implementation for the details. .. _config_network_filters_tcp_proxy_subset_lb: @@ -34,8 +34,8 @@ To define metadata that a suitable upstream host must match, use one of the foll and :ref:`ClusterWeight.metadata_match` to define required metadata for a weighted upstream cluster (metadata from the latter will be merged on top of the former). -In addition, dynamic metadata can be set by earlier network filters on the `StreamInfo`. Setting the dynamic metadata -must happen before `onNewConnection()` is called on the `TcpProxy` filter to affect load balancing. +In addition, dynamic metadata can be set by earlier network filters on the ``StreamInfo``. Setting the dynamic metadata +must happen before ``onNewConnection()`` is called on the ``TcpProxy`` filter to affect load balancing. .. _config_network_filters_tcp_proxy_stats: diff --git a/docs/root/configuration/listeners/network_filters/thrift_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/thrift_proxy_filter.rst index 94972ebe3112e..9ddf82738eb7c 100644 --- a/docs/root/configuration/listeners/network_filters/thrift_proxy_filter.rst +++ b/docs/root/configuration/listeners/network_filters/thrift_proxy_filter.rst @@ -11,7 +11,7 @@ Cluster Protocol Options Thrift connections to upstream hosts can be configured by adding an entry to the appropriate Cluster's :ref:`extension_protocol_options` -keyed by `envoy.filters.network.thrift_proxy`. The +keyed by ``envoy.filters.network.thrift_proxy``. The :ref:`ThriftProtocolOptions` message describes the available options. @@ -47,16 +47,16 @@ Twitter protocol request contexts are converted into headers which are available In addition, the following fields are presented as headers: Client Identifier - The ClientId's `name` field (nested in the RequestHeader `client_id` field) becomes the - `:client-id` header. + The ClientId's ``name`` field (nested in the RequestHeader ``client_id`` field) becomes the + ``:client-id`` header. Destination - The RequestHeader `dest` field becomes the `:dest` header. + The RequestHeader ``dest`` field becomes the ``:dest`` header. Delegations - Each Delegation from the RequestHeader `delegations` field is added as a header. The header - name is the prefix `:d:` followed by the Delegation's `src`. The value is the Delegation's - `dst` field. + Each Delegation from the RequestHeader ``delegations`` field is added as a header. The header + name is the prefix ``:d:`` followed by the Delegation's ``src``. The value is the Delegation's + ``dst`` field. Metadata Interoperability ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/root/configuration/observability/access_log/usage.rst b/docs/root/configuration/observability/access_log/usage.rst index 22817c3db4d14..d7409a7b1a57d 100644 --- a/docs/root/configuration/observability/access_log/usage.rst +++ b/docs/root/configuration/observability/access_log/usage.rst @@ -331,7 +331,7 @@ The following command operators are supported: %UPSTREAM_CLUSTER% Upstream cluster to which the upstream host belongs to. If runtime feature - `envoy.reloadable_features.use_observable_cluster_name` is enabled, then :ref:`alt_stat_name + ``envoy.reloadable_features.use_observable_cluster_name`` is enabled, then :ref:`alt_stat_name ` will be used if provided. %UPSTREAM_LOCAL_ADDRESS% diff --git a/docs/root/configuration/observability/statistics.rst b/docs/root/configuration/observability/statistics.rst index 0c2b51d565fd5..57188dfde65ce 100644 --- a/docs/root/configuration/observability/statistics.rst +++ b/docs/root/configuration/observability/statistics.rst @@ -26,10 +26,10 @@ Server related statistics are rooted at *server.* with following statistics: version, Gauge, Integer represented version number based on SCM revision or :ref:`stats_server_version_override ` if set. days_until_first_cert_expiring, Gauge, Number of days until the next certificate being managed will expire seconds_until_first_ocsp_response_expiring, Gauge, Number of seconds until the next OCSP response being managed will expire - hot_restart_epoch, Gauge, Current hot restart epoch -- an integer passed via command line flag `--restart-epoch` usually indicating generation. + hot_restart_epoch, Gauge, Current hot restart epoch -- an integer passed via command line flag ``--restart-epoch`` usually indicating generation. hot_restart_generation, Gauge, Current hot restart generation -- like hot_restart_epoch but computed automatically by incrementing from parent. initialization_time_ms, Histogram, Total time taken for Envoy initialization in milliseconds. This is the time from server start-up until the worker threads are ready to accept new connections - debug_assertion_failures, Counter, Number of debug assertion failures detected in a release build if compiled with `--define log_debug_assert_in_release=enabled` or zero otherwise + debug_assertion_failures, Counter, Number of debug assertion failures detected in a release build if compiled with ``--define log_debug_assert_in_release=enabled`` or zero otherwise envoy_bug_failures, Counter, Number of envoy bug failures detected in a release build. File or report the issue if this increments as this may be serious. static_unknown_fields, Counter, Number of messages in static configuration with unknown fields dynamic_unknown_fields, Counter, Number of messages in dynamic configuration with unknown fields diff --git a/docs/root/configuration/operations/overload_manager/overload_manager.rst b/docs/root/configuration/operations/overload_manager/overload_manager.rst index d4565f3abd206..53fab324e46c6 100644 --- a/docs/root/configuration/operations/overload_manager/overload_manager.rst +++ b/docs/root/configuration/operations/overload_manager/overload_manager.rst @@ -58,8 +58,8 @@ Triggers connect resource monitors to actions. There are two types of triggers s * - :ref:`scaled ` - Sets the action state to 0 when the resource pressure is below the :ref:`scaling_threshold `, - `(pressure - scaling_threshold)/(saturation_threshold - scaling_threshold)` when - `scaling_threshold < pressure < saturation_threshold`, and to 1 (*saturated*) when the + ``(pressure - scaling_threshold)/(saturation_threshold - scaling_threshold)`` when + ``scaling_threshold < pressure < saturation_threshold``, and to 1 (*saturated*) when the pressure is above the :ref:`saturation_threshold `." @@ -101,7 +101,7 @@ The following overload actions are supported: Reducing timeouts ^^^^^^^^^^^^^^^^^ -The `envoy.overload_actions.reduce_timeouts` overload action will reduce the amount of time Envoy +The ``envoy.overload_actions.reduce_timeouts`` overload action will reduce the amount of time Envoy will spend waiting for some interactions to finish in response to resource pressure. The amount of reduction can be configured per timeout type by specifying the minimum timer value to use when the triggering resource monitor detects saturation. The minimum value for each timeout can be specified @@ -129,14 +129,14 @@ to remain idle before being closed in response to heap size. When the heap usage idle connections will time out at their usual time, which is configured through :ref:`HttpConnectionManager.common_http_protocol_options.idle_timeout `. When the heap usage is at or above 95%, idle connections will be closed after the specified -`min_timeout`, here 2 seconds. If the heap usage is between 85% and 95%, the idle connection timeout +``min_timeout``, here 2 seconds. If the heap usage is between 85% and 95%, the idle connection timeout will vary between those two based on the formula for the :ref:`scaled trigger ` -So if `RouteAction.idle_timeout = 600 seconds` and heap usage is at 92%, idle connections will time +So if ``RouteAction.idle_timeout = 600 seconds`` and heap usage is at 92%, idle connections will time out after :math:`2s + (600s - 2s) \cdot (95\% - 92\%) / (95\% - 85\%) = 181.4s`. Note in the example that the minimum idle time is specified as an absolute duration. If, instead, -`min_timeout: 2s` were to be replaced with `min_scale: { value: 10 }`, the minimum timer value -would be computed based on the maximum (specified elsewhere). So if `idle_timeout` is +``min_timeout: 2s`` were to be replaced with ``min_scale: { value: 10 }``, the minimum timer value +would be computed based on the maximum (specified elsewhere). So if ``idle_timeout`` is again 600 seconds, then the minimum timer value would be :math:`10\% \cdot 600s = 60s`. Limiting Active Connections diff --git a/docs/root/configuration/operations/runtime.rst b/docs/root/configuration/operations/runtime.rst index 881d3cfdea3a4..a560662359f82 100644 --- a/docs/root/configuration/operations/runtime.rst +++ b/docs/root/configuration/operations/runtime.rst @@ -159,7 +159,7 @@ Values can be viewed at the :ref:`/runtime admin endpoint `. Values can be modified and added at the :ref:`/runtime_modify admin endpoint `. If runtime is not configured, an empty provider is used which has the effect of using all defaults -built into the code, except for any values added via `/runtime_modify`. +built into the code, except for any values added via ``/runtime_modify``. .. attention:: @@ -256,7 +256,7 @@ as soon as possible. Fatal-by-default configuration indicates that the removal o imminent. It is far better for both Envoy users and for Envoy contributors if any bugs or feature gaps with the new code paths are flushed out ahead of time, rather than after the code is removed! -By enabling the runtime key `envoy.features.fail_on_any_deprecated_feature`, Envoy users can +By enabling the runtime key ``envoy.features.fail_on_any_deprecated_feature``, Envoy users can trigger a configuration load failure during the typical warn-by-default phase. This is a useful way to verify what fields you are using ahead of Envoy's deprecation schedule. diff --git a/docs/root/configuration/operations/tools/router_check.rst b/docs/root/configuration/operations/tools/router_check.rst index befaf853c8787..e60ef9e737a53 100644 --- a/docs/root/configuration/operations/tools/router_check.rst +++ b/docs/root/configuration/operations/tools/router_check.rst @@ -181,7 +181,7 @@ The router check tool will report route coverage at the end of a successful test Current route coverage: 0.0744863 This reporting can be leveraged to enforce a minimum coverage percentage by using -the `-f` or `--fail-under` flag. If coverage falls below this percentage the test +the ``-f`` or ``--fail-under`` flag. If coverage falls below this percentage the test run will fail. .. code:: bash @@ -194,7 +194,7 @@ run will fail. By default the coverage report measures test coverage by checking that at least one field is verified for every route. However, this can leave holes in the tests where fields aren't validated and later changed. For more comprehensive coverage you can add a flag, -`--covall`, which will calculate coverage taking into account all of the possible +``--covall``, which will calculate coverage taking into account all of the possible fields that could be tested. .. code:: bash diff --git a/docs/root/configuration/other_protocols/thrift_filters/router_filter.rst b/docs/root/configuration/other_protocols/thrift_filters/router_filter.rst index 009c22e3e50a3..a6d5c50ad4d2e 100644 --- a/docs/root/configuration/other_protocols/thrift_filters/router_filter.rst +++ b/docs/root/configuration/other_protocols/thrift_filters/router_filter.rst @@ -26,7 +26,7 @@ The filter outputs generic routing error statistics in the *thrift. The filter is also responsible for cluster-level statistics derived from routed upstream clusters. -Since these stats utilize the underlying cluster scope, we prefix with the `thrift` namespace. +Since these stats utilize the underlying cluster scope, we prefix with the ``thrift`` namespace. .. csv-table:: :header: Name, Type, Description diff --git a/docs/root/configuration/overview/examples.rst b/docs/root/configuration/overview/examples.rst index c7483d21c0c1d..f6d14cf5df4b2 100644 --- a/docs/root/configuration/overview/examples.rst +++ b/docs/root/configuration/overview/examples.rst @@ -135,7 +135,7 @@ an otherwise completely dynamic configurations, some static resources need to be defined to point Envoy at its xDS management server(s). It's important to set appropriate :ref:`TCP Keep-Alive options ` -in the `tcp_keepalive` block. This will help detect TCP half open connections to the xDS management +in the ``tcp_keepalive`` block. This will help detect TCP half open connections to the xDS management server and re-establish a full connection. In the above example, the EDS management server could then return a proto encoding of a diff --git a/docs/root/configuration/overview/extension.rst b/docs/root/configuration/overview/extension.rst index e71bf1fcae1b1..a9685edce5b94 100644 --- a/docs/root/configuration/overview/extension.rst +++ b/docs/root/configuration/overview/extension.rst @@ -3,10 +3,10 @@ Extension configuration ----------------------- -Each configuration resource in Envoy has a type URL in the `typed_config`. This +Each configuration resource in Envoy has a type URL in the ``typed_config``. This type corresponds to a versioned schema. If the type URL uniquely identifies an extension capable of interpreting the configuration, then the extension is -selected regardless of the `name` field. In this case the `name` field becomes +selected regardless of the ``name`` field. In this case the ``name`` field becomes optional and can be used as an identifier or as an annotation for the particular instance of the extension configuration. For example, the following filter configuration snippet is permitted: @@ -35,7 +35,7 @@ filter configuration snippet is permitted: dynamic_stats: true In case the control plane lacks the schema definitions for an extension, -`udpa.type.v1.TypedStruct` should be used as a generic container. The type URL +``udpa.type.v1.TypedStruct`` should be used as a generic container. The type URL inside it is then used by a client to convert the contents to a typed configuration resource. For example, the above example could be written as follows: diff --git a/docs/root/faq/api/why_versioning.rst b/docs/root/faq/api/why_versioning.rst index a53f5c7cd1596..fce027dccd1db 100644 --- a/docs/root/faq/api/why_versioning.rst +++ b/docs/root/faq/api/why_versioning.rst @@ -17,19 +17,19 @@ For the v3 xDS APIs, a brief list of the key improvements that were made with a * Packages organization was improved to reflect a more logical grouping of related APIs: - - The legacy `envoy.api.v2` tree was eliminated, with protos moved to their logical groupings, - e.g. `envoy.config.core.v3`, `envoy.server.listener.v3`. - - All packages are now versioned with a `vN` at the end. This allows for type-level identification + - The legacy ``envoy.api.v2`` tree was eliminated, with protos moved to their logical groupings, + e.g. ``envoy.config.core.v3``, ``envoy.server.listener.v3``. + - All packages are now versioned with a ``vN`` at the end. This allows for type-level identification of major version. - - xDS service endpoints/transport and configuration are split between `envoy.service` and - `envoy.config`. - - Extensions now reflect the Envoy source tree layout under `envoy.extensions`. -* `std::regex` regular expressions were dropped from the API, in favor of RE2. The former have dangerous + - xDS service endpoints/transport and configuration are split between ``envoy.service`` and + ``envoy.config``. + - Extensions now reflect the Envoy source tree layout under ``envoy.extensions``. +* ``std::regex`` regular expressions were dropped from the API, in favor of RE2. The former have dangerous security implications. -* `google.protobuf.Struct` configuration of extensions was dropped from the API, in favor of +* ``google.protobuf.Struct`` configuration of extensions was dropped from the API, in favor of typed configuration. This provides for better support for multiple instances of extensions, e.g. in filter chains, and more flexible naming of extension instances. * Over 60 deprecated fields were removed from the API. * Tooling and processes were established for API versioning support. This has now been reflected in - the bootstrap `Node`, providing a long term notion of API support that control planes can depend + the bootstrap ``Node``, providing a long term notion of API support that control planes can depend upon for client negotiation. diff --git a/docs/root/faq/configuration/sni.rst b/docs/root/faq/configuration/sni.rst index 7ef61ef565e32..e35cf141df02f 100644 --- a/docs/root/faq/configuration/sni.rst +++ b/docs/root/faq/configuration/sni.rst @@ -71,8 +71,8 @@ How do I configure SNI for clusters? ==================================== For clusters, a fixed SNI can be set in :ref:`UpstreamTlsContext `. -To derive SNI from HTTP `host` or `:authority` header, turn on +To derive SNI from HTTP ``host`` or ``:authority`` header, turn on :ref:`auto_sni ` to override the fixed SNI in `UpstreamTlsContext`. If upstream will present certificates with the hostname in SAN, turn on :ref:`auto_san_validation ` too. -It still needs a trust CA in validation context in `UpstreamTlsContext` for trust anchor. +It still needs a trust CA in validation context in ``UpstreamTlsContext`` for trust anchor. diff --git a/docs/root/faq/load_balancing/disable_circuit_breaking.rst b/docs/root/faq/load_balancing/disable_circuit_breaking.rst index 338c30caf0c78..525a20c466e92 100644 --- a/docs/root/faq/load_balancing/disable_circuit_breaking.rst +++ b/docs/root/faq/load_balancing/disable_circuit_breaking.rst @@ -6,10 +6,10 @@ Is there a way to disable circuit breaking? Envoy comes with :ref:`certain defaults ` for each kind of circuit breaking. Currently, there isn't a switch to turn circuit breaking off completely; however, you could achieve a similar behavior -by setting these thresholds very high, for example, to `std::numeric_limits::max()`. +by setting these thresholds very high, for example, to ``std::numeric_limits::max()``. Following is a sample configuration that tries to effectively disable all kinds -of circuit breaking by setting the thresholds to a value of `1000000000`. +of circuit breaking by setting the thresholds to a value of ``1000000000``. .. code-block:: yaml diff --git a/docs/root/faq/performance/how_to_benchmark_envoy.rst b/docs/root/faq/performance/how_to_benchmark_envoy.rst index 8729765fd7977..5af7f25fee608 100644 --- a/docs/root/faq/performance/how_to_benchmark_envoy.rst +++ b/docs/root/faq/performance/how_to_benchmark_envoy.rst @@ -7,7 +7,7 @@ ensuring an apples-to-apples comparison with other systems by configuring and lo appropriately. As a result, we can't provide a canonical benchmark configuration, but instead offer the following guidance: -* A release Envoy binary should be used. If building, please ensure that `-c opt` +* A release Envoy binary should be used. If building, please ensure that ``-c opt`` is used on the Bazel command line. When consuming Envoy point releases, make sure you are using the latest point release; given the pace of Envoy development it's not reasonable to pick older versions when making a statement about Envoy @@ -69,7 +69,7 @@ the following guidance: load generator and measurement tool. We are committed to building out benchmarking and latency measurement best practices in this tool. -* Examine `perf` profiles of Envoy during the benchmark run, e.g. with `flame graphs +* Examine ``perf`` profiles of Envoy during the benchmark run, e.g. with `flame graphs `_. Verify that Envoy is spending its time doing the expected essential work under test, rather than some unrelated or tangential work. diff --git a/docs/root/intro/arch_overview/advanced/attributes.rst b/docs/root/intro/arch_overview/advanced/attributes.rst index 69031b5869f29..3ef7070f03fae 100644 --- a/docs/root/intro/arch_overview/advanced/attributes.rst +++ b/docs/root/intro/arch_overview/advanced/attributes.rst @@ -5,28 +5,28 @@ Attributes Attributes refer to contextual properties provided by Envoy during request and connection processing. They are named by a dot-separated path (e.g. -`request.path`), have a fixed type (e.g. `string` or `int`), and may be +``request.path``), have a fixed type (e.g. ``string`` or ``int``), and may be absent or present depending on the context. Attributes are exposed to CEL runtime in :ref:`RBAC filter `, as well as Wasm extensions -via `get_property` ABI method. +via ``get_property`` ABI method. Attribute value types are limited to: -* `string` for UTF-8 strings -* `bytes` for byte buffers -* `int` for 64-bit signed integers -* `uint` for 64-bit unsigned integers -* `bool` for booleans -* `list` for lists of values -* `map` for associative arrays with string keys -* `timestamp` for timestamps as specified by `Timestamp `_ -* `duration` for durations as specified by `Duration `_ +* ``string`` for UTF-8 strings +* ``bytes`` for byte buffers +* ``int`` for 64-bit signed integers +* ``uint`` for 64-bit unsigned integers +* ``bool`` for booleans +* ``list`` for lists of values +* ``map`` for associative arrays with string keys +* ``timestamp`` for timestamps as specified by `Timestamp `_ +* ``duration`` for durations as specified by `Duration `_ * Protocol buffer message types CEL provides standard helper functions for operating on abstract types such as -`getMonth` for `timestamp` values. Note that integer literals (e.g. `7`) are of -type `int`, which is distinct from `uint` (e.g. `7u`), and the arithmetic -conversion is not automatic (use `uint(7)` for explicit conversion). +``getMonth`` for ``timestamp`` values. Note that integer literals (e.g. ``7``) are of +type ``int``, which is distinct from ``uint`` (e.g. ``7u``), and the arithmetic +conversion is not automatic (use ``uint(7)`` for explicit conversion). Wasm extensions receive the attribute values as a serialized buffer according to the type of the attribute. Strings and bytes are passed as-is, integers are @@ -56,10 +56,10 @@ processing, which makes them suitable for RBAC policies: request.referer, string, Referer request header request.useragent, string, User agent request header request.time, timestamp, Time of the first byte received - request.id, string, Request ID corresponding to `x-request-id` header value + request.id, string, Request ID corresponding to ``x-request-id`` header value request.protocol, string, "Request protocol ('"HTTP/1.0'", '"HTTP/1.1'", '"HTTP/2'", or '"HTTP/3'")" -Header values in `request.headers` associative array are comma-concatenated in case of multiple values. +Header values in ``request.headers`` associative array are comma-concatenated in case of multiple values. Additional attributes are available once the request completes: @@ -185,7 +185,7 @@ Path expressions Path expressions allow access to inner fields in structured attributes via a sequence of field names, map, and list indexes following an attribute name. For -example, `get_property({"node", "id"})` in Wasm ABI extracts the value of `id` -field in `node` message attribute, while `get_property({"request", "headers", -"my-header"})` refers to the comma-concatenated value of a particular request +example, ``get_property({"node", "id"})`` in Wasm ABI extracts the value of ``id`` +field in ``node`` message attribute, while ``get_property({"request", "headers", +"my-header"})`` refers to the comma-concatenated value of a particular request header. diff --git a/docs/root/intro/arch_overview/advanced/data_sharing_between_filters.rst b/docs/root/intro/arch_overview/advanced/data_sharing_between_filters.rst index 326d9e1bc9bc7..ab50e8c8a9601 100644 --- a/docs/root/intro/arch_overview/advanced/data_sharing_between_filters.rst +++ b/docs/root/intro/arch_overview/advanced/data_sharing_between_filters.rst @@ -40,19 +40,19 @@ logic for a specific key. Incoming config metadata (via xDS) is converted to class objects at config load time. Filters can then obtain a typed variant of the metadata at runtime (per request or connection), thereby eliminating the need for filters to repeatedly convert from -`ProtobufWkt::Struct` to some internal object during request/connection +``ProtobufWkt::Struct`` to some internal object during request/connection processing. For example, a filter that desires to have a convenience wrapper class over -an opaque metadata with key `xxx.service.policy` in `ClusterInfo` could -register a factory `ServicePolicyFactory` that inherits from -`ClusterTypedMetadataFactory`. The factory translates the `ProtobufWkt::Struct` -into an instance of `ServicePolicy` class (inherited from -`FilterState::Object`). When a `Cluster` is created, the associated -`ServicePolicy` instance will be created and cached. Note that typed +an opaque metadata with key ``xxx.service.policy`` in ``ClusterInfo`` could +register a factory ``ServicePolicyFactory`` that inherits from +``ClusterTypedMetadataFactory``. The factory translates the ``ProtobufWkt::Struct`` +into an instance of ``ServicePolicy`` class (inherited from +``FilterState::Object``). When a ``Cluster`` is created, the associated +``ServicePolicy`` instance will be created and cached. Note that typed metadata is not a new source of metadata. It is obtained from metadata that -is specified as part of the configuration. A `FilterState::Object` implements -`serializeAsProto` method can be configured in access loggers to log it. +is specified as part of the configuration. A ``FilterState::Object`` implements +``serializeAsProto`` method can be configured in access loggers to log it. HTTP Per-Route Filter Configuration ----------------------------------- @@ -66,9 +66,9 @@ implementation to treat the route-specific filter config as a replacement to global config or an enhancement. For example, the HTTP fault filter uses this technique to provide per-route fault configuration. -`typed_per_filter_config` is a `map`. The Connection +``typed_per_filter_config`` is a ``map``. The Connection manager iterates over this map and invokes the filter factory interface -`createRouteSpecificFilterConfigTyped` to parse/validate the struct value and +``createRouteSpecificFilterConfigTyped`` to parse/validate the struct value and convert it into a typed class object that’s stored with the route itself. HTTP filters can then query the route-specific filter config during request processing. @@ -80,11 +80,11 @@ Dynamic state is generated per network connection or per HTTP stream. Dynamic state can be mutable if desired by the filter generating the state. -`Envoy::Network::Connection` and `Envoy::Http::Filter` provide a -`StreamInfo` object that contains information about the current TCP +``Envoy::Network::Connection`` and ``Envoy::Http::Filter`` provide a +``StreamInfo`` object that contains information about the current TCP connection and HTTP stream (i.e., HTTP request/response pair) -respectively. `StreamInfo` contains a set of fixed attributes as part of +respectively. ``StreamInfo`` contains a set of fixed attributes as part of the class definition (e.g., HTTP protocol, requested server name, etc.). In addition, it provides a facility to store typed objects in a map -(`map`). The state stored per filter can be +(``map``). The state stored per filter can be either write-once (immutable), or write-many (mutable). diff --git a/docs/root/intro/arch_overview/advanced/matching/matching_api.rst b/docs/root/intro/arch_overview/advanced/matching/matching_api.rst index 28b8182691a25..893ce77d14e4d 100644 --- a/docs/root/intro/arch_overview/advanced/matching/matching_api.rst +++ b/docs/root/intro/arch_overview/advanced/matching/matching_api.rst @@ -26,12 +26,12 @@ which will then attempt to evaluate the matching rules with the provided data, t action if match evaluation completes in an action. In the above example, we are specifying that we want to match on the incoming request header -`some-header` by setting the `input` to +``some-header`` by setting the ``input`` to :ref:`HttpRequestHeaderMatchInput ` and configuring the header key to use. Using the value contained by this header, the provided -`exact_match_map` specifies which values we care about: we've configured a single value -(`some_value_to_match_on`) to match against. As a result, this config means that if we -receive a request which contains `some-header: some_value_to_match_on` as a header, the +``exact_match_map`` specifies which values we care about: we've configured a single value +(``some_value_to_match_on``) to match against. As a result, this config means that if we +receive a request which contains ``some-header: some_value_to_match_on`` as a header, the :ref:`SkipFilter ` action will be resolved (causing the associated HTTP filter to be skipped). If no such header is present, no action will be resolved and the filter will be applied as usual. @@ -43,8 +43,8 @@ Above is a slightly more complicated example which combines a top level tree mat linear matcher. While the tree matchers provide very efficient matching, they are not very expressive. The list matcher can be used to provide a much richer matching API, and can be combined with the tree matcher in an arbitrary order. The example describes the following match logic: skip -the filter if `some-header: skip_filter` is present and `second-header` is set to *either* `foo` or -`bar`. +the filter if ``some-header: skip_filter`` is present and ``second-header`` is set to *either* ``foo`` or +``bar``. .. _arch_overview_matching_api_iteration_impact: diff --git a/docs/root/intro/arch_overview/http/upgrades.rst b/docs/root/intro/arch_overview/http/upgrades.rst index 532b22e16f3e4..d9d0765eaddd9 100644 --- a/docs/root/intro/arch_overview/http/upgrades.rst +++ b/docs/root/intro/arch_overview/http/upgrades.rst @@ -132,12 +132,12 @@ CONNECT. An example set up proxying HTTP would look something like this: [TCP Server] --- raw TCP --- [L2 Envoy] --- TCP tunneled over HTTP/2 or HTTP/1.1 POST --- [Intermidate Proxies] --- HTTP/2 or HTTP/1.1 POST --- [L1 Envoy] --- raw TCP --- [TCP Client] Examples of such a set up can be found in the Envoy example config :repo:`directory ` -For HTTP/1.1 CONNECT run `bazel-bin/source/exe/envoy-static --config-path configs/encapsulate_in_http1_connect.yaml --base-id 1` -and `bazel-bin/source/exe/envoy-static --config-path configs/terminate_http1_connect.yaml`. -For HTTP/2 CONNECT run `bazel-bin/source/exe/envoy-static --config-path configs/encapsulate_in_http2_connect.yaml --base-id 1` -and `bazel-bin/source/exe/envoy-static --config-path configs/terminate_http2_connect.yaml`. -For HTTP/2 POST run `bazel-bin/source/exe/envoy-static --config-path configs/encapsulate_in_http2_post.yaml --base-id 1` -and `bazel-bin/source/exe/envoy-static --config-path configs/terminate_http2_post.yaml`. +For HTTP/1.1 CONNECT run ``bazel-bin/source/exe/envoy-static --config-path configs/encapsulate_in_http1_connect.yaml --base-id 1`` +and ``bazel-bin/source/exe/envoy-static --config-path configs/terminate_http1_connect.yaml``. +For HTTP/2 CONNECT run ``bazel-bin/source/exe/envoy-static --config-path configs/encapsulate_in_http2_connect.yaml --base-id 1`` +and ``bazel-bin/source/exe/envoy-static --config-path configs/terminate_http2_connect.yaml``. +For HTTP/2 POST run ``bazel-bin/source/exe/envoy-static --config-path configs/encapsulate_in_http2_post.yaml --base-id 1`` +and ``bazel-bin/source/exe/envoy-static --config-path configs/terminate_http2_post.yaml``. In all cases you will be running a first Envoy listening for TCP traffic on port 10000 and encapsulating it in an HTTP CONNECT or HTTP POST request, and a second one listening on 10001, diff --git a/docs/root/intro/arch_overview/security/google_vrp.rst b/docs/root/intro/arch_overview/security/google_vrp.rst index 0c156801e899a..aff95e541fe70 100644 --- a/docs/root/intro/arch_overview/security/google_vrp.rst +++ b/docs/root/intro/arch_overview/security/google_vrp.rst @@ -82,36 +82,36 @@ We supply Docker images that act as the reference environment for this program: vulnerability submission are eligible for the program. They must not be subject to any publicly disclosed vulnerability at that point in time. -Two Envoy processes are available when these images are launched via `docker run`: +Two Envoy processes are available when these images are launched via ``docker run``: * The *edge* Envoy is listening on ports 10000 (HTTPS). It has a :repo:`static configuration ` that is configured according to Envoy's :ref:`edge hardening principles `. It has sinkhole, direct response and request forwarding routing rules (in order): - 1. `/content/*`: route to the origin Envoy server. - 2. `/*`: return 403 (denied). + 1. ``/content/*``: route to the origin Envoy server. + 2. ``/*``: return 403 (denied). * The *origin* Envoy is an upstream of the edge Envoy. It has a :repo:`static configuration ` that features only direct responses, effectively acting as an HTTP origin server. There are two route rules (in order): - 1. `/blockedz`: return 200 `hidden treasure`. It should never be possible to have + 1. ``/blockedz``: return 200 ``hidden treasure``. It should never be possible to have traffic on the Envoy edge server's 10000 port receive this response unless a qualifying vulnerability is present. - 2. `/*`: return 200 `normal`. + 2. ``/*``: return 200 ``normal``. When running the Docker images, the following command line options should be supplied: -* `-m 3g` to ensure that memory is bounded to 3GB. At least this much memory should be available +* ``-m 3g`` to ensure that memory is bounded to 3GB. At least this much memory should be available to the execution environment. Each Envoy process has an overload manager configured to limit at 1GB. -* `-e ENVOY_EDGE_EXTRA_ARGS="<...>"` supplies additional CLI args for the edge Envoy. This +* ``-e ENVOY_EDGE_EXTRA_ARGS="<...>"`` supplies additional CLI args for the edge Envoy. This needs to be set but can be empty. -* `-e ENVOY_ORIGIN_EXTRA_ARGS="<...>"` supplies additional CLI args for the origin Envoy. This +* ``-e ENVOY_ORIGIN_EXTRA_ARGS="<...>"`` supplies additional CLI args for the origin Envoy. This needs to be set but can be empty. .. _arch_overview_google_vrp_objectives: @@ -127,9 +127,9 @@ that falls into one of these categories: * OOM: requests that cause the edge Envoy process to OOM. There should be no more than 100 connections and streams in total involved to cause this to happen (i.e. brute force connection/stream DoS is excluded). -* Routing rule bypass: requests that are able to access `hidden treasure`. +* Routing rule bypass: requests that are able to access ``hidden treasure``. * TLS certificate exfiltration: requests that are able to obtain the edge Envoy's - `serverkey.pem`. + ``serverkey.pem``. * Remote code exploits: any root shell obtained via the network data plane. * At the discretion of the OSS Envoy security team, sufficiently interesting vulnerabilities that don't fit the above categories but are likely to fall into the category of high or critical @@ -149,7 +149,7 @@ port 10000 looks like: envoyproxy/envoy-google-vrp-dev:latest When debugging, additional args may prove useful, e.g. in order to obtain trace logs, make -use of `wireshark` and `gdb`: +use of ``wireshark`` and ``gdb``: .. code-block:: bash @@ -165,7 +165,7 @@ You can obtain a shell in the Docker container with: docker exec -it envoy-google-vrp /bin/bash -The Docker images include `gdb`, `strace`, `tshark` (feel free to contribute other +The Docker images include ``gdb``, ``strace``, ``tshark`` (feel free to contribute other suggestions via PRs updating the :repo:`Docker build file `). Rebuilding the Docker image diff --git a/docs/root/intro/arch_overview/security/rbac_filter.rst b/docs/root/intro/arch_overview/security/rbac_filter.rst index 2826a7c3ae2d3..a5ba2b8e2488a 100644 --- a/docs/root/intro/arch_overview/security/rbac_filter.rst +++ b/docs/root/intro/arch_overview/security/rbac_filter.rst @@ -43,7 +43,7 @@ In addition to the pre-defined permissions and principals, a policy may optional authorization condition written in the `Common Expression Language `_. The condition specifies an extra clause that must be satisfied for the policy to match. For example, the following condition checks -whether the request path starts with `/v1/`: +whether the request path starts with ``/v1/``: .. code-block:: yaml @@ -61,5 +61,4 @@ whether the request path starts with `/v1/`: Envoy provides a number of :ref:`request attributes ` for expressive policies. Most attributes are optional and provide the default value based on the type of the attribute. CEL supports presence checks for -attributes and maps using `has()` syntax, e.g. `has(request.referer)`. - +attributes and maps using ``has()`` syntax, e.g. ``has(request.referer)``. diff --git a/docs/root/intro/arch_overview/security/ssl.rst b/docs/root/intro/arch_overview/security/ssl.rst index 9849fda2df9d2..4c770e00341b4 100644 --- a/docs/root/intro/arch_overview/security/ssl.rst +++ b/docs/root/intro/arch_overview/security/ssl.rst @@ -103,7 +103,7 @@ Custom Certificate Validator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The configuration explained above is used by the "default" certificate validator. -Envoy also supports custom validators in `envoy.tls.cert_validator` extension category which can be +Envoy also supports custom validators in ``envoy.tls.cert_validator`` extension category which can be configured on :ref:`CertificateValidationContext `. For example, Envoy can be configured to verify peer certificates following the `SPIFFE `_ specification diff --git a/docs/root/intro/arch_overview/security/threat_model.rst b/docs/root/intro/arch_overview/security/threat_model.rst index e01ef69d3c84d..b624e32d0c13c 100644 --- a/docs/root/intro/arch_overview/security/threat_model.rst +++ b/docs/root/intro/arch_overview/security/threat_model.rst @@ -66,7 +66,7 @@ The control plane management server is generally trusted. We do not consider wir against the xDS transport protocol to be a concern as a result. However, the configuration delivered to Envoy over xDS may originate from untrusted sources and may not be fully sanitized. An example of this might be a service operator that hosts multiple tenants on an Envoy, where tenants may specify -a regular expression on a header match in `RouteConfiguration`. In this case, we expect that Envoy +a regular expression on a header match in ``RouteConfiguration``. In this case, we expect that Envoy is resilient against the risks posed by malicious configuration from a confidentiality, integrity and availability perspective, as described above. diff --git a/docs/root/intro/arch_overview/upstream/circuit_breaking.rst b/docs/root/intro/arch_overview/upstream/circuit_breaking.rst index 5c808e4d4c06c..56dcd13bcd1c1 100644 --- a/docs/root/intro/arch_overview/upstream/circuit_breaking.rst +++ b/docs/root/intro/arch_overview/upstream/circuit_breaking.rst @@ -19,8 +19,8 @@ configure and code each application independently. Envoy supports various types allocated. This has the implication that the :ref:`upstream_cx_active ` count for a cluster may be higher than the cluster maximum connection circuit breaker, with an upper bound of - `cluster maximum connections + (number of endpoints in a cluster) * (connection pools for the - cluster)`. This bound applies to the sum of connections across all workers threads. See + ``cluster maximum connections + (number of endpoints in a cluster) * (connection pools for the + cluster)``. This bound applies to the sum of connections across all workers threads. See :ref:`connection pooling ` for details on how many connection pools a cluster may have. * **Cluster maximum pending requests**: The maximum number of requests that will be queued while diff --git a/docs/root/intro/arch_overview/upstream/load_balancing/load_balancers.rst b/docs/root/intro/arch_overview/upstream/load_balancing/load_balancers.rst index f3e4a4bd56f84..820a93ae96c6f 100644 --- a/docs/root/intro/arch_overview/upstream/load_balancing/load_balancers.rst +++ b/docs/root/intro/arch_overview/upstream/load_balancing/load_balancers.rst @@ -45,7 +45,7 @@ same or different weights. In this case the weights are calculated at the time a host is picked using the following formula: - `weight = load_balancing_weight / (active_requests + 1)^active_request_bias`. + ``weight = load_balancing_weight / (active_requests + 1)^active_request_bias``. :ref:`active_request_bias` can be configured via runtime and defaults to 1.0. It must be greater than or equal to 0.0. @@ -53,7 +53,7 @@ same or different weights. The larger the active request bias is, the more aggressively active requests will lower the effective weight. - If `active_request_bias` is set to 0.0, the least request load balancer behaves like the round + If ``active_request_bias`` is set to 0.0, the least request load balancer behaves like the round robin load balancer and ignores the active request count at the time of picking. For example, if active_request_bias is 1.0, a host with weight 2 and an active request count of 4 diff --git a/docs/root/intro/life_of_a_request.rst b/docs/root/intro/life_of_a_request.rst index c8f0d8c847a0e..fbd7cccdf21f5 100644 --- a/docs/root/intro/life_of_a_request.rst +++ b/docs/root/intro/life_of_a_request.rst @@ -110,7 +110,7 @@ It's helpful to focus on one at a time, so this example covers the following: and upstream. * The :ref:`HTTP connection manager ` as the only :ref:`network filter `. -* A hypothetical CustomFilter and the `router ` filter as the :ref:`HTTP +* A hypothetical CustomFilter and the :ref:`router ` filter as the :ref:`HTTP filter ` chain. * :ref:`Filesystem access logging `. * :ref:`Statsd sink `. @@ -143,7 +143,7 @@ downstream to upstream. We use the terms :ref:`listener subsystem ` and :ref:`cluster subsystem ` above to refer to the group of modules and instance classes that -are created by the top level `ListenerManager` and `ClusterManager` classes. There are many +are created by the top level ``ListenerManager`` and ``ClusterManager`` classes. There are many components that we discuss below that are instantiated before and during the course of a request by these management systems, for example listeners, filter chains, codecs, connection pools and load balancing data structures. @@ -190,7 +190,7 @@ A brief outline of the life cycle of a request and response using the example co 6. For each HTTP stream, an :ref:`HTTP filter ` chain is created and runs. The request first passes through CustomFilter which may read and modify the request. The most important HTTP filter is the router filter which sits at the end of the HTTP filter chain. - When `decodeHeaders` is invoked on the router filter, the route is selected and a cluster is + When ``decodeHeaders`` is invoked on the router filter, the route is selected and a cluster is picked. The request headers on the stream are forwarded to an upstream endpoint in that cluster. The :ref:`router ` filter obtains an HTTP :ref:`connection pool ` from the cluster manager for the matched cluster to do this. @@ -256,7 +256,7 @@ chain. The TLS inspector filter implements the :repo:`ListenerFilter ` interface. All filter interfaces, whether listener or network/HTTP, require that filters implement -callbacks for specific connection or stream events. In the case of `ListenerFilter`, this is: +callbacks for specific connection or stream events. In the case of ``ListenerFilter``, this is: .. code-block:: cpp @@ -311,7 +311,7 @@ handshake. 4. Network filter chain processing ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -As with the listener filter chain, Envoy, via `Network::FilterManagerImpl`, will instantiate a +As with the listener filter chain, Envoy, via ``Network::FilterManagerImpl``, will instantiate a series of :ref:`network filters ` from their filter factories. The instance is fresh for each new connection. Network filters, like transport sockets, follow TCP lifecycle events and are invoked as data becomes available from the transport socket. @@ -390,8 +390,8 @@ There are three kinds of HTTP filter interfaces: * :repo:`StreamDecoderFilter ` with callbacks for request processing. * :repo:`StreamEncoderFilter ` with callbacks for response processing. -* :repo:`StreamFilter ` implementing both `StreamDecoderFilter` and - `StreamEncoderFilter`. +* :repo:`StreamFilter ` implementing both ``StreamDecoderFilter`` and + ``StreamEncoderFilter``. Looking at the decoder filter interface: @@ -435,9 +435,9 @@ route selection is finalized and a cluster is picked. The HCM selects a route fr ``RouteConfiguration`` at the start of HTTP filter chain execution. This is referred to as the *cached route*. Filters may modify headers and cause a new route to be selected, by asking HCM to clear the route cache and requesting HCM to reevaluate the route selection. Filters may also -directly set this cached route selection via a `setRoute` callback. When the router filter is +directly set this cached route selection via a ``setRoute`` callback. When the router filter is invoked, the route is finalized. The selected route’s configuration will point at an upstream -cluster name. The router filter then asks the `ClusterManager` for an HTTP :ref:`connection pool +cluster name. The router filter then asks the ``ClusterManager`` for an HTTP :ref:`connection pool ` for the cluster. This involves load balancing and the connection pool, discussed in the next section. @@ -445,7 +445,7 @@ discussed in the next section. :width: 70% :align: center -The resulting HTTP connection pool is used to build an `UpstreamRequest` object in the router, which +The resulting HTTP connection pool is used to build an ``UpstreamRequest`` object in the router, which encapsulates the HTTP encoding and decoding callback methods for the upstream HTTP request. Once a stream is allocated on a connection in the HTTP connection pool, the request headers are forwarded to the upstream endpoint by the invocation of ``UpstreamRequest::encoderHeaders()``. diff --git a/docs/root/operations/admin.rst b/docs/root/operations/admin.rst index 403201f84c9ab..8d53d4c613ad0 100644 --- a/docs/root/operations/admin.rst +++ b/docs/root/operations/admin.rst @@ -281,13 +281,13 @@ modify different aspects of the server: .. note:: - Generally only used during development. With `--enable-fine-grain-logging` being set, the logger is represented - by the path of the file it belongs to (to be specific, the path determined by `__FILE__`), so the logger list + Generally only used during development. With ``--enable-fine-grain-logging`` being set, the logger is represented + by the path of the file it belongs to (to be specific, the path determined by ``__FILE__``), so the logger list will show a list of file paths, and the specific path should be used as to change the log level. .. http:get:: /memory - Prints current memory allocation / heap usage, in bytes. Useful in lieu of printing all `/stats` and filtering to get the memory-related statistics. + Prints current memory allocation / heap usage, in bytes. Useful in lieu of printing all ``/stats`` and filtering to get the memory-related statistics. .. http:post:: /quitquitquit @@ -307,7 +307,7 @@ modify different aspects of the server: .. http:post:: /drain_listeners?inboundonly - :ref:`Drains ` all inbound listeners. `traffic_direction` field in + :ref:`Drains ` all inbound listeners. ``traffic_direction`` field in :ref:`Listener ` is used to determine whether a listener is inbound or outbound. @@ -395,7 +395,7 @@ modify different aspects of the server: LIVE - See the `state` field of the :ref:`ServerInfo proto ` for an + See the ``state`` field of the :ref:`ServerInfo proto ` for an explanation of the output. .. _operations_admin_interface_stats: @@ -420,10 +420,10 @@ modify different aspects of the server: .. http:get:: /stats?filter=regex Filters the returned stats to those with names matching the regular expression - `regex`. Compatible with `usedonly`. Performs partial matching by default, so - `/stats?filter=server` will return all stats containing the word `server`. + ``regex``. Compatible with ``usedonly``. Performs partial matching by default, so + ``/stats?filter=server`` will return all stats containing the word ``server``. Full-string matching can be specified with begin- and end-line anchors. (i.e. - `/stats?filter=^server.concurrency$`) + ``/stats?filter=^server.concurrency$``) .. http:get:: /stats?format=json @@ -490,7 +490,7 @@ modify different aspects of the server: Outputs /stats in `Prometheus `_ v0.0.4 format. This can be used to integrate with a Prometheus server. - You can optionally pass the `usedonly` URL query argument to only get statistics that + You can optionally pass the ``usedonly`` URL query argument to only get statistics that Envoy has updated (counters incremented at least once, gauges changed at least once, and histograms added to at least once) @@ -500,7 +500,7 @@ modify different aspects of the server: issues in the stats system. Initially, only the count of StatName lookups is acumulated, not the specific names that are being looked up. In order to see specific recent requests, you must enable the - feature by POSTing to `/stats/recentlookups/enable`. There may be + feature by POSTing to ``/stats/recentlookups/enable``. There may be approximately 40-100 nanoseconds of added overhead per lookup. When enabled, this endpoint emits a table of stat names that were @@ -519,15 +519,15 @@ modify different aspects of the server: .. http:post:: /stats/recentlookups/enable Turns on collection of recent lookup of stat-names, thus enabling - `/stats/recentlookups`. + ``/stats/recentlookups``. See :repo:`source/docs/stats.md` for more details. .. http:post:: /stats/recentlookups/disable Turns off collection of recent lookup of stat-names, thus disabling - `/stats/recentlookups`. It also clears the list of lookups. However, - the total count, visible as stat `server.stats_recent_lookups`, is + ``/stats/recentlookups``. It also clears the list of lookups. However, + the total count, visible as stat ``server.stats_recent_lookups``, is not cleared, and continues to accumulate. See :repo:`source/docs/stats.md` for more details. diff --git a/docs/root/operations/cli.rst b/docs/root/operations/cli.rst index 8ed5c73e6e512..34e50110842a9 100644 --- a/docs/root/operations/cli.rst +++ b/docs/root/operations/cli.rst @@ -94,8 +94,8 @@ following are the command line options that Envoy supports. .. option:: --component-log-level *(optional)* The comma separated list of logging level per component. Non developers should generally - never set this option. For example, if you want `upstream` component to run at `debug` level and - `connection` component to run at `trace` level, you should pass ``upstream:debug,connection:trace`` to + never set this option. For example, if you want ``upstream`` component to run at ``debug`` level and + ``connection`` component to run at ``trace`` level, you should pass ``upstream:debug,connection:trace`` to this flag. See ``ALL_LOGGER_IDS`` in :repo:`/source/common/common/logger.h` for a list of components. .. option:: --cpuset-threads @@ -178,10 +178,10 @@ following are the command line options that Envoy supports. .. option:: --enable-fine-grain-logging *(optional)* Enables fine-grain logger with file level log control and runtime update at administration - interface. If enabled, main log macros including `ENVOY_LOG`, `ENVOY_CONN_LOG`, `ENVOY_STREAM_LOG` and - `ENVOY_FLUSH_LOG` will use a per-file logger, and the usage doesn't need `Envoy::Logger::Loggable` any - more. The administration interface usage is similar. Please see `Administration interface - `_ for more detail. + interface. If enabled, main log macros including ``ENVOY_LOG``, ``ENVOY_CONN_LOG``, ``ENVOY_STREAM_LOG`` and + ``ENVOY_FLUSH_LOG`` will use a per-file logger, and the usage doesn't need ``Envoy::Logger::Loggable`` any + more. The administration interface usage is similar. Please see :ref:`Administration interface + ` for more detail. .. option:: --socket-path diff --git a/docs/root/operations/hot_restarter.rst b/docs/root/operations/hot_restarter.rst index 3cb902dedca0e..cd5eb4af3deb9 100644 --- a/docs/root/operations/hot_restarter.rst +++ b/docs/root/operations/hot_restarter.rst @@ -13,7 +13,7 @@ The restarter is invoked like so: hot-restarter.py start_envoy.sh -`start_envoy.sh` might be defined like so (using salt/jinja like syntax): +``start_envoy.sh`` might be defined like so (using salt/jinja like syntax): .. code-block:: jinja @@ -24,7 +24,7 @@ The restarter is invoked like so: exec /usr/sbin/envoy -c /etc/envoy/envoy.cfg --restart-epoch $RESTART_EPOCH --service-cluster {{ grains['cluster_name'] }} --service-node {{ grains['service_node'] }} --service-zone {{ grains.get('ec2_availability-zone', 'unknown') }} -Note on `inotify.max_user_watches`: If Envoy is being configured to watch many files for configuration in a directory +Note on ``inotify.max_user_watches``: If Envoy is being configured to watch many files for configuration in a directory on a Linux machine, increase this value as Linux enforces limits on the maximum number of files that can be watched. The *RESTART_EPOCH* environment variable is set by the restarter on each restart and must be passed diff --git a/docs/root/operations/tools/config_generator.rst b/docs/root/operations/tools/config_generator.rst index 02bda90aa8a1d..272e69662cc3c 100644 --- a/docs/root/operations/tools/config_generator.rst +++ b/docs/root/operations/tools/config_generator.rst @@ -22,14 +22,14 @@ To generate the example configurations run the following from the root of the re tar xvf $PWD/bazel-out/k8-fastbuild/bin/configs/example_configs.tar -C generated/configs The previous command will produce three fully expanded configurations using some variables -defined inside of `configgen.py`. See the comments inside of `configgen.py` for detailed +defined inside of ``configgen.py``. See the comments inside of ``configgen.py`` for detailed information on how the different expansions work. A few notes about the example configurations: * An instance of :ref:`endpoint discovery service ` is assumed - to be running at `discovery.yourcompany.net`. -* DNS for `yourcompany.net` is assumed to be setup for various things. Search the configuration + to be running at ``discovery.yourcompany.net``. +* DNS for ``yourcompany.net`` is assumed to be setup for various things. Search the configuration templates for different instances of this. * Tracing is configured for `LightStep `_. To disable this or enable `Zipkin `_ or `Datadog `_ tracing, delete or @@ -38,6 +38,6 @@ A few notes about the example configurations: `. To disable this delete the :ref:`rate limit configuration `. * :ref:`Route discovery service ` is configured for the service to service - reference configuration and it is assumed to be running at `rds.yourcompany.net`. + reference configuration and it is assumed to be running at ``rds.yourcompany.net``. * :ref:`Cluster discovery service ` is configured for the service to - service reference configuration and it is assumed that be running at `cds.yourcompany.net`. + service reference configuration and it is assumed that be running at ``cds.yourcompany.net``. diff --git a/docs/root/operations/traffic_tapping.rst b/docs/root/operations/traffic_tapping.rst index d15eee5cffc95..9c95d7fad5e36 100644 --- a/docs/root/operations/traffic_tapping.rst +++ b/docs/root/operations/traffic_tapping.rst @@ -26,7 +26,7 @@ Tapping can be configured on :ref:`Listener ` transport sockets, providing the ability to interpose on downstream and upstream L4 connections respectively. -To configure traffic tapping, add an `envoy.transport_sockets.tap` transport socket +To configure traffic tapping, add an ``envoy.transport_sockets.tap`` transport socket :ref:`configuration ` to the listener or cluster. For a plain text socket this might look like: @@ -74,8 +74,8 @@ where the TLS context configuration replaces any existing :ref:`downstream ` TLS configuration on the listener or cluster, respectively. -Each unique socket instance will generate a trace file prefixed with `path_prefix`. E.g. -`/some/tap/path_0.pb`. +Each unique socket instance will generate a trace file prefixed with ``path_prefix``. E.g. +``/some/tap/path_0.pb``. Buffered data limits -------------------- @@ -109,7 +109,7 @@ PCAP generation The generated trace file can be converted to `libpcap format `_, suitable for analysis with tools such as `Wireshark `_ with the -`tap2pcap` utility, e.g.: +``tap2pcap`` utility, e.g.: .. code-block:: bash diff --git a/docs/root/start/building.rst b/docs/root/start/building.rst index 297c403971d08..e5bf5fe682024 100644 --- a/docs/root/start/building.rst +++ b/docs/root/start/building.rst @@ -26,7 +26,7 @@ Building Envoy has the following requirements: :ref:`this FAQ entry ` for more information on build performance. * These :repo:`Bazel native ` dependencies. -Please note that for Clang/LLVM 8 and lower, Envoy may need to be built with `--define tcmalloc=gperftools` +Please note that for Clang/LLVM 8 and lower, Envoy may need to be built with ``--define tcmalloc=gperftools`` as the new tcmalloc code is not guaranteed to compile with lower versions of Clang. diff --git a/docs/root/start/quick-start/run-envoy.rst b/docs/root/start/quick-start/run-envoy.rst index 3b2c1749a3163..3d6c7b8c103b8 100644 --- a/docs/root/start/quick-start/run-envoy.rst +++ b/docs/root/start/quick-start/run-envoy.rst @@ -155,7 +155,7 @@ Check Envoy is proxying on http://localhost:10000. $ curl -v localhost:10000 ... -You can exit the server with `Ctrl-c`. +You can exit the server with ``Ctrl-c``. See the :ref:`admin quick start guide ` for more information about the Envoy admin interface. From fe62d976a26faa46efc590a48a734f11ee6545f9 Mon Sep 17 00:00:00 2001 From: danzh Date: Mon, 3 May 2021 16:24:18 -0400 Subject: [PATCH 133/209] quiche: make flow control configurable (#15865) Commit Message: Add initial stream and connection level flow control windows to envoy.config.core.v3.QuicProtocolOptions which is be used in QUIC listener config and Http3 upstream cluster config. Risk Level: low Testing: re-enable more Http3 downstream protocol test. Part of #2557 #12930 #14829 Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- api/envoy/config/core/v3/protocol.proto | 22 ++++++ api/envoy/config/core/v4alpha/protocol.proto | 22 ++++++ docs/protodoc_manifest.yaml | 5 ++ .../envoy/config/core/v3/protocol.proto | 22 ++++++ .../envoy/config/core/v4alpha/protocol.proto | 22 ++++++ source/common/http/http3/codec_stats.h | 1 + source/common/http/http3/conn_pool.cc | 5 +- source/common/http/utility.cc | 5 ++ source/common/http/utility.h | 10 ++- source/common/quic/BUILD | 2 + source/common/quic/active_quic_listener.cc | 1 + .../quic/client_connection_factory_impl.cc | 10 ++- .../quic/client_connection_factory_impl.h | 3 + .../common/quic/envoy_quic_client_stream.cc | 5 +- source/common/quic/envoy_quic_dispatcher.cc | 6 -- .../common/quic/envoy_quic_server_stream.cc | 3 +- source/common/quic/envoy_quic_utils.cc | 27 +++++++ source/common/quic/envoy_quic_utils.h | 6 ++ test/common/network/BUILD | 5 +- test/common/quic/BUILD | 1 + test/common/quic/active_quic_listener_test.cc | 76 ++++++++++++++++++- .../quic/envoy_quic_client_stream_test.cc | 10 +++ .../quic/envoy_quic_server_stream_test.cc | 12 +++ test/config/utility.cc | 29 ++++++- test/config/utility.h | 5 ++ test/integration/http_integration.cc | 17 ++++- test/integration/http_integration.h | 3 +- .../multiplexed_integration_test.cc | 12 +-- test/integration/protocol_integration_test.cc | 10 +-- test/integration/protocol_integration_test.h | 2 +- .../integration/quic_http_integration_test.cc | 31 ++------ 31 files changed, 326 insertions(+), 64 deletions(-) diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index 4109b19a4abd3..65bd64d863a2b 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -31,6 +31,28 @@ message QuicProtocolOptions { // Maximum number of streams that the client can negotiate per connection. 100 // if not specified. google.protobuf.UInt32Value max_concurrent_streams = 1; + + // `Initial stream-level flow-control receive window + // `_ size. Valid values range from + // 1 to 16777216 (2^24, maximum supported by QUICHE) and defaults to 65536 (2^16). + // + // NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. If configured smaller than it, we will use 16384 instead. + // QUICHE IETF Quic implementation supports 1 bytes window. We only support increasing the default window size now, so it's also the minimum. + // + // This field also acts as a soft limit on the number of bytes Envoy will buffer per-stream in the + // QUIC stream send and receive buffers. Once the buffer reaches this pointer, watermark callbacks will fire to + // stop the flow of data to the stream buffers. + google.protobuf.UInt32Value initial_stream_window_size = 2 + [(validate.rules).uint32 = {lte: 16777216 gte: 1}]; + + // Similar to *initial_stream_window_size*, but for connection-level + // flow-control. Valid values rage from 1 to 25165824 (24MB, maximum supported by QUICHE) and defaults to 65536 (2^16). + // window. Currently, this has the same minimum/default as *initial_stream_window_size*. + // + // NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. We only support increasing the default + // window size now, so it's also the minimum. + google.protobuf.UInt32Value initial_connection_window_size = 3 + [(validate.rules).uint32 = {lte: 25165824 gte: 1}]; } message UpstreamHttpProtocolOptions { diff --git a/api/envoy/config/core/v4alpha/protocol.proto b/api/envoy/config/core/v4alpha/protocol.proto index 1e14ae5ee999a..59c61510b4bc7 100644 --- a/api/envoy/config/core/v4alpha/protocol.proto +++ b/api/envoy/config/core/v4alpha/protocol.proto @@ -33,6 +33,28 @@ message QuicProtocolOptions { // Maximum number of streams that the client can negotiate per connection. 100 // if not specified. google.protobuf.UInt32Value max_concurrent_streams = 1; + + // `Initial stream-level flow-control receive window + // `_ size. Valid values range from + // 1 to 16777216 (2^24, maximum supported by QUICHE) and defaults to 65536 (2^16). + // + // NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. If configured smaller than it, we will use 16384 instead. + // QUICHE IETF Quic implementation supports 1 bytes window. We only support increasing the default window size now, so it's also the minimum. + // + // This field also acts as a soft limit on the number of bytes Envoy will buffer per-stream in the + // QUIC stream send and receive buffers. Once the buffer reaches this pointer, watermark callbacks will fire to + // stop the flow of data to the stream buffers. + google.protobuf.UInt32Value initial_stream_window_size = 2 + [(validate.rules).uint32 = {lte: 16777216 gte: 1}]; + + // Similar to *initial_stream_window_size*, but for connection-level + // flow-control. Valid values rage from 1 to 25165824 (24MB, maximum supported by QUICHE) and defaults to 65536 (2^16). + // window. Currently, this has the same minimum/default as *initial_stream_window_size*. + // + // NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. We only support increasing the default + // window size now, so it's also the minimum. + google.protobuf.UInt32Value initial_connection_window_size = 3 + [(validate.rules).uint32 = {lte: 25165824 gte: 1}]; } message UpstreamHttpProtocolOptions { diff --git a/docs/protodoc_manifest.yaml b/docs/protodoc_manifest.yaml index cffe486ea1748..e12bdea114202 100644 --- a/docs/protodoc_manifest.yaml +++ b/docs/protodoc_manifest.yaml @@ -52,3 +52,8 @@ fields: example: 300s # 5 mins envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.use_remote_address: edge_config: { example: true } + envoy.config.listener.v3.QuicProtocolOptions.quic_protocol_options: + edge_config: + example: + initial_stream_window_size: 65536 # 64 KiB + initial_connection_window_size: 65536 # 64 KiB diff --git a/generated_api_shadow/envoy/config/core/v3/protocol.proto b/generated_api_shadow/envoy/config/core/v3/protocol.proto index 4109b19a4abd3..65bd64d863a2b 100644 --- a/generated_api_shadow/envoy/config/core/v3/protocol.proto +++ b/generated_api_shadow/envoy/config/core/v3/protocol.proto @@ -31,6 +31,28 @@ message QuicProtocolOptions { // Maximum number of streams that the client can negotiate per connection. 100 // if not specified. google.protobuf.UInt32Value max_concurrent_streams = 1; + + // `Initial stream-level flow-control receive window + // `_ size. Valid values range from + // 1 to 16777216 (2^24, maximum supported by QUICHE) and defaults to 65536 (2^16). + // + // NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. If configured smaller than it, we will use 16384 instead. + // QUICHE IETF Quic implementation supports 1 bytes window. We only support increasing the default window size now, so it's also the minimum. + // + // This field also acts as a soft limit on the number of bytes Envoy will buffer per-stream in the + // QUIC stream send and receive buffers. Once the buffer reaches this pointer, watermark callbacks will fire to + // stop the flow of data to the stream buffers. + google.protobuf.UInt32Value initial_stream_window_size = 2 + [(validate.rules).uint32 = {lte: 16777216 gte: 1}]; + + // Similar to *initial_stream_window_size*, but for connection-level + // flow-control. Valid values rage from 1 to 25165824 (24MB, maximum supported by QUICHE) and defaults to 65536 (2^16). + // window. Currently, this has the same minimum/default as *initial_stream_window_size*. + // + // NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. We only support increasing the default + // window size now, so it's also the minimum. + google.protobuf.UInt32Value initial_connection_window_size = 3 + [(validate.rules).uint32 = {lte: 25165824 gte: 1}]; } message UpstreamHttpProtocolOptions { diff --git a/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto b/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto index 85032f6637351..badf94fc995fe 100644 --- a/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto +++ b/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto @@ -34,6 +34,28 @@ message QuicProtocolOptions { // Maximum number of streams that the client can negotiate per connection. 100 // if not specified. google.protobuf.UInt32Value max_concurrent_streams = 1; + + // `Initial stream-level flow-control receive window + // `_ size. Valid values range from + // 1 to 16777216 (2^24, maximum supported by QUICHE) and defaults to 65536 (2^16). + // + // NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. If configured smaller than it, we will use 16384 instead. + // QUICHE IETF Quic implementation supports 1 bytes window. We only support increasing the default window size now, so it's also the minimum. + // + // This field also acts as a soft limit on the number of bytes Envoy will buffer per-stream in the + // QUIC stream send and receive buffers. Once the buffer reaches this pointer, watermark callbacks will fire to + // stop the flow of data to the stream buffers. + google.protobuf.UInt32Value initial_stream_window_size = 2 + [(validate.rules).uint32 = {lte: 16777216 gte: 1}]; + + // Similar to *initial_stream_window_size*, but for connection-level + // flow-control. Valid values rage from 1 to 25165824 (24MB, maximum supported by QUICHE) and defaults to 65536 (2^16). + // window. Currently, this has the same minimum/default as *initial_stream_window_size*. + // + // NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. We only support increasing the default + // window size now, so it's also the minimum. + google.protobuf.UInt32Value initial_connection_window_size = 3 + [(validate.rules).uint32 = {lte: 25165824 gte: 1}]; } message UpstreamHttpProtocolOptions { diff --git a/source/common/http/http3/codec_stats.h b/source/common/http/http3/codec_stats.h index bb720172907cd..327b345f1d468 100644 --- a/source/common/http/http3/codec_stats.h +++ b/source/common/http/http3/codec_stats.h @@ -21,6 +21,7 @@ namespace Http3 { COUNTER(rx_reset) \ COUNTER(trailers) \ COUNTER(tx_reset) \ + COUNTER(metadata_not_supported_error) \ GAUGE(streams_active, Accumulate) /** diff --git a/source/common/http/http3/conn_pool.cc b/source/common/http/http3/conn_pool.cc index 1184618ef01a3..254bc204622d6 100644 --- a/source/common/http/http3/conn_pool.cc +++ b/source/common/http/http3/conn_pool.cc @@ -14,6 +14,7 @@ #ifdef ENVOY_ENABLE_QUIC #include "common/quic/client_connection_factory_impl.h" +#include "common/quic/envoy_quic_utils.h" #else #error "http3 conn pool should not be built with QUIC disabled" #endif @@ -44,6 +45,8 @@ class Http3ConnPoolImpl : public FixedHttpConnPoolImpl { quic_info_ = std::make_unique( dispatcher, transport_socket_factory, host->cluster().statsScope(), time_source, source_address); + Quic::configQuicInitialFlowControlWindow( + host_->cluster().http3Options().quic_protocol_options(), quic_info_->quic_config_); } // Make sure all connections are torn down before quic_info_ is deleted. @@ -51,7 +54,7 @@ class Http3ConnPoolImpl : public FixedHttpConnPoolImpl { // Store quic helpers which can be shared between connections and must live // beyond the lifetime of individual connections. - std::unique_ptr quic_info_; + std::unique_ptr quic_info_; }; ConnectionPool::InstancePtr diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index 435edbfdbe447..4c5a237ef2ccc 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -199,8 +199,13 @@ initializeAndValidateOptions(const envoy::config::core::v3::Http2ProtocolOptions } // namespace Utility } // namespace Http2 + namespace Http3 { namespace Utility { + +const uint32_t OptionsLimits::DEFAULT_INITIAL_STREAM_WINDOW_SIZE; +const uint32_t OptionsLimits::DEFAULT_INITIAL_CONNECTION_WINDOW_SIZE; + envoy::config::core::v3::Http3ProtocolOptions initializeAndValidateOptions(const envoy::config::core::v3::Http3ProtocolOptions& options, bool hcm_stream_error_set, diff --git a/source/common/http/utility.h b/source/common/http/utility.h index 64d85613ee1a3..3cf328ea3b5ff 100644 --- a/source/common/http/utility.h +++ b/source/common/http/utility.h @@ -118,11 +118,19 @@ initializeAndValidateOptions(const envoy::config::core::v3::Http2ProtocolOptions } // namespace Http2 namespace Http3 { namespace Utility { + +// Limits and defaults for `envoy::config::core::v3::Http3ProtocolOptions` protos. +struct OptionsLimits { + // The same as kStreamReceiveWindowLimit in QUICHE which is the maximum supported by QUICHE. + static const uint32_t DEFAULT_INITIAL_STREAM_WINDOW_SIZE = 16 * 1024 * 1024; + // The same as kSessionReceiveWindowLimit in QUICHE which is the maximum supported by QUICHE. + static const uint32_t DEFAULT_INITIAL_CONNECTION_WINDOW_SIZE = 24 * 1024 * 1024; +}; + envoy::config::core::v3::Http3ProtocolOptions initializeAndValidateOptions(const envoy::config::core::v3::Http3ProtocolOptions& options, bool hcm_stream_error_set, const Protobuf::BoolValue& hcm_stream_error); - } // namespace Utility } // namespace Http3 namespace Http { diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index 9e4dae98192f9..e84c3fb274a45 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -359,8 +359,10 @@ envoy_cc_library( "//source/common/network:socket_option_factory_lib", "//source/common/quic:quic_io_handle_wrapper_lib", "//source/extensions/transport_sockets:well_known_names", + "@com_googlesource_quiche//:quic_core_config_lib", "@com_googlesource_quiche//:quic_core_http_header_list_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/config/listener/v3:pkg_cc_proto", ], ) diff --git a/source/common/quic/active_quic_listener.cc b/source/common/quic/active_quic_listener.cc index 6fe1af4b38d84..6115c9e5acfa6 100644 --- a/source/common/quic/active_quic_listener.cc +++ b/source/common/quic/active_quic_listener.cc @@ -232,6 +232,7 @@ ActiveQuicListenerFactory::ActiveQuicListenerFactory( PROTOBUF_GET_WRAPPED_OR_DEFAULT(config.quic_protocol_options(), max_concurrent_streams, 100); quic_config_.SetMaxBidirectionalStreamsToSend(max_streams); quic_config_.SetMaxUnidirectionalStreamsToSend(max_streams); + configQuicInitialFlowControlWindow(config.quic_protocol_options(), quic_config_); } Network::ConnectionHandler::ActiveUdpListenerPtr ActiveQuicListenerFactory::createActiveUdpListener( diff --git a/source/common/quic/client_connection_factory_impl.cc b/source/common/quic/client_connection_factory_impl.cc index ffb6951237f9a..615f04a02a275 100644 --- a/source/common/quic/client_connection_factory_impl.cc +++ b/source/common/quic/client_connection_factory_impl.cc @@ -31,7 +31,6 @@ namespace { // This was preexisting but should either be removed or potentially moved inside // PersistentQuicInfoImpl. struct StaticInfo { - quic::QuicConfig quic_config_; quic::QuicClientPushPromiseIndex push_promise_index_; static StaticInfo& get() { MUTABLE_CONSTRUCT_ON_FIRST_USE(StaticInfo); } @@ -51,10 +50,15 @@ createQuicNetworkConnection(Http::PersistentQuicInfo& info, Event::Dispatcher& d info_impl->alarm_factory_, quic::ParsedQuicVersionVector{info_impl->supported_versions_[0]}, local_addr, dispatcher, nullptr); auto& static_info = StaticInfo::get(); + + ASSERT(!info_impl->supported_versions_.empty()); + // QUICHE client session always use the 1st version to start handshake. + // TODO(alyssawilk) pass in ClusterInfo::perConnectionBufferLimitBytes() for + // send_buffer_limit instead of using 0. auto ret = std::make_unique( - static_info.quic_config_, info_impl->supported_versions_, std::move(connection), + info_impl->quic_config_, info_impl->supported_versions_, std::move(connection), info_impl->server_id_, info_impl->crypto_config_.get(), &static_info.push_promise_index_, - dispatcher, 0); + dispatcher, /*send_buffer_limit=*/0); return ret; } diff --git a/source/common/quic/client_connection_factory_impl.h b/source/common/quic/client_connection_factory_impl.h index df52e80beb019..8564280a43b45 100644 --- a/source/common/quic/client_connection_factory_impl.h +++ b/source/common/quic/client_connection_factory_impl.h @@ -28,7 +28,10 @@ struct PersistentQuicInfoImpl : public Http::PersistentQuicInfo { // given connection pool. quic::QuicServerId server_id_; quic::ParsedQuicVersionVector supported_versions_{quic::CurrentSupportedVersions()}; + // TODO(danzh) move this into client transport socket factory so that it can + // be updated with SDS. std::unique_ptr crypto_config_; + quic::QuicConfig quic_config_; }; std::unique_ptr diff --git a/source/common/quic/envoy_quic_client_stream.cc b/source/common/quic/envoy_quic_client_stream.cc index 185c7bc588d22..cda06215167d9 100644 --- a/source/common/quic/envoy_quic_client_stream.cc +++ b/source/common/quic/envoy_quic_client_stream.cc @@ -105,8 +105,9 @@ void EnvoyQuicClientStream::encodeTrailers(const Http::RequestTrailerMap& traile } void EnvoyQuicClientStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { - // Metadata Frame is not supported in QUIC. - // TODO(danzh): add stats for metadata not supported error. + // Metadata Frame is not supported in QUICHE. + ENVOY_STREAM_LOG(debug, "METADATA is not supported in Http3.", *this); + stats_.metadata_not_supported_error_.inc(); } void EnvoyQuicClientStream::resetStream(Http::StreamResetReason reason) { diff --git a/source/common/quic/envoy_quic_dispatcher.cc b/source/common/quic/envoy_quic_dispatcher.cc index 8626830194043..f1577a40ce52e 100644 --- a/source/common/quic/envoy_quic_dispatcher.cc +++ b/source/common/quic/envoy_quic_dispatcher.cc @@ -52,12 +52,6 @@ std::unique_ptr EnvoyQuicDispatcher::CreateQuicSession( const quic::QuicSocketAddress& peer_address, absl::string_view alpn, const quic::ParsedQuicVersion& version, absl::string_view sni) { quic::QuicConfig quic_config = config(); - // TODO(danzh) setup flow control window via config. - quic_config.SetInitialStreamFlowControlWindowToSend( - Http2::Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE); - quic_config.SetInitialSessionFlowControlWindowToSend( - 1.5 * Http2::Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE); - Network::ConnectionSocketPtr connection_socket = createServerConnectionSocket( listen_socket_.ioHandle(), self_address, peer_address, std::string(sni), alpn); const Network::FilterChain* filter_chain = diff --git a/source/common/quic/envoy_quic_server_stream.cc b/source/common/quic/envoy_quic_server_stream.cc index 094b1069fb05b..ab2d0bc9e5467 100644 --- a/source/common/quic/envoy_quic_server_stream.cc +++ b/source/common/quic/envoy_quic_server_stream.cc @@ -113,7 +113,8 @@ void EnvoyQuicServerStream::encodeTrailers(const Http::ResponseTrailerMap& trail void EnvoyQuicServerStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { // Metadata Frame is not supported in QUIC. - // TODO(danzh): add stats for metadata not supported error. + ENVOY_STREAM_LOG(debug, "METADATA is not supported in Http3.", *this); + stats_.metadata_not_supported_error_.inc(); } void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { diff --git a/source/common/quic/envoy_quic_utils.cc b/source/common/quic/envoy_quic_utils.cc index 8f9460db3e6ce..969782322f098 100644 --- a/source/common/quic/envoy_quic_utils.cc +++ b/source/common/quic/envoy_quic_utils.cc @@ -5,6 +5,7 @@ #include "envoy/common/platform.h" #include "envoy/config/core/v3/base.pb.h" +#include "common/http/utility.h" #include "common/network/socket_option_factory.h" #include "common/network/utility.h" @@ -243,5 +244,31 @@ createServerConnectionSocket(Network::IoHandle& io_handle, return connection_socket; } +void configQuicInitialFlowControlWindow(const envoy::config::core::v3::QuicProtocolOptions& config, + quic::QuicConfig& quic_config) { + size_t stream_flow_control_window_to_send = PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, initial_stream_window_size, + Http3::Utility::OptionsLimits::DEFAULT_INITIAL_STREAM_WINDOW_SIZE); + if (stream_flow_control_window_to_send < quic::kMinimumFlowControlSendWindow) { + // If the configured value is smaller than 16kB, only use it for IETF QUIC, because Google QUIC + // requires minimum 16kB stream flow control window. The QUICHE default 16kB will be used for + // Google QUIC connections. + quic_config.SetInitialMaxStreamDataBytesIncomingBidirectionalToSend( + stream_flow_control_window_to_send); + } else { + // Both Google QUIC and IETF Quic can be configured from this. + quic_config.SetInitialStreamFlowControlWindowToSend(stream_flow_control_window_to_send); + } + + uint32_t session_flow_control_window_to_send = PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, initial_connection_window_size, + Http3::Utility::OptionsLimits::DEFAULT_INITIAL_CONNECTION_WINDOW_SIZE); + // Config connection level flow control window shouldn't be smaller than the minimum flow control + // window supported in QUICHE which is 16kB. + quic_config.SetInitialSessionFlowControlWindowToSend( + std::max(quic::kMinimumFlowControlSendWindow, + static_cast(session_flow_control_window_to_send))); +} + } // namespace Quic } // namespace Envoy diff --git a/source/common/quic/envoy_quic_utils.h b/source/common/quic/envoy_quic_utils.h index f71d2fd0c58fb..2625e7acc0e0c 100644 --- a/source/common/quic/envoy_quic_utils.h +++ b/source/common/quic/envoy_quic_utils.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/common/platform.h" +#include "envoy/config/listener/v3/quic_config.pb.h" #include "envoy/http/codec.h" #include "common/common/assert.h" @@ -16,6 +17,7 @@ #endif #include "quiche/quic/core/quic_types.h" +#include "quiche/quic/core/quic_config.h" #if defined(__GNUC__) #pragma GCC diagnostic pop @@ -162,5 +164,9 @@ createServerConnectionSocket(Network::IoHandle& io_handle, const quic::QuicSocketAddress& peer_address, const std::string& hostname, absl::string_view alpn); +// Set initial flow control windows in quic_config according to the given Envoy config. +void configQuicInitialFlowControlWindow(const envoy::config::core::v3::QuicProtocolOptions& config, + quic::QuicConfig& quic_config); + } // namespace Quic } // namespace Envoy diff --git a/test/common/network/BUILD b/test/common/network/BUILD index e38391834b07a..e38f0aa74ff2e 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -291,7 +291,10 @@ envoy_cc_test( name = "udp_listener_impl_batch_writer_test", srcs = ["udp_listener_impl_batch_writer_test.cc"], # Skipping as quiche quic_gso_batch_writer.h does not exist on Windows - tags = ["skip_on_windows"], + tags = [ + "nofips", + "skip_on_windows", + ], deps = [ ":udp_listener_impl_test_base_lib", "//source/common/event:dispatcher_lib", diff --git a/test/common/quic/BUILD b/test/common/quic/BUILD index 53967319be888..4c020dfed84d5 100644 --- a/test/common/quic/BUILD +++ b/test/common/quic/BUILD @@ -169,6 +169,7 @@ envoy_cc_test( deps = [ ":quic_test_utils_for_envoy_lib", ":test_utils_lib", + "//source/common/http:utility_lib", "//source/common/network:udp_packet_writer_handler_lib", "//source/common/quic:active_quic_listener_lib", "//source/common/quic:envoy_quic_utils_lib", diff --git a/test/common/quic/active_quic_listener_test.cc b/test/common/quic/active_quic_listener_test.cc index d4cfe8a54231a..b9df504e88a44 100644 --- a/test/common/quic/active_quic_listener_test.cc +++ b/test/common/quic/active_quic_listener_test.cc @@ -27,6 +27,7 @@ #include "common/network/udp_packet_writer_handler_impl.h" #include "common/runtime/runtime_impl.h" #include "common/quic/active_quic_listener.h" +#include "common/http/utility.h" #include "test/common/quic/test_utils.h" #include "test/common/quic/test_proof_source.h" #include "test/test_common/simulated_time_system.h" @@ -124,7 +125,9 @@ class ActiveQuicListenerTest : public QuicMultiVersionTest { return std::make_unique(io_handle); #endif })); + } + void initialize() { listener_factory_ = createQuicListenerFactory(yamlForQuicConfig()); EXPECT_CALL(listener_config_, filterChainManager()) .WillRepeatedly(ReturnRef(filter_chain_manager_)); @@ -257,11 +260,15 @@ class ActiveQuicListenerTest : public QuicMultiVersionTest { protected: virtual std::string yamlForQuicConfig() { - return R"EOF( + return fmt::format(R"EOF( + quic_protocol_options: + initial_connection_window_size: {} + initial_stream_window_size: {} enabled: default_value: true runtime_key: quic.enabled -)EOF"; +)EOF", + connection_window_size_, stream_window_size_); } Network::Address::IpVersion version_; @@ -301,12 +308,15 @@ class ActiveQuicListenerTest : public QuicMultiVersionTest { std::list> filter_factories_; const Network::MockFilterChain* filter_chain_; quic::ParsedQuicVersion quic_version_; + uint32_t connection_window_size_{1024u}; + uint32_t stream_window_size_{1024u}; }; INSTANTIATE_TEST_SUITE_P(ActiveQuicListenerTests, ActiveQuicListenerTest, testing::ValuesIn(generateTestParam()), testParamsToString); TEST_P(ActiveQuicListenerTest, FailSocketOptionUponCreation) { + initialize(); auto option = std::make_unique(); EXPECT_CALL(*option, setOption(_, envoy::config::core::v3::SocketOption::STATE_BOUND)) .WillOnce(Return(false)); @@ -323,17 +333,59 @@ TEST_P(ActiveQuicListenerTest, FailSocketOptionUponCreation) { } TEST_P(ActiveQuicListenerTest, ReceiveCHLO) { + initialize(); quic::QuicBufferedPacketStore* const buffered_packets = quic::test::QuicDispatcherPeer::GetBufferedPackets(quic_dispatcher_); maybeConfigureMocks(/* connection_count = */ 1); - sendCHLO(quic::test::TestConnectionId(1)); + quic::QuicConnectionId connection_id = quic::test::TestConnectionId(1); + sendCHLO(connection_id); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); EXPECT_FALSE(buffered_packets->HasChlosBuffered()); EXPECT_NE(0u, quic_dispatcher_->NumSessions()); + const quic::QuicSession* session = + quic::test::QuicDispatcherPeer::FindSession(quic_dispatcher_, connection_id); + ASSERT(session != nullptr); + // 1024 is too small for QUICHE, should be adjusted to the minimum supported by QUICHE. + EXPECT_EQ(quic::kMinimumFlowControlSendWindow, const_cast(session) + ->config() + ->GetInitialSessionFlowControlWindowToSend()); + // IETF Quic supports low flow control limit. But Google Quic only supports flow control window no + // smaller than 16kB. + if (GetParam().second == QuicVersionType::Iquic) { + EXPECT_EQ(stream_window_size_, const_cast(session) + ->config() + ->GetInitialMaxStreamDataBytesIncomingBidirectionalToSend()); + } else { + EXPECT_EQ(quic::kMinimumFlowControlSendWindow, const_cast(session) + ->config() + ->GetInitialStreamFlowControlWindowToSend()); + } + readFromClientSockets(); +} + +TEST_P(ActiveQuicListenerTest, ConfigureReasonableInitialFlowControlWindow) { + // These initial flow control windows should be accepted by both Google QUIC and IETF QUIC. + connection_window_size_ = 64 * 1024; + stream_window_size_ = 32 * 1024; + initialize(); + maybeConfigureMocks(/* connection_count = */ 1); + quic::QuicConnectionId connection_id = quic::test::TestConnectionId(1); + sendCHLO(connection_id); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + const quic::QuicSession* session = + quic::test::QuicDispatcherPeer::FindSession(quic_dispatcher_, connection_id); + ASSERT(session != nullptr); + EXPECT_EQ(connection_window_size_, const_cast(session) + ->config() + ->GetInitialSessionFlowControlWindowToSend()); + EXPECT_EQ(stream_window_size_, const_cast(session) + ->config() + ->GetInitialMaxStreamDataBytesIncomingBidirectionalToSend()); readFromClientSockets(); } TEST_P(ActiveQuicListenerTest, ProcessBufferedChlos) { + initialize(); quic::QuicBufferedPacketStore* const buffered_packets = quic::test::QuicDispatcherPeer::GetBufferedPackets(quic_dispatcher_); const uint32_t count = (ActiveQuicListener::kNumSessionsToCreatePerLoop * 2) + 1; @@ -359,6 +411,7 @@ TEST_P(ActiveQuicListenerTest, ProcessBufferedChlos) { } TEST_P(ActiveQuicListenerTest, QuicProcessingDisabledAndEnabled) { + initialize(); maybeConfigureMocks(/* connection_count = */ 2); EXPECT_TRUE(ActiveQuicListenerPeer::enabled(*quic_listener_)); sendCHLO(quic::test::TestConnectionId(1)); @@ -382,6 +435,7 @@ TEST_P(ActiveQuicListenerTest, QuicProcessingDisabledAndEnabled) { class ActiveQuicListenerEmptyFlagConfigTest : public ActiveQuicListenerTest { protected: std::string yamlForQuicConfig() override { + // Do not config flow control windows. return R"EOF( quic_protocol_options: max_concurrent_streams: 10 @@ -395,14 +449,28 @@ INSTANTIATE_TEST_SUITE_P(ActiveQuicListenerEmptyFlagConfigTests, // Quic listener should be enabled by default, if not enabled explicitly in config. TEST_P(ActiveQuicListenerEmptyFlagConfigTest, ReceiveFullQuicCHLO) { + initialize(); quic::QuicBufferedPacketStore* const buffered_packets = quic::test::QuicDispatcherPeer::GetBufferedPackets(quic_dispatcher_); maybeConfigureMocks(/* connection_count = */ 1); - sendCHLO(quic::test::TestConnectionId(1)); + quic::QuicConnectionId connection_id = quic::test::TestConnectionId(1); + sendCHLO(connection_id); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); EXPECT_FALSE(buffered_packets->HasChlosBuffered()); EXPECT_NE(0u, quic_dispatcher_->NumSessions()); EXPECT_TRUE(ActiveQuicListenerPeer::enabled(*quic_listener_)); + const quic::QuicSession* session = + quic::test::QuicDispatcherPeer::FindSession(quic_dispatcher_, connection_id); + ASSERT(session != nullptr); + // Check defaults stream and connection flow control window to send. + EXPECT_EQ(Http3::Utility::OptionsLimits::DEFAULT_INITIAL_CONNECTION_WINDOW_SIZE, + const_cast(session) + ->config() + ->GetInitialSessionFlowControlWindowToSend()); + EXPECT_EQ(Http3::Utility::OptionsLimits::DEFAULT_INITIAL_STREAM_WINDOW_SIZE, + const_cast(session) + ->config() + ->GetInitialMaxStreamDataBytesIncomingBidirectionalToSend()); readFromClientSockets(); } diff --git a/test/common/quic/envoy_quic_client_stream_test.cc b/test/common/quic/envoy_quic_client_stream_test.cc index 3cda1218570aa..23b3f8aa8d774 100644 --- a/test/common/quic/envoy_quic_client_stream_test.cc +++ b/test/common/quic/envoy_quic_client_stream_test.cc @@ -577,6 +577,16 @@ TEST_P(EnvoyQuicClientStreamTest, CloseConnectionDuringDecodingTrailer) { } } +TEST_P(EnvoyQuicClientStreamTest, MetadataNotSupported) { + Http::MetadataMap metadata_map = {{"key", "value"}}; + Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); + Http::MetadataMapVector metadata_map_vector; + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + quic_stream_->encodeMetadata(metadata_map_vector); + EXPECT_EQ(1, TestUtility::findCounter(scope_, "http3.metadata_not_supported_error")->value()); + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); +} + // Tests that posted stream block callback won't cause use-after-free crash. TEST_P(EnvoyQuicClientStreamTest, ReadDisabledBeforeClose) { const auto result = quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/true); diff --git a/test/common/quic/envoy_quic_server_stream_test.cc b/test/common/quic/envoy_quic_server_stream_test.cc index b73e685f5c249..31738861bbd2c 100644 --- a/test/common/quic/envoy_quic_server_stream_test.cc +++ b/test/common/quic/envoy_quic_server_stream_test.cc @@ -727,5 +727,17 @@ TEST_P(EnvoyQuicServerStreamTest, ConnectionCloseAfterEndStreamEncoded) { quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/true); } +TEST_P(EnvoyQuicServerStreamTest, MetadataNotSupported) { + Http::MetadataMap metadata_map = {{"key", "value"}}; + Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); + Http::MetadataMapVector metadata_map_vector; + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + quic_stream_->encodeMetadata(metadata_map_vector); + EXPECT_EQ(1, + TestUtility::findCounter(listener_config_.scope_, "http3.metadata_not_supported_error") + ->value()); + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); +} + } // namespace Quic } // namespace Envoy diff --git a/test/config/utility.cc b/test/config/utility.cc index 0a444ee9cff49..59e66346c0d61 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -941,6 +941,20 @@ void ConfigHelper::setBufferLimits(uint32_t upstream_buffer_limit, RELEASE_ASSERT(bootstrap_.mutable_static_resources()->listeners_size() == 1, ""); auto* listener = bootstrap_.mutable_static_resources()->mutable_listeners(0); listener->mutable_per_connection_buffer_limit_bytes()->set_value(downstream_buffer_limit); + const uint32_t stream_buffer_size = std::max( + downstream_buffer_limit, Http2::Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE); + if (Network::Utility::protobufAddressSocketType(listener->address()) == + Network::Socket::Type::Datagram && + listener->udp_listener_config().has_quic_options()) { + // QUIC stream's flow control window is configured in listener config. + listener->mutable_udp_listener_config() + ->mutable_quic_options() + ->mutable_quic_protocol_options() + ->mutable_initial_stream_window_size() + // The same as kStreamReceiveWindowLimit in QUICHE which only supports stream flow control + // window no larger than 16MB. + ->set_value(std::min(16u * 1024 * 1024, stream_buffer_size)); + } auto* static_resources = bootstrap_.mutable_static_resources(); for (int i = 0; i < bootstrap_.mutable_static_resources()->clusters_size(); ++i) { @@ -955,10 +969,8 @@ void ConfigHelper::setBufferLimits(uint32_t upstream_buffer_limit, loadHttpConnectionManager(hcm_config); if (hcm_config.codec_type() == envoy::extensions::filters::network::http_connection_manager:: v3::HttpConnectionManager::HTTP2) { - const uint32_t size = std::max(downstream_buffer_limit, - Http2::Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE); auto* options = hcm_config.mutable_http2_protocol_options(); - options->mutable_initial_stream_window_size()->set_value(size); + options->mutable_initial_stream_window_size()->set_value(stream_buffer_size); storeHttpConnectionManager(hcm_config); } } @@ -1358,6 +1370,17 @@ void ConfigHelper::setLocalReply( storeHttpConnectionManager(hcm_config); } +void ConfigHelper::adjustUpstreamTimeoutForTsan( + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& hcm) { + auto* route = + hcm.mutable_route_config()->mutable_virtual_hosts(0)->mutable_routes(0)->mutable_route(); + uint64_t timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(*route, timeout, 15000u); + auto* timeout = route->mutable_timeout(); + // QUIC stream processing is slow under TSAN. Use larger timeout to prevent + // upstream_response_timeout. + timeout->set_seconds(TSAN_TIMEOUT_FACTOR * timeout_ms / 1000); +} + CdsHelper::CdsHelper() : cds_path_(TestEnvironment::writeStringToFileForTest("cds.pb_text", "")) {} void CdsHelper::setCds(const std::vector& clusters) { diff --git a/test/config/utility.h b/test/config/utility.h index f8daf258f7ed5..f73a3ff311fd8 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -315,6 +315,11 @@ class ConfigHelper { const envoy::extensions::filters::network::http_connection_manager::v3::LocalReplyConfig& config); + // Adjust the upstream route with larger timeout if running tsan. This is the duration between + // whole request being processed and whole response received. + static void adjustUpstreamTimeoutForTsan( + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& hcm); + using HttpProtocolOptions = envoy::extensions::upstreams::http::v3::HttpProtocolOptions; static void setProtocolOptions(envoy::config::cluster::v3::Cluster& cluster, HttpProtocolOptions& protocol_options); diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index ea2bd3eb32bd0..e7ff7a489bda2 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -340,8 +340,17 @@ void HttpIntegrationTest::initialize() { Network::Address::InstanceConstSharedPtr server_addr = Network::Utility::resolveUrl(fmt::format( "udp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), lookupPort("http"))); // Needs to outlive all QUIC connections. - quic_connection_persistent_info_ = std::make_unique( + auto quic_connection_persistent_info = std::make_unique( *dispatcher_, *quic_transport_socket_factory_, stats_store_, timeSystem(), server_addr); + // Config IETF QUIC flow control window. + quic_connection_persistent_info->quic_config_ + .SetInitialMaxStreamDataBytesIncomingBidirectionalToSend( + Http3::Utility::OptionsLimits::DEFAULT_INITIAL_STREAM_WINDOW_SIZE); + // Config Google QUIC flow control window. + quic_connection_persistent_info->quic_config_.SetInitialStreamFlowControlWindowToSend( + Http3::Utility::OptionsLimits::DEFAULT_INITIAL_STREAM_WINDOW_SIZE); + quic_connection_persistent_info_ = std::move(quic_connection_persistent_info); + #else ASSERT(false, "running a QUIC integration test without compiling QUIC"); #endif @@ -1155,7 +1164,8 @@ void HttpIntegrationTest::testLargeRequestUrl(uint32_t url_size, uint32_t max_he } void HttpIntegrationTest::testLargeRequestHeaders(uint32_t size, uint32_t count, uint32_t max_size, - uint32_t max_count) { + uint32_t max_count, + std::chrono::milliseconds timeout) { useAccessLog("%RESPONSE_CODE_DETAILS%"); // `size` parameter dictates the size of each header that will be added to the request and `count` // parameter is the number of headers to be added. The actual request byte size will exceed `size` @@ -1196,7 +1206,8 @@ void HttpIntegrationTest::testLargeRequestHeaders(uint32_t size, uint32_t count, codec_client_->close(); } } else { - auto response = sendRequestAndWaitForResponse(big_headers, 0, default_response_headers_, 0); + auto response = + sendRequestAndWaitForResponse(big_headers, 0, default_response_headers_, 0, 0, timeout); EXPECT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); } diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index fee3c43f9f63f..23227ce7f2fbe 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -213,7 +213,8 @@ class HttpIntegrationTest : public BaseIntegrationTest { uint32_t max_size); void testLargeRequestUrl(uint32_t url_size, uint32_t max_headers_size); void testLargeRequestHeaders(uint32_t size, uint32_t count, uint32_t max_size = 60, - uint32_t max_count = 100); + uint32_t max_count = 100, + std::chrono::milliseconds timeout = TestUtility::DefaultTimeout); void testLargeRequestTrailers(uint32_t size, uint32_t max_size = 60); void testManyRequestHeaders(std::chrono::milliseconds time = TestUtility::DefaultTimeout); diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index a3f10631a9e2c..5700f535fb408 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -41,20 +41,20 @@ TEST_P(Http2IntegrationTest, RouterRequestAndResponseWithBodyNoBuffer) { } TEST_P(Http2IntegrationTest, RouterRequestAndResponseWithGiantBodyNoBuffer) { - EXCLUDE_DOWNSTREAM_HTTP3; // sort out timeouts + config_helper_.addConfigModifier(ConfigHelper::adjustUpstreamTimeoutForTsan); testRouterRequestAndResponseWithBody(10 * 1024 * 1024, 10 * 1024 * 1024, false, false, nullptr, TSAN_TIMEOUT_FACTOR * TestUtility::DefaultTimeout); } TEST_P(Http2IntegrationTest, FlowControlOnAndGiantBody) { - EXCLUDE_DOWNSTREAM_HTTP3; + config_helper_.addConfigModifier(ConfigHelper::adjustUpstreamTimeoutForTsan); config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream. testRouterRequestAndResponseWithBody(10 * 1024 * 1024, 10 * 1024 * 1024, false, false, nullptr, TSAN_TIMEOUT_FACTOR * TestUtility::DefaultTimeout); } TEST_P(Http2IntegrationTest, LargeFlowControlOnAndGiantBody) { - EXCLUDE_DOWNSTREAM_HTTP3; // sort out timeouts + config_helper_.addConfigModifier(ConfigHelper::adjustUpstreamTimeoutForTsan); config_helper_.setBufferLimits(128 * 1024, 128 * 1024); // Set buffer limits upstream and downstream. testRouterRequestAndResponseWithBody(10 * 1024 * 1024, 10 * 1024 * 1024, false, false, nullptr, @@ -66,20 +66,20 @@ TEST_P(Http2IntegrationTest, RouterRequestAndResponseWithBodyAndContentLengthNoB } TEST_P(Http2IntegrationTest, RouterRequestAndResponseWithGiantBodyAndContentLengthNoBuffer) { - EXCLUDE_DOWNSTREAM_HTTP3; // sort out timeouts + config_helper_.addConfigModifier(ConfigHelper::adjustUpstreamTimeoutForTsan); testRouterRequestAndResponseWithBody(10 * 1024 * 1024, 10 * 1024 * 1024, false, true, nullptr, TSAN_TIMEOUT_FACTOR * TestUtility::DefaultTimeout); } TEST_P(Http2IntegrationTest, FlowControlOnAndGiantBodyWithContentLength) { - EXCLUDE_DOWNSTREAM_HTTP3; + config_helper_.addConfigModifier(ConfigHelper::adjustUpstreamTimeoutForTsan); config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream. testRouterRequestAndResponseWithBody(10 * 1024 * 1024, 10 * 1024 * 1024, false, true, nullptr, TSAN_TIMEOUT_FACTOR * TestUtility::DefaultTimeout); } TEST_P(Http2IntegrationTest, LargeFlowControlOnAndGiantBodyWithContentLength) { - EXCLUDE_DOWNSTREAM_HTTP3; // sort out timeouts + config_helper_.addConfigModifier(ConfigHelper::adjustUpstreamTimeoutForTsan); config_helper_.setBufferLimits(128 * 1024, 128 * 1024); // Set buffer limits upstream and downstream. testRouterRequestAndResponseWithBody(10 * 1024 * 1024, 10 * 1024 * 1024, false, true, nullptr, diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 3e34c3e2c9a5f..fe732211ee20e 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -1593,7 +1593,7 @@ TEST_P(DownstreamProtocolIntegrationTest, ManyLargeRequestHeadersAccepted) { // long event loop. EXCLUDE_DOWNSTREAM_HTTP3; // Send 70 headers each of size 100 kB with limit 8192 kB (8 MB) and 100 headers. - testLargeRequestHeaders(100, 70, 8192, 100); + testLargeRequestHeaders(100, 70, 8192, 100, TSAN_TIMEOUT_FACTOR * TestUtility::DefaultTimeout); } TEST_P(DownstreamProtocolIntegrationTest, ManyRequestHeadersRejected) { @@ -1779,8 +1779,6 @@ TEST_P(ProtocolIntegrationTest, LargeRequestMethod) { // Tests StopAllIterationAndBuffer. Verifies decode-headers-return-stop-all-filter calls decodeData // once after iteration is resumed. TEST_P(DownstreamProtocolIntegrationTest, TestDecodeHeadersReturnsStopAll) { - // TODO(danzh) Enable after setting QUICHE stream initial flow control window from http3 options. - EXCLUDE_DOWNSTREAM_HTTP3 config_helper_.addFilter(R"EOF( name: call-decodedata-once-filter )EOF"); @@ -1832,8 +1830,6 @@ name: passthrough-filter // Tests StopAllIterationAndWatermark. decode-headers-return-stop-all-watermark-filter sets buffer // limit to 100. Verifies data pause when limit is reached, and resume after iteration continues. TEST_P(DownstreamProtocolIntegrationTest, TestDecodeHeadersReturnsStopAllWatermark) { - // TODO(danzh) Re-enable after codec buffer can be set according to http3 options. - EXCLUDE_DOWNSTREAM_HTTP3 config_helper_.addFilter(R"EOF( name: decode-headers-return-stop-all-filter )EOF"); @@ -1892,8 +1888,6 @@ name: passthrough-filter // Test two filters that return StopAllIterationAndBuffer back-to-back. TEST_P(DownstreamProtocolIntegrationTest, TestTwoFiltersDecodeHeadersReturnsStopAll) { - // TODO(danzh) Re-enable after codec buffer can be set according to http3 options. - EXCLUDE_DOWNSTREAM_HTTP3 config_helper_.addFilter(R"EOF( name: decode-headers-return-stop-all-filter )EOF"); @@ -1986,7 +1980,7 @@ name: encode-headers-return-stop-all-filter // Tests encodeHeaders() returns StopAllIterationAndWatermark. TEST_P(DownstreamProtocolIntegrationTest, TestEncodeHeadersReturnsStopAllWatermark) { - // TODO(danzh) Re-enable after codec buffer can be set according to http3 options. + // Metadata is not supported in QUICHE. EXCLUDE_DOWNSTREAM_HTTP3 config_helper_.addFilter(R"EOF( name: encode-headers-return-stop-all-filter diff --git a/test/integration/protocol_integration_test.h b/test/integration/protocol_integration_test.h index 4b2ab19c2422e..953111451954b 100644 --- a/test/integration/protocol_integration_test.h +++ b/test/integration/protocol_integration_test.h @@ -42,7 +42,7 @@ class DownstreamProtocolIntegrationTest : public HttpProtocolIntegrationTest { } void verifyUpStreamRequestAfterStopAllFilter() { - if (downstreamProtocol() == Http::CodecClient::Type::HTTP2) { + if (downstreamProtocol() >= Http::CodecClient::Type::HTTP2) { // decode-headers-return-stop-all-filter calls addDecodedData in decodeData and // decodeTrailers. 2 decoded data were added. EXPECT_EQ(count_ * size_ + added_decoded_data_size_ * 2, upstream_request_->bodyLength()); diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index a3d3910d7a179..bdf7046a8cb16 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -57,17 +57,6 @@ void updateResource(AtomicFileUpdater& updater, double pressure) { updater.update(absl::StrCat(pressure)); } -void setUpstreamTimeout( - envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& hcm) { - auto* route = - hcm.mutable_route_config()->mutable_virtual_hosts(0)->mutable_routes(0)->mutable_route(); - uint64_t timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(*route, timeout, 15000u); - auto* timeout = route->mutable_timeout(); - // QUIC stream processing is slow under TSAN. Use larger timeout to prevent - // upstream_response_timeout. - timeout->set_seconds(TSAN_TIMEOUT_FACTOR * timeout_ms / 1000); -} - // A test that sets up its own client connection with customized quic version and connection ID. class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVersionTest { public: @@ -111,17 +100,14 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers getNextConnectionId(), server_addr_, conn_helper_, alarm_factory_, quic::ParsedQuicVersionVector{supported_versions_[0]}, local_addr, *dispatcher_, nullptr); quic_connection_ = connection.get(); - // TODO(danzh) defer setting flow control window till getting http3 options. This requires - // QUICHE support to set the session's flow controller after instantiation. - quic_config_.SetInitialStreamFlowControlWindowToSend( - Http2::Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE); - quic_config_.SetInitialSessionFlowControlWindowToSend( - 1.5 * Http2::Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE); ASSERT(quic_connection_persistent_info_ != nullptr); auto& persistent_info = static_cast(*quic_connection_persistent_info_); auto session = std::make_unique( - quic_config_, supported_versions_, std::move(connection), persistent_info.server_id_, - persistent_info.crypto_config_.get(), &push_promise_index_, *dispatcher_, + persistent_info.quic_config_, supported_versions_, std::move(connection), + persistent_info.server_id_, persistent_info.crypto_config_.get(), &push_promise_index_, + *dispatcher_, + // Use smaller window than the default one to have test coverage of client codec buffer + // exceeding high watermark. /*send_buffer_limit=*/2 * Http2::Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE); return session; } @@ -250,7 +236,6 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers } protected: - quic::QuicConfig quic_config_; quic::QuicClientPushPromiseIndex push_promise_index_; quic::ParsedQuicVersionVector supported_versions_; EnvoyQuicConnectionHelper conn_helper_; @@ -312,7 +297,7 @@ TEST_P(QuicHttpIntegrationTest, RouterUpstreamResponseBeforeRequestComplete) { TEST_P(QuicHttpIntegrationTest, Retry) { testRetry(); } TEST_P(QuicHttpIntegrationTest, UpstreamReadDisabledOnGiantResponseBody) { - config_helper_.addConfigModifier(setUpstreamTimeout); + config_helper_.addConfigModifier(ConfigHelper::adjustUpstreamTimeoutForTsan); config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); testRouterRequestAndResponseWithBody(/*request_size=*/512, /*response_size=*/10 * 1024 * 1024, false, false, nullptr, @@ -320,14 +305,14 @@ TEST_P(QuicHttpIntegrationTest, UpstreamReadDisabledOnGiantResponseBody) { } TEST_P(QuicHttpIntegrationTest, DownstreamReadDisabledOnGiantPost) { - config_helper_.addConfigModifier(setUpstreamTimeout); + config_helper_.addConfigModifier(ConfigHelper::adjustUpstreamTimeoutForTsan); config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); testRouterRequestAndResponseWithBody(/*request_size=*/10 * 1024 * 1024, /*response_size=*/1024, false); } TEST_P(QuicHttpIntegrationTest, LargeFlowControlOnAndGiantBody) { - config_helper_.addConfigModifier(setUpstreamTimeout); + config_helper_.addConfigModifier(ConfigHelper::adjustUpstreamTimeoutForTsan); config_helper_.setBufferLimits(/*upstream_buffer_limit=*/128 * 1024, /*downstream_buffer_limit=*/128 * 1024); testRouterRequestAndResponseWithBody(/*request_size=*/10 * 1024 * 1024, From 15cd9a6c3ec722b566706ac88798fce79e8fbcf6 Mon Sep 17 00:00:00 2001 From: htuch Date: Mon, 3 May 2021 20:25:58 -0400 Subject: [PATCH 134/209] bootstrap/runtime: remove support for v2 bootstrap runtime field. (#16274) This is the first PR in a series to remove v2 API support from Envoy. The process is as follows: 1. Identify uses of a hidden_envoy_deprecated_* field in source/. 2. Remove implementation support for occurences identified in (1). 3. Remove any tests in test/ covering (1). It is necessary to validate manually that other tests exist for replacement functionality; in some cases the deprecated test might be providing functional coverage not applied to the deprecated feature's replacement. 4. Wash, rinse and repeat. Risk level: Low Testing: Added a unit test to cover otherwise missing behaviors in runtime. Signed-off-by: Harvey Tuch --- source/server/configuration_impl.cc | 2 - test/common/protobuf/utility_test.cc | 36 ++++-------- test/config/utility.cc | 18 +++--- test/server/configuration_impl_test.cc | 79 -------------------------- 4 files changed, 21 insertions(+), 114 deletions(-) diff --git a/source/server/configuration_impl.cc b/source/server/configuration_impl.cc index 570fcb891d01e..debbfef503d25 100644 --- a/source/server/configuration_impl.cc +++ b/source/server/configuration_impl.cc @@ -233,8 +233,6 @@ InitialImpl::InitialImpl(const envoy::config::bootstrap::v3::Bootstrap& bootstra if (layered_runtime_.layers().empty()) { layered_runtime_.add_layers()->mutable_admin_layer(); } - } else { - Config::translateRuntime(bootstrap.hidden_envoy_deprecated_runtime(), layered_runtime_); } if (enable_deprecated_v2_api_) { auto* enabled_deprecated_v2_api_layer = layered_runtime_.add_layers(); diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index 3a371621dc27b..c183339fe5285 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -304,36 +304,22 @@ TEST_F(ProtobufUtilityTest, LoadBinaryProtoFromFile) { EXPECT_TRUE(TestUtility::protoEqual(bootstrap, proto_from_file)); } -TEST_F(ProtobufV2ApiUtilityTest, DEPRECATED_FEATURE_TEST(LoadBinaryV2ProtoFromFile)) { - // Allow the use of v2.Bootstrap.runtime. - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.deprecated_features:envoy.config.bootstrap.v2.Bootstrap.runtime", "True "}}); - envoy::config::bootstrap::v2::Bootstrap bootstrap; - bootstrap.mutable_runtime()->set_symlink_root("/"); - - const std::string filename = - TestEnvironment::writeStringToFileForTest("proto.pb", bootstrap.SerializeAsString()); - - envoy::config::bootstrap::v3::Bootstrap proto_from_file; - TestUtility::loadFromFile(filename, proto_from_file, *api_); - EXPECT_EQ("/", proto_from_file.hidden_envoy_deprecated_runtime().symlink_root()); - EXPECT_GT(runtime_deprecated_feature_use_.value(), 0); -} - // Verify that a config with a deprecated field can be loaded with runtime global override. -TEST_F(ProtobufUtilityTest, DEPRECATED_FEATURE_TEST(LoadBinaryV2GlobalOverrideProtoFromFile)) { - // Allow the use of v2.Bootstrap.runtime. - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.features.enable_all_deprecated_features", "true"}}); - envoy::config::bootstrap::v2::Bootstrap bootstrap; - bootstrap.mutable_runtime()->set_symlink_root("/"); - +TEST_F(ProtobufUtilityTest, DEPRECATED_FEATURE_TEST(LoadBinaryGlobalOverrideProtoFromFile)) { + const std::string bootstrap_yaml = R"EOF( +layered_runtime: + layers: + - name: static_layer + static_layer: + envoy.features.enable_all_deprecated_features: true +watchdog: { miss_timeout: 1s })EOF"; const std::string filename = - TestEnvironment::writeStringToFileForTest("proto.pb", bootstrap.SerializeAsString()); + TestEnvironment::writeStringToFileForTest("proto.yaml", bootstrap_yaml); envoy::config::bootstrap::v3::Bootstrap proto_from_file; TestUtility::loadFromFile(filename, proto_from_file, *api_); - EXPECT_EQ("/", proto_from_file.hidden_envoy_deprecated_runtime().symlink_root()); + TestUtility::validate(proto_from_file); + EXPECT_TRUE(proto_from_file.has_watchdog()); EXPECT_GT(runtime_deprecated_feature_use_.value(), 0); } diff --git a/test/config/utility.cc b/test/config/utility.cc index 59e66346c0d61..1c0e08b53355b 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -624,6 +624,16 @@ ConfigHelper::ConfigHelper(const Network::Address::IpVersion version, Api::Api& } } } + + // Ensure we have a basic admin-capable runtime layer. + if (bootstrap_.mutable_layered_runtime()->layers_size() == 0) { + auto* static_layer = bootstrap_.mutable_layered_runtime()->add_layers(); + static_layer->set_name("static_layer"); + static_layer->mutable_static_layer(); + auto* admin_layer = bootstrap_.mutable_layered_runtime()->add_layers(); + admin_layer->set_name("admin"); + admin_layer->mutable_admin_layer(); + } } void ConfigHelper::addClusterFilterMetadata(absl::string_view metadata_yaml, @@ -741,14 +751,6 @@ void ConfigHelper::configureUpstreamTls(bool use_alpn, bool http3) { } void ConfigHelper::addRuntimeOverride(const std::string& key, const std::string& value) { - if (bootstrap_.mutable_layered_runtime()->layers_size() == 0) { - auto* static_layer = bootstrap_.mutable_layered_runtime()->add_layers(); - static_layer->set_name("static_layer"); - static_layer->mutable_static_layer(); - auto* admin_layer = bootstrap_.mutable_layered_runtime()->add_layers(); - admin_layer->set_name("admin"); - admin_layer->mutable_admin_layer(); - } auto* static_layer = bootstrap_.mutable_layered_runtime()->mutable_layers(0)->mutable_static_layer(); (*static_layer->mutable_fields())[std::string(key)] = ValueUtil::stringValue(std::string(value)); diff --git a/test/server/configuration_impl_test.cc b/test/server/configuration_impl_test.cc index 6db1c12849db8..a277dbbaeb40f 100644 --- a/test/server/configuration_impl_test.cc +++ b/test/server/configuration_impl_test.cc @@ -640,85 +640,6 @@ TEST(InitialImplTest, EmptyLayeredRuntime) { EXPECT_THAT(config.runtime(), ProtoEq(expected_runtime)); } -// An empty deprecated Runtime has an empty static and admin layer injected. -TEST(InitialImplTest, EmptyDeprecatedRuntime) { - const auto bootstrap = TestUtility::parseYaml("{}"); - NiceMock options; - NiceMock server; - InitialImpl config(bootstrap, options, server); - - const std::string expected_yaml = R"EOF( - layers: - - name: base - static_layer: {} - - name: admin - admin_layer: {} - )EOF"; - const auto expected_runtime = - TestUtility::parseYaml(expected_yaml); - EXPECT_THAT(config.runtime(), ProtoEq(expected_runtime)); -} - -// A deprecated Runtime is transformed to the equivalent LayeredRuntime. -TEST(InitialImplTest, DeprecatedRuntimeTranslation) { - TestDeprecatedV2Api _deprecated_v2_api; - const std::string bootstrap_yaml = R"EOF( - runtime: - symlink_root: /srv/runtime/current - subdirectory: envoy - override_subdirectory: envoy_override - base: - health_check: - min_interval: 5 - )EOF"; - const auto bootstrap = - TestUtility::parseYaml(bootstrap_yaml); - NiceMock options; - NiceMock server; - InitialImpl config(bootstrap, options, server); - - const std::string expected_yaml = R"EOF( - layers: - - name: base - static_layer: - health_check: - min_interval: 5 - - name: root - disk_layer: { symlink_root: /srv/runtime/current, subdirectory: envoy } - - name: override - disk_layer: { symlink_root: /srv/runtime/current, subdirectory: envoy_override, append_service_cluster: true } - - name: admin - admin_layer: {} - )EOF"; - const auto expected_runtime = - TestUtility::parseYaml(expected_yaml); - EXPECT_THAT(config.runtime(), ProtoEq(expected_runtime)); -} - -// A v2 bootstrap implies runtime override for API features. -TEST(InitialImplTest, V2BootstrapRuntimeInjection) { - const auto bootstrap = TestUtility::parseYaml("{}"); - NiceMock options; - absl::optional version{2}; - EXPECT_CALL(options, bootstrapVersion()).WillOnce(ReturnRef(version)); - NiceMock server; - InitialImpl config(bootstrap, options, server); - - const std::string expected_yaml = R"EOF( - layers: - - name: base - static_layer: {} - - name: admin - admin_layer: {} - - name: "enabled_deprecated_v2_api (auto-injected)" - static_layer: - envoy.test_only.broken_in_production.enable_deprecated_v2_api: true - )EOF"; - const auto expected_runtime = - TestUtility::parseYaml(expected_yaml); - EXPECT_THAT(config.runtime(), ProtoEq(expected_runtime)); -} - TEST_F(ConfigurationImplTest, AdminSocketOptions) { std::string json = R"EOF( { From fcb415e28219f9e6210b9f40c761507d5162fe8a Mon Sep 17 00:00:00 2001 From: James Mulcahy Date: Mon, 3 May 2021 17:52:38 -0700 Subject: [PATCH 135/209] =?UTF-8?q?tools:=20allow=20generate=5Fgo=5Fprotob?= =?UTF-8?q?uf.py=20to=20skip=20syncing=20with=20go-control-=E2=80=A6=20(#1?= =?UTF-8?q?6287)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, it also syncs them with go_control_plane, but it can be useful for local development work to be able to just generate the code Risk Level: Low Testing: I've manually invoked the scripted and tested out the code. The existing behavior is retained as the default. Signed-off-by: James Mulcahy --- ci/go_mirror.sh | 2 +- tools/api/generate_go_protobuf.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ci/go_mirror.sh b/ci/go_mirror.sh index 96743eef62620..74abb005dd8f2 100755 --- a/ci/go_mirror.sh +++ b/ci/go_mirror.sh @@ -8,5 +8,5 @@ MAIN_BRANCH="refs/heads/main" . "$(dirname "$0")"/setup_cache.sh if [[ "${AZP_BRANCH}" == "${MAIN_BRANCH}" ]]; then - BAZEL_BUILD_OPTIONS="${BAZEL_BUILD_EXTRA_OPTIONS}" tools/api/generate_go_protobuf.py + BAZEL_BUILD_OPTIONS="${BAZEL_BUILD_EXTRA_OPTIONS}" tools/api/generate_go_protobuf.py --sync fi diff --git a/tools/api/generate_go_protobuf.py b/tools/api/generate_go_protobuf.py index 397b7bee848e5..bde296a423aef 100755 --- a/tools/api/generate_go_protobuf.py +++ b/tools/api/generate_go_protobuf.py @@ -2,10 +2,12 @@ from subprocess import check_output from subprocess import check_call +import argparse import glob import os import shlex import shutil +import sys import re # Needed for CI to pass down bazel options. @@ -13,7 +15,6 @@ TARGETS = '@envoy_api//...' IMPORT_BASE = 'github.com/envoyproxy/go-control-plane' -OUTPUT_BASE = 'build_go' REPO_BASE = 'go-control-plane' BRANCH = 'main' MIRROR_MSG = 'Mirrored from envoyproxy/envoy @ ' @@ -121,9 +122,19 @@ def updated(repo): if __name__ == "__main__": + parser = argparse.ArgumentParser( + description='Generate Go protobuf files and sync with go-control-plane') + parser.add_argument('--sync', action='store_true') + parser.add_argument('--output_base', default='build_go') + args = parser.parse_args() + workspace = check_output(['bazel', 'info', 'workspace']).decode().strip() - output = os.path.join(workspace, OUTPUT_BASE) + output = os.path.join(workspace, args.output_base) generate_protobufs(output) + if not args.sync: + print('Skipping sync with go-control-plane') + sys.exit() + repo = os.path.join(workspace, REPO_BASE) clone_go_protobufs(repo) sync_go_protobufs(output, repo) From b9d21f08f62c0985ed87fc950923d762fd3d78d0 Mon Sep 17 00:00:00 2001 From: Christoph Pakulski Date: Mon, 3 May 2021 21:13:42 -0400 Subject: [PATCH 136/209] access_log: refactored SubstitutionFormatParser::parseCommand (#16121) Signed-off-by: Christoph Pakulski --- .../formatter/substitution_formatter.cc | 69 +++++------ .../common/formatter/substitution_formatter.h | 62 +++++++--- .../substitution_formatter_speed_test.cc | 10 ++ .../formatter/substitution_formatter_test.cc | 113 ++++++++++++++++++ test/mocks/router/mocks.h | 1 - 5 files changed, 198 insertions(+), 57 deletions(-) diff --git a/source/common/formatter/substitution_formatter.cc b/source/common/formatter/substitution_formatter.cc index 9360e62709609..6c1c48c513202 100644 --- a/source/common/formatter/substitution_formatter.cc +++ b/source/common/formatter/substitution_formatter.cc @@ -302,19 +302,17 @@ void SubstitutionFormatParser::parseCommandHeader(const std::string& token, cons std::string& main_header, std::string& alternative_header, absl::optional& max_length) { + // subs is used only to check if there are more than 2 tokens separated by '?'. std::vector subs; - parseCommand(token, start, "?", main_header, subs, max_length); - if (subs.size() > 1) { + alternative_header = ""; + parseCommand(token, start, '?', max_length, main_header, alternative_header, subs); + if (!subs.empty()) { throw EnvoyException( // Header format rules support only one alternative header. // docs/root/configuration/access_log.rst#format-rules absl::StrCat("More than 1 alternative header specified in token: ", token)); } - if (subs.size() == 1) { - alternative_header = subs.front(); - } else { - alternative_header = ""; - } + // The main and alternative header should not contain invalid characters {NUL, LR, CF}. if (std::regex_search(main_header, getNewlinePattern()) || std::regex_search(alternative_header, getNewlinePattern())) { @@ -322,25 +320,24 @@ void SubstitutionFormatParser::parseCommandHeader(const std::string& token, cons } } -void SubstitutionFormatParser::parseCommand(const std::string& token, const size_t start, - const std::string& separator, std::string& main, - std::vector& sub_items, - absl::optional& max_length) { - // TODO(dnoe): Convert this to use string_view throughout. - const size_t end_request = token.find(')', start); - sub_items.clear(); - if (end_request != token.length() - 1) { +void SubstitutionFormatParser::tokenizeCommand(const std::string& command, const size_t start, + const char separator, + std::vector& tokens, + absl::optional& max_length) { + const size_t end_request = command.find(')', start); + tokens.clear(); + if (end_request != command.length() - 1) { // Closing bracket is not found. if (end_request == std::string::npos) { - throw EnvoyException(absl::StrCat("Closing bracket is missing in token: ", token)); + throw EnvoyException(absl::StrCat("Closing bracket is missing in token: ", command)); } // Closing bracket should be either last one or followed by ':' to denote limitation. - if (token[end_request + 1] != ':') { - throw EnvoyException(absl::StrCat("Incorrect position of ')' in token: ", token)); + if (command[end_request + 1] != ':') { + throw EnvoyException(absl::StrCat("Incorrect position of ')' in token: ", command)); } - const auto length_str = absl::string_view(token).substr(end_request + 2); + const auto length_str = absl::string_view(command).substr(end_request + 2); uint64_t length_value; if (!absl::SimpleAtoi(length_str, &length_value)) { @@ -350,20 +347,10 @@ void SubstitutionFormatParser::parseCommand(const std::string& token, const size max_length = length_value; } - const std::string name_data = token.substr(start, end_request - start); - if (!separator.empty()) { - const std::vector keys = absl::StrSplit(name_data, separator); - if (!keys.empty()) { - // The main value is the first key - main = keys.at(0); - if (keys.size() > 1) { - // Sub items contain additional keys - sub_items.insert(sub_items.end(), keys.begin() + 1, keys.end()); - } - } - } else { - main = name_data; - } + absl::string_view name_data(command); + name_data.remove_prefix(start); + name_data.remove_suffix(command.length() - end_request); + tokens = absl::StrSplit(name_data, separator); } std::vector SubstitutionFormatParser::parse(const std::string& format) { @@ -406,7 +393,7 @@ FormatterProviderPtr SubstitutionFormatParser::parseBuiltinCommand(const std::st std::vector path; const size_t start = DYNAMIC_META_TOKEN.size(); - parseCommand(token, start, ":", filter_namespace, path, max_length); + parseCommand(token, start, ':', max_length, filter_namespace, path); return std::make_unique(filter_namespace, path, max_length); } else if (absl::StartsWith(token, CLUSTER_META_TOKEN)) { std::string filter_namespace; @@ -414,22 +401,22 @@ FormatterProviderPtr SubstitutionFormatParser::parseBuiltinCommand(const std::st std::vector path; const size_t start = CLUSTER_META_TOKEN.size(); - parseCommand(token, start, ":", filter_namespace, path, max_length); + parseCommand(token, start, ':', max_length, filter_namespace, path); return std::make_unique(filter_namespace, path, max_length); } else if (absl::StartsWith(token, FILTER_STATE_TOKEN)) { - std::string key; + std::string key, serialize_type; absl::optional max_length; - std::vector path; + std::string path; const size_t start = FILTER_STATE_TOKEN.size(); - parseCommand(token, start, ":", key, path, max_length); + parseCommand(token, start, ':', max_length, key, serialize_type); if (key.empty()) { throw EnvoyException("Invalid filter state configuration, key cannot be empty."); } - const absl::string_view serialize_type = - !path.empty() ? path[path.size() - 1] : TYPED_SERIALIZATION; - + if (serialize_type.empty()) { + serialize_type = TYPED_SERIALIZATION; + } if (serialize_type != PLAIN_SERIALIZATION && serialize_type != TYPED_SERIALIZATION) { throw EnvoyException("Invalid filter state serialize type, only support PLAIN/TYPED."); } diff --git a/source/common/formatter/substitution_formatter.h b/source/common/formatter/substitution_formatter.h index 32d33eab4d3b5..2a3150be4f2fd 100644 --- a/source/common/formatter/substitution_formatter.h +++ b/source/common/formatter/substitution_formatter.h @@ -38,29 +38,61 @@ class SubstitutionFormatParser { absl::optional& max_length); /** - * General parse command utility. Will parse token from start position. Token is expected to end - * with ')'. An optional ":max_length" may be specified after the closing ')' char. Token may - * contain multiple values separated by "separator" string. First value will be populated in - * "main" and any additional sub values will be set in the vector "subitems". For example token - * of: "com.test.my_filter:test_object:inner_key):100" with separator of ":" will set the - * following: - * - main: com.test.my_filter - * - subitems: {test_object, inner_key} - * - max_length: 100 + * General tokenize utility. Will parse command from start position. Command is expected to end + * with ')'. An optional ":max_length" may be specified after the closing ')' char. Command may + * contain multiple values separated by "separator" character. Those values will be places + * into tokens container. If no separator is found, entire command (up to ')') will be + * placed as only item in the container. * - * @param token the token to parse + * @param command the command to parse * @param start the index to start parsing from * @param separator separator between values - * @param main the first value - * @param sub_items any additional values + * @param tokens values found in command separated by separator * @param max_length optional max_length will be populated if specified * * TODO(glicht) Rewrite with a parser library. See: * https://github.com/envoyproxy/envoy/issues/2967 */ - static void parseCommand(const std::string& token, const size_t start, - const std::string& separator, std::string& main, - std::vector& sub_items, absl::optional& max_length); + static void tokenizeCommand(const std::string& command, const size_t start, const char separator, + std::vector& tokens, + absl::optional& max_length); + + /* Variadic function template which invokes tokenizeCommand method to parse the + token command and assigns found tokens to sequence of params. + params must be a sequence of std::string& with optional container storing std::string. Here are + examples of params: + - std::string& token1 + - std::string& token1, std::string& token2 + - std::string& token1, std::string& token2, std::vector& remaining + + If command contains more tokens than number of passed params, unassigned tokens will be + ignored. If command contains less tokens than number of passed params, some params will be left + untouched. + */ + template + static void parseCommand(const std::string& command, const size_t start, const char separator, + absl::optional& max_length, Tokens&&... params) { + std::vector tokens; + tokenizeCommand(command, start, separator, tokens, max_length); + std::vector::iterator it = tokens.begin(); + ( + [&](auto& param) { + if (it != tokens.end()) { + if constexpr (std::is_same_v::type, + std::string>) { + // Compile time handler for std::string. + param = *it; + it++; + } else { + // Compile time handler for container type. It will catch all remaining tokens and + // move iterator to the end. + param.insert(param.begin(), it, tokens.end()); + it = tokens.end(); + } + } + }(params), + ...); + } /** * Return a FormatterProviderPtr if a built-in command is parsed from the token. This method diff --git a/test/common/formatter/substitution_formatter_speed_test.cc b/test/common/formatter/substitution_formatter_speed_test.cc index 5da6fead0f898..8fbb0573e8c5f 100644 --- a/test/common/formatter/substitution_formatter_speed_test.cc +++ b/test/common/formatter/substitution_formatter_speed_test.cc @@ -163,4 +163,14 @@ static void BM_TypedJsonAccessLogFormatter(benchmark::State& state) { } BENCHMARK(BM_TypedJsonAccessLogFormatter); +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_FormatterCommandParsing(benchmark::State& state) { + const std::string token = "(Listener:namespace:key):100"; + std::string listener, names, key; + absl::optional len; + for (auto _ : state) { // NOLINT: Silences warning about dead store + Formatter::SubstitutionFormatParser::parseCommand(token, 1, ':', len, listener, names, key); + } +} +BENCHMARK(BM_FormatterCommandParsing); } // namespace Envoy diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index e1c77c75dc186..df374eaef4a8a 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -100,6 +100,119 @@ class TestSerializedStringFilterState : public StreamInfo::FilterState::Object { std::string raw_string_; }; +// Test tests command tokenize utility. +TEST(SubstitutionFormatParser, tokenizer) { + std::vector tokens; + absl::optional max_length; + + std::string command = "COMMAND(item1)"; + + // The second parameter indicates where command name and opening bracket ends. + // In this case "COMMAND(" ends at index 8 (counting from zero). + SubstitutionFormatParser::tokenizeCommand(command, 8, ':', tokens, max_length); + ASSERT_EQ(tokens.size(), 1); + ASSERT_EQ(tokens.at(0), "item1"); + ASSERT_EQ(max_length, absl::nullopt); + + command = "COMMAND(item1:item2:item3)"; + SubstitutionFormatParser::tokenizeCommand(command, 8, ':', tokens, max_length); + ASSERT_EQ(tokens.size(), 3); + ASSERT_EQ(tokens.at(0), "item1"); + ASSERT_EQ(tokens.at(1), "item2"); + ASSERT_EQ(tokens.at(2), "item3"); + ASSERT_EQ(max_length, absl::nullopt); + + command = "COMMAND(item1:item2:item3:item4):234"; + SubstitutionFormatParser::tokenizeCommand(command, 8, ':', tokens, max_length); + ASSERT_EQ(tokens.size(), 4); + ASSERT_EQ(tokens.at(0), "item1"); + ASSERT_EQ(tokens.at(1), "item2"); + ASSERT_EQ(tokens.at(2), "item3"); + ASSERT_EQ(tokens.at(3), "item4"); + ASSERT_TRUE(max_length.has_value()); + ASSERT_EQ(max_length.value(), 234); + + // Tokenizing the following commands should fail. + std::vector wrong_commands = { + "COMMAND(item1:item2", // Missing closing bracket. + "COMMAND(item1:item2))", // Unexpected second closing bracket. + "COMMAND(item1:item2):", // Missing length field. + "COMMAND(item1:item2):LENGTH", // Length field must be integer. + "COMMAND(item1:item2):100:23", // Length field must be integer. + "COMMAND(item1:item2):100):23"}; // Extra fields after length. + + for (const auto& test_command : wrong_commands) { + EXPECT_THROW( + SubstitutionFormatParser::tokenizeCommand(test_command, 8, ':', tokens, max_length), + EnvoyException) + << test_command; + } +} + +// Test tests multiple versions of variadic template method parseCommand +// extracting tokens. +TEST(SubstitutionFormatParser, commandParser) { + std::vector tokens; + absl::optional max_length; + std::string token1; + + std::string command = "COMMAND(item1)"; + SubstitutionFormatParser::parseCommand(command, 8, ':', max_length, token1); + ASSERT_EQ(token1, "item1"); + ASSERT_EQ(max_length, absl::nullopt); + + std::string token2; + command = "COMMAND(item1:item2)"; + SubstitutionFormatParser::parseCommand(command, 8, ':', max_length, token1, token2); + ASSERT_EQ(token1, "item1"); + ASSERT_EQ(token2, "item2"); + ASSERT_EQ(max_length, absl::nullopt); + + // Three tokens with optional length. + std::string token3; + command = "COMMAND(item1?item2?item3):345"; + SubstitutionFormatParser::parseCommand(command, 8, '?', max_length, token1, token2, token3); + ASSERT_EQ(token1, "item1"); + ASSERT_EQ(token2, "item2"); + ASSERT_EQ(token3, "item3"); + ASSERT_TRUE(max_length.has_value()); + ASSERT_EQ(max_length.value(), 345); + + // Command string has 4 tokens but 3 are expected. + // The first 3 will be read, the fourth will be ignored. + command = "COMMAND(item1?item2?item3?item4):345"; + SubstitutionFormatParser::parseCommand(command, 8, '?', max_length, token1, token2, token3); + ASSERT_EQ(token1, "item1"); + ASSERT_EQ(token2, "item2"); + ASSERT_EQ(token3, "item3"); + ASSERT_TRUE(max_length.has_value()); + ASSERT_EQ(max_length.value(), 345); + + // Command string has 2 tokens but 3 are expected. + // The third extracted token should be empty. + command = "COMMAND(item1?item2):345"; + token3.erase(); + SubstitutionFormatParser::parseCommand(command, 8, '?', max_length, token1, token2, token3); + ASSERT_EQ(token1, "item1"); + ASSERT_EQ(token2, "item2"); + ASSERT_TRUE(token3.empty()); + ASSERT_TRUE(max_length.has_value()); + ASSERT_EQ(max_length.value(), 345); + + // Command string has 4 tokens. Get first 2 into the strings + // and remaining 2 into a vector of strings. + command = "COMMAND(item1?item2?item3?item4):345"; + std::vector bucket; + SubstitutionFormatParser::parseCommand(command, 8, '?', max_length, token1, token2, bucket); + ASSERT_EQ(token1, "item1"); + ASSERT_EQ(token2, "item2"); + ASSERT_EQ(bucket.size(), 2); + ASSERT_EQ(bucket.at(0), "item3"); + ASSERT_EQ(bucket.at(1), "item4"); + ASSERT_TRUE(max_length.has_value()); + ASSERT_EQ(max_length.value(), 345); +} + TEST(SubstitutionFormatUtilsTest, protocolToString) { EXPECT_EQ("HTTP/1.0", SubstitutionFormatUtils::protocolToString(Http::Protocol::Http10).value().get()); diff --git a/test/mocks/router/mocks.h b/test/mocks/router/mocks.h index 733217eb9110c..13f56a239ef5e 100644 --- a/test/mocks/router/mocks.h +++ b/test/mocks/router/mocks.h @@ -381,7 +381,6 @@ class MockRouteEntry : public RouteEntry { MOCK_METHOD(absl::optional, maxGrpcTimeout, (), (const)); MOCK_METHOD(absl::optional, grpcTimeoutOffset, (), (const)); MOCK_METHOD(const VirtualCluster*, virtualCluster, (const Http::HeaderMap& headers), (const)); - MOCK_METHOD(const std::string&, virtualHostName, (), (const)); MOCK_METHOD(const VirtualHost&, virtualHost, (), (const)); MOCK_METHOD(bool, autoHostRewrite, (), (const)); MOCK_METHOD((const std::multimap&), opaqueConfig, (), (const)); From 38d1c1984f202a523e6ecf7c4da4b895bb7c9c93 Mon Sep 17 00:00:00 2001 From: chaoqin-li1123 <55518381+chaoqin-li1123@users.noreply.github.com> Date: Mon, 3 May 2021 20:15:30 -0500 Subject: [PATCH 137/209] [filter]: Add option to disable fault filter stats that trace downstream server name (#16235) Signed-off-by: chaoqin-li1123 --- .../filters/http/fault/v3/fault.proto | 8 ++- .../filters/http/fault/v4alpha/fault.proto | 8 ++- .../filters/http/fault/v3/fault.proto | 8 ++- .../filters/http/fault/v4alpha/fault.proto | 8 ++- .../filters/http/fault/fault_filter.cc | 15 ++-- .../filters/http/fault/fault_filter.h | 3 + .../fault/fault_filter_integration_test.cc | 70 +++++++++++++++++++ .../filters/http/fault/fault_filter_test.cc | 58 +++++++++++++++ 8 files changed, 168 insertions(+), 10 deletions(-) diff --git a/api/envoy/extensions/filters/http/fault/v3/fault.proto b/api/envoy/extensions/filters/http/fault/v3/fault.proto index d28ed28b11100..fb3c51cca9d00 100644 --- a/api/envoy/extensions/filters/http/fault/v3/fault.proto +++ b/api/envoy/extensions/filters/http/fault/v3/fault.proto @@ -54,7 +54,7 @@ message FaultAbort { type.v3.FractionalPercent percentage = 3; } -// [#next-free-field: 15] +// [#next-free-field: 16] message HTTPFault { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.fault.v2.HTTPFault"; @@ -141,4 +141,10 @@ message HTTPFault { // The runtime key to override the :ref:`default ` // runtime. The default is: fault.http.abort.grpc_status string abort_grpc_status_runtime = 14; + + // To control whether stats storage is allocated dynamically for each downstream server. + // If set to true, "x-envoy-downstream-service-cluster" field of header will be ignored by this filter. + // If set to false, dynamic stats storage will be allocated for the downstream cluster name. + // Default value is false. + bool disable_downstream_cluster_stats = 15; } diff --git a/api/envoy/extensions/filters/http/fault/v4alpha/fault.proto b/api/envoy/extensions/filters/http/fault/v4alpha/fault.proto index 7dd4f48aa476b..5155007268456 100644 --- a/api/envoy/extensions/filters/http/fault/v4alpha/fault.proto +++ b/api/envoy/extensions/filters/http/fault/v4alpha/fault.proto @@ -54,7 +54,7 @@ message FaultAbort { type.v3.FractionalPercent percentage = 3; } -// [#next-free-field: 15] +// [#next-free-field: 16] message HTTPFault { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.fault.v3.HTTPFault"; @@ -141,4 +141,10 @@ message HTTPFault { // The runtime key to override the :ref:`default ` // runtime. The default is: fault.http.abort.grpc_status string abort_grpc_status_runtime = 14; + + // To control whether stats storage is allocated dynamically for each downstream server. + // If set to true, "x-envoy-downstream-service-cluster" field of header will be ignored by this filter. + // If set to false, dynamic stats storage will be allocated for the downstream cluster name. + // Default value is false. + bool disable_downstream_cluster_stats = 15; } diff --git a/generated_api_shadow/envoy/extensions/filters/http/fault/v3/fault.proto b/generated_api_shadow/envoy/extensions/filters/http/fault/v3/fault.proto index d28ed28b11100..fb3c51cca9d00 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/fault/v3/fault.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/fault/v3/fault.proto @@ -54,7 +54,7 @@ message FaultAbort { type.v3.FractionalPercent percentage = 3; } -// [#next-free-field: 15] +// [#next-free-field: 16] message HTTPFault { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.fault.v2.HTTPFault"; @@ -141,4 +141,10 @@ message HTTPFault { // The runtime key to override the :ref:`default ` // runtime. The default is: fault.http.abort.grpc_status string abort_grpc_status_runtime = 14; + + // To control whether stats storage is allocated dynamically for each downstream server. + // If set to true, "x-envoy-downstream-service-cluster" field of header will be ignored by this filter. + // If set to false, dynamic stats storage will be allocated for the downstream cluster name. + // Default value is false. + bool disable_downstream_cluster_stats = 15; } diff --git a/generated_api_shadow/envoy/extensions/filters/http/fault/v4alpha/fault.proto b/generated_api_shadow/envoy/extensions/filters/http/fault/v4alpha/fault.proto index 7dd4f48aa476b..5155007268456 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/fault/v4alpha/fault.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/fault/v4alpha/fault.proto @@ -54,7 +54,7 @@ message FaultAbort { type.v3.FractionalPercent percentage = 3; } -// [#next-free-field: 15] +// [#next-free-field: 16] message HTTPFault { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.fault.v3.HTTPFault"; @@ -141,4 +141,10 @@ message HTTPFault { // The runtime key to override the :ref:`default ` // runtime. The default is: fault.http.abort.grpc_status string abort_grpc_status_runtime = 14; + + // To control whether stats storage is allocated dynamically for each downstream server. + // If set to true, "x-envoy-downstream-service-cluster" field of header will be ignored by this filter. + // If set to false, dynamic stats storage will be allocated for the downstream cluster name. + // Default value is false. + bool disable_downstream_cluster_stats = 15; } diff --git a/source/extensions/filters/http/fault/fault_filter.cc b/source/extensions/filters/http/fault/fault_filter.cc index ca1ff7f42a9e1..de302a2216986 100644 --- a/source/extensions/filters/http/fault/fault_filter.cc +++ b/source/extensions/filters/http/fault/fault_filter.cc @@ -51,7 +51,8 @@ FaultSettings::FaultSettings(const envoy::extensions::filters::http::fault::v3:: fault, max_active_faults_runtime, RuntimeKeys::get().MaxActiveFaultsKey)), response_rate_limit_percent_runtime_( PROTOBUF_GET_STRING_OR_DEFAULT(fault, response_rate_limit_percent_runtime, - RuntimeKeys::get().ResponseRateLimitPercentKey)) { + RuntimeKeys::get().ResponseRateLimitPercentKey)), + disable_downstream_cluster_stats_(fault.disable_downstream_cluster_stats()) { if (fault.has_abort()) { request_abort_config_ = std::make_unique(fault.abort()); @@ -89,8 +90,10 @@ FaultFilterConfig::FaultFilterConfig( stats_prefix_(stat_name_set_->add(absl::StrCat(stats_prefix, "fault"))) {} void FaultFilterConfig::incCounter(Stats::StatName downstream_cluster, Stats::StatName stat_name) { - Stats::Utility::counterFromStatNames(scope_, {stats_prefix_, downstream_cluster, stat_name}) - .inc(); + if (!settings_.disableDownstreamClusterStats()) { + Stats::Utility::counterFromStatNames(scope_, {stats_prefix_, downstream_cluster, stat_name}) + .inc(); + } } FaultFilter::FaultFilter(FaultFilterConfigSharedPtr config) : config_(config) {} @@ -134,7 +137,7 @@ Http::FilterHeadersStatus FaultFilter::decodeHeaders(Http::RequestHeaderMap& hea if (headers.EnvoyDownstreamServiceCluster()) { downstream_cluster_ = std::string(headers.getEnvoyDownstreamServiceClusterValue()); - if (!downstream_cluster_.empty()) { + if (!downstream_cluster_.empty() && !fault_settings_->disableDownstreamClusterStats()) { downstream_cluster_storage_ = std::make_unique( downstream_cluster_, config_->scope().symbolTable()); } @@ -369,7 +372,7 @@ FaultFilter::abortGrpcStatus(const Http::RequestHeaderMap& request_headers) { void FaultFilter::recordDelaysInjectedStats() { // Downstream specific stats. - if (!downstream_cluster_.empty()) { + if (!downstream_cluster_.empty() && !fault_settings_->disableDownstreamClusterStats()) { config_->incDelays(downstream_cluster_storage_->statName()); } @@ -378,7 +381,7 @@ void FaultFilter::recordDelaysInjectedStats() { void FaultFilter::recordAbortsInjectedStats() { // Downstream specific stats. - if (!downstream_cluster_.empty()) { + if (!downstream_cluster_.empty() && !fault_settings_->disableDownstreamClusterStats()) { config_->incAborts(downstream_cluster_storage_->statName()); } diff --git a/source/extensions/filters/http/fault/fault_filter.h b/source/extensions/filters/http/fault/fault_filter.h index 5a75f1244db70..fefea8bd06f51 100644 --- a/source/extensions/filters/http/fault/fault_filter.h +++ b/source/extensions/filters/http/fault/fault_filter.h @@ -74,6 +74,7 @@ class FaultSettings : public Router::RouteSpecificFilterConfig { const std::string& responseRateLimitPercentRuntime() const { return response_rate_limit_percent_runtime_; } + bool disableDownstreamClusterStats() const { return disable_downstream_cluster_stats_; } private: class RuntimeKeyValues { @@ -96,6 +97,7 @@ class FaultSettings : public Router::RouteSpecificFilterConfig { const std::vector fault_filter_headers_; absl::flat_hash_set downstream_nodes_{}; // Inject failures for specific downstream absl::optional max_active_faults_; + Filters::Common::Fault::FaultRateLimitConfigPtr response_rate_limit_; const std::string delay_percent_runtime_; const std::string abort_percent_runtime_; @@ -104,6 +106,7 @@ class FaultSettings : public Router::RouteSpecificFilterConfig { const std::string abort_grpc_status_runtime_; const std::string max_active_faults_runtime_; const std::string response_rate_limit_percent_runtime_; + const bool disable_downstream_cluster_stats_; }; /** diff --git a/test/extensions/filters/http/fault/fault_filter_integration_test.cc b/test/extensions/filters/http/fault/fault_filter_integration_test.cc index c00bf3ee55a0b..f23633aa308a9 100644 --- a/test/extensions/filters/http/fault/fault_filter_integration_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_integration_test.cc @@ -48,6 +48,26 @@ name: fault numerator: 100 )EOF"; + const std::string disable_stats_fault_config_ = + R"EOF( +name: fault +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault + abort: + header_abort: {} + percentage: + numerator: 100 + delay: + header_delay: {} + percentage: + numerator: 100 + response_rate_limit: + header_limit: {} + percentage: + numerator: 100 + disable_downstream_cluster_stats: true +)EOF"; + const std::string abort_grpc_fault_config_ = R"EOF( name: fault @@ -173,6 +193,56 @@ TEST_P(FaultIntegrationTestAllProtocols, HeaderFaultAbortConfig) { EXPECT_EQ(0UL, test_server_->gauge("http.config_test.fault.active_faults")->value()); } +// Request abort controlled via header configuration and enable downstream server stats. +TEST_P(FaultIntegrationTestAllProtocols, HeaderFaultAbortConfigEnableDownstreamServerStats) { + initializeFilter(header_fault_config_); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-envoy-fault-abort-request", "429"}, + {"x-envoy-downstream-service-cluster", "superman"}}); + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(response->complete()); + EXPECT_THAT(response->headers(), Envoy::Http::HttpStatusIs("429")); + + EXPECT_EQ(1UL, test_server_->counter("http.config_test.fault.aborts_injected")->value()); + EXPECT_EQ(0UL, test_server_->counter("http.config_test.fault.delays_injected")->value()); + EXPECT_EQ(0UL, test_server_->counter("http.config_test.fault.response_rl_injected")->value()); + EXPECT_EQ(0UL, test_server_->gauge("http.config_test.fault.active_faults")->value()); + EXPECT_EQ(1UL, test_server_->counter("http.config_test.fault.superman.aborts_injected")->value()); + EXPECT_EQ(nullptr, test_server_->counter("http.config_test.fault.superman.delays_injected")); +} + +// Request abort controlled via header configuration and disable downstream server stats. +TEST_P(FaultIntegrationTestAllProtocols, HeaderFaultAbortConfigDisableDownstreamServerStats) { + initializeFilter(disable_stats_fault_config_); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-envoy-fault-abort-request", "429"}, + {"x-envoy-downstream-service-cluster", "superman"}}); + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(response->complete()); + EXPECT_THAT(response->headers(), Envoy::Http::HttpStatusIs("429")); + + EXPECT_EQ(1UL, test_server_->counter("http.config_test.fault.aborts_injected")->value()); + EXPECT_EQ(0UL, test_server_->counter("http.config_test.fault.delays_injected")->value()); + EXPECT_EQ(0UL, test_server_->counter("http.config_test.fault.response_rl_injected")->value()); + EXPECT_EQ(0UL, test_server_->gauge("http.config_test.fault.active_faults")->value()); + EXPECT_EQ(nullptr, test_server_->counter("http.config_test.fault.superman.aborts_injected")); + EXPECT_EQ(nullptr, test_server_->counter("http.config_test.fault.superman.delays_injected")); +} + // Request faults controlled via header configuration. TEST_P(FaultIntegrationTestAllProtocols, HeaderFaultsConfig0PercentageHeaders) { initializeFilter(header_fault_config_); diff --git a/test/extensions/filters/http/fault/fault_filter_test.cc b/test/extensions/filters/http/fault/fault_filter_test.cc index c40a80171ed23..07d16bd6d41af 100644 --- a/test/extensions/filters/http/fault/fault_filter_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_test.cc @@ -66,6 +66,15 @@ class FaultFilterTest : public testing::Test { fixed_delay: 5s )EOF"; + const std::string fixed_delay_only_disable_stats_yaml = R"EOF( + delay: + percentage: + numerator: 100 + denominator: HUNDRED + fixed_delay: 5s + disable_downstream_cluster_stats: true + )EOF"; + const std::string abort_only_yaml = R"EOF( abort: percentage: @@ -717,6 +726,55 @@ TEST_F(FaultFilterTest, DelayForDownstreamCluster) { EXPECT_EQ(0UL, stats_.counter("prefix.fault.cluster.aborts_injected").value()); } +TEST_F(FaultFilterTest, DelayForDownstreamClusterDisableTracing) { + setUpTest(fixed_delay_only_disable_stats_yaml); + + EXPECT_CALL(runtime_.snapshot_, + getInteger("fault.http.max_active_faults", std::numeric_limits::max())) + .WillOnce(Return(std::numeric_limits::max())); + + request_headers_.addCopy("x-envoy-downstream-service-cluster", "cluster"); + + // Delay related calls. + EXPECT_CALL( + runtime_.snapshot_, + featureEnabled("fault.http.cluster.delay.fixed_delay_percent", + testing::Matcher(Percent(100)))) + .WillOnce(Return(true)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.delay.fixed_duration_ms", 5000)) + .WillOnce(Return(125UL)); + EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.cluster.delay.fixed_duration_ms", 125UL)) + .WillOnce(Return(500UL)); + expectDelayTimer(500UL); + EXPECT_CALL(decoder_filter_callbacks_.stream_info_, + setResponseFlag(StreamInfo::ResponseFlag::DelayInjected)); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers_, false)); + + // Delay only case, no aborts. + EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.cluster.abort.http_status", _)).Times(0); + EXPECT_CALL(runtime_.snapshot_, getInteger("fault.http.abort.http_status", _)).Times(0); + EXPECT_CALL(decoder_filter_callbacks_, encodeHeaders_(_, _)).Times(0); + EXPECT_CALL(decoder_filter_callbacks_.stream_info_, + setResponseFlag(StreamInfo::ResponseFlag::FaultInjected)) + .Times(0); + EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(data_, false)); + + EXPECT_CALL(decoder_filter_callbacks_.dispatcher_, pushTrackedObject(_)); + EXPECT_CALL(decoder_filter_callbacks_.dispatcher_, popTrackedObject(_)); + timer_->invokeCallback(); + + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + + EXPECT_EQ(1UL, config_->stats().delays_injected_.value()); + EXPECT_EQ(0UL, config_->stats().aborts_injected_.value()); + EXPECT_EQ(0UL, stats_.counter("prefix.fault.cluster.delays_injected").value()); + EXPECT_EQ(0UL, stats_.counter("prefix.fault.cluster.aborts_injected").value()); +} + TEST_F(FaultFilterTest, FixedDelayAndAbortDownstream) { setUpTest(fixed_delay_and_abort_yaml); From 3e568421ef8ba59f3effe16ee18c83800817c174 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 4 May 2021 08:41:50 -0400 Subject: [PATCH 138/209] http3: turn up the last upstream tests! (#16279) Risk Level: n/a Testing: yes! Docs Changes: n/a Release Notes: n/a #14829 among others Signed-off-by: Alyssa Wilk --- .../multiplexed_upstream_integration_test.cc | 7 ------ test/integration/protocol_integration_test.cc | 24 +++++++++---------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/test/integration/multiplexed_upstream_integration_test.cc b/test/integration/multiplexed_upstream_integration_test.cc index 107f67b1e39b9..24690e011e6ca 100644 --- a/test/integration/multiplexed_upstream_integration_test.cc +++ b/test/integration/multiplexed_upstream_integration_test.cc @@ -15,12 +15,6 @@ namespace Envoy { -// TODO(#14829) categorize or fix all failures. -#define EXCLUDE_UPSTREAM_HTTP3 \ - if (upstreamProtocol() == FakeHttpConnection::Type::HTTP3) { \ - return; \ - } - INSTANTIATE_TEST_SUITE_P(Protocols, Http2UpstreamIntegrationTest, testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( {Http::CodecClient::Type::HTTP2}, {FakeHttpConnection::Type::HTTP2})), @@ -310,7 +304,6 @@ TEST_P(Http2UpstreamIntegrationTest, ManyLargeSimultaneousRequestWithBufferLimit } TEST_P(Http2UpstreamIntegrationTest, ManyLargeSimultaneousRequestWithRandomBackup) { - EXCLUDE_UPSTREAM_HTTP3; // fails: no 200s. config_helper_.addFilter( fmt::format(R"EOF( name: pause-filter{} diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index fe732211ee20e..da18aa66e82b1 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -53,12 +53,6 @@ void setDoNotValidateRouteConfig( route_config->mutable_validate_clusters()->set_value(false); }; -// TODO(#14829) categorize or fix all failures. -#define EXCLUDE_UPSTREAM_HTTP3 \ - if (upstreamProtocol() == FakeHttpConnection::Type::HTTP3) { \ - return; \ - } - // TODO(#2557) fix all the failures. #define EXCLUDE_DOWNSTREAM_HTTP3 \ if (downstreamProtocol() == Http::CodecClient::Type::HTTP3) { \ @@ -771,7 +765,6 @@ TEST_P(DownstreamProtocolIntegrationTest, RetryAttemptCountHeader) { // The retry priority will always target P1, which would otherwise never be hit due to P0 being // healthy. TEST_P(DownstreamProtocolIntegrationTest, RetryPriority) { - EXCLUDE_UPSTREAM_HTTP3; // Timed out waiting for new stream. const Upstream::HealthyLoad healthy_priority_load({0u, 100u}); const Upstream::DegradedLoad degraded_priority_load({0u, 100u}); NiceMock retry_priority(healthy_priority_load, @@ -825,6 +818,10 @@ TEST_P(DownstreamProtocolIntegrationTest, RetryPriority) { ASSERT_TRUE(upstream_request_->waitForReset()); } + if (upstreamProtocol() == FakeHttpConnection::Type::HTTP3) { + // Make sure waitForNextUpstreamRequest waits for a new connection. + fake_upstream_connection_.reset(); + } waitForNextUpstreamRequest(1); upstream_request_->encodeHeaders(default_response_headers_, false); upstream_request_->encodeData(512, true); @@ -1078,10 +1075,7 @@ TEST_P(ProtocolIntegrationTest, EnvoyProxyingLateMultiple1xx) { TEST_P(ProtocolIntegrationTest, TwoRequests) { testTwoRequests(); } -TEST_P(ProtocolIntegrationTest, TwoRequestsWithForcedBackup) { - EXCLUDE_UPSTREAM_HTTP3; - testTwoRequests(true); -} +TEST_P(ProtocolIntegrationTest, TwoRequestsWithForcedBackup) { testTwoRequests(true); } TEST_P(ProtocolIntegrationTest, BasicMaxStreamDuration) { testMaxStreamDuration(); } @@ -1576,8 +1570,12 @@ TEST_P(DownstreamProtocolIntegrationTest, LargeRequestHeadersRejected) { } TEST_P(DownstreamProtocolIntegrationTest, VeryLargeRequestHeadersRejected) { - EXCLUDE_DOWNSTREAM_HTTP3; - EXCLUDE_UPSTREAM_HTTP3; +#ifdef WIN32 + // TODO(alyssawilk, wrowe) debug. + if (upstreamProtocol() == FakeHttpConnection::Type::HTTP3) { + return; + } +#endif // Send one very large 2048 kB (2 MB) header with limit 1024 kB (1 MB) and 100 headers. testLargeRequestHeaders(2048, 1, 1024, 100); } From 510873db5e7173de55dc65d9fd64b1fbbabced26 Mon Sep 17 00:00:00 2001 From: Mike Schore Date: Tue, 4 May 2021 23:59:24 +0800 Subject: [PATCH 139/209] quic: fix missing cast in assertions (#16301) Signed-off-by: Mike Schore --- source/common/quic/envoy_quic_client_stream.cc | 3 ++- source/common/quic/envoy_quic_server_stream.cc | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/source/common/quic/envoy_quic_client_stream.cc b/source/common/quic/envoy_quic_client_stream.cc index cda06215167d9..6cb99fbc02d6d 100644 --- a/source/common/quic/envoy_quic_client_stream.cc +++ b/source/common/quic/envoy_quic_client_stream.cc @@ -40,7 +40,8 @@ EnvoyQuicClientStream::EnvoyQuicClientStream( static_cast(GetReceiveWindow().value()), *filterManagerConnection(), [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }, stats, http3_options) { - ASSERT(GetReceiveWindow() > 8 * 1024, "Send buffer limit should be larger than 8KB."); + ASSERT(static_cast(GetReceiveWindow().value()) > 8 * 1024, + "Send buffer limit should be larger than 8KB."); } EnvoyQuicClientStream::EnvoyQuicClientStream( diff --git a/source/common/quic/envoy_quic_server_stream.cc b/source/common/quic/envoy_quic_server_stream.cc index ab2d0bc9e5467..0c90cf66f6c97 100644 --- a/source/common/quic/envoy_quic_server_stream.cc +++ b/source/common/quic/envoy_quic_server_stream.cc @@ -45,7 +45,8 @@ EnvoyQuicServerStream::EnvoyQuicServerStream( [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }, stats, http3_options), headers_with_underscores_action_(headers_with_underscores_action) { - ASSERT(GetReceiveWindow() > 8 * 1024, "Send buffer limit should be larger than 8KB."); + ASSERT(static_cast(GetReceiveWindow().value()) > 8 * 1024, + "Send buffer limit should be larger than 8KB."); } EnvoyQuicServerStream::EnvoyQuicServerStream( From 3994805a55c9b09bbb26b25cdd7a636b490bb7b3 Mon Sep 17 00:00:00 2001 From: Charissa Plattner Date: Tue, 4 May 2021 12:03:31 -0400 Subject: [PATCH 140/209] docs: fix type_url for v3.TlsInspector (#16290) Signed-off-by: Charissa Sonder Plattner --- .../listeners/listener_filters/tls_inspector.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/root/configuration/listeners/listener_filters/tls_inspector.rst b/docs/root/configuration/listeners/listener_filters/tls_inspector.rst index 9e6d424485e5f..75081b8b32909 100644 --- a/docs/root/configuration/listeners/listener_filters/tls_inspector.rst +++ b/docs/root/configuration/listeners/listener_filters/tls_inspector.rst @@ -17,7 +17,7 @@ of a :ref:`FilterChainMatch ` * :ref:`v3 API reference ` * This filter may be configured with the name *envoy.filters.listener.tls_inspector* or - *type.googleapis.com/envoy.extensions.listeners.tls_inspector.v3.TlsInspector* as the + *type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector* as the `type_url `_. Example @@ -39,7 +39,7 @@ of the *typed_config*: listener_filters: - name: "tls_inspector" typed_config: - "@type": type.googleapis.com/envoy.extensions.listeners.tls_inspector.v3.TlsInspector + "@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector Statistics ---------- From 595bfcf04defcf7070d6fe0a5be2cc34f4febed9 Mon Sep 17 00:00:00 2001 From: yihuaz Date: Tue, 4 May 2021 10:46:22 -0700 Subject: [PATCH 141/209] alts: Fix TsiSocket doWrite on short writes (#15962) Signed-off-by: yihuaz --- .../alts/tsi_frame_protector.cc | 8 +- .../alts/tsi_frame_protector.h | 7 +- .../transport_sockets/alts/tsi_socket.cc | 92 +++++- .../transport_sockets/alts/tsi_socket.h | 13 + .../alts/alts_integration_test.cc | 2 + .../alts/tsi_frame_protector_test.cc | 26 +- .../transport_sockets/alts/tsi_socket_test.cc | 285 +++++++++++++++++- 7 files changed, 385 insertions(+), 48 deletions(-) diff --git a/source/extensions/transport_sockets/alts/tsi_frame_protector.cc b/source/extensions/transport_sockets/alts/tsi_frame_protector.cc index d248c3f380d0f..2739f2029a981 100644 --- a/source/extensions/transport_sockets/alts/tsi_frame_protector.cc +++ b/source/extensions/transport_sockets/alts/tsi_frame_protector.cc @@ -3,7 +3,6 @@ #include "common/buffer/buffer_impl.h" #include "common/common/assert.h" -#include "grpc/slice_buffer.h" #include "src/core/tsi/transport_security_grpc.h" #include "src/core/tsi/transport_security_interface.h" @@ -15,16 +14,14 @@ namespace Alts { TsiFrameProtector::TsiFrameProtector(CFrameProtectorPtr&& frame_protector) : frame_protector_(std::move(frame_protector)) {} -tsi_result TsiFrameProtector::protect(Buffer::Instance& input, Buffer::Instance& output) { +tsi_result TsiFrameProtector::protect(const grpc_slice& input_slice, Buffer::Instance& output) { ASSERT(frame_protector_); - if (input.length() == 0) { + if (GRPC_SLICE_LENGTH(input_slice) == 0) { return TSI_OK; } grpc_core::ExecCtx exec_ctx; - grpc_slice input_slice = grpc_slice_from_copied_buffer( - reinterpret_cast(input.linearize(input.length())), input.length()); grpc_slice_buffer message_buffer; grpc_slice_buffer_init(&message_buffer); @@ -58,7 +55,6 @@ tsi_result TsiFrameProtector::protect(Buffer::Instance& input, Buffer::Instance& }); output.addBufferFragment(*fragment); - input.drain(input.length()); grpc_slice_buffer_destroy(&message_buffer); grpc_slice_buffer_destroy(&protected_buffer); diff --git a/source/extensions/transport_sockets/alts/tsi_frame_protector.h b/source/extensions/transport_sockets/alts/tsi_frame_protector.h index 00a3d49ed7b4b..5ea60bb5a0c76 100644 --- a/source/extensions/transport_sockets/alts/tsi_frame_protector.h +++ b/source/extensions/transport_sockets/alts/tsi_frame_protector.h @@ -4,6 +4,8 @@ #include "extensions/transport_sockets/alts/grpc_tsi.h" +#include "grpc/slice_buffer.h" + namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -20,11 +22,12 @@ class TsiFrameProtector final { /** * Wrapper for tsi_frame_protector_protect - * @param input supplies the input data to protect, the method will drain it when it is processed. + * @param input_slice supplies the input data to protect. Its ownership will + * be transferred. * @param output supplies the buffer where the protected data will be stored. * @return tsi_result the status. */ - tsi_result protect(Buffer::Instance& input, Buffer::Instance& output); + tsi_result protect(const grpc_slice& input_slice, Buffer::Instance& output); /** * Wrapper for tsi_frame_protector_unprotect diff --git a/source/extensions/transport_sockets/alts/tsi_socket.cc b/source/extensions/transport_sockets/alts/tsi_socket.cc index aaa948a6acf5c..ff0cd252f11d0 100644 --- a/source/extensions/transport_sockets/alts/tsi_socket.cc +++ b/source/extensions/transport_sockets/alts/tsi_socket.cc @@ -166,7 +166,11 @@ Network::PostIoAction TsiSocket::doHandshakeNextDone(NextResultPtr&& next_result // Try to write raw buffer when next call is done, even this is not in do[Read|Write] stack. if (raw_write_buffer_.length() > 0) { - return raw_buffer_socket_->doWrite(raw_write_buffer_, false).action_; + Network::IoResult result = raw_buffer_socket_->doWrite(raw_write_buffer_, false); + if (handshake_complete_ && raw_write_buffer_.length() > 0) { + write_buffer_contains_handshake_bytes_ = true; + } + return result.action_; } return Network::PostIoAction::KeepOpen; @@ -259,28 +263,84 @@ Network::IoResult TsiSocket::doRead(Buffer::Instance& buffer) { return repeatReadAndUnprotect(buffer, result); } +Network::IoResult TsiSocket::repeatProtectAndWrite(Buffer::Instance& buffer, bool end_stream) { + uint64_t total_bytes_written = 0; + Network::IoResult result = {Network::PostIoAction::KeepOpen, 0, false}; + + ASSERT(!write_buffer_contains_handshake_bytes_); + while (true) { + uint64_t bytes_to_drain_this_iteration = + prev_bytes_to_drain_ > 0 + ? prev_bytes_to_drain_ + : std::min(buffer.length(), actual_frame_size_to_use_ - frame_overhead_size_); + // Consumed all data. Exit. + if (bytes_to_drain_this_iteration == 0) { + break; + } + // Short write did not occur previously. + if (raw_write_buffer_.length() == 0) { + ASSERT(frame_protector_); + ASSERT(prev_bytes_to_drain_ == 0); + + // Do protect. + ENVOY_CONN_LOG(debug, "TSI: protecting buffer size: {}", callbacks_->connection(), + bytes_to_drain_this_iteration); + tsi_result status = frame_protector_->protect( + grpc_slice_from_static_buffer(buffer.linearize(bytes_to_drain_this_iteration), + bytes_to_drain_this_iteration), + raw_write_buffer_); + ENVOY_CONN_LOG(debug, "TSI: protected buffer left: {} result: {}", callbacks_->connection(), + bytes_to_drain_this_iteration, tsi_result_to_string(status)); + } + + // Write raw_write_buffer_ to network. + ENVOY_CONN_LOG(debug, "TSI: raw_write length {} end_stream {}", callbacks_->connection(), + raw_write_buffer_.length(), end_stream); + result = raw_buffer_socket_->doWrite(raw_write_buffer_, end_stream && (buffer.length() == 0)); + + // Short write. Exit. + if (raw_write_buffer_.length() > 0) { + prev_bytes_to_drain_ = bytes_to_drain_this_iteration; + break; + } else { + buffer.drain(bytes_to_drain_this_iteration); + prev_bytes_to_drain_ = 0; + total_bytes_written += bytes_to_drain_this_iteration; + } + } + + return {result.action_, total_bytes_written, false}; +} + Network::IoResult TsiSocket::doWrite(Buffer::Instance& buffer, bool end_stream) { if (!handshake_complete_) { Network::PostIoAction action = doHandshake(); + // Envoy ALTS implements asynchronous tsi_handshaker_next() interface + // which returns immediately after scheduling a handshake request to + // the handshake service. The handshake response will be handled by a + // dedicated thread in a separate API within which handshake_complete_ + // will be set to true if the handshake completes. + ASSERT(!handshake_complete_); ASSERT(action == Network::PostIoAction::KeepOpen); // TODO(lizan): Handle synchronous handshake when TsiHandshaker supports it. - } - - if (handshake_complete_) { + return {Network::PostIoAction::KeepOpen, 0, false}; + } else { ASSERT(frame_protector_); - ENVOY_CONN_LOG(debug, "TSI: protecting buffer size: {}", callbacks_->connection(), - buffer.length()); - tsi_result status = frame_protector_->protect(buffer, raw_write_buffer_); - ENVOY_CONN_LOG(debug, "TSI: protected buffer left: {} result: {}", callbacks_->connection(), - buffer.length(), tsi_result_to_string(status)); - } - - if (raw_write_buffer_.length() > 0) { - ENVOY_CONN_LOG(debug, "TSI: raw_write length {} end_stream {}", callbacks_->connection(), - raw_write_buffer_.length(), end_stream); - return raw_buffer_socket_->doWrite(raw_write_buffer_, end_stream && (buffer.length() == 0)); + // Check if we need to flush outstanding handshake bytes. + if (write_buffer_contains_handshake_bytes_) { + ASSERT(raw_write_buffer_.length() > 0); + ENVOY_CONN_LOG(debug, "TSI: raw_write length {} end_stream {}", callbacks_->connection(), + raw_write_buffer_.length(), end_stream); + Network::IoResult result = + raw_buffer_socket_->doWrite(raw_write_buffer_, end_stream && (buffer.length() == 0)); + // Check if short write occurred. + if (raw_write_buffer_.length() > 0) { + return {result.action_, 0, false}; + } + write_buffer_contains_handshake_bytes_ = false; + } + return repeatProtectAndWrite(buffer, end_stream); } - return {Network::PostIoAction::KeepOpen, 0, false}; } void TsiSocket::closeSocket(Network::ConnectionEvent) { diff --git a/source/extensions/transport_sockets/alts/tsi_socket.h b/source/extensions/transport_sockets/alts/tsi_socket.h index 1d1fa547c6a3e..2d74b293d048f 100644 --- a/source/extensions/transport_sockets/alts/tsi_socket.h +++ b/source/extensions/transport_sockets/alts/tsi_socket.h @@ -74,6 +74,10 @@ class TsiSocket : public Network::TransportSocket, // This API should be called only after ALTS handshake finishes successfully. size_t actualFrameSizeToUse() { return actual_frame_size_to_use_; } + // Set actual_frame_size_to_use_. Exposed for testing purpose. + void setActualFrameSizeToUse(size_t frame_size) { actual_frame_size_to_use_ = frame_size; } + // Set frame_overhead_size_. Exposed for testing purpose. + void setFrameOverheadSize(size_t overhead_size) { frame_overhead_size_ = overhead_size; } private: Network::PostIoAction doHandshake(); @@ -82,6 +86,8 @@ class TsiSocket : public Network::TransportSocket, // Helper function to perform repeated read and unprotect operations. Network::IoResult repeatReadAndUnprotect(Buffer::Instance& buffer, Network::IoResult prev_result); + // Helper function to perform repeated protect and write operations. + Network::IoResult repeatProtectAndWrite(Buffer::Instance& buffer, bool end_stream); // Helper function to read from a raw socket and update status. Network::IoResult readFromRawSocket(); @@ -97,6 +103,11 @@ class TsiSocket : public Network::TransportSocket, // actual_frame_size_to_use_ is the actual frame size used by // frame protector, which is the result of frame size negotiation. size_t actual_frame_size_to_use_{0}; + // frame_overhead_size_ includes 4 bytes frame message type and 16 bytes tag length. + // It is consistent with gRPC ALTS zero copy frame protector implementation. + // The maximum size of data that can be protected for each frame is equal to + // actual_frame_size_to_use_ - frame_overhead_size_. + size_t frame_overhead_size_{20}; Envoy::Network::TransportSocketCallbacks* callbacks_{}; std::unique_ptr tsi_callbacks_; @@ -107,6 +118,8 @@ class TsiSocket : public Network::TransportSocket, bool handshake_complete_{}; bool end_stream_read_{}; bool read_error_{}; + bool write_buffer_contains_handshake_bytes_{}; + uint64_t prev_bytes_to_drain_{}; }; /** diff --git a/test/extensions/transport_sockets/alts/alts_integration_test.cc b/test/extensions/transport_sockets/alts/alts_integration_test.cc index 95c7fcd5e0ed5..283ce7cf835f1 100644 --- a/test/extensions/transport_sockets/alts/alts_integration_test.cc +++ b/test/extensions/transport_sockets/alts/alts_integration_test.cc @@ -183,6 +183,8 @@ class AltsIntegrationTestBase : public Event::TestUsingSimulatedTime, Network::Address::InstanceConstSharedPtr address = getAddress(version_, lookupPort("http")); auto client_transport_socket = client_alts_->createTransportSocket(nullptr); client_tsi_socket_ = dynamic_cast(client_transport_socket.get()); + client_tsi_socket_->setActualFrameSizeToUse(16384); + client_tsi_socket_->setFrameOverheadSize(4); return dispatcher_->createClientConnection(address, Network::Address::InstanceConstSharedPtr(), std::move(client_transport_socket), nullptr); } diff --git a/test/extensions/transport_sockets/alts/tsi_frame_protector_test.cc b/test/extensions/transport_sockets/alts/tsi_frame_protector_test.cc index ecc9cc066b8a8..26e4941a626cd 100644 --- a/test/extensions/transport_sockets/alts/tsi_frame_protector_test.cc +++ b/test/extensions/transport_sockets/alts/tsi_frame_protector_test.cc @@ -31,30 +31,28 @@ class TsiFrameProtectorTest : public testing::Test { TEST_F(TsiFrameProtectorTest, Protect) { { - Buffer::OwnedImpl input, encrypted; - input.add("foo"); + Buffer::OwnedImpl encrypted; - EXPECT_EQ(TSI_OK, frame_protector_.protect(input, encrypted)); + EXPECT_EQ(TSI_OK, frame_protector_.protect(grpc_slice_from_static_string("foo"), encrypted)); EXPECT_EQ("\x07\0\0\0foo"s, encrypted.toString()); } { - Buffer::OwnedImpl input, encrypted; - input.add("foo"); + Buffer::OwnedImpl encrypted; - EXPECT_EQ(TSI_OK, frame_protector_.protect(input, encrypted)); + EXPECT_EQ(TSI_OK, frame_protector_.protect(grpc_slice_from_static_string("foo"), encrypted)); EXPECT_EQ("\x07\0\0\0foo"s, encrypted.toString()); - input.add("bar"); - EXPECT_EQ(TSI_OK, frame_protector_.protect(input, encrypted)); + EXPECT_EQ(TSI_OK, frame_protector_.protect(grpc_slice_from_static_string("bar"), encrypted)); EXPECT_EQ("\x07\0\0\0foo\x07\0\0\0bar"s, encrypted.toString()); } { - Buffer::OwnedImpl input, encrypted; - input.add(std::string(20000, 'a')); + Buffer::OwnedImpl encrypted; - EXPECT_EQ(TSI_OK, frame_protector_.protect(input, encrypted)); + EXPECT_EQ(TSI_OK, + frame_protector_.protect( + grpc_slice_from_static_string(std::string(20000, 'a').c_str()), encrypted)); // fake frame protector will split long buffer to 2 "encrypted" frames with length 16K. std::string expected = @@ -71,10 +69,10 @@ TEST_F(TsiFrameProtectorTest, ProtectError) { }; raw_frame_protector_->vtable = &mock_vtable; - Buffer::OwnedImpl input, encrypted; - input.add("foo"); + Buffer::OwnedImpl encrypted; - EXPECT_EQ(TSI_INTERNAL_ERROR, frame_protector_.protect(input, encrypted)); + EXPECT_EQ(TSI_INTERNAL_ERROR, + frame_protector_.protect(grpc_slice_from_static_string("foo"), encrypted)); raw_frame_protector_->vtable = vtable; } diff --git a/test/extensions/transport_sockets/alts/tsi_socket_test.cc b/test/extensions/transport_sockets/alts/tsi_socket_test.cc index 550d4231cb7c1..046ff9d4032ae 100644 --- a/test/extensions/transport_sockets/alts/tsi_socket_test.cc +++ b/test/extensions/transport_sockets/alts/tsi_socket_test.cc @@ -19,6 +19,13 @@ using testing::NiceMock; using testing::Return; using testing::ReturnRef; +static const std::string ClientToServerData = "hello from client"; +static const std::string ClientToServerDataFirstHalf = "hello fro"; +static const std::string ClientToServerDataSecondHalf = "m client"; +static const std::string ServerToClientData = "hello from server"; +static const uint32_t LargeFrameSize = 100; +static const uint32_t SmallFrameSize = 13; + class TsiSocketTest : public testing::Test { protected: TsiSocketTest() { @@ -95,6 +102,9 @@ class TsiSocketTest : public testing::Test { EXPECT_CALL(*server_.raw_socket_, setTransportSocketCallbacks(_)); server_.tsi_socket_->setTransportSocketCallbacks(server_.callbacks_); + + server_.tsi_socket_->setFrameOverheadSize(4); + client_.tsi_socket_->setFrameOverheadSize(4); } void expectIoResult(Network::IoResult expected, Network::IoResult actual) { @@ -181,8 +191,10 @@ class TsiSocketTest : public testing::Test { EXPECT_EQ("", client_.tsi_socket_->protocol()); + client_.tsi_socket_->setActualFrameSizeToUse(LargeFrameSize); + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)); - expectIoResult({Network::PostIoAction::KeepOpen, 21UL, false}, + expectIoResult({Network::PostIoAction::KeepOpen, 17UL, false}, client_.tsi_socket_->doWrite(client_.write_buffer_, false)); EXPECT_EQ(makeFakeTsiFrame(data), client_to_server_.toString()); EXPECT_CALL(*server_.raw_socket_, doRead(_)).WillOnce(Invoke([&](Buffer::Instance& buffer) { @@ -214,8 +226,6 @@ class TsiSocketTest : public testing::Test { NiceMock dispatcher_; }; -static const std::string ClientToServerData = "hello from client"; - TEST_F(TsiSocketTest, DoesNotHaveSsl) { initialize(nullptr, nullptr); EXPECT_EQ(nullptr, client_.tsi_socket_->ssl()); @@ -456,8 +466,10 @@ TEST_F(TsiSocketTest, DoReadEndOfStream) { doHandshakeAndExpectSuccess(); + client_.tsi_socket_->setActualFrameSizeToUse(LargeFrameSize); + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)); - expectIoResult({Network::PostIoAction::KeepOpen, 21UL, false}, + expectIoResult({Network::PostIoAction::KeepOpen, 17UL, false}, client_.tsi_socket_->doWrite(client_.write_buffer_, false)); EXPECT_CALL(*server_.raw_socket_, doRead(_)).WillOnce(Invoke([&](Buffer::Instance& buffer) { @@ -481,8 +493,10 @@ TEST_F(TsiSocketTest, DoReadNoData) { doHandshakeAndExpectSuccess(); + client_.tsi_socket_->setActualFrameSizeToUse(LargeFrameSize); + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)); - expectIoResult({Network::PostIoAction::KeepOpen, 21UL, false}, + expectIoResult({Network::PostIoAction::KeepOpen, 17UL, false}, client_.tsi_socket_->doWrite(client_.write_buffer_, false)); EXPECT_CALL(*server_.raw_socket_, doRead(_)).WillOnce(Invoke([&](Buffer::Instance& buffer) { @@ -511,8 +525,10 @@ TEST_F(TsiSocketTest, DoReadTwiceError) { doHandshakeAndExpectSuccess(); + client_.tsi_socket_->setActualFrameSizeToUse(LargeFrameSize); + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)); - expectIoResult({Network::PostIoAction::KeepOpen, 21UL, false}, + expectIoResult({Network::PostIoAction::KeepOpen, 17UL, false}, client_.tsi_socket_->doWrite(client_.write_buffer_, false)); EXPECT_CALL(*server_.raw_socket_, doRead(_)).WillOnce(Invoke([&](Buffer::Instance& buffer) { @@ -541,8 +557,10 @@ TEST_F(TsiSocketTest, DoReadOnceError) { doHandshakeAndExpectSuccess(); + client_.tsi_socket_->setActualFrameSizeToUse(LargeFrameSize); + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)); - expectIoResult({Network::PostIoAction::KeepOpen, 21UL, false}, + expectIoResult({Network::PostIoAction::KeepOpen, 17UL, false}, client_.tsi_socket_->doWrite(client_.write_buffer_, false)); EXPECT_CALL(*server_.raw_socket_, doRead(_)).WillOnce(Invoke([&](Buffer::Instance& buffer) { @@ -566,8 +584,10 @@ TEST_F(TsiSocketTest, DoReadDrainBuffer) { doHandshakeAndExpectSuccess(); + client_.tsi_socket_->setActualFrameSizeToUse(LargeFrameSize); + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)); - expectIoResult({Network::PostIoAction::KeepOpen, 21UL, false}, + expectIoResult({Network::PostIoAction::KeepOpen, 17UL, false}, client_.tsi_socket_->doWrite(client_.write_buffer_, false)); EXPECT_CALL(*server_.raw_socket_, doRead(_)).WillOnce(Invoke([&](Buffer::Instance& buffer) { @@ -590,8 +610,10 @@ TEST_F(TsiSocketTest, DoReadDrainBufferTwice) { doHandshakeAndExpectSuccess(); + client_.tsi_socket_->setActualFrameSizeToUse(LargeFrameSize); + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)); - expectIoResult({Network::PostIoAction::KeepOpen, 21UL, false}, + expectIoResult({Network::PostIoAction::KeepOpen, 17UL, false}, client_.tsi_socket_->doWrite(client_.write_buffer_, false)); EXPECT_CALL(*server_.raw_socket_, doRead(_)).WillOnce(Invoke([&](Buffer::Instance& buffer) { @@ -608,7 +630,7 @@ TEST_F(TsiSocketTest, DoReadDrainBufferTwice) { server_.read_buffer_.drain(server_.read_buffer_.length()); client_.write_buffer_.add(ClientToServerData); EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)); - expectIoResult({Network::PostIoAction::KeepOpen, 21UL, false}, + expectIoResult({Network::PostIoAction::KeepOpen, 17UL, false}, client_.tsi_socket_->doWrite(client_.write_buffer_, false)); EXPECT_CALL(*server_.raw_socket_, doRead(_)).WillOnce(Invoke([&](Buffer::Instance& buffer) { @@ -628,6 +650,249 @@ TEST_F(TsiSocketTest, DoReadDrainBufferTwice) { EXPECT_EQ(ClientToServerData, server_.read_buffer_.toString()); } +TEST_F(TsiSocketTest, DoWriteSmallFrameSize) { + auto validator = [](const tsi_peer&, std::string&) { return true; }; + initialize(validator, validator); + + InSequence s; + client_.write_buffer_.add(ClientToServerData); + + doHandshakeAndExpectSuccess(); + + EXPECT_EQ(0L, server_.read_buffer_.length()); + EXPECT_EQ(0L, client_.read_buffer_.length()); + + EXPECT_EQ("", client_.tsi_socket_->protocol()); + client_.tsi_socket_->setActualFrameSizeToUse(SmallFrameSize); + // Since we use a small frame size, original data is divided into two parts, + // and written to network in two iterations. + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) { + Network::IoResult result = {Network::PostIoAction::KeepOpen, buffer.length(), false}; + client_to_server_.move(buffer); + return result; + })); + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) { + Network::IoResult result = {Network::PostIoAction::KeepOpen, buffer.length(), false}; + client_to_server_.move(buffer); + return result; + })); + expectIoResult({Network::PostIoAction::KeepOpen, 17UL, false}, + client_.tsi_socket_->doWrite(client_.write_buffer_, false)); + + EXPECT_EQ(makeFakeTsiFrame(ClientToServerDataFirstHalf) + + makeFakeTsiFrame(ClientToServerDataSecondHalf), + client_to_server_.toString()); + + EXPECT_CALL(*server_.raw_socket_, doRead(_)).WillOnce(Invoke([&](Buffer::Instance& buffer) { + Network::IoResult result = {Network::PostIoAction::Close, client_to_server_.length(), false}; + buffer.move(client_to_server_); + return result; + })); + expectIoResult({Network::PostIoAction::Close, 17UL, false}, + server_.tsi_socket_->doRead(server_.read_buffer_)); + EXPECT_EQ(ClientToServerData, server_.read_buffer_.toString()); +} + +TEST_F(TsiSocketTest, DoWriteSingleShortWrite) { + auto validator = [](const tsi_peer&, std::string&) { return true; }; + initialize(validator, validator); + + InSequence s; + client_.write_buffer_.add(ClientToServerData); + + doHandshakeAndExpectSuccess(); + + client_.tsi_socket_->setActualFrameSizeToUse(LargeFrameSize); + + // Write the whole data except for the last byte. + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) { + Network::IoResult result = {Network::PostIoAction::KeepOpen, buffer.length() - 1, false}; + client_to_server_.add(buffer.linearize(0), buffer.length() - 1); + return result; + })); + expectIoResult({Network::PostIoAction::KeepOpen, 0UL, false}, + client_.tsi_socket_->doWrite(client_.write_buffer_, false)); + + EXPECT_EQ(makeFakeTsiFrame(ClientToServerData).substr(0, 20), client_to_server_.toString()); + + // TSI frame is invalid, return with Close action. + EXPECT_CALL(*server_.raw_socket_, doRead(_)).WillOnce(Invoke([&](Buffer::Instance& buffer) { + Network::IoResult result = {Network::PostIoAction::Close, 20UL, true}; + buffer.move(client_to_server_); + return result; + })); + expectIoResult({Network::PostIoAction::Close, 0UL, true}, + server_.tsi_socket_->doRead(server_.read_buffer_)); +} + +TEST_F(TsiSocketTest, DoWriteMultipleShortWrites) { + auto validator = [](const tsi_peer&, std::string&) { return true; }; + initialize(validator, validator); + + InSequence s; + client_.write_buffer_.add(ClientToServerData); + + doHandshakeAndExpectSuccess(); + + client_.tsi_socket_->setActualFrameSizeToUse(LargeFrameSize); + + // Write the whole data except for the last byte. + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) { + Network::IoResult result = {Network::PostIoAction::KeepOpen, buffer.length() - 1, false}; + client_to_server_.add(buffer.linearize(0), buffer.length() - 1); + buffer.drain(buffer.length() - 1); + return result; + })); + expectIoResult({Network::PostIoAction::KeepOpen, 0UL, false}, + client_.tsi_socket_->doWrite(client_.write_buffer_, false)); + + EXPECT_EQ(makeFakeTsiFrame(ClientToServerData).substr(0, 20), client_to_server_.toString()); + + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) { + Network::IoResult result = {Network::PostIoAction::KeepOpen, 1, false}; + client_to_server_.add(buffer.linearize(buffer.length() - 1), 1); + return result; + })); + expectIoResult({Network::PostIoAction::KeepOpen, 0UL, false}, + client_.tsi_socket_->doWrite(client_.write_buffer_, false)); + EXPECT_EQ(makeFakeTsiFrame(ClientToServerData), client_to_server_.toString()); + + EXPECT_CALL(*server_.raw_socket_, doRead(_)).WillOnce(Invoke([&](Buffer::Instance& buffer) { + Network::IoResult result = {Network::PostIoAction::Close, client_to_server_.length(), false}; + buffer.move(client_to_server_); + return result; + })); + expectIoResult({Network::PostIoAction::Close, 17UL, false}, + server_.tsi_socket_->doRead(server_.read_buffer_)); + EXPECT_EQ(ClientToServerData, server_.read_buffer_.toString()); +} + +TEST_F(TsiSocketTest, DoWriteMixShortFullWrites) { + auto validator = [](const tsi_peer&, std::string&) { return true; }; + initialize(validator, validator); + + InSequence s; + client_.write_buffer_.add(ClientToServerData); + + doHandshakeAndExpectSuccess(); + + client_.tsi_socket_->setActualFrameSizeToUse(SmallFrameSize); + + // Short write occurred when writing the first half of data. + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) { + Network::IoResult result = {Network::PostIoAction::KeepOpen, buffer.length() - 1, false}; + client_to_server_.add(buffer.linearize(0), buffer.length() - 1); + buffer.drain(buffer.length() - 1); + return result; + })); + expectIoResult({Network::PostIoAction::KeepOpen, 0UL, false}, + client_.tsi_socket_->doWrite(client_.write_buffer_, false)); + EXPECT_EQ(makeFakeTsiFrame(ClientToServerDataFirstHalf).substr(0, 12), + client_to_server_.toString()); + + // In the next write, we first finish the remaining data that has not been + // written in the previous write and then write the second half of data. + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) { + Network::IoResult result = {Network::PostIoAction::KeepOpen, 1, false}; + client_to_server_.move(buffer); + return result; + })); + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) { + Network::IoResult result = {Network::PostIoAction::KeepOpen, buffer.length(), false}; + client_to_server_.move(buffer); + return result; + })); + expectIoResult({Network::PostIoAction::KeepOpen, 17UL, false}, + client_.tsi_socket_->doWrite(client_.write_buffer_, false)); + EXPECT_EQ(makeFakeTsiFrame(ClientToServerDataFirstHalf) + + makeFakeTsiFrame(ClientToServerDataSecondHalf), + client_to_server_.toString()); + + EXPECT_CALL(*server_.raw_socket_, doRead(_)).WillOnce(Invoke([&](Buffer::Instance& buffer) { + Network::IoResult result = {Network::PostIoAction::Close, client_to_server_.length(), false}; + buffer.move(client_to_server_); + return result; + })); + expectIoResult({Network::PostIoAction::Close, 17UL, false}, + server_.tsi_socket_->doRead(server_.read_buffer_)); + EXPECT_EQ(ClientToServerData, server_.read_buffer_.toString()); +} + +TEST_F(TsiSocketTest, DoWriteOutstandingHandshakeData) { + auto validator = [](const tsi_peer&, std::string&) { return true; }; + initialize(validator, validator); + + InSequence s; + doFakeInitHandshake(); + + EXPECT_CALL(*client_.raw_socket_, doRead(_)); + EXPECT_CALL(*client_.raw_socket_, doWrite(_, false)); + expectIoResult({Network::PostIoAction::KeepOpen, 0UL, false}, + client_.tsi_socket_->doRead(client_.read_buffer_)); + EXPECT_EQ(makeFakeTsiFrame("CLIENT_FINISHED"), client_to_server_.toString()); + EXPECT_EQ(0L, client_.read_buffer_.length()); + + EXPECT_CALL(*server_.raw_socket_, doRead(_)); + EXPECT_CALL(server_.callbacks_, raiseEvent(Network::ConnectionEvent::Connected)); + + // Write the first part of handshake data (14 bytes). + EXPECT_CALL(*server_.raw_socket_, doWrite(_, false)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) { + Network::IoResult result = {Network::PostIoAction::KeepOpen, buffer.length() - 5, false}; + server_to_client_.move(buffer, 14); + return result; + })); + EXPECT_CALL(*server_.raw_socket_, doRead(_)); + expectIoResult({Network::PostIoAction::KeepOpen, 0UL, false}, + server_.tsi_socket_->doRead(server_.read_buffer_)); + + EXPECT_EQ(makeFakeTsiFrame("SERVER_FINISHED").length(), 19); + EXPECT_EQ(makeFakeTsiFrame("SERVER_FINISHED").substr(0, 14), server_to_client_.toString()); + + server_.write_buffer_.add(ServerToClientData); + server_.tsi_socket_->setActualFrameSizeToUse(LargeFrameSize); + + // Write the second part of handshake data (4 bytes). + EXPECT_CALL(*server_.raw_socket_, doWrite(_, false)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) { + Network::IoResult result = {Network::PostIoAction::KeepOpen, 4, false}; + server_to_client_.move(buffer, 4); + return result; + })); + expectIoResult({Network::PostIoAction::KeepOpen, 0UL, false}, + server_.tsi_socket_->doWrite(server_.write_buffer_, false)); + EXPECT_EQ(makeFakeTsiFrame("SERVER_FINISHED").substr(0, 18), server_to_client_.toString()); + + // Write the last part of handshake data (1 byte) and frame data. + EXPECT_CALL(*server_.raw_socket_, doWrite(_, false)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) { + Network::IoResult result = {Network::PostIoAction::KeepOpen, 1, false}; + server_to_client_.move(buffer); + return result; + })); + EXPECT_CALL(*server_.raw_socket_, doWrite(_, false)); + expectIoResult({Network::PostIoAction::KeepOpen, 17UL, false}, + server_.tsi_socket_->doWrite(server_.write_buffer_, false)); + EXPECT_EQ(makeFakeTsiFrame("SERVER_FINISHED") + makeFakeTsiFrame(ServerToClientData), + server_to_client_.toString()); + + // Check client side (handshake completes + receive unused data). + EXPECT_CALL(*client_.raw_socket_, doRead(_)); + EXPECT_CALL(client_.callbacks_, raiseEvent(Network::ConnectionEvent::Connected)); + EXPECT_CALL(*client_.raw_socket_, doRead(_)); + expectIoResult({Network::PostIoAction::KeepOpen, 17UL, false}, + client_.tsi_socket_->doRead(client_.read_buffer_)); + EXPECT_EQ(ServerToClientData, client_.read_buffer_.toString()); +} + class TsiSocketFactoryTest : public testing::Test { protected: void SetUp() override { From 282246305aef1abaab327d22168867ffc505dd2e Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 4 May 2021 19:28:26 +0100 Subject: [PATCH 142/209] dependabot: Aggregate updates (#16228) * build(deps): bump babel from 2.9.0 to 2.9.1 in /docs Bumps [babel](https://github.com/python-babel/babel) from 2.9.0 to 2.9.1. - [Release notes](https://github.com/python-babel/babel/releases) - [Changelog](https://github.com/python-babel/babel/blob/master/CHANGES) - [Commits](https://github.com/python-babel/babel/compare/v2.9.0...v2.9.1) * build(deps): bump pyjwt from 2.0.1 to 2.1.0 in /tools/dependency Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.0.1 to 2.1.0. - [Release notes](https://github.com/jpadilla/pyjwt/releases) - [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst) - [Commits](https://github.com/jpadilla/pyjwt/compare/2.0.1...2.1.0) * build(deps): bump pyjwt from 2.0.1 to 2.1.0 in /tools/deprecate_version Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.0.1 to 2.1.0. - [Release notes](https://github.com/jpadilla/pyjwt/releases) - [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst) - [Commits](https://github.com/jpadilla/pyjwt/compare/2.0.1...2.1.0) * build(deps): bump typing-extensions in /ci/flaky_test Bumps [typing-extensions](https://github.com/python/typing) from 3.7.4.3 to 3.10.0.0. - [Release notes](https://github.com/python/typing/releases) - [Commits](https://github.com/python/typing/compare/3.7.4.3...3.10.0.0) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey --- ci/flaky_test/requirements.txt | 8 ++++---- docs/requirements.txt | 6 +++--- tools/dependency/requirements.txt | 6 +++--- tools/deprecate_version/requirements.txt | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ci/flaky_test/requirements.txt b/ci/flaky_test/requirements.txt index b64c4c1908be2..95bac14fdd655 100644 --- a/ci/flaky_test/requirements.txt +++ b/ci/flaky_test/requirements.txt @@ -91,10 +91,10 @@ multidict==5.1.0 \ slackclient==2.9.3 \ --hash=sha256:2d68d668c02f4038299897e5c4723ab85dd40a3548354924b24f333a435856f8 \ --hash=sha256:07ec8fa76f6aa64852210ae235ff9e637ba78124e06c0b07a7eeea4abb955965 -typing-extensions==3.7.4.3 \ - --hash=sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918 \ - --hash=sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c \ - --hash=sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f +typing-extensions==3.10.0.0 \ + --hash=sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497 \ + --hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84 \ + --hash=sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342 wheel==0.36.2 \ --hash=sha256:78b5b185f0e5763c26ca1e324373aadd49182ca90e825f7853f4b2509215dc0e \ --hash=sha256:e11eefd162658ea59a60a0f6c7d493a7190ea4b9a85e335b33489d9f17e0245e diff --git a/docs/requirements.txt b/docs/requirements.txt index 376c7c4fbb809..3647d79e14bdf 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -10,9 +10,9 @@ alabaster==0.7.12 \ # via # -r docs/requirements.txt # sphinx -babel==2.9.0 \ - --hash=sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5 \ - --hash=sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05 +babel==2.9.1 \ + --hash=sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9 \ + --hash=sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0 # via # -r docs/requirements.txt # sphinx diff --git a/tools/dependency/requirements.txt b/tools/dependency/requirements.txt index 311fcbd6d18ee..e9cec75b711c1 100644 --- a/tools/dependency/requirements.txt +++ b/tools/dependency/requirements.txt @@ -75,9 +75,9 @@ pygithub==1.55 \ --hash=sha256:1bbfff9372047ff3f21d5cd8e07720f3dbfdaf6462fcaed9d815f528f1ba7283 \ --hash=sha256:2caf0054ea079b71e539741ae56c5a95e073b81fa472ce222e81667381b9601b # via -r tools/dependency/requirements.txt -pyjwt==2.0.1 \ - --hash=sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7 \ - --hash=sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847 +pyjwt==2.1.0 \ + --hash=sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1 \ + --hash=sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130 # via pygithub pynacl==1.4.0 \ --hash=sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4 \ diff --git a/tools/deprecate_version/requirements.txt b/tools/deprecate_version/requirements.txt index e616d1bc66eff..042b3f3e5911d 100644 --- a/tools/deprecate_version/requirements.txt +++ b/tools/deprecate_version/requirements.txt @@ -85,9 +85,9 @@ pygithub==1.55 \ --hash=sha256:1bbfff9372047ff3f21d5cd8e07720f3dbfdaf6462fcaed9d815f528f1ba7283 \ --hash=sha256:2caf0054ea079b71e539741ae56c5a95e073b81fa472ce222e81667381b9601b # via -r tools/deprecate_version/requirements.txt -pyjwt==2.0.1 \ - --hash=sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7 \ - --hash=sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847 +pyjwt==2.1.0 \ + --hash=sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1 \ + --hash=sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130 # via pygithub pynacl==1.4.0 \ --hash=sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4 \ From 64739c567af14f14895d24d2389f2206b0c35ef9 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 4 May 2021 15:03:08 -0400 Subject: [PATCH 143/209] udp: log when BPF is not attempted (#16304) Adding an informative log since I ended up confused when trying to sort out #15845 and needed to fix #15926 accordingly. Risk Level: n/a (adding info log) Testing: manual Signed-off-by: Alyssa Wilk --- source/common/quic/active_quic_listener.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/common/quic/active_quic_listener.cc b/source/common/quic/active_quic_listener.cc index 6115c9e5acfa6..2421b70e168b4 100644 --- a/source/common/quic/active_quic_listener.cc +++ b/source/common/quic/active_quic_listener.cc @@ -284,6 +284,8 @@ Network::ConnectionHandler::ActiveUdpListenerPtr ActiveQuicListenerFactory::crea options->push_back(std::make_shared( envoy::config::core::v3::SocketOption::STATE_BOUND, ENVOY_ATTACH_REUSEPORT_CBPF, absl::string_view(reinterpret_cast(&prog), sizeof(prog)))); + } else { + ENVOY_LOG(info, "Not applying BPF because concurrency is 1"); } }); From 40f66623ac83831069ab8baf2fa147dd20ce5749 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Tue, 4 May 2021 16:51:59 -0700 Subject: [PATCH 144/209] wasm: fix support for non-cloneable Wasm runtimes. (#16263) Signed-off-by: Piotr Sikora --- source/extensions/common/wasm/wasm.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index 3895d0429cbc7..c037c988c28ac 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -106,7 +106,8 @@ Wasm::Wasm(WasmConfig& config, absl::string_view vm_key, const Stats::ScopeShare Wasm::Wasm(WasmHandleSharedPtr base_wasm_handle, Event::Dispatcher& dispatcher) : WasmBase(base_wasm_handle, [&base_wasm_handle]() { - return createWasmVm(base_wasm_handle->wasm()->wasm_vm()->runtime()); + return createWasmVm(absl::StrCat("envoy.wasm.runtime.", + base_wasm_handle->wasm()->wasm_vm()->runtime())); }), scope_(getWasm(base_wasm_handle)->scope_), cluster_manager_(getWasm(base_wasm_handle)->clusterManager()), dispatcher_(dispatcher), From e0b03feaead2287a34878b9e67f198a868186fa7 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 5 May 2021 08:55:53 +0900 Subject: [PATCH 145/209] wasm: fix fail-close streams on VM failure. (#16112) Fixes https://github.com/envoyproxy/envoy/issues/14947 and properly closes streams. This commit differentiates `failStream` from `closeStream` where the former is called when a VM fails, and the latter is called via `close_stream` or `grpc_close` by user Wasm codes. Notably, we try to send local response with 503 for http streams as expected by the description of `fail_open` api. The change here is a little and mostly done in Proxy-Wasm C++ host implementation(https://github.com/proxy-wasm/proxy-wasm-cpp-host/pull/155). Signed-off-by: Takeshi Yoneda --- bazel/repository_locations.bzl | 6 +- source/extensions/common/wasm/context.cc | 41 ++++- source/extensions/common/wasm/context.h | 1 + test/extensions/filters/http/wasm/BUILD | 1 + .../filters/http/wasm/config_test.cc | 9 +- .../filters/http/wasm/test_data/BUILD | 12 ++ .../filters/http/wasm/test_data/panic_rust.rs | 39 +++++ .../wasm/test_data/test_close_stream_cpp.cc | 40 +++++ .../http/wasm/test_data/test_panic_cpp.cc | 66 ++++++++ .../filters/http/wasm/wasm_filter_test.cc | 157 ++++++++++++++++++ test/extensions/filters/network/wasm/BUILD | 1 + .../filters/network/wasm/test_data/BUILD | 12 ++ .../network/wasm/test_data/panic_rust.rs | 27 +++ .../wasm/test_data/test_close_stream_cpp.cc | 37 +++++ .../network/wasm/test_data/test_panic_cpp.cc | 43 +++++ .../filters/network/wasm/wasm_filter_test.cc | 101 ++++++++++- 16 files changed, 581 insertions(+), 12 deletions(-) create mode 100644 test/extensions/filters/http/wasm/test_data/panic_rust.rs create mode 100644 test/extensions/filters/http/wasm/test_data/test_close_stream_cpp.cc create mode 100644 test/extensions/filters/http/wasm/test_data/test_panic_cpp.cc create mode 100644 test/extensions/filters/network/wasm/test_data/panic_rust.rs create mode 100644 test/extensions/filters/network/wasm/test_data/test_close_stream_cpp.cc create mode 100644 test/extensions/filters/network/wasm/test_data/test_panic_cpp.cc diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index eaf034c2bf7de..d4be5d0a79f86 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -948,8 +948,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host", - version = "4b33df7638637fcc680291daa9d7a57c59e0411e", - sha256 = "e78507e04dc8b154ced5b19d6175bd1435fd850fdd127bce541d31799db06cd4", + version = "579189940ee48ebf1fb1d6539483506bee89f0b4", + sha256 = "eedcc5d0e73a715d9361eda39b21b178e077ab4d749d6bf2f030de81f668d6d3", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], @@ -964,7 +964,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.wasm.runtime.wavm", "envoy.wasm.runtime.wasmtime", ], - release_date = "2021-04-20", + release_date = "2021-04-28", cpe = "N/A", ), proxy_wasm_rust_sdk = dict( diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 1ee17e9d72bd1..6d95ec6876232 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -88,8 +88,6 @@ Http::RequestHeaderMapPtr buildRequestHeaderMapFromPairs(const Pairs& pairs) { template static uint32_t headerSize(const P& p) { return p ? p->size() : 0; } -constexpr absl::string_view FailStreamResponseDetails = "wasm_fail_stream"; - } // namespace // Test support. @@ -1584,12 +1582,14 @@ WasmResult Context::continueStream(WasmStreamType stream_type) { return WasmResult::Ok; } +constexpr absl::string_view CloseStreamResponseDetails = "wasm_close_stream"; + WasmResult Context::closeStream(WasmStreamType stream_type) { switch (stream_type) { case WasmStreamType::Request: if (decoder_callbacks_) { if (!decoder_callbacks_->streamInfo().responseCodeDetails().has_value()) { - decoder_callbacks_->streamInfo().setResponseCodeDetails(FailStreamResponseDetails); + decoder_callbacks_->streamInfo().setResponseCodeDetails(CloseStreamResponseDetails); } decoder_callbacks_->resetStream(); } @@ -1597,7 +1597,7 @@ WasmResult Context::closeStream(WasmStreamType stream_type) { case WasmStreamType::Response: if (encoder_callbacks_) { if (!encoder_callbacks_->streamInfo().responseCodeDetails().has_value()) { - encoder_callbacks_->streamInfo().setResponseCodeDetails(FailStreamResponseDetails); + encoder_callbacks_->streamInfo().setResponseCodeDetails(CloseStreamResponseDetails); } encoder_callbacks_->resetStream(); } @@ -1618,6 +1618,39 @@ WasmResult Context::closeStream(WasmStreamType stream_type) { return WasmResult::BadArgument; } +constexpr absl::string_view FailStreamResponseDetails = "wasm_fail_stream"; + +void Context::failStream(WasmStreamType stream_type) { + switch (stream_type) { + case WasmStreamType::Request: + if (decoder_callbacks_) { + decoder_callbacks_->sendLocalReply(Envoy::Http::Code::ServiceUnavailable, "", nullptr, + Grpc::Status::WellKnownGrpcStatus::Unavailable, + FailStreamResponseDetails); + } + break; + case WasmStreamType::Response: + if (encoder_callbacks_) { + encoder_callbacks_->sendLocalReply(Envoy::Http::Code::ServiceUnavailable, "", nullptr, + Grpc::Status::WellKnownGrpcStatus::Unavailable, + FailStreamResponseDetails); + } + break; + case WasmStreamType::Downstream: + if (network_read_filter_callbacks_) { + network_read_filter_callbacks_->connection().close( + Envoy::Network::ConnectionCloseType::FlushWrite); + } + break; + case WasmStreamType::Upstream: + if (network_write_filter_callbacks_) { + network_write_filter_callbacks_->connection().close( + Envoy::Network::ConnectionCloseType::FlushWrite); + } + break; + } +} + WasmResult Context::sendLocalResponse(uint32_t response_code, absl::string_view body_text, Pairs additional_headers, uint32_t grpc_status, absl::string_view details) { diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index 5863737695725..26b51d9987659 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -209,6 +209,7 @@ class Context : public proxy_wasm::ContextBase, // Continue WasmResult continueStream(WasmStreamType stream_type) override; WasmResult closeStream(WasmStreamType stream_type) override; + void failStream(WasmStreamType stream_type) override; WasmResult sendLocalResponse(uint32_t response_code, absl::string_view body_text, Pairs additional_headers, uint32_t grpc_status, absl::string_view details) override; diff --git a/test/extensions/filters/http/wasm/BUILD b/test/extensions/filters/http/wasm/BUILD index 3ad408691e67c..0a3b7363af8a2 100644 --- a/test/extensions/filters/http/wasm/BUILD +++ b/test/extensions/filters/http/wasm/BUILD @@ -27,6 +27,7 @@ envoy_extension_cc_test( "//test/extensions/filters/http/wasm/test_data:body_rust.wasm", "//test/extensions/filters/http/wasm/test_data:headers_rust.wasm", "//test/extensions/filters/http/wasm/test_data:metadata_rust.wasm", + "//test/extensions/filters/http/wasm/test_data:panic_rust.wasm", "//test/extensions/filters/http/wasm/test_data:resume_call_rust.wasm", "//test/extensions/filters/http/wasm/test_data:shared_data_rust.wasm", "//test/extensions/filters/http/wasm/test_data:shared_queue_rust.wasm", diff --git a/test/extensions/filters/http/wasm/config_test.cc b/test/extensions/filters/http/wasm/config_test.cc index e80d77ec98928..35be66bca4743 100644 --- a/test/extensions/filters/http/wasm/config_test.cc +++ b/test/extensions/filters/http/wasm/config_test.cc @@ -837,9 +837,12 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteSuccessBadcode) { context->setDecoderFilterCallbacks(decoder_callbacks); EXPECT_CALL(decoder_callbacks, streamInfo()).WillRepeatedly(ReturnRef(stream_info)); - EXPECT_CALL(stream_info, setResponseCodeDetails("wasm_fail_stream")); - EXPECT_CALL(decoder_callbacks, resetStream()); - + auto headers = Http::TestResponseHeaderMapImpl{{":status", "503"}}; + EXPECT_CALL(decoder_callbacks, encodeHeaders_(HeaderMapEqualRef(&headers), true)); + EXPECT_CALL(decoder_callbacks, + sendLocalReply(Envoy::Http::Code::ServiceUnavailable, testing::Eq(""), _, + testing::Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + testing::Eq("wasm_fail_stream"))); EXPECT_EQ(context->onRequestHeaders(10, false), proxy_wasm::FilterHeadersStatus::StopAllIterationAndWatermark); } diff --git a/test/extensions/filters/http/wasm/test_data/BUILD b/test/extensions/filters/http/wasm/test_data/BUILD index 3e807289f76cb..0501db42c10b3 100644 --- a/test/extensions/filters/http/wasm/test_data/BUILD +++ b/test/extensions/filters/http/wasm/test_data/BUILD @@ -48,6 +48,15 @@ wasm_rust_binary( ], ) +wasm_rust_binary( + name = "panic_rust.wasm", + srcs = ["panic_rust.rs"], + deps = [ + "@proxy_wasm_rust_sdk//:proxy_wasm", + "@proxy_wasm_rust_sdk//bazel/cargo:log", + ], +) + wasm_rust_binary( name = "resume_call_rust.wasm", srcs = ["resume_call_rust.rs"], @@ -80,6 +89,7 @@ envoy_cc_library( srcs = [ "test_async_call_cpp.cc", "test_body_cpp.cc", + "test_close_stream_cpp.cc", "test_cpp.cc", "test_cpp_null_plugin.cc", "test_grpc_call_cpp.cc", @@ -107,9 +117,11 @@ envoy_wasm_cc_binary( srcs = [ "test_async_call_cpp.cc", "test_body_cpp.cc", + "test_close_stream_cpp.cc", "test_cpp.cc", "test_grpc_call_cpp.cc", "test_grpc_stream_cpp.cc", + "test_panic_cpp.cc", "test_resume_call_cpp.cc", "test_shared_data_cpp.cc", "test_shared_queue_cpp.cc", diff --git a/test/extensions/filters/http/wasm/test_data/panic_rust.rs b/test/extensions/filters/http/wasm/test_data/panic_rust.rs new file mode 100644 index 0000000000000..c22fa50ba2fd7 --- /dev/null +++ b/test/extensions/filters/http/wasm/test_data/panic_rust.rs @@ -0,0 +1,39 @@ +use proxy_wasm::traits::{Context, HttpContext}; +use proxy_wasm::types::*; + +#[no_mangle] +pub fn _start() { + proxy_wasm::set_http_context(|_, _| -> Box { + Box::new(TestStream{}) + }); +} + +struct TestStream {} + +impl Context for TestStream {} + +impl HttpContext for TestStream { + fn on_http_request_headers(&mut self, _: usize) -> Action { + panic!(""); + } + + fn on_http_request_body(&mut self, _: usize, _: bool) -> Action { + panic!(""); + } + + fn on_http_request_trailers(&mut self, _: usize) -> Action { + panic!(""); + } + + fn on_http_response_headers(&mut self, _: usize) -> Action { + panic!(""); + } + + fn on_http_response_body(&mut self, _: usize, _: bool) -> Action { + panic!(""); + } + + fn on_http_response_trailers(&mut self, _: usize) -> Action { + panic!(""); + } +} diff --git a/test/extensions/filters/http/wasm/test_data/test_close_stream_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_close_stream_cpp.cc new file mode 100644 index 0000000000000..7aa7c7d687a2e --- /dev/null +++ b/test/extensions/filters/http/wasm/test_data/test_close_stream_cpp.cc @@ -0,0 +1,40 @@ +// NOLINT(namespace-envoy) +#include +#include +#include + +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics_lite.h" +#else +#include "extensions/common/wasm/ext/envoy_null_plugin.h" +#endif + +START_WASM_PLUGIN(HttpWasmTestCpp) + +class CloseStreamRootContext : public RootContext { +public: + explicit CloseStreamRootContext(uint32_t id, std::string_view root_id) : RootContext(id, root_id) {} +}; + +class CloseStreamContext : public Context { +public: + explicit CloseStreamContext(uint32_t id, RootContext* root) : Context(id, root) {} + + FilterHeadersStatus onRequestHeaders(uint32_t, bool) override; + FilterHeadersStatus onResponseHeaders(uint32_t, bool) override; +}; + +static RegisterContextFactory register_CloseStreamContext(CONTEXT_FACTORY(CloseStreamContext), + ROOT_FACTORY(CloseStreamRootContext), "close_stream"); + +FilterHeadersStatus CloseStreamContext::onRequestHeaders(uint32_t, bool) { + closeRequest(); + return FilterHeadersStatus::Continue; +} + +FilterHeadersStatus CloseStreamContext::onResponseHeaders(uint32_t, bool) { + closeResponse(); + return FilterHeadersStatus::Continue; +} + +END_WASM_PLUGIN diff --git a/test/extensions/filters/http/wasm/test_data/test_panic_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_panic_cpp.cc new file mode 100644 index 0000000000000..44fa5e19f9892 --- /dev/null +++ b/test/extensions/filters/http/wasm/test_data/test_panic_cpp.cc @@ -0,0 +1,66 @@ +// NOLINT(namespace-envoy) +#include +#include +#include + +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics_lite.h" +#else +#include "extensions/common/wasm/ext/envoy_null_plugin.h" +#endif + +START_WASM_PLUGIN(HttpWasmTestCpp) + +class PanicRootContext : public RootContext { +public: + explicit PanicRootContext(uint32_t id, std::string_view root_id) : RootContext(id, root_id) {} +}; + +class PanicContext : public Context { +public: + explicit PanicContext(uint32_t id, RootContext* root) : Context(id, root) {} + + FilterHeadersStatus onRequestHeaders(uint32_t, bool) override; + FilterDataStatus onRequestBody(size_t , bool ) override; + FilterTrailersStatus onRequestTrailers(uint32_t) override; + FilterHeadersStatus onResponseHeaders(uint32_t, bool) override; + FilterDataStatus onResponseBody(size_t, bool) override; + FilterTrailersStatus onResponseTrailers(uint32_t) override; +}; + +static RegisterContextFactory register_PanicContext(CONTEXT_FACTORY(PanicContext), + ROOT_FACTORY(PanicRootContext), "panic"); + +static int* badptr = nullptr; + +FilterHeadersStatus PanicContext::onRequestHeaders(uint32_t, bool) { + *badptr = 0; + return FilterHeadersStatus::Continue; +} + +FilterHeadersStatus PanicContext::onResponseHeaders(uint32_t, bool) { + *badptr = 0; + return FilterHeadersStatus::Continue; +} + +FilterTrailersStatus PanicContext::onRequestTrailers(uint32_t) { + *badptr = 0; + return FilterTrailersStatus::Continue; +} + +FilterDataStatus PanicContext::onRequestBody(size_t , bool ) { + *badptr = 0; + return FilterDataStatus::Continue; +} + +FilterDataStatus PanicContext::onResponseBody(size_t, bool) { + *badptr = 0; + return FilterDataStatus::Continue; +} + +FilterTrailersStatus PanicContext::onResponseTrailers(uint32_t) { + *badptr = 0; + return FilterTrailersStatus::Continue; +} + +END_WASM_PLUGIN diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index adfff9ce26f4d..74f6c0ce9dbe1 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -1820,6 +1820,163 @@ TEST_P(WasmHttpFilterTest, RootId2) { filter().decodeHeaders(request_headers, true)); } +TEST_P(WasmHttpFilterTest, PanicOnRequestHeaders) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupTest("panic"); + setupFilter(); + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + + auto headers = Http::TestResponseHeaderMapImpl{{":status", "503"}}; + EXPECT_CALL(decoder_callbacks, encodeHeaders_(HeaderMapEqualRef(&headers), true)); + EXPECT_CALL(decoder_callbacks, + sendLocalReply(Envoy::Http::Code::ServiceUnavailable, testing::Eq(""), _, + testing::Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + testing::Eq("wasm_fail_stream"))); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().onRequestHeaders(0, false)); +} + +TEST_P(WasmHttpFilterTest, PanicOnRequestBody) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupTest("panic"); + setupFilter(); + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + + auto headers = Http::TestResponseHeaderMapImpl{{":status", "503"}}; + EXPECT_CALL(decoder_callbacks, encodeHeaders_(HeaderMapEqualRef(&headers), true)); + EXPECT_CALL(decoder_callbacks, + sendLocalReply(Envoy::Http::Code::ServiceUnavailable, testing::Eq(""), _, + testing::Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + testing::Eq("wasm_fail_stream"))); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterDataStatus::StopIterationNoBuffer, filter().onRequestBody(0, false)); +} + +TEST_P(WasmHttpFilterTest, PanicOnRequestTrailers) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupTest("panic"); + setupFilter(); + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + + auto headers = Http::TestResponseHeaderMapImpl{{":status", "503"}}; + EXPECT_CALL(decoder_callbacks, encodeHeaders_(HeaderMapEqualRef(&headers), true)); + EXPECT_CALL(decoder_callbacks, + sendLocalReply(Envoy::Http::Code::ServiceUnavailable, testing::Eq(""), _, + testing::Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + testing::Eq("wasm_fail_stream"))); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterTrailersStatus::StopIteration, filter().onRequestTrailers(0)); +} + +TEST_P(WasmHttpFilterTest, PanicOnResponseHeaders) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupTest("panic"); + setupFilter(); + Http::MockStreamEncoderFilterCallbacks encoder_callbacks; + filter().setEncoderFilterCallbacks(encoder_callbacks); + EXPECT_CALL(encoder_callbacks, + sendLocalReply(Envoy::Http::Code::ServiceUnavailable, testing::Eq(""), _, + testing::Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + testing::Eq("wasm_fail_stream"))); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().onResponseHeaders(0, false)); +} + +TEST_P(WasmHttpFilterTest, PanicOnResponseBody) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupTest("panic"); + setupFilter(); + Http::MockStreamEncoderFilterCallbacks encoder_callbacks; + filter().setEncoderFilterCallbacks(encoder_callbacks); + EXPECT_CALL(encoder_callbacks, + sendLocalReply(Envoy::Http::Code::ServiceUnavailable, testing::Eq(""), _, + testing::Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + testing::Eq("wasm_fail_stream"))); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterDataStatus::StopIterationNoBuffer, filter().onResponseBody(0, false)); +} + +TEST_P(WasmHttpFilterTest, PanicOnResponseTrailers) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupTest("panic"); + setupFilter(); + Http::MockStreamEncoderFilterCallbacks encoder_callbacks; + filter().setEncoderFilterCallbacks(encoder_callbacks); + EXPECT_CALL(encoder_callbacks, + sendLocalReply(Envoy::Http::Code::ServiceUnavailable, testing::Eq(""), _, + testing::Eq(Grpc::Status::WellKnownGrpcStatus::Unavailable), + testing::Eq("wasm_fail_stream"))); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterTrailersStatus::StopIteration, filter().onResponseTrailers(0)); +} + +TEST_P(WasmHttpFilterTest, CloseRequest) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(mathetake): not yet supported in the Rust SDK. + return; + } + setupTest("close_stream"); + setupFilter(); + NiceMock stream_info; + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + EXPECT_CALL(decoder_callbacks, streamInfo()).WillRepeatedly(ReturnRef(stream_info)); + EXPECT_CALL(stream_info, setResponseCodeDetails("wasm_close_stream")); + EXPECT_CALL(decoder_callbacks, resetStream()); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterHeadersStatus::Continue, filter().onRequestHeaders(0, false)); +} + +TEST_P(WasmHttpFilterTest, CloseResponse) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(mathetake): not yet supported in the Rust SDK. + return; + } + setupTest("close_stream"); + setupFilter(); + NiceMock stream_info; + Http::MockStreamEncoderFilterCallbacks encoder_callbacks; + filter().setEncoderFilterCallbacks(encoder_callbacks); + EXPECT_CALL(encoder_callbacks, streamInfo()).WillRepeatedly(ReturnRef(stream_info)); + EXPECT_CALL(stream_info, setResponseCodeDetails("wasm_close_stream")); + EXPECT_CALL(encoder_callbacks, resetStream()); + + // Create in-VM context. + filter().onCreate(); + EXPECT_EQ(proxy_wasm::FilterHeadersStatus::Continue, filter().onResponseHeaders(0, false)); +} + } // namespace Wasm } // namespace HttpFilters } // namespace Extensions diff --git a/test/extensions/filters/network/wasm/BUILD b/test/extensions/filters/network/wasm/BUILD index d974226945d71..2d4b634840909 100644 --- a/test/extensions/filters/network/wasm/BUILD +++ b/test/extensions/filters/network/wasm/BUILD @@ -46,6 +46,7 @@ envoy_extension_cc_test( "//test/extensions/filters/network/wasm/test_data:test_cpp.wasm", ]) + envoy_select_wasm_rust_tests([ "//test/extensions/filters/network/wasm/test_data:logging_rust.wasm", + "//test/extensions/filters/network/wasm/test_data:panic_rust.wasm", ]), extension_name = "envoy.filters.network.wasm", deps = [ diff --git a/test/extensions/filters/network/wasm/test_data/BUILD b/test/extensions/filters/network/wasm/test_data/BUILD index 2ab2622650bc2..02fbb6028c224 100644 --- a/test/extensions/filters/network/wasm/test_data/BUILD +++ b/test/extensions/filters/network/wasm/test_data/BUILD @@ -18,9 +18,19 @@ wasm_rust_binary( ], ) +wasm_rust_binary( + name = "panic_rust.wasm", + srcs = ["panic_rust.rs"], + deps = [ + "@proxy_wasm_rust_sdk//:proxy_wasm", + "@proxy_wasm_rust_sdk//bazel/cargo:log", + ], +) + envoy_cc_library( name = "test_cpp_plugin", srcs = [ + "test_close_stream_cpp.cc", "test_cpp.cc", "test_cpp_null_plugin.cc", "test_resume_call_cpp.cc", @@ -39,7 +49,9 @@ envoy_cc_library( envoy_wasm_cc_binary( name = "test_cpp.wasm", srcs = [ + "test_close_stream_cpp.cc", "test_cpp.cc", + "test_panic_cpp.cc", "test_resume_call_cpp.cc", ], deps = [ diff --git a/test/extensions/filters/network/wasm/test_data/panic_rust.rs b/test/extensions/filters/network/wasm/test_data/panic_rust.rs new file mode 100644 index 0000000000000..d994650e4166b --- /dev/null +++ b/test/extensions/filters/network/wasm/test_data/panic_rust.rs @@ -0,0 +1,27 @@ +use proxy_wasm::traits::{Context, StreamContext}; +use proxy_wasm::types::*; + +#[no_mangle] +pub fn _start() { + proxy_wasm::set_stream_context(|_, _| -> Box { + Box::new(TestStream {}) + }); +} + +struct TestStream {} + +impl Context for TestStream {} + +impl StreamContext for TestStream { + fn on_new_connection(&mut self) -> Action { + panic!(""); + } + + fn on_downstream_data(&mut self, _: usize, _: bool) -> Action { + panic!(""); + } + + fn on_upstream_data(&mut self, _: usize, _: bool) -> Action { + panic!(""); + } +} diff --git a/test/extensions/filters/network/wasm/test_data/test_close_stream_cpp.cc b/test/extensions/filters/network/wasm/test_data/test_close_stream_cpp.cc new file mode 100644 index 0000000000000..a45dccca1bc0b --- /dev/null +++ b/test/extensions/filters/network/wasm/test_data/test_close_stream_cpp.cc @@ -0,0 +1,37 @@ +// NOLINT(namespace-envoy) +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics.h" +#else +#include "include/proxy-wasm/null_plugin.h" +#endif + +START_WASM_PLUGIN(NetworkTestCpp) + +class CloseStreamContext : public Context { +public: + explicit CloseStreamContext(uint32_t id, RootContext* root) : Context(id, root) {} + FilterStatus onDownstreamData(size_t data_length, bool end_stream) override; + FilterStatus onUpstreamData(size_t data_length, bool end_stream) override; +}; + +class CloseStreamRootContext : public RootContext { +public: + explicit CloseStreamRootContext(uint32_t id, std::string_view root_id) + : RootContext(id, root_id) {} +}; + +static RegisterContextFactory register_CloseStreamContext(CONTEXT_FACTORY(CloseStreamContext), + ROOT_FACTORY(CloseStreamRootContext), + "close_stream"); + +FilterStatus CloseStreamContext::onDownstreamData(size_t, bool) { + closeDownstream(); + return FilterStatus::Continue; +} + +FilterStatus CloseStreamContext::onUpstreamData(size_t, bool) { + closeUpstream(); + return FilterStatus::Continue; +} + +END_WASM_PLUGIN diff --git a/test/extensions/filters/network/wasm/test_data/test_panic_cpp.cc b/test/extensions/filters/network/wasm/test_data/test_panic_cpp.cc new file mode 100644 index 0000000000000..695f8d0eff0cc --- /dev/null +++ b/test/extensions/filters/network/wasm/test_data/test_panic_cpp.cc @@ -0,0 +1,43 @@ +// NOLINT(namespace-envoy) +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics.h" +#else +#include "include/proxy-wasm/null_plugin.h" +#endif + +START_WASM_PLUGIN(NetworkTestCpp) + +class PanicContext : public Context { +public: + explicit PanicContext(uint32_t id, RootContext* root) : Context(id, root) {} + FilterStatus onNewConnection() override; + FilterStatus onDownstreamData(size_t data_length, bool end_stream) override; + FilterStatus onUpstreamData(size_t data_length, bool end_stream) override; +}; + +class PanicRootContext : public RootContext { +public: + explicit PanicRootContext(uint32_t id, std::string_view root_id) + : RootContext(id, root_id) {} +}; + +static RegisterContextFactory register_PanicContext(CONTEXT_FACTORY(PanicContext), + ROOT_FACTORY(PanicRootContext), "panic"); + +static int* badptr = nullptr; +FilterStatus PanicContext::onNewConnection() { + *badptr = 0; + return FilterStatus::Continue; +} + +FilterStatus PanicContext::onDownstreamData(size_t, bool) { + *badptr = 0; + return FilterStatus::Continue; +} + +FilterStatus PanicContext::onUpstreamData(size_t, bool) { + *badptr = 0; + return FilterStatus::Continue; +} + +END_WASM_PLUGIN diff --git a/test/extensions/filters/network/wasm/wasm_filter_test.cc b/test/extensions/filters/network/wasm/wasm_filter_test.cc index 6977de806d294..f0ab349395019 100644 --- a/test/extensions/filters/network/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/network/wasm/wasm_filter_test.cc @@ -1,3 +1,4 @@ +#include "envoy/network/transport_socket.h" #include "envoy/server/lifecycle_notifier.h" #include "extensions/common/wasm/wasm.h" @@ -263,8 +264,13 @@ TEST_P(WasmNetworkFilterTest, RestrictLog) { // Do not expect this call, because proxy_log is not allowed EXPECT_CALL(filter(), log_(spdlog::level::trace, Eq(absl::string_view("onNewConnection 2")))) .Times(0); - EXPECT_EQ(Network::FilterStatus::Continue, filter().onNewConnection()); - + if (std::get<1>(GetParam()) == "rust") { + // Rust code panics on WasmResult::InternalFailure returned by restricted calls, and + // that eventually results in calling failStream on post VM failure check after onNewConnection. + EXPECT_EQ(Network::FilterStatus::StopIteration, filter().onNewConnection()); + } else { + EXPECT_EQ(Network::FilterStatus::Continue, filter().onNewConnection()); + } // Do not expect this call, because proxy_log is not allowed EXPECT_CALL(filter(), log_(spdlog::level::trace, Eq(absl::string_view("onDownstreamConnectionClose 2 1")))) @@ -370,6 +376,97 @@ TEST_P(WasmNetworkFilterTest, StopAndResumeUpstreamViaAsyncCall) { EXPECT_NE(callbacks, nullptr); } +TEST_P(WasmNetworkFilterTest, PanicOnNewConnection) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupConfig("", "panic"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::critical, _)) + .Times(testing::AtMost(1)); // Rust logs on panic. + EXPECT_EQ(read_filter_callbacks_.connection().state(), Network::Connection::State::Open); + EXPECT_EQ(write_filter_callbacks_.connection().state(), Network::Connection::State::Open); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter().onNewConnection()); + + // Should close both up and down streams. + EXPECT_EQ(read_filter_callbacks_.connection().state(), Network::Connection::State::Closed); + EXPECT_EQ(write_filter_callbacks_.connection().state(), Network::Connection::State::Closed); +} + +TEST_P(WasmNetworkFilterTest, PanicOnDownstreamData) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupConfig("", "panic"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::critical, _)) + .Times(testing::AtMost(1)); // Rust logs on panic. + EXPECT_EQ(read_filter_callbacks_.connection().state(), Network::Connection::State::Open); + EXPECT_EQ(write_filter_callbacks_.connection().state(), Network::Connection::State::Open); + Buffer::OwnedImpl fake_downstream_data("Fake"); + filter().onCreate(); // Create context without calling OnNewConnection. + EXPECT_EQ(Network::FilterStatus::StopIteration, filter().onData(fake_downstream_data, false)); + + // Should close both up and down streams. + EXPECT_EQ(read_filter_callbacks_.connection().state(), Network::Connection::State::Closed); + EXPECT_EQ(write_filter_callbacks_.connection().state(), Network::Connection::State::Closed); +} + +TEST_P(WasmNetworkFilterTest, PanicOnUpstreamData) { + if (std::get<0>(GetParam()) == "null") { + return; + } + setupConfig("", "panic"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::critical, _)) + .Times(testing::AtMost(1)); // Rust logs on panic. + EXPECT_EQ(read_filter_callbacks_.connection().state(), Network::Connection::State::Open); + EXPECT_EQ(write_filter_callbacks_.connection().state(), Network::Connection::State::Open); + Buffer::OwnedImpl fake_downstream_data("Fake"); + filter().onCreate(); // Create context without calling OnNewConnection. + EXPECT_EQ(Network::FilterStatus::StopIteration, filter().onWrite(fake_downstream_data, false)); + + // Should close both up and down streams. + EXPECT_EQ(read_filter_callbacks_.connection().state(), Network::Connection::State::Closed); + EXPECT_EQ(write_filter_callbacks_.connection().state(), Network::Connection::State::Closed); +} + +TEST_P(WasmNetworkFilterTest, CloseDownstream) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(mathetake): not yet supported in the Rust SDK. + return; + } + setupConfig("", "close_stream"); + setupFilter(); + EXPECT_EQ(read_filter_callbacks_.connection().state(), Network::Connection::State::Open); + EXPECT_EQ(write_filter_callbacks_.connection().state(), Network::Connection::State::Open); + Buffer::OwnedImpl fake_downstream_data("Fake"); + filter().onCreate(); // Create context without calling OnNewConnection. + EXPECT_EQ(Network::FilterStatus::Continue, filter().onWrite(fake_downstream_data, false)); + + // Should close downstream. + EXPECT_EQ(read_filter_callbacks_.connection().state(), Network::Connection::State::Open); + EXPECT_EQ(write_filter_callbacks_.connection().state(), Network::Connection::State::Closed); +} + +TEST_P(WasmNetworkFilterTest, CloseUpstream) { + if (std::get<1>(GetParam()) == "rust") { + // TODO(mathetake): not yet supported in the Rust SDK. + return; + } + setupConfig("", "close_stream"); + setupFilter(); + EXPECT_EQ(read_filter_callbacks_.connection().state(), Network::Connection::State::Open); + EXPECT_EQ(write_filter_callbacks_.connection().state(), Network::Connection::State::Open); + Buffer::OwnedImpl fake_upstream_data("Fake"); + filter().onCreate(); // Create context without calling OnNewConnection. + EXPECT_EQ(Network::FilterStatus::Continue, filter().onData(fake_upstream_data, false)); + + // Should close downstream. + EXPECT_EQ(read_filter_callbacks_.connection().state(), Network::Connection::State::Closed); + EXPECT_EQ(write_filter_callbacks_.connection().state(), Network::Connection::State::Open); +} + } // namespace Wasm } // namespace NetworkFilters } // namespace Extensions From 338b7b742cc6df1c807cb29d5a8c4027f46f93bd Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Tue, 4 May 2021 17:41:19 -0700 Subject: [PATCH 146/209] Win32 docs FAQ (#16176) * Windows FAQ Entries Authored-by: Sotiris Nanopoulos --- docs/root/api-v3/config/trace/trace.rst | 2 + docs/root/api-v3/config/watchdog/watchdog.rst | 2 + docs/root/faq/overview.rst | 14 ++++++ docs/root/faq/windows/win_fips_support.rst | 7 +++ .../windows/win_not_supported_features.rst | 14 ++++++ docs/root/faq/windows/win_performance.rst | 8 ++++ docs/root/faq/windows/win_requirements.rst | 9 ++++ docs/root/faq/windows/win_run_as_service.rst | 47 +++++++++++++++++++ docs/root/faq/windows/win_security.rst | 4 ++ 9 files changed, 107 insertions(+) create mode 100644 docs/root/faq/windows/win_fips_support.rst create mode 100644 docs/root/faq/windows/win_not_supported_features.rst create mode 100644 docs/root/faq/windows/win_performance.rst create mode 100644 docs/root/faq/windows/win_requirements.rst create mode 100644 docs/root/faq/windows/win_run_as_service.rst create mode 100644 docs/root/faq/windows/win_security.rst diff --git a/docs/root/api-v3/config/trace/trace.rst b/docs/root/api-v3/config/trace/trace.rst index 886eaef55efe9..b31b60a1df99f 100644 --- a/docs/root/api-v3/config/trace/trace.rst +++ b/docs/root/api-v3/config/trace/trace.rst @@ -1,3 +1,5 @@ +.. _http_tracers: + HTTP Tracers ============== diff --git a/docs/root/api-v3/config/watchdog/watchdog.rst b/docs/root/api-v3/config/watchdog/watchdog.rst index 6f6dabfb73531..8a8ab843cb0e2 100644 --- a/docs/root/api-v3/config/watchdog/watchdog.rst +++ b/docs/root/api-v3/config/watchdog/watchdog.rst @@ -1,3 +1,5 @@ +.. _watchdog_api_reference: + Watchdog ======== diff --git a/docs/root/faq/overview.rst b/docs/root/faq/overview.rst index 935cbf9f9347f..33d8f906f0eaf 100644 --- a/docs/root/faq/overview.rst +++ b/docs/root/faq/overview.rst @@ -81,3 +81,17 @@ Extensions :maxdepth: 2 extensions/contract + +Windows +------- + +.. toctree:: + :maxdepth: 2 + + windows/win_requirements + windows/win_not_supported_features + windows/win_fips_support + windows/win_performance + windows/win_security + windows/win_run_as_service + diff --git a/docs/root/faq/windows/win_fips_support.rst b/docs/root/faq/windows/win_fips_support.rst new file mode 100644 index 0000000000000..ed23af85d5fac --- /dev/null +++ b/docs/root/faq/windows/win_fips_support.rst @@ -0,0 +1,7 @@ +Does Envoy on Windows support FIPS? +=================================== + +Envoy uses `BoringSSL `_ which is a slimmed down TLS implementation. At the time of writing, +BoringSSL does not support a FIPS mode on Windows. As a result, Envoy does not offer support for FIPS on Windows. + +If FIPS is a requirement for you, please reach out to the contributors. diff --git a/docs/root/faq/windows/win_not_supported_features.rst b/docs/root/faq/windows/win_not_supported_features.rst new file mode 100644 index 0000000000000..21685e8c97b0b --- /dev/null +++ b/docs/root/faq/windows/win_not_supported_features.rst @@ -0,0 +1,14 @@ +Which Envoy features are not supported on Windows? +================================================== + +The vast majority of Envoy features are supported on Windows. There are few exceptions that are documented explicitly. +The most notable features that are not supported on Windows are: + +* :ref:`Watchdog ` +* :ref:`Tracers ` +* :ref:`Original Src HTTP Filter `. +* :ref:`Hot restart ` + +There are certain Envoy features that require newer versions of Windows. These features explicitly document the required version. + +We will continue adding support for the missing features over time and define a roadmap to bring platform support to parity with Linux. diff --git a/docs/root/faq/windows/win_performance.rst b/docs/root/faq/windows/win_performance.rst new file mode 100644 index 0000000000000..180238cc10ed1 --- /dev/null +++ b/docs/root/faq/windows/win_performance.rst @@ -0,0 +1,8 @@ +How fast is Envoy on Windows? +============================= + +Everything that is mentioned in :ref:`How fast is Envoy? ` applies to Windows. We have +done some work to improve the event loop on Windows. That being said, we have observed that the tail performance of Envoy on Windows +tends to be worse compared to Linux, especially when TLS is involved. + +We are actively investigating performance and aim to improve on a continuous basis. diff --git a/docs/root/faq/windows/win_requirements.rst b/docs/root/faq/windows/win_requirements.rst new file mode 100644 index 0000000000000..fa808d3fb3077 --- /dev/null +++ b/docs/root/faq/windows/win_requirements.rst @@ -0,0 +1,9 @@ +What are the requirements to run on Envoy on Windows? +===================================================== + +Envoy is tested on Windows Server Core 2019 (Long-Term Servicing Channel). This corresponds to OS version 10.0.17763.1879. We have also tested a few more recent versions of Windows Server +and in general higher versions will be fully supported. For more info please see `Windows Server Core `_. +To build Envoy from source you will need to have at least Windows 10 SDK, version 1803 (10.0.17134.12). +Earlier versions will not compile because the ``afunix.h`` header is not available. + +There might be Envoy features that require newer versions of Windows. These features will explicitly document the required version. diff --git a/docs/root/faq/windows/win_run_as_service.rst b/docs/root/faq/windows/win_run_as_service.rst new file mode 100644 index 0000000000000..9458d901cdcb0 --- /dev/null +++ b/docs/root/faq/windows/win_run_as_service.rst @@ -0,0 +1,47 @@ +Can I run Envoy on Windows under SCM? +===================================== + +.. note:: + + This feature is still in Experimental state. + + +You can start Envoy as Windows Service that is managed under `Windows Service Control Manager `_. +First, you need to create the service. Assuming you have a custom configuration in the current directory named ``envoy-custom.yaml``. After you create the service you +can start it. + +From an **administrator** prompt run the following commands (note that you need replace C:\EnvoyProxy\ with the path to the envoy.exe binary and the config file): + +.. code-block:: console + + > sc create EnvoyProxy binpath="C:\EnvoyProxy\envoy.exe --config-path C:\EnvoyProxy\envoy-demo.yaml" start=auto depend=Tcpip/Afd + [SC] CreateService SUCCESS + > sc start EnvoyProxy + SERVICE_NAME: envoyproxy + TYPE : 10 WIN32_OWN_PROCESS + STATE : 2 START_PENDING + (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN) + WIN32_EXIT_CODE : 0 (0x0) + SERVICE_EXIT_CODE : 0 (0x0) + CHECKPOINT : 0x0 + WAIT_HINT : 0x7d0 + PID : 3924 + FLAGS : + > sc query EnvoyProxy + SERVICE_NAME: envoyproxy + TYPE : 10 WIN32_OWN_PROCESS + STATE : 4 RUNNING + (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN) + WIN32_EXIT_CODE : 0 (0x0) + SERVICE_EXIT_CODE : 0 (0x0) + CHECKPOINT : 0x0 + WAIT_HINT : 0x0 + ... + + +Use `sc.exe `_ to configure the service startup and error handling. + +.. tip:: + + The output of ``sc query envoyproxy`` contains the exit code of Envoy Proxy. In case the arguments are invalid we set it to ``E_INVALIDARG``. For more information + Envoy is reporting startup failures with error messages on Windows Event Viewer. diff --git a/docs/root/faq/windows/win_security.rst b/docs/root/faq/windows/win_security.rst new file mode 100644 index 0000000000000..7eb9a653cc065 --- /dev/null +++ b/docs/root/faq/windows/win_security.rst @@ -0,0 +1,4 @@ +What is the security release process? +===================================== + +We follow the process described at `Security Reporting Process `_. From 95bd93e9cd26938f3d54ea5718d0e2291e2484de Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Wed, 5 May 2021 13:36:00 +0900 Subject: [PATCH 147/209] skywalking: skip gRPC cluster validation by default (#16203) Signed-off-by: Rei Shimizu --- source/extensions/tracers/skywalking/skywalking_tracer_impl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc index 92e17d54966f7..fa40d5469717e 100644 --- a/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc +++ b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc @@ -37,7 +37,7 @@ Driver::Driver(const envoy::config::trace::v3::SkyWalkingConfig& proto_config, tls_slot_ptr_->set([proto_config, &factory_context, this](Event::Dispatcher& dispatcher) { TracerPtr tracer = std::make_unique(std::make_unique( factory_context.clusterManager().grpcAsyncClientManager().factoryForGrpcService( - proto_config.grpc_service(), factory_context.scope(), false), + proto_config.grpc_service(), factory_context.scope(), true), dispatcher, factory_context.api().randomGenerator(), tracing_stats_, config_.delayed_buffer_size(), config_.token())); return std::make_shared(std::move(tracer)); From 7dc20d44ab5b2cc2498c02b0df3301e3f6d0eec4 Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Wed, 5 May 2021 13:38:46 +0900 Subject: [PATCH 148/209] skywalking: fix string_view UB while span reporting (#16264) Signed-off-by: Shikugawa --- source/extensions/tracers/skywalking/tracer.cc | 8 ++++---- test/extensions/tracers/skywalking/tracer_test.cc | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/source/extensions/tracers/skywalking/tracer.cc b/source/extensions/tracers/skywalking/tracer.cc index 437a558dccf33..bcca619fb886c 100644 --- a/source/extensions/tracers/skywalking/tracer.cc +++ b/source/extensions/tracers/skywalking/tracer.cc @@ -18,14 +18,14 @@ const Http::LowerCaseString& skywalkingPropagationHeaderKey() { void Span::setTag(absl::string_view name, absl::string_view value) { if (name == Tracing::Tags::get().HttpUrl) { - span_entity_->addTag(UrlTag.data(), value.data()); + span_entity_->addTag(UrlTag.data(), std::string(value)); } else if (name == Tracing::Tags::get().HttpStatusCode) { - span_entity_->addTag(StatusCodeTag.data(), value.data()); + span_entity_->addTag(StatusCodeTag.data(), std::string(value)); } else if (name == Tracing::Tags::get().Error) { span_entity_->setErrorStatus(); - span_entity_->addTag(name.data(), value.data()); + span_entity_->addTag(std::string(name), std::string(value)); } else { - span_entity_->addTag(name.data(), value.data()); + span_entity_->addTag(std::string(name), std::string(value)); } } diff --git a/test/extensions/tracers/skywalking/tracer_test.cc b/test/extensions/tracers/skywalking/tracer_test.cc index 75522ab296b61..26b68d4c73fa0 100644 --- a/test/extensions/tracers/skywalking/tracer_test.cc +++ b/test/extensions/tracers/skywalking/tracer_test.cc @@ -1,3 +1,5 @@ +#include "common/tracing/http_tracer_impl.h" + #include "extensions/tracers/skywalking/tracer.h" #include "test/extensions/tracers/skywalking/skywalking_test_helper.h" @@ -122,6 +124,11 @@ TEST_F(TracerTest, TracerTestCreateNewSpanWithNoPropagationHeaders) { EXPECT_EQ(1, span->spanEntity()->logs().size()); EXPECT_LT(0, span->spanEntity()->logs().at(0).time()); EXPECT_EQ("abc", span->spanEntity()->logs().at(0).data().at(0).value()); + + absl::string_view sample{"GETxx"}; + sample.remove_suffix(2); + span->setTag(Tracing::Tags::get().HttpMethod, sample); + EXPECT_EQ("GET", span->spanEntity()->tags().at(5).second); } { From efa7c884620667385eb94e3f00c56d9c898e9feb Mon Sep 17 00:00:00 2001 From: Eleonora Kiziv <37271985+ekiziv@users.noreply.github.com> Date: Wed, 5 May 2021 08:16:32 -0400 Subject: [PATCH 149/209] http::cache: Adds serving HEAD requests from cache. (#15910) * http::cache: Adds serving HEAD requests from cache. Relevant issue: https://github.com/envoyproxy/envoy/issues/10020 Signed-off-by: Eleonora Kiziv Commit Message: Adds serving HEAD requests from cache. Does not add storing HEAD responses in cache. Additional Description: Relevant issue: #10020 Risk Level: Low Testing: Unit, integration Signed-off-by: Eleonora Kiziv --- docs/root/version_history/current.rst | 2 + .../filters/http/cache/cache_filter.cc | 22 +- .../filters/http/cache/cache_filter.h | 1 + .../filters/http/cache/cacheability_utils.cc | 4 +- .../filters/http/cache/cacheability_utils.h | 10 +- .../cache/cache_filter_integration_test.cc | 477 +++++++++++++----- .../http/cache/cacheability_utils_test.cc | 48 +- 7 files changed, 396 insertions(+), 168 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 732d412372c50..c3e20c37564e0 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -17,10 +17,12 @@ Minor Behavior Changes (require upstream 1xx or 204 responses to not have Transfer-Encoding or non-zero Content-Length headers) and ``envoy.reloadable_features.send_strict_1xx_and_204_response_headers`` (do not send 1xx or 204 responses with these headers). Both are true by default. +* http: serve HEAD requests from cache. * listener: respect the :ref:`connection balance config ` defined within the listener where the sockets are redirected to. Clear that field to restore the previous behavior. + Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/source/extensions/filters/http/cache/cache_filter.cc b/source/extensions/filters/http/cache/cache_filter.cc index a9719da0ddf45..8eea39694faf9 100644 --- a/source/extensions/filters/http/cache/cache_filter.cc +++ b/source/extensions/filters/http/cache/cache_filter.cc @@ -56,7 +56,7 @@ Http::FilterHeadersStatus CacheFilter::decodeHeaders(Http::RequestHeaderMap& hea *decoder_callbacks_, headers); return Http::FilterHeadersStatus::Continue; } - if (!CacheabilityUtils::isCacheableRequest(headers)) { + if (!CacheabilityUtils::canServeRequestFromCache(headers)) { ENVOY_STREAM_LOG(debug, "CacheFilter::decodeHeaders ignoring uncacheable request: {}", *decoder_callbacks_, headers); return Http::FilterHeadersStatus::Continue; @@ -65,6 +65,7 @@ Http::FilterHeadersStatus CacheFilter::decodeHeaders(Http::RequestHeaderMap& hea LookupRequest lookup_request(headers, time_source_.systemTime(), vary_allow_list_); request_allows_inserts_ = !lookup_request.requestCacheControl().no_store_; + is_head_request_ = headers.getMethodValue() == Http::Headers::get().MethodValues.Head; lookup_ = cache_.makeLookupContext(std::move(lookup_request)); ASSERT(lookup_); @@ -91,12 +92,17 @@ Http::FilterHeadersStatus CacheFilter::encodeHeaders(Http::ResponseHeaderMap& he if (filter_state_ == FilterState::ValidatingCachedResponse && isResponseNotModified(headers)) { processSuccessfulValidation(headers); // Stop the encoding stream until the cached response is fetched & added to the encoding stream. - return Http::FilterHeadersStatus::StopIteration; + if (is_head_request_) { + // Return since HEAD requests are not cached + return Http::FilterHeadersStatus::Continue; + } else { + return Http::FilterHeadersStatus::StopIteration; + } } // Either a cache miss or a cache entry that is no longer valid. // Check if the new response can be cached. - if (request_allows_inserts_ && + if (request_allows_inserts_ && !is_head_request_ && CacheabilityUtils::isCacheableResponse(headers, vary_allow_list_)) { ENVOY_STREAM_LOG(debug, "CacheFilter::encodeHeaders inserting headers", *encoder_callbacks_); insert_ = cache_.makeInsertContext(std::move(lookup_)); @@ -445,7 +451,8 @@ void CacheFilter::encodeCachedResponse() { "does not point to a cache lookup result"); response_has_trailers_ = lookup_result_->has_trailers_; - const bool end_stream = (lookup_result_->content_length_ == 0 && !response_has_trailers_); + const bool end_stream = + (lookup_result_->content_length_ == 0 && !response_has_trailers_) || is_head_request_; // Set appropriate response flags and codes. Http::StreamFilterCallbacks* callbacks = @@ -463,8 +470,11 @@ void CacheFilter::encodeCachedResponse() { decoder_callbacks_->encodeHeaders(std::move(lookup_result_->headers_), end_stream, CacheResponseCodeDetails::get().ResponseFromCacheFilter); } - - if (lookup_result_->content_length_ > 0) { + if (filter_state_ == FilterState::EncodeServingFromCache && is_head_request_) { + filter_state_ = FilterState::ResponseServedFromCache; + return; + } + if (lookup_result_->content_length_ > 0 && !is_head_request_) { // No range has been added, so we add entire body to the response. if (remaining_ranges_.empty()) { remaining_ranges_.emplace_back(0, lookup_result_->content_length_); diff --git a/source/extensions/filters/http/cache/cache_filter.h b/source/extensions/filters/http/cache/cache_filter.h index 935128e8154b6..360c91fa816c7 100644 --- a/source/extensions/filters/http/cache/cache_filter.h +++ b/source/extensions/filters/http/cache/cache_filter.h @@ -122,6 +122,7 @@ class CacheFilter : public Http::PassThroughFilter, }; FilterState filter_state_ = FilterState::Initial; + bool is_head_request_ = false; }; using CacheFilterSharedPtr = std::shared_ptr; diff --git a/source/extensions/filters/http/cache/cacheability_utils.cc b/source/extensions/filters/http/cache/cacheability_utils.cc index 45057c0dfc95e..a4c0d8a0c6737 100644 --- a/source/extensions/filters/http/cache/cacheability_utils.cc +++ b/source/extensions/filters/http/cache/cacheability_utils.cc @@ -32,7 +32,7 @@ const std::vector& conditionalHeaders() { } } // namespace -bool CacheabilityUtils::isCacheableRequest(const Http::RequestHeaderMap& headers) { +bool CacheabilityUtils::canServeRequestFromCache(const Http::RequestHeaderMap& headers) { const absl::string_view method = headers.getMethodValue(); const absl::string_view forwarded_proto = headers.getForwardedProtoValue(); const Http::HeaderValues& header_values = Http::Headers::get(); @@ -52,7 +52,7 @@ bool CacheabilityUtils::isCacheableRequest(const Http::RequestHeaderMap& headers // Cache-related headers are checked in HttpCache::LookupRequest. return headers.Path() && headers.Host() && !headers.getInline(CacheCustomHeaders::authorization()) && - (method == header_values.MethodValues.Get) && + (method == header_values.MethodValues.Get || method == header_values.MethodValues.Head) && (forwarded_proto == header_values.SchemeValues.Http || forwarded_proto == header_values.SchemeValues.Https); } diff --git a/source/extensions/filters/http/cache/cacheability_utils.h b/source/extensions/filters/http/cache/cacheability_utils.h index 88c2b1cd75fee..d968c3261cf07 100644 --- a/source/extensions/filters/http/cache/cacheability_utils.h +++ b/source/extensions/filters/http/cache/cacheability_utils.h @@ -15,13 +15,15 @@ class CacheabilityUtils { // This does not depend on cache-control headers as // request cache-control headers only decide whether // validation is required and whether the response can be cached. - static bool isCacheableRequest(const Http::RequestHeaderMap& headers); + static bool canServeRequestFromCache(const Http::RequestHeaderMap& headers); // Checks if a response can be stored in cache. - // Note that if a request is not cacheable according to 'isCacheableRequest' + // Note that if a request is not cacheable according to 'canServeRequestFromCache' // then its response is also not cacheable. - // Therefore, isCacheableRequest, isCacheableResponse and CacheFilter::request_allows_inserts_ - // together should cover https://httpwg.org/specs/rfc7234.html#response.cacheability. + // Therefore, canServeRequestFromCache, isCacheableResponse and + // CacheFilter::request_allows_inserts_ together should cover + // https://httpwg.org/specs/rfc7234.html#response.cacheability. Head requests are not + // cacheable. However, this function is never called for head requests. static bool isCacheableResponse(const Http::ResponseHeaderMap& headers, const VaryHeader& vary_allow_list); }; diff --git a/test/extensions/filters/http/cache/cache_filter_integration_test.cc b/test/extensions/filters/http/cache/cache_filter_integration_test.cc index e72bff19e7d8e..7421ec6a44f0b 100644 --- a/test/extensions/filters/http/cache/cache_filter_integration_test.cc +++ b/test/extensions/filters/http/cache/cache_filter_integration_test.cc @@ -1,3 +1,8 @@ +#include +#include + +#include "envoy/common/optref.h" + #include "test/integration/http_protocol_integration.h" #include "test/test_common/simulated_time_system.h" @@ -15,6 +20,13 @@ namespace { class CacheIntegrationTest : public Event::TestUsingSimulatedTime, public HttpProtocolIntegrationTest { public: + void SetUp() override { + useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS%"); + // Set system time to cause Envoy's cached formatted time to match time on this thread. + simTime().setSystemTime(std::chrono::hours(1)); + initializeFilter(default_config); + } + void TearDown() override { cleanupUpstreamAndDownstream(); HttpProtocolIntegrationTest::TearDown(); @@ -26,6 +38,51 @@ class CacheIntegrationTest : public Event::TestUsingSimulatedTime, codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); } + Http::TestRequestHeaderMapImpl httpRequestHeader(std::string method, std::string authority) { + return {{":method", method}, + {":path", absl::StrCat("/", protocolTestParamsToString({GetParam(), 0}))}, + {":scheme", "http"}, + {":authority", authority}}; + } + + Http::TestResponseHeaderMapImpl httpResponseHeadersForBody( + const std::string& body, const std::string& cache_control = "public,max-age=3600", + std::initializer_list> extra_headers = {}) { + Http::TestResponseHeaderMapImpl response = {{":status", "200"}, + {"date", formatter_.now(simTime())}, + {"cache-control", cache_control}, + {"content-length", std::to_string(body.size())}}; + for (auto& header : extra_headers) { + response.addCopy(header.first, header.second); + } + return response; + } + + IntegrationStreamDecoderPtr sendHeaderOnlyRequestAwaitResponse( + const Http::TestRequestHeaderMapImpl& headers, + std::function simulate_upstream = []() {}) { + IntegrationStreamDecoderPtr response_decoder = codec_client_->makeHeaderOnlyRequest(headers); + simulate_upstream(); + // Wait for the response to be read by the codec client. + EXPECT_TRUE(response_decoder->waitForEndStream()); + EXPECT_TRUE(response_decoder->complete()); + return response_decoder; + } + + std::function simulateUpstreamResponse(const Http::TestResponseHeaderMapImpl& headers, + OptRef body) { + return [this, headers = std::move(headers), body = std::move(body)]() { + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(headers, /*end_stream=*/!body); + if (body.has_value()) { + upstream_request_->encodeData(body.ref(), /*end_stream=*/true); + } + }; + } + std::function serveFromCache() { + return []() {}; + }; + const std::string default_config{R"EOF( name: "envoy.filters.http.cache" typed_config: @@ -41,38 +98,18 @@ INSTANTIATE_TEST_SUITE_P(Protocols, CacheIntegrationTest, HttpProtocolIntegrationTest::protocolTestParamsToString); TEST_P(CacheIntegrationTest, MissInsertHit) { - useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS%"); - // Set system time to cause Envoy's cached formatted time to match time on this thread. - simTime().setSystemTime(std::chrono::hours(1)); - initializeFilter(default_config); - // Include test name and params in URL to make each test's requests unique. - const Http::TestRequestHeaderMapImpl request_headers = { - {":method", "GET"}, - {":path", absl::StrCat("/", protocolTestParamsToString({GetParam(), 0}))}, - {":scheme", "http"}, - {":authority", "MissInsertHit"}}; - + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("GET", /*authority=*/"MissInsertHit"); const std::string response_body(42, 'a'); - Http::TestResponseHeaderMapImpl response_headers = { - {":status", "200"}, - {"date", formatter_.now(simTime())}, - {"cache-control", "public,max-age=3600"}, - {"content-length", std::to_string(response_body.size())}}; + Http::TestResponseHeaderMapImpl response_headers = httpResponseHeadersForBody(response_body); // Send first request, and get response from upstream. { - IntegrationStreamDecoderPtr response_decoder = - codec_client_->makeHeaderOnlyRequest(request_headers); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(response_headers, /*end_stream=*/false); - // send 42 'a's - upstream_request_->encodeData(response_body, /*end_stream=*/true); - // Wait for the response to be read by the codec client. - ASSERT_TRUE(response_decoder->waitForEndStream()); - EXPECT_TRUE(response_decoder->complete()); + IntegrationStreamDecoderPtr response_decoder = sendHeaderOnlyRequestAwaitResponse( + request_headers, simulateUpstreamResponse(response_headers, makeOptRef(response_body))); EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); - EXPECT_TRUE(response_decoder->headers().get(Http::CustomHeaders::get().Age).empty()); + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); EXPECT_EQ(response_decoder->body(), response_body); EXPECT_THAT(waitForAccessLog(access_log_name_), testing::HasSubstr("- via_upstream")); } @@ -83,9 +120,7 @@ TEST_P(CacheIntegrationTest, MissInsertHit) { // Send second request, and get response from cache. { IntegrationStreamDecoderPtr response_decoder = - codec_client_->makeHeaderOnlyRequest(request_headers); - ASSERT_TRUE(response_decoder->waitForEndStream()); - EXPECT_TRUE(response_decoder->complete()); + sendHeaderOnlyRequestAwaitResponse(request_headers, serveFromCache()); EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); EXPECT_EQ(response_decoder->body(), response_body); EXPECT_THAT(response_decoder->headers(), @@ -98,39 +133,19 @@ TEST_P(CacheIntegrationTest, MissInsertHit) { } TEST_P(CacheIntegrationTest, ExpiredValidated) { - useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS%"); - // Set system time to cause Envoy's cached formatted time to match time on this thread. - simTime().setSystemTime(std::chrono::hours(1)); - initializeFilter(default_config); - // Include test name and params in URL to make each test's requests unique. - const Http::TestRequestHeaderMapImpl request_headers = { - {":method", "GET"}, - {":path", absl::StrCat("/", protocolTestParamsToString({GetParam(), 0}))}, - {":scheme", "http"}, - {":authority", "ExpiredValidated"}}; - + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("GET", /*authority=*/"ExpiredValidated"); const std::string response_body(42, 'a'); - Http::TestResponseHeaderMapImpl response_headers = { - {":status", "200"}, - {"date", formatter_.now(simTime())}, - {"cache-control", "max-age=10"}, // expires after 10 s - {"content-length", std::to_string(response_body.size())}, - {"etag", "abc123"}}; + Http::TestResponseHeaderMapImpl response_headers = httpResponseHeadersForBody( + response_body, /*cache_control=*/"max-age=10", /*extra_headers=*/{{"etag", "abc123"}}); // Send first request, and get response from upstream. { - IntegrationStreamDecoderPtr response_decoder = - codec_client_->makeHeaderOnlyRequest(request_headers); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(response_headers, /*end_stream=*/false); - // send 42 'a's - upstream_request_->encodeData(response_body, true); - // Wait for the response to be read by the codec client. - ASSERT_TRUE(response_decoder->waitForEndStream()); - EXPECT_TRUE(response_decoder->complete()); + IntegrationStreamDecoderPtr response_decoder = sendHeaderOnlyRequestAwaitResponse( + request_headers, simulateUpstreamResponse(response_headers, makeOptRef(response_body))); EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); - EXPECT_TRUE(response_decoder->headers().get(Http::CustomHeaders::get().Age).empty()); + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); EXPECT_EQ(response_decoder->body(), response_body); EXPECT_THAT(waitForAccessLog(access_log_name_), testing::HasSubstr("- via_upstream")); } @@ -141,35 +156,32 @@ TEST_P(CacheIntegrationTest, ExpiredValidated) { // Send second request, the cached response should be validate then served { - IntegrationStreamDecoderPtr response_decoder = - codec_client_->makeHeaderOnlyRequest(request_headers); - waitForNextUpstreamRequest(); - - // Check for injected precondition headers - const Http::TestRequestHeaderMapImpl injected_headers = {{"if-none-match", "abc123"}}; - EXPECT_THAT(upstream_request_->headers(), IsSupersetOfHeaders(injected_headers)); - // Create a 304 (not modified) response -> cached response is valid const std::string not_modified_date = formatter_.now(simTime()); const Http::TestResponseHeaderMapImpl not_modified_response_headers = { {":status", "304"}, {"date", not_modified_date}}; - upstream_request_->encodeHeaders(not_modified_response_headers, /*end_stream=*/true); + + IntegrationStreamDecoderPtr response_decoder = + sendHeaderOnlyRequestAwaitResponse(request_headers, [&]() { + waitForNextUpstreamRequest(); + // Check for injected precondition headers + Http::TestRequestHeaderMapImpl injected_headers = {{"if-none-match", "abc123"}}; + EXPECT_THAT(upstream_request_->headers(), IsSupersetOfHeaders(injected_headers)); + + upstream_request_->encodeHeaders(not_modified_response_headers, /*end_stream=*/true); + }); // The original response headers should be updated with 304 response headers response_headers.setDate(not_modified_date); - // Wait for the response to be read by the codec client. - ASSERT_TRUE(response_decoder->waitForEndStream()); - // Check that the served response is the cached response - EXPECT_TRUE(response_decoder->complete()); EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); EXPECT_EQ(response_decoder->body(), response_body); - // A response that has been validated should not contain an Age header as it is equivalent to a - // freshly served response from the origin, unless the 304 response has an Age header, which + // A response that has been validated should not contain an Age header as it is equivalent to + // a freshly served response from the origin, unless the 304 response has an Age header, which // means it was served by an upstream cache. - EXPECT_TRUE(response_decoder->headers().get(Http::CustomHeaders::get().Age).empty()); + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); // Advance time to force a log flush. simTime().advanceTimeWait(Seconds(1)); @@ -179,39 +191,19 @@ TEST_P(CacheIntegrationTest, ExpiredValidated) { } TEST_P(CacheIntegrationTest, ExpiredFetchedNewResponse) { - useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS%"); - // Set system time to cause Envoy's cached formatted time to match time on this thread. - simTime().setSystemTime(std::chrono::hours(1)); - initializeFilter(default_config); - // Include test name and params in URL to make each test's requests unique. - const Http::TestRequestHeaderMapImpl request_headers = { - {":method", "GET"}, - {":path", absl::StrCat("/", protocolTestParamsToString({GetParam(), 0}))}, - {":scheme", "http"}, - {":authority", "ExpiredFetchedNewResponse"}}; + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("GET", /*authority=*/"ExpiredFetchedNewResponse"); // Send first request, and get response from upstream. { const std::string response_body(10, 'a'); - Http::TestResponseHeaderMapImpl response_headers = { - {":status", "200"}, - {"date", formatter_.now(simTime())}, - {"cache-control", "max-age=10"}, // expires after 10 s - {"content-length", std::to_string(response_body.size())}, - {"etag", "a1"}}; - - IntegrationStreamDecoderPtr response_decoder = - codec_client_->makeHeaderOnlyRequest(request_headers); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(response_headers, /*end_stream=*/false); - // send 10 'a's - upstream_request_->encodeData(response_body, /*end_stream=*/true); - // Wait for the response to be read by the codec client. - ASSERT_TRUE(response_decoder->waitForEndStream()); - EXPECT_TRUE(response_decoder->complete()); + Http::TestResponseHeaderMapImpl response_headers = httpResponseHeadersForBody( + response_body, /*cache_control=*/"max-age=10", /*extra_headers=*/{{"etag", "a1"}}); + IntegrationStreamDecoderPtr response_decoder = sendHeaderOnlyRequestAwaitResponse( + request_headers, simulateUpstreamResponse(response_headers, makeOptRef(response_body))); EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); - EXPECT_TRUE(response_decoder->headers().get(Http::CustomHeaders::get().Age).empty()); + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); EXPECT_EQ(response_decoder->body(), response_body); EXPECT_THAT(waitForAccessLog(access_log_name_), testing::HasSubstr("- via_upstream")); } @@ -224,33 +216,27 @@ TEST_P(CacheIntegrationTest, ExpiredFetchedNewResponse) { // The new response should be served { const std::string response_body(20, 'a'); - Http::TestResponseHeaderMapImpl response_headers = { - {":status", "200"}, - {"date", formatter_.now(simTime())}, - {"content-length", std::to_string(response_body.size())}, - {"etag", "a2"}}; + Http::TestResponseHeaderMapImpl response_headers = httpResponseHeadersForBody( + response_body, + /*cache_control=*/"max-age=10", /*extra_headers=*/{{"etag", "a2"}}); IntegrationStreamDecoderPtr response_decoder = - codec_client_->makeHeaderOnlyRequest(request_headers); - waitForNextUpstreamRequest(); - - // Check for injected precondition headers - Http::TestRequestHeaderMapImpl injected_headers = {{"if-none-match", "a1"}}; - EXPECT_THAT(upstream_request_->headers(), IsSupersetOfHeaders(injected_headers)); - - // Reply with the updated response -> cached response is invalid - upstream_request_->encodeHeaders(response_headers, /*end_stream=*/false); - // send 20 'a's - upstream_request_->encodeData(response_body, /*end_stream=*/true); - - // Wait for the response to be read by the codec client. - ASSERT_TRUE(response_decoder->waitForEndStream()); + sendHeaderOnlyRequestAwaitResponse(request_headers, [&]() { + waitForNextUpstreamRequest(); + // Check for injected precondition headers + Http::TestRequestHeaderMapImpl injected_headers = {{"if-none-match", "a1"}}; + EXPECT_THAT(upstream_request_->headers(), IsSupersetOfHeaders(injected_headers)); + + // Reply with the updated response -> cached response is invalid + upstream_request_->encodeHeaders(response_headers, /*end_stream=*/false); + // send 20 'a's + upstream_request_->encodeData(response_body, /*end_stream=*/true); + }); // Check that the served response is the updated response - EXPECT_TRUE(response_decoder->complete()); EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); EXPECT_EQ(response_decoder->body(), response_body); // Check that age header does not exist as this is not a cached response - EXPECT_TRUE(response_decoder->headers().get(Http::CustomHeaders::get().Age).empty()); + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); // Advance time to force a log flush. simTime().advanceTimeWait(Seconds(1)); @@ -261,22 +247,14 @@ TEST_P(CacheIntegrationTest, ExpiredFetchedNewResponse) { // Send the same GET request with body and trailers twice, then check that the response // doesn't have an age header, to confirm that it wasn't served from cache. TEST_P(CacheIntegrationTest, GetRequestWithBodyAndTrailers) { - // Set system time to cause Envoy's cached formatted time to match time on this thread. - simTime().setSystemTime(std::chrono::hours(1)); - initializeFilter(default_config); - // Include test name and params in URL to make each test's requests unique. - const Http::TestRequestHeaderMapImpl request_headers = { - {":method", "GET"}, - {":path", absl::StrCat("/", protocolTestParamsToString({GetParam(), 0}))}, - {":scheme", "http"}, - {":authority", "MissInsertHit"}}; + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("GET", /*authority=*/"GetRequestWithBodyAndTrailers"); + Http::TestRequestTrailerMapImpl request_trailers{{"request1", "trailer1"}, {"request2", "trailer2"}}; - Http::TestResponseHeaderMapImpl response_headers = {{":status", "200"}, - {"date", formatter_.now(simTime())}, - {"cache-control", "public,max-age=3600"}, - {"content-length", "42"}}; + const std::string response_body(42, 'a'); + Http::TestResponseHeaderMapImpl response_headers = httpResponseHeadersForBody(response_body); for (int i = 0; i < 2; ++i) { auto encoder_decoder = codec_client_->startRequest(request_headers); @@ -297,6 +275,239 @@ TEST_P(CacheIntegrationTest, GetRequestWithBodyAndTrailers) { } } +TEST_P(CacheIntegrationTest, ServeHeadRequest) { + // Include test name and params in URL to make each test's requests unique. + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("HEAD", "ServeHeadRequest"); + const std::string response_body(42, 'a'); + Http::TestResponseHeaderMapImpl response_headers = httpResponseHeadersForBody(response_body); + + // Send first request, and get response from upstream. + { + // Since it is a head request, no need to encodeData => the response_body is absl::nullopt. + IntegrationStreamDecoderPtr response_decoder = sendHeaderOnlyRequestAwaitResponse( + request_headers, + simulateUpstreamResponse(response_headers, + /*response_body*/ makeOptRefFromPtr(nullptr))); + EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); + EXPECT_EQ(response_decoder->body().size(), 0); + EXPECT_THAT(waitForAccessLog(access_log_name_), testing::HasSubstr("- via_upstream")); + } + + // Advance time, to verify the original date header is preserved. + simTime().advanceTimeWait(Seconds(10)); + + // Send second request, and get response from upstream, since the head requests are not stored + // in cache. + { + // Since it is a head request, no need to encodeData => the response_body is empty. + IntegrationStreamDecoderPtr response_decoder = sendHeaderOnlyRequestAwaitResponse( + request_headers, + simulateUpstreamResponse(response_headers, + /*response_body*/ makeOptRefFromPtr(nullptr))); + EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); + EXPECT_EQ(response_decoder->body().size(), 0); + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); + // Advance time to force a log flush. + simTime().advanceTimeWait(Seconds(1)); + EXPECT_THAT(waitForAccessLog(access_log_name_), testing::HasSubstr("- via_upstream")); + } +} + +TEST_P(CacheIntegrationTest, ServeHeadFromCacheAfterGetRequest) { + const std::string response_body(42, 'a'); + Http::TestResponseHeaderMapImpl response_headers = httpResponseHeadersForBody(response_body); + + // Send GET request, and get response from upstream. + { + // Include test name and params in URL to make each test's requests unique. + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("GET", /*authority=*/"ServeHeadFromCacheAfterGetRequest"); + IntegrationStreamDecoderPtr response_decoder = sendHeaderOnlyRequestAwaitResponse( + request_headers, simulateUpstreamResponse(response_headers, makeOptRef(response_body))); + EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); + EXPECT_EQ(response_decoder->body(), response_body); + EXPECT_THAT(waitForAccessLog(access_log_name_), testing::HasSubstr("- via_upstream")); + } + // Advance time, to verify the original date header is preserved. + simTime().advanceTimeWait(Seconds(10)); + + // Send HEAD request, and get response from cache. + { + // Include test name and params in URL to make each test's requests unique. + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("HEAD", "ServeHeadFromCacheAfterGetRequest"); + IntegrationStreamDecoderPtr response_decoder = + sendHeaderOnlyRequestAwaitResponse(request_headers, serveFromCache()); + EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); + EXPECT_EQ(response_decoder->body().size(), 0); + EXPECT_THAT(response_decoder->headers(), + HeaderHasValueRef(Http::CustomHeaders::get().Age, "10")); + // Advance time to force a log flush. + simTime().advanceTimeWait(Seconds(1)); + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), + testing::HasSubstr("RFCF cache.response_from_cache_filter")); + } +} + +TEST_P(CacheIntegrationTest, ServeGetFromUpstreamAfterHeadRequest) { + const std::string response_body(42, 'a'); + Http::TestResponseHeaderMapImpl response_headers = httpResponseHeadersForBody(response_body); + + // Send HEAD request, and get response from upstream. + { + // Include test name and params in URL to make each test's requests unique. + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("HEAD", "ServeGetFromUpstreamAfterHeadRequest"); + // No need to encode the data, therefore response_body is empty. + IntegrationStreamDecoderPtr response_decoder = sendHeaderOnlyRequestAwaitResponse( + request_headers, + simulateUpstreamResponse(response_headers, + /*response_body*/ makeOptRefFromPtr(nullptr))); + EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); + EXPECT_EQ(response_decoder->body().size(), 0); + EXPECT_THAT(waitForAccessLog(access_log_name_), testing::HasSubstr("- via_upstream")); + } + + // Advance time, to verify the original date header is preserved. + simTime().advanceTimeWait(Seconds(10)); + + // Send GET request, and get response from cache. + { + // Include test name and params in URL to make each test's requests unique. + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("GET", /*authority=*/"ServeGetFromUpstreamAfterHeadRequest"); + IntegrationStreamDecoderPtr response_decoder = sendHeaderOnlyRequestAwaitResponse( + request_headers, simulateUpstreamResponse(response_headers, makeOptRef(response_body))); + EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); + EXPECT_EQ(response_decoder->body(), response_body); + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); + EXPECT_THAT(waitForAccessLog(access_log_name_), testing::HasSubstr("- via_upstream")); + } +} + +TEST_P(CacheIntegrationTest, ServeGetFollowedByHead304WithValidation) { + const std::string response_body(42, 'a'); + Http::TestResponseHeaderMapImpl response_headers = httpResponseHeadersForBody( + response_body, /*cache_control=*/"max-age=10", /*extra_headers=*/{{"etag", "abc123"}}); + + // Send GET request, and get response from upstream. + { + // Include test name and params in URL to make each test's requests unique. + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("GET", /*authority=*/"ServeGetFollowedByHead304WithValidation"); + + IntegrationStreamDecoderPtr response_decoder = sendHeaderOnlyRequestAwaitResponse( + request_headers, simulateUpstreamResponse(response_headers, makeOptRef(response_body))); + EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); + EXPECT_EQ(response_decoder->body(), response_body); + EXPECT_THAT(waitForAccessLog(access_log_name_), testing::HasSubstr("- via_upstream")); + } + // Advance time for the cached response to be stale (expired) + // Also to make sure response date header gets updated with the 304 date + simTime().advanceTimeWait(Seconds(11)); + + // Send HEAD request, the cached response should be validate then served + { + // Include test name and params in URL to make each test's requests unique. + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("HEAD", "ServeGetFollowedByHead304WithValidation"); + + // Create a 304 (not modified) response -> cached response is valid + const std::string not_modified_date = formatter_.now(simTime()); + const Http::TestResponseHeaderMapImpl not_modified_response_headers = { + {":status", "304"}, {"date", not_modified_date}}; + + IntegrationStreamDecoderPtr response_decoder = + sendHeaderOnlyRequestAwaitResponse(request_headers, [&]() { + waitForNextUpstreamRequest(); + + // Check for injected precondition headers + const Http::TestRequestHeaderMapImpl injected_headers = {{"if-none-match", "abc123"}}; + EXPECT_THAT(upstream_request_->headers(), IsSupersetOfHeaders(injected_headers)); + + upstream_request_->encodeHeaders(not_modified_response_headers, + /*end_stream=*/true); + }); + + // The original response headers should be updated with 304 response headers + response_headers.setDate(not_modified_date); + + EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); + EXPECT_EQ(response_decoder->body().size(), 0); + + // A response that has been validated should not contain an Age header as it is equivalent to + // a freshly served response from the origin, unless the 304 response has an Age header, which + // means it was served by an upstream cache. + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); + + // Advance time to force a log flush. + simTime().advanceTimeWait(Seconds(1)); + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), + testing::HasSubstr("RFCF cache.response_from_cache_filter")); + } +} + +TEST_P(CacheIntegrationTest, ServeGetFollowedByHead200WithValidation) { + // Send GET request, and get response from upstream. + { + // Include test name and params in URL to make each test's requests unique. + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("GET", /*authority=*/"ServeGetFollowedByHead200WithValidation"); + const std::string response_body(10, 'a'); + Http::TestResponseHeaderMapImpl response_headers = httpResponseHeadersForBody( + response_body, /*cache-control*/ "max-age=10", /*extra_headers=*/{{"etag", "a1"}}); + + IntegrationStreamDecoderPtr response_decoder = sendHeaderOnlyRequestAwaitResponse( + request_headers, simulateUpstreamResponse(response_headers, makeOptRef(response_body))); + EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); + EXPECT_EQ(response_decoder->body(), response_body); + EXPECT_THAT(waitForAccessLog(access_log_name_), testing::HasSubstr("- via_upstream")); + } + + // Advance time for the cached response to be stale (expired) + // Also to make sure response date header gets updated with the 304 date + simTime().advanceTimeWait(Seconds(11)); + + // Send HEAD request, validation of the cached response should be attempted but should fail + { + // Include test name and params in URL to make each test's requests unique. + const Http::TestRequestHeaderMapImpl request_headers = + httpRequestHeader("HEAD", "ServeGetFollowedByHead200WithValidation"); + const std::string response_body(20, 'a'); + Http::TestResponseHeaderMapImpl response_headers = + httpResponseHeadersForBody(response_body, + /*cache_control=*/"max-age=10", + /*extra_headers=*/{{"etag", "a2"}}); + + IntegrationStreamDecoderPtr response_decoder = + sendHeaderOnlyRequestAwaitResponse(request_headers, [&]() { + waitForNextUpstreamRequest(); + + // Check for injected precondition headers + Http::TestRequestHeaderMapImpl injected_headers = {{"if-none-match", "a1"}}; + EXPECT_THAT(upstream_request_->headers(), IsSupersetOfHeaders(injected_headers)); + + // Reply with the updated response -> cached response is invalid + upstream_request_->encodeHeaders(response_headers, + /*end_stream=*/true); + }); + EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); + EXPECT_EQ(response_decoder->body().size(), 0); + // Check that age header does not exist as this is not a cached response + EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); + + // Advance time to force a log flush. + simTime().advanceTimeWait(Seconds(1)); + EXPECT_THAT(waitForAccessLog(access_log_name_), testing::HasSubstr("- via_upstream")); + } +} + } // namespace } // namespace Cache } // namespace HttpFilters diff --git a/test/extensions/filters/http/cache/cacheability_utils_test.cc b/test/extensions/filters/http/cache/cacheability_utils_test.cc index ad7d9a4d65b9d..797f8e6858037 100644 --- a/test/extensions/filters/http/cache/cacheability_utils_test.cc +++ b/test/extensions/filters/http/cache/cacheability_utils_test.cc @@ -12,7 +12,7 @@ namespace HttpFilters { namespace Cache { namespace { -class IsCacheableRequestTest : public testing::Test { +class CanServeRequestFromCacheTest : public testing::Test { protected: Http::TestRequestHeaderMapImpl request_headers_ = {{":path", "/"}, {":method", "GET"}, @@ -49,46 +49,48 @@ class IsCacheableResponseTest : public testing::Test { VaryHeader vary_allow_list_; }; -TEST_F(IsCacheableRequestTest, CacheableRequest) { - EXPECT_TRUE(CacheabilityUtils::isCacheableRequest(request_headers_)); +TEST_F(CanServeRequestFromCacheTest, CacheableRequest) { + EXPECT_TRUE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); } -TEST_F(IsCacheableRequestTest, PathHeader) { - EXPECT_TRUE(CacheabilityUtils::isCacheableRequest(request_headers_)); +TEST_F(CanServeRequestFromCacheTest, PathHeader) { + EXPECT_TRUE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); request_headers_.removePath(); - EXPECT_FALSE(CacheabilityUtils::isCacheableRequest(request_headers_)); + EXPECT_FALSE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); } -TEST_F(IsCacheableRequestTest, HostHeader) { - EXPECT_TRUE(CacheabilityUtils::isCacheableRequest(request_headers_)); +TEST_F(CanServeRequestFromCacheTest, HostHeader) { + EXPECT_TRUE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); request_headers_.removeHost(); - EXPECT_FALSE(CacheabilityUtils::isCacheableRequest(request_headers_)); + EXPECT_FALSE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); } -TEST_F(IsCacheableRequestTest, MethodHeader) { +TEST_F(CanServeRequestFromCacheTest, MethodHeader) { const Http::HeaderValues& header_values = Http::Headers::get(); - EXPECT_TRUE(CacheabilityUtils::isCacheableRequest(request_headers_)); + EXPECT_TRUE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); request_headers_.setMethod(header_values.MethodValues.Post); - EXPECT_FALSE(CacheabilityUtils::isCacheableRequest(request_headers_)); + EXPECT_FALSE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); request_headers_.setMethod(header_values.MethodValues.Put); - EXPECT_FALSE(CacheabilityUtils::isCacheableRequest(request_headers_)); + EXPECT_FALSE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); + request_headers_.setMethod(header_values.MethodValues.Head); + EXPECT_TRUE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); request_headers_.removeMethod(); - EXPECT_FALSE(CacheabilityUtils::isCacheableRequest(request_headers_)); + EXPECT_FALSE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); } -TEST_F(IsCacheableRequestTest, ForwardedProtoHeader) { - EXPECT_TRUE(CacheabilityUtils::isCacheableRequest(request_headers_)); +TEST_F(CanServeRequestFromCacheTest, ForwardedProtoHeader) { + EXPECT_TRUE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); request_headers_.setForwardedProto("ftp"); - EXPECT_FALSE(CacheabilityUtils::isCacheableRequest(request_headers_)); + EXPECT_FALSE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); request_headers_.removeForwardedProto(); - EXPECT_FALSE(CacheabilityUtils::isCacheableRequest(request_headers_)); + EXPECT_FALSE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); } -TEST_F(IsCacheableRequestTest, AuthorizationHeader) { - EXPECT_TRUE(CacheabilityUtils::isCacheableRequest(request_headers_)); +TEST_F(CanServeRequestFromCacheTest, AuthorizationHeader) { + EXPECT_TRUE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); request_headers_.setReferenceKey(Http::CustomHeaders::get().Authorization, "basic YWxhZGRpbjpvcGVuc2VzYW1l"); - EXPECT_FALSE(CacheabilityUtils::isCacheableRequest(request_headers_)); + EXPECT_FALSE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); } INSTANTIATE_TEST_SUITE_P(ConditionalHeaders, RequestConditionalHeadersTest, @@ -102,9 +104,9 @@ INSTANTIATE_TEST_SUITE_P(ConditionalHeaders, RequestConditionalHeadersTest, }); TEST_P(RequestConditionalHeadersTest, ConditionalHeaders) { - EXPECT_TRUE(CacheabilityUtils::isCacheableRequest(request_headers_)); + EXPECT_TRUE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); request_headers_.setCopy(Http::LowerCaseString{conditionalHeader()}, "test-value"); - EXPECT_FALSE(CacheabilityUtils::isCacheableRequest(request_headers_)); + EXPECT_FALSE(CacheabilityUtils::canServeRequestFromCache(request_headers_)); } TEST_F(IsCacheableResponseTest, CacheableResponse) { From 0d429a0d82f68f2bbe71bca09223d18c02574ffd Mon Sep 17 00:00:00 2001 From: danzh Date: Wed, 5 May 2021 08:57:48 -0400 Subject: [PATCH 150/209] remove backtrace (#16285) Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- source/common/quic/envoy_quic_proof_source_base.h | 1 - 1 file changed, 1 deletion(-) diff --git a/source/common/quic/envoy_quic_proof_source_base.h b/source/common/quic/envoy_quic_proof_source_base.h index c522a821fa76f..22de52a0ce5e9 100644 --- a/source/common/quic/envoy_quic_proof_source_base.h +++ b/source/common/quic/envoy_quic_proof_source_base.h @@ -23,7 +23,6 @@ #include "openssl/ssl.h" #include "envoy/network/filter.h" -#include "server/backtrace.h" #include "common/common/logger.h" namespace Envoy { From d5129233705e86160a2f6d349e5431aa18f6372d Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 5 May 2021 14:15:01 +0100 Subject: [PATCH 151/209] api: Fix generated_api_shadow BUILD (#16330) Commit Message: api: Fix generated_api_shadow BUILD Additional Description: Some changes were made to the api/BUILD file in #16077 which were not propagated to generated_api_shadow this is also a possible reason that a dev reported an issue with ci formatting Signed-off-by: Ryan Northey --- generated_api_shadow/BUILD | 6 ------ 1 file changed, 6 deletions(-) diff --git a/generated_api_shadow/BUILD b/generated_api_shadow/BUILD index 7a6671dd681f9..d8782fb96b230 100644 --- a/generated_api_shadow/BUILD +++ b/generated_api_shadow/BUILD @@ -133,18 +133,12 @@ proto_library( "//envoy/config/common/matcher/v3:pkg", "//envoy/config/core/v3:pkg", "//envoy/config/endpoint/v3:pkg", - "//envoy/config/filter/thrift/router/v2alpha1:pkg", "//envoy/config/grpc_credential/v3:pkg", - "//envoy/config/health_checker/redis/v2:pkg", "//envoy/config/listener/v3:pkg", "//envoy/config/metrics/v3:pkg", "//envoy/config/overload/v3:pkg", "//envoy/config/ratelimit/v3:pkg", "//envoy/config/rbac/v3:pkg", - "//envoy/config/resource_monitor/fixed_heap/v2alpha:pkg", - "//envoy/config/resource_monitor/injected_resource/v2alpha:pkg", - "//envoy/config/retry/omit_canary_hosts/v2:pkg", - "//envoy/config/retry/previous_hosts/v2:pkg", "//envoy/config/route/v3:pkg", "//envoy/config/tap/v3:pkg", "//envoy/config/trace/v3:pkg", From 691487d8e422bd0bac927dd129273bd51fa93aa3 Mon Sep 17 00:00:00 2001 From: David Schinazi Date: Wed, 5 May 2021 08:18:58 -0700 Subject: [PATCH 152/209] quic: add support for client-side QUIC 0-RTT (#16260) This PR introduces EnvoyQuicSessionCache, which is responsible for saving QUIC+TLS session tickets which are required for 0-RTT. Risk Level: Low Testing: unit tests, integration test Docs Changes: None Release Notes: None Platform Specific Features: None Closes #16234 Signed-off-by: David Schinazi --- source/common/quic/BUILD | 12 + .../quic/client_connection_factory_impl.cc | 8 +- .../common/quic/envoy_quic_session_cache.cc | 171 +++++++++ source/common/quic/envoy_quic_session_cache.h | 65 ++++ test/common/quic/BUILD | 10 + .../quic/envoy_quic_session_cache_test.cc | 342 ++++++++++++++++++ test/integration/BUILD | 1 + .../integration/quic_http_integration_test.cc | 31 ++ 8 files changed, 637 insertions(+), 3 deletions(-) create mode 100644 source/common/quic/envoy_quic_session_cache.cc create mode 100644 source/common/quic/envoy_quic_session_cache.h create mode 100644 test/common/quic/envoy_quic_session_cache_test.cc diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index e84c3fb274a45..be256eccbf6b9 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -112,6 +112,17 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "envoy_quic_session_cache_lib", + srcs = ["envoy_quic_session_cache.cc"], + hdrs = ["envoy_quic_session_cache.h"], + external_deps = ["quiche_quic_platform"], + tags = ["nofips"], + deps = [ + "@com_googlesource_quiche//:quic_core_crypto_crypto_handshake_lib", + ], +) + envoy_cc_library( name = "spdy_server_push_utils_for_envoy_lib", srcs = ["spdy_server_push_utils_for_envoy.cc"], @@ -150,6 +161,7 @@ envoy_cc_library( ":envoy_quic_connection_helper_lib", ":envoy_quic_proof_verifier_lib", ":envoy_quic_server_session_lib", + ":envoy_quic_session_cache_lib", ":envoy_quic_utils_lib", "//include/envoy/http:codec_interface", "//include/envoy/registry", diff --git a/source/common/quic/client_connection_factory_impl.cc b/source/common/quic/client_connection_factory_impl.cc index 615f04a02a275..c65b566147048 100644 --- a/source/common/quic/client_connection_factory_impl.cc +++ b/source/common/quic/client_connection_factory_impl.cc @@ -1,5 +1,6 @@ #include "common/quic/client_connection_factory_impl.h" +#include "common/quic/envoy_quic_session_cache.h" #include "common/quic/quic_transport_socket_factory.h" namespace Envoy { @@ -20,9 +21,10 @@ PersistentQuicInfoImpl::PersistentQuicInfoImpl( : conn_helper_(dispatcher), alarm_factory_(dispatcher, *conn_helper_.GetClock()), server_id_{getConfig(transport_socket_factory).serverNameIndication(), static_cast(server_addr->ip()->port()), false}, - crypto_config_( - std::make_unique(std::make_unique( - stats_scope, getConfig(transport_socket_factory), time_source))) { + crypto_config_(std::make_unique( + std::make_unique(stats_scope, getConfig(transport_socket_factory), + time_source), + std::make_unique(time_source))) { quiche::FlagRegistry::getInstance(); } diff --git a/source/common/quic/envoy_quic_session_cache.cc b/source/common/quic/envoy_quic_session_cache.cc new file mode 100644 index 0000000000000..7404f132f1537 --- /dev/null +++ b/source/common/quic/envoy_quic_session_cache.cc @@ -0,0 +1,171 @@ +#include "common/quic/envoy_quic_session_cache.h" + +namespace Envoy { +namespace Quic { +namespace { + +// This value was chosen arbitrarily. We can make this configurable if needed. +// TODO(14829) ensure this is tested and scaled for upstream. +constexpr size_t MaxSessionCacheEntries = 1024; + +// Returns false if the SSL session doesn't exist or it is expired. +bool isSessionValid(SSL_SESSION* session, SystemTime now) { + if (session == nullptr) { + return false; + } + const time_t now_time_t = std::chrono::system_clock::to_time_t(now); + if (now_time_t < 0) { + return false; + } + const uint64_t now_u64 = static_cast(now_time_t); + const uint64_t session_time = SSL_SESSION_get_time(session); + const uint64_t session_expiration = session_time + SSL_SESSION_get_timeout(session); + // now_u64 may be slightly behind because of differences in how time is calculated at this layer + // versus BoringSSL. Add a second of wiggle room to account for this. + return session_time <= now_u64 + 1 && now_u64 < session_expiration; +} + +bool doApplicationStatesMatch(const quic::ApplicationState* state, + const quic::ApplicationState* other) { + if (state == other) { + return true; + } + if ((state != nullptr && other == nullptr) || (state == nullptr && other != nullptr)) { + return false; + } + return (*state == *other); +} + +} // namespace + +EnvoyQuicSessionCache::EnvoyQuicSessionCache(TimeSource& time_source) : time_source_(time_source) {} + +EnvoyQuicSessionCache::~EnvoyQuicSessionCache() = default; + +void EnvoyQuicSessionCache::Insert(const quic::QuicServerId& server_id, + bssl::UniquePtr session, + const quic::TransportParameters& params, + const quic::ApplicationState* application_state) { + auto it = cache_.find(server_id); + if (it == cache_.end()) { + // New server ID, add a new entry. + createAndInsertEntry(server_id, std::move(session), params, application_state); + return; + } + Entry& entry = it->second; + if (params == *entry.params && + doApplicationStatesMatch(application_state, entry.application_state.get())) { + // The states are both the same, so we only need to insert the session. + entry.pushSession(std::move(session)); + return; + } + // States are different, replace the entry. + cache_.erase(it); + createAndInsertEntry(server_id, std::move(session), params, application_state); +} + +std::unique_ptr +EnvoyQuicSessionCache::Lookup(const quic::QuicServerId& server_id, const SSL_CTX* /*ctx*/) { + auto it = cache_.find(server_id); + if (it == cache_.end()) { + return nullptr; + } + Entry& entry = it->second; + const SystemTime system_time = time_source_.systemTime(); + if (!isSessionValid(entry.peekSession(), system_time)) { + cache_.erase(it); + return nullptr; + } + auto state = std::make_unique(); + state->tls_session = entry.popSession(); + if (entry.params != nullptr) { + state->transport_params = std::make_unique(*entry.params); + } + if (entry.application_state != nullptr) { + state->application_state = std::make_unique(*entry.application_state); + } + return state; +} + +void EnvoyQuicSessionCache::ClearEarlyData(const quic::QuicServerId& server_id) { + auto it = cache_.find(server_id); + if (it == cache_.end()) { + return; + } + for (bssl::UniquePtr& session : it->second.sessions) { + if (session != nullptr) { + session.reset(SSL_SESSION_copy_without_early_data(session.get())); + } + } +} + +void EnvoyQuicSessionCache::prune() { + quic::QuicServerId oldest_id; + uint64_t oldest_expiration = std::numeric_limits::max(); + const SystemTime system_time = time_source_.systemTime(); + auto it = cache_.begin(); + while (it != cache_.end()) { + Entry& entry = it->second; + SSL_SESSION* session = entry.peekSession(); + if (!isSessionValid(session, system_time)) { + it = cache_.erase(it); + } else { + if (cache_.size() >= MaxSessionCacheEntries) { + // Only track the oldest session if we are at the size limit. + const uint64_t session_expiration = + SSL_SESSION_get_time(session) + SSL_SESSION_get_timeout(session); + if (session_expiration < oldest_expiration) { + oldest_expiration = session_expiration; + oldest_id = it->first; + } + } + ++it; + } + } + if (cache_.size() >= MaxSessionCacheEntries) { + cache_.erase(oldest_id); + } +} + +void EnvoyQuicSessionCache::createAndInsertEntry(const quic::QuicServerId& server_id, + bssl::UniquePtr session, + const quic::TransportParameters& params, + const quic::ApplicationState* application_state) { + prune(); + ASSERT(cache_.size() < MaxSessionCacheEntries); + Entry entry; + entry.pushSession(std::move(session)); + entry.params = std::make_unique(params); + if (application_state != nullptr) { + entry.application_state = std::make_unique(*application_state); + } + cache_.insert(std::make_pair(server_id, std::move(entry))); +} + +size_t EnvoyQuicSessionCache::size() const { return cache_.size(); } + +EnvoyQuicSessionCache::Entry::Entry() = default; +EnvoyQuicSessionCache::Entry::Entry(Entry&&) noexcept = default; +EnvoyQuicSessionCache::Entry::~Entry() = default; + +void EnvoyQuicSessionCache::Entry::pushSession(bssl::UniquePtr session) { + if (sessions[0] != nullptr) { + sessions[1] = std::move(sessions[0]); + } + sessions[0] = std::move(session); +} + +bssl::UniquePtr EnvoyQuicSessionCache::Entry::popSession() { + if (sessions[0] == nullptr) { + return nullptr; + } + bssl::UniquePtr session = std::move(sessions[0]); + sessions[0] = std::move(sessions[1]); + sessions[1] = nullptr; + return session; +} + +SSL_SESSION* EnvoyQuicSessionCache::Entry::peekSession() { return sessions[0].get(); } + +} // namespace Quic +} // namespace Envoy diff --git a/source/common/quic/envoy_quic_session_cache.h b/source/common/quic/envoy_quic_session_cache.h new file mode 100644 index 0000000000000..c15fe0d0e658e --- /dev/null +++ b/source/common/quic/envoy_quic_session_cache.h @@ -0,0 +1,65 @@ +#pragma once + +#include "envoy/common/time.h" + +#include "quiche/quic/core/crypto/quic_crypto_client_config.h" + +namespace Envoy { +namespace Quic { + +// Implementation of quic::SessionCache using an Envoy time source. +class EnvoyQuicSessionCache : public quic::SessionCache { +public: + explicit EnvoyQuicSessionCache(TimeSource& time_source); + ~EnvoyQuicSessionCache() override; + + // From quic::SessionCache. + void Insert(const quic::QuicServerId& server_id, bssl::UniquePtr session, + const quic::TransportParameters& params, + const quic::ApplicationState* application_state) override; + std::unique_ptr Lookup(const quic::QuicServerId& server_id, + const SSL_CTX* ctx) override; + void ClearEarlyData(const quic::QuicServerId& server_id) override; + + // Returns number of entries in the cache. + size_t size() const; + +private: + struct Entry { + Entry(); + Entry(Entry&&) noexcept; + ~Entry(); + + // Adds a new session onto sessions, dropping the oldest one if two are + // already stored. + void pushSession(bssl::UniquePtr session); + + // Retrieves and removes the latest session from the entry. + bssl::UniquePtr popSession(); + + SSL_SESSION* peekSession(); + + // We only save the last two sessions per server as that is sufficient in practice. This is + // because we only need one to create a new connection, and that new connection should send + // us another ticket. We keep two instead of one in case that connection attempt fails. + bssl::UniquePtr sessions[2]; + std::unique_ptr params; + std::unique_ptr application_state; + }; + + // Remove all entries that are no longer valid. If all entries were valid but the cache is at its + // size limit, instead remove the oldest entry. This walks the entire list of entries. + void prune(); + + // Creates a new entry and insert into cache_. This walks the entire list of entries. + void createAndInsertEntry(const quic::QuicServerId& server_id, + bssl::UniquePtr session, + const quic::TransportParameters& params, + const quic::ApplicationState* application_state); + + std::map cache_; + TimeSource& time_source_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/test/common/quic/BUILD b/test/common/quic/BUILD index 4c020dfed84d5..f187dc7629492 100644 --- a/test/common/quic/BUILD +++ b/test/common/quic/BUILD @@ -54,6 +54,16 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "envoy_quic_session_cache_test", + srcs = ["envoy_quic_session_cache_test.cc"], + external_deps = ["quiche_quic_platform"], + tags = ["nofips"], + deps = [ + "//source/common/quic:envoy_quic_session_cache_lib", + ], +) + envoy_cc_test( name = "envoy_quic_proof_verifier_test", srcs = ["envoy_quic_proof_verifier_test.cc"], diff --git a/test/common/quic/envoy_quic_session_cache_test.cc b/test/common/quic/envoy_quic_session_cache_test.cc new file mode 100644 index 0000000000000..d2129d14cf105 --- /dev/null +++ b/test/common/quic/envoy_quic_session_cache_test.cc @@ -0,0 +1,342 @@ +#include "common/quic/envoy_quic_session_cache.h" + +#include "absl/strings/escaping.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Quic { +namespace { + +constexpr uint32_t Timeout = 1000; +const quic::QuicVersionLabel FakeVersionLabel = 0x01234567; +const quic::QuicVersionLabel FakeVersionLabel2 = 0x89ABCDEF; +const uint64_t FakeIdleTimeoutMilliseconds = 12012; +const uint8_t FakeStatelessResetTokenData[16] = {0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F}; +const uint64_t FakeMaxPacketSize = 9001; +const uint64_t FakeInitialMaxData = 101; +const bool FakeDisableMigration = true; +const auto CustomParameter1 = static_cast(0xffcd); +const char* CustomParameter1Value = "foo"; +const auto CustomParameter2 = static_cast(0xff34); +const char* CustomParameter2Value = "bar"; + +std::vector createFakeStatelessResetToken() { + return std::vector(FakeStatelessResetTokenData, + FakeStatelessResetTokenData + sizeof(FakeStatelessResetTokenData)); +} + +// Make a TransportParameters that has a few fields set to help test comparison. +std::unique_ptr makeFakeTransportParams() { + auto params = std::make_unique(); + params->perspective = quic::Perspective::IS_CLIENT; + params->version = FakeVersionLabel; + params->supported_versions.push_back(FakeVersionLabel); + params->supported_versions.push_back(FakeVersionLabel2); + params->max_idle_timeout_ms.set_value(FakeIdleTimeoutMilliseconds); + params->stateless_reset_token = createFakeStatelessResetToken(); + params->max_udp_payload_size.set_value(FakeMaxPacketSize); + params->initial_max_data.set_value(FakeInitialMaxData); + params->disable_active_migration = FakeDisableMigration; + params->custom_parameters[CustomParameter1] = CustomParameter1Value; + params->custom_parameters[CustomParameter2] = CustomParameter2Value; + return params; +} + +// Generated with SSL_SESSION_to_bytes. +static constexpr char CachedSession[] = + "3082068702010102020304040213010420b9c2a657e565db0babd09e192a9fc4d768fbd706" + "9f03f9278a4a0be62392e55b0420d87ed2ab8cafc986fd2e288bd2d654cd57c3a2bed1d532" + "20726e55fed39d021ea10602045ed16771a205020302a300a382025f3082025b30820143a0" + "03020102020104300d06092a864886f70d01010b0500302c3110300e060355040a13074163" + "6d6520436f311830160603550403130f496e7465726d656469617465204341301e170d3133" + "303130313130303030305a170d3233313233313130303030305a302d3110300e060355040a" + "130741636d6520436f3119301706035504031310746573742e6578616d706c652e636f6d30" + "59301306072a8648ce3d020106082a8648ce3d030107034200040526220e77278300d06bc0" + "86aff4f999a828a2ed5cc75adc2972794befe885aa3a9b843de321b36b0a795289cebff1a5" + "428bad5e34665ce5e36daad08fb3ffd8a3523050300e0603551d0f0101ff04040302078030" + "130603551d25040c300a06082b06010505070301300c0603551d130101ff04023000301b06" + "03551d11041430128210746573742e6578616d706c652e636f6d300d06092a864886f70d01" + "010b050003820101008c1f1e380831b6437a8b9284d28d4ead38d9503a9fc936db89048aa2" + "edd6ec2fb830d962ef7a4f384e679504f4d5520f3272e0b9e702b110aff31711578fa5aeb1" + "11e9d184c994b0f97e7b17d1995f3f477f25bc1258398ec0ec729caed55d594a009f48093a" + "17f33a7f3bb6e420cc3499838398a421d93c7132efa8bee5ed2645cbc55179c400da006feb" + "761badd356cac3bd7a0e6b22a511106a355ec62a4c0ac2541d2996adb4a918c866d10c3e31" + "62039a91d4ce600b276740d833380b37f66866d261bf6efa8855e7ae6c7d12a8a864cd9a1f" + "4663e07714b0204e51bbc189a2d04c2a5043202379ff1c8cbf30cbb44fde4ee9a1c0c976dc" + "4943df2c132ca4020400aa7f047d494e534543555245003072020101020203040402130104" + "000420d87ed2ab8cafc986fd2e288bd2d654cd57c3a2bed1d53220726e55fed39d021ea106" + "02045ed16771a205020302a300a4020400b20302011db5060404bd909308b807020500ffff" + "ffffb9050203093a80ba07040568332d3238bb030101ffbc03040100b20302011db3820307" + "30820303308201eba003020102020102300d06092a864886f70d01010b050030243110300e" + "060355040a130741636d6520436f3110300e06035504031307526f6f74204341301e170d31" + "33303130313130303030305a170d3233313233313130303030305a302c3110300e06035504" + "0a130741636d6520436f311830160603550403130f496e7465726d65646961746520434130" + "820122300d06092a864886f70d01010105000382010f003082010a0282010100cd3550e70a" + "6880e52bf0012b93110c50f723e1d8d2ed489aea3b649f82fae4ad2396a8a19b31d1d64ab2" + "79f1c18003184154a5303a82bd57109cfd5d34fd19d3211bcb06e76640e1278998822dd72e" + "0d5c059a740d45de325e784e81b4c86097f08b2a8ce057f6b9db5a53641d27e09347d993ee" + "acf67be7d297b1a6853775ffaaf78fae924e300b5654fd32f99d3cd82e95f56417ff26d265" + "e2b1786c835d67a4d8ae896b6eb34b35a5b1033c209779ed0bf8de25a13a507040ae9e0475" + "a26a2f15845b08c3e0554e47dbbc7925b02e580dbcaaa6f2eecde6b8028c5b00b33d44d0a6" + "bfb3e72e9d4670de45d1bd79bdc0f2470b71286091c29873152db4b1f30203010001a33830" + "36300e0603551d0f0101ff04040302020430130603551d25040c300a06082b060105050703" + "01300f0603551d130101ff040530030101ff300d06092a864886f70d01010b050003820101" + "00bc4f8234860558dd404a626403819bfc759029d625a002143e75ebdb2898d1befdd326c3" + "4b14dc3507d732bb29af7e6af31552db53052a2be0d950efee5e0f699304231611ed8bf73a" + "6f216a904c6c2f1a2186d1ed08a8005a7914394d71e7d4b643c808f86365c5fecad8b52934" + "2d3b3f03447126d278d75b1dab3ed53f23e36e9b3d695f28727916e5ee56ce22d387c81f05" + "919b2a37bd4981eb67d9f57b7072285dbbb61f48b6b14768c069a092aad5a094cf295dafd2" + "3ca008f89a5f5ab37a56e5f68df45091c7cb85574677127087a2887ba3baa6d4fc436c6e40" + "40885e81621d38974f0c7f0d792418c5adebb10e92a165f8d79b169617ff575c0d4a85b506" + "0404bd909308b603010100b70402020403b807020500ffffffffb9050203093a80ba070405" + "68332d3238bb030101ff"; + +class FakeTimeSource : public TimeSource { +public: + // From TimeSource. + SystemTime systemTime() override { return fake_time_; } + MonotonicTime monotonicTime() override { + // EnvoyQuicSessionCache does not use monotonic time, return empty value. + ADD_FAILURE() << "Unexpected call to monotonicTime"; + return MonotonicTime(); + } + + void advance(int seconds) { fake_time_ += std::chrono::seconds(seconds); } + +private: + SystemTime fake_time_{}; +}; + +} // namespace + +class EnvoyQuicSessionCacheTest : public ::testing::Test { +public: + EnvoyQuicSessionCacheTest() + : ssl_ctx_(SSL_CTX_new(TLS_method())), cache_(time_source_), + params_(makeFakeTransportParams()) {} + +protected: + bssl::UniquePtr makeSession(uint32_t timeout = Timeout) { + std::string cached_session = + absl::HexStringToBytes(absl::string_view(CachedSession, sizeof(CachedSession))); + SSL_SESSION* session = + SSL_SESSION_from_bytes(reinterpret_cast(cached_session.data()), + cached_session.size(), ssl_ctx_.get()); + SSL_SESSION_set_time(session, std::chrono::system_clock::to_time_t(time_source_.systemTime())); + SSL_SESSION_set_timeout(session, timeout); + return bssl::UniquePtr(session); + } + + bssl::UniquePtr ssl_ctx_; + FakeTimeSource time_source_; + EnvoyQuicSessionCache cache_; + std::unique_ptr params_; +}; + +// Tests that simple insertion and lookup work correctly. +TEST_F(EnvoyQuicSessionCacheTest, SingleSession) { + bssl::UniquePtr session = makeSession(); + quic::QuicServerId id1("a.com", 443); + + std::unique_ptr params2 = makeFakeTransportParams(); + bssl::UniquePtr session2 = makeSession(); + SSL_SESSION* unowned2 = session2.get(); + quic::QuicServerId id2("b.com", 443); + + EXPECT_EQ(nullptr, cache_.Lookup(id1, ssl_ctx_.get())); + EXPECT_EQ(nullptr, cache_.Lookup(id2, ssl_ctx_.get())); + EXPECT_EQ(0u, cache_.size()); + + cache_.Insert(id1, std::move(session), *params_, nullptr); + EXPECT_EQ(1u, cache_.size()); + std::unique_ptr resumption_state = cache_.Lookup(id1, ssl_ctx_.get()); + ASSERT_NE(resumption_state, nullptr); + ASSERT_NE(resumption_state->transport_params, nullptr); + EXPECT_EQ(*params_, *(resumption_state->transport_params)); + EXPECT_EQ(nullptr, cache_.Lookup(id2, ssl_ctx_.get())); + // No session is available for id1, even though the entry exists. + EXPECT_EQ(1u, cache_.size()); + EXPECT_EQ(nullptr, cache_.Lookup(id1, ssl_ctx_.get())); + // Lookup() will trigger a deletion of invalid entry. + EXPECT_EQ(0u, cache_.size()); + + bssl::UniquePtr session3 = makeSession(); + SSL_SESSION* unowned3 = session3.get(); + quic::QuicServerId id3("c.com", 443); + cache_.Insert(id3, std::move(session3), *params_, nullptr); + cache_.Insert(id2, std::move(session2), *params2, nullptr); + EXPECT_EQ(2u, cache_.size()); + std::unique_ptr resumption_state2 = cache_.Lookup(id2, ssl_ctx_.get()); + ASSERT_NE(resumption_state2, nullptr); + EXPECT_EQ(unowned2, resumption_state2->tls_session.get()); + std::unique_ptr resumption_state3 = cache_.Lookup(id3, ssl_ctx_.get()); + ASSERT_NE(resumption_state3, nullptr); + EXPECT_EQ(unowned3, resumption_state3->tls_session.get()); + + // Verify that the cache is cleared after Lookups. + EXPECT_EQ(nullptr, cache_.Lookup(id1, ssl_ctx_.get())); + EXPECT_EQ(nullptr, cache_.Lookup(id2, ssl_ctx_.get())); + EXPECT_EQ(nullptr, cache_.Lookup(id3, ssl_ctx_.get())); + EXPECT_EQ(0u, cache_.size()); +} + +TEST_F(EnvoyQuicSessionCacheTest, MultipleSessions) { + bssl::UniquePtr session = makeSession(); + quic::QuicServerId id1("a.com", 443); + bssl::UniquePtr session2 = makeSession(); + SSL_SESSION* unowned2 = session2.get(); + bssl::UniquePtr session3 = makeSession(); + SSL_SESSION* unowned3 = session3.get(); + + cache_.Insert(id1, std::move(session), *params_, nullptr); + cache_.Insert(id1, std::move(session2), *params_, nullptr); + cache_.Insert(id1, std::move(session3), *params_, nullptr); + // The latest session is popped first. + std::unique_ptr resumption_state1 = cache_.Lookup(id1, ssl_ctx_.get()); + ASSERT_NE(resumption_state1, nullptr); + EXPECT_EQ(unowned3, resumption_state1->tls_session.get()); + std::unique_ptr resumption_state2 = cache_.Lookup(id1, ssl_ctx_.get()); + ASSERT_NE(resumption_state2, nullptr); + EXPECT_EQ(unowned2, resumption_state2->tls_session.get()); + // Only two sessions are cache. + EXPECT_EQ(nullptr, cache_.Lookup(id1, ssl_ctx_.get())); +} + +// Test that when a different TransportParameter is inserted for +// the same server id, the existing entry is removed. +TEST_F(EnvoyQuicSessionCacheTest, DifferentTransportParams) { + bssl::UniquePtr session = makeSession(); + quic::QuicServerId id1("a.com", 443); + bssl::UniquePtr session2 = makeSession(); + bssl::UniquePtr session3 = makeSession(); + SSL_SESSION* unowned3 = session3.get(); + + cache_.Insert(id1, std::move(session), *params_, nullptr); + cache_.Insert(id1, std::move(session2), *params_, nullptr); + // tweak the transport parameters a little bit. + params_->perspective = quic::Perspective::IS_SERVER; + cache_.Insert(id1, std::move(session3), *params_, nullptr); + std::unique_ptr resumption_state = cache_.Lookup(id1, ssl_ctx_.get()); + ASSERT_NE(resumption_state, nullptr); + EXPECT_EQ(unowned3, resumption_state->tls_session.get()); + EXPECT_EQ(*params_.get(), *resumption_state->transport_params); + EXPECT_EQ(nullptr, cache_.Lookup(id1, ssl_ctx_.get())); +} + +TEST_F(EnvoyQuicSessionCacheTest, DifferentApplicationState) { + bssl::UniquePtr session = makeSession(); + quic::QuicServerId id1("a.com", 443); + bssl::UniquePtr session2 = makeSession(); + bssl::UniquePtr session3 = makeSession(); + SSL_SESSION* unowned3 = session3.get(); + quic::ApplicationState state; + state.push_back('a'); + + cache_.Insert(id1, std::move(session), *params_, &state); + cache_.Insert(id1, std::move(session2), *params_, &state); + cache_.Insert(id1, std::move(session3), *params_, nullptr); + std::unique_ptr resumption_state = cache_.Lookup(id1, ssl_ctx_.get()); + ASSERT_NE(resumption_state, nullptr); + EXPECT_EQ(unowned3, resumption_state->tls_session.get()); + EXPECT_EQ(nullptr, resumption_state->application_state); + EXPECT_EQ(nullptr, cache_.Lookup(id1, ssl_ctx_.get())); +} + +TEST_F(EnvoyQuicSessionCacheTest, BothStatesDifferent) { + bssl::UniquePtr session = makeSession(); + quic::QuicServerId id1("a.com", 443); + bssl::UniquePtr session2 = makeSession(); + bssl::UniquePtr session3 = makeSession(); + SSL_SESSION* unowned3 = session3.get(); + quic::ApplicationState state; + state.push_back('a'); + + cache_.Insert(id1, std::move(session), *params_, &state); + cache_.Insert(id1, std::move(session2), *params_, &state); + params_->perspective = quic::Perspective::IS_SERVER; + cache_.Insert(id1, std::move(session3), *params_, nullptr); + std::unique_ptr resumption_state = cache_.Lookup(id1, ssl_ctx_.get()); + ASSERT_NE(resumption_state, nullptr); + EXPECT_EQ(unowned3, resumption_state->tls_session.get()); + EXPECT_EQ(*params_, *resumption_state->transport_params); + EXPECT_EQ(nullptr, resumption_state->application_state); + EXPECT_EQ(nullptr, cache_.Lookup(id1, ssl_ctx_.get())); +} + +// When the size limit is exceeded, the oldest entry should be erased. +TEST_F(EnvoyQuicSessionCacheTest, SizeLimit) { + constexpr size_t size_limit = 1024; + std::array unowned_sessions; + for (size_t i = 0; i <= size_limit; i++) { + time_source_.advance(1); + bssl::UniquePtr session = makeSession(/*timeout=*/10000); + unowned_sessions[i] = session.get(); + quic::QuicServerId id(absl::StrCat("domain", i, ".example.com"), 443); + cache_.Insert(id, std::move(session), *params_, nullptr); + } + EXPECT_EQ(cache_.size(), size_limit); + // First entry has been removed. + quic::QuicServerId id0("domain0.example.com", 443); + EXPECT_EQ(nullptr, cache_.Lookup(id0, ssl_ctx_.get())); + // All other entries all present. + for (size_t i = 1; i <= size_limit; i++) { + quic::QuicServerId id(absl::StrCat("domain", i, ".example.com"), 443); + std::unique_ptr resumption_state = cache_.Lookup(id, ssl_ctx_.get()); + ASSERT_NE(resumption_state, nullptr) << i; + EXPECT_EQ(resumption_state->tls_session.get(), unowned_sessions[i]); + } +} + +TEST_F(EnvoyQuicSessionCacheTest, ClearEarlyData) { + SSL_CTX_set_early_data_enabled(ssl_ctx_.get(), 1); + bssl::UniquePtr session = makeSession(); + quic::QuicServerId id1("a.com", 443); + bssl::UniquePtr session2 = makeSession(); + + EXPECT_TRUE(SSL_SESSION_early_data_capable(session.get())); + EXPECT_TRUE(SSL_SESSION_early_data_capable(session2.get())); + + cache_.Insert(id1, std::move(session), *params_, nullptr); + cache_.Insert(id1, std::move(session2), *params_, nullptr); + + cache_.ClearEarlyData(id1); + + std::unique_ptr resumption_state = cache_.Lookup(id1, ssl_ctx_.get()); + ASSERT_NE(resumption_state, nullptr); + EXPECT_FALSE(SSL_SESSION_early_data_capable(resumption_state->tls_session.get())); + resumption_state = cache_.Lookup(id1, ssl_ctx_.get()); + EXPECT_FALSE(SSL_SESSION_early_data_capable(resumption_state->tls_session.get())); + EXPECT_EQ(nullptr, cache_.Lookup(id1, ssl_ctx_.get())); +} + +// Expired session isn't considered valid and nullptr will be returned upon +// Lookup. +TEST_F(EnvoyQuicSessionCacheTest, Expiration) { + bssl::UniquePtr session = makeSession(); + quic::QuicServerId id1("a.com", 443); + + bssl::UniquePtr session2 = makeSession(3 * Timeout); + SSL_SESSION* unowned2 = session2.get(); + quic::QuicServerId id2("b.com", 443); + + cache_.Insert(id1, std::move(session), *params_, nullptr); + cache_.Insert(id2, std::move(session2), *params_, nullptr); + + EXPECT_EQ(2u, cache_.size()); + // Expire the session. + time_source_.advance(Timeout * 2); + // The entry has not been removed yet. + EXPECT_EQ(2u, cache_.size()); + + EXPECT_EQ(nullptr, cache_.Lookup(id1, ssl_ctx_.get())); + EXPECT_EQ(1u, cache_.size()); + std::unique_ptr resumption_state = cache_.Lookup(id2, ssl_ctx_.get()); + ASSERT_NE(resumption_state, nullptr); + EXPECT_EQ(unowned2, resumption_state->tls_session.get()); + EXPECT_EQ(1u, cache_.size()); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/integration/BUILD b/test/integration/BUILD index 7d0ed082a0216..b4b02eec880f1 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1726,5 +1726,6 @@ envoy_cc_test( "@envoy_api//envoy/config/overload/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/transport_sockets/quic/v3:pkg_cc_proto", + "@com_googlesource_quiche//:quic_test_tools_session_peer_lib", ]), ) diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index bdf7046a8cb16..1e1cc4def1917 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -21,6 +21,7 @@ #include "quiche/quic/core/http/quic_client_push_promise_index.h" #include "quiche/quic/core/quic_utils.h" #include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/quic/test_tools/quic_session_peer.h" #if defined(__GNUC__) #pragma GCC diagnostic pop @@ -258,6 +259,36 @@ TEST_P(QuicHttpIntegrationTest, GetRequestAndEmptyResponse) { testRouterHeaderOnlyRequestAndResponse(); } +TEST_P(QuicHttpIntegrationTest, ZeroRtt) { + // Make sure both connections use the same PersistentQuicInfoImpl. + concurrency_ = 1; + initialize(); + // Start the first connection. + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + // Send a complete request on the first connection. + auto response1 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(0); + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response1->waitForEndStream()); + // Close the first connection. + codec_client_->close(); + // Start a second connection. + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + // Send a complete request on the second connection. + auto response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(0); + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response2->waitForEndStream()); + // Ensure 0-RTT was used by second connection. + EnvoyQuicClientSession* quic_session = + static_cast(codec_client_->connection()); + EXPECT_TRUE(static_cast( + quic::test::QuicSessionPeer::GetMutableCryptoStream(quic_session)) + ->EarlyDataAccepted()); + // Close the second connection. + codec_client_->close(); +} + TEST_P(QuicHttpIntegrationTest, GetRequestAndResponseWithBody) { initialize(); sendRequestAndVerifyResponse(default_request_headers_, /*request_size=*/0, From 48969eae438d7d31b60f674610a437da31c61ab6 Mon Sep 17 00:00:00 2001 From: Taylor Barrella Date: Wed, 5 May 2021 12:02:04 -0700 Subject: [PATCH 153/209] listener: allow changing address (#16134) * listener: allow changing address Signed-off-by: Taylor Barrella --- docs/root/configuration/listeners/lds.rst | 2 +- docs/root/version_history/current.rst | 1 + include/envoy/server/listener_manager.h | 5 +- source/server/listener_manager_impl.cc | 127 +++++------ source/server/listener_manager_impl.h | 3 + test/integration/BUILD | 1 + .../listener_lds_integration_test.cc | 67 ++++++ test/server/listener_manager_impl_test.cc | 207 +++++++++++++++--- 8 files changed, 311 insertions(+), 102 deletions(-) diff --git a/docs/root/configuration/listeners/lds.rst b/docs/root/configuration/listeners/lds.rst index 21560e958c6f2..4933d483ab6ea 100644 --- a/docs/root/configuration/listeners/lds.rst +++ b/docs/root/configuration/listeners/lds.rst @@ -16,7 +16,7 @@ The semantics of listener updates are as follows: references an :ref:`RDS ` configuration, that configuration will be resolved and fetched before the listener is moved to "active." * Listeners are effectively constant once created. Thus, when a listener is updated, an entirely - new listener is created (with the same listen socket). This listener goes through the same + new listener is created (if the listener's address is unchanged, the new one uses the same listen socket). This listener goes through the same warming process described above for a newly added listener. * When a listener is removed, the old listener will be placed into a "draining" state much like when the entire server is drained for restart. Connections owned by the listener will diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index c3e20c37564e0..76651aa7f917e 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -49,6 +49,7 @@ Removed Config or Runtime New Features ------------ +* listener: added ability to change an existing listener's address. * metric service: added support for sending metric tags as labels. This can be enabled by setting the :ref:`emit_tags_as_labels ` field to true. * udp_proxy: added :ref:`key ` as another hash policy to support hash based routing on any given key. diff --git a/include/envoy/server/listener_manager.h b/include/envoy/server/listener_manager.h index 01fe285b1c867..9f43b26a4e051 100644 --- a/include/envoy/server/listener_manager.h +++ b/include/envoy/server/listener_manager.h @@ -149,9 +149,8 @@ class ListenerManager { * Add or update a listener. Listeners are referenced by a unique name. If no name is provided, * the manager will allocate a UUID. Listeners that expect to be dynamically updated should * provide a unique name. The manager will search by name to find the existing listener that - * should be updated. The new listener must have the same configured address. The old listener - * will be gracefully drained once the new listener is ready to take traffic (e.g. when RDS has - * been initialized). + * should be updated. The old listener will be gracefully drained once the new listener is ready + * to take traffic (e.g. when RDS has been initialized). * @param config supplies the configuration proto. * @param version_info supplies the xDS version of the listener. * @param modifiable supplies whether the added listener can be updated or removed. If the diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index 81b44c3d36316..5bf3709c41de6 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -438,42 +438,25 @@ bool ListenerManagerImpl::addOrUpdateListenerInternal( ListenerImpl& new_listener_ref = *new_listener; - // We mandate that a listener with the same name must have the same configured address. This - // avoids confusion during updates and allows us to use the same bound address. Note that in - // the case of port 0 binding, the new listener will implicitly use the same bound port from - // the existing listener. - bool active_listener_exists = false; - bool warming_listener_exists = false; - if (existing_warming_listener != warming_listeners_.end() && - *(*existing_warming_listener)->address() != *new_listener->address()) { - warming_listener_exists = true; - } - if (existing_active_listener != active_listeners_.end() && - *(*existing_active_listener)->address() != *new_listener->address()) { - active_listener_exists = true; - } - if (active_listener_exists || warming_listener_exists) { - const std::string message = - fmt::format("error updating listener: '{}' has a different address '{}' from existing " - "listener address '{}'", - name, new_listener->address()->asString(), - warming_listener_exists ? (*existing_warming_listener)->address()->asString() - : (*existing_active_listener)->address()->asString()); - ENVOY_LOG(warn, "{}", message); - throw EnvoyException(message); - } - bool added = false; if (existing_warming_listener != warming_listeners_.end()) { // In this case we can just replace inline. ASSERT(workers_started_); new_listener->debugLog("update warming listener"); - new_listener->setSocketFactory((*existing_warming_listener)->getSocketFactory()); + if (*(*existing_warming_listener)->address() != *new_listener->address()) { + setNewOrDrainingSocketFactory(name, config.address(), *new_listener, config.reuse_port()); + } else { + new_listener->setSocketFactory((*existing_warming_listener)->getSocketFactory()); + } *existing_warming_listener = std::move(new_listener); } else if (existing_active_listener != active_listeners_.end()) { // In this case we have no warming listener, so what we do depends on whether workers - // have been started or not. Either way we get the socket from the existing listener. - new_listener->setSocketFactory((*existing_active_listener)->getSocketFactory()); + // have been started or not. + if (*(*existing_active_listener)->address() != *new_listener->address()) { + setNewOrDrainingSocketFactory(name, config.address(), *new_listener, config.reuse_port()); + } else { + new_listener->setSocketFactory((*existing_active_listener)->getSocketFactory()); + } if (workers_started_) { new_listener->debugLog("add warming listener"); warming_listeners_.emplace_back(std::move(new_listener)); @@ -482,49 +465,9 @@ bool ListenerManagerImpl::addOrUpdateListenerInternal( *existing_active_listener = std::move(new_listener); } } else { - // Typically we catch address issues when we try to bind to the same address multiple times. - // However, for listeners that do not bind we must check to make sure we are not duplicating. - // This is an edge case and nothing will explicitly break, but there is no possibility that - // two listeners that do not bind will ever be used. Only the first one will be used when - // searched for by address. Thus we block it. - if (!new_listener->bindToPort() && - (hasListenerWithAddress(warming_listeners_, *new_listener->address()) || - hasListenerWithAddress(active_listeners_, *new_listener->address()))) { - const std::string message = - fmt::format("error adding listener: '{}' has duplicate address '{}' as existing listener", - name, new_listener->address()->asString()); - ENVOY_LOG(warn, "{}", message); - throw EnvoyException(message); - } - // We have no warming or active listener so we need to make a new one. What we do depends on - // whether workers have been started or not. Additionally, search through draining listeners - // to see if there is a listener that has a socket factory for the same address we are - // configured for and doesn't use SO_REUSEPORT. This is an edge case, but may happen if a - // listener is removed and then added back with a same or different name and intended to listen - // on the same address. This should work and not fail. - Network::ListenSocketFactorySharedPtr draining_listen_socket_factory; - auto existing_draining_listener = std::find_if( - draining_listeners_.cbegin(), draining_listeners_.cend(), - [&new_listener](const DrainingListener& listener) { - return listener.listener_->listenSocketFactory().sharedSocket().has_value() && - listener.listener_->listenSocketFactory().sharedSocket()->get().isOpen() && - *new_listener->address() == - *listener.listener_->listenSocketFactory().localAddress(); - }); - - if (existing_draining_listener != draining_listeners_.cend()) { - draining_listen_socket_factory = existing_draining_listener->listener_->getSocketFactory(); - } - - Network::Socket::Type socket_type = - Network::Utility::protobufAddressSocketType(config.address()); - new_listener->setSocketFactory( - draining_listen_socket_factory - ? draining_listen_socket_factory - : createListenSocketFactory(config.address(), *new_listener, - (socket_type == Network::Socket::Type::Datagram) || - config.reuse_port())); + // whether workers have been started or not. + setNewOrDrainingSocketFactory(name, config.address(), *new_listener, config.reuse_port()); if (workers_started_) { new_listener->debugLog("add warming listener"); warming_listeners_.emplace_back(std::move(new_listener)); @@ -1048,6 +991,50 @@ Network::DrainableFilterChainSharedPtr ListenerFilterChainFactoryBuilder::buildF return filter_chain_res; } +void ListenerManagerImpl::setNewOrDrainingSocketFactory( + const std::string& name, const envoy::config::core::v3::Address& proto_address, + ListenerImpl& listener, bool reuse_port) { + // Typically we catch address issues when we try to bind to the same address multiple times. + // However, for listeners that do not bind we must check to make sure we are not duplicating. This + // is an edge case and nothing will explicitly break, but there is no possibility that two + // listeners that do not bind will ever be used. Only the first one will be used when searched for + // by address. Thus we block it. + if (!listener.bindToPort() && (hasListenerWithAddress(warming_listeners_, *listener.address()) || + hasListenerWithAddress(active_listeners_, *listener.address()))) { + const std::string message = + fmt::format("error adding listener: '{}' has duplicate address '{}' as existing listener", + name, listener.address()->asString()); + ENVOY_LOG(warn, "{}", message); + throw EnvoyException(message); + } + + // Search through draining listeners to see if there is a listener that has a socket factory for + // the same address we are configured for and doesn't use SO_REUSEPORT. This is an edge case, but + // may happen if a listener is removed and then added back with a same or different name and + // intended to listen on the same address. This should work and not fail. + Network::ListenSocketFactorySharedPtr draining_listen_socket_factory; + auto existing_draining_listener = std::find_if( + draining_listeners_.cbegin(), draining_listeners_.cend(), + [&listener](const DrainingListener& draining_listener) { + return draining_listener.listener_->listenSocketFactory().sharedSocket().has_value() && + draining_listener.listener_->listenSocketFactory().sharedSocket()->get().isOpen() && + *listener.address() == + *draining_listener.listener_->listenSocketFactory().localAddress(); + }); + + if (existing_draining_listener != draining_listeners_.cend()) { + draining_listen_socket_factory = existing_draining_listener->listener_->getSocketFactory(); + } + + Network::Socket::Type socket_type = Network::Utility::protobufAddressSocketType(proto_address); + listener.setSocketFactory( + draining_listen_socket_factory + ? draining_listen_socket_factory + : createListenSocketFactory(proto_address, listener, + (socket_type == Network::Socket::Type::Datagram) || + reuse_port)); +} + Network::ListenSocketFactorySharedPtr ListenerManagerImpl::createListenSocketFactory( const envoy::config::core::v3::Address& proto_address, ListenerImpl& listener, bool reuse_port) { diff --git a/source/server/listener_manager_impl.h b/source/server/listener_manager_impl.h index 22fb1bceba306..045465f837cee 100644 --- a/source/server/listener_manager_impl.h +++ b/source/server/listener_manager_impl.h @@ -284,6 +284,9 @@ class ListenerManagerImpl : public ListenerManager, Logger::LoggablewaitForCounterGe("listener_manager.lds.update_success", 1); + // testing-listener-0 is not initialized as we haven't pushed any RDS yet. + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); + // Workers not started, the LDS added listener 0 is in active_listeners_ list. + EXPECT_EQ(test_server_->server().listenerManager().listeners().size(), 1); + registerTestServerPorts({listener_name_}); + + const std::string route_config_tmpl = R"EOF( + name: {} + virtual_hosts: + - name: integration + domains: ["*"] + routes: + - match: {{ prefix: "/" }} + route: {{ cluster: {} }} +)EOF"; + sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1"); + test_server_->waitForCounterGe( + fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1); + // Now testing-listener-0 finishes initialization, Server initManager will be ready. + EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); + + test_server_->waitUntilListenersReady(); + // NOTE: The line above doesn't tell you if listener is up and listening. + test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + const uint32_t old_port = lookupPort(listener_name_); + // Make a connection to the listener from version 1. + codec_client_ = makeHttpConnection(old_port); + + // Change the listener address from loopback to wildcard. + const std::string new_address = Network::Test::getAnyAddressString(ipVersion()); + listener_config_.mutable_address()->mutable_socket_address()->set_address(new_address); + sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "2"); + sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "2"); + + test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + registerTestServerPorts({listener_name_}); + // Verify that the listener was updated and that the next connection will be to the new listener. + // (Note that connecting to 127.0.0.1 works whether the listener address is 127.0.0.1 or 0.0.0.0.) + const uint32_t new_port = lookupPort(listener_name_); + EXPECT_NE(old_port, new_port); + + // Wait for the client to be disconnected. + ASSERT_TRUE(codec_client_->waitForDisconnect()); + // Make a new connection to the new listener. + codec_client_ = makeHttpConnection(new_port); + const uint32_t response_size = 800; + const uint32_t request_size = 10; + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, + {"server_id", "cluster_0, backend_0"}}; + auto response = sendRequestAndWaitForResponse( + Http::TestResponseHeaderMapImpl{ + {":method", "GET"}, {":path", "/"}, {":authority", "host"}, {":scheme", "http"}}, + request_size, response_headers, response_size, /*cluster_0*/ 0); + verifyResponse(std::move(response), "200", response_headers, std::string(response_size, 'a')); + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(request_size, upstream_request_->bodyLength()); +} + class RebalancerTest : public testing::TestWithParam, public BaseIntegrationTest { public: diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index fc370054d27a2..2b1e1b2c4fe88 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -630,8 +630,15 @@ TEST_F(ListenerManagerImplTest, ModifyOnlyDrainType) { } TEST_F(ListenerManagerImplTest, AddListenerAddressNotMatching) { + time_system_.setSystemTime(std::chrono::milliseconds(1001001001001)); + InSequence s; + auto* lds_api = new MockLdsApi(); + EXPECT_CALL(listener_factory_, createLdsApi_(_, _)).WillOnce(Return(lds_api)); + envoy::config::core::v3::ConfigSource lds_config; + manager_->createLdsApi(lds_config, nullptr); + // Add foo listener. const std::string listener_foo_yaml = R"EOF( name: foo @@ -639,40 +646,194 @@ name: foo socket_address: address: 127.0.0.1 port_value: 1234 -filter_chains: -- filters: [] -drain_type: default - +filter_chains: {} )EOF"; ListenerHandle* listener_foo = expectListenerCreate(false, true); EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, {true})); - EXPECT_TRUE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(listener_foo_yaml), "", true)); + EXPECT_TRUE( + manager_->addOrUpdateListener(parseListenerFromV3Yaml(listener_foo_yaml), "version1", true)); checkStats(__LINE__, 1, 0, 0, 0, 1, 0, 0); + EXPECT_CALL(*lds_api, versionInfo()).WillOnce(Return("version1")); + checkConfigDump(R"EOF( +version_info: version1 +static_listeners: +dynamic_listeners: + - name: foo + warming_state: + version_info: version1 + listener: + "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: foo + address: + socket_address: + address: 127.0.0.1 + port_value: 1234 + filter_chains: {} + last_updated: + seconds: 1001001001 + nanos: 1000000 +)EOF"); - // Update foo listener, but with a different address. Should throw. + // Update foo listener, but with a different address. const std::string listener_foo_different_address_yaml = R"EOF( name: foo address: socket_address: address: 127.0.0.1 port_value: 1235 -filter_chains: -- filters: [] -drain_type: modify_only +filter_chains: {} )EOF"; - ListenerHandle* listener_foo_different_address = - expectListenerCreate(false, true, envoy::config::listener::v3::Listener::MODIFY_ONLY); - EXPECT_CALL(*listener_foo_different_address, onDestroy()); - EXPECT_THROW_WITH_MESSAGE( - manager_->addOrUpdateListener(parseListenerFromV3Yaml(listener_foo_different_address_yaml), - "", true), - EnvoyException, - "error updating listener: 'foo' has a different address " - "'127.0.0.1:1235' from existing listener address '127.0.0.1:1234'"); + time_system_.setSystemTime(std::chrono::milliseconds(2002002002002)); + ListenerHandle* listener_foo_different_address = expectListenerCreate(false, true); + // Another socket should be created. + EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, {true})); EXPECT_CALL(*listener_foo, onDestroy()); + EXPECT_TRUE(manager_->addOrUpdateListener( + parseListenerFromV3Yaml(listener_foo_different_address_yaml), "version2", true)); + checkStats(__LINE__, 1, 1, 0, 0, 1, 0, 0); + EXPECT_CALL(*lds_api, versionInfo()).WillOnce(Return("version2")); + checkConfigDump(R"EOF( +version_info: version2 +static_listeners: +dynamic_listeners: + - name: foo + warming_state: + version_info: version2 + listener: + "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: foo + address: + socket_address: + address: 127.0.0.1 + port_value: 1235 + filter_chains: {} + last_updated: + seconds: 2002002002 + nanos: 2000000 +)EOF"); + + EXPECT_CALL(*worker_, addListener(_, _, _)); + EXPECT_CALL(*worker_, start(_)); + manager_->startWorkers(guard_dog_, callback_.AsStdFunction()); + worker_->callAddCompletion(true); + + time_system_.setSystemTime(std::chrono::milliseconds(3003003003003)); + + // Add baz listener, this time requiring initializing. + const std::string listener_baz_yaml = R"EOF( +name: baz +address: + socket_address: + address: "127.0.0.1" + port_value: 1236 +filter_chains: {} + )EOF"; + + ListenerHandle* listener_baz = expectListenerCreate(true, true); + EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, {true})); + EXPECT_CALL(listener_baz->target_, initialize()); + EXPECT_TRUE( + manager_->addOrUpdateListener(parseListenerFromV3Yaml(listener_baz_yaml), "version3", true)); + EXPECT_CALL(*lds_api, versionInfo()).WillOnce(Return("version3")); + checkConfigDump(R"EOF( +version_info: version3 +static_listeners: +dynamic_listeners: + - name: foo + active_state: + version_info: version2 + listener: + "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: foo + address: + socket_address: + address: 127.0.0.1 + port_value: 1235 + filter_chains: {} + last_updated: + seconds: 2002002002 + nanos: 2000000 + - name: baz + warming_state: + version_info: version3 + listener: + "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: baz + address: + socket_address: + address: 127.0.0.1 + port_value: 1236 + filter_chains: {} + last_updated: + seconds: 3003003003 + nanos: 3000000 +)EOF"); + + time_system_.setSystemTime(std::chrono::milliseconds(4004004004004)); + + const std::string listener_baz_different_address_yaml = R"EOF( +name: baz +address: + socket_address: + address: "127.0.0.1" + port_value: 1237 +filter_chains: {} + )EOF"; + + // Modify the address of a warming listener. + ListenerHandle* listener_baz_different_address = expectListenerCreate(true, true); + // Another socket should be created. + EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, {true})); + EXPECT_CALL(*listener_baz, onDestroy()).WillOnce(Invoke([listener_baz]() -> void { + listener_baz->target_.ready(); + })); + EXPECT_CALL(listener_baz_different_address->target_, initialize()); + EXPECT_TRUE(manager_->addOrUpdateListener( + parseListenerFromV3Yaml(listener_baz_different_address_yaml), "version4", true)); + EXPECT_CALL(*lds_api, versionInfo()).WillOnce(Return("version4")); + checkConfigDump(R"EOF( +version_info: version4 +static_listeners: +dynamic_listeners: + - name: foo + active_state: + version_info: version2 + listener: + "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: foo + address: + socket_address: + address: 127.0.0.1 + port_value: 1235 + filter_chains: {} + last_updated: + seconds: 2002002002 + nanos: 2000000 + - name: baz + warming_state: + version_info: version4 + listener: + "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: baz + address: + socket_address: + address: 127.0.0.1 + port_value: 1237 + filter_chains: {} + last_updated: + seconds: 4004004004 + nanos: 4000000 +)EOF"); + + EXPECT_CALL(*worker_, addListener(_, _, _)); + listener_baz_different_address->target_.ready(); + worker_->callAddCompletion(true); + + EXPECT_CALL(*listener_foo_different_address, onDestroy()); + EXPECT_CALL(*listener_baz_different_address, onDestroy()); } // Make sure that a listener creation does not fail on IPv4 only setups when FilterChainMatch is not @@ -917,16 +1078,6 @@ version_info: version1 nanos: 2000000 )EOF"); - // While it is in warming state, try updating the address. It should fail. - ListenerHandle* listener_foo3 = expectListenerCreate(true, true); - EXPECT_CALL(*listener_foo3, onDestroy()); - EXPECT_THROW_WITH_MESSAGE( - manager_->addOrUpdateListener(parseListenerFromV3Yaml(listener_foo_address_update_yaml), - "version3", true), - EnvoyException, - "error updating listener: 'foo' has a different address " - "'127.0.0.1:1235' from existing listener address '127.0.0.1:1234'"); - // Delete foo-listener again. EXPECT_CALL(*listener_foo2, onDestroy()); EXPECT_TRUE(manager_->removeListener("foo")); From f06269d02cb36686a2a1db777a15b70bb3229e29 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 5 May 2021 20:28:30 +0100 Subject: [PATCH 154/209] protos: Readd v2 legacy protos in v3 (#16338) Signed-off-by: Ryan Northey --- api/BUILD | 6 ++++++ docs/root/api-v3/config/filter/thrift/thrift.rst | 1 + docs/root/api-v3/config/health_checker/health_checker.rst | 1 + .../api-v3/config/resource_monitor/resource_monitor.rst | 1 + docs/root/api-v3/config/retry/retry.rst | 1 + generated_api_shadow/BUILD | 6 ++++++ 6 files changed, 16 insertions(+) diff --git a/api/BUILD b/api/BUILD index d8782fb96b230..7a6671dd681f9 100644 --- a/api/BUILD +++ b/api/BUILD @@ -133,12 +133,18 @@ proto_library( "//envoy/config/common/matcher/v3:pkg", "//envoy/config/core/v3:pkg", "//envoy/config/endpoint/v3:pkg", + "//envoy/config/filter/thrift/router/v2alpha1:pkg", "//envoy/config/grpc_credential/v3:pkg", + "//envoy/config/health_checker/redis/v2:pkg", "//envoy/config/listener/v3:pkg", "//envoy/config/metrics/v3:pkg", "//envoy/config/overload/v3:pkg", "//envoy/config/ratelimit/v3:pkg", "//envoy/config/rbac/v3:pkg", + "//envoy/config/resource_monitor/fixed_heap/v2alpha:pkg", + "//envoy/config/resource_monitor/injected_resource/v2alpha:pkg", + "//envoy/config/retry/omit_canary_hosts/v2:pkg", + "//envoy/config/retry/previous_hosts/v2:pkg", "//envoy/config/route/v3:pkg", "//envoy/config/tap/v3:pkg", "//envoy/config/trace/v3:pkg", diff --git a/docs/root/api-v3/config/filter/thrift/thrift.rst b/docs/root/api-v3/config/filter/thrift/thrift.rst index 688961fd16c1d..7e40e6f951942 100644 --- a/docs/root/api-v3/config/filter/thrift/thrift.rst +++ b/docs/root/api-v3/config/filter/thrift/thrift.rst @@ -5,4 +5,5 @@ Thrift filters :glob: :maxdepth: 2 + router/v2alpha1/* ../../../extensions/filters/network/thrift_proxy/**/v3/* diff --git a/docs/root/api-v3/config/health_checker/health_checker.rst b/docs/root/api-v3/config/health_checker/health_checker.rst index 4f4230a6137f5..b533d675ad7b1 100644 --- a/docs/root/api-v3/config/health_checker/health_checker.rst +++ b/docs/root/api-v3/config/health_checker/health_checker.rst @@ -5,4 +5,5 @@ Health checkers :glob: :maxdepth: 2 + */v2/* ../../extensions/health_checkers/*/v3/* diff --git a/docs/root/api-v3/config/resource_monitor/resource_monitor.rst b/docs/root/api-v3/config/resource_monitor/resource_monitor.rst index 767f4ffeb1827..c755d656cc8b3 100644 --- a/docs/root/api-v3/config/resource_monitor/resource_monitor.rst +++ b/docs/root/api-v3/config/resource_monitor/resource_monitor.rst @@ -8,3 +8,4 @@ Resource monitors :maxdepth: 2 ../../extensions/resource_monitors/*/v3/* + */v2alpha/* diff --git a/docs/root/api-v3/config/retry/retry.rst b/docs/root/api-v3/config/retry/retry.rst index b26870b1e9567..2ba4572ab2edc 100644 --- a/docs/root/api-v3/config/retry/retry.rst +++ b/docs/root/api-v3/config/retry/retry.rst @@ -6,4 +6,5 @@ Retry Predicates :maxdepth: 2 */empty/* + */v2/* ../../extensions/retry/**/v3/* diff --git a/generated_api_shadow/BUILD b/generated_api_shadow/BUILD index d8782fb96b230..7a6671dd681f9 100644 --- a/generated_api_shadow/BUILD +++ b/generated_api_shadow/BUILD @@ -133,12 +133,18 @@ proto_library( "//envoy/config/common/matcher/v3:pkg", "//envoy/config/core/v3:pkg", "//envoy/config/endpoint/v3:pkg", + "//envoy/config/filter/thrift/router/v2alpha1:pkg", "//envoy/config/grpc_credential/v3:pkg", + "//envoy/config/health_checker/redis/v2:pkg", "//envoy/config/listener/v3:pkg", "//envoy/config/metrics/v3:pkg", "//envoy/config/overload/v3:pkg", "//envoy/config/ratelimit/v3:pkg", "//envoy/config/rbac/v3:pkg", + "//envoy/config/resource_monitor/fixed_heap/v2alpha:pkg", + "//envoy/config/resource_monitor/injected_resource/v2alpha:pkg", + "//envoy/config/retry/omit_canary_hosts/v2:pkg", + "//envoy/config/retry/previous_hosts/v2:pkg", "//envoy/config/route/v3:pkg", "//envoy/config/tap/v3:pkg", "//envoy/config/trace/v3:pkg", From 9f2bb17be1fe970119e18dbe507dc2ddd31928e6 Mon Sep 17 00:00:00 2001 From: Bryce Anderson Date: Wed, 5 May 2021 15:47:08 -0600 Subject: [PATCH 155/209] Support starttls for client connections (#15443) Add support for starttls on the client side. This is exactly the same code as used for the server implementation, just reused. Risk level: Low Signed-off-by: Bryce Anderson --- .../starttls/v3/starttls.proto | 32 +- .../starttls/v4alpha/starttls.proto | 35 +- .../starttls/v3/starttls.proto | 32 +- .../starttls/v4alpha/starttls.proto | 35 +- include/envoy/network/transport_socket.h | 3 + .../transport_sockets/starttls/BUILD | 1 - .../transport_sockets/starttls/config.cc | 44 +- .../transport_sockets/starttls/config.h | 41 +- .../starttls/starttls_socket.cc | 6 +- .../starttls/starttls_socket.h | 18 +- test/config/utility.cc | 43 ++ test/config/utility.h | 3 + .../transport_sockets/starttls/BUILD | 20 + .../starttls/starttls_socket_test.cc | 14 +- .../upstream_starttls_integration_test.cc | 410 ++++++++++++++++++ 15 files changed, 660 insertions(+), 77 deletions(-) create mode 100644 test/extensions/transport_sockets/starttls/upstream_starttls_integration_test.cc diff --git a/api/envoy/extensions/transport_sockets/starttls/v3/starttls.proto b/api/envoy/extensions/transport_sockets/starttls/v3/starttls.proto index 2ebc9509ca071..69254819baf7b 100644 --- a/api/envoy/extensions/transport_sockets/starttls/v3/starttls.proto +++ b/api/envoy/extensions/transport_sockets/starttls/v3/starttls.proto @@ -17,19 +17,35 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.transport_sockets.starttls] // StartTls transport socket addresses situations when a protocol starts in clear-text and -// negotiates an in-band switch to TLS. StartTls transport socket is protocol agnostic and requires -// a network filter which understands protocol exchange and a state machine to signal to the StartTls -// transport socket when a switch to TLS is required. - -// Configuration for StartTls transport socket. +// negotiates an in-band switch to TLS. StartTls transport socket is protocol agnostic. In the +// case of downstream StartTls a network filter is required which understands protocol exchange +// and a state machine to signal to the StartTls transport socket when a switch to TLS is +// required. Similarly, upstream StartTls requires the owner of an upstream transport socket to +// manage the state machine necessary to properly coordinate negotiation with the upstream and +// signal to the transport socket when a switch to secure transport is required. + +// Configuration for a downstream StartTls transport socket. // StartTls transport socket wraps two sockets: -// - raw_buffer socket which is used at the beginning of the session -// - TLS socket used when a protocol negotiates a switch to encrypted traffic. +// * raw_buffer socket which is used at the beginning of the session +// * TLS socket used when a protocol negotiates a switch to encrypted traffic. message StartTlsConfig { // (optional) Configuration for clear-text socket used at the beginning of the session. raw_buffer.v3.RawBuffer cleartext_socket_config = 1; - // Configuration for TLS socket. + // Configuration for a downstream TLS socket. transport_sockets.tls.v3.DownstreamTlsContext tls_socket_config = 2 [(validate.rules).message = {required: true}]; } + +// Configuration for an upstream StartTls transport socket. +// StartTls transport socket wraps two sockets: +// * raw_buffer socket which is used at the beginning of the session +// * TLS socket used when a protocol negotiates a switch to encrypted traffic. +message UpstreamStartTlsConfig { + // (optional) Configuration for clear-text socket used at the beginning of the session. + raw_buffer.v3.RawBuffer cleartext_socket_config = 1; + + // Configuration for an upstream TLS socket. + transport_sockets.tls.v3.UpstreamTlsContext tls_socket_config = 2 + [(validate.rules).message = {required: true}]; +} diff --git a/api/envoy/extensions/transport_sockets/starttls/v4alpha/starttls.proto b/api/envoy/extensions/transport_sockets/starttls/v4alpha/starttls.proto index f35e57765c2a0..d2a9dbeaf2ed4 100644 --- a/api/envoy/extensions/transport_sockets/starttls/v4alpha/starttls.proto +++ b/api/envoy/extensions/transport_sockets/starttls/v4alpha/starttls.proto @@ -18,14 +18,17 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#extension: envoy.transport_sockets.starttls] // StartTls transport socket addresses situations when a protocol starts in clear-text and -// negotiates an in-band switch to TLS. StartTls transport socket is protocol agnostic and requires -// a network filter which understands protocol exchange and a state machine to signal to the StartTls -// transport socket when a switch to TLS is required. - -// Configuration for StartTls transport socket. +// negotiates an in-band switch to TLS. StartTls transport socket is protocol agnostic. In the +// case of downstream StartTls a network filter is required which understands protocol exchange +// and a state machine to signal to the StartTls transport socket when a switch to TLS is +// required. Similarly, upstream StartTls requires the owner of an upstream transport socket to +// manage the state machine necessary to properly coordinate negotiation with the upstream and +// signal to the transport socket when a switch to secure transport is required. + +// Configuration for a downstream StartTls transport socket. // StartTls transport socket wraps two sockets: -// - raw_buffer socket which is used at the beginning of the session -// - TLS socket used when a protocol negotiates a switch to encrypted traffic. +// * raw_buffer socket which is used at the beginning of the session +// * TLS socket used when a protocol negotiates a switch to encrypted traffic. message StartTlsConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.transport_sockets.starttls.v3.StartTlsConfig"; @@ -33,7 +36,23 @@ message StartTlsConfig { // (optional) Configuration for clear-text socket used at the beginning of the session. raw_buffer.v3.RawBuffer cleartext_socket_config = 1; - // Configuration for TLS socket. + // Configuration for a downstream TLS socket. transport_sockets.tls.v4alpha.DownstreamTlsContext tls_socket_config = 2 [(validate.rules).message = {required: true}]; } + +// Configuration for an upstream StartTls transport socket. +// StartTls transport socket wraps two sockets: +// * raw_buffer socket which is used at the beginning of the session +// * TLS socket used when a protocol negotiates a switch to encrypted traffic. +message UpstreamStartTlsConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.extensions.transport_sockets.starttls.v3.UpstreamStartTlsConfig"; + + // (optional) Configuration for clear-text socket used at the beginning of the session. + raw_buffer.v3.RawBuffer cleartext_socket_config = 1; + + // Configuration for an upstream TLS socket. + transport_sockets.tls.v4alpha.UpstreamTlsContext tls_socket_config = 2 + [(validate.rules).message = {required: true}]; +} diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/starttls/v3/starttls.proto b/generated_api_shadow/envoy/extensions/transport_sockets/starttls/v3/starttls.proto index 2ebc9509ca071..69254819baf7b 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/starttls/v3/starttls.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/starttls/v3/starttls.proto @@ -17,19 +17,35 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.transport_sockets.starttls] // StartTls transport socket addresses situations when a protocol starts in clear-text and -// negotiates an in-band switch to TLS. StartTls transport socket is protocol agnostic and requires -// a network filter which understands protocol exchange and a state machine to signal to the StartTls -// transport socket when a switch to TLS is required. - -// Configuration for StartTls transport socket. +// negotiates an in-band switch to TLS. StartTls transport socket is protocol agnostic. In the +// case of downstream StartTls a network filter is required which understands protocol exchange +// and a state machine to signal to the StartTls transport socket when a switch to TLS is +// required. Similarly, upstream StartTls requires the owner of an upstream transport socket to +// manage the state machine necessary to properly coordinate negotiation with the upstream and +// signal to the transport socket when a switch to secure transport is required. + +// Configuration for a downstream StartTls transport socket. // StartTls transport socket wraps two sockets: -// - raw_buffer socket which is used at the beginning of the session -// - TLS socket used when a protocol negotiates a switch to encrypted traffic. +// * raw_buffer socket which is used at the beginning of the session +// * TLS socket used when a protocol negotiates a switch to encrypted traffic. message StartTlsConfig { // (optional) Configuration for clear-text socket used at the beginning of the session. raw_buffer.v3.RawBuffer cleartext_socket_config = 1; - // Configuration for TLS socket. + // Configuration for a downstream TLS socket. transport_sockets.tls.v3.DownstreamTlsContext tls_socket_config = 2 [(validate.rules).message = {required: true}]; } + +// Configuration for an upstream StartTls transport socket. +// StartTls transport socket wraps two sockets: +// * raw_buffer socket which is used at the beginning of the session +// * TLS socket used when a protocol negotiates a switch to encrypted traffic. +message UpstreamStartTlsConfig { + // (optional) Configuration for clear-text socket used at the beginning of the session. + raw_buffer.v3.RawBuffer cleartext_socket_config = 1; + + // Configuration for an upstream TLS socket. + transport_sockets.tls.v3.UpstreamTlsContext tls_socket_config = 2 + [(validate.rules).message = {required: true}]; +} diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/starttls/v4alpha/starttls.proto b/generated_api_shadow/envoy/extensions/transport_sockets/starttls/v4alpha/starttls.proto index f35e57765c2a0..d2a9dbeaf2ed4 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/starttls/v4alpha/starttls.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/starttls/v4alpha/starttls.proto @@ -18,14 +18,17 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#extension: envoy.transport_sockets.starttls] // StartTls transport socket addresses situations when a protocol starts in clear-text and -// negotiates an in-band switch to TLS. StartTls transport socket is protocol agnostic and requires -// a network filter which understands protocol exchange and a state machine to signal to the StartTls -// transport socket when a switch to TLS is required. - -// Configuration for StartTls transport socket. +// negotiates an in-band switch to TLS. StartTls transport socket is protocol agnostic. In the +// case of downstream StartTls a network filter is required which understands protocol exchange +// and a state machine to signal to the StartTls transport socket when a switch to TLS is +// required. Similarly, upstream StartTls requires the owner of an upstream transport socket to +// manage the state machine necessary to properly coordinate negotiation with the upstream and +// signal to the transport socket when a switch to secure transport is required. + +// Configuration for a downstream StartTls transport socket. // StartTls transport socket wraps two sockets: -// - raw_buffer socket which is used at the beginning of the session -// - TLS socket used when a protocol negotiates a switch to encrypted traffic. +// * raw_buffer socket which is used at the beginning of the session +// * TLS socket used when a protocol negotiates a switch to encrypted traffic. message StartTlsConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.transport_sockets.starttls.v3.StartTlsConfig"; @@ -33,7 +36,23 @@ message StartTlsConfig { // (optional) Configuration for clear-text socket used at the beginning of the session. raw_buffer.v3.RawBuffer cleartext_socket_config = 1; - // Configuration for TLS socket. + // Configuration for a downstream TLS socket. transport_sockets.tls.v4alpha.DownstreamTlsContext tls_socket_config = 2 [(validate.rules).message = {required: true}]; } + +// Configuration for an upstream StartTls transport socket. +// StartTls transport socket wraps two sockets: +// * raw_buffer socket which is used at the beginning of the session +// * TLS socket used when a protocol negotiates a switch to encrypted traffic. +message UpstreamStartTlsConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.extensions.transport_sockets.starttls.v3.UpstreamStartTlsConfig"; + + // (optional) Configuration for clear-text socket used at the beginning of the session. + raw_buffer.v3.RawBuffer cleartext_socket_config = 1; + + // Configuration for an upstream TLS socket. + transport_sockets.tls.v4alpha.UpstreamTlsContext tls_socket_config = 2 + [(validate.rules).message = {required: true}]; +} diff --git a/include/envoy/network/transport_socket.h b/include/envoy/network/transport_socket.h index e71bcbed89d25..a7cec14f8ecf9 100644 --- a/include/envoy/network/transport_socket.h +++ b/include/envoy/network/transport_socket.h @@ -149,6 +149,9 @@ class TransportSocket { /** * Instructs a transport socket to start using secure transport. + * It is up to the caller of this method to manage the coordination between the client + * and server with regard to when to start secure transport. This is typically done via + * some signal message. See STARTTLS for an example of such negotiation. * Note: Not all transport sockets support such operation. * @return boolean indicating if the transport socket was able to start secure transport. */ diff --git a/source/extensions/transport_sockets/starttls/BUILD b/source/extensions/transport_sockets/starttls/BUILD index a286e46cad866..8748f049b9b7b 100644 --- a/source/extensions/transport_sockets/starttls/BUILD +++ b/source/extensions/transport_sockets/starttls/BUILD @@ -29,7 +29,6 @@ envoy_cc_extension( "//source/common/config:utility_lib", "//source/extensions/transport_sockets:well_known_names", "@envoy_api//envoy/extensions/transport_sockets/starttls/v3:pkg_cc_proto", - "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/transport_sockets/starttls/config.cc b/source/extensions/transport_sockets/starttls/config.cc index 6f5cd15f847af..2e06edd4ad4d1 100644 --- a/source/extensions/transport_sockets/starttls/config.cc +++ b/source/extensions/transport_sockets/starttls/config.cc @@ -1,11 +1,5 @@ #include "extensions/transport_sockets/starttls/config.h" -#include "envoy/extensions/transport_sockets/starttls/v3/starttls.pb.h" -#include "envoy/extensions/transport_sockets/starttls/v3/starttls.pb.validate.h" -#include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" - -#include "common/config/utility.h" - #include "extensions/transport_sockets/starttls/starttls_socket.h" namespace Envoy { @@ -20,33 +14,49 @@ Network::TransportSocketFactoryPtr DownstreamStartTlsSocketFactory::createTransp const envoy::extensions::transport_sockets::starttls::v3::StartTlsConfig&>( message, context.messageValidationVisitor()); - auto& raw_socket_config_factory = Config::Utility::getAndCheckFactoryByName< - Server::Configuration::DownstreamTransportSocketConfigFactory>( - TransportSocketNames::get().RawBuffer); + auto& raw_socket_config_factory = rawSocketConfigFactory(); + auto& tls_socket_config_factory = tlsSocketConfigFactory(); Network::TransportSocketFactoryPtr raw_socket_factory = raw_socket_config_factory.createTransportSocketFactory(outer_config.cleartext_socket_config(), context, server_names); - auto& tls_socket_config_factory = Config::Utility::getAndCheckFactoryByName< - Server::Configuration::DownstreamTransportSocketConfigFactory>( - TransportSocketNames::get().Tls); - Network::TransportSocketFactoryPtr tls_socket_factory = tls_socket_config_factory.createTransportSocketFactory(outer_config.tls_socket_config(), context, server_names); - return std::make_unique(outer_config, std::move(raw_socket_factory), - std::move(tls_socket_factory)); + return std::make_unique(std::move(raw_socket_factory), + std::move(tls_socket_factory)); } -ProtobufTypes::MessagePtr DownstreamStartTlsSocketFactory::createEmptyConfigProto() { - return std::make_unique(); +Network::TransportSocketFactoryPtr UpstreamStartTlsSocketFactory::createTransportSocketFactory( + const Protobuf::Message& message, + Server::Configuration::TransportSocketFactoryContext& context) { + + const auto& outer_config = MessageUtil::downcastAndValidate< + const envoy::extensions::transport_sockets::starttls::v3::UpstreamStartTlsConfig&>( + message, context.messageValidationVisitor()); + auto& raw_socket_config_factory = rawSocketConfigFactory(); + auto& tls_socket_config_factory = tlsSocketConfigFactory(); + + Network::TransportSocketFactoryPtr raw_socket_factory = + raw_socket_config_factory.createTransportSocketFactory(outer_config.cleartext_socket_config(), + context); + + Network::TransportSocketFactoryPtr tls_socket_factory = + tls_socket_config_factory.createTransportSocketFactory(outer_config.tls_socket_config(), + context); + + return std::make_unique(std::move(raw_socket_factory), + std::move(tls_socket_factory)); } REGISTER_FACTORY(DownstreamStartTlsSocketFactory, Server::Configuration::DownstreamTransportSocketConfigFactory){"starttls"}; +REGISTER_FACTORY(UpstreamStartTlsSocketFactory, + Server::Configuration::UpstreamTransportSocketConfigFactory){"starttls"}; + } // namespace StartTls } // namespace TransportSockets } // namespace Extensions diff --git a/source/extensions/transport_sockets/starttls/config.h b/source/extensions/transport_sockets/starttls/config.h index eb1508ec7f474..89765fc5f1cfa 100644 --- a/source/extensions/transport_sockets/starttls/config.h +++ b/source/extensions/transport_sockets/starttls/config.h @@ -1,8 +1,11 @@ #pragma once +#include "envoy/extensions/transport_sockets/starttls/v3/starttls.pb.h" #include "envoy/registry/registry.h" #include "envoy/server/transport_socket_config.h" +#include "common/config/utility.h" + #include "extensions/transport_sockets/well_known_names.h" namespace Envoy { @@ -10,18 +13,50 @@ namespace Extensions { namespace TransportSockets { namespace StartTls { +template +class BaseStartTlsSocketFactory : public ConfigFactory { +public: + std::string name() const override { return TransportSocketNames::get().StartTls; } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + +protected: + ConfigFactory& rawSocketConfigFactory() { + return Config::Utility::getAndCheckFactoryByName( + TransportSocketNames::get().RawBuffer); + } + + ConfigFactory& tlsSocketConfigFactory() { + return Config::Utility::getAndCheckFactoryByName( + TransportSocketNames::get().Tls); + } +}; + class DownstreamStartTlsSocketFactory - : public Server::Configuration::DownstreamTransportSocketConfigFactory { + : public BaseStartTlsSocketFactory< + Server::Configuration::DownstreamTransportSocketConfigFactory, + envoy::extensions::transport_sockets::starttls::v3::StartTlsConfig> { public: Network::TransportSocketFactoryPtr createTransportSocketFactory(const Protobuf::Message& config, Server::Configuration::TransportSocketFactoryContext& context, const std::vector& server_names) override; - ProtobufTypes::MessagePtr createEmptyConfigProto() override; - std::string name() const override { return TransportSocketNames::get().StartTls; } +}; + +class UpstreamStartTlsSocketFactory + : public BaseStartTlsSocketFactory< + Server::Configuration::UpstreamTransportSocketConfigFactory, + envoy::extensions::transport_sockets::starttls::v3::UpstreamStartTlsConfig> { +public: + Network::TransportSocketFactoryPtr createTransportSocketFactory( + const Protobuf::Message& config, + Server::Configuration::TransportSocketFactoryContext& context) override; }; DECLARE_FACTORY(DownstreamStartTlsSocketFactory); +DECLARE_FACTORY(UpstreamStartTlsSocketFactory); } // namespace StartTls } // namespace TransportSockets diff --git a/source/extensions/transport_sockets/starttls/starttls_socket.cc b/source/extensions/transport_sockets/starttls/starttls_socket.cc index 84b4d5cde3dd0..c87df0748d766 100644 --- a/source/extensions/transport_sockets/starttls/starttls_socket.cc +++ b/source/extensions/transport_sockets/starttls/starttls_socket.cc @@ -1,7 +1,5 @@ #include "extensions/transport_sockets/starttls/starttls_socket.h" -#include - namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -24,10 +22,10 @@ bool StartTlsSocket::startSecureTransport() { return true; } -Network::TransportSocketPtr ServerStartTlsSocketFactory::createTransportSocket( +Network::TransportSocketPtr StartTlsSocketFactory::createTransportSocket( Network::TransportSocketOptionsSharedPtr transport_socket_options) const { return std::make_unique( - config_, raw_socket_factory_->createTransportSocket(transport_socket_options), + raw_socket_factory_->createTransportSocket(transport_socket_options), tls_socket_factory_->createTransportSocket(transport_socket_options), transport_socket_options); } diff --git a/source/extensions/transport_sockets/starttls/starttls_socket.h b/source/extensions/transport_sockets/starttls/starttls_socket.h index cb35e4d00eadf..dc84d776be0dc 100644 --- a/source/extensions/transport_sockets/starttls/starttls_socket.h +++ b/source/extensions/transport_sockets/starttls/starttls_socket.h @@ -18,8 +18,7 @@ namespace StartTls { class StartTlsSocket : public Network::TransportSocket, Logger::Loggable { public: - StartTlsSocket(const envoy::extensions::transport_sockets::starttls::v3::StartTlsConfig&, - Network::TransportSocketPtr raw_socket, // RawBufferSocket + StartTlsSocket(Network::TransportSocketPtr raw_socket, // RawBufferSocket Network::TransportSocketPtr tls_socket, // TlsSocket const Network::TransportSocketOptionsSharedPtr&) : active_socket_(std::move(raw_socket)), tls_socket_(std::move(tls_socket)) {} @@ -66,17 +65,15 @@ class StartTlsSocket : public Network::TransportSocket, Logger::Loggable { +class StartTlsSocketFactory : public Network::TransportSocketFactory, + Logger::Loggable { public: - ~ServerStartTlsSocketFactory() override = default; + ~StartTlsSocketFactory() override = default; - ServerStartTlsSocketFactory( - const envoy::extensions::transport_sockets::starttls::v3::StartTlsConfig& config, - Network::TransportSocketFactoryPtr raw_socket_factory, - Network::TransportSocketFactoryPtr tls_socket_factory) + StartTlsSocketFactory(Network::TransportSocketFactoryPtr raw_socket_factory, + Network::TransportSocketFactoryPtr tls_socket_factory) : raw_socket_factory_(std::move(raw_socket_factory)), - tls_socket_factory_(std::move(tls_socket_factory)), config_(config) {} + tls_socket_factory_(std::move(tls_socket_factory)) {} Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; @@ -86,7 +83,6 @@ class ServerStartTlsSocketFactory : public Network::TransportSocketFactory, private: Network::TransportSocketFactoryPtr raw_socket_factory_; Network::TransportSocketFactoryPtr tls_socket_factory_; - envoy::extensions::transport_sockets::starttls::v3::StartTlsConfig config_; }; } // namespace StartTls diff --git a/test/config/utility.cc b/test/config/utility.cc index 1c0e08b53355b..5668902035761 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -146,6 +146,49 @@ std::string ConfigHelper::startTlsConfig() { TestEnvironment::runfilesPath("test/config/integration/certs/serverkey.pem"))); } +envoy::config::cluster::v3::Cluster ConfigHelper::buildStartTlsCluster(const std::string& address, + int port) { + API_NO_BOOST(envoy::config::cluster::v3::Cluster) cluster; + auto config_str = fmt::format( + R"EOF( + name: dummy_cluster + connect_timeout: 5s + type: STATIC + load_assignment: + cluster_name: dummy_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: {} + port_value: {} + transport_socket: + name: "starttls" + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.starttls.v3.UpstreamStartTlsConfig + cleartext_socket_config: + tls_socket_config: + common_tls_context: + tls_certificates: + certificate_chain: + filename: {} + private_key: + filename: {} + lb_policy: ROUND_ROBIN + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http2_protocol_options: {{}} + )EOF", + address, port, TestEnvironment::runfilesPath("test/config/integration/certs/clientcert.pem"), + TestEnvironment::runfilesPath("test/config/integration/certs/clientkey.pem")); + + TestUtility::loadFromYaml(config_str, cluster); + return cluster; +} + std::string ConfigHelper::tlsInspectorFilter() { return R"EOF( name: "envoy.filters.listener.tls_inspector" diff --git a/test/config/utility.h b/test/config/utility.h index f73a3ff311fd8..f2b6ccc38b284 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -137,6 +137,9 @@ class ConfigHelper { static std::string defaultSquashFilter(); // A string for startTls transport socket config. static std::string startTlsConfig(); + // A cluster that uses the startTls transport socket. + static envoy::config::cluster::v3::Cluster buildStartTlsCluster(const std::string& address, + int port); // Configuration for L7 proxying, with clusters cluster_1 and cluster_2 meant to be added via CDS. // api_type should be REST, GRPC, or DELTA_GRPC. diff --git a/test/extensions/transport_sockets/starttls/BUILD b/test/extensions/transport_sockets/starttls/BUILD index a54e9b757c205..64883b0c14201 100644 --- a/test/extensions/transport_sockets/starttls/BUILD +++ b/test/extensions/transport_sockets/starttls/BUILD @@ -50,3 +50,23 @@ envoy_extension_cc_test( "@envoy_api//envoy/extensions/transport_sockets/raw_buffer/v3:pkg_cc_proto", ], ) + +envoy_extension_cc_test( + name = "upstream_starttls_integration_test", + srcs = [ + "upstream_starttls_integration_test.cc", + ], + data = [ + "//test/config/integration/certs", + ], + extension_name = "envoy.transport_sockets.starttls", + deps = [ + ":starttls_integration_proto_cc_proto", + "//source/extensions/filters/network/tcp_proxy:config", + "//source/extensions/transport_sockets/raw_buffer:config", + "//source/extensions/transport_sockets/starttls:config", + "//test/integration:integration_lib", + "//test/test_common:registry_lib", + "@envoy_api//envoy/extensions/transport_sockets/raw_buffer/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/transport_sockets/starttls/starttls_socket_test.cc b/test/extensions/transport_sockets/starttls/starttls_socket_test.cc index bac41cb5f1495..23f0a19428124 100644 --- a/test/extensions/transport_sockets/starttls/starttls_socket_test.cc +++ b/test/extensions/transport_sockets/starttls/starttls_socket_test.cc @@ -26,7 +26,6 @@ class StartTlsTransportSocketMock : public Network::MockTransportSocket { }; TEST(StartTlsTest, BasicSwitch) { - const envoy::extensions::transport_sockets::starttls::v3::StartTlsConfig config; Network::TransportSocketOptionsSharedPtr options = std::make_shared(); NiceMock transport_callbacks; @@ -34,9 +33,8 @@ TEST(StartTlsTest, BasicSwitch) { Network::MockTransportSocket* ssl_socket = new Network::MockTransportSocket; Buffer::OwnedImpl buf; - std::unique_ptr socket = - std::make_unique(config, Network::TransportSocketPtr(raw_socket), - Network::TransportSocketPtr(ssl_socket), options); + std::unique_ptr socket = std::make_unique( + Network::TransportSocketPtr(raw_socket), Network::TransportSocketPtr(ssl_socket), options); socket->setTransportSocketCallbacks(transport_callbacks); // StartTls socket is initial clear-text state. All calls should be forwarded to raw socket. @@ -108,15 +106,13 @@ TEST(StartTlsTest, BasicSwitch) { // Factory test. TEST(StartTls, BasicFactoryTest) { - const envoy::extensions::transport_sockets::starttls::v3::StartTlsConfig config; NiceMock* raw_buffer_factory = new NiceMock; NiceMock* ssl_factory = new NiceMock; - std::unique_ptr factory = - std::make_unique( - config, Network::TransportSocketFactoryPtr(raw_buffer_factory), - Network::TransportSocketFactoryPtr(ssl_factory)); + std::unique_ptr factory = std::make_unique( + Network::TransportSocketFactoryPtr(raw_buffer_factory), + Network::TransportSocketFactoryPtr(ssl_factory)); ASSERT_FALSE(factory->implementsSecureTransport()); ASSERT_FALSE(factory->usesProxyProtocolOptions()); } diff --git a/test/extensions/transport_sockets/starttls/upstream_starttls_integration_test.cc b/test/extensions/transport_sockets/starttls/upstream_starttls_integration_test.cc new file mode 100644 index 0000000000000..df86c4f17e4aa --- /dev/null +++ b/test/extensions/transport_sockets/starttls/upstream_starttls_integration_test.cc @@ -0,0 +1,410 @@ +#include "envoy/extensions/transport_sockets/raw_buffer/v3/raw_buffer.pb.h" +#include "envoy/extensions/transport_sockets/raw_buffer/v3/raw_buffer.pb.validate.h" +#include "envoy/network/filter.h" +#include "envoy/server/filter_config.h" + +#include "common/network/connection_impl.h" + +#include "extensions/filters/network/common/factory_base.h" +#include "extensions/transport_sockets/raw_buffer/config.h" +#include "extensions/transport_sockets/starttls/starttls_socket.h" + +#include "test/config/utility.h" +#include "test/extensions/transport_sockets/starttls/starttls_integration_test.pb.h" +#include "test/extensions/transport_sockets/starttls/starttls_integration_test.pb.validate.h" +#include "test/integration/integration.h" +#include "test/integration/ssl_utility.h" +#include "test/test_common/registry.h" + +#include "gtest/gtest.h" + +namespace Envoy { + +class TerminalServerTlsFilter : public Network::ReadFilter { +public: + // Network::ReadFilter + Network::FilterStatus onData(Buffer::Instance& buf, bool) override { + auto message = buf.toString(); + if (message != "usetls") { + // Just echo anything other than the 'usetls' command. + read_callbacks_->connection().write(buf, false); + } else { + read_callbacks_->connection().addBytesSentCallback([=](uint64_t bytes) -> bool { + // Wait until 6 bytes long "usetls" has been sent. + if (bytes >= 6) { + read_callbacks_->connection().startSecureTransport(); + // Unsubscribe the callback. + // Switch to tls has been completed. + return false; + } + return true; + }); + + buf.drain(buf.length()); + buf.add("switch"); + read_callbacks_->connection().write(buf, false); + } + + return Network::FilterStatus::StopIteration; + } + + Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; } + + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override { + read_callbacks_ = &callbacks; + } + +private: + Network::ReadFilterCallbacks* read_callbacks_{}; +}; + +// Simple filter for test purposes. This filter will be injected into the filter chain during +// tests. The filter reacts only to few keywords. If received payload does not contain +// allowed keyword, filter will stop iteration. +// The filter will be configured to sit on top of tcp_proxy and use start-tls transport socket. +// If it receives a data which is not known keyword it means that transport socket has not been +// successfully converted to use TLS and filter receives either encrypted data or TLS handshake +// messages. +class StartTlsSwitchFilter : public Network::ReadFilter { +public: + ~StartTlsSwitchFilter() override { + if (upstream_connection_) { + upstream_connection_->close(Network::ConnectionCloseType::NoFlush); + } + } + + void upstreamWrite(Buffer::Instance& data, bool end_stream); + + // Network::ReadFilter + Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override; + Network::FilterStatus onNewConnection() override; + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override { + read_callbacks_ = &callbacks; + } + + static std::shared_ptr + newInstance(Upstream::ClusterManager& cluster_manager) { + auto p = std::shared_ptr(new StartTlsSwitchFilter(cluster_manager)); + p->self_ = p; + return p; + } + + struct UpstreamReadFilter : public Network::ReadFilter { + + UpstreamReadFilter(std::weak_ptr parent) : parent_(parent) {} + + Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override { + if (auto parent = parent_.lock()) { + parent->upstreamWrite(data, end_stream); + return Network::FilterStatus::Continue; + } else { + read_callbacks_->connection().close(Network::ConnectionCloseType::FlushWrite); + return Network::FilterStatus::StopIteration; + } + } + + Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; } + + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override { + read_callbacks_ = &callbacks; + } + + std::weak_ptr parent_{}; + Network::ReadFilterCallbacks* read_callbacks_{}; + }; + +private: + StartTlsSwitchFilter(Upstream::ClusterManager& cluster_manager) + : cluster_manager_(cluster_manager) {} + + std::weak_ptr self_{}; + Network::ReadFilterCallbacks* read_callbacks_{}; + Network::ClientConnectionPtr upstream_connection_{}; + Upstream::ClusterManager& cluster_manager_; +}; + +Network::FilterStatus StartTlsSwitchFilter::onNewConnection() { + auto c = cluster_manager_.getThreadLocalCluster("dummy_cluster"); + auto h = c->loadBalancer().chooseHost(nullptr); + upstream_connection_ = + h->createConnection(read_callbacks_->connection().dispatcher(), nullptr, nullptr).connection_; + upstream_connection_->addReadFilter(std::make_shared(self_)); + upstream_connection_->connect(); + return Network::FilterStatus::Continue; +} + +Network::FilterStatus StartTlsSwitchFilter::onData(Buffer::Instance& data, bool end_stream) { + if (end_stream) { + upstream_connection_->close(Network::ConnectionCloseType::FlushWrite); + return Network::FilterStatus::StopIteration; + } + + upstream_connection_->write(data, end_stream); + return Network::FilterStatus::Continue; +} + +void StartTlsSwitchFilter::upstreamWrite(Buffer::Instance& buf, bool end_stream) { + const std::string message = buf.toString(); + if (message == "switch") { + // Start the upstream secure transport immediately since we clearly have all the bytes + ASSERT_TRUE(upstream_connection_->startSecureTransport()); + read_callbacks_->connection().addBytesSentCallback([=](uint64_t bytes) -> bool { + // Wait until 6 bytes long "switch" has been sent. + if (bytes >= 6) { + read_callbacks_->connection().startSecureTransport(); + // Unsubscribe the callback. + // Switch to tls has been completed. + return false; + } + return true; + }); + } + // Finally just forward the data downstream. + read_callbacks_->connection().write(buf, end_stream); +} + +// Config factory for StartTlsSwitchFilter. +class StartTlsSwitchFilterConfigFactory : public Extensions::NetworkFilters::Common::FactoryBase< + test::integration::starttls::StartTlsFilterConfig> { +public: + explicit StartTlsSwitchFilterConfigFactory(const std::string& name) : FactoryBase(name) {} + + Network::FilterFactoryCb + createFilterFactoryFromProtoTyped(const test::integration::starttls::StartTlsFilterConfig&, + Server::Configuration::FactoryContext& context) override { + return [&](Network::FilterManager& filter_manager) -> void { + filter_manager.addReadFilter(StartTlsSwitchFilter::newInstance(context.clusterManager())); + }; + } + + std::string name() const override { return name_; } + +private: + const std::string name_; +}; + +// ClientTestConnection is used for simulating a client +// which initiates a connection to Envoy in clear-text and then switches to TLS +// without closing the socket. +class ClientTestConnection : public Network::ClientConnectionImpl { +public: + ClientTestConnection(Event::Dispatcher& dispatcher, + const Network::Address::InstanceConstSharedPtr& remote_address, + const Network::Address::InstanceConstSharedPtr& source_address, + Network::TransportSocketPtr&& transport_socket, + const Network::ConnectionSocket::OptionsSharedPtr& options) + : ClientConnectionImpl(dispatcher, remote_address, source_address, + std::move(transport_socket), options) {} + + void setTransportSocket(Network::TransportSocketPtr&& transport_socket) { + transport_socket_ = std::move(transport_socket); + transport_socket_->setTransportSocketCallbacks(*this); + + // Reset connection's state machine. + connecting_ = true; + + // Issue event which will trigger TLS handshake. + ioHandle().activateFileEvents(Event::FileReadyType::Write); + } +}; + +// Fixture class for integration tests. +class StartTlsIntegrationTest : public testing::TestWithParam, + public BaseIntegrationTest { +public: + StartTlsIntegrationTest() + : BaseIntegrationTest(GetParam(), ConfigHelper::startTlsConfig()), + stream_info_(timeSystem(), nullptr) {} + void initialize() override; + void addStartTlsSwitchFilter(ConfigHelper& config_helper); + + // Contexts needed by raw buffer and tls transport sockets. + std::unique_ptr tls_context_manager_; + Network::TransportSocketFactoryPtr tls_context_; + Network::TransportSocketFactoryPtr cleartext_context_; + + MockWatermarkBuffer* client_write_buffer_{nullptr}; + ConnectionStatusCallbacks connect_callbacks_; + + // Config factory for StartTlsSwitchFilter. + StartTlsSwitchFilterConfigFactory config_factory_{"startTls"}; + Registry::InjectFactory + registered_config_factory_{config_factory_}; + + std::unique_ptr conn_; + std::shared_ptr payload_reader_; + Network::ListenerPtr listener_; + Network::MockTcpListenerCallbacks listener_callbacks_; + Network::ServerConnectionPtr server_connection_; + + // Technically unused. + StreamInfo::StreamInfoImpl stream_info_; +}; + +void StartTlsIntegrationTest::initialize() { + EXPECT_CALL(*mock_buffer_factory_, create_(_, _, _)) + // Connection constructor will first create write buffer. + // Test tracks how many bytes are sent. + .WillOnce(Invoke([&](std::function below_low, std::function above_high, + std::function above_overflow) -> Buffer::Instance* { + client_write_buffer_ = + new NiceMock(below_low, above_high, above_overflow); + ON_CALL(*client_write_buffer_, move(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); + ON_CALL(*client_write_buffer_, drain(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); + return client_write_buffer_; + })) + // Connection constructor will also create read buffer, but the test does + // not track received bytes. + .WillRepeatedly(Invoke([&](std::function below_low, std::function above_high, + std::function above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); + })); + + config_helper_.renameListener("tcp_proxy"); + addStartTlsSwitchFilter(config_helper_); + + // Setup factories and contexts for upstream clear-text raw buffer transport socket. + auto config = std::make_unique(); + + auto factory = + std::make_unique(); + cleartext_context_ = Network::TransportSocketFactoryPtr{ + factory->createTransportSocketFactory(*config, factory_context_)}; + + // Setup factories and contexts for tls transport socket. + tls_context_manager_ = + std::make_unique(timeSystem()); + tls_context_ = Ssl::createClientSslTransportSocketFactory({}, *tls_context_manager_, *api_); + payload_reader_ = std::make_shared(*dispatcher_); + + auto socket = std::make_shared( + Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); + + // Prepare for the server side listener + EXPECT_CALL(listener_callbacks_, onAccept_(_)) + .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket) -> void { + auto server_tls_context_ = Ssl::createUpstreamSslContext(*tls_context_manager_, *api_); + + auto startTlsTransportSocket = + Extensions::TransportSockets::StartTls::StartTlsSocketFactory(move(cleartext_context_), + move(server_tls_context_)) + .createTransportSocket(std::make_shared( + absl::string_view(""), std::vector(), std::vector())); + + server_connection_ = dispatcher_->createServerConnection( + std::move(socket), move(startTlsTransportSocket), stream_info_); + server_connection_->addReadFilter(std::make_shared()); + })); + + listener_ = + dispatcher_->createListener(socket, listener_callbacks_, true, ENVOY_TCP_BACKLOG_SIZE); + + // Add a start_tls cluster + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + *bootstrap.mutable_static_resources()->add_clusters() = ConfigHelper::buildStartTlsCluster( + socket->addressProvider().localAddress()->ip()->addressAsString(), + socket->addressProvider().localAddress()->ip()->port()); + }); + + BaseIntegrationTest::initialize(); + + Network::Address::InstanceConstSharedPtr address = + Ssl::getSslAddress(version_, lookupPort("tcp_proxy")); + + conn_ = std::make_unique( + *dispatcher_, address, Network::Address::InstanceConstSharedPtr(), + cleartext_context_->createTransportSocket( + std::make_shared( + absl::string_view(""), std::vector(), std::vector())), + nullptr); + + conn_->enableHalfClose(true); + conn_->addConnectionCallbacks(connect_callbacks_); + conn_->addReadFilter(payload_reader_); +} + +// Method adds StartTlsSwitchFilter into the filter chain. +// The filter is required to instruct StartTls transport +// socket to start using Tls. +void StartTlsIntegrationTest::addStartTlsSwitchFilter(ConfigHelper& config_helper) { + config_helper.addNetworkFilter(R"EOF( + name: startTls + typed_config: + "@type": type.googleapis.com/test.integration.starttls.StartTlsFilterConfig + )EOF"); + // double-check the filter was actually added + config_helper.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + ASSERT_EQ("startTls", + bootstrap.static_resources().listeners(0).filter_chains(0).filters(0).name()); + }); +} + +// Test creates a clear-text connection from a client to Envoy and sends several messages. +// Then a special message is sent, which causes StartTlsSwitchFilter to +// instruct StartTls transport socket to start using tls for both the upstream and +// downstream. Connections. The Client connection starts using tls, performs tls handshake +// and a message is sent over tls. start-tls transport socket de-crypts the messages and +// forwards them upstream over yet another start-tls transport. +TEST_P(StartTlsIntegrationTest, SwitchToTlsFromClient) { + initialize(); + + // Open clear-text connection. + conn_->connect(); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + + ASSERT_THAT(test_server_->server().listenerManager().numConnections(), 1); + + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + // Send a message to switch to tls on the receiver side. + // StartTlsSwitchFilter will switch transport socket on the + // upstream side upon receiving "switch" message and forward + // back the message "switch" to this client. + payload_reader_->set_data_to_wait_for("switch"); + Buffer::OwnedImpl buffer; + buffer.add("usetls"); + conn_->write(buffer, false); + while (client_write_buffer_->bytesDrained() != 6) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + ASSERT_TRUE(payload_reader_->waitForLength(6, std::chrono::milliseconds(100000))); + + // Make sure we received the 'switch' command from the upstream. + ASSERT_EQ("switch", payload_reader_->data()); + payload_reader_->clearData(); + + // Without closing the connection, switch to tls. + conn_->setTransportSocket( + tls_context_->createTransportSocket(std::make_shared( + absl::string_view(""), std::vector(), std::vector()))); + connect_callbacks_.reset(); + + while (!connect_callbacks_.connected() && !connect_callbacks_.closed()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + // // Send few messages over the encrypted connection. + buffer.add("hola"); + conn_->write(buffer, false); + while (client_write_buffer_->bytesDrained() != 10) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + // Make sure we get our echo back + ASSERT_TRUE(payload_reader_->waitForLength(4, std::chrono::milliseconds(100000))); + ASSERT_EQ("hola", payload_reader_->data()); + payload_reader_->clearData(); + + conn_->close(Network::ConnectionCloseType::FlushWrite); + server_connection_->close(Network::ConnectionCloseType::FlushWrite); +} + +INSTANTIATE_TEST_SUITE_P(StartTlsIntegrationTestSuite, StartTlsIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); + +} // namespace Envoy From b7ce74e8cc8d882ef23146c5039712e84796950e Mon Sep 17 00:00:00 2001 From: Greg Brail Date: Wed, 5 May 2021 14:48:06 -0700 Subject: [PATCH 156/209] ext_proc: Support clearing the route cache (#16288) Commit Message: Support the clear_route_cache parameter on responses from the remote server Risk Level: Low. Only enabled if the flag is set. Testing: New unit test to ensure that the method is called. Docs Changes: Marked "clear_route_cache" in the API as no longer "not-implemented". Release Notes: If the clear_route_cache flag is set on a response from the external processing server, then the filter will call the "clearRouteCache" method on the filter state. Processors should set this flag if they have changed any headers, such as ":path", which may affect routing after the filter runs. Signed-off-by: Gregory Brail --- .../ext_proc/v3alpha/external_processor.proto | 1 - .../ext_proc/v3alpha/external_processor.proto | 1 - .../filters/http/ext_proc/processor_state.cc | 6 +++ .../filters/http/ext_proc/filter_test.cc | 43 +++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/api/envoy/service/ext_proc/v3alpha/external_processor.proto b/api/envoy/service/ext_proc/v3alpha/external_processor.proto index 3246a3c3bdab3..e57ea2da9e409 100644 --- a/api/envoy/service/ext_proc/v3alpha/external_processor.proto +++ b/api/envoy/service/ext_proc/v3alpha/external_processor.proto @@ -264,7 +264,6 @@ message CommonResponse { // along with the CONTINUE_AND_REPLACE status. config.core.v3.HeaderMap trailers = 4; - // [#not-implemented-hide:] // Clear the route cache for the current request. // This is necessary if the remote server // modified headers that are used to calculate the route. diff --git a/generated_api_shadow/envoy/service/ext_proc/v3alpha/external_processor.proto b/generated_api_shadow/envoy/service/ext_proc/v3alpha/external_processor.proto index 3246a3c3bdab3..e57ea2da9e409 100644 --- a/generated_api_shadow/envoy/service/ext_proc/v3alpha/external_processor.proto +++ b/generated_api_shadow/envoy/service/ext_proc/v3alpha/external_processor.proto @@ -264,7 +264,6 @@ message CommonResponse { // along with the CONTINUE_AND_REPLACE status. config.core.v3.HeaderMap trailers = 4; - // [#not-implemented-hide:] // Clear the route cache for the current request. // This is necessary if the remote server // modified headers that are used to calculate the route. diff --git a/source/extensions/filters/http/ext_proc/processor_state.cc b/source/extensions/filters/http/ext_proc/processor_state.cc index 3d60779a42295..19b0dc8c4ec16 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.cc +++ b/source/extensions/filters/http/ext_proc/processor_state.cc @@ -25,6 +25,9 @@ bool ProcessorState::handleHeadersResponse(const HeadersResponse& response) { if (callback_state_ == CallbackState::HeadersCallback) { ENVOY_LOG(debug, "applying headers response"); MutationUtils::applyCommonHeaderResponse(response, *headers_); + if (response.response().clear_route_cache()) { + filter_callbacks_->clearRouteCache(); + } callback_state_ = CallbackState::Idle; clearWatermark(); message_timer_->disableTimer(); @@ -66,6 +69,9 @@ bool ProcessorState::handleBodyResponse(const BodyResponse& response) { modifyBufferedData([this, &response](Buffer::Instance& data) { MutationUtils::applyCommonBodyResponse(response, headers_, data); }); + if (response.response().clear_route_cache()) { + filter_callbacks_->clearRouteCache(); + } headers_ = nullptr; callback_state_ = CallbackState::Idle; message_timer_->disableTimer(); diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index 5f440cd5ea921..5fb454b97f1ce 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -1276,6 +1276,49 @@ TEST_F(HttpFilterTest, ProcessingModeResponseHeadersOnly) { EXPECT_EQ(1, config_->stats().streams_closed_.value()); } +// Using the default configuration, verify that the "clear_route_cache_ flag makes the appropriate +// callback on the filter. +TEST_F(HttpFilterTest, ClearRouteCache) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + response_body_mode: "BUFFERED" + )EOF"); + + // Create synthetic HTTP request + HttpTestUtility::addDefaultHeaders(request_headers_, "GET"); + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, true)); + + EXPECT_CALL(decoder_callbacks_, clearRouteCache()); + processRequestHeaders(false, [](const HttpHeaders&, ProcessingResponse&, HeadersResponse& resp) { + resp.mutable_response()->set_clear_route_cache(true); + }); + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false)); + processResponseHeaders(true, absl::nullopt); + + Buffer::OwnedImpl resp_data("foo"); + Buffer::OwnedImpl buffered_response_data; + setUpEncodingBuffering(buffered_response_data); + + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_data, true)); + + EXPECT_CALL(encoder_callbacks_, clearRouteCache()); + processResponseBody([](const HttpBody&, ProcessingResponse&, BodyResponse& resp) { + resp.mutable_response()->set_clear_route_cache(true); + }); + + filter_->onDestroy(); + + EXPECT_EQ(1, config_->stats().streams_started_.value()); + EXPECT_EQ(3, config_->stats().stream_msgs_sent_.value()); + EXPECT_EQ(3, config_->stats().stream_msgs_received_.value()); + EXPECT_EQ(1, config_->stats().streams_closed_.value()); +} + // Using the default configuration, test the filter with a processor that // replies to the request_headers message incorrectly by sending a // request_body message, which should result in the stream being closed From 889769e6446672b4f58465f09e2c9d3f171747f7 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 5 May 2021 17:54:06 -0400 Subject: [PATCH 157/209] test: disabling flaky test (#16341) Signed-off-by: Alyssa Wilk --- test/integration/multiplexed_integration_test.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index 5700f535fb408..433790b46d7dd 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -79,6 +79,8 @@ TEST_P(Http2IntegrationTest, FlowControlOnAndGiantBodyWithContentLength) { } TEST_P(Http2IntegrationTest, LargeFlowControlOnAndGiantBodyWithContentLength) { + // https://github.com/envoyproxy/envoy/issues/16335 + EXCLUDE_DOWNSTREAM_HTTP3; config_helper_.addConfigModifier(ConfigHelper::adjustUpstreamTimeoutForTsan); config_helper_.setBufferLimits(128 * 1024, 128 * 1024); // Set buffer limits upstream and downstream. From 9d846501bc388d0fd614ff04ed496c075d2bf94a Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Wed, 5 May 2021 18:06:28 -0400 Subject: [PATCH 158/209] Cleanup: Remove WatermarkBuffer::setWatermarks(low,high). (#16307) Signed-off-by: Kevin Baichoo --- source/common/buffer/watermark_buffer.cc | 5 +-- source/common/buffer/watermark_buffer.h | 3 +- source/common/http/http2/codec_impl.cc | 2 +- source/common/http/http2/codec_impl.h | 6 +-- test/common/buffer/watermark_buffer_test.cc | 48 ++++++++++----------- test/common/http/http2/codec_impl_test.cc | 2 +- test/mocks/http/stream.h | 2 +- 7 files changed, 33 insertions(+), 35 deletions(-) diff --git a/source/common/buffer/watermark_buffer.cc b/source/common/buffer/watermark_buffer.cc index f7d95e5183f4e..eefa6022c8ef0 100644 --- a/source/common/buffer/watermark_buffer.cc +++ b/source/common/buffer/watermark_buffer.cc @@ -90,8 +90,7 @@ void WatermarkBuffer::appendSliceForTest(absl::string_view data) { appendSliceForTest(data.data(), data.size()); } -void WatermarkBuffer::setWatermarks(uint32_t low_watermark, uint32_t high_watermark) { - ASSERT(low_watermark < high_watermark || (high_watermark == 0 && low_watermark == 0)); +void WatermarkBuffer::setWatermarks(uint32_t high_watermark) { uint32_t overflow_watermark_multiplier = Runtime::getInteger("envoy.buffer.overflow_multiplier", 0); if (overflow_watermark_multiplier > 0 && @@ -101,7 +100,7 @@ void WatermarkBuffer::setWatermarks(uint32_t low_watermark, uint32_t high_waterm "high_watermark is overflowing. Disabling overflow watermark."); overflow_watermark_multiplier = 0; } - low_watermark_ = low_watermark; + low_watermark_ = high_watermark / 2; high_watermark_ = high_watermark; overflow_watermark_ = overflow_watermark_multiplier * high_watermark; checkHighAndOverflowWatermarks(); diff --git a/source/common/buffer/watermark_buffer.h b/source/common/buffer/watermark_buffer.h index 9150cdaf54f94..68d260b155320 100644 --- a/source/common/buffer/watermark_buffer.h +++ b/source/common/buffer/watermark_buffer.h @@ -39,8 +39,7 @@ class WatermarkBuffer : public OwnedImpl { void appendSliceForTest(const void* data, uint64_t size) override; void appendSliceForTest(absl::string_view data) override; - void setWatermarks(uint32_t watermark) override { setWatermarks(watermark / 2, watermark); } - void setWatermarks(uint32_t low_watermark, uint32_t high_watermark); + void setWatermarks(uint32_t high_watermark) override; uint32_t highWatermark() const override { return high_watermark_; } // Returns true if the high watermark callbacks have been called more recently // than the low watermark callbacks. diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index 574d9ef9d40c6..758dea9d326fc 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -136,7 +136,7 @@ ConnectionImpl::StreamImpl::StreamImpl(ConnectionImpl& parent, uint32_t buffer_l pending_send_buffer_high_watermark_called_(false), reset_due_to_messaging_error_(false) { parent_.stats_.streams_active_.inc(); if (buffer_limit > 0) { - setWriteBufferWatermarks(buffer_limit / 2, buffer_limit); + setWriteBufferWatermarks(buffer_limit); } } diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index 1f73d14f423f5..6f4a3a6f0270e 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -257,9 +257,9 @@ class ConnectionImpl : public virtual Connection, } } - void setWriteBufferWatermarks(uint32_t low_watermark, uint32_t high_watermark) { - pending_recv_data_.setWatermarks(low_watermark, high_watermark); - pending_send_data_.setWatermarks(low_watermark, high_watermark); + void setWriteBufferWatermarks(uint32_t high_watermark) { + pending_recv_data_.setWatermarks(high_watermark); + pending_send_data_.setWatermarks(high_watermark); } // If the receive buffer encounters watermark callbacks, enable/disable reads on this stream. diff --git a/test/common/buffer/watermark_buffer_test.cc b/test/common/buffer/watermark_buffer_test.cc index b007625b2cd8a..70ddb07d98005 100644 --- a/test/common/buffer/watermark_buffer_test.cc +++ b/test/common/buffer/watermark_buffer_test.cc @@ -18,7 +18,7 @@ const char TEN_BYTES[] = "0123456789"; class WatermarkBufferTest : public testing::Test { public: - WatermarkBufferTest() { buffer_.setWatermarks(5, 10); } + WatermarkBufferTest() { buffer_.setWatermarks(10); } Buffer::WatermarkBuffer buffer_{[&]() -> void { ++times_low_watermark_called_; }, [&]() -> void { ++times_high_watermark_called_; }, @@ -104,7 +104,7 @@ TEST_F(WatermarkBufferTest, PrependBuffer) { WatermarkBuffer prefixBuffer{[&]() -> void { ++prefix_buffer_low_watermark_hits; }, [&]() -> void { ++prefix_buffer_high_watermark_hits; }, [&]() -> void { ++prefix_buffer_overflow_watermark_hits; }}; - prefixBuffer.setWatermarks(5, 10); + prefixBuffer.setWatermarks(10); prefixBuffer.add(prefix); prefixBuffer.add(suffix); @@ -196,18 +196,18 @@ TEST_F(WatermarkBufferTest, DrainUsingExtract) { // Verify that low watermark callback is called on drain in the case where the // high watermark is non-zero and low watermark is 0. TEST_F(WatermarkBufferTest, DrainWithLowWatermarkOfZero) { - buffer_.setWatermarks(0, 10); + buffer_.setWatermarks(1); // Draining from above to below the low watermark does nothing if the high // watermark never got hit. - buffer_.add(TEN_BYTES, 10); - buffer_.drain(10); + buffer_.add(TEN_BYTES, 1); + buffer_.drain(1); EXPECT_EQ(0, times_high_watermark_called_); EXPECT_EQ(0, times_low_watermark_called_); // Go above the high watermark then drain down to just above the low watermark. - buffer_.add(TEN_BYTES, 11); - buffer_.drain(10); + buffer_.add(TEN_BYTES, 2); + buffer_.drain(1); EXPECT_EQ(1, buffer_.length()); EXPECT_EQ(0, times_low_watermark_called_); @@ -216,7 +216,7 @@ TEST_F(WatermarkBufferTest, DrainWithLowWatermarkOfZero) { EXPECT_EQ(1, times_low_watermark_called_); // Going back above should trigger the high again - buffer_.add(TEN_BYTES, 11); + buffer_.add(TEN_BYTES, 2); EXPECT_EQ(2, times_high_watermark_called_); } @@ -284,18 +284,18 @@ TEST_F(WatermarkBufferTest, WatermarkFdFunctions) { TEST_F(WatermarkBufferTest, MoveWatermarks) { buffer_.add(TEN_BYTES, 9); EXPECT_EQ(0, times_high_watermark_called_); - buffer_.setWatermarks(1, 9); + buffer_.setWatermarks(9); EXPECT_EQ(0, times_high_watermark_called_); - buffer_.setWatermarks(1, 8); + buffer_.setWatermarks(8); EXPECT_EQ(1, times_high_watermark_called_); - buffer_.setWatermarks(8, 20); + buffer_.setWatermarks(16); EXPECT_EQ(0, times_low_watermark_called_); - buffer_.setWatermarks(9, 20); + buffer_.setWatermarks(18); EXPECT_EQ(1, times_low_watermark_called_); - buffer_.setWatermarks(7, 20); + buffer_.setWatermarks(14); EXPECT_EQ(1, times_low_watermark_called_); - buffer_.setWatermarks(9, 20); + buffer_.setWatermarks(18); EXPECT_EQ(1, times_low_watermark_called_); EXPECT_EQ(0, times_overflow_watermark_called_); @@ -355,7 +355,7 @@ TEST_F(WatermarkBufferTest, MoveBackWithWatermarks) { Buffer::WatermarkBuffer buffer1{[&]() -> void { ++low_watermark_buffer1; }, [&]() -> void { ++high_watermark_buffer1; }, [&]() -> void { ++overflow_watermark_buffer1; }}; - buffer1.setWatermarks(5, 10); + buffer1.setWatermarks(10); // Stick 20 bytes in buffer_ and expect the high watermark is hit. buffer_.add(TEN_BYTES, 10); @@ -393,7 +393,7 @@ TEST_F(WatermarkBufferTest, OverflowWatermark) { Buffer::WatermarkBuffer buffer1{[&]() -> void { ++low_watermark_buffer1; }, [&]() -> void { ++high_watermark_buffer1; }, [&]() -> void { ++overflow_watermark_buffer1; }}; - buffer1.setWatermarks(5, 10); + buffer1.setWatermarks(10); buffer1.add(TEN_BYTES, 10); EXPECT_EQ(0, high_watermark_buffer1); @@ -439,7 +439,7 @@ TEST_F(WatermarkBufferTest, OverflowWatermarkDisabled) { Buffer::WatermarkBuffer buffer1{[&]() -> void { ++low_watermark_buffer1; }, [&]() -> void { ++high_watermark_buffer1; }, [&]() -> void { ++overflow_watermark_buffer1; }}; - buffer1.setWatermarks(5, 10); + buffer1.setWatermarks(10); buffer1.add(TEN_BYTES, 10); EXPECT_EQ(0, high_watermark_buffer1); @@ -510,7 +510,7 @@ TEST_F(WatermarkBufferTest, OverflowWatermarkEqualHighWatermark) { Buffer::WatermarkBuffer buffer1{[&]() -> void { ++low_watermark_buffer1; }, [&]() -> void { ++high_watermark_buffer1; }, [&]() -> void { ++overflow_watermark_buffer1; }}; - buffer1.setWatermarks(5, 10); + buffer1.setWatermarks(10); buffer1.add(TEN_BYTES, 10); EXPECT_EQ(0, high_watermark_buffer1); @@ -540,25 +540,25 @@ TEST_F(WatermarkBufferTest, MoveWatermarksOverflow) { Buffer::WatermarkBuffer buffer1{[&]() -> void { ++low_watermark_buffer1; }, [&]() -> void { ++high_watermark_buffer1; }, [&]() -> void { ++overflow_watermark_buffer1; }}; - buffer1.setWatermarks(5, 10); + buffer1.setWatermarks(10); buffer1.add(TEN_BYTES, 9); EXPECT_EQ(0, high_watermark_buffer1); EXPECT_EQ(0, overflow_watermark_buffer1); - buffer1.setWatermarks(1, 9); + buffer1.setWatermarks(9); EXPECT_EQ(0, high_watermark_buffer1); EXPECT_EQ(0, overflow_watermark_buffer1); - buffer1.setWatermarks(1, 8); + buffer1.setWatermarks(8); EXPECT_EQ(1, high_watermark_buffer1); EXPECT_EQ(0, overflow_watermark_buffer1); - buffer1.setWatermarks(1, 5); + buffer1.setWatermarks(5); EXPECT_EQ(1, high_watermark_buffer1); EXPECT_EQ(0, overflow_watermark_buffer1); - buffer1.setWatermarks(1, 4); + buffer1.setWatermarks(4); EXPECT_EQ(1, high_watermark_buffer1); EXPECT_EQ(1, overflow_watermark_buffer1); // Overflow is only triggered once - buffer1.setWatermarks(3, 6); + buffer1.setWatermarks(6); EXPECT_EQ(0, low_watermark_buffer1); EXPECT_EQ(1, high_watermark_buffer1); EXPECT_EQ(1, overflow_watermark_buffer1); diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index f6327caaa71bf..5572f3f5dcb09 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -1419,7 +1419,7 @@ TEST_P(Http2CodecImplFlowControlTest, FlowControlPendingRecvData) { // the recv buffer can be overrun by a client which negotiates a larger // SETTINGS_MAX_FRAME_SIZE but there's no current easy way to tweak that in // envoy (without sending raw HTTP/2 frames) so we lower the buffer limit instead. - server_->getStream(1)->setWriteBufferWatermarks(10, 20); + server_->getStream(1)->setWriteBufferWatermarks(20); EXPECT_CALL(request_decoder_, decodeData(_, false)); Buffer::OwnedImpl data(std::string(40, 'a')); diff --git a/test/mocks/http/stream.h b/test/mocks/http/stream.h index b155af4a121db..ce6579f13ad4e 100644 --- a/test/mocks/http/stream.h +++ b/test/mocks/http/stream.h @@ -17,7 +17,7 @@ class MockStream : public Stream { MOCK_METHOD(void, removeCallbacks, (StreamCallbacks & callbacks)); MOCK_METHOD(void, resetStream, (StreamResetReason reason)); MOCK_METHOD(void, readDisable, (bool disable)); - MOCK_METHOD(void, setWriteBufferWatermarks, (uint32_t, uint32_t)); + MOCK_METHOD(void, setWriteBufferWatermarks, (uint32_t)); MOCK_METHOD(uint32_t, bufferLimit, ()); MOCK_METHOD(const Network::Address::InstanceConstSharedPtr&, connectionLocalAddress, ()); MOCK_METHOD(void, setFlushTimeout, (std::chrono::milliseconds timeout)); From 0cdd980286615044b66ee585d56fedd71631c9df Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 5 May 2021 20:09:44 -0400 Subject: [PATCH 159/209] test: cleaning up unneeded helpers. (#16334) Signed-off-by: Alyssa Wilk --- .../redis_proxy_integration_test.cc | 4 +- test/integration/base_integration_test.h | 50 ++++--------------- test/integration/fake_upstream.cc | 10 ---- test/integration/fake_upstream.h | 4 -- test/integration/hds_integration_test.cc | 13 ++--- test/integration/uds_integration_test.h | 6 ++- 6 files changed, 21 insertions(+), 66 deletions(-) diff --git a/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc b/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc index 965f0c849f7ed..4b014fc08c1be 100644 --- a/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc +++ b/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc @@ -758,9 +758,7 @@ TEST_P(RedisProxyWithRedirectionIntegrationTest, RedirectToUnknownServer) { std::string request = makeBulkStringArray({"get", "foo"}); initialize(); - auto endpoint = - Network::Utility::parseInternetAddress(Network::Test::getAnyAddressString(version_), 0); - FakeUpstreamPtr target_server{createFakeUpstream(endpoint, upstreamProtocol())}; + FakeUpstreamPtr target_server{std::make_unique(0, version_, upstreamConfig())}; std::stringstream redirection_error; redirection_error << "-MOVED 1111 " << redisAddressAndPort(target_server) << "\r\n"; diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index 1baab383b20e8..94a3a7243e8aa 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -307,52 +307,20 @@ class BaseIntegrationTest : protected Logger::Loggable { *dispatcher_, std::move(transport_socket)); } - // Helper to create FakeUpstream. - // Creates a fake upstream bound to the specified unix domain socket path. - std::unique_ptr createFakeUpstream(const std::string& uds_path, - FakeHttpConnection::Type type) { - FakeUpstreamConfig config = upstream_config_; - config.upstream_protocol_ = type; - return std::make_unique(uds_path, config); - } - // Creates a fake upstream bound to the specified |address|. - std::unique_ptr - createFakeUpstream(const Network::Address::InstanceConstSharedPtr& address, - FakeHttpConnection::Type type) { - FakeUpstreamConfig config = upstream_config_; - config.upstream_protocol_ = type; - return std::make_unique(address, config); - } - // Creates a fake upstream bound to INADDR_ANY and there is no specified port. - std::unique_ptr createFakeUpstream(FakeHttpConnection::Type type) { + // Add a fake upstream bound to INADDR_ANY and there is no specified port. + FakeUpstream& addFakeUpstream(FakeHttpConnection::Type type) { FakeUpstreamConfig config = upstream_config_; config.upstream_protocol_ = type; - return std::make_unique(0, version_, config); + fake_upstreams_.emplace_back(std::make_unique(0, version_, config)); + return *fake_upstreams_.back(); } - std::unique_ptr - createFakeUpstream(Network::TransportSocketFactoryPtr&& transport_socket_factory, - FakeHttpConnection::Type type) { + FakeUpstream& addFakeUpstream(Network::TransportSocketFactoryPtr&& transport_socket_factory, + FakeHttpConnection::Type type) { FakeUpstreamConfig config = upstream_config_; config.upstream_protocol_ = type; - return std::make_unique(std::move(transport_socket_factory), 0, version_, config); - } - // Helper to add FakeUpstream. - // Add a fake upstream bound to the specified unix domain socket path. - void addFakeUpstream(const std::string& uds_path, FakeHttpConnection::Type type) { - fake_upstreams_.emplace_back(createFakeUpstream(uds_path, type)); - } - // Add a fake upstream bound to the specified |address|. - void addFakeUpstream(const Network::Address::InstanceConstSharedPtr& address, - FakeHttpConnection::Type type) { - fake_upstreams_.emplace_back(createFakeUpstream(address, type)); - } - // Add a fake upstream bound to INADDR_ANY and there is no specified port. - void addFakeUpstream(FakeHttpConnection::Type type) { - fake_upstreams_.emplace_back(createFakeUpstream(type)); - } - void addFakeUpstream(Network::TransportSocketFactoryPtr&& transport_socket_factory, - FakeHttpConnection::Type type) { - fake_upstreams_.emplace_back(createFakeUpstream(std::move(transport_socket_factory), type)); + fake_upstreams_.emplace_back( + std::make_unique(std::move(transport_socket_factory), 0, version_, config)); + return *fake_upstreams_.back(); } protected: diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index 3364b23afbad9..a4248c52cea39 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -479,16 +479,6 @@ makeListenSocket(const FakeUpstreamConfig& config, : makeTcpListenSocket(address)); } -FakeUpstream::FakeUpstream(const Network::Address::InstanceConstSharedPtr& address, - const FakeUpstreamConfig& config) - : FakeUpstream(Network::Test::createRawBufferSocketFactory(), makeListenSocket(config, address), - config) { - ENVOY_LOG(info, "starting fake server on socket {}:{}. Address version is {}. UDP={}", - address->ip()->addressAsString(), address->ip()->port(), - Network::Test::addressVersionAsString(address->ip()->version()), - config.udp_fake_upstream_.has_value()); -} - FakeUpstream::FakeUpstream(uint32_t port, Network::Address::IpVersion version, const FakeUpstreamConfig& config) : FakeUpstream(Network::Test::createRawBufferSocketFactory(), diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index 04bccb733b66a..a2d170eb110e2 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -574,10 +574,6 @@ class FakeUpstream : Logger::Loggable, const Network::Address::InstanceConstSharedPtr& address, const FakeUpstreamConfig& config); - // Creates a fake upstream bound to the specified |address|. - FakeUpstream(const Network::Address::InstanceConstSharedPtr& address, - const FakeUpstreamConfig& config); - // Creates a fake upstream bound to INADDR_ANY and the specified |port|. FakeUpstream(uint32_t port, Network::Address::IpVersion version, const FakeUpstreamConfig& config); diff --git a/test/integration/hds_integration_test.cc b/test/integration/hds_integration_test.cc index 4eaa5a3459a4a..3ade7ed52e87b 100644 --- a/test/integration/hds_integration_test.cc +++ b/test/integration/hds_integration_test.cc @@ -63,12 +63,12 @@ class HdsIntegrationTest : public Grpc::VersionedGrpcClientIntegrationParamTest, // Endpoint connections if (tls_hosts_) { host_upstream_ = - createFakeUpstream(HttpIntegrationTest::createUpstreamTlsContext(), http_conn_type_); + &addFakeUpstream(HttpIntegrationTest::createUpstreamTlsContext(), http_conn_type_); host2_upstream_ = - createFakeUpstream(HttpIntegrationTest::createUpstreamTlsContext(), http_conn_type_); + &addFakeUpstream(HttpIntegrationTest::createUpstreamTlsContext(), http_conn_type_); } else { - host_upstream_ = createFakeUpstream(http_conn_type_); - host2_upstream_ = createFakeUpstream(http_conn_type_); + host_upstream_ = &addFakeUpstream(http_conn_type_); + host2_upstream_ = &addFakeUpstream(http_conn_type_); } } @@ -362,8 +362,9 @@ class HdsIntegrationTest : public Grpc::VersionedGrpcClientIntegrationParamTest, FakeStreamPtr hds_stream_; FakeUpstream* hds_upstream_{}; uint32_t hds_requests_{}; - FakeUpstreamPtr host_upstream_{}; - FakeUpstreamPtr host2_upstream_{}; + // These two are owned by fake_upstreams_ + FakeUpstream* host_upstream_{}; + FakeUpstream* host2_upstream_{}; FakeStreamPtr host_stream_; FakeStreamPtr host2_stream_; FakeHttpConnectionPtr host_fake_connection_; diff --git a/test/integration/uds_integration_test.h b/test/integration/uds_integration_test.h index 43fdeabd5b569..8d5675e168053 100644 --- a/test/integration/uds_integration_test.h +++ b/test/integration/uds_integration_test.h @@ -25,8 +25,10 @@ class UdsUpstreamIntegrationTest abstract_namespace_(std::get<1>(GetParam())) {} void createUpstreams() override { - addFakeUpstream(TestEnvironment::unixDomainSocketPath("udstest.1.sock", abstract_namespace_), - FakeHttpConnection::Type::HTTP1); + FakeUpstreamConfig config = upstreamConfig(); + config.upstream_protocol_ = FakeHttpConnection::Type::HTTP1; + auto uds_path = TestEnvironment::unixDomainSocketPath("udstest.1.sock", abstract_namespace_); + fake_upstreams_.emplace_back(std::make_unique(uds_path, config)); config_helper_.addConfigModifier( [&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { From 185e483c7d93711c1b087ffc7464b03f950e1f84 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 6 May 2021 07:03:10 -0400 Subject: [PATCH 160/209] http3: improving upstream TLS code (#16281) Addressing a TODO for upstream quic crypto. The clientContextConfig looks safe to access, as it does not reload, but QUIC was using the context to create a fresh Tls::ClientContextImpl per connection instead of reusing the factory built shared Ssl::ClientContextSharedPtr. This PR moves it to the ssl shared pointer. Risk Level: low (mostly H3 only) Testing: n/a (refactor) Docs Changes: n/a Release Notes: n/a part of #15649 Signed-off-by: Alyssa Wilk --- source/common/http/http3/conn_pool.cc | 3 +-- .../common/quic/client_connection_factory_impl.cc | 15 +++++++++++---- .../common/quic/client_connection_factory_impl.h | 2 +- source/common/quic/envoy_quic_proof_verifier.cc | 6 +++++- source/common/quic/envoy_quic_proof_verifier.h | 7 +++---- .../common/quic/quic_transport_socket_factory.h | 3 ++- .../transport_sockets/tls/ssl_socket.cc | 5 +++++ .../extensions/transport_sockets/tls/ssl_socket.h | 2 ++ test/common/quic/envoy_quic_proof_source_test.cc | 5 +++-- .../common/quic/envoy_quic_proof_verifier_test.cc | 5 +++-- test/integration/http_integration.cc | 6 +++--- test/integration/utility.cc | 7 +++++-- test/integration/utility.h | 4 +--- 13 files changed, 45 insertions(+), 25 deletions(-) diff --git a/source/common/http/http3/conn_pool.cc b/source/common/http/http3/conn_pool.cc index 254bc204622d6..703b5b1c110f1 100644 --- a/source/common/http/http3/conn_pool.cc +++ b/source/common/http/http3/conn_pool.cc @@ -43,8 +43,7 @@ class Http3ConnPoolImpl : public FixedHttpConnPoolImpl { } Network::TransportSocketFactory& transport_socket_factory = host->transportSocketFactory(); quic_info_ = std::make_unique( - dispatcher, transport_socket_factory, host->cluster().statsScope(), time_source, - source_address); + dispatcher, transport_socket_factory, time_source, source_address); Quic::configQuicInitialFlowControlWindow( host_->cluster().http3Options().quic_protocol_options(), quic_info_->quic_config_); } diff --git a/source/common/quic/client_connection_factory_impl.cc b/source/common/quic/client_connection_factory_impl.cc index c65b566147048..b8c3c2bd91906 100644 --- a/source/common/quic/client_connection_factory_impl.cc +++ b/source/common/quic/client_connection_factory_impl.cc @@ -14,16 +14,23 @@ getConfig(Network::TransportSocketFactory& transport_socket_factory) { return quic_socket_factory->clientContextConfig(); } +Envoy::Ssl::ClientContextSharedPtr +getContext(Network::TransportSocketFactory& transport_socket_factory) { + auto* quic_socket_factory = + dynamic_cast(&transport_socket_factory); + ASSERT(quic_socket_factory != nullptr); + ASSERT(quic_socket_factory->sslCtx() != nullptr); + return quic_socket_factory->sslCtx(); +} + PersistentQuicInfoImpl::PersistentQuicInfoImpl( Event::Dispatcher& dispatcher, Network::TransportSocketFactory& transport_socket_factory, - Stats::Scope& stats_scope, TimeSource& time_source, - Network::Address::InstanceConstSharedPtr server_addr) + TimeSource& time_source, Network::Address::InstanceConstSharedPtr server_addr) : conn_helper_(dispatcher), alarm_factory_(dispatcher, *conn_helper_.GetClock()), server_id_{getConfig(transport_socket_factory).serverNameIndication(), static_cast(server_addr->ip()->port()), false}, crypto_config_(std::make_unique( - std::make_unique(stats_scope, getConfig(transport_socket_factory), - time_source), + std::make_unique(getContext(transport_socket_factory)), std::make_unique(time_source))) { quiche::FlagRegistry::getInstance(); } diff --git a/source/common/quic/client_connection_factory_impl.h b/source/common/quic/client_connection_factory_impl.h index 8564280a43b45..3f736f596cdbf 100644 --- a/source/common/quic/client_connection_factory_impl.h +++ b/source/common/quic/client_connection_factory_impl.h @@ -19,7 +19,7 @@ namespace Quic { struct PersistentQuicInfoImpl : public Http::PersistentQuicInfo { PersistentQuicInfoImpl(Event::Dispatcher& dispatcher, Network::TransportSocketFactory& transport_socket_factory, - Stats::Scope& stats_scope, TimeSource& time_source, + TimeSource& time_source, Network::Address::InstanceConstSharedPtr server_addr); EnvoyQuicConnectionHelper conn_helper_; diff --git a/source/common/quic/envoy_quic_proof_verifier.cc b/source/common/quic/envoy_quic_proof_verifier.cc index a89937d5b2f36..2e79eeb7ef7c1 100644 --- a/source/common/quic/envoy_quic_proof_verifier.cc +++ b/source/common/quic/envoy_quic_proof_verifier.cc @@ -29,7 +29,11 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( sk_X509_push(intermediates.get(), cert.release()); } } - bool success = context_impl_.verifyCertChain(*leaf, *intermediates, *error_details); + // We down cast rather than add verifyCertChain to Envoy::Ssl::Context because + // verifyCertChain uses a bunch of SSL-specific structs which we want to keep + // out of the interface definition. + bool success = static_cast(context_.get()) + ->verifyCertChain(*leaf, *intermediates, *error_details); if (!success) { return quic::QUIC_FAILURE; } diff --git a/source/common/quic/envoy_quic_proof_verifier.h b/source/common/quic/envoy_quic_proof_verifier.h index ac887b28ab559..f12d570848f01 100644 --- a/source/common/quic/envoy_quic_proof_verifier.h +++ b/source/common/quic/envoy_quic_proof_verifier.h @@ -11,9 +11,8 @@ namespace Quic { // client context config. class EnvoyQuicProofVerifier : public EnvoyQuicProofVerifierBase { public: - EnvoyQuicProofVerifier(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config, - TimeSource& time_source) - : context_impl_(scope, config, time_source) {} + EnvoyQuicProofVerifier(Envoy::Ssl::ClientContextSharedPtr&& context) + : context_(std::move(context)) {} // EnvoyQuicProofVerifierBase quic::QuicAsyncStatus @@ -25,7 +24,7 @@ class EnvoyQuicProofVerifier : public EnvoyQuicProofVerifierBase { std::unique_ptr callback) override; private: - Extensions::TransportSockets::Tls::ClientContextImpl context_impl_; + Envoy::Ssl::ClientContextSharedPtr context_; }; } // namespace Quic diff --git a/source/common/quic/quic_transport_socket_factory.h b/source/common/quic/quic_transport_socket_factory.h index ec8d182ce17e7..cfad2cc329a6f 100644 --- a/source/common/quic/quic_transport_socket_factory.h +++ b/source/common/quic/quic_transport_socket_factory.h @@ -112,7 +112,8 @@ class QuicClientTransportSocketFactory : public QuicTransportSocketFactoryBase { return fallback_factory_->createTransportSocket(options); } - // TODO(14829) make sure that clientContextConfig() is safe when secrets are updated. + Envoy::Ssl::ClientContextSharedPtr sslCtx() { return fallback_factory_->sslCtx(); } + const Ssl::ClientContextConfig& clientContextConfig() const { return fallback_factory_->config(); } diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index a9fcfbf84ed52..b7d8a85635124 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -397,6 +397,11 @@ ServerSslSocketFactory::ServerSslSocketFactory(Envoy::Ssl::ServerContextConfigPt config_->setSecretUpdateCallback([this]() { onAddOrUpdateSecret(); }); } +Envoy::Ssl::ClientContextSharedPtr ClientSslSocketFactory::sslCtx() { + absl::ReaderMutexLock l(&ssl_ctx_mu_); + return ssl_ctx_; +} + Network::TransportSocketPtr ServerSslSocketFactory::createTransportSocket(Network::TransportSocketOptionsSharedPtr) const { // onAddOrUpdateSecret() could be invoked in the middle of checking the existence of ssl_ctx and diff --git a/source/extensions/transport_sockets/tls/ssl_socket.h b/source/extensions/transport_sockets/tls/ssl_socket.h index 5b9239d266053..203fbe7078493 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.h +++ b/source/extensions/transport_sockets/tls/ssl_socket.h @@ -118,6 +118,8 @@ class ClientSslSocketFactory : public Network::TransportSocketFactory, const Ssl::ClientContextConfig& config() const { return *config_; } + Envoy::Ssl::ClientContextSharedPtr sslCtx(); + private: Envoy::Ssl::ContextManager& manager_; Stats::Scope& stats_scope_; diff --git a/test/common/quic/envoy_quic_proof_source_test.cc b/test/common/quic/envoy_quic_proof_source_test.cc index bd41d2991544a..7596d970757ae 100644 --- a/test/common/quic/envoy_quic_proof_source_test.cc +++ b/test/common/quic/envoy_quic_proof_source_test.cc @@ -73,8 +73,9 @@ class TestGetProofCallback : public quic::ProofSource::Callback { .WillByDefault(ReturnRef(empty_string_list)); const absl::optional nullopt = absl::nullopt; ON_CALL(cert_validation_ctx_config_, customValidatorConfig()).WillByDefault(ReturnRef(nullopt)); - verifier_ = - std::make_unique(store_, client_context_config_, time_system_); + auto context = std::make_shared( + store_, client_context_config_, time_system_); + verifier_ = std::make_unique(std::move(context)); } // quic::ProofSource::Callback diff --git a/test/common/quic/envoy_quic_proof_verifier_test.cc b/test/common/quic/envoy_quic_proof_verifier_test.cc index 52b22a4767fb8..6b42341fa685d 100644 --- a/test/common/quic/envoy_quic_proof_verifier_test.cc +++ b/test/common/quic/envoy_quic_proof_verifier_test.cc @@ -68,8 +68,9 @@ class EnvoyQuicProofVerifierTest : public testing::Test { .WillRepeatedly(ReturnRef(empty_string_list_)); EXPECT_CALL(cert_validation_ctx_config_, customValidatorConfig()) .WillRepeatedly(ReturnRef(custom_validator_config_)); - verifier_ = - std::make_unique(store_, client_context_config_, time_system_); + auto context = std::make_shared( + store_, client_context_config_, time_system_); + verifier_ = std::make_unique(std::move(context)); } protected: diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index e7ff7a489bda2..2c225369cb8f4 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -327,8 +327,8 @@ void HttpIntegrationTest::initialize() { #ifdef ENVOY_ENABLE_QUIC // Needs to be instantiated before base class calls initialize() which starts a QUIC listener // according to the config. - quic_transport_socket_factory_ = - IntegrationUtil::createQuicUpstreamTransportSocketFactory(*api_, stats_store_, san_to_match_); + quic_transport_socket_factory_ = IntegrationUtil::createQuicUpstreamTransportSocketFactory( + *api_, stats_store_, context_manager_, san_to_match_); // Needed to config QUIC transport socket factory, and needs to be added before base class calls // initialize(). @@ -341,7 +341,7 @@ void HttpIntegrationTest::initialize() { "udp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), lookupPort("http"))); // Needs to outlive all QUIC connections. auto quic_connection_persistent_info = std::make_unique( - *dispatcher_, *quic_transport_socket_factory_, stats_store_, timeSystem(), server_addr); + *dispatcher_, *quic_transport_socket_factory_, timeSystem(), server_addr); // Config IETF QUIC flow control window. quic_connection_persistent_info->quic_config_ .SetInitialMaxStreamDataBytesIncomingBidirectionalToSend( diff --git a/test/integration/utility.cc b/test/integration/utility.cc index 0f78a4a113df1..7d99f7e4dcc8b 100644 --- a/test/integration/utility.cc +++ b/test/integration/utility.cc @@ -125,10 +125,12 @@ class TestConnectionCallbacks : public Network::ConnectionCallbacks { Network::TransportSocketFactoryPtr IntegrationUtil::createQuicUpstreamTransportSocketFactory(Api::Api& api, Stats::Store& store, + Ssl::ContextManager& context_manager, const std::string& san_to_match) { NiceMock context; ON_CALL(context, api()).WillByDefault(testing::ReturnRef(api)); ON_CALL(context, scope()).WillByDefault(testing::ReturnRef(store)); + ON_CALL(context, sslContextManager()).WillByDefault(testing::ReturnRef(context_manager)); envoy::extensions::transport_sockets::quic::v3::QuicUpstreamTransport quic_transport_socket_config; auto* tls_context = quic_transport_socket_config.mutable_upstream_tls_context(); @@ -204,12 +206,13 @@ IntegrationUtil::makeSingleRequest(const Network::Address::InstanceConstSharedPt } #ifdef ENVOY_ENABLE_QUIC + Extensions::TransportSockets::Tls::ContextManagerImpl manager(time_system); Network::TransportSocketFactoryPtr transport_socket_factory = - createQuicUpstreamTransportSocketFactory(api, mock_stats_store, + createQuicUpstreamTransportSocketFactory(api, mock_stats_store, manager, "spiffe://lyft.com/backend-team"); std::unique_ptr persistent_info; persistent_info = std::make_unique( - *dispatcher, *transport_socket_factory, mock_stats_store, time_system, addr); + *dispatcher, *transport_socket_factory, time_system, addr); Network::Address::InstanceConstSharedPtr local_address; if (addr->ip()->version() == Network::Address::IpVersion::v4) { diff --git a/test/integration/utility.h b/test/integration/utility.h index f143676f86d36..2deaf89203701 100644 --- a/test/integration/utility.h +++ b/test/integration/utility.h @@ -192,13 +192,11 @@ class IntegrationUtil { /** * Create transport socket factory for Quic upstream transport socket. - * @param context supplies the port to connect to on localhost. - * @param san_to_match configs |context| to match Subject Alternative Name during certificate - * verification. * @return TransportSocketFactoryPtr the client transport socket factory. */ static Network::TransportSocketFactoryPtr createQuicUpstreamTransportSocketFactory(Api::Api& api, Stats::Store& store, + Ssl::ContextManager& context_manager, const std::string& san_to_match); }; From e72cefd514398ae93d92eed2c77e71eda7ec23d6 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Thu, 6 May 2021 17:13:26 +0000 Subject: [PATCH 161/209] Record control plane identifier of delta xDS responses in stats (#16256) Signed-off-by: Yan Avlasov --- source/common/config/new_grpc_mux_impl.cc | 5 ++++- test/common/config/new_grpc_mux_impl_test.cc | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/source/common/config/new_grpc_mux_impl.cc b/source/common/config/new_grpc_mux_impl.cc index 95d38e7822cf6..75fb14f4c6f66 100644 --- a/source/common/config/new_grpc_mux_impl.cc +++ b/source/common/config/new_grpc_mux_impl.cc @@ -78,9 +78,12 @@ void NewGrpcMuxImpl::registerVersionedTypeUrl(const std::string& type_url) { void NewGrpcMuxImpl::onDiscoveryResponse( std::unique_ptr&& message, - ControlPlaneStats&) { + ControlPlaneStats& control_plane_stats) { ENVOY_LOG(debug, "Received DeltaDiscoveryResponse for {} at version {}", message->type_url(), message->system_version_info()); + if (message->has_control_plane()) { + control_plane_stats.identifier_.set(message->control_plane().identifier()); + } auto sub = subscriptions_.find(message->type_url()); // If this type url is not watched, try another version type url. if (enable_type_url_downgrade_and_upgrade_ && sub == subscriptions_.end()) { diff --git a/test/common/config/new_grpc_mux_impl_test.cc b/test/common/config/new_grpc_mux_impl_test.cc index 37256ee412f6b..00fb2770273a3 100644 --- a/test/common/config/new_grpc_mux_impl_test.cc +++ b/test/common/config/new_grpc_mux_impl_test.cc @@ -317,6 +317,7 @@ TEST_F(NewGrpcMuxImplTest, ConfigUpdateWithAliases) { auto response = std::make_unique(); response->set_type_url(type_url); response->set_system_version_info("1"); + response->mutable_control_plane()->set_identifier("HAL 9000"); envoy::config::route::v3::VirtualHost vhost; vhost.set_name("vhost_1"); @@ -335,6 +336,7 @@ TEST_F(NewGrpcMuxImplTest, ConfigUpdateWithAliases) { EXPECT_TRUE(sub != subscriptions.end()); watch->update({}); + EXPECT_EQ("HAL 9000", stats_.textReadout("control_plane.identifier").value()); } // DeltaDiscoveryResponse that comes in response to an on-demand request that couldn't be resolved From 479b02b84f695196aeadce5650699ab7cf1b11df Mon Sep 17 00:00:00 2001 From: danzh Date: Thu, 6 May 2021 14:07:39 -0400 Subject: [PATCH 162/209] quiche: support connection ID longer than 8 bytes with multiple worker threads (#16320) fix an issue where CID longer than 8 bytes is replaced by a hashed 8-byte CID which isn't compatible with existing BPF filter which requires stable first 4-byte in CID. Signed-off-by: Dan Zhang --- source/common/quic/envoy_quic_dispatcher.cc | 14 ++++++++ source/common/quic/envoy_quic_dispatcher.h | 7 ++++ .../integration/quic_http_integration_test.cc | 33 +++++++++++++++++-- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/source/common/quic/envoy_quic_dispatcher.cc b/source/common/quic/envoy_quic_dispatcher.cc index f1577a40ce52e..4191e80d4d793 100644 --- a/source/common/quic/envoy_quic_dispatcher.cc +++ b/source/common/quic/envoy_quic_dispatcher.cc @@ -1,5 +1,6 @@ #include "common/quic/envoy_quic_dispatcher.h" +#include "common/common/safe_memcpy.h" #include "common/http/utility.h" #include "common/quic/envoy_quic_server_connection.h" #include "common/quic/envoy_quic_server_session.h" @@ -86,5 +87,18 @@ std::unique_ptr EnvoyQuicDispatcher::CreateQuicSession( return quic_session; } +quic::QuicConnectionId EnvoyQuicDispatcher::ReplaceLongServerConnectionId( + const quic::ParsedQuicVersion& version, const quic::QuicConnectionId& server_connection_id, + uint8_t expected_server_connection_id_length) const { + quic::QuicConnectionId new_connection_id = quic::QuicDispatcher::ReplaceLongServerConnectionId( + version, server_connection_id, expected_server_connection_id_length); + char* new_connection_id_data = new_connection_id.mutable_data(); + const char* server_connection_id_ptr = server_connection_id.data(); + auto* first_four_bytes = reinterpret_cast(server_connection_id_ptr); + // Override the first 4 bytes of the new CID to the original CID's first 4 bytes. + safeMemcpyUnsafeDst(new_connection_id_data, first_four_bytes); + return new_connection_id; +} + } // namespace Quic } // namespace Envoy diff --git a/source/common/quic/envoy_quic_dispatcher.h b/source/common/quic/envoy_quic_dispatcher.h index 994a17481acfd..987dd2b450651 100644 --- a/source/common/quic/envoy_quic_dispatcher.h +++ b/source/common/quic/envoy_quic_dispatcher.h @@ -60,12 +60,19 @@ class EnvoyQuicDispatcher : public quic::QuicDispatcher { quic::ConnectionCloseSource source) override; protected: + // quic::QuicDispatcher std::unique_ptr CreateQuicSession(quic::QuicConnectionId server_connection_id, const quic::QuicSocketAddress& self_address, const quic::QuicSocketAddress& peer_address, absl::string_view alpn, const quic::ParsedQuicVersion& version, absl::string_view sni) override; + // Overridden to restore the first 4 bytes of the connection ID because our BPF filter only looks + // at the first 4 bytes. This ensures that the replacement routes to the same quic dispatcher. + quic::QuicConnectionId + ReplaceLongServerConnectionId(const quic::ParsedQuicVersion& version, + const quic::QuicConnectionId& server_connection_id, + uint8_t expected_server_connection_id_length) const override; private: Network::ConnectionHandler& connection_handler_; diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 1e1cc4def1917..1653d6e6f36e6 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -199,7 +199,7 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers std::vector codec_clients; for (size_t i = 1; i <= concurrency_; ++i) { // The BPF filter and ActiveQuicListener::destination() look at the 1st word of connection id - // in the packet header. And currently all QUIC versions support 8 bytes connection id. So + // in the packet header. And currently all QUIC versions support >= 8 bytes connection id. So // create connections with the first 4 bytes of connection id different from each // other so they should be evenly distributed. designated_connection_ids_.push_back(quic::test::TestConnectionId(i << 32)); @@ -232,6 +232,24 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers } } for (size_t i = 0; i < concurrency_; ++i) { + fake_upstream_connection_ = nullptr; + upstream_request_ = nullptr; + auto encoder_decoder = + codec_clients[i]->startRequest(Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}}); + auto& request_encoder = encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + codec_clients[i]->sendData(request_encoder, 0, true); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}, + {"set-cookie", "foo"}, + {"set-cookie", "bar"}}, + true); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); codec_clients[i]->close(); } } @@ -400,7 +418,18 @@ TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsNoBPF) { testMultipleQuicConnections(); } -TEST_P(QuicHttpIntegrationTest, ConnectionMigration) { +// Tests that the packets from a connection with CID longer than 8 bytes are routed to the same +// worker. +TEST_P(QuicHttpIntegrationTest, MultiWorkerWithLongConnectionId) { + concurrency_ = 8; + set_reuse_port_ = true; + initialize(); + // Setup 9-byte CID for the next connection. + designated_connection_ids_.push_back(quic::test::TestConnectionIdNineBytesLong(2u)); + testRouterHeaderOnlyRequestAndResponse(); +} + +TEST_P(QuicHttpIntegrationTest, PortMigration) { concurrency_ = 2; set_reuse_port_ = true; initialize(); From 4c078edb82e4956a1717cf841c095d2dd63cccca Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Thu, 6 May 2021 11:48:51 -0700 Subject: [PATCH 163/209] grid: Use the alternate protocols cache in the grid (#16347) Allow the ConnectivityGrid to take an optional AlternateProtocolsCache and use that for deciding if HTTP/3 should be enabled. Rename AlternateProtocols to AlternateProtocolsCache. Use uint32_t instead of int for ports in AlternateProtocolsCache to match other code in Envoy. Follow up PRs will make the AlternateProtocolsCache mandatory and will plumb it in from the ClusterManager, based on the config. Risk Level: Low Testing: new unit tests Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Part of #15649 Signed-off-by: Ryan Hamilton --- source/common/http/BUILD | 7 +- source/common/http/alternate_protocols.cc | 46 ----- .../common/http/alternate_protocols_cache.cc | 51 ++++++ ...rotocols.h => alternate_protocols_cache.h} | 16 +- source/common/http/conn_pool_grid.cc | 65 ++++++- source/common/http/conn_pool_grid.h | 8 + .../common/upstream/cluster_manager_impl.cc | 4 +- test/common/http/BUILD | 6 +- ...t.cc => alternate_protocols_cache_test.cc} | 44 ++--- test/common/http/conn_pool_grid_test.cc | 160 +++++++++++++++++- 10 files changed, 313 insertions(+), 94 deletions(-) delete mode 100644 source/common/http/alternate_protocols.cc create mode 100644 source/common/http/alternate_protocols_cache.cc rename source/common/http/{alternate_protocols.h => alternate_protocols_cache.h} (91%) rename test/common/http/{alternate_protocols_test.cc => alternate_protocols_cache_test.cc} (53%) diff --git a/source/common/http/BUILD b/source/common/http/BUILD index a8056973014fe..ab7fd3ba3c6c9 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -160,9 +160,9 @@ envoy_cc_library( ) envoy_cc_library( - name = "alternate_protocols", - srcs = ["alternate_protocols.cc"], - hdrs = ["alternate_protocols.h"], + name = "alternate_protocols_cache", + srcs = ["alternate_protocols_cache.cc"], + hdrs = ["alternate_protocols_cache.h"], deps = [ "//include/envoy/common:time_interface", ], @@ -173,6 +173,7 @@ envoy_cc_library( srcs = ["conn_pool_grid.cc"], hdrs = ["conn_pool_grid.h"], deps = [ + ":alternate_protocols_cache", ":http3_status_tracker", ":mixed_conn_pool", "//source/common/http/http3:conn_pool_lib", diff --git a/source/common/http/alternate_protocols.cc b/source/common/http/alternate_protocols.cc deleted file mode 100644 index 25c7f0860756e..0000000000000 --- a/source/common/http/alternate_protocols.cc +++ /dev/null @@ -1,46 +0,0 @@ -#include "common/http/alternate_protocols.h" - -namespace Envoy { -namespace Http { - -AlternateProtocols::AlternateProtocol::AlternateProtocol(absl::string_view alpn, - absl::string_view hostname, int port) - : alpn_(alpn), hostname_(hostname), port_(port) {} - -AlternateProtocols::Origin::Origin(absl::string_view scheme, absl::string_view hostname, int port) - : scheme_(scheme), hostname_(hostname), port_(port) {} - -AlternateProtocols::AlternateProtocols(TimeSource& time_source) : time_source_(time_source) {} - -void AlternateProtocols::setAlternatives(const Origin& origin, - const std::vector& protocols, - const MonotonicTime& expiration) { - Entry& entry = protocols_[origin]; - if (entry.protocols_ != protocols) { - entry.protocols_ = protocols; - } - if (entry.expiration_ != expiration) { - entry.expiration_ = expiration; - } -} - -OptRef> -AlternateProtocols::findAlternatives(const Origin& origin) { - auto entry_it = protocols_.find(origin); - if (entry_it == protocols_.end()) { - return makeOptRefFromPtr>(nullptr); - } - - const Entry& entry = entry_it->second; - if (time_source_.monotonicTime() > entry.expiration_) { - // Expire the entry. - protocols_.erase(entry_it); - return makeOptRefFromPtr>(nullptr); - } - return makeOptRef(entry.protocols_); -} - -size_t AlternateProtocols::size() const { return protocols_.size(); } - -} // namespace Http -} // namespace Envoy diff --git a/source/common/http/alternate_protocols_cache.cc b/source/common/http/alternate_protocols_cache.cc new file mode 100644 index 0000000000000..dfb5deb1fc95b --- /dev/null +++ b/source/common/http/alternate_protocols_cache.cc @@ -0,0 +1,51 @@ +#include "common/http/alternate_protocols_cache.h" + +namespace Envoy { +namespace Http { + +AlternateProtocolsCache::AlternateProtocol::AlternateProtocol(absl::string_view alpn, + absl::string_view hostname, + uint32_t port) + : alpn_(alpn), hostname_(hostname), port_(port) {} + +AlternateProtocolsCache::Origin::Origin(absl::string_view scheme, absl::string_view hostname, + uint32_t port) + : scheme_(scheme), hostname_(hostname), port_(port) {} + +AlternateProtocolsCache::AlternateProtocolsCache(TimeSource& time_source) + : time_source_(time_source) {} + +void AlternateProtocolsCache::setAlternatives(const Origin& origin, + const std::vector& protocols, + const MonotonicTime& expiration) { + Entry& entry = protocols_[origin]; + if (entry.protocols_ != protocols) { + entry.protocols_ = protocols; + } + if (entry.expiration_ != expiration) { + entry.expiration_ = expiration; + } +} + +OptRef> +AlternateProtocolsCache::findAlternatives(const Origin& origin) { + auto entry_it = protocols_.find(origin); + if (entry_it == protocols_.end()) { + return makeOptRefFromPtr>( + nullptr); + } + + const Entry& entry = entry_it->second; + if (time_source_.monotonicTime() > entry.expiration_) { + // Expire the entry. + protocols_.erase(entry_it); + return makeOptRefFromPtr>( + nullptr); + } + return makeOptRef(entry.protocols_); +} + +size_t AlternateProtocolsCache::size() const { return protocols_.size(); } + +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/alternate_protocols.h b/source/common/http/alternate_protocols_cache.h similarity index 91% rename from source/common/http/alternate_protocols.h rename to source/common/http/alternate_protocols_cache.h index 39e90c887a4ca..d879ff7f23b13 100644 --- a/source/common/http/alternate_protocols.h +++ b/source/common/http/alternate_protocols_cache.h @@ -17,12 +17,12 @@ namespace Http { // See https://tools.ietf.org/html/rfc7838 for HTTP Alternate Services and // https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04 for the // "HTTPS" DNS resource record. -class AlternateProtocols { +class AlternateProtocolsCache { public: // Represents an HTTP origin to be connected too. struct Origin { public: - Origin(absl::string_view scheme, absl::string_view hostname, int port); + Origin(absl::string_view scheme, absl::string_view hostname, uint32_t port); bool operator==(const Origin& other) const { return std::tie(scheme_, hostname_, port_) == @@ -53,13 +53,13 @@ class AlternateProtocols { std::string scheme_; std::string hostname_; - int port_{}; + uint32_t port_{}; }; // Represents an alternative protocol that can be used to connect to an origin. struct AlternateProtocol { public: - AlternateProtocol(absl::string_view alpn, absl::string_view hostname, int port); + AlternateProtocol(absl::string_view alpn, absl::string_view hostname, uint32_t port); bool operator==(const AlternateProtocol& other) const { return std::tie(alpn_, hostname_, port_) == @@ -70,10 +70,10 @@ class AlternateProtocols { std::string alpn_; std::string hostname_; - int port_; + uint32_t port_; }; - explicit AlternateProtocols(TimeSource& time_source); + explicit AlternateProtocolsCache(TimeSource& time_source); // Sets the possible alternative protocols which can be used to connect to the // specified origin. Expires after the specified expiration time. @@ -82,8 +82,8 @@ class AlternateProtocols { // Returns the possible alternative protocols which can be used to connect to the // specified origin, or nullptr if not alternatives are found. The returned pointer - // is owned by the AlternateProtocols and is valid until the next operation on - // AlternateProtocols. + // is owned by the AlternateProtocolsCache and is valid until the next operation on + // AlternateProtocolsCache. OptRef> findAlternatives(const Origin& origin); // Returns the number of entries in the map. diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc index 521b3a2e362d0..3613d787a4959 100644 --- a/source/common/http/conn_pool_grid.cc +++ b/source/common/http/conn_pool_grid.cc @@ -3,6 +3,8 @@ #include "common/http/http3/conn_pool.h" #include "common/http/mixed_conn_pool.h" +#include "quiche/quic/core/quic_versions.h" + namespace Envoy { namespace Http { @@ -190,11 +192,12 @@ ConnectivityGrid::ConnectivityGrid( const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options, Upstream::ClusterConnectivityState& state, TimeSource& time_source, + OptRef alternate_protocols, std::chrono::milliseconds next_attempt_duration, ConnectivityOptions connectivity_options) : dispatcher_(dispatcher), random_generator_(random_generator), host_(host), priority_(priority), options_(options), transport_socket_options_(transport_socket_options), state_(state), next_attempt_duration_(next_attempt_duration), time_source_(time_source), - http3_status_tracker_(dispatcher_) { + http3_status_tracker_(dispatcher_), alternate_protocols_(alternate_protocols) { // ProdClusterManagerFactory::allocateConnPool verifies the protocols are HTTP/1, HTTP/2 and // HTTP/3. // TODO(#15649) support v6/v4, WiFi/cellular. @@ -247,11 +250,8 @@ ConnectionPool::Cancellable* ConnectivityGrid::newStream(Http::ResponseDecoder& createNextPool(); } PoolIterator pool = pools_.begin(); - if (http3_status_tracker_.isHttp3Broken()) { - ENVOY_LOG(trace, "HTTP/3 is broken to host '{}', skipping.", describePool(**pool), - host_->hostname()); - // Since HTTP/3 is broken, presumably both pools have already been created so this - // is just to be safe. + if (!shouldAttemptHttp3()) { + // Before skipping to the next pool, make sure it has been created. createNextPool(); ++pool; } @@ -334,5 +334,58 @@ void ConnectivityGrid::onDrainReceived() { } } +bool ConnectivityGrid::shouldAttemptHttp3() { + if (http3_status_tracker_.isHttp3Broken()) { + ENVOY_LOG(trace, "HTTP/3 is broken to host '{}', skipping.", host_->hostname()); + return false; + } + if (!alternate_protocols_.has_value()) { + ENVOY_LOG(trace, "No alternate protocols cache. Attempting HTTP/3 to host '{}'.", + host_->hostname()); + return true; + } + if (host_->address()->type() != Network::Address::Type::Ip) { + ENVOY_LOG(error, "Address is not an IP address"); + ASSERT(false); + return false; + } + uint32_t port = host_->address()->ip()->port(); + // TODO(RyanTheOptimist): Figure out how scheme gets plumbed in here. + AlternateProtocolsCache::Origin origin("https", host_->hostname(), port); + OptRef> protocols = + alternate_protocols_->findAlternatives(origin); + if (!protocols.has_value()) { + ENVOY_LOG(trace, "No alternate protocols available for host '{}', skipping HTTP/3.", + host_->hostname()); + return false; + } + + for (const AlternateProtocolsCache::AlternateProtocol& protocol : protocols.ref()) { + // TODO(RyanTheOptimist): Handle alternate protocols which change hostname or port. + if (!protocol.hostname_.empty() || protocol.port_ != port) { + ENVOY_LOG(trace, + "Alternate protocol for host '{}' attempts to change host or port, skipping.", + host_->hostname()); + continue; + } + + // TODO(RyanTheOptimist): Cache this mapping, but handle the supported versions list + // changing dynamically. + for (const quic::ParsedQuicVersion& version : quic::CurrentSupportedVersions()) { + if (quic::AlpnForVersion(version) == protocol.alpn_) { + // TODO(RyanTheOptimist): Pass this version down to the HTTP/3 pool. + ENVOY_LOG(trace, "HTTP/3 advertised for host '{}'", host_->hostname()); + return true; + } + } + + ENVOY_LOG(trace, "Alternate protocol for host '{}' has unsupported ALPN '{}', skipping.", + host_->hostname(), protocol.alpn_); + } + + ENVOY_LOG(trace, "HTTP/3 is not available to host '{}', skipping.", host_->hostname()); + return false; +} + } // namespace Http } // namespace Envoy diff --git a/source/common/http/conn_pool_grid.h b/source/common/http/conn_pool_grid.h index 69c2a303c6c3a..6f3903ed966c4 100644 --- a/source/common/http/conn_pool_grid.h +++ b/source/common/http/conn_pool_grid.h @@ -1,5 +1,6 @@ #pragma once +#include "common/http/alternate_protocols_cache.h" #include "common/http/conn_pool_base.h" #include "common/http/http3_status_tracker.h" @@ -131,6 +132,7 @@ class ConnectivityGrid : public ConnectionPool::Instance, const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options, Upstream::ClusterConnectivityState& state, TimeSource& time_source, + OptRef alternate_protocols, std::chrono::milliseconds next_attempt_duration, ConnectivityOptions connectivity_options); ~ConnectivityGrid() override; @@ -170,6 +172,10 @@ class ConnectivityGrid : public ConnectionPool::Instance, // drained_callbacks_ once all pools have drained. void onDrainReceived(); + // Returns true if HTTP/3 should be attempted because there is an alternate protocol + // that specifies HTTP/3 and HTTP/3 is not broken. + bool shouldAttemptHttp3(); + // Creates the next pool in the priority list, or absl::nullopt if all pools // have been created. virtual absl::optional createNextPool(); @@ -185,6 +191,8 @@ class ConnectivityGrid : public ConnectionPool::Instance, std::chrono::milliseconds next_attempt_duration_; TimeSource& time_source_; Http3StatusTracker http3_status_tracker_; + // TODO(RyanTheOptimist): Make the alternate_protocols_ member non-optional. + OptRef alternate_protocols_; // Tracks how many drains are needed before calling drain callbacks. This is // set to the number of pools when the first drain callbacks are added, and diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 63eaac2820e46..b029e4e94d126 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -1522,10 +1522,12 @@ Http::ConnectionPool::InstancePtr ProdClusterManagerFactory::allocateConnPool( ASSERT(contains(protocols, {Http::Protocol::Http11, Http::Protocol::Http2, Http::Protocol::Http3})); #ifdef ENVOY_ENABLE_QUIC + // TODO(RyanTheOptimist): Plumb an actual alternate protocols cache. + auto alternate_protocols = makeOptRefFromPtr(nullptr); Envoy::Http::ConnectivityGrid::ConnectivityOptions coptions{protocols}; return std::make_unique( dispatcher, api_.randomGenerator(), host, priority, options, transport_socket_options, - state, source, std::chrono::milliseconds(300), coptions); + state, source, alternate_protocols, std::chrono::milliseconds(300), coptions); #else // Should be blocked by configuration checking at an earlier point. NOT_REACHED_GCOVR_EXCL_LINE; diff --git a/test/common/http/BUILD b/test/common/http/BUILD index 04d0fa0b10ec3..f4beec13a489b 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -451,11 +451,11 @@ envoy_cc_test( ) envoy_cc_test( - name = "alternate_protocols_test", - srcs = ["alternate_protocols_test.cc"], + name = "alternate_protocols_cache_test", + srcs = ["alternate_protocols_cache_test.cc"], deps = [ ":common_lib", - "//source/common/http:alternate_protocols", + "//source/common/http:alternate_protocols_cache", "//test/mocks:common_lib", "//test/test_common:simulated_time_system_lib", ], diff --git a/test/common/http/alternate_protocols_test.cc b/test/common/http/alternate_protocols_cache_test.cc similarity index 53% rename from test/common/http/alternate_protocols_test.cc rename to test/common/http/alternate_protocols_cache_test.cc index 211b2313fb3b0..8891827fa3d1d 100644 --- a/test/common/http/alternate_protocols_test.cc +++ b/test/common/http/alternate_protocols_cache_test.cc @@ -1,4 +1,4 @@ -#include "common/http/alternate_protocols.h" +#include "common/http/alternate_protocols_cache.h" #include "test/test_common/simulated_time_system.h" @@ -8,64 +8,64 @@ namespace Envoy { namespace Http { namespace { -class AlternateProtocolsTest : public testing::Test, public Event::TestUsingSimulatedTime { +class AlternateProtocolsCacheTest : public testing::Test, public Event::TestUsingSimulatedTime { public: - AlternateProtocolsTest() : protocols_(simTime()) {} + AlternateProtocolsCacheTest() : protocols_(simTime()) {} - AlternateProtocols protocols_; + AlternateProtocolsCache protocols_; const std::string hostname1_ = "hostname1"; const std::string hostname2_ = "hostname2"; - const int port1_ = 1; - const int port2_ = 2; + const uint32_t port1_ = 1; + const uint32_t port2_ = 2; const std::string https_ = "https"; const std::string http_ = "http"; const std::string alpn1_ = "alpn1"; const std::string alpn2_ = "alpn2"; - const AlternateProtocols::Origin origin1_ = {https_, hostname1_, port1_}; - const AlternateProtocols::Origin origin2_ = {https_, hostname2_, port2_}; + const AlternateProtocolsCache::Origin origin1_ = {https_, hostname1_, port1_}; + const AlternateProtocolsCache::Origin origin2_ = {https_, hostname2_, port2_}; - const AlternateProtocols::AlternateProtocol protocol1_ = {alpn1_, hostname1_, port1_}; - const AlternateProtocols::AlternateProtocol protocol2_ = {alpn2_, hostname2_, port2_}; + const AlternateProtocolsCache::AlternateProtocol protocol1_ = {alpn1_, hostname1_, port1_}; + const AlternateProtocolsCache::AlternateProtocol protocol2_ = {alpn2_, hostname2_, port2_}; - const std::vector protocols1_ = {protocol1_}; - const std::vector protocols2_ = {protocol2_}; + const std::vector protocols1_ = {protocol1_}; + const std::vector protocols2_ = {protocol2_}; const MonotonicTime expiration1_ = simTime().monotonicTime() + Seconds(5); const MonotonicTime expiration2_ = simTime().monotonicTime() + Seconds(10); }; -TEST_F(AlternateProtocolsTest, Init) { EXPECT_EQ(0, protocols_.size()); } +TEST_F(AlternateProtocolsCacheTest, Init) { EXPECT_EQ(0, protocols_.size()); } -TEST_F(AlternateProtocolsTest, SetAlternatives) { +TEST_F(AlternateProtocolsCacheTest, SetAlternatives) { EXPECT_EQ(0, protocols_.size()); protocols_.setAlternatives(origin1_, protocols1_, expiration1_); EXPECT_EQ(1, protocols_.size()); } -TEST_F(AlternateProtocolsTest, FindAlternatives) { +TEST_F(AlternateProtocolsCacheTest, FindAlternatives) { protocols_.setAlternatives(origin1_, protocols1_, expiration1_); - OptRef> protocols = + OptRef> protocols = protocols_.findAlternatives(origin1_); ASSERT_TRUE(protocols.has_value()); EXPECT_EQ(protocols1_, protocols.ref()); } -TEST_F(AlternateProtocolsTest, FindAlternativesAfterReplacement) { +TEST_F(AlternateProtocolsCacheTest, FindAlternativesAfterReplacement) { protocols_.setAlternatives(origin1_, protocols1_, expiration1_); protocols_.setAlternatives(origin1_, protocols2_, expiration2_); - OptRef> protocols = + OptRef> protocols = protocols_.findAlternatives(origin1_); ASSERT_TRUE(protocols.has_value()); EXPECT_EQ(protocols2_, protocols.ref()); EXPECT_NE(protocols1_, protocols.ref()); } -TEST_F(AlternateProtocolsTest, FindAlternativesForMultipleOrigins) { +TEST_F(AlternateProtocolsCacheTest, FindAlternativesForMultipleOrigins) { protocols_.setAlternatives(origin1_, protocols1_, expiration1_); protocols_.setAlternatives(origin2_, protocols2_, expiration2_); - OptRef> protocols = + OptRef> protocols = protocols_.findAlternatives(origin1_); ASSERT_TRUE(protocols.has_value()); EXPECT_EQ(protocols1_, protocols.ref()); @@ -74,10 +74,10 @@ TEST_F(AlternateProtocolsTest, FindAlternativesForMultipleOrigins) { EXPECT_EQ(protocols2_, protocols.ref()); } -TEST_F(AlternateProtocolsTest, FindAlternativesAfterExpiration) { +TEST_F(AlternateProtocolsCacheTest, FindAlternativesAfterExpiration) { protocols_.setAlternatives(origin1_, protocols1_, expiration1_); simTime().setMonotonicTime(expiration1_ + Seconds(1)); - OptRef> protocols = + OptRef> protocols = protocols_.findAlternatives(origin1_); ASSERT_FALSE(protocols.has_value()); EXPECT_EQ(0, protocols_.size()); diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index 8b588626ae3c5..4350e46a9efc1 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -1,3 +1,4 @@ +#include "common/http/alternate_protocols_cache.h" #include "common/http/conn_pool_grid.h" #include "test/common/http/common.h" @@ -93,19 +94,35 @@ class ConnectivityGridForTest : public ConnectivityGrid { }; namespace { -class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testing::Test { +class ConnectivityGridTestBase : public Event::TestUsingSimulatedTime, public testing::Test { public: - ConnectivityGridTest() + ConnectivityGridTestBase(bool use_alternate_protocols) : options_({Http::Protocol::Http11, Http::Protocol::Http2, Http::Protocol::Http3}), + alternate_protocols_(simTime()), grid_(dispatcher_, random_, Upstream::makeTestHost(cluster_, "hostname", "tcp://127.0.0.1:9000", simTime()), Upstream::ResourcePriority::Default, socket_options_, transport_socket_options_, - state_, simTime(), std::chrono::milliseconds(300), options_), + state_, simTime(), maybeCreateAlternateProtocolsCache(use_alternate_protocols), + std::chrono::milliseconds(300), options_), host_(grid_.host()) { grid_.info_ = &info_; grid_.encoder_ = &encoder_; } + OptRef maybeCreateAlternateProtocolsCache(bool use_alternate_protocols) { + if (!use_alternate_protocols) { + return makeOptRefFromPtr(nullptr); + } + return alternate_protocols_; + } + + void addHttp3AlternateProtocol() { + AlternateProtocolsCache::Origin origin("https", "hostname", 9000); + const std::vector protocols = { + {"h3-29", "", origin.port_}}; + alternate_protocols_.setAlternatives(origin, protocols, simTime().monotonicTime() + Seconds(5)); + } + const Network::ConnectionSocket::OptionsSharedPtr socket_options_; const Network::TransportSocketOptionsSharedPtr transport_socket_options_; ConnectivityGrid::ConnectivityOptions options_; @@ -113,6 +130,7 @@ class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testin NiceMock dispatcher_; std::shared_ptr cluster_{new NiceMock()}; NiceMock random_; + AlternateProtocolsCache alternate_protocols_; ConnectivityGridForTest grid_; Upstream::HostDescriptionConstSharedPtr host_; @@ -123,12 +141,27 @@ class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testin NiceMock encoder_; }; +// Tests of the Grid in which no alternate protocols cache is configured. +class ConnectivityGridTest : public ConnectivityGridTestBase { +public: + ConnectivityGridTest() : ConnectivityGridTestBase(false) {} +}; + +// Tests of the Grid in which an alternate protocols cache is configured. +class ConnectivityGridWithAlternateProtocolsCacheTest : public ConnectivityGridTestBase { +public: + ConnectivityGridWithAlternateProtocolsCacheTest() : ConnectivityGridTestBase(true) {} +}; + // Test the first pool successfully connecting. TEST_F(ConnectivityGridTest, Success) { EXPECT_EQ(grid_.first(), nullptr); - EXPECT_NE(grid_.newStream(decoder_, callbacks_), nullptr); + EXPECT_LOG_CONTAINS("trace", + "No alternate protocols cache. Attempting HTTP/3 to host 'hostname'.", + EXPECT_NE(grid_.newStream(decoder_, callbacks_), nullptr)); EXPECT_NE(grid_.first(), nullptr); + EXPECT_EQ(grid_.second(), nullptr); // onPoolReady should be passed from the pool back to the original caller. ASSERT_NE(grid_.callbacks(), nullptr); @@ -424,10 +457,11 @@ TEST_F(ConnectivityGridTest, NoDrainOnTeardown) { // Test that when HTTP/3 is broken then the HTTP/3 pool is skipped. TEST_F(ConnectivityGridTest, SuccessAfterBroken) { + addHttp3AlternateProtocol(); grid_.markHttp3Broken(); EXPECT_EQ(grid_.first(), nullptr); - EXPECT_LOG_CONTAINS("trace", "HTTP/3 is broken to host 'first', skipping.", + EXPECT_LOG_CONTAINS("trace", "HTTP/3 is broken to host 'hostname', skipping.", EXPECT_NE(grid_.newStream(decoder_, callbacks_), nullptr)); EXPECT_NE(grid_.first(), nullptr); EXPECT_NE(grid_.second(), nullptr); @@ -439,6 +473,122 @@ TEST_F(ConnectivityGridTest, SuccessAfterBroken) { EXPECT_TRUE(grid_.isHttp3Broken()); } +// Test the HTTP/3 pool successfully connecting when HTTP/3 is available. +TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, Success) { + addHttp3AlternateProtocol(); + EXPECT_EQ(grid_.first(), nullptr); + + EXPECT_NE(grid_.newStream(decoder_, callbacks_), nullptr); + EXPECT_NE(grid_.first(), nullptr); + EXPECT_EQ(grid_.second(), nullptr); + + // onPoolReady should be passed from the pool back to the original caller. + ASSERT_NE(grid_.callbacks(), nullptr); + EXPECT_CALL(callbacks_.pool_ready_, ready()); + grid_.callbacks()->onPoolReady(encoder_, host_, info_, absl::nullopt); + EXPECT_FALSE(grid_.isHttp3Broken()); +} + +// Test that when HTTP/3 is not available then the HTTP/3 pool is skipped. +TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithoutHttp3) { + EXPECT_EQ(grid_.first(), nullptr); + + EXPECT_LOG_CONTAINS("trace", + "No alternate protocols available for host 'hostname', skipping HTTP/3.", + EXPECT_NE(grid_.newStream(decoder_, callbacks_), nullptr)); + EXPECT_NE(grid_.first(), nullptr); + EXPECT_NE(grid_.second(), nullptr); + + // onPoolReady should be passed from the pool back to the original caller. + ASSERT_NE(grid_.callbacks(), nullptr); + EXPECT_CALL(callbacks_.pool_ready_, ready()); + grid_.callbacks()->onPoolReady(encoder_, host_, info_, absl::nullopt); +} + +// Test that when HTTP/3 is not available then the HTTP/3 pool is skipped. +TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithExpiredHttp3) { + AlternateProtocolsCache::Origin origin("https", "hostname", 9000); + const std::vector protocols = { + {"h3-29", "", origin.port_}}; + alternate_protocols_.setAlternatives(origin, protocols, simTime().monotonicTime() + Seconds(5)); + simTime().setMonotonicTime(simTime().monotonicTime() + Seconds(10)); + + EXPECT_EQ(grid_.first(), nullptr); + + EXPECT_LOG_CONTAINS("trace", + "No alternate protocols available for host 'hostname', skipping HTTP/3.", + EXPECT_NE(grid_.newStream(decoder_, callbacks_), nullptr)); + EXPECT_NE(grid_.first(), nullptr); + EXPECT_NE(grid_.second(), nullptr); + + // onPoolReady should be passed from the pool back to the original caller. + ASSERT_NE(grid_.callbacks(), nullptr); + EXPECT_CALL(callbacks_.pool_ready_, ready()); + grid_.callbacks()->onPoolReady(encoder_, host_, info_, absl::nullopt); +} + +// Test that when the alternate protocol specifies a different host, then the HTTP/3 pool is +// skipped. +TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithoutHttp3NoMatchingHostname) { + AlternateProtocolsCache::Origin origin("https", "hostname", 9000); + const std::vector protocols = { + {"h3-29", "otherhostname", origin.port_}}; + alternate_protocols_.setAlternatives(origin, protocols, simTime().monotonicTime() + Seconds(5)); + + EXPECT_EQ(grid_.first(), nullptr); + + EXPECT_LOG_CONTAINS("trace", "HTTP/3 is not available to host 'hostname', skipping.", + EXPECT_NE(grid_.newStream(decoder_, callbacks_), nullptr)); + EXPECT_NE(grid_.first(), nullptr); + EXPECT_NE(grid_.second(), nullptr); + + // onPoolReady should be passed from the pool back to the original caller. + ASSERT_NE(grid_.callbacks(), nullptr); + EXPECT_CALL(callbacks_.pool_ready_, ready()); + grid_.callbacks()->onPoolReady(encoder_, host_, info_, absl::nullopt); +} + +// Test that when the alternate protocol specifies a different port, then the HTTP/3 pool is +// skipped. +TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithoutHttp3NoMatchingPort) { + AlternateProtocolsCache::Origin origin("https", "hostname", 9000); + const std::vector protocols = { + {"h3-29", "", origin.port_ + 1}}; + alternate_protocols_.setAlternatives(origin, protocols, simTime().monotonicTime() + Seconds(5)); + + EXPECT_EQ(grid_.first(), nullptr); + + EXPECT_LOG_CONTAINS("trace", "HTTP/3 is not available to host 'hostname', skipping.", + EXPECT_NE(grid_.newStream(decoder_, callbacks_), nullptr)); + EXPECT_NE(grid_.first(), nullptr); + EXPECT_NE(grid_.second(), nullptr); + + // onPoolReady should be passed from the pool back to the original caller. + ASSERT_NE(grid_.callbacks(), nullptr); + EXPECT_CALL(callbacks_.pool_ready_, ready()); + grid_.callbacks()->onPoolReady(encoder_, host_, info_, absl::nullopt); +} + +// Test that when the alternate protocol specifies an invalid ALPN, then the HTTP/3 pool is skipped. +TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithoutHttp3NoMatchingAlpn) { + AlternateProtocolsCache::Origin origin("https", "hostname", 9000); + const std::vector protocols = { + {"http/2", "", origin.port_}}; + alternate_protocols_.setAlternatives(origin, protocols, simTime().monotonicTime() + Seconds(5)); + + EXPECT_EQ(grid_.first(), nullptr); + + EXPECT_LOG_CONTAINS("trace", "HTTP/3 is not available to host 'hostname', skipping.", + EXPECT_NE(grid_.newStream(decoder_, callbacks_), nullptr)); + EXPECT_NE(grid_.first(), nullptr); + EXPECT_NE(grid_.second(), nullptr); + + // onPoolReady should be passed from the pool back to the original caller. + ASSERT_NE(grid_.callbacks(), nullptr); + EXPECT_CALL(callbacks_.pool_ready_, ready()); + grid_.callbacks()->onPoolReady(encoder_, host_, info_, absl::nullopt); +} + #ifdef ENVOY_ENABLE_QUICHE } // namespace From 6c672b75b1be59676bc5f576af96323e6c626a03 Mon Sep 17 00:00:00 2001 From: Samra Belachew Date: Thu, 6 May 2021 15:34:13 -0700 Subject: [PATCH 164/209] grpc: block stat collection in grpc bridge filter with runtime (#16284) Guard grpc bridge filter stat collection through runtime config in favor of existing grpc stat filter Documentation shows stat collection has been "deprecated" for a while; this change creates a runtime config to block stat collection. See #16286. Runtime guard: envoy.reloadable_features.grpc_bridge_stats_disabled Signed-off-by: Samra Belachew --- .../http_filters/grpc_http1_bridge_filter.rst | 7 ++- docs/root/version_history/current.rst | 2 + source/common/runtime/runtime_features.cc | 1 + .../grpc_http1_bridge/http1_bridge_filter.h | 8 ++- .../filters/http/grpc_http1_bridge/BUILD | 1 + .../http1_bridge_filter_test.cc | 63 +++++++++++++------ 6 files changed, 60 insertions(+), 22 deletions(-) diff --git a/docs/root/configuration/http/http_filters/grpc_http1_bridge_filter.rst b/docs/root/configuration/http/http_filters/grpc_http1_bridge_filter.rst index 5454468c5ac24..eac2f887a5cf0 100644 --- a/docs/root/configuration/http/http_filters/grpc_http1_bridge_filter.rst +++ b/docs/root/configuration/http/http_filters/grpc_http1_bridge_filter.rst @@ -38,9 +38,10 @@ More info: wire format in `gRPC over HTTP/2 `. The use of this filter for gRPC telemetry - has been deprecated. + Note that statistics should be collected by the dedicated :ref:`gRPC stats filter + ` instead. The use of this filter for gRPC telemetry + has been disabled. Set the runtime value of ``envoy.reloadable_features.grpc_bridge_stats_disabled`` + to false to turn on stat collection. Statistics ---------- diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 76651aa7f917e..50bef1756a674 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -5,6 +5,8 @@ Incompatible Behavior Changes ----------------------------- *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* +* grpc_bridge_filter: the filter no longer collects grpc stats in favor of the existing grpc stats filter. + The behavior can be reverted by changing runtime key ``envoy.reloadable_features.grpc_bridge_stats_disabled``. * tracing: update Apache SkyWalking tracer version to be compatible with 8.4.0 data collect protocol. This change will introduce incompatibility with SkyWalking 8.3.0. Minor Behavior Changes diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index cbabc221f4b39..01e83ce35caeb 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -65,6 +65,7 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.disable_tls_inspector_injection", "envoy.reloadable_features.dont_add_content_length_for_bodiless_requests", "envoy.reloadable_features.enable_compression_without_content_length_header", + "envoy.reloadable_features.grpc_bridge_stats_disabled", "envoy.reloadable_features.grpc_web_fix_non_proto_encoded_response_handling", "envoy.reloadable_features.grpc_json_transcoder_adhere_to_buffer_limits", "envoy.reloadable_features.hash_multiple_header_values", diff --git a/source/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter.h b/source/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter.h index 991458360fe96..84b1ad18a4d01 100644 --- a/source/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter.h +++ b/source/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter.h @@ -4,6 +4,7 @@ #include "envoy/upstream/cluster_manager.h" #include "common/grpc/context_impl.h" +#include "common/runtime/runtime_features.h" namespace Envoy { namespace Extensions { @@ -47,7 +48,12 @@ class Http1BridgeFilter : public Http::StreamFilter { encoder_callbacks_ = &callbacks; } - bool doStatTracking() const { return request_stat_names_.has_value(); } + bool doStatTracking() const { + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.grpc_bridge_stats_disabled")) { + return false; + } + return request_stat_names_.has_value(); + } private: void chargeStat(const Http::ResponseHeaderOrTrailerMap& headers); diff --git a/test/extensions/filters/http/grpc_http1_bridge/BUILD b/test/extensions/filters/http/grpc_http1_bridge/BUILD index a68ebdec1e392..386d6ffe900d6 100644 --- a/test/extensions/filters/http/grpc_http1_bridge/BUILD +++ b/test/extensions/filters/http/grpc_http1_bridge/BUILD @@ -21,6 +21,7 @@ envoy_extension_cc_test( "//source/extensions/filters/http/grpc_http1_bridge:http1_bridge_filter_lib", "//test/mocks/http:http_mocks", "//test/test_common:global_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter_test.cc b/test/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter_test.cc index 63f7fb1593873..6c7d238247950 100644 --- a/test/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter_test.cc +++ b/test/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter_test.cc @@ -8,6 +8,7 @@ #include "test/mocks/http/mocks.h" #include "test/test_common/global.h" #include "test/test_common/printers.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -29,6 +30,8 @@ class GrpcHttp1BridgeFilterTest : public testing::Test { filter_.setDecoderFilterCallbacks(decoder_callbacks_); filter_.setEncoderFilterCallbacks(encoder_callbacks_); ON_CALL(decoder_callbacks_.stream_info_, protocol()).WillByDefault(ReturnPointee(&protocol_)); + ON_CALL(*decoder_callbacks_.cluster_info_, statsScope()) + .WillByDefault(testing::ReturnRef(stats_store_)); } ~GrpcHttp1BridgeFilterTest() override { filter_.onDestroy(); } @@ -38,6 +41,7 @@ class GrpcHttp1BridgeFilterTest : public testing::Test { Http1BridgeFilter filter_; NiceMock decoder_callbacks_; NiceMock encoder_callbacks_; + NiceMock stats_store_; absl::optional protocol_{Http::Protocol::Http11}; }; @@ -69,6 +73,29 @@ TEST_F(GrpcHttp1BridgeFilterTest, NoCluster) { Http::TestResponseHeaderMapImpl response_headers{{":status", "404"}}; } +TEST_F(GrpcHttp1BridgeFilterTest, Http2HeaderOnlyResponse) { + protocol_ = Http::Protocol::Http2; + + Http::TestRequestHeaderMapImpl request_headers{ + {"content-type", "application/grpc"}, + {":path", "/lyft.users.BadCompanions/GetBadCompanions"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.decodeHeaders(request_headers, true)); + + Http::TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_.encode100ContinueHeaders(continue_headers)); + Http::MetadataMap metadata_map{{"metadata", "metadata"}}; + EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter_.encodeMetadata(metadata_map)); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, {"grpc-status", "1"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.encodeHeaders(response_headers, true)); + EXPECT_FALSE( + stats_store_.findCounterByString("grpc.lyft.users.BadCompanions.GetBadCompanions.failure")); + EXPECT_FALSE( + stats_store_.findCounterByString("grpc.lyft.users.BadCompanions.GetBadCompanions.total")); +} + TEST_F(GrpcHttp1BridgeFilterTest, StatsHttp2HeaderOnlyResponse) { protocol_ = Http::Protocol::Http2; @@ -76,6 +103,10 @@ TEST_F(GrpcHttp1BridgeFilterTest, StatsHttp2HeaderOnlyResponse) { {"content-type", "application/grpc"}, {":path", "/lyft.users.BadCompanions/GetBadCompanions"}}; + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.grpc_bridge_stats_disabled", "false"}}); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.decodeHeaders(request_headers, true)); Http::TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; @@ -86,6 +117,10 @@ TEST_F(GrpcHttp1BridgeFilterTest, StatsHttp2HeaderOnlyResponse) { Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, {"grpc-status", "1"}}; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.encodeHeaders(response_headers, true)); + EXPECT_TRUE( + stats_store_.findCounterByString("grpc.lyft.users.BadCompanions.GetBadCompanions.failure")); + EXPECT_TRUE( + stats_store_.findCounterByString("grpc.lyft.users.BadCompanions.GetBadCompanions.total")); EXPECT_EQ(1UL, decoder_callbacks_.clusterInfo() ->statsScope() .counterFromString("grpc.lyft.users.BadCompanions.GetBadCompanions.failure") @@ -96,7 +131,7 @@ TEST_F(GrpcHttp1BridgeFilterTest, StatsHttp2HeaderOnlyResponse) { .value()); } -TEST_F(GrpcHttp1BridgeFilterTest, StatsHttp2NormalResponse) { +TEST_F(GrpcHttp1BridgeFilterTest, Http2NormalResponse) { protocol_ = Http::Protocol::Http2; Http::TestRequestHeaderMapImpl request_headers{ @@ -111,17 +146,13 @@ TEST_F(GrpcHttp1BridgeFilterTest, StatsHttp2NormalResponse) { EXPECT_EQ(Http::FilterDataStatus::Continue, filter_.encodeData(data, false)); Http::TestResponseTrailerMapImpl response_trailers{{"grpc-status", "0"}}; EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_.encodeTrailers(response_trailers)); - EXPECT_EQ(1UL, decoder_callbacks_.clusterInfo() - ->statsScope() - .counterFromString("grpc.lyft.users.BadCompanions.GetBadCompanions.success") - .value()); - EXPECT_EQ(1UL, decoder_callbacks_.clusterInfo() - ->statsScope() - .counterFromString("grpc.lyft.users.BadCompanions.GetBadCompanions.total") - .value()); + EXPECT_FALSE( + stats_store_.findCounterByString("grpc.lyft.users.BadCompanions.GetBadCompanions.success")); + EXPECT_FALSE( + stats_store_.findCounterByString("grpc.lyft.users.BadCompanions.GetBadCompanions.total")); } -TEST_F(GrpcHttp1BridgeFilterTest, StatsHttp2ContentTypeGrpcPlusProto) { +TEST_F(GrpcHttp1BridgeFilterTest, Http2ContentTypeGrpcPlusProto) { protocol_ = Http::Protocol::Http2; Http::TestRequestHeaderMapImpl request_headers{ @@ -134,14 +165,10 @@ TEST_F(GrpcHttp1BridgeFilterTest, StatsHttp2ContentTypeGrpcPlusProto) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.encodeHeaders(response_headers, false)); Http::TestResponseTrailerMapImpl response_trailers{{"grpc-status", "0"}}; EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_.encodeTrailers(response_trailers)); - EXPECT_EQ(1UL, decoder_callbacks_.clusterInfo() - ->statsScope() - .counterFromString("grpc.lyft.users.BadCompanions.GetBadCompanions.success") - .value()); - EXPECT_EQ(1UL, decoder_callbacks_.clusterInfo() - ->statsScope() - .counterFromString("grpc.lyft.users.BadCompanions.GetBadCompanions.total") - .value()); + EXPECT_FALSE( + stats_store_.findCounterByString("grpc.lyft.users.BadCompanions.GetBadCompanions.success")); + EXPECT_FALSE( + stats_store_.findCounterByString("grpc.lyft.users.BadCompanions.GetBadCompanions.total")); } TEST_F(GrpcHttp1BridgeFilterTest, NotHandlingHttp2) { From 48321c97017335b57bf5ad149122db6e4bf28263 Mon Sep 17 00:00:00 2001 From: William A Rowe Jr Date: Thu, 6 May 2021 21:40:01 -0500 Subject: [PATCH 165/209] Use /c/build/ as HOME consistent with envoy linux docker env (#16368) This will persist changes and notes made in /home/wrowe between docker invocations and allow developers to manage desired constants such as .bazelrc Signed-off-by: William A Rowe Jr --- ci/run_envoy_docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index b545950d787eb..9a06ff233acb4 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -27,7 +27,7 @@ if is_windows; then BUILD_DIR_MOUNT_DEST=C:/build SOURCE_DIR=$(echo "${PWD}" | sed -E "s#^/([a-zA-Z])/#\1:/#") SOURCE_DIR_MOUNT_DEST=C:/source - START_COMMAND=("bash" "-c" "cd source && $*") + START_COMMAND=("bash" "-c" "cd /c/source && export HOME=/c/build && $*") else [[ -z "${IMAGE_NAME}" ]] && IMAGE_NAME="envoyproxy/envoy-build-ubuntu" # We run as root and later drop permissions. This is required to setup the USER From e52d5b365c07bb7efab0702020255c7a6ceb9e4e Mon Sep 17 00:00:00 2001 From: Yuchen Dai Date: Thu, 6 May 2021 19:40:31 -0700 Subject: [PATCH 166/209] test: temporarily disable RebalancerTest (#16367) Signed-off-by: Yuchen Dai --- .../listener_lds_integration_test.cc | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/test/integration/listener_lds_integration_test.cc b/test/integration/listener_lds_integration_test.cc index 8616adba8f7f9..6b9a2fc024bab 100644 --- a/test/integration/listener_lds_integration_test.cc +++ b/test/integration/listener_lds_integration_test.cc @@ -510,8 +510,7 @@ class RebalancerTest : public testing::TestWithParammutable_exact_balance(); - // 127.0.0.2 is defined in FakeOriginalDstListenerFilter. This virtual listener does not - // listen on a passive socket so it's safe to use any ip address. + // TODO(lambdai): Replace by getLoopbackAddressUrlString to emulate the real world. *virtual_listener_config.mutable_address()->mutable_socket_address()->mutable_address() = "127.0.0.2"; virtual_listener_config.mutable_address()->mutable_socket_address()->set_port_value(80); @@ -539,7 +538,11 @@ struct PerConnection { // Verify the connections are distributed evenly on the 2 worker threads of the redirected // listener. -TEST_P(RebalancerTest, RedirectConnectionIsBalancedOnDestinationListener) { +// Currently flaky because the virtual listener create listen socket anyway despite the socket is +// never used. Will enable this test once https://github.com/envoyproxy/envoy/pull/16259 is merged. +TEST_P(RebalancerTest, DISABLED_RedirectConnectionIsBalancedOnDestinationListener) { + auto ip_address_str = + Network::Test::getLoopbackAddressUrlString(TestEnvironment::getIpVersionsForTest().front()); concurrency_ = 2; int repeats = 10; initialize(); @@ -561,14 +564,14 @@ TEST_P(RebalancerTest, RedirectConnectionIsBalancedOnDestinationListener) { } } - ASSERT_EQ(TestUtility::findCounter(test_server_->statStore(), - "listener.127.0.0.2_80.worker_0.downstream_cx_total") - + ASSERT_EQ(TestUtility::findCounter( + test_server_->statStore(), + absl::StrCat("listener.", ip_address_str, "_80.worker_0.downstream_cx_total")) ->value(), repeats); - ASSERT_EQ(TestUtility::findCounter(test_server_->statStore(), - "listener.127.0.0.2_80.worker_1.downstream_cx_total") - + ASSERT_EQ(TestUtility::findCounter( + test_server_->statStore(), + absl::StrCat("listener.", ip_address_str, "_80.worker_1.downstream_cx_total")) ->value(), repeats); } From 8e7fae96713921b4db2b5e9aa9373661ad881c40 Mon Sep 17 00:00:00 2001 From: Yuchen Dai Date: Thu, 6 May 2021 20:18:08 -0700 Subject: [PATCH 167/209] Listener: fix mixed full listener update and intelligent listener update (#16177) Fix a crash when the filter chain only update listener update is superseded by the listener removal or full listener update. Risk Level: LOW Testing: unit test Signed-off-by: Yuchen Dai --- docs/root/version_history/current.rst | 1 + source/server/connection_handler_impl.cc | 9 +- test/common/http/conn_manager_impl_fuzz.proto | 2 +- test/common/http/conn_manager_impl_test_2.cc | 2 +- test/config/utility.cc | 2 +- .../http/jwt_authn/filter_integration_test.cc | 2 +- test/integration/xds_integration_test.cc | 78 ++++++++++++- test/server/connection_handler_test.cc | 103 +++++++++++++++++- test/server/listener_manager_impl_test.cc | 85 ++++++++++++++- 9 files changed, 271 insertions(+), 13 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 50bef1756a674..25d881e1d6e71 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -31,6 +31,7 @@ Bug Fixes * http: port stripping now works for CONNECT requests, though the port will be restored if the CONNECT request is sent upstream. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.strip_port_from_connect`` to false. * http: raise max configurable max_request_headers_kb limit to 8192 KiB (8MiB) from 96 KiB in http connection manager. +* listener: fix the crash which could happen when the ongoing filter chain only listener update is followed by the listener removal or full listener update. * validation: fix an issue that causes TAP sockets to panic during config validation mode. * xray: fix the default sampling 'rate' for AWS X-Ray tracer extension to be 5% as opposed to 50%. * zipkin: fix timestamp serializaiton in annotations. A prior bug fix exposed an issue with timestamps being serialized as strings. diff --git a/source/server/connection_handler_impl.cc b/source/server/connection_handler_impl.cc index 043fbdb20b539..84ea2bbc416fc 100644 --- a/source/server/connection_handler_impl.cc +++ b/source/server/connection_handler_impl.cc @@ -88,12 +88,13 @@ void ConnectionHandlerImpl::removeFilterChains( for (auto& listener : listeners_) { if (listener.second.listener_->listenerTag() == listener_tag) { listener.second.tcpListener()->get().deferredRemoveFilterChains(filter_chains); - // Completion is deferred because the above removeFilterChains() may defer delete connection. - Event::DeferredTaskUtil::deferredRun(dispatcher_, std::move(completion)); - return; + break; } } - NOT_REACHED_GCOVR_EXCL_LINE; + // Reach here if the target listener is found or the target listener was removed by a full + // listener update. In either case, the completion must be deferred so that any active connection + // referencing the filter chain can finish prior to deletion. + Event::DeferredTaskUtil::deferredRun(dispatcher_, std::move(completion)); } void ConnectionHandlerImpl::stopListeners(uint64_t listener_tag) { diff --git a/test/common/http/conn_manager_impl_fuzz.proto b/test/common/http/conn_manager_impl_fuzz.proto index 004af6a82e4bb..6299f9cfa20ef 100644 --- a/test/common/http/conn_manager_impl_fuzz.proto +++ b/test/common/http/conn_manager_impl_fuzz.proto @@ -42,7 +42,7 @@ message DecoderFilterCallbackAction { bool streaming = 2; } oneof decoder_filter_callback_action_selector { - // TODO(htuch): More decoder filer callback actions + // TODO(htuch): More decoder filter callback actions AddDecodedData add_decoded_data = 1; } } diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index d837d4d9fbadd..96adb828b74a6 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -1737,7 +1737,7 @@ TEST_F(HttpConnectionManagerImplTest, AddDataWithAllContinue) { } // This test verifies proper sequences of decodeData() and encodeData() are called -// when the first filer is "stopped" and "continue" in following case: +// when the first filter is "stopped" and "continue" in following case: // // 3 decode filters: // diff --git a/test/config/utility.cc b/test/config/utility.cc index 5668902035761..6a77b020e6909 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -69,7 +69,7 @@ std::string ConfigHelper::baseConfig() { address: 127.0.0.1 port_value: 0 listeners: - name: listener_0 + - name: listener_0 address: socket_address: address: 127.0.0.1 diff --git a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc index 8fb7f918e46a9..7adac3450ce60 100644 --- a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc @@ -226,7 +226,7 @@ TEST_P(LocalJwksIntegrationTest, CorsPreflight) { EXPECT_EQ("200", response->headers().getStatusValue()); } -// This test verifies JwtRequirement specified from filer state rules +// This test verifies JwtRequirement specified from filter state rules TEST_P(LocalJwksIntegrationTest, FilterStateRequirement) { // A config with metadata rules. const std::string auth_filter_conf = R"( diff --git a/test/integration/xds_integration_test.cc b/test/integration/xds_integration_test.cc index 1e95474dd945e..c5d3167719665 100644 --- a/test/integration/xds_integration_test.cc +++ b/test/integration/xds_integration_test.cc @@ -124,6 +124,18 @@ class LdsInplaceUpdateTcpProxyIntegrationTest "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy stat_prefix: tcp_stats cluster: cluster_1 + - name: another_listener_to_avoid_worker_thread_routine_exit + address: + socket_address: + address: "127.0.0.1" + port_value: 0 + filter_chains: + - filters: + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: tcp_stats + cluster: cluster_0 )EOF") {} void initialize() override { @@ -147,8 +159,8 @@ class LdsInplaceUpdateTcpProxyIntegrationTest BaseIntegrationTest::initialize(); - context_manager_ = - std::make_unique(timeSystem()); + context_manager_ = std::make_unique( + BaseIntegrationTest::timeSystem()); context_ = Ssl::createClientSslTransportSocketFactory({}, *context_manager_, *api_); } @@ -568,5 +580,67 @@ TEST_P(LdsIntegrationTest, FailConfigLoad) { }); EXPECT_DEATH(initialize(), "Didn't find a registered implementation for name: 'grewgragra'"); } + +// This test case uses `SimulatedTimeSystem` to stack two listener update in the same time point. +class LdsStsIntegrationTest : public Event::SimulatedTimeSystem, + public LdsInplaceUpdateTcpProxyIntegrationTest {}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, LdsStsIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +// Verify that the listener in place update will accomplish anyway if the listener is removed. +TEST_P(LdsStsIntegrationTest, TcpListenerRemoveFilterChainCalledAfterListenerIsRemoved) { + // The in place listener update takes 2 seconds. We will remove the listener. + drain_time_ = std::chrono::seconds(2); + // 1. Start the first in place listener update. + setUpstreamCount(2); + initialize(); + std::string response_0; + auto client_conn_0 = createConnectionAndWrite("alpn0", "hello", response_0); + client_conn_0->waitForConnection(); + FakeRawConnectionPtr fake_upstream_connection_0; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection_0)); + + ConfigHelper new_config_helper( + version_, *api_, MessageUtil::getJsonStringFromMessageOrDie(config_helper_.bootstrap())); + new_config_helper.addConfigModifier( + [&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); + listener->mutable_filter_chains()->RemoveLast(); + }); + new_config_helper.setLds("1"); + + // 2. Remove the tcp listener immediately. This listener update should stack in the same poller + // cycle so that this listener update has the same time stamp as the first update. + ConfigHelper new_config_helper1( + version_, *api_, MessageUtil::getJsonStringFromMessageOrDie(config_helper_.bootstrap())); + new_config_helper1.addConfigModifier( + [&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + bootstrap.mutable_static_resources()->mutable_listeners(0)->Swap( + bootstrap.mutable_static_resources()->mutable_listeners(1)); + bootstrap.mutable_static_resources()->mutable_listeners()->RemoveLast(); + }); + new_config_helper1.setLds("2"); + + std::string observed_data_0; + ASSERT_TRUE(fake_upstream_connection_0->waitForData(5, &observed_data_0)); + EXPECT_EQ("hello", observed_data_0); + + ASSERT_TRUE(fake_upstream_connection_0->write("world")); + while (response_0.find("world") == std::string::npos) { + client_conn_0->run(Event::Dispatcher::RunType::NonBlock); + } + client_conn_0->close(); + while (!client_conn_0->closed()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + // Wait for the filter chain removal start. Ideally we have `drain_time_` to detect the + // value 1. Increase the drain_time_ at the beginning of the test if the test is flaky. + test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 1); + // Wait for the filter chain removal at worker thread. When the value drops from 1, all pending + // removal at the worker is completed. This is the end of the in place update. + test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 0); +} } // namespace } // namespace Envoy diff --git a/test/server/connection_handler_test.cc b/test/server/connection_handler_test.cc index 1e9a174fee063..c15f984a4e943 100644 --- a/test/server/connection_handler_test.cc +++ b/test/server/connection_handler_test.cc @@ -31,6 +31,7 @@ using testing::_; using testing::HasSubstr; using testing::InSequence; using testing::Invoke; +using testing::MockFunction; using testing::NiceMock; using testing::Return; using testing::ReturnPointee; @@ -1046,7 +1047,7 @@ TEST_F(ConnectionHandlerTest, ListenerFilterReportError) { dispatcher_.clearDeferredDeleteList(); // Make sure the error leads to no listener timer created. EXPECT_CALL(dispatcher_, createTimer_(_)).Times(0); - // Make sure we never try to match the filer chain since listener filter doesn't complete. + // Make sure we never try to match the filter chain since listener filter doesn't complete. EXPECT_CALL(manager_, findFilterChain(_)).Times(0); EXPECT_CALL(*listener, onDestroy()); @@ -1146,7 +1147,7 @@ TEST_F(ConnectionHandlerTest, TcpListenerRemoveFilterChain) { const std::list filter_chains{filter_chain_.get()}; - // The completion callback is scheduled + // The completion callback is scheduled. handler_->removeFilterChains(listener_tag, filter_chains, []() { ENVOY_LOG(debug, "removed filter chains"); }); // Trigger the deletion if any. @@ -1162,6 +1163,104 @@ TEST_F(ConnectionHandlerTest, TcpListenerRemoveFilterChain) { handler_.reset(); } +// `removeListeners` and `removeFilterChains` are posted from main thread. The two post actions are +// triggered by two timers. In some corner cases, the two timers have the same expiration time +// point. Thus `removeListeners` may be executed prior to `removeFilterChains`. This test case +// verifies that the work threads remove the listener and filter chains successfully under the above +// sequence. +TEST_F(ConnectionHandlerTest, TcpListenerRemoveFilterChainCalledAfterListenerIsRemoved) { + InSequence s; + uint64_t listener_tag = 1; + Network::TcpListenerCallbacks* listener_callbacks; + auto listener = new NiceMock(); + TestListener* test_listener = + addListener(listener_tag, true, false, "test_listener", listener, &listener_callbacks); + EXPECT_CALL(*socket_factory_, localAddress()).WillOnce(ReturnRef(local_address_)); + handler_->addListener(absl::nullopt, *test_listener); + + Network::MockConnectionSocket* connection = new NiceMock(); + EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(filter_chain_.get())); + auto* server_connection = new NiceMock(); + EXPECT_CALL(dispatcher_, createServerConnection_()).WillOnce(Return(server_connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); + + listener_callbacks->onAccept(Network::ConnectionSocketPtr{connection}); + + EXPECT_EQ(1UL, handler_->numConnections()); + EXPECT_EQ(1UL, TestUtility::findCounter(stats_store_, "downstream_cx_total")->value()); + EXPECT_EQ(1UL, TestUtility::findGauge(stats_store_, "downstream_cx_active")->value()); + EXPECT_EQ(1UL, TestUtility::findCounter(stats_store_, "test.downstream_cx_total")->value()); + EXPECT_EQ(1UL, TestUtility::findGauge(stats_store_, "test.downstream_cx_active")->value()); + + EXPECT_CALL(*listener, onDestroy()); + handler_->stopListeners(listener_tag); + + EXPECT_CALL(dispatcher_, clearDeferredDeleteList()); + EXPECT_CALL(*access_log_, log(_, _, _, _)); + + { + // Filter chain removal in the same poll cycle but earlier. + handler_->removeListeners(listener_tag); + } + EXPECT_EQ(0UL, handler_->numConnections()); + + EXPECT_EQ(0UL, TestUtility::findGauge(stats_store_, "downstream_cx_active")->value()); + EXPECT_EQ(0UL, TestUtility::findGauge(stats_store_, "test.downstream_cx_active")->value()); + + const std::list filter_chains{filter_chain_.get()}; + MockFunction on_filter_chain_removed; + { + // Listener removal in the same poll cycle but later than filter chain removal. + handler_->removeFilterChains(listener_tag, filter_chains, + [&on_filter_chain_removed]() { on_filter_chain_removed.Call(); }); + } + EXPECT_CALL(dispatcher_, clearDeferredDeleteList()); + // on_filter_chain_removed must be deferred called. + EXPECT_CALL(on_filter_chain_removed, Call()); + dispatcher_.clearDeferredDeleteList(); + + // Final counters. + EXPECT_EQ(1UL, TestUtility::findCounter(stats_store_, "downstream_cx_total")->value()); + EXPECT_EQ(0UL, TestUtility::findGauge(stats_store_, "downstream_cx_active")->value()); + EXPECT_EQ(1UL, TestUtility::findCounter(stats_store_, "test.downstream_cx_total")->value()); + EXPECT_EQ(0UL, TestUtility::findGauge(stats_store_, "test.downstream_cx_active")->value()); + + // Verify that the callback is invoked already. + testing::Mock::VerifyAndClearExpectations(&on_filter_chain_removed); + handler_.reset(); +} + +TEST_F(ConnectionHandlerTest, TcpListenerRemoveListener) { + InSequence s; + + Network::TcpListenerCallbacks* listener_callbacks; + auto listener = new NiceMock(); + TestListener* test_listener = + addListener(1, true, false, "test_listener", listener, &listener_callbacks); + EXPECT_CALL(*socket_factory_, localAddress()).WillOnce(ReturnRef(local_address_)); + handler_->addListener(absl::nullopt, *test_listener); + + Network::MockConnectionSocket* connection = new NiceMock(); + EXPECT_CALL(*access_log_, log(_, _, _, _)); + listener_callbacks->onAccept(Network::ConnectionSocketPtr{connection}); + EXPECT_EQ(0UL, handler_->numConnections()); + + // Test stop/remove of not existent listener. + handler_->stopListeners(0); + handler_->removeListeners(0); + + EXPECT_CALL(*listener, onDestroy()); + handler_->stopListeners(1); + + EXPECT_CALL(dispatcher_, clearDeferredDeleteList()); + handler_->removeListeners(1); + EXPECT_EQ(0UL, handler_->numConnections()); + + // Test stop/remove of not existent listener. + handler_->stopListeners(0); + handler_->removeListeners(0); +} + TEST_F(ConnectionHandlerTest, TcpListenerGlobalCxLimitReject) { Network::TcpListenerCallbacks* listener_callbacks; auto listener = new NiceMock(); diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 2b1e1b2c4fe88..246579d4f1295 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -91,7 +91,8 @@ class ListenerManagerImplWithRealFiltersTest : public ListenerManagerImplTest { } }; -class ListenerManagerImplForInPlaceFilterChainUpdateTest : public ListenerManagerImplTest { +class ListenerManagerImplForInPlaceFilterChainUpdateTest : public Event::SimulatedTimeSystem, + public ListenerManagerImplTest { public: envoy::config::listener::v3::Listener createDefaultListener() { envoy::config::listener::v3::Listener listener_proto; @@ -4918,6 +4919,88 @@ traffic_direction: INBOUND EXPECT_CALL(*listener_foo_update1, onDestroy()); } +TEST_F(ListenerManagerImplForInPlaceFilterChainUpdateTest, RemoveTheInplaceUpdatingListener) { + InSequence s; + + EXPECT_CALL(*worker_, start(_)); + manager_->startWorkers(guard_dog_, callback_.AsStdFunction()); + + // Add foo listener into warming. + const std::string listener_foo_yaml = R"EOF( +name: foo +traffic_direction: INBOUND +address: + socket_address: + address: 127.0.0.1 + port_value: 1234 +filter_chains: +- filters: [] + )EOF"; + + ListenerHandle* listener_foo = expectListenerCreate(true, true); + EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, {true})); + EXPECT_CALL(listener_foo->target_, initialize()); + EXPECT_TRUE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(listener_foo_yaml), "", true)); + checkStats(__LINE__, 1, 0, 0, 1, 0, 0, 0); + EXPECT_CALL(*worker_, addListener(_, _, _)); + listener_foo->target_.ready(); + worker_->callAddCompletion(true); + EXPECT_EQ(1UL, manager_->listeners().size()); + + // Update foo into warming. + const auto listener_foo_update1_proto = parseListenerFromV3Yaml(R"EOF( +name: foo +traffic_direction: INBOUND +address: + socket_address: + address: 127.0.0.1 + port_value: 1234 +filter_chains: +- filters: + filter_chain_match: + destination_port: 1234 + )EOF"); + + ListenerHandle* listener_foo_update1 = expectListenerOverridden(true, listener_foo); + EXPECT_CALL(listener_foo_update1->target_, initialize()); + EXPECT_TRUE(manager_->addOrUpdateListener(listener_foo_update1_proto, "", true)); + EXPECT_EQ(1UL, manager_->listeners().size()); + EXPECT_EQ(1, server_.stats_store_.counter("listener_manager.listener_in_place_updated").value()); + checkStats(__LINE__, 1, 1, 0, 1, 1, 0, 0); + + // The warmed up starts the drain timer. + EXPECT_CALL(*worker_, addListener(_, _, _)); + EXPECT_CALL(server_.options_, drainTime()).WillOnce(Return(std::chrono::seconds(600))); + Event::MockTimer* filter_chain_drain_timer = new Event::MockTimer(&server_.dispatcher_); + EXPECT_CALL(*filter_chain_drain_timer, enableTimer(std::chrono::milliseconds(600000), _)); + listener_foo_update1->target_.ready(); + // Sub warming and bump draining filter chain. + checkStats(__LINE__, 1, 1, 0, 0, 1, 0, 1); + worker_->callAddCompletion(true); + + auto listener_foo_update2_proto = listener_foo_update1_proto; + listener_foo_update2_proto.set_traffic_direction( + ::envoy::config::core::v3::TrafficDirection::OUTBOUND); + ListenerHandle* listener_foo_update2 = expectListenerCreate(false, true); + expectUpdateToThenDrain(listener_foo_update2_proto, listener_foo_update1); + // Bump modified. + checkStats(__LINE__, 1, 2, 0, 0, 1, 0, 1); + + expectRemove(listener_foo_update2_proto, listener_foo_update2); + // Bump removed and sub active. + checkStats(__LINE__, 1, 2, 1, 0, 0, 0, 1); + + // Timer expires, worker close connections if any. + EXPECT_CALL(*worker_, removeFilterChains(_, _, _)); + filter_chain_drain_timer->invokeCallback(); + + // Once worker clean up is done, it's safe for the main thread to remove the original listener. + EXPECT_CALL(*listener_foo, onDestroy()); + worker_->callDrainFilterChainsComplete(); + // Sub draining filter chain. + checkStats(__LINE__, 1, 2, 1, 0, 0, 0, 0); +} + TEST_F(ListenerManagerImplTest, DrainageDuringInplaceUpdate) { InSequence s; From b91d63b8732c40a3df5ed5f1d962e8078c0d6da9 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 7 May 2021 04:19:44 +0100 Subject: [PATCH 168/209] dependabot: Updates (#16331) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 12 ++++++------ .../filters/network/thrift_proxy/requirements.txt | 6 +++--- tools/dependency/requirements.txt | 6 +++--- tools/deprecate_features/requirements.txt | 6 +++--- tools/deprecate_version/requirements.txt | 6 +++--- tools/testing/requirements.txt | 6 +++--- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 3647d79e14bdf..12f2d494a08ef 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -106,9 +106,9 @@ packaging==20.9 \ # via # -r docs/requirements.txt # sphinx -pygments==2.8.1 \ - --hash=sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94 \ - --hash=sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8 +pygments==2.9.0 \ + --hash=sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e \ + --hash=sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f # via # -r docs/requirements.txt # sphinx @@ -131,9 +131,9 @@ requests==2.25.1 \ # via # -r docs/requirements.txt # sphinx -six==1.15.0 \ - --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ - --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +six==1.16.0 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 # via # -r docs/requirements.txt # sphinxcontrib-httpdomain diff --git a/test/extensions/filters/network/thrift_proxy/requirements.txt b/test/extensions/filters/network/thrift_proxy/requirements.txt index 100ee46c8839e..f5868c4c7d449 100644 --- a/test/extensions/filters/network/thrift_proxy/requirements.txt +++ b/test/extensions/filters/network/thrift_proxy/requirements.txt @@ -1,5 +1,5 @@ thrift==0.13.0 \ --hash=sha256:9af1c86bf73433afc6010ed376a6c6aca2b54099cc0d61895f640870a9ae7d89 -six==1.15.0 \ - --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ - --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +six==1.16.0 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 diff --git a/tools/dependency/requirements.txt b/tools/dependency/requirements.txt index e9cec75b711c1..68995769e3133 100644 --- a/tools/dependency/requirements.txt +++ b/tools/dependency/requirements.txt @@ -136,9 +136,9 @@ requests==2.25.1 \ # via # -r tools/dependency/requirements.txt # pygithub -six==1.15.0 \ - --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ - --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +six==1.16.0 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 # via pynacl urllib3==1.26.4 \ --hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \ diff --git a/tools/deprecate_features/requirements.txt b/tools/deprecate_features/requirements.txt index 0cd0b42727685..643fcd2d4a395 100644 --- a/tools/deprecate_features/requirements.txt +++ b/tools/deprecate_features/requirements.txt @@ -1,3 +1,3 @@ -six==1.15.0 \ - --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ - --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +six==1.16.0 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 diff --git a/tools/deprecate_version/requirements.txt b/tools/deprecate_version/requirements.txt index 042b3f3e5911d..68732cef92589 100644 --- a/tools/deprecate_version/requirements.txt +++ b/tools/deprecate_version/requirements.txt @@ -115,9 +115,9 @@ requests==2.25.1 \ # via # -r tools/deprecate_version/requirements.txt # pygithub -six==1.15.0 \ - --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ - --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +six==1.16.0 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 # via pynacl smmap==4.0.0 \ --hash=sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182 \ diff --git a/tools/testing/requirements.txt b/tools/testing/requirements.txt index f192d8e37ba60..075ebbd7a2247 100644 --- a/tools/testing/requirements.txt +++ b/tools/testing/requirements.txt @@ -98,9 +98,9 @@ pytest-cov==2.11.1 \ --hash=sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7 \ --hash=sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da # via -r tools/testing/requirements.txt -pytest==6.2.3 \ - --hash=sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634 \ - --hash=sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc +pytest==6.2.4 \ + --hash=sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890 \ + --hash=sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b # via # -r tools/testing/requirements.txt # pytest-cov From 01a7a6b81d320657016a0b6074f0e2910cfe0596 Mon Sep 17 00:00:00 2001 From: chaoqin-li1123 <55518381+chaoqin-li1123@users.noreply.github.com> Date: Fri, 7 May 2021 02:02:54 -0500 Subject: [PATCH 169/209] Fix hash inconsistency in srds update (#16295) Currently we use MessageUtil::hash(scoped_route_config.key()) in detecting key conflict and scopeKey().hash() in some other places, which cause inconsistency and trigger assertion in some circumstances(the added test case). The new implementation will use scopeKey().hash() everywhere for consistency. Risk Level: Low Testing: Add integration test. Signed-off-by: chaoqin-li1123 --- source/common/router/scoped_rds.cc | 7 +++-- .../scoped_rds_integration_test.cc | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/source/common/router/scoped_rds.cc b/source/common/router/scoped_rds.cc index 581c0a05ee2b0..92b02189f4b89 100644 --- a/source/common/router/scoped_rds.cc +++ b/source/common/router/scoped_rds.cc @@ -297,6 +297,8 @@ ScopedRdsConfigSubscription::removeScopes( std::move(rds_config_provider_helper_iter->second)); route_provider_by_scope_.erase(rds_config_provider_helper_iter); } + ASSERT(scope_name_by_hash_.find(iter->second->scopeKey().hash()) != + scope_name_by_hash_.end()); scope_name_by_hash_.erase(iter->second->scopeKey().hash()); scoped_route_map_.erase(iter); removed_scope_names.push_back(scope_name); @@ -453,9 +455,10 @@ ScopedRdsConfigSubscription::detectUpdateConflictAndCleanupRemoved( exception_msg = fmt::format("duplicate scoped route configuration '{}' found", scope_name); return clean_removed_resources; } - const envoy::config::route::v3::ScopedRouteConfiguration& scoped_route_config = + envoy::config::route::v3::ScopedRouteConfiguration scoped_route_config = scope_config_inserted.first->second; - const uint64_t key_fingerprint = MessageUtil::hash(scoped_route_config.key()); + const uint64_t key_fingerprint = + ScopedRouteInfo(std::move(scoped_route_config), nullptr).scopeKey().hash(); if (!scope_name_by_hash.try_emplace(key_fingerprint, scope_name).second) { exception_msg = fmt::format("scope key conflict found, first scope is '{}', second scope is '{}'", diff --git a/test/integration/scoped_rds_integration_test.cc b/test/integration/scoped_rds_integration_test.cc index 783b9e0468597..512853adb3922 100644 --- a/test/integration/scoped_rds_integration_test.cc +++ b/test/integration/scoped_rds_integration_test.cc @@ -470,6 +470,36 @@ route_configuration_name: foo_route1 cleanupUpstreamAndDownstream(); } +TEST_P(ScopedRdsIntegrationTest, RejectKeyConflictInDeltaUpdate) { + if (!isDelta()) { + return; + } + const std::string scope_route1 = R"EOF( +name: foo_scope1 +route_configuration_name: foo_route1 +key: + fragments: + - string_key: foo +)EOF"; + on_server_init_function_ = [this, &scope_route1]() { + createScopedRdsStream(); + sendSrdsResponse({}, {scope_route1}, {}, "1"); + }; + initialize(); + // Delta SRDS update with key conflict, should be rejected. + const std::string scope_route2 = R"EOF( +name: foo_scope2 +route_configuration_name: foo_route1 +key: + fragments: + - string_key: foo +)EOF"; + sendSrdsResponse({}, {scope_route2}, {}, "2"); + test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_rejected", + 1); + sendSrdsResponse({}, {}, {"foo_scope1", "foo_scope2"}, "3"); +} + // Verify SRDS works when reference via a xdstp:// collection locator. TEST_P(ScopedRdsIntegrationTest, XdsTpCollection) { if (!isDelta()) { From a5020ce885592e8e3a5d9bb644554a30c0f79a18 Mon Sep 17 00:00:00 2001 From: Kuat Date: Fri, 7 May 2021 00:04:37 -0700 Subject: [PATCH 170/209] lightstep: allow file-less access token option (#16150) This allows self-contained bootstrap without extra token files. Signed-off-by: Kuat Yessenov --- api/envoy/config/trace/v3/lightstep.proto | 9 +++++- .../tracers/lightstep/v4alpha/BUILD | 1 + .../tracers/lightstep/v4alpha/lightstep.proto | 11 ++++++-- configs/envoy_double_proxy.template.yaml | 3 +- configs/envoy_front_proxy.template.yaml | 3 +- .../envoy/config/trace/v3/lightstep.proto | 9 +++++- .../tracers/lightstep/v4alpha/BUILD | 2 ++ .../tracers/lightstep/v4alpha/lightstep.proto | 9 +++++- source/extensions/tracers/lightstep/BUILD | 1 + source/extensions/tracers/lightstep/config.cc | 14 +++++++--- .../tracers/lightstep/config_test.cc | 28 ++++++++++++++++++- 11 files changed, 77 insertions(+), 13 deletions(-) diff --git a/api/envoy/config/trace/v3/lightstep.proto b/api/envoy/config/trace/v3/lightstep.proto index 0b7be7c4e6090..b5cff53fea96a 100644 --- a/api/envoy/config/trace/v3/lightstep.proto +++ b/api/envoy/config/trace/v3/lightstep.proto @@ -2,6 +2,9 @@ syntax = "proto3"; package envoy.config.trace.v3; +import "envoy/config/core/v3/base.proto"; + +import "envoy/annotations/deprecation.proto"; import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -42,7 +45,11 @@ message LightstepConfig { // File containing the access token to the `LightStep // `_ API. - string access_token_file = 2 [(validate.rules).string = {min_len: 1}]; + string access_token_file = 2 + [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; + + // Access token to the `LightStep `_ API. + core.v3.DataSource access_token = 4; // Propagation modes to use by LightStep's tracer. repeated PropagationMode propagation_modes = 3 diff --git a/api/envoy/extensions/tracers/lightstep/v4alpha/BUILD b/api/envoy/extensions/tracers/lightstep/v4alpha/BUILD index d500cc41da1fe..1d56979cc4660 100644 --- a/api/envoy/extensions/tracers/lightstep/v4alpha/BUILD +++ b/api/envoy/extensions/tracers/lightstep/v4alpha/BUILD @@ -6,6 +6,7 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/config/core/v4alpha:pkg", "//envoy/config/trace/v3:pkg", "@com_github_cncf_udpa//udpa/annotations:pkg", ], diff --git a/api/envoy/extensions/tracers/lightstep/v4alpha/lightstep.proto b/api/envoy/extensions/tracers/lightstep/v4alpha/lightstep.proto index d7e306754dc9c..11d5b2ea84a91 100644 --- a/api/envoy/extensions/tracers/lightstep/v4alpha/lightstep.proto +++ b/api/envoy/extensions/tracers/lightstep/v4alpha/lightstep.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package envoy.extensions.tracers.lightstep.v4alpha; +import "envoy/config/core/v4alpha/base.proto"; + import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; @@ -34,12 +36,15 @@ message LightstepConfig { TRACE_CONTEXT = 3; } + reserved 2; + + reserved "access_token_file"; + // The cluster manager cluster that hosts the LightStep collectors. string collector_cluster = 1 [(validate.rules).string = {min_len: 1}]; - // File containing the access token to the `LightStep - // `_ API. - string access_token_file = 2 [(validate.rules).string = {min_len: 1}]; + // Access token to the `LightStep `_ API. + config.core.v4alpha.DataSource access_token = 4; // Propagation modes to use by LightStep's tracer. repeated PropagationMode propagation_modes = 3 diff --git a/configs/envoy_double_proxy.template.yaml b/configs/envoy_double_proxy.template.yaml index 69d084d6bfbee..806ba9d03a418 100644 --- a/configs/envoy_double_proxy.template.yaml +++ b/configs/envoy_double_proxy.template.yaml @@ -67,7 +67,8 @@ name: envoy.tracers.lightstep typed_config: "@type": type.googleapis.com/envoy.config.trace.v3.LightstepConfig - access_token_file: "/etc/envoy/lightstep_access_token" + access_token: + filename: "/etc/envoy/lightstep_access_token" collector_cluster: lightstep_saas {% endif %} common_http_protocol_options: diff --git a/configs/envoy_front_proxy.template.yaml b/configs/envoy_front_proxy.template.yaml index 876c009aec1b7..48fcec3c0695a 100644 --- a/configs/envoy_front_proxy.template.yaml +++ b/configs/envoy_front_proxy.template.yaml @@ -79,7 +79,8 @@ typed_config: "@type": type.googleapis.com/envoy.config.trace.v3.LightstepConfig collector_cluster: lightstep_saas - access_token_file: "/etc/envoy/lightstep_access_token" + access_token: + filename: "/etc/envoy/lightstep_access_token" {% endif %} common_http_protocol_options: idle_timeout: 840s diff --git a/generated_api_shadow/envoy/config/trace/v3/lightstep.proto b/generated_api_shadow/envoy/config/trace/v3/lightstep.proto index 0b7be7c4e6090..b5cff53fea96a 100644 --- a/generated_api_shadow/envoy/config/trace/v3/lightstep.proto +++ b/generated_api_shadow/envoy/config/trace/v3/lightstep.proto @@ -2,6 +2,9 @@ syntax = "proto3"; package envoy.config.trace.v3; +import "envoy/config/core/v3/base.proto"; + +import "envoy/annotations/deprecation.proto"; import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -42,7 +45,11 @@ message LightstepConfig { // File containing the access token to the `LightStep // `_ API. - string access_token_file = 2 [(validate.rules).string = {min_len: 1}]; + string access_token_file = 2 + [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; + + // Access token to the `LightStep `_ API. + core.v3.DataSource access_token = 4; // Propagation modes to use by LightStep's tracer. repeated PropagationMode propagation_modes = 3 diff --git a/generated_api_shadow/envoy/extensions/tracers/lightstep/v4alpha/BUILD b/generated_api_shadow/envoy/extensions/tracers/lightstep/v4alpha/BUILD index d500cc41da1fe..8e63f3d426681 100644 --- a/generated_api_shadow/envoy/extensions/tracers/lightstep/v4alpha/BUILD +++ b/generated_api_shadow/envoy/extensions/tracers/lightstep/v4alpha/BUILD @@ -6,6 +6,8 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/annotations:pkg", + "//envoy/config/core/v4alpha:pkg", "//envoy/config/trace/v3:pkg", "@com_github_cncf_udpa//udpa/annotations:pkg", ], diff --git a/generated_api_shadow/envoy/extensions/tracers/lightstep/v4alpha/lightstep.proto b/generated_api_shadow/envoy/extensions/tracers/lightstep/v4alpha/lightstep.proto index d7e306754dc9c..c169d86e0ca07 100644 --- a/generated_api_shadow/envoy/extensions/tracers/lightstep/v4alpha/lightstep.proto +++ b/generated_api_shadow/envoy/extensions/tracers/lightstep/v4alpha/lightstep.proto @@ -2,6 +2,9 @@ syntax = "proto3"; package envoy.extensions.tracers.lightstep.v4alpha; +import "envoy/config/core/v4alpha/base.proto"; + +import "envoy/annotations/deprecation.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; @@ -39,7 +42,11 @@ message LightstepConfig { // File containing the access token to the `LightStep // `_ API. - string access_token_file = 2 [(validate.rules).string = {min_len: 1}]; + string hidden_envoy_deprecated_access_token_file = 2 + [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; + + // Access token to the `LightStep `_ API. + config.core.v4alpha.DataSource access_token = 4; // Propagation modes to use by LightStep's tracer. repeated PropagationMode propagation_modes = 3 diff --git a/source/extensions/tracers/lightstep/BUILD b/source/extensions/tracers/lightstep/BUILD index 0bfc9f44ec6f4..72f4ff80f1461 100644 --- a/source/extensions/tracers/lightstep/BUILD +++ b/source/extensions/tracers/lightstep/BUILD @@ -39,6 +39,7 @@ envoy_cc_extension( security_posture = "robust_to_untrusted_downstream", deps = [ ":lightstep_tracer_lib", + "//source/common/config:datasource_lib", "//source/extensions/tracers/common:factory_base_lib", "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", ], diff --git a/source/extensions/tracers/lightstep/config.cc b/source/extensions/tracers/lightstep/config.cc index 3a636b76dd9ab..04a5de828390d 100644 --- a/source/extensions/tracers/lightstep/config.cc +++ b/source/extensions/tracers/lightstep/config.cc @@ -5,6 +5,7 @@ #include "envoy/registry/registry.h" #include "common/common/utility.h" +#include "common/config/datasource.h" #include "common/tracing/http_tracer_impl.h" #include "extensions/tracers/lightstep/lightstep_tracer_impl.h" @@ -22,10 +23,15 @@ Tracing::HttpTracerSharedPtr LightstepTracerFactory::createHttpTracerTyped( const envoy::config::trace::v3::LightstepConfig& proto_config, Server::Configuration::TracerFactoryContext& context) { auto opts = std::make_unique(); - const auto access_token_file = context.serverFactoryContext().api().fileSystem().fileReadToEnd( - proto_config.access_token_file()); - const auto access_token_sv = StringUtil::rtrim(access_token_file); - opts->access_token.assign(access_token_sv.data(), access_token_sv.size()); + if (proto_config.has_access_token()) { + opts->access_token = Config::DataSource::read(proto_config.access_token(), true, + context.serverFactoryContext().api()); + } else { + const auto access_token_file = context.serverFactoryContext().api().fileSystem().fileReadToEnd( + proto_config.access_token_file()); + const auto access_token_sv = StringUtil::rtrim(access_token_file); + opts->access_token.assign(access_token_sv.data(), access_token_sv.size()); + } opts->component_name = context.serverFactoryContext().localInfo().clusterName(); Tracing::DriverPtr lightstep_driver = std::make_unique( diff --git a/test/extensions/tracers/lightstep/config_test.cc b/test/extensions/tracers/lightstep/config_test.cc index 83167e1de4335..60725e610fd26 100644 --- a/test/extensions/tracers/lightstep/config_test.cc +++ b/test/extensions/tracers/lightstep/config_test.cc @@ -20,7 +20,7 @@ namespace Tracers { namespace Lightstep { namespace { -TEST(LightstepTracerConfigTest, LightstepHttpTracer) { +TEST(LightstepTracerConfigTest, DEPRECATED_FEATURE_TEST(LightstepHttpTracer)) { NiceMock context; context.server_factory_context_.cluster_manager_.initializeClusters({"fake_cluster"}, {}); ON_CALL(*context.server_factory_context_.cluster_manager_.active_clusters_["fake_cluster"]->info_, @@ -45,6 +45,32 @@ TEST(LightstepTracerConfigTest, LightstepHttpTracer) { EXPECT_NE(nullptr, lightstep_tracer); } +TEST(LightstepTracerConfigTest, LightstepHttpTracerAccessToken) { + NiceMock context; + context.server_factory_context_.cluster_manager_.initializeClusters({"fake_cluster"}, {}); + ON_CALL(*context.server_factory_context_.cluster_manager_.active_clusters_["fake_cluster"]->info_, + features()) + .WillByDefault(Return(Upstream::ClusterInfo::Features::HTTP2)); + + const std::string yaml_string = R"EOF( + http: + name: lightstep + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.LightstepConfig + collector_cluster: fake_cluster + access_token: + inline_string: fake_token + )EOF"; + envoy::config::trace::v3::Tracing configuration; + TestUtility::loadFromYaml(yaml_string, configuration); + + LightstepTracerFactory factory; + auto message = Config::Utility::translateToFactoryConfig( + configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); + Tracing::HttpTracerSharedPtr lightstep_tracer = factory.createHttpTracer(*message, context); + EXPECT_NE(nullptr, lightstep_tracer); +} + // Test that the deprecated extension name still functions. TEST(LightstepTracerConfigTest, DEPRECATED_FEATURE_TEST(DeprecatedExtensionFilterName)) { const std::string deprecated_name = "envoy.lightstep"; From 73ade9addeeac0f052f0b3e8fb29d76975b89aa9 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Fri, 7 May 2021 08:28:08 -0400 Subject: [PATCH 171/209] docs: unhiding HTTP/3 and adding docs (#15926) Unhiding HTTP/3 upstream and downstream configuration, linking to example configs, and updating docs for HTTP/3 alpha. Risk Level: n/a Testing: n/a Docs Changes: yes Release Notes: inline #14829 #2557 #15845 Fixes #12923 Co-Authored-By: Michael Payne michael@sooper.org Signed-off-by: Alyssa Wilk --- api/envoy/config/core/v3/protocol.proto | 2 - api/envoy/config/core/v4alpha/protocol.proto | 2 - .../listener/v3/udp_listener_config.proto | 6 ++- .../v4alpha/udp_listener_config.proto | 6 ++- .../http/v3/http_protocol_options.proto | 24 ++++++--- .../http/v4alpha/http_protocol_options.proto | 24 ++++++--- ...envoyproxy_io_proxy_http3_downstream.yaml} | 53 ++++++++++++++++-- .../configuration/best_practices/edge.rst | 2 +- .../best_practices/level_two.rst | 12 ++--- .../http/http_conn_man/stats.rst | 7 ++- .../http/http_filters/aws_lambda_filter.rst | 2 +- .../http_filters/grpc_http1_bridge_filter.rst | 2 +- .../grpc_http1_reverse_bridge_filter.rst | 2 +- .../http/http_filters/lua_filter.rst | 2 +- .../observability/access_log/stats.rst | 2 +- .../observability/access_log/usage.rst | 2 +- .../cluster_manager/cluster_runtime.rst | 7 ++- .../cluster_manager/cluster_stats.rst | 5 +- docs/root/faq/configuration/flow_control.rst | 8 +-- docs/root/faq/configuration/timeouts.rst | 2 +- .../faq/load_balancing/concurrency_lb.rst | 2 +- docs/root/intro/arch_overview/http/http.rst | 1 + docs/root/intro/arch_overview/http/http3.rst | 52 ++++++++++++++++++ .../http/http_connection_management.rst | 4 +- .../intro/arch_overview/http/upgrades.rst | 24 ++++----- .../arch_overview/other_protocols/grpc.rst | 10 ++-- .../upstream/circuit_breaking.rst | 2 +- .../upstream/cluster_manager.rst | 4 +- .../upstream/connection_pooling.rst | 54 ++++++++++++++++--- .../intro/deployment_types/double_proxy.rst | 2 +- .../intro/deployment_types/front_proxy.rst | 2 +- .../deployment_types/service_to_service.rst | 4 +- docs/root/intro/what_is_envoy.rst | 7 ++- docs/root/start/sandboxes/grpc_bridge.rst | 3 +- docs/root/version_history/current.rst | 1 + .../envoy/config/core/v3/protocol.proto | 2 - .../envoy/config/core/v4alpha/protocol.proto | 2 - .../listener/v3/udp_listener_config.proto | 6 ++- .../v4alpha/udp_listener_config.proto | 6 ++- .../http/v3/http_protocol_options.proto | 24 ++++++--- .../http/v4alpha/http_protocol_options.proto | 24 ++++++--- tools/spelling/spelling_dictionary.txt | 1 + 42 files changed, 301 insertions(+), 108 deletions(-) rename configs/{envoyproxy_io_proxy_http3_downstream.template.yaml => envoyproxy_io_proxy_http3_downstream.yaml} (56%) create mode 100644 docs/root/intro/arch_overview/http/http3.rst diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index 65bd64d863a2b..2c0ee96803dcf 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -438,8 +438,6 @@ message GrpcProtocolOptions { Http2ProtocolOptions http2_protocol_options = 1; } -// [#not-implemented-hide:] -// // A message which allows using HTTP/3. message Http3ProtocolOptions { QuicProtocolOptions quic_protocol_options = 1; diff --git a/api/envoy/config/core/v4alpha/protocol.proto b/api/envoy/config/core/v4alpha/protocol.proto index 59c61510b4bc7..965596136e067 100644 --- a/api/envoy/config/core/v4alpha/protocol.proto +++ b/api/envoy/config/core/v4alpha/protocol.proto @@ -432,8 +432,6 @@ message GrpcProtocolOptions { Http2ProtocolOptions http2_protocol_options = 1; } -// [#not-implemented-hide:] -// // A message which allows using HTTP/3. message Http3ProtocolOptions { option (udpa.annotations.versioning).previous_message_type = diff --git a/api/envoy/config/listener/v3/udp_listener_config.proto b/api/envoy/config/listener/v3/udp_listener_config.proto index 614f7e9d323ea..57088ac5fe1f1 100644 --- a/api/envoy/config/listener/v3/udp_listener_config.proto +++ b/api/envoy/config/listener/v3/udp_listener_config.proto @@ -33,8 +33,10 @@ message UdpListenerConfig { // Configuration for QUIC protocol. If empty, QUIC will not be enabled on this listener. Set // to the default object to enable QUIC without modifying any additional options. - // [#not-implemented-hide:] - // [#comment:Unhide when QUIC alpha is announced with other docs.] + // + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. QuicProtocolOptions quic_options = 7; } diff --git a/api/envoy/config/listener/v4alpha/udp_listener_config.proto b/api/envoy/config/listener/v4alpha/udp_listener_config.proto index fc99b86dd4207..3cd272de3172e 100644 --- a/api/envoy/config/listener/v4alpha/udp_listener_config.proto +++ b/api/envoy/config/listener/v4alpha/udp_listener_config.proto @@ -33,8 +33,10 @@ message UdpListenerConfig { // Configuration for QUIC protocol. If empty, QUIC will not be enabled on this listener. Set // to the default object to enable QUIC without modifying any additional options. - // [#not-implemented-hide:] - // [#comment:Unhide when QUIC alpha is announced with other docs.] + // + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. QuicProtocolOptions quic_options = 7; } diff --git a/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto b/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto index 2ce22fe6c0a79..6f28a4f4b2340 100644 --- a/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto +++ b/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto @@ -59,7 +59,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#next-free-field: 6] message HttpProtocolOptions { // If this is used, the cluster will only operate on one of the possible upstream protocols. - // Note that HTTP/2 should generally be used for upstream clusters doing gRPC. + // Note that HTTP/2 or above should generally be used for upstream gRPC clusters. message ExplicitHttpConfig { oneof protocol_config { option (validate.required) = true; @@ -68,7 +68,9 @@ message HttpProtocolOptions { config.core.v3.Http2ProtocolOptions http2_protocol_options = 2; - // [#not-implemented-hide:] + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. config.core.v3.Http3ProtocolOptions http3_protocol_options = 3; } } @@ -80,7 +82,9 @@ message HttpProtocolOptions { config.core.v3.Http2ProtocolOptions http2_protocol_options = 2; - // [#not-implemented-hide:] + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. config.core.v3.Http3ProtocolOptions http3_protocol_options = 3; } @@ -98,11 +102,17 @@ message HttpProtocolOptions { config.core.v3.Http2ProtocolOptions http2_protocol_options = 2; - // [#not-implemented-hide:] // Unlike HTTP/1 and HTTP/2, HTTP/3 will not be configured unless it is - // present. If HTTP/3 is present, attempts to connect will first be made - // via HTTP/3, and if the HTTP/3 connection fails, TCP (HTTP/1 or HTTP/2 - // based on ALPN) will be used instead. + // present, and (soon) only if there is an indication of server side + // support. + // See :ref:`here ` for more information on + // when HTTP/3 will be used, and when Envoy will fail over to TCP. + // + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. + // AutoHttpConfig config is undergoing especially rapid change and as it + // is alpha is not guaranteed to be API-stable. config.core.v3.Http3ProtocolOptions http3_protocol_options = 3; } diff --git a/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto b/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto index 2011abc5a5a79..8545b77c1f0b2 100644 --- a/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto +++ b/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto @@ -63,7 +63,7 @@ message HttpProtocolOptions { "envoy.extensions.upstreams.http.v3.HttpProtocolOptions"; // If this is used, the cluster will only operate on one of the possible upstream protocols. - // Note that HTTP/2 should generally be used for upstream clusters doing gRPC. + // Note that HTTP/2 or above should generally be used for upstream gRPC clusters. message ExplicitHttpConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.upstreams.http.v3.HttpProtocolOptions.ExplicitHttpConfig"; @@ -75,7 +75,9 @@ message HttpProtocolOptions { config.core.v4alpha.Http2ProtocolOptions http2_protocol_options = 2; - // [#not-implemented-hide:] + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. config.core.v4alpha.Http3ProtocolOptions http3_protocol_options = 3; } } @@ -90,7 +92,9 @@ message HttpProtocolOptions { config.core.v4alpha.Http2ProtocolOptions http2_protocol_options = 2; - // [#not-implemented-hide:] + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. config.core.v4alpha.Http3ProtocolOptions http3_protocol_options = 3; } @@ -111,11 +115,17 @@ message HttpProtocolOptions { config.core.v4alpha.Http2ProtocolOptions http2_protocol_options = 2; - // [#not-implemented-hide:] // Unlike HTTP/1 and HTTP/2, HTTP/3 will not be configured unless it is - // present. If HTTP/3 is present, attempts to connect will first be made - // via HTTP/3, and if the HTTP/3 connection fails, TCP (HTTP/1 or HTTP/2 - // based on ALPN) will be used instead. + // present, and (soon) only if there is an indication of server side + // support. + // See :ref:`here ` for more information on + // when HTTP/3 will be used, and when Envoy will fail over to TCP. + // + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. + // AutoHttpConfig config is undergoing especially rapid change and as it + // is alpha is not guaranteed to be API-stable. config.core.v4alpha.Http3ProtocolOptions http3_protocol_options = 3; } diff --git a/configs/envoyproxy_io_proxy_http3_downstream.template.yaml b/configs/envoyproxy_io_proxy_http3_downstream.yaml similarity index 56% rename from configs/envoyproxy_io_proxy_http3_downstream.template.yaml rename to configs/envoyproxy_io_proxy_http3_downstream.yaml index 2e7ace6c478a0..2037b192af08a 100644 --- a/configs/envoyproxy_io_proxy_http3_downstream.template.yaml +++ b/configs/envoyproxy_io_proxy_http3_downstream.yaml @@ -6,6 +6,50 @@ admin: address: 0.0.0.0 port_value: 9901 static_resources: + listeners: + - name: listener_tcp + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + downstream_tls_context: + common_tls_context: + tls_certificates: + - certificate_chain: + filename: certs/servercert.pem + private_key: + filename: certs/serverkey.pem + filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: HTTP2 + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: local_service + response_headers_to_add: + - header: + key: alt-svc + value: h3=":10000"; ma=86400, h3-29=":10000"; ma=86400 + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + http3_protocol_options: + http_filters: + - name: envoy.filters.http.router + listeners: - name: listener_udp address: @@ -19,18 +63,17 @@ static_resources: downstream_socket_config: prefer_gro: true filter_chains: - transport_socket: + - transport_socket: name: envoy.transport_sockets.quic typed_config: '@type': type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicDownstreamTransport downstream_tls_context: common_tls_context: - alpn_protocols: h3 tls_certificates: - certificate_chain: - filename: test/config/integration/certs/servercert.pem + - certificate_chain: + filename: certs/servercert.pem private_key: - filename: test/config/integration/certs/serverkey.pem + filename: certs/serverkey.pem filters: - name: envoy.filters.network.http_connection_manager typed_config: diff --git a/docs/root/configuration/best_practices/edge.rst b/docs/root/configuration/best_practices/edge.rst index d61c4684c71ab..fdeb376d23b44 100644 --- a/docs/root/configuration/best_practices/edge.rst +++ b/docs/root/configuration/best_practices/edge.rst @@ -20,7 +20,7 @@ HTTP proxies should additionally configure: to true (to avoid consuming HTTP headers from external clients, see :ref:`HTTP header sanitizing ` for details), * :ref:`connection and stream timeouts `, -* :ref:`HTTP/2 maximum concurrent streams limit ` to 100, +* :ref:`HTTP/2 maximum concurrent streams limit ` and :ref:`HTTP/3 maximum concurrent streams limit ` to 100 * :ref:`HTTP/2 initial stream window size limit ` to 64 KiB, * :ref:`HTTP/2 initial connection window size limit ` to 1 MiB. * :ref:`headers_with_underscores_action setting ` to REJECT_REQUEST, to protect upstream services that treat '_' and '-' as interchangeable. diff --git a/docs/root/configuration/best_practices/level_two.rst b/docs/root/configuration/best_practices/level_two.rst index 857649ce67879..18d26b0318c90 100644 --- a/docs/root/configuration/best_practices/level_two.rst +++ b/docs/root/configuration/best_practices/level_two.rst @@ -10,20 +10,20 @@ edge use case may need to be adjusted when using Envoy in a multi-level deployme .. image:: /_static/multilevel_deployment.svg **In summary, if you run level two Envoy version 1.11.1 or greater which terminates -HTTP/2, we strongly advise you to change the HttpConnectionManager configuration of your level +HTTP/2 or above, we strongly advise you to change the HttpConnectionManager configuration of your level two Envoy, by setting its downstream** :ref:`validation of HTTP messaging option ` **to true.** -If there is an invalid HTTP/2 request and this option is not set, the Envoy in +If there is an invalid request and this option is not set, the Envoy in question will reset the entire connection. This behavior was changed as part of the 1.11.1 security release, to increase the security of Edge Envoys. Unfortunately, -because there are no guarantees that edge proxies will enforce HTTP/1 or HTTP/2 -standards compliance as rigorously as Envoy’s HTTP/2 stack does, this can result +because there are no guarantees that edge proxies will enforce HTTP +standards compliance as rigorously as Envoy’s stack does, this can result in a problem as follows. If one client sends a request that for example passes level one proxy's validation checks, and it is forwarded over an upstream multiplexed -HTTP/2 connection (potentially shared with other clients) the strict enforcement on -the level two Envoy HTTP/2 will reset all the streams on that connection, causing +connection (potentially shared with other clients) the strict enforcement on +the level two Envoy will reset all the streams on that connection, causing a service disruption to the clients sharing that L1-L2 connection. If a malicious user has insight into what traffic will bypass level one checks, they could spray “bad” traffic across the level one fleet, causing serious disruption to other users’ diff --git a/docs/root/configuration/http/http_conn_man/stats.rst b/docs/root/configuration/http/http_conn_man/stats.rst index bbabd97d60e99..21e561f920707 100644 --- a/docs/root/configuration/http/http_conn_man/stats.rst +++ b/docs/root/configuration/http/http_conn_man/stats.rst @@ -15,6 +15,7 @@ statistics: downstream_cx_http1_total, Counter, Total HTTP/1.1 connections downstream_cx_upgrades_total, Counter, Total successfully upgraded connections. These are also counted as total http1/http2 connections. downstream_cx_http2_total, Counter, Total HTTP/2 connections + downstream_cx_http3_total, Counter, Total HTTP/3 connections downstream_cx_destroy, Counter, Total connections destroyed downstream_cx_destroy_remote, Counter, Total connections destroyed due to remote close downstream_cx_destroy_local, Counter, Total connections destroyed due to local close @@ -26,6 +27,7 @@ statistics: downstream_cx_http1_active, Gauge, Total active HTTP/1.1 connections downstream_cx_upgrades_active, Gauge, Total active upgraded connections. These are also counted as active http1/http2 connections. downstream_cx_http2_active, Gauge, Total active HTTP/2 connections + downstream_cx_http3_active, Gauge, Total active HTTP/3 connections downstream_cx_protocol_error, Counter, Total protocol errors downstream_cx_length_ms, Histogram, Connection length milliseconds downstream_cx_rx_bytes_total, Counter, Total bytes received @@ -41,6 +43,7 @@ statistics: downstream_rq_total, Counter, Total requests downstream_rq_http1_total, Counter, Total HTTP/1.1 requests downstream_rq_http2_total, Counter, Total HTTP/2 requests + downstream_rq_http3_total, Counter, Total HTTP/3 requests downstream_rq_active, Gauge, Total active requests downstream_rq_response_before_rq_complete, Counter, Total responses sent before the request was complete downstream_rq_rx_reset, Counter, Total request resets received @@ -150,8 +153,8 @@ On the upstream side all http2 statistics are rooted at *cluster..http2.* .. attention:: - The HTTP/2 ``streams_active`` gauge may be greater than the HTTP connection manager - ``downstream_rq_active`` gauge due to differences in stream accounting between the codec and the + The HTTP/2 `streams_active` gauge may be greater than the HTTP connection manager + `downstream_rq_active` gauge due to differences in stream accounting between the codec and the HTTP connection manager. Tracing statistics diff --git a/docs/root/configuration/http/http_filters/aws_lambda_filter.rst b/docs/root/configuration/http/http_filters/aws_lambda_filter.rst index 38813aaeb8e05..701b399d7e243 100644 --- a/docs/root/configuration/http/http_filters/aws_lambda_filter.rst +++ b/docs/root/configuration/http/http_filters/aws_lambda_filter.rst @@ -11,7 +11,7 @@ AWS Lambda The AWS Lambda filter is currently under active development. -The HTTP AWS Lambda filter is used to trigger an AWS Lambda function from a standard HTTP/1.x or HTTP/2 request. +The HTTP AWS Lambda filter is used to trigger an AWS Lambda function from a standard HTTP request. It supports a few options to control whether to pass through the HTTP request payload as is or to wrap it in a JSON schema. diff --git a/docs/root/configuration/http/http_filters/grpc_http1_bridge_filter.rst b/docs/root/configuration/http/http_filters/grpc_http1_bridge_filter.rst index eac2f887a5cf0..13aa5388ebd71 100644 --- a/docs/root/configuration/http/http_filters/grpc_http1_bridge_filter.rst +++ b/docs/root/configuration/http/http_filters/grpc_http1_bridge_filter.rst @@ -32,7 +32,7 @@ response trailers to a compliant gRPC server. It works by doing the following: work with unary gRPC APIs. This filter also collects stats for all gRPC requests that transit, even if those requests are -normal gRPC requests over HTTP/2. +normal gRPC requests over HTTP/2 or above. More info: wire format in `gRPC over HTTP/2 `_. diff --git a/docs/root/configuration/http/http_filters/grpc_http1_reverse_bridge_filter.rst b/docs/root/configuration/http/http_filters/grpc_http1_reverse_bridge_filter.rst index d8d918c76c3b5..96e1c4979042a 100644 --- a/docs/root/configuration/http/http_filters/grpc_http1_reverse_bridge_filter.rst +++ b/docs/root/configuration/http/http_filters/grpc_http1_reverse_bridge_filter.rst @@ -8,7 +8,7 @@ gRPC HTTP/1.1 reverse bridge * This filter should be configured with the name *envoy.filters.http.grpc_http1_reverse_bridge*. This is a filter that enables converting an incoming gRPC request into a HTTP/1.1 request to allow -a server that does not understand HTTP/2 or gRPC semantics to handle the request. +a server that does not understand HTTP/2 or HTTP/3 or gRPC semantics to handle the request. The filter works by: diff --git a/docs/root/configuration/http/http_filters/lua_filter.rst b/docs/root/configuration/http/http_filters/lua_filter.rst index 32180e901451c..05cd149fc6ba8 100644 --- a/docs/root/configuration/http/http_filters/lua_filter.rst +++ b/docs/root/configuration/http/http_filters/lua_filter.rst @@ -659,7 +659,7 @@ protocol() streamInfo:protocol() Returns the string representation of :repo:`HTTP protocol ` -used by the current request. The possible values are: *HTTP/1.0*, *HTTP/1.1*, and *HTTP/2*. +used by the current request. The possible values are: ``HTTP/1.0``, ``HTTP/1.1``, ``HTTP/2`` and ``HTTP/3*``. downstreamLocalAddress() ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/root/configuration/observability/access_log/stats.rst b/docs/root/configuration/observability/access_log/stats.rst index 9ea5d26ccec0b..632ebaf482831 100644 --- a/docs/root/configuration/observability/access_log/stats.rst +++ b/docs/root/configuration/observability/access_log/stats.rst @@ -15,7 +15,7 @@ The gRPC access log has statistics rooted at *access_logs.grpc_access_log.* with :widths: 1, 1, 2 logs_written, Counter, Total log entries sent to the logger which were not dropped. This does not imply the logs have been flushed to the gRPC endpoint yet. - logs_dropped, Counter, Total log entries dropped due to network or HTTP/2 back up. + logs_dropped, Counter, Total log entries dropped due to network or application level back up. File access log statistics diff --git a/docs/root/configuration/observability/access_log/usage.rst b/docs/root/configuration/observability/access_log/usage.rst index d7409a7b1a57d..4c4029c245294 100644 --- a/docs/root/configuration/observability/access_log/usage.rst +++ b/docs/root/configuration/observability/access_log/usage.rst @@ -181,7 +181,7 @@ The following command operators are supported: %PROTOCOL% HTTP - Protocol. Currently either *HTTP/1.1* or *HTTP/2*. + Protocol. Currently either *HTTP/1.1* *HTTP/2* or *HTTP/3*. TCP Not implemented ("-"). diff --git a/docs/root/configuration/upstream/cluster_manager/cluster_runtime.rst b/docs/root/configuration/upstream/cluster_manager/cluster_runtime.rst index 9f6c8808d17e5..1efe95bc7936d 100644 --- a/docs/root/configuration/upstream/cluster_manager/cluster_runtime.rst +++ b/docs/root/configuration/upstream/cluster_manager/cluster_runtime.rst @@ -140,9 +140,14 @@ upstream.healthy_panic_threshold Defaults to 50%. upstream.use_http2 - Whether the cluster utilizes the *http2* if configured in `HttpProtocolOptions `. + Whether the cluster uses ``HTTP/2`` if configured in `HttpProtocolOptions `. Set to 0 to disable HTTP/2 even if the feature is configured. Defaults to enabled. +upstream.use_http3 + Whether the cluster uses ``HTTP/3`` if configured in `HttpProtocolOptions `. + Set to 0 to disable HTTP/3 even if the feature is configured. Defaults to enabled. + + .. _config_cluster_manager_cluster_runtime_zone_routing: Zone aware load balancing diff --git a/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst b/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst index dec6e20fb7d73..055240a77172b 100644 --- a/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst +++ b/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst @@ -38,6 +38,7 @@ Every cluster has a statistics tree rooted at *cluster..* with the followi upstream_cx_active, Gauge, Total active connections upstream_cx_http1_total, Counter, Total HTTP/1.1 connections upstream_cx_http2_total, Counter, Total HTTP/2 connections + upstream_cx_http3_total, Counter, Total HTTP/3 connections upstream_cx_connect_fail, Counter, Total connection failures upstream_cx_connect_timeout, Counter, Total connection connect timeouts upstream_cx_idle_timeout, Counter, Total connection idle timeouts @@ -51,7 +52,7 @@ Every cluster has a statistics tree rooted at *cluster..* with the followi upstream_cx_destroy_with_active_rq, Counter, Total connections destroyed with 1+ active request upstream_cx_destroy_local_with_active_rq, Counter, Total connections destroyed locally with 1+ active request upstream_cx_destroy_remote_with_active_rq, Counter, Total connections destroyed remotely with 1+ active request - upstream_cx_close_notify, Counter, Total connections closed via HTTP/1.1 connection close header or HTTP/2 GOAWAY + upstream_cx_close_notify, Counter, Total connections closed via HTTP/1.1 connection close header or HTTP/2 or HTTP/3 GOAWAY upstream_cx_rx_bytes_total, Counter, Total received connection bytes upstream_cx_rx_bytes_buffered, Gauge, Received connection bytes currently buffered upstream_cx_tx_bytes_total, Counter, Total sent connection bytes @@ -63,7 +64,7 @@ Every cluster has a statistics tree rooted at *cluster..* with the followi upstream_rq_total, Counter, Total requests upstream_rq_active, Gauge, Total active requests upstream_rq_pending_total, Counter, Total requests pending a connection pool connection - upstream_rq_pending_overflow, Counter, Total requests that overflowed connection pool or requests (mainly for HTTP/2) circuit breaking and were failed + upstream_rq_pending_overflow, Counter, Total requests that overflowed connection pool or requests (mainly for HTTP/2 and above) circuit breaking and were failed upstream_rq_pending_failure_eject, Counter, Total requests that were failed due to a connection pool connection failure or remote connection termination upstream_rq_pending_active, Gauge, Total active requests pending a connection pool connection upstream_rq_cancelled, Counter, Total requests cancelled before obtaining a connection pool connection diff --git a/docs/root/faq/configuration/flow_control.rst b/docs/root/faq/configuration/flow_control.rst index 9bbce146a95ec..6bc3ef3e01336 100644 --- a/docs/root/faq/configuration/flow_control.rst +++ b/docs/root/faq/configuration/flow_control.rst @@ -22,12 +22,12 @@ and downstream. The listener limits are also propogated to the HttpConnectionManager, and applied on a per-stream basis to HTTP/1.1 L7 buffers described below. As such they limit the size of HTTP/1 requests and -response bodies that can be buffered. For HTTP/2, as many streams can be multiplexed over one TCP +response bodies that can be buffered. For HTTP/2 and HTTP/3, as many streams can be multiplexed over one connection, the L7 and L4 buffer limits can be tuned separately, and the configuration option :ref:`http2 stream limits ` -is applied to all of the L7 buffers. Note that for both HTTP/1 and -HTTP/2 Envoy can and will proxy arbitrarily large bodies on routes where all L7 filters are -streaming, but many filters such as the transcoder or buffer filters require the full HTTP body to +is applied to all of the L7 buffers. Note that for all version of HTTP Envoy can and will proxy +arbitrarily large bodies on routes where all L7 filters are streaming, but many filters such as the transcoder +or buffer filters require the full HTTP body to be buffered, so limit the request and response size based on the listener limit. The cluster limits affect how much raw data will be read per read() call from upstream, as diff --git a/docs/root/faq/configuration/timeouts.rst b/docs/root/faq/configuration/timeouts.rst index 7ca390c45d06b..fd530474ea344 100644 --- a/docs/root/faq/configuration/timeouts.rst +++ b/docs/root/faq/configuration/timeouts.rst @@ -60,7 +60,7 @@ context request/stream is interchangeable. is the amount of time that the connection manager will allow a stream to exist with no upstream or downstream activity. The default stream idle timeout is *5 minutes*. This timeout is strongly recommended for all requests (not just streaming requests/responses) as it additionally defends - against an HTTP/2 peer that does not open stream window once an entire response has been buffered + against a peer that does not open the stream window once an entire response has been buffered to be sent to a downstream client. * The HTTP protocol :ref:`max_stream_duration ` is defined in a generic message used by the HTTP connection manager. The max stream duration is the diff --git a/docs/root/faq/load_balancing/concurrency_lb.rst b/docs/root/faq/load_balancing/concurrency_lb.rst index 0c247da7a17af..682d9b1ef69ad 100644 --- a/docs/root/faq/load_balancing/concurrency_lb.rst +++ b/docs/root/faq/load_balancing/concurrency_lb.rst @@ -7,5 +7,5 @@ load balancing policies such as :ref:`round robin ` are not shared between workers. diff --git a/docs/root/intro/arch_overview/http/http.rst b/docs/root/intro/arch_overview/http/http.rst index 33b7ebffa6fa2..0877cf3e62dbd 100644 --- a/docs/root/intro/arch_overview/http/http.rst +++ b/docs/root/intro/arch_overview/http/http.rst @@ -7,5 +7,6 @@ HTTP http_connection_management http_filters http_routing + http3 upgrades http_proxy diff --git a/docs/root/intro/arch_overview/http/http3.rst b/docs/root/intro/arch_overview/http/http3.rst new file mode 100644 index 0000000000000..8f10cc4b4b8c5 --- /dev/null +++ b/docs/root/intro/arch_overview/http/http3.rst @@ -0,0 +1,52 @@ +.. _arch_overview_http3: + +HTTP3 overview +============== + +.. warning:: + + HTTP/3 support is still in Alpha, and should be used with caution. + Outstanding issues required for HTTP/3 to go GA can be found + `here `_ + For example QUIC does not currently support in-place filter chain updates, so users + requiring dynamic config reload for QUIC should wait until + `#13115 `_ has been addressed. + + For general feature requests beyond production readiness, you can track + the `area-quic `_ tag. + +HTTP3 downstream +---------------- + +Downstream Envoy HTTP/3 support can be turned up via adding +:ref:`quic_options ` and +ensuring the downstream transport socket is a QuicDownstreamTransport. + +See example :repo:`downstream HTTP/3 configuration ` for example configuration. + +Note that the example configuration includes both a TCP and a UDP listener, and the TCP +listener is advertising http/3 support via an ``alt-svc header``. Advertising HTTP/3 is not necessary for +in-house deployments where HTTP/3 is explicitly configured, but is needed for internet facing deployments +where TCP is the default, and clients such as Chrome will only attempt HTTP/3 if it is explicitly advertised. + +By default the example configuration uses kernel UDP support, but for production performance use of +BPF is strongly advised if Envoy is running with multiple worker threads. Envoy will attepmt to +use BPF on Linux by default if multiple worker threads are configured, but may require root, or at least +sudo-with-permissions (e.g. sudo setcap cap_bpf+ep). If multiple worker threads are configured, Envoy will +log a warning on start-up if BPF is unsupported on the platform, or is attempted and fails. + +HTTP3 upstream +-------------- + +HTTP/3 upstream support is still in Alpha, and should be used with caution. +Outstanding issues required for HTTP/3 to go GA can be found +`here `_ + +Envoy HTTP/3 support can be turned up by turning up HTTP/3 support in +:ref:`http_protocol_options `, +Either configuring HTTP/3 explicitly on, or using the auto_http option to use HTTP/3 if it is supported. + +See :ref:`here ` for more information about HTTP/3 connection pooling, including +detailed information of where QUIC will be used, and how it fails over to TCP when QUIC use is configured to be optional. + +An example upstream HTTP/3 configuration file can be found :repo:`here . diff --git a/docs/root/intro/arch_overview/http/http_connection_management.rst b/docs/root/intro/arch_overview/http/http_connection_management.rst index 575da12520c8e..f25d721cc52ec 100644 --- a/docs/root/intro/arch_overview/http/http_connection_management.rst +++ b/docs/root/intro/arch_overview/http/http_connection_management.rst @@ -19,14 +19,14 @@ HTTP connection manager :ref:`configuration `. HTTP protocols -------------- -Envoy’s HTTP connection manager has native support for HTTP/1.1, WebSockets, and HTTP/2. It does not support +Envoy’s HTTP connection manager has native support for HTTP/1.1, WebSockets, HTTP/2 and HTTP/3. It does not support SPDY. Envoy’s HTTP support was designed to first and foremost be an HTTP/2 multiplexing proxy. Internally, HTTP/2 terminology is used to describe system components. For example, an HTTP request and response take place on a *stream*. A codec API is used to translate from different wire protocols into a protocol agnostic form for streams, requests, responses, etc. In the case of HTTP/1.1, the codec translates the serial/pipelining capabilities of the protocol into something that looks like HTTP/2 to higher layers. This means that the majority of the code does not need to -understand whether a stream originated on an HTTP/1.1 or HTTP/2 connection. +understand whether a stream originated on an HTTP/1.1, HTTP/2, or HTTP/3 connection. HTTP header sanitizing ---------------------- diff --git a/docs/root/intro/arch_overview/http/upgrades.rst b/docs/root/intro/arch_overview/http/upgrades.rst index d9d0765eaddd9..c2f8278c8417f 100644 --- a/docs/root/intro/arch_overview/http/upgrades.rst +++ b/docs/root/intro/arch_overview/http/upgrades.rst @@ -40,30 +40,30 @@ Note that the statistics for upgrades are all bundled together so WebSocket and :ref:`statistics ` are tracked by stats such as downstream_cx_upgrades_total and downstream_cx_upgrades_active -Websocket over HTTP/2 hops -^^^^^^^^^^^^^^^^^^^^^^^^^^ +Websocket over HTTP/2 or HTTP/3 hops +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -While HTTP/2 support for WebSockets is off by default, Envoy does support tunneling WebSockets over -HTTP/2 streams for deployments that prefer a uniform HTTP/2 mesh throughout; this enables, for example, +While HTTP/2 and HTTP/3 support for WebSockets is off by default, Envoy does support tunneling WebSockets over +HTTP/2 and above for deployments that prefer a uniform HTTP/2+ mesh throughout; this enables, for example, a deployment of the form: [Client] ---- HTTP/1.1 ---- [Front Envoy] ---- HTTP/2 ---- [Sidecar Envoy ---- H1 ---- App] In this case, if a client is for example using WebSocket, we want the Websocket to arrive at the -upstream server functionally intact, which means it needs to traverse the HTTP/2 hop. +upstream server functionally intact, which means it needs to traverse the HTTP/2+ hop. This is accomplished via `Extended CONNECT (RFC8441) `_ support, turned on by setting :ref:`allow_connect ` true at the second layer Envoy. The -WebSocket request will be transformed into an HTTP/2 CONNECT stream, with :protocol header -indicating the original upgrade, traverse the HTTP/2 hop, and be downgraded back into an HTTP/1 +WebSocket request will be transformed into an HTTP/2+ CONNECT stream, with :protocol header +indicating the original upgrade, traverse the HTTP/2+ hop, and be downgraded back into an HTTP/1 WebSocket Upgrade. This same Upgrade-CONNECT-Upgrade transformation will be performed on any -HTTP/2 hop, with the documented flaw that the HTTP/1.1 method is always assumed to be GET. +HTTP/2+ hop, with the documented flaw that the HTTP/1.1 method is always assumed to be GET. Non-WebSocket upgrades are allowed to use any valid HTTP method (i.e. POST) and the current upgrade/downgrade mechanism will drop the original method and transform the Upgrade request to a GET method on the final Envoy-Upstream hop. -Note that the HTTP/2 upgrade path has very strict HTTP/1.1 compliance, so will not proxy WebSocket +Note that the HTTP/2+ upgrade path has very strict HTTP/1.1 compliance, so will not proxy WebSocket upgrade requests or responses with bodies. CONNECT support @@ -73,7 +73,7 @@ Envoy CONNECT support is off by default (Envoy will send an internally generated CONNECT requests). CONNECT support can be enabled via the upgrade options described above, setting the upgrade value to the special keyword "CONNECT". -While for HTTP/2, CONNECT request may have a path, in general and for HTTP/1.1 CONNECT requests do +While for HTTP/2 and above, CONNECT request may have a path, in general and for HTTP/1.1 CONNECT requests do not have a path, and can only be matched using a :ref:`connect_matcher `. Please also note that when doing non-wildcard domain matching for CONNECT requests, the CONNECT target is matched @@ -110,7 +110,7 @@ Tunneling TCP over HTTP Envoy also has support for tunneling raw TCP over HTTP CONNECT or HTTP POST requests. Find below some usage scenarios. -HTTP/2 CONNECT can be used to proxy multiplexed TCP over pre-warmed secure connections and amortize +HTTP/2+ CONNECT can be used to proxy multiplexed TCP over pre-warmed secure connections and amortize the cost of any TLS handshake. An example set up proxying SMTP would look something like this: @@ -124,7 +124,7 @@ supporting HTTP/2): Note that when using HTTP/1 CONNECT you will end up having a TCP connection between L1 and L2 Envoy for each TCP client connection, it is preferable to use -HTTP/2 when you have the choice. +HTTP/2 or above when you have the choice. HTTP POST can also be used to proxy multiplexed TCP when intermediate proxies that don't support CONNECT. An example set up proxying HTTP would look something like this: diff --git a/docs/root/intro/arch_overview/other_protocols/grpc.rst b/docs/root/intro/arch_overview/other_protocols/grpc.rst index 01275183b1553..201af54d21c93 100644 --- a/docs/root/intro/arch_overview/other_protocols/grpc.rst +++ b/docs/root/intro/arch_overview/other_protocols/grpc.rst @@ -4,12 +4,12 @@ gRPC ==== `gRPC `_ is an RPC framework from Google. It uses protocol buffers as the -underlying serialization/IDL format. At the transport layer it uses HTTP/2 for request/response +underlying serialization/IDL format. At the transport layer it uses HTTP/2 or above for request/response multiplexing. Envoy has first class support for gRPC both at the transport layer as well as at the application layer: -* gRPC makes use of HTTP/2 trailers to convey request status. Envoy is one of very few HTTP proxies - that correctly supports HTTP/2 trailers and is thus one of the few proxies that can transport +* gRPC makes use of trailers to convey request status. Envoy is one of very few HTTP proxies + that correctly supports trailers and is thus one of the few proxies that can transport gRPC requests and responses. * The gRPC runtime for some languages is relatively immature. See :ref:`below ` for an overview of filters that can help bring gRPC to more languages. @@ -29,7 +29,7 @@ gRPC bridging Envoy supports two gRPC bridges: * :ref:`grpc_http1_bridge filter ` which allows gRPC requests to be sent to Envoy over - HTTP/1.1. Envoy then translates the requests to HTTP/2 for transport to the target server. The response is translated back to HTTP/1.1. + HTTP/1.1. Envoy then translates the requests to HTTP/2 or HTTP/3 for transport to the target server. The response is translated back to HTTP/1.1. When installed, the bridge filter gathers per RPC statistics in addition to the standard array of global HTTP statistics. * :ref:`grpc_http1_reverse_bridge filter ` which allows gRPC requests to be sent to Envoy and then translated to HTTP/1.1 when sent to the upstream. The response is then converted back into gRPC when sent to the downstream. @@ -52,7 +52,7 @@ When specifying gRPC services, it's necessary to specify the use of either the discuss the tradeoffs in this choice below. The Envoy gRPC client is a minimal custom implementation of gRPC that makes use -of Envoy's HTTP/2 upstream connection management. Services are specified as +of Envoy's HTTP/2 or HTTP/3 upstream connection management. Services are specified as regular Envoy :ref:`clusters `, with regular treatment of :ref:`timeouts, retries `, endpoint :ref:`discovery `/:ref:`load diff --git a/docs/root/intro/arch_overview/upstream/circuit_breaking.rst b/docs/root/intro/arch_overview/upstream/circuit_breaking.rst index 56dcd13bcd1c1..9096c5641ff82 100644 --- a/docs/root/intro/arch_overview/upstream/circuit_breaking.rst +++ b/docs/root/intro/arch_overview/upstream/circuit_breaking.rst @@ -31,7 +31,7 @@ configure and code each application independently. Envoy supports various types configured, all requests will be multiplexed over the same connection so this circuit breaker will only be hit when no connection is already established. If this circuit breaker overflows the :ref:`upstream_rq_pending_overflow ` counter for the cluster will - increment. + increment. For HTTP/3 the equivalent to HTTP/2's :ref:`max concurrent streams ` is :ref:`max concurrent streams ` * **Cluster maximum requests**: The maximum number of requests that can be outstanding to all hosts in a cluster at any given time. If this circuit breaker overflows the :ref:`upstream_rq_pending_overflow ` counter for the cluster will increment. diff --git a/docs/root/intro/arch_overview/upstream/cluster_manager.rst b/docs/root/intro/arch_overview/upstream/cluster_manager.rst index 8550d3a0655ba..fd367d4e959ac 100644 --- a/docs/root/intro/arch_overview/upstream/cluster_manager.rst +++ b/docs/root/intro/arch_overview/upstream/cluster_manager.rst @@ -11,11 +11,11 @@ Upstream clusters and hosts are abstracted from the network/HTTP filter stack gi clusters and hosts may be used for any number of different proxy tasks. The cluster manager exposes APIs to the filter stack that allow filters to obtain a L3/L4 connection to an upstream cluster, or a handle to an abstract HTTP connection pool to an upstream cluster (whether the upstream host -supports HTTP/1.1 or HTTP/2 is hidden). A filter stage determines whether it needs an L3/L4 +supports HTTP/1.1, HTTP/2, or HTTP/3 is hidden). A filter stage determines whether it needs an L3/L4 connection or a new HTTP stream and the cluster manager handles all of the complexity of knowing which hosts are available and healthy, load balancing, thread local storage of upstream connection data (since most Envoy code is written to be single threaded), upstream connection type (TCP/IP, -UDS), upstream protocol where applicable (HTTP/1.1, HTTP/2), etc. +UDS), upstream protocol where applicable (HTTP/1.1, HTTP/2, HTTP/3), etc. Clusters known to the cluster manager can be configured either statically, or fetched dynamically via the cluster discovery service (CDS) API. Dynamic cluster fetches allow more configuration to diff --git a/docs/root/intro/arch_overview/upstream/connection_pooling.rst b/docs/root/intro/arch_overview/upstream/connection_pooling.rst index dc794404f6307..0c1a26d79dca9 100644 --- a/docs/root/intro/arch_overview/upstream/connection_pooling.rst +++ b/docs/root/intro/arch_overview/upstream/connection_pooling.rst @@ -4,7 +4,7 @@ Connection pooling ================== For HTTP traffic, Envoy supports abstract connection pools that are layered on top of the underlying -wire protocol (HTTP/1.1 or HTTP/2). The utilizing filter code does not need to be aware of whether +wire protocol (HTTP/1.1, HTTP/2, HTTP/3). The utilizing filter code does not need to be aware of whether the underlying protocol supports true multiplexing or not. In practice the underlying implementations have the following high level properties: @@ -32,17 +32,59 @@ pool will drain the affected connection. Once a connection reaches its :ref:`max stream limit `, it will be marked as busy until a stream is available. New connections are established anytime there is a pending request without a connection that can be dispatched to (up to circuit breaker limits for -connections). HTTP/2 is the preferred communication protocol, as connections rarely, if ever, get -severed. +connections). HTTP/2 is the preferred communication protocol when Envoy is operating as a reverse proxy, +as connections rarely, if ever, get severed. + +HTTP/3 +------ + +The HTTP/3 connection pool multiplexes multiple requests over a single connection, up to the limits +imposed by :ref:`max concurrent streams +` and :ref:`max +requests per connection `. +The HTTP/3 connection pool establishes as many connections as are needed to serve requests. With no +limits, this will be only a single connection. If a GOAWAY frame is received or if the connection +reaches the :ref:`maximum requests per connection +` limit, the connection +pool will drain the affected connection. Once a connection reaches its :ref:`maximum concurrent +stream limit `, it +will be marked as busy until a stream is available. New connections are established anytime there is +a pending request without a connection that can be dispatched to (up to circuit breaker limits for +connections). HTTP/3 upstream support is currently only usable in situations where HTTP/3 is guaranteed +to work, but automatic failover to TCP is coming soon!. + +Automatic protocol selection +---------------------------- + +For Envoy acting as a forward proxy, the preferred configuration is the +`AutoHttpConfig ` +, configued via +`http_protocol_options `. +By default it will use TCP and ALPN to select the best available protocol of HTTP/2 and HTTP/1.1. + +.. _arch_overview_http3_upstream: + +If HTTP/3 is configured in the automatic pool it will currently attempt an QUIC connection first, +then 300ms later, if a QUIC connection is not established, will also attempt to establish a TCP connection. +Whichever handshake succeeds will be used for the initial +stream, but if both TCP and QUIC connections are established, QUIC will eventually be preferred. + +Upcoming versions of HTTP/3 support will include only selecting HTTP/3 if the upstream advertises support +either via `HTTP Alternative Services `_, +`HTTPS DNS RR `_, or "QUIC hints" which +will be manually configured. This path is alpha and rapidly undergoing improvements with the goal of having +the default behavior result in optimal latency for internet environments, so please be patient and follow along with Envoy release notes +to stay aprised of the latest and greatest changes. + .. _arch_overview_conn_pool_how_many: Number of connection pools -------------------------- -Each host in each cluster will have one or more connection pools. If the cluster is HTTP/1 or HTTP/2 -only, then the host may have only a single connection pool. However, if the cluster supports multiple -upstream protocols, then at least one connection pool per protocol will be allocated. Separate +Each host in each cluster will have one or more connection pools. If the cluster has a single explicit +protocol configured, then the host may have only a single connection pool. However, if the cluster supports multiple +upstream protocols, then unless it is using ALPN, one connection pool per protocol may be allocated. Separate connection pools are also allocated for each of the following features: * :ref:`Routing priority ` diff --git a/docs/root/intro/deployment_types/double_proxy.rst b/docs/root/intro/deployment_types/double_proxy.rst index 84d882d2bb5aa..cbf6cef40f4b6 100644 --- a/docs/root/intro/deployment_types/double_proxy.rst +++ b/docs/root/intro/deployment_types/double_proxy.rst @@ -11,7 +11,7 @@ another Envoy cluster running as a *double proxy*. The idea behind the double pr more efficient to terminate TLS and client connections as close as possible to the user (shorter round trip times for the TLS handshake, faster TCP CWND expansion, less chance for packet loss, etc.). Connections that terminate in the double proxy are then multiplexed onto long lived HTTP/2 -connections running in the main data center. +or HTTP/3 connections running in the main data center. In the above diagram, the front Envoy proxy running in region 1 authenticates itself with the front Envoy proxy running in region 2 via TLS mutual authentication and pinned certificates. This allows diff --git a/docs/root/intro/deployment_types/front_proxy.rst b/docs/root/intro/deployment_types/front_proxy.rst index f335f7a05d003..a8a11d4177c96 100644 --- a/docs/root/intro/deployment_types/front_proxy.rst +++ b/docs/root/intro/deployment_types/front_proxy.rst @@ -10,7 +10,7 @@ configuration sitting behind an Envoy cluster used as an HTTP L7 edge reverse pr reverse proxy provides the following features: * Terminates TLS. -* Supports both HTTP/1.1 and HTTP/2. +* Supports HTTP/1.1, HTTP/2, and HTTP/3. * Full HTTP L7 routing support. * Talks to the service to service Envoy clusters via the standard :ref:`ingress port ` and using the discovery service for host diff --git a/docs/root/intro/deployment_types/service_to_service.rst b/docs/root/intro/deployment_types/service_to_service.rst index 73e785bda9e69..eb5dde1101d7d 100644 --- a/docs/root/intro/deployment_types/service_to_service.rst +++ b/docs/root/intro/deployment_types/service_to_service.rst @@ -14,7 +14,7 @@ Service to service egress listener ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is the port used by applications to talk to other services in the infrastructure. For example, -*http://localhost:9001*. HTTP and gRPC requests use the HTTP/1.1 *host* header or the HTTP/2 +http://localhost:9001. HTTP and gRPC requests use the HTTP/1.1 *host* header or the HTTP/2 or HTTP/3 *:authority* header to indicate which remote cluster the request is destined for. Envoy handles service discovery, load balancing, rate limiting, etc. depending on the details in the configuration. Services only need to know about the local Envoy and do not need to concern @@ -38,7 +38,7 @@ performs buffering, circuit breaking, etc. as needed. Our default configurations use HTTP/2 for all Envoy to Envoy communication, regardless of whether the application uses HTTP/1.1 or HTTP/2 when egressing out of a local Envoy. HTTP/2 provides -better performance via long lived connections and explicit reset notifications. +better performance than HTTP/1.1 via long lived connections and explicit reset notifications. .. image:: /_static/service_to_service_ingress_listener.svg :width: 55% diff --git a/docs/root/intro/what_is_envoy.rst b/docs/root/intro/what_is_envoy.rst index 9f89575877d6c..84acb61ac0878 100644 --- a/docs/root/intro/what_is_envoy.rst +++ b/docs/root/intro/what_is_envoy.rst @@ -46,13 +46,16 @@ clients and target servers can be bridged. The recommended service to service co HTTP/2 between all Envoys to create a mesh of persistent connections that requests and responses can be multiplexed over. +**HTTP/3 support (currently in alpha):** As of 1.19.0, Envoy now supports HTTP/3 upstream and downstream, +and translating between any combination of HTTP/1.1, HTTP/2 and HTTP/3 in either direction. + **HTTP L7 routing:** When operating in HTTP mode, Envoy supports a :ref:`routing ` subsystem that is capable of routing and redirecting requests based on path, authority, content type, :ref:`runtime ` values, etc. This functionality is most useful when using Envoy as a front/edge proxy but is also leveraged when building a service to service mesh. -**gRPC support:** `gRPC `_ is an RPC framework from Google that uses HTTP/2 +**gRPC support:** `gRPC `_ is an RPC framework from Google that uses HTTP/2 or above as the underlying multiplexed transport. Envoy :ref:`supports ` all of the HTTP/2 features required to be used as the routing and load balancing substrate for gRPC requests and responses. The two systems are very complementary. @@ -88,7 +91,7 @@ racing. **Front/edge proxy support:** There is substantial benefit in using the same software at the edge (observability, management, identical service discovery and load balancing algorithms, etc.). Envoy has a feature set that makes it well suited as an edge proxy for most modern web application use -cases. This includes :ref:`TLS ` termination, HTTP/1.1 and HTTP/2 :ref:`support +cases. This includes :ref:`TLS ` termination, HTTP/1.1 HTTP/2 and HTTP/3 :ref:`support `, as well as HTTP L7 :ref:`routing `. **Best in class observability:** As stated above, the primary goal of Envoy is to make the network diff --git a/docs/root/start/sandboxes/grpc_bridge.rst b/docs/root/start/sandboxes/grpc_bridge.rst index 90b98021729b3..e3ce921141c0d 100644 --- a/docs/root/start/sandboxes/grpc_bridge.rst +++ b/docs/root/start/sandboxes/grpc_bridge.rst @@ -13,7 +13,8 @@ The gRPC bridge sandbox is an example usage of Envoy's This is an example of a key-value store where an ``http``-based client CLI, written in ``Python``, updates a remote store, written in ``Go``, using the stubs generated for both languages. -The client send messages through a proxy that upgrades the HTTP requests from ``http/1.1`` to ``http/2``. +The client send messages through a proxy that upgrades the HTTP requests from ``http/1.1`` to ``http/2`` or +``http/3`` ``[client](http/1.1) -> [client-egress-proxy](http/2) -> [server-ingress-proxy](http/2) -> [server]`` diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 25d881e1d6e71..10ec44499448b 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -52,6 +52,7 @@ Removed Config or Runtime New Features ------------ +* http: added upstream and downstream alpha HTTP/3 support! See :ref:`quic_options ` for downstream and the new http3_protocol_options in :ref:`http_protocol_options ` for upstream HTTP/3. * listener: added ability to change an existing listener's address. * metric service: added support for sending metric tags as labels. This can be enabled by setting the :ref:`emit_tags_as_labels ` field to true. * udp_proxy: added :ref:`key ` as another hash policy to support hash based routing on any given key. diff --git a/generated_api_shadow/envoy/config/core/v3/protocol.proto b/generated_api_shadow/envoy/config/core/v3/protocol.proto index 65bd64d863a2b..2c0ee96803dcf 100644 --- a/generated_api_shadow/envoy/config/core/v3/protocol.proto +++ b/generated_api_shadow/envoy/config/core/v3/protocol.proto @@ -438,8 +438,6 @@ message GrpcProtocolOptions { Http2ProtocolOptions http2_protocol_options = 1; } -// [#not-implemented-hide:] -// // A message which allows using HTTP/3. message Http3ProtocolOptions { QuicProtocolOptions quic_protocol_options = 1; diff --git a/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto b/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto index badf94fc995fe..199503fce37b3 100644 --- a/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto +++ b/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto @@ -444,8 +444,6 @@ message GrpcProtocolOptions { Http2ProtocolOptions http2_protocol_options = 1; } -// [#not-implemented-hide:] -// // A message which allows using HTTP/3. message Http3ProtocolOptions { option (udpa.annotations.versioning).previous_message_type = diff --git a/generated_api_shadow/envoy/config/listener/v3/udp_listener_config.proto b/generated_api_shadow/envoy/config/listener/v3/udp_listener_config.proto index 8c89ad0032ac1..276e98153aeb5 100644 --- a/generated_api_shadow/envoy/config/listener/v3/udp_listener_config.proto +++ b/generated_api_shadow/envoy/config/listener/v3/udp_listener_config.proto @@ -34,8 +34,10 @@ message UdpListenerConfig { // Configuration for QUIC protocol. If empty, QUIC will not be enabled on this listener. Set // to the default object to enable QUIC without modifying any additional options. - // [#not-implemented-hide:] - // [#comment:Unhide when QUIC alpha is announced with other docs.] + // + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. QuicProtocolOptions quic_options = 7; oneof config_type { diff --git a/generated_api_shadow/envoy/config/listener/v4alpha/udp_listener_config.proto b/generated_api_shadow/envoy/config/listener/v4alpha/udp_listener_config.proto index fc99b86dd4207..3cd272de3172e 100644 --- a/generated_api_shadow/envoy/config/listener/v4alpha/udp_listener_config.proto +++ b/generated_api_shadow/envoy/config/listener/v4alpha/udp_listener_config.proto @@ -33,8 +33,10 @@ message UdpListenerConfig { // Configuration for QUIC protocol. If empty, QUIC will not be enabled on this listener. Set // to the default object to enable QUIC without modifying any additional options. - // [#not-implemented-hide:] - // [#comment:Unhide when QUIC alpha is announced with other docs.] + // + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. QuicProtocolOptions quic_options = 7; } diff --git a/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto b/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto index 2ce22fe6c0a79..6f28a4f4b2340 100644 --- a/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto +++ b/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto @@ -59,7 +59,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#next-free-field: 6] message HttpProtocolOptions { // If this is used, the cluster will only operate on one of the possible upstream protocols. - // Note that HTTP/2 should generally be used for upstream clusters doing gRPC. + // Note that HTTP/2 or above should generally be used for upstream gRPC clusters. message ExplicitHttpConfig { oneof protocol_config { option (validate.required) = true; @@ -68,7 +68,9 @@ message HttpProtocolOptions { config.core.v3.Http2ProtocolOptions http2_protocol_options = 2; - // [#not-implemented-hide:] + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. config.core.v3.Http3ProtocolOptions http3_protocol_options = 3; } } @@ -80,7 +82,9 @@ message HttpProtocolOptions { config.core.v3.Http2ProtocolOptions http2_protocol_options = 2; - // [#not-implemented-hide:] + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. config.core.v3.Http3ProtocolOptions http3_protocol_options = 3; } @@ -98,11 +102,17 @@ message HttpProtocolOptions { config.core.v3.Http2ProtocolOptions http2_protocol_options = 2; - // [#not-implemented-hide:] // Unlike HTTP/1 and HTTP/2, HTTP/3 will not be configured unless it is - // present. If HTTP/3 is present, attempts to connect will first be made - // via HTTP/3, and if the HTTP/3 connection fails, TCP (HTTP/1 or HTTP/2 - // based on ALPN) will be used instead. + // present, and (soon) only if there is an indication of server side + // support. + // See :ref:`here ` for more information on + // when HTTP/3 will be used, and when Envoy will fail over to TCP. + // + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. + // AutoHttpConfig config is undergoing especially rapid change and as it + // is alpha is not guaranteed to be API-stable. config.core.v3.Http3ProtocolOptions http3_protocol_options = 3; } diff --git a/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto b/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto index 2011abc5a5a79..8545b77c1f0b2 100644 --- a/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto +++ b/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto @@ -63,7 +63,7 @@ message HttpProtocolOptions { "envoy.extensions.upstreams.http.v3.HttpProtocolOptions"; // If this is used, the cluster will only operate on one of the possible upstream protocols. - // Note that HTTP/2 should generally be used for upstream clusters doing gRPC. + // Note that HTTP/2 or above should generally be used for upstream gRPC clusters. message ExplicitHttpConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.upstreams.http.v3.HttpProtocolOptions.ExplicitHttpConfig"; @@ -75,7 +75,9 @@ message HttpProtocolOptions { config.core.v4alpha.Http2ProtocolOptions http2_protocol_options = 2; - // [#not-implemented-hide:] + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. config.core.v4alpha.Http3ProtocolOptions http3_protocol_options = 3; } } @@ -90,7 +92,9 @@ message HttpProtocolOptions { config.core.v4alpha.Http2ProtocolOptions http2_protocol_options = 2; - // [#not-implemented-hide:] + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. config.core.v4alpha.Http3ProtocolOptions http3_protocol_options = 3; } @@ -111,11 +115,17 @@ message HttpProtocolOptions { config.core.v4alpha.Http2ProtocolOptions http2_protocol_options = 2; - // [#not-implemented-hide:] // Unlike HTTP/1 and HTTP/2, HTTP/3 will not be configured unless it is - // present. If HTTP/3 is present, attempts to connect will first be made - // via HTTP/3, and if the HTTP/3 connection fails, TCP (HTTP/1 or HTTP/2 - // based on ALPN) will be used instead. + // present, and (soon) only if there is an indication of server side + // support. + // See :ref:`here ` for more information on + // when HTTP/3 will be used, and when Envoy will fail over to TCP. + // + // .. warning:: + // QUIC support is currently alpha and should be used with caution. Please + // see :ref:`here ` for details. + // AutoHttpConfig config is undergoing especially rapid change and as it + // is alpha is not guaranteed to be API-stable. config.core.v4alpha.Http3ProtocolOptions http3_protocol_options = 3; } diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 878eeb1d81fe5..44481c60f7af5 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1134,6 +1134,7 @@ subtypes subzone superclass superset +svc symlink symlinked symlinks From 8d934d163fb0b91965d9ed7247266acdb5bc20a8 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Sat, 8 May 2021 00:55:44 +0900 Subject: [PATCH 172/209] wasm: implement getMonotonicTimeNanoseconds. (#16352) Supports clock_time_get in WASI with monotonic clock_id. Ref: https://github.com/proxy-wasm/proxy-wasm-cpp-host/pull/156 and https://github.com/proxy-wasm/proxy-wasm-rust-sdk/issues/103 Signed-off-by: Takeshi Yoneda --- bazel/repository_locations.bzl | 6 +++--- source/extensions/common/wasm/context.cc | 6 ++++++ source/extensions/common/wasm/context.h | 1 + test/extensions/common/wasm/test_data/test_cpp.cc | 8 ++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index d4be5d0a79f86..da0ac43116a15 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -948,8 +948,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host", - version = "579189940ee48ebf1fb1d6539483506bee89f0b4", - sha256 = "eedcc5d0e73a715d9361eda39b21b178e077ab4d749d6bf2f030de81f668d6d3", + version = "31c75e0039f2f5c42dc6e12556cb151a38da6d8b", + sha256 = "779e7a8e0fd8ed8b3133b464a8e5a9974bdedb345792d3a6148cb5a87e26976b", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], @@ -964,7 +964,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.wasm.runtime.wavm", "envoy.wasm.runtime.wasmtime", ], - release_date = "2021-04-28", + release_date = "2021-05-06", cpe = "N/A", ), proxy_wasm_rust_sdk = dict( diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 6d95ec6876232..2f42f921fdd47 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -166,6 +166,12 @@ uint64_t Context::getCurrentTimeNanoseconds() { .count(); } +uint64_t Context::getMonotonicTimeNanoseconds() { + return std::chrono::duration_cast( + wasm()->time_source_.monotonicTime().time_since_epoch()) + .count(); +} + void Context::onCloseTCP() { if (tcp_connection_closed_ || !in_vm_context_created_) { return; diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index 26b51d9987659..1a7455f938e0c 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -197,6 +197,7 @@ class Context : public proxy_wasm::ContextBase, // General WasmResult log(uint32_t level, absl::string_view message) override; uint64_t getCurrentTimeNanoseconds() override; + uint64_t getMonotonicTimeNanoseconds() override; absl::string_view getConfiguration() override; std::pair getStatus() override; diff --git a/test/extensions/common/wasm/test_data/test_cpp.cc b/test/extensions/common/wasm/test_data/test_cpp.cc index f2003072ee8c9..a344eace4943d 100644 --- a/test/extensions/common/wasm/test_data/test_cpp.cc +++ b/test/extensions/common/wasm/test_data/test_cpp.cc @@ -4,6 +4,7 @@ #endif #include +#include #include #include #include @@ -194,6 +195,8 @@ WASM_EXPORT(uint32_t, proxy_on_vm_start, (uint32_t context_id, uint32_t configur } else if (configuration == "WASI") { // These checks depend on Emscripten's support for `WASI` and will only // work if invoked on a "real" Wasm VM. + // Call to clock_time_get on monotonic clock should be available. + const std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now(); int err = fprintf(stdout, "WASI write to stdout\n"); if (err < 0) { FAIL_NOW("stdout write should succeed"); @@ -213,6 +216,11 @@ WASM_EXPORT(uint32_t, proxy_on_vm_start, (uint32_t context_id, uint32_t configur if (pathenv != nullptr) { FAIL_NOW("PATH environment variable should not be available"); } + // Check if the monotonic clock actually increases monotonically. + const std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now(); + if ((t2-t1).count() <= 0) { + FAIL_NOW("monotonic clock should be available"); + } #ifndef WIN32 // Exercise the `WASI` `fd_fdstat_get` a little bit int tty = isatty(1); From 46b646cbfa87c274700174b142be4525c46bb0d0 Mon Sep 17 00:00:00 2001 From: Jose Ulises Nino Rivera Date: Fri, 7 May 2021 13:12:03 -0500 Subject: [PATCH 173/209] apple dns: fix interface issue with localhost lookup (#16369) Commit Message: apple dns - fix interface issue with localhost lookup Additional Description: deleting ENVOY_BUG statement, as localhost dns resolution renders a valid non-zero interface index. Risk Level: low Testing: fixed previously existing test that did not have a run block. Signed-off-by: Jose Nino --- source/common/network/apple_dns_impl.cc | 2 -- test/common/network/apple_dns_impl_test.cc | 32 +--------------------- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/source/common/network/apple_dns_impl.cc b/source/common/network/apple_dns_impl.cc index 45d082ad38ea9..25a44ace0a666 100644 --- a/source/common/network/apple_dns_impl.cc +++ b/source/common/network/apple_dns_impl.cc @@ -324,8 +324,6 @@ void AppleDnsResolverImpl::PendingResolution::onDNSServiceGetAddrInfoReply( return; } - ENVOY_BUG(interface_index == 0, fmt::format("unexpected interface_index={}", interface_index)); - // Only add this address to the list if kDNSServiceFlagsAdd is set. Callback targets are only // additive. if (flags & kDNSServiceFlagsAdd) { diff --git a/test/common/network/apple_dns_impl_test.cc b/test/common/network/apple_dns_impl_test.cc index 1dc149dbf1274..66cc2921300bb 100644 --- a/test/common/network/apple_dns_impl_test.cc +++ b/test/common/network/apple_dns_impl_test.cc @@ -139,6 +139,7 @@ TEST_F(AppleDnsImplTest, DestructPending) { TEST_F(AppleDnsImplTest, LocalLookup) { EXPECT_NE(nullptr, resolveWithExpectations("localhost", DnsLookupFamily::Auto, DnsResolver::ResolutionStatus::Success, true)); + dispatcher_->run(Event::Dispatcher::RunType::Block); } TEST_F(AppleDnsImplTest, DnsIpAddressVersion) { @@ -414,37 +415,6 @@ TEST_F(AppleDnsImplFakeApiTest, QuerySynchronousCompletion) { dns_callback_executed.WaitForNotification(); } -TEST_F(AppleDnsImplFakeApiTest, IncorrectInterfaceIndexReturned) { - createResolver(); - - const std::string hostname = "foo.com"; - sockaddr_in addr4; - addr4.sin_family = AF_INET; - EXPECT_EQ(1, inet_pton(AF_INET, "1.2.3.4", &addr4.sin_addr)); - addr4.sin_port = htons(6502); - - Network::Address::Ipv4Instance address(&addr4); - - EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); - EXPECT_CALL(dns_service_, - dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, - kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, - StrEq(hostname.c_str()), _, _)) - .WillOnce(DoAll( - // Have the API call synchronously call the provided callback. Notice the incorrect - // interface_index "2". This will cause an assertion failure. - WithArgs<5, 6>(Invoke([&](DNSServiceGetAddrInfoReply callback, void* context) -> void { - EXPECT_DEATH(callback(nullptr, kDNSServiceFlagsAdd, 2, kDNSServiceErr_NoError, - hostname.c_str(), address.sockAddr(), 30, context), - "unexpected interface_index=2"); - })), - Return(kDNSServiceErr_NoError))); - - resolver_->resolve( - hostname, Network::DnsLookupFamily::Auto, - [](DnsResolver::ResolutionStatus, std::list&&) -> void { FAIL(); }); -} - TEST_F(AppleDnsImplFakeApiTest, QueryCompletedWithError) { createResolver(); From 10c17a7cd90b013c38dfbfbf715d3c24fdd0477c Mon Sep 17 00:00:00 2001 From: John Howard Date: Fri, 7 May 2021 11:25:00 -0700 Subject: [PATCH 174/209] Fix small typo in http3 docs link (#16382) Signed-off-by: John Howard --- docs/root/intro/arch_overview/http/http3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/root/intro/arch_overview/http/http3.rst b/docs/root/intro/arch_overview/http/http3.rst index 8f10cc4b4b8c5..60ef56adc9cf6 100644 --- a/docs/root/intro/arch_overview/http/http3.rst +++ b/docs/root/intro/arch_overview/http/http3.rst @@ -22,7 +22,7 @@ Downstream Envoy HTTP/3 support can be turned up via adding :ref:`quic_options ` and ensuring the downstream transport socket is a QuicDownstreamTransport. -See example :repo:`downstream HTTP/3 configuration ` for example configuration. +See example :repo:`downstream HTTP/3 configuration ` for example configuration. Note that the example configuration includes both a TCP and a UDP listener, and the TCP listener is advertising http/3 support via an ``alt-svc header``. Advertising HTTP/3 is not necessary for @@ -30,7 +30,7 @@ in-house deployments where HTTP/3 is explicitly configured, but is needed for in where TCP is the default, and clients such as Chrome will only attempt HTTP/3 if it is explicitly advertised. By default the example configuration uses kernel UDP support, but for production performance use of -BPF is strongly advised if Envoy is running with multiple worker threads. Envoy will attepmt to +BPF is strongly advised if Envoy is running with multiple worker threads. Envoy will attempt to use BPF on Linux by default if multiple worker threads are configured, but may require root, or at least sudo-with-permissions (e.g. sudo setcap cap_bpf+ep). If multiple worker threads are configured, Envoy will log a warning on start-up if BPF is unsupported on the platform, or is attempted and fails. From 3b9610658f63d2cc01f8fd9e6ad65f423b3a716d Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Fri, 7 May 2021 14:19:10 -0700 Subject: [PATCH 175/209] windows: Remove various Win32 #ifdefs (#16348) Signed-off-by: davinci26 --- source/common/filesystem/posix/filesystem_impl.h | 2 ++ source/common/filesystem/win32/filesystem_impl.h | 3 +++ source/common/quic/platform/quic_file_utils_impl.cc | 6 +----- test/common/common/logger_test.cc | 9 ++++----- test/common/event/dispatcher_impl_test.cc | 7 ------- test/common/filesystem/filesystem_impl_test.cc | 12 +++--------- test/config_test/example_configs_test.cc | 8 +------- test/test_common/file_system_for_test.cc | 8 +------- 8 files changed, 15 insertions(+), 40 deletions(-) diff --git a/source/common/filesystem/posix/filesystem_impl.h b/source/common/filesystem/posix/filesystem_impl.h index 7c4e1592b77cd..65ff10c64c1a3 100644 --- a/source/common/filesystem/posix/filesystem_impl.h +++ b/source/common/filesystem/posix/filesystem_impl.h @@ -46,5 +46,7 @@ class InstanceImplPosix : public Instance { friend class FileSystemImplTest; }; +using FileImpl = FileImplPosix; +using InstanceImpl = InstanceImplPosix; } // namespace Filesystem } // namespace Envoy diff --git a/source/common/filesystem/win32/filesystem_impl.h b/source/common/filesystem/win32/filesystem_impl.h index ace57fc3eb642..fe9b1fea0dc15 100644 --- a/source/common/filesystem/win32/filesystem_impl.h +++ b/source/common/filesystem/win32/filesystem_impl.h @@ -85,5 +85,8 @@ class InstanceImplWin32 : public Instance { bool illegalPath(const std::string& path) override; }; +using FileImpl = FileImplWin32; +using InstanceImpl = InstanceImplWin32; + } // namespace Filesystem } // namespace Envoy diff --git a/source/common/quic/platform/quic_file_utils_impl.cc b/source/common/quic/platform/quic_file_utils_impl.cc index 6140ecf825ae9..4112785357ebc 100644 --- a/source/common/quic/platform/quic_file_utils_impl.cc +++ b/source/common/quic/platform/quic_file_utils_impl.cc @@ -46,11 +46,7 @@ std::vector ReadFileContentsImpl(const std::string& dirname) { // Reads the contents of |filename| as a string into |contents|. // NOLINTNEXTLINE(readability-identifier-naming) void ReadFileContentsImpl(absl::string_view filename, std::string* contents) { -#ifdef WIN32 - Envoy::Filesystem::InstanceImplWin32 fs; -#else - Envoy::Filesystem::InstanceImplPosix fs; -#endif + Envoy::Filesystem::InstanceImpl fs; *contents = fs.fileReadToEnd(std::string(filename.data(), filename.size())); } diff --git a/test/common/common/logger_test.cc b/test/common/common/logger_test.cc index e93d9389df042..199285aa5ec4a 100644 --- a/test/common/common/logger_test.cc +++ b/test/common/common/logger_test.cc @@ -4,6 +4,8 @@ #include "common/common/json_escape_string.h" #include "common/common/logger.h" +#include "test/test_common/environment.h" + #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -110,11 +112,8 @@ class LoggerCustomFlagsTest : public testing::Test { testing::internal::CaptureStderr(); logger_->log(spdlog::details::log_msg("test", spdlog::level::info, message)); -#ifdef WIN32 - EXPECT_EQ(expected + "\r\n", testing::internal::GetCapturedStderr()); -#else - EXPECT_EQ(expected + "\n", testing::internal::GetCapturedStderr()); -#endif + EXPECT_EQ(absl::StrCat(expected, TestEnvironment::newLine), + testing::internal::GetCapturedStderr()); } protected: diff --git a/test/common/event/dispatcher_impl_test.cc b/test/common/event/dispatcher_impl_test.cc index 345305a35b63b..4a0ed4e9ef32b 100644 --- a/test/common/event/dispatcher_impl_test.cc +++ b/test/common/event/dispatcher_impl_test.cc @@ -1356,13 +1356,6 @@ class TimerImplTimingTest : public testing::Test { while (timer.enabled()) { time_system.advanceTimeAndRun(std::chrono::microseconds(1), dispatcher, Dispatcher::RunType::NonBlock); -#ifdef WIN32 - // The event loop runs for a single iteration in NonBlock mode on Windows. A few iterations - // are required to ensure that next iteration callbacks have a chance to run before time - // advances once again. - dispatcher.run(Dispatcher::RunType::NonBlock); - dispatcher.run(Dispatcher::RunType::NonBlock); -#endif } return time_system.monotonicTime() - start; } diff --git a/test/common/filesystem/filesystem_impl_test.cc b/test/common/filesystem/filesystem_impl_test.cc index 8d6cdaee1f237..bc406b6d0f18f 100644 --- a/test/common/filesystem/filesystem_impl_test.cc +++ b/test/common/filesystem/filesystem_impl_test.cc @@ -21,22 +21,16 @@ static constexpr FlagSet DefaultFlags{ class FileSystemImplTest : public testing::Test { protected: filesystem_os_id_t getFd(File* file) { -#ifdef WIN32 - auto file_impl = dynamic_cast(file); -#else - auto file_impl = dynamic_cast(file); -#endif + auto file_impl = dynamic_cast(file); RELEASE_ASSERT(file_impl != nullptr, "failed to cast File* to FileImpl*"); return file_impl->fd_; } -#ifdef WIN32 - InstanceImplWin32 file_system_; -#else +#ifndef WIN32 Api::SysCallStringResult canonicalPath(const std::string& path) { return file_system_.canonicalPath(path); } - InstanceImplPosix file_system_; #endif + InstanceImpl file_system_; }; TEST_F(FileSystemImplTest, FileExists) { diff --git a/test/config_test/example_configs_test.cc b/test/config_test/example_configs_test.cc index 939b8df53e9cc..ca52b4b768ba3 100644 --- a/test/config_test/example_configs_test.cc +++ b/test/config_test/example_configs_test.cc @@ -11,13 +11,7 @@ namespace Envoy { TEST(ExampleConfigsTest, All) { TestEnvironment::exec( {TestEnvironment::runfilesPath("test/config_test/example_configs_test_setup.sh")}); - -#ifdef WIN32 - Filesystem::InstanceImplWin32 file_system; -#else - Filesystem::InstanceImplPosix file_system; -#endif - + Filesystem::InstanceImpl file_system; const auto config_file_count = std::stoi( file_system.fileReadToEnd(TestEnvironment::temporaryDirectory() + "/config-file-count.txt")); diff --git a/test/test_common/file_system_for_test.cc b/test/test_common/file_system_for_test.cc index 96cac1553bd04..c00e82d5835b6 100644 --- a/test/test_common/file_system_for_test.cc +++ b/test/test_common/file_system_for_test.cc @@ -7,13 +7,7 @@ namespace Envoy { namespace Filesystem { MemfileInstanceImpl::MemfileInstanceImpl() -#ifdef WIN32 - : file_system_{new InstanceImplWin32()}, -#else - : file_system_{new InstanceImplPosix()}, -#endif - use_memfiles_(false) { -} + : file_system_{new InstanceImpl()}, use_memfiles_(false) {} MemfileInstanceImpl& fileSystemForTest() { static MemfileInstanceImpl* file_system = new MemfileInstanceImpl(); From 267bed448626e3b075e878ad582131f4494d787d Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Fri, 7 May 2021 14:20:45 -0700 Subject: [PATCH 176/209] logging: Remove unnecessary logging in IoSocketError (#16296) Signed-off-by: davinci26 --- include/envoy/api/io_error.h | 2 + include/envoy/common/platform.h | 2 + source/common/network/io_socket_error_impl.cc | 62 ++++++++++--------- source/common/network/io_socket_error_impl.h | 17 ++++- .../network/io_socket_handle_impl_test.cc | 13 ++-- 5 files changed, 59 insertions(+), 37 deletions(-) diff --git a/include/envoy/api/io_error.h b/include/envoy/api/io_error.h index d671dea790e1f..ae03b17dad865 100644 --- a/include/envoy/api/io_error.h +++ b/include/envoy/api/io_error.h @@ -33,6 +33,8 @@ class IoError { AddressNotAvailable, // Bad file descriptor. BadFd, + // An existing connection was forcibly closed by the remote host. + ConnectionReset, // Other error codes cannot be mapped to any one above in getErrorCode(). UnknownError }; diff --git a/include/envoy/common/platform.h b/include/envoy/common/platform.h index 3bcaa1a23f427..36ed51becf89d 100644 --- a/include/envoy/common/platform.h +++ b/include/envoy/common/platform.h @@ -149,6 +149,7 @@ struct msghdr { #define SOCKET_ERROR_INVAL WSAEINVAL #define SOCKET_ERROR_ADDR_IN_USE WSAEADDRINUSE #define SOCKET_ERROR_BADF WSAEBADF +#define SOCKET_ERROR_CONNRESET WSAECONNRESET #define HANDLE_ERROR_PERM ERROR_ACCESS_DENIED #define HANDLE_ERROR_INVALID ERROR_INVALID_HANDLE @@ -257,6 +258,7 @@ typedef int signal_t; // NOLINT(modernize-use-using) #define SOCKET_ERROR_INVAL EINVAL #define SOCKET_ERROR_ADDR_IN_USE EADDRINUSE #define SOCKET_ERROR_BADF EBADF +#define SOCKET_ERROR_CONNRESET ECONNRESET // Mapping POSIX file errors to common error names #define HANDLE_ERROR_PERM EACCES diff --git a/source/common/network/io_socket_error_impl.cc b/source/common/network/io_socket_error_impl.cc index 246e868c28b36..04a91c3d79623 100644 --- a/source/common/network/io_socket_error_impl.cc +++ b/source/common/network/io_socket_error_impl.cc @@ -8,11 +8,37 @@ namespace Envoy { namespace Network { -Api::IoError::IoErrorCode IoSocketError::getErrorCode() const { - switch (errno_) { +Api::IoError::IoErrorCode IoSocketError::getErrorCode() const { return error_code_; } + +std::string IoSocketError::getErrorDetails() const { return errorDetails(errno_); } + +IoSocketError* IoSocketError::getIoSocketInvalidAddressInstance() { + static auto* instance = + new IoSocketError(SOCKET_ERROR_NOT_SUP, Api::IoError::IoErrorCode::NoSupport); + return instance; +} + +IoSocketError* IoSocketError::getIoSocketEagainInstance() { + static auto* instance = new IoSocketError(SOCKET_ERROR_AGAIN, Api::IoError::IoErrorCode::Again); + return instance; +} + +void IoSocketError::deleteIoError(Api::IoError* err) { + ASSERT(err != nullptr); + ASSERT(err != getIoSocketInvalidAddressInstance()); + if (err != getIoSocketEagainInstance()) { + delete err; + } +} + +Api::IoCallUint64Result IoSocketError::ioResultSocketInvalidAddress() { + return Api::IoCallUint64Result( + 0, Api::IoErrorPtr(getIoSocketInvalidAddressInstance(), [](IoError*) {})); +} + +Api::IoError::IoErrorCode IoSocketError::errorCodeFromErrno(int sys_errno) { + switch (sys_errno) { case SOCKET_ERROR_AGAIN: - ASSERT(this == IoSocketError::getIoSocketEagainInstance(), - "Didn't use getIoSocketEagainInstance() to generate `Again`."); return IoErrorCode::Again; case SOCKET_ERROR_NOT_SUP: return IoErrorCode::NoSupport; @@ -30,35 +56,13 @@ Api::IoError::IoErrorCode IoSocketError::getErrorCode() const { return IoErrorCode::AddressNotAvailable; case SOCKET_ERROR_BADF: return IoErrorCode::BadFd; + case SOCKET_ERROR_CONNRESET: + return IoErrorCode::ConnectionReset; default: - ENVOY_LOG_MISC(debug, "Unknown error code {} details {}", errno_, getErrorDetails()); + ENVOY_LOG_MISC(debug, "Unknown error code {} details {}", sys_errno, errorDetails(sys_errno)); return IoErrorCode::UnknownError; } } -std::string IoSocketError::getErrorDetails() const { return errorDetails(errno_); } - -IoSocketError* IoSocketError::getIoSocketEagainInstance() { - static auto* instance = new IoSocketError(SOCKET_ERROR_AGAIN); - return instance; -} - -void IoSocketError::deleteIoError(Api::IoError* err) { - ASSERT(err != nullptr); - if (err != getIoSocketEagainInstance()) { - delete err; - } -} - -inline IoSocketError* getIoSocketInvalidAddressInstance() { - static auto* instance = new IoSocketError(SOCKET_ERROR_NOT_SUP); - return instance; -} - -Api::IoCallUint64Result IoSocketError::ioResultSocketInvalidAddress() { - return Api::IoCallUint64Result( - 0, Api::IoErrorPtr(getIoSocketInvalidAddressInstance(), [](IoError*) {})); -} - } // namespace Network } // namespace Envoy diff --git a/source/common/network/io_socket_error_impl.h b/source/common/network/io_socket_error_impl.h index 50d08b55f26a0..1b361a9a3e2f3 100644 --- a/source/common/network/io_socket_error_impl.h +++ b/source/common/network/io_socket_error_impl.h @@ -9,8 +9,11 @@ namespace Network { class IoSocketError : public Api::IoError { public: - explicit IoSocketError(int sys_errno) : errno_(sys_errno) {} - + explicit IoSocketError(int sys_errno) + : errno_(sys_errno), error_code_(errorCodeFromErrno(errno_)) { + ASSERT(error_code_ != IoErrorCode::Again, + "Didn't use getIoSocketEagainInstance() to generate `Again`."); + } ~IoSocketError() override = default; Api::IoError::IoErrorCode getErrorCode() const override; @@ -30,7 +33,15 @@ class IoSocketError : public Api::IoError { static void deleteIoError(Api::IoError* err); private: - int errno_; + explicit IoSocketError(int sys_errno, Api::IoError::IoErrorCode error_code) + : errno_(sys_errno), error_code_(error_code) {} + + static Api::IoError::IoErrorCode errorCodeFromErrno(int sys_errno); + + static IoSocketError* getIoSocketInvalidAddressInstance(); + + const int errno_; + const Api::IoError::IoErrorCode error_code_; }; } // namespace Network diff --git a/test/common/network/io_socket_handle_impl_test.cc b/test/common/network/io_socket_handle_impl_test.cc index 789e69e384966..86bdd8df5d189 100644 --- a/test/common/network/io_socket_handle_impl_test.cc +++ b/test/common/network/io_socket_handle_impl_test.cc @@ -24,8 +24,7 @@ namespace Network { namespace { TEST(IoSocketHandleImplTest, TestIoSocketError) { - IoSocketError error1(SOCKET_ERROR_AGAIN); - EXPECT_DEBUG_DEATH(error1.getErrorCode(), + EXPECT_DEBUG_DEATH(IoSocketError(SOCKET_ERROR_AGAIN), ".*assert failure: .* Details: Didn't use getIoSocketEagainInstance.*"); EXPECT_EQ(errorDetails(SOCKET_ERROR_AGAIN), IoSocketError::getIoSocketEagainInstance()->getErrorDetails()); @@ -58,10 +57,14 @@ TEST(IoSocketHandleImplTest, TestIoSocketError) { EXPECT_EQ(IoSocketError::IoErrorCode::AddressNotAvailable, error8.getErrorCode()); EXPECT_EQ(errorDetails(SOCKET_ERROR_ADDR_NOT_AVAIL), error8.getErrorDetails()); + IoSocketError error9(SOCKET_ERROR_CONNRESET); + EXPECT_EQ(IoSocketError::IoErrorCode::ConnectionReset, error9.getErrorCode()); + EXPECT_EQ(errorDetails(SOCKET_ERROR_CONNRESET), error9.getErrorDetails()); + // Random unknown error - IoSocketError error9(123); - EXPECT_EQ(IoSocketError::IoErrorCode::UnknownError, error9.getErrorCode()); - EXPECT_EQ(errorDetails(123), error9.getErrorDetails()); + IoSocketError error10(123); + EXPECT_EQ(IoSocketError::IoErrorCode::UnknownError, error10.getErrorCode()); + EXPECT_EQ(errorDetails(123), error10.getErrorDetails()); } TEST(IoSocketHandleImpl, LastRoundTripTimeReturnsEmptyOptionalIfGetSocketFails) { From 85a8570cd0530402de21c48c6688dddb187775d5 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 7 May 2021 22:46:24 +0100 Subject: [PATCH 177/209] protodoc: Add redirects for v2 protos to current version and links to old (#16303) Fix #16297 Signed-off-by: Ryan Northey --- .bazelrc | 2 +- .devcontainer/Dockerfile | 2 +- bazel/repository_locations.bzl | 6 +- docs/BUILD | 5 +- docs/build.sh | 6 + docs/v2_mapping.json | 155 ++++++++++++++++++++++ examples/wasm-cc/docker-compose-wasm.yaml | 4 +- tools/protodoc/BUILD | 5 +- tools/protodoc/protodoc.py | 43 +++++- 9 files changed, 212 insertions(+), 16 deletions(-) create mode 100644 docs/v2_mapping.json diff --git a/.bazelrc b/.bazelrc index b97516ba3f9f0..19c8eae5d70fe 100644 --- a/.bazelrc +++ b/.bazelrc @@ -247,7 +247,7 @@ build:remote-clang-cl --config=rbe-toolchain-clang-cl # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:3d0491e2034287959a292806e3891fd0b7dd2703 +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:55d9e4719d2bd0accce8f829b44dab70cd42112a build:docker-sandbox --spawn_strategy=docker build:docker-sandbox --strategy=Javac=docker build:docker-sandbox --strategy=Closure=docker diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 17524a28f2574..378a45b4f1af2 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/envoy-ci/envoy-build:3d0491e2034287959a292806e3891fd0b7dd2703 +FROM gcr.io/envoy-ci/envoy-build:55d9e4719d2bd0accce8f829b44dab70cd42112a ARG USERNAME=vscode ARG USER_UID=501 diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index da0ac43116a15..2a8cad76c73da 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -65,11 +65,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "envoy-build-tools", project_desc = "Common build tools shared by the Envoy/UDPA ecosystem", project_url = "https://github.com/envoyproxy/envoy-build-tools", - version = "50ea97bb4289441f4f87537e8c145ffb79b1c9ba", - sha256 = "3c905c71e9f2f29f4c24d9e980c95a45c73510e10b9baa6199e3fa4acfafba5e", + version = "2d4bdba38113cd9bf758c2609f40ce90014e52af", + sha256 = "8e872990609d67f9b635790020672d1972b906bed30e4d08d97f964be1ced483", strip_prefix = "envoy-build-tools-{version}", urls = ["https://github.com/envoyproxy/envoy-build-tools/archive/{version}.tar.gz"], - release_date = "2021-04-14", + release_date = "2021-05-06", use_category = ["build"], ), boringssl = dict( diff --git a/docs/BUILD b/docs/BUILD index df6aa85f655f4..0c2797f66ff16 100644 --- a/docs/BUILD +++ b/docs/BUILD @@ -5,7 +5,10 @@ load( licenses(["notice"]) # Apache 2 -exports_files(["protodoc_manifest.yaml"]) +exports_files([ + "protodoc_manifest.yaml", + "v2_mapping.json", +]) envoy_package() diff --git a/docs/build.sh b/docs/build.sh index fc3e89d117bdc..8d2d79911df1b 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -137,6 +137,9 @@ generate_api_rst v3 find "${GENERATED_RST_DIR}"/api-v3 -name "*.rst" -print0 | xargs -0 sed -i -e "s#envoy_api_#envoy_v3_api_#g" find "${GENERATED_RST_DIR}"/api-v3 -name "*.rst" -print0 | xargs -0 sed -i -e "s#config_resource_monitors#v3_config_resource_monitors#g" +# TODO(phlax): Remove this once above is removed +find "${GENERATED_RST_DIR}"/api-v3 -name "*.rst" -print0 | xargs -0 sed -i -e "s#envoy_v2_api_#envoy_api_#g" + # xDS protocol spec. mkdir -p "${GENERATED_RST_DIR}/api-docs" cp -f "${API_DIR}"/xds_protocol.rst "${GENERATED_RST_DIR}/api-docs/xds_protocol.rst" @@ -160,6 +163,9 @@ rsync -av \ "${SCRIPT_DIR}"/_ext \ "${GENERATED_RST_DIR}" +# Merge generated redirects +jq -r 'with_entries(.key |= sub("^envoy/";"api-v3/")) | with_entries(.value |= sub("^envoy/";"api-v2/")) | to_entries[] | "\(.value)\t\t\(.key)"' docs/v2_mapping.json >> "${GENERATED_RST_DIR}"/redirects.txt + # To speed up validate_fragment invocations in validating_code_block bazel build "${BAZEL_BUILD_OPTIONS[@]}" //tools/config_validation:validate_fragment diff --git a/docs/v2_mapping.json b/docs/v2_mapping.json new file mode 100644 index 0000000000000..8d437476dff6e --- /dev/null +++ b/docs/v2_mapping.json @@ -0,0 +1,155 @@ +{ + "envoy/admin/v3/certs.proto": "envoy/admin/v2alpha/certs.proto", + "envoy/admin/v3/clusters.proto": "envoy/admin/v2alpha/clusters.proto", + "envoy/admin/v3/config_dump.proto": "envoy/admin/v2alpha/config_dump.proto", + "envoy/admin/v3/listeners.proto": "envoy/admin/v2alpha/listeners.proto", + "envoy/admin/v3/memory.proto": "envoy/admin/v2alpha/memory.proto", + "envoy/admin/v3/metrics.proto": "envoy/admin/v2alpha/metrics.proto", + "envoy/admin/v3/mutex_stats.proto": "envoy/admin/v2alpha/mutex_stats.proto", + "envoy/admin/v3/server_info.proto": "envoy/admin/v2alpha/server_info.proto", + "envoy/admin/v3/tap.proto": "envoy/admin/v2alpha/tap.proto", + "envoy/extensions/transport_sockets/tls/v3/common.proto": "envoy/api/v2/auth/common.proto", + "envoy/extensions/transport_sockets/tls/v3/secret.proto": "envoy/api/v2/auth/secret.proto", + "envoy/extensions/transport_sockets/tls/v3/tls.proto": "envoy/api/v2/auth/tls.proto", + "envoy/service/cluster/v3/cds.proto": "envoy/api/v2/cds.proto", + "envoy/config/cluster/v3/cluster.proto": "envoy/api/v2/cluster.proto", + "envoy/config/cluster/v3/circuit_breaker.proto": "envoy/api/v2/cluster/circuit_breaker.proto", + "envoy/config/cluster/v3/filter.proto": "envoy/api/v2/cluster/filter.proto", + "envoy/config/cluster/v3/outlier_detection.proto": "envoy/api/v2/cluster/outlier_detection.proto", + "envoy/config/core/v3/address.proto": "envoy/api/v2/core/address.proto", + "envoy/config/core/v3/backoff.proto": "envoy/api/v2/core/backoff.proto", + "envoy/config/core/v3/base.proto": "envoy/api/v2/core/base.proto", + "envoy/config/core/v3/config_source.proto": "envoy/api/v2/core/config_source.proto", + "envoy/config/core/v3/event_service_config.proto": "envoy/api/v2/core/event_service_config.proto", + "envoy/config/core/v3/grpc_method_list.proto": "envoy/api/v2/core/grpc_method_list.proto", + "envoy/config/core/v3/grpc_service.proto": "envoy/api/v2/core/grpc_service.proto", + "envoy/config/core/v3/health_check.proto": "envoy/api/v2/core/health_check.proto", + "envoy/config/core/v3/http_uri.proto": "envoy/api/v2/core/http_uri.proto", + "envoy/config/core/v3/protocol.proto": "envoy/api/v2/core/protocol.proto", + "envoy/config/core/v3/socket_option.proto": "envoy/api/v2/core/socket_option.proto", + "envoy/service/discovery/v3/discovery.proto": "envoy/api/v2/discovery.proto", + "envoy/service/endpoint/v3/eds.proto": "envoy/api/v2/eds.proto", + "envoy/config/endpoint/v3/endpoint.proto": "envoy/api/v2/endpoint.proto", + "envoy/config/endpoint/v3/endpoint_components.proto": "envoy/api/v2/endpoint/endpoint_components.proto", + "envoy/service/listener/v3/lds.proto": "envoy/api/v2/lds.proto", + "envoy/config/listener/v3/listener.proto": "envoy/api/v2/listener.proto", + "envoy/config/listener/v3/listener_components.proto": "envoy/api/v2/listener/listener_components.proto", + "envoy/config/listener/v3/quic_config.proto": "envoy/api/v2/listener/quic_config.proto", + "envoy/config/listener/v3/udp_listener_config.proto": "envoy/api/v2/listener/udp_listener_config.proto", + "envoy/extensions/common/ratelimit/v3/ratelimit.proto": "envoy/api/v2/ratelimit/ratelimit.proto", + "envoy/service/route/v3/rds.proto": "envoy/api/v2/rds.proto", + "envoy/config/route/v3/route.proto": "envoy/api/v2/route.proto", + "envoy/config/route/v3/route_components.proto": "envoy/api/v2/route/route_components.proto", + "envoy/config/route/v3/scoped_route.proto": "envoy/api/v2/scoped_route.proto", + "envoy/service/route/v3/srds.proto": "envoy/api/v2/srds.proto", + "envoy/extensions/access_loggers/grpc/v3/als.proto": "envoy/config/accesslog/v2/als.proto", + "envoy/extensions/access_loggers/file/v3/file.proto": "envoy/config/accesslog/v2/file.proto", + "envoy/config/bootstrap/v3/bootstrap.proto": "envoy/config/bootstrap/v2/bootstrap.proto", + "envoy/extensions/clusters/aggregate/v3/cluster.proto": "envoy/config/cluster/aggregate/v2alpha/cluster.proto", + "envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto": "envoy/config/cluster/dynamic_forward_proxy/v2alpha/cluster.proto", + "envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto": "envoy/config/common/dynamic_forward_proxy/v2alpha/dns_cache.proto", + "envoy/extensions/common/tap/v3/common.proto": "envoy/config/common/tap/v2alpha/common.proto", + "envoy/config/accesslog/v3/accesslog.proto": "envoy/config/filter/accesslog/v2/accesslog.proto", + "envoy/extensions/filters/network/dubbo_proxy/router/v3/router.proto": "envoy/config/filter/dubbo/router/v2alpha1/router.proto", + "envoy/extensions/filters/common/fault/v3/fault.proto": "envoy/config/filter/fault/v2/fault.proto", + "envoy/extensions/filters/http/adaptive_concurrency/v3/adaptive_concurrency.proto": "envoy/config/filter/http/adaptive_concurrency/v2alpha/adaptive_concurrency.proto", + "envoy/extensions/filters/http/aws_lambda/v3/aws_lambda.proto": "envoy/config/filter/http/aws_lambda/v2alpha/aws_lambda.proto", + "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto": "envoy/config/filter/http/aws_request_signing/v2alpha/aws_request_signing.proto", + "envoy/extensions/filters/http/buffer/v3/buffer.proto": "envoy/config/filter/http/buffer/v2/buffer.proto", + "envoy/extensions/filters/http/cache/v3alpha/cache.proto": "envoy/config/filter/http/cache/v2alpha/cache.proto", + "envoy/extensions/filters/http/compressor/v3/compressor.proto": "envoy/config/filter/http/compressor/v2/compressor.proto", + "envoy/extensions/filters/http/cors/v3/cors.proto": "envoy/config/filter/http/cors/v2/cors.proto", + "envoy/extensions/filters/http/csrf/v3/csrf.proto": "envoy/config/filter/http/csrf/v2/csrf.proto", + "envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto": "envoy/config/filter/http/dynamic_forward_proxy/v2alpha/dynamic_forward_proxy.proto", + "envoy/extensions/filters/http/dynamo/v3/dynamo.proto": "envoy/config/filter/http/dynamo/v2/dynamo.proto", + "envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto": "envoy/config/filter/http/ext_authz/v2/ext_authz.proto", + "envoy/extensions/filters/http/fault/v3/fault.proto": "envoy/config/filter/http/fault/v2/fault.proto", + "envoy/extensions/filters/http/grpc_http1_bridge/v3/config.proto": "envoy/config/filter/http/grpc_http1_bridge/v2/config.proto", + "envoy/extensions/filters/http/grpc_http1_reverse_bridge/v3/config.proto": "envoy/config/filter/http/grpc_http1_reverse_bridge/v2alpha1/config.proto", + "envoy/extensions/filters/http/grpc_stats/v3/config.proto": "envoy/config/filter/http/grpc_stats/v2alpha/config.proto", + "envoy/extensions/filters/http/grpc_web/v3/grpc_web.proto": "envoy/config/filter/http/grpc_web/v2/grpc_web.proto", + "envoy/extensions/filters/http/gzip/v3/gzip.proto": "envoy/config/filter/http/gzip/v2/gzip.proto", + "envoy/extensions/filters/http/header_to_metadata/v3/header_to_metadata.proto": "envoy/config/filter/http/header_to_metadata/v2/header_to_metadata.proto", + "envoy/extensions/filters/http/health_check/v3/health_check.proto": "envoy/config/filter/http/health_check/v2/health_check.proto", + "envoy/extensions/filters/http/ip_tagging/v3/ip_tagging.proto": "envoy/config/filter/http/ip_tagging/v2/ip_tagging.proto", + "envoy/extensions/filters/http/jwt_authn/v3/config.proto": "envoy/config/filter/http/jwt_authn/v2alpha/config.proto", + "envoy/extensions/filters/http/lua/v3/lua.proto": "envoy/config/filter/http/lua/v2/lua.proto", + "envoy/extensions/filters/http/on_demand/v3/on_demand.proto": "envoy/config/filter/http/on_demand/v2/on_demand.proto", + "envoy/extensions/filters/http/original_src/v3/original_src.proto": "envoy/config/filter/http/original_src/v2alpha1/original_src.proto", + "envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto": "envoy/config/filter/http/rate_limit/v2/rate_limit.proto", + "envoy/extensions/filters/http/rbac/v3/rbac.proto": "envoy/config/filter/http/rbac/v2/rbac.proto", + "envoy/extensions/filters/http/router/v3/router.proto": "envoy/config/filter/http/router/v2/router.proto", + "envoy/extensions/filters/http/squash/v3/squash.proto": "envoy/config/filter/http/squash/v2/squash.proto", + "envoy/extensions/filters/http/tap/v3/tap.proto": "envoy/config/filter/http/tap/v2alpha/tap.proto", + "envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto": "envoy/config/filter/http/transcoder/v2/transcoder.proto", + "envoy/extensions/filters/listener/http_inspector/v3/http_inspector.proto": "envoy/config/filter/listener/http_inspector/v2/http_inspector.proto", + "envoy/extensions/filters/listener/original_dst/v3/original_dst.proto": "envoy/config/filter/listener/original_dst/v2/original_dst.proto", + "envoy/extensions/filters/listener/original_src/v3/original_src.proto": "envoy/config/filter/listener/original_src/v2alpha1/original_src.proto", + "envoy/extensions/filters/listener/proxy_protocol/v3/proxy_protocol.proto": "envoy/config/filter/listener/proxy_protocol/v2/proxy_protocol.proto", + "envoy/extensions/filters/listener/tls_inspector/v3/tls_inspector.proto": "envoy/config/filter/listener/tls_inspector/v2/tls_inspector.proto", + "envoy/extensions/filters/network/client_ssl_auth/v3/client_ssl_auth.proto": "envoy/config/filter/network/client_ssl_auth/v2/client_ssl_auth.proto", + "envoy/extensions/filters/network/direct_response/v3/config.proto": "envoy/config/filter/network/direct_response/v2/config.proto", + "envoy/extensions/filters/network/dubbo_proxy/v3/dubbo_proxy.proto": "envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.proto", + "envoy/extensions/filters/network/dubbo_proxy/v3/route.proto": "envoy/config/filter/network/dubbo_proxy/v2alpha1/route.proto", + "envoy/extensions/filters/network/echo/v3/echo.proto": "envoy/config/filter/network/echo/v2/echo.proto", + "envoy/extensions/filters/network/ext_authz/v3/ext_authz.proto": "envoy/config/filter/network/ext_authz/v2/ext_authz.proto", + "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto": "envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto", + "envoy/extensions/filters/network/kafka_broker/v3/kafka_broker.proto": "envoy/config/filter/network/kafka_broker/v2alpha1/kafka_broker.proto", + "envoy/extensions/filters/network/local_ratelimit/v3/local_rate_limit.proto": "envoy/config/filter/network/local_rate_limit/v2alpha/local_rate_limit.proto", + "envoy/extensions/filters/network/mongo_proxy/v3/mongo_proxy.proto": "envoy/config/filter/network/mongo_proxy/v2/mongo_proxy.proto", + "envoy/extensions/filters/network/ratelimit/v3/rate_limit.proto": "envoy/config/filter/network/rate_limit/v2/rate_limit.proto", + "envoy/extensions/filters/network/rbac/v3/rbac.proto": "envoy/config/filter/network/rbac/v2/rbac.proto", + "envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto": "envoy/config/filter/network/redis_proxy/v2/redis_proxy.proto", + "envoy/extensions/filters/network/sni_cluster/v3/sni_cluster.proto": "envoy/config/filter/network/sni_cluster/v2/sni_cluster.proto", + "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto": "envoy/config/filter/network/tcp_proxy/v2/tcp_proxy.proto", + "envoy/extensions/filters/network/thrift_proxy/v3/route.proto": "envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto", + "envoy/extensions/filters/network/thrift_proxy/v3/thrift_proxy.proto": "envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.proto", + "envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v3/rate_limit.proto": "envoy/config/filter/thrift/rate_limit/v2alpha1/rate_limit.proto", + "envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto": "envoy/config/filter/udp/udp_proxy/v2alpha/udp_proxy.proto", + "envoy/config/grpc_credential/v3/aws_iam.proto": "envoy/config/grpc_credential/v2alpha/aws_iam.proto", + "envoy/config/grpc_credential/v3/file_based_metadata.proto": "envoy/config/grpc_credential/v2alpha/file_based_metadata.proto", + "envoy/config/listener/v3/api_listener.proto": "envoy/config/listener/v2/api_listener.proto", + "envoy/config/metrics/v3/metrics_service.proto": "envoy/config/metrics/v2/metrics_service.proto", + "envoy/config/metrics/v3/stats.proto": "envoy/config/metrics/v2/stats.proto", + "envoy/config/overload/v3/overload.proto": "envoy/config/overload/v2alpha/overload.proto", + "envoy/config/ratelimit/v3/rls.proto": "envoy/config/ratelimit/v2/rls.proto", + "envoy/config/rbac/v3/rbac.proto": "envoy/config/rbac/v2/rbac.proto", + "envoy/extensions/retry/host/omit_canary_hosts/v3/omit_canary_hosts.proto": "envoy/config/retry/omit_canary_hosts/v2/omit_canary_hosts.proto", + "envoy/extensions/retry/host/omit_host_metadata/v3/omit_host_metadata_config.proto": "envoy/config/retry/omit_host_metadata/v2/omit_host_metadata_config.proto", + "envoy/extensions/retry/host/previous_hosts/v3/previous_hosts.proto": "envoy/config/retry/previous_hosts/v2/previous_hosts.proto", + "envoy/config/trace/v3/datadog.proto": "envoy/config/trace/v2/datadog.proto", + "envoy/config/trace/v3/dynamic_ot.proto": "envoy/config/trace/v2/dynamic_ot.proto", + "envoy/config/trace/v3/http_tracer.proto": "envoy/config/trace/v2/http_tracer.proto", + "envoy/config/trace/v3/lightstep.proto": "envoy/config/trace/v2/lightstep.proto", + "envoy/config/trace/v3/opencensus.proto": "envoy/config/trace/v2/opencensus.proto", + "envoy/config/trace/v3/service.proto": "envoy/config/trace/v2/service.proto", + "envoy/config/trace/v3/zipkin.proto": "envoy/config/trace/v2/zipkin.proto", + "envoy/config/trace/v3/xray.proto": "envoy/config/trace/v2alpha/xray.proto", + "envoy/extensions/transport_sockets/alts/v3/alts.proto": "envoy/config/transport_socket/alts/v2alpha/alts.proto", + "envoy/extensions/transport_sockets/raw_buffer/v3/raw_buffer.proto": "envoy/config/transport_socket/raw_buffer/v2/raw_buffer.proto", + "envoy/extensions/transport_sockets/tap/v3/tap.proto": "envoy/config/transport_socket/tap/v2alpha/tap.proto", + "envoy/data/accesslog/v3/accesslog.proto": "envoy/data/accesslog/v2/accesslog.proto", + "envoy/data/cluster/v3/outlier_detection_event.proto": "envoy/data/cluster/v2alpha/outlier_detection_event.proto", + "envoy/data/core/v3/health_check_event.proto": "envoy/data/core/v2alpha/health_check_event.proto", + "envoy/data/dns/v3/dns_table.proto": "envoy/data/dns/v2alpha/dns_table.proto", + "envoy/data/tap/v3/common.proto": "envoy/data/tap/v2alpha/common.proto", + "envoy/data/tap/v3/http.proto": "envoy/data/tap/v2alpha/http.proto", + "envoy/data/tap/v3/transport.proto": "envoy/data/tap/v2alpha/transport.proto", + "envoy/data/tap/v3/wrapper.proto": "envoy/data/tap/v2alpha/wrapper.proto", + "envoy/service/accesslog/v3/als.proto": "envoy/service/accesslog/v2/als.proto", + "envoy/service/auth/v3/attribute_context.proto": "envoy/service/auth/v2/attribute_context.proto", + "envoy/service/auth/v3/external_auth.proto": "envoy/service/auth/v2/external_auth.proto", + "envoy/service/discovery/v3/ads.proto": "envoy/service/discovery/v2/ads.proto", + "envoy/service/runtime/v3/rtds.proto": "envoy/service/discovery/v2/rtds.proto", + "envoy/service/secret/v3/sds.proto": "envoy/service/discovery/v2/sds.proto", + "envoy/service/event_reporting/v3/event_reporting_service.proto": "envoy/service/event_reporting/v2alpha/event_reporting_service.proto", + "envoy/service/load_stats/v3/lrs.proto": "envoy/service/load_stats/v2/lrs.proto", + "envoy/service/metrics/v3/metrics_service.proto": "envoy/service/metrics/v2/metrics_service.proto", + "envoy/service/ratelimit/v3/rls.proto": "envoy/service/ratelimit/v2/rls.proto", + "envoy/service/status/v3/csds.proto": "envoy/service/status/v2/csds.proto", + "envoy/config/tap/v3/common.proto": "envoy/service/tap/v2alpha/common.proto", + "envoy/service/tap/v3/tap.proto": "envoy/service/tap/v2alpha/tap.proto", + "envoy/service/trace/v3/trace_service.proto": "envoy/service/trace/v2/trace_service.proto", + "envoy/type/metadata/v3/metadata.proto": "envoy/type/metadata/v2/metadata.proto", + "envoy/type/tracing/v3/custom_tag.proto": "envoy/type/tracing/v2/custom_tag.proto" +} diff --git a/examples/wasm-cc/docker-compose-wasm.yaml b/examples/wasm-cc/docker-compose-wasm.yaml index eb7424995583b..32caf4245c275 100644 --- a/examples/wasm-cc/docker-compose-wasm.yaml +++ b/examples/wasm-cc/docker-compose-wasm.yaml @@ -2,7 +2,7 @@ version: "3.7" services: wasm_compile_update: - image: envoyproxy/envoy-build-ubuntu:3d0491e2034287959a292806e3891fd0b7dd2703 + image: envoyproxy/envoy-build-ubuntu:55d9e4719d2bd0accce8f829b44dab70cd42112a command: | bash -c "bazel build //examples/wasm-cc:envoy_filter_http_wasm_updated_example.wasm \ && cp -a bazel-bin/examples/wasm-cc/* /build" @@ -12,7 +12,7 @@ services: - ./lib:/build wasm_compile: - image: envoyproxy/envoy-build-ubuntu:3d0491e2034287959a292806e3891fd0b7dd2703 + image: envoyproxy/envoy-build-ubuntu:55d9e4719d2bd0accce8f829b44dab70cd42112a command: | bash -c "bazel build //examples/wasm-cc:envoy_filter_http_wasm_example.wasm \ && cp -a bazel-bin/examples/wasm-cc/* /build" diff --git a/tools/protodoc/BUILD b/tools/protodoc/BUILD index 919df98a81374..d9eeb6ac203fb 100644 --- a/tools/protodoc/BUILD +++ b/tools/protodoc/BUILD @@ -21,7 +21,10 @@ envoy_proto_library( py_binary( name = "protodoc", srcs = ["protodoc.py"], - data = ["//docs:protodoc_manifest.yaml"], + data = [ + "//docs:protodoc_manifest.yaml", + "//docs:v2_mapping.json", + ], visibility = ["//visibility:public"], deps = [ ":manifest_proto_py_proto", diff --git a/tools/protodoc/protodoc.py b/tools/protodoc/protodoc.py index e3fbb4a940681..0dbfe44db6642 100755 --- a/tools/protodoc/protodoc.py +++ b/tools/protodoc/protodoc.py @@ -36,6 +36,9 @@ # Namespace prefix for Envoy core APIs. ENVOY_API_NAMESPACE_PREFIX = '.envoy.api.v2.' +# Last documented v2 api version +ENVOY_LAST_V2_VERSION = "1.17.2" + # Namespace prefix for Envoy top-level APIs. ENVOY_PREFIX = '.envoy.' @@ -57,7 +60,7 @@ """ .. _extension_{{extension}}: -This extension may be referenced by the qualified name *{{extension}}* +This extension may be referenced by the qualified name ``{{extension}}`` .. note:: {{status}} @@ -124,6 +127,18 @@ for _cat in _v['categories']: EXTENSION_CATEGORIES.setdefault(_cat, []).append(_k) +V2_LINK_TEMPLATE = Template( + """ +This documentation is for the Envoy v3 API. + +As of Envoy v1.18 the v2 API has been removed and is no longer supported. + +If you are upgrading from v2 API config you may wish to view the v2 API documentation: + + :ref:`{{v2_text}} <{{v2_url}}>` + +""") + class ProtodocError(Exception): """Base error class for the protodoc module.""" @@ -260,7 +275,7 @@ def format_extension_category(extension_category): category=extension_category, extensions=sorted(extensions)) -def format_header_from_file(style, source_code_info, proto_name): +def format_header_from_file(style, source_code_info, proto_name, v2_link): """Format RST header based on special file level title Args: @@ -281,9 +296,10 @@ def format_header_from_file(style, source_code_info, proto_name): formatted_extension = format_extension(extension) if annotations.DOC_TITLE_ANNOTATION in source_code_info.file_level_annotations: return anchor + format_header( - style, source_code_info.file_level_annotations[ - annotations.DOC_TITLE_ANNOTATION]) + formatted_extension, stripped_comment - return anchor + format_header(style, proto_name) + formatted_extension, stripped_comment + style, source_code_info.file_level_annotations[annotations.DOC_TITLE_ANNOTATION] + ) + v2_link + "\n\n" + formatted_extension, stripped_comment + return anchor + format_header( + style, proto_name) + v2_link + "\n\n" + formatted_extension, stripped_comment def format_field_type_as_json(type_context, field): @@ -648,6 +664,10 @@ class RstFormatVisitor(visitor.Visitor): def __init__(self): r = runfiles.Create() + + with open(r.Rlocation('envoy/docs/v2_mapping.json'), 'r') as f: + self.v2_mapping = json.load(f) + with open(r.Rlocation('envoy/docs/protodoc_manifest.yaml'), 'r') as f: # Load as YAML, emit as JSON and then parse as proto to provide type # checking. @@ -681,6 +701,7 @@ def visit_message(self, msg_proto, type_context, nested_msgs, nested_enums): formatted_leading_comment = format_comment_with_annotations(leading_comment, 'message') if hide_not_implemented(leading_comment): return '' + return anchor + header + proto_link + formatted_leading_comment + format_message_as_json( type_context, msg_proto) + format_message_as_definition_list( type_context, msg_proto, @@ -691,6 +712,14 @@ def visit_file(self, file_proto, type_context, services, msgs, enums): if all(len(msg) == 0 for msg in msgs) and all(len(enum) == 0 for enum in enums): has_messages = False + v2_link = "" + if file_proto.name in self.v2_mapping: + # TODO(phlax): remove _v2_ from filepath once sed mangling is removed + v2_filepath = f"envoy_v2_api_file_{self.v2_mapping[file_proto.name]}" + v2_text = v2_filepath.split('/', 1)[1] + v2_url = f"v{ENVOY_LAST_V2_VERSION}:{v2_filepath}" + v2_link = V2_LINK_TEMPLATE.render(v2_url=v2_url, v2_text=v2_text) + # TODO(mattklein123): The logic in both the doc and transform tool around files without messages # is confusing and should be cleaned up. This is a stop gap to have titles for all proto docs # in the common case. @@ -704,7 +733,7 @@ def visit_file(self, file_proto, type_context, services, msgs, enums): # Find the earliest detached comment, attribute it to file level. # Also extract file level titles if any. header, comment = format_header_from_file( - '=', type_context.source_code_info, file_proto.name) + '=', type_context.source_code_info, file_proto.name, v2_link) # If there are no messages, we don't include in the doc tree (no support for # service rendering yet). We allow these files to be missing from the # toctrees. @@ -716,7 +745,7 @@ def visit_file(self, file_proto, type_context, services, msgs, enums): warnings += ( '.. warning::\n This API is work-in-progress and is ' 'subject to breaking changes.\n\n') - debug_proto = format_proto_as_block_comment(file_proto) + # debug_proto = format_proto_as_block_comment(file_proto) return header + warnings + comment + '\n'.join(msgs) + '\n'.join(enums) # + debug_proto From 45ec050f91407147ed53a999434b09ef77590177 Mon Sep 17 00:00:00 2001 From: phlax Date: Sun, 9 May 2021 16:55:08 +0100 Subject: [PATCH 178/209] docs: Simplify api build (#16277) This fixes the refs in protos and removes the sed operations for build The benefits are: - less indirection in proto refs - (small) speedup of build - simplify docs build - simplify reuse of proto comments (eg generating jsonschemas) Signed-off-by: Ryan Northey --- api/envoy/admin/v3/clusters.proto | 12 +- api/envoy/admin/v3/config_dump.proto | 24 +-- api/envoy/admin/v4alpha/clusters.proto | 12 +- api/envoy/admin/v4alpha/config_dump.proto | 24 +-- api/envoy/config/accesslog/v3/accesslog.proto | 4 +- .../config/accesslog/v4alpha/accesslog.proto | 4 +- api/envoy/config/bootstrap/v3/bootstrap.proto | 42 ++--- .../config/bootstrap/v4alpha/bootstrap.proto | 38 ++--- .../config/cluster/v3/circuit_breaker.proto | 10 +- api/envoy/config/cluster/v3/cluster.proto | 130 ++++++++-------- .../config/cluster/v3/outlier_detection.proto | 18 +-- .../cluster/v4alpha/circuit_breaker.proto | 10 +- .../config/cluster/v4alpha/cluster.proto | 96 ++++++------ .../cluster/v4alpha/outlier_detection.proto | 18 +-- api/envoy/config/core/v3/address.proto | 14 +- api/envoy/config/core/v3/backoff.proto | 6 +- api/envoy/config/core/v3/base.proto | 16 +- api/envoy/config/core/v3/config_source.proto | 10 +- api/envoy/config/core/v3/grpc_service.proto | 8 +- api/envoy/config/core/v3/health_check.proto | 20 +-- api/envoy/config/core/v3/protocol.proto | 6 +- api/envoy/config/core/v4alpha/address.proto | 14 +- api/envoy/config/core/v4alpha/backoff.proto | 6 +- api/envoy/config/core/v4alpha/base.proto | 16 +- .../config/core/v4alpha/config_source.proto | 10 +- .../config/core/v4alpha/grpc_service.proto | 8 +- .../config/core/v4alpha/health_check.proto | 20 +-- api/envoy/config/core/v4alpha/protocol.proto | 6 +- api/envoy/config/endpoint/v3/endpoint.proto | 4 +- .../endpoint/v3/endpoint_components.proto | 10 +- .../config/endpoint/v3/load_report.proto | 6 +- api/envoy/config/listener/v3/listener.proto | 36 ++--- .../listener/v3/listener_components.proto | 4 +- .../config/listener/v4alpha/listener.proto | 36 ++--- .../v4alpha/listener_components.proto | 4 +- .../config/metrics/v3/metrics_service.proto | 4 +- api/envoy/config/metrics/v3/stats.proto | 18 +-- .../metrics/v4alpha/metrics_service.proto | 4 +- api/envoy/config/metrics/v4alpha/stats.proto | 18 +-- api/envoy/config/rbac/v3/rbac.proto | 6 +- api/envoy/config/rbac/v4alpha/rbac.proto | 6 +- api/envoy/config/route/v3/route.proto | 20 +-- .../config/route/v3/route_components.proto | 144 +++++++++--------- api/envoy/config/route/v3/scoped_route.proto | 16 +- api/envoy/config/route/v4alpha/route.proto | 20 +-- .../route/v4alpha/route_components.proto | 124 +++++++-------- .../config/route/v4alpha/scoped_route.proto | 16 +- api/envoy/config/tap/v3/common.proto | 30 ++-- api/envoy/config/tap/v4alpha/common.proto | 24 +-- api/envoy/config/trace/v3/http_tracer.proto | 6 +- .../config/trace/v4alpha/http_tracer.proto | 6 +- .../cluster/v3/outlier_detection_event.proto | 10 +- .../data/core/v3/health_check_event.proto | 2 +- api/envoy/data/tap/v3/common.proto | 6 +- api/envoy/data/tap/v3/transport.proto | 4 +- .../access_loggers/file/v3/file.proto | 2 +- .../access_loggers/file/v4alpha/file.proto | 2 +- .../access_loggers/grpc/v3/als.proto | 14 +- .../access_loggers/grpc/v4alpha/als.proto | 14 +- .../open_telemetry/v3alpha/logs_service.proto | 2 +- .../open_telemetry/v4alpha/logs_service.proto | 2 +- .../access_loggers/stream/v3/stream.proto | 4 +- .../stream/v4alpha/stream.proto | 4 +- .../access_loggers/wasm/v3/wasm.proto | 2 +- .../dynamic_forward_proxy/v3/cluster.proto | 4 +- .../clusters/redis/v3/redis_cluster.proto | 2 +- .../v3/aws_request_signing.proto | 2 +- .../filters/http/composite/v3/composite.proto | 4 +- .../http/compressor/v3/compressor.proto | 2 +- .../http/compressor/v4alpha/compressor.proto | 2 +- .../filters/http/csrf/v3/csrf.proto | 6 +- .../filters/http/csrf/v4alpha/csrf.proto | 6 +- .../http/decompressor/v3/decompressor.proto | 2 +- .../v3/dynamic_forward_proxy.proto | 6 +- .../filters/http/ext_authz/v3/ext_authz.proto | 38 ++--- .../http/ext_authz/v4alpha/ext_authz.proto | 38 ++--- .../filters/http/fault/v3/fault.proto | 2 +- .../filters/http/fault/v4alpha/fault.proto | 2 +- .../grpc_json_transcoder/v3/transcoder.proto | 10 +- .../filters/http/grpc_stats/v3/config.proto | 4 +- .../local_ratelimit/v3/local_rate_limit.proto | 6 +- .../network/ext_authz/v3/ext_authz.proto | 4 +- .../network/ext_authz/v4alpha/ext_authz.proto | 4 +- .../v3/http_connection_manager.proto | 50 +++--- .../v4alpha/http_connection_manager.proto | 50 +++--- .../local_ratelimit/v3/local_rate_limit.proto | 2 +- .../v3alpha/postgres_proxy.proto | 2 +- .../network/redis_proxy/v3/redis_proxy.proto | 4 +- .../v3alpha/sni_dynamic_forward_proxy.proto | 2 +- .../network/tcp_proxy/v3/tcp_proxy.proto | 2 +- .../network/tcp_proxy/v4alpha/tcp_proxy.proto | 2 +- .../filters/ratelimit/v3/rate_limit.proto | 2 +- .../ratelimit/v4alpha/rate_limit.proto | 2 +- .../network/thrift_proxy/v3/route.proto | 12 +- .../thrift_proxy/v3/thrift_proxy.proto | 10 +- .../network/thrift_proxy/v4alpha/route.proto | 12 +- .../thrift_proxy/v4alpha/thrift_proxy.proto | 10 +- .../v3/allow_listed_routes_config.proto | 2 +- .../transport_sockets/tls/v3/common.proto | 32 ++-- .../transport_sockets/tls/v3/tls.proto | 8 +- .../tls/v3/tls_spiffe_validator_config.proto | 4 +- .../tls/v4alpha/common.proto | 32 ++-- .../transport_sockets/tls/v4alpha/tls.proto | 8 +- .../v4alpha/tls_spiffe_validator_config.proto | 4 +- .../http/v3/http_protocol_options.proto | 2 +- .../http/v4alpha/http_protocol_options.proto | 2 +- api/envoy/service/accesslog/v3/als.proto | 2 +- api/envoy/service/accesslog/v4alpha/als.proto | 2 +- .../service/auth/v3/attribute_context.proto | 2 +- .../auth/v4alpha/attribute_context.proto | 2 +- .../service/discovery/v3/discovery.proto | 4 +- .../service/discovery/v4alpha/discovery.proto | 4 +- .../v3/event_reporting_service.proto | 4 +- .../v4alpha/event_reporting_service.proto | 4 +- api/envoy/service/health/v3/hds.proto | 4 +- api/envoy/service/health/v4alpha/hds.proto | 4 +- api/envoy/service/load_stats/v3/lrs.proto | 8 +- .../service/load_stats/v4alpha/lrs.proto | 8 +- api/envoy/service/route/v3/rds.proto | 4 +- api/envoy/service/route/v3/srds.proto | 4 +- api/envoy/service/tap/v3/tap.proto | 2 +- api/envoy/service/tap/v4alpha/tap.proto | 2 +- .../type/http/v3/path_transformation.proto | 2 +- api/envoy/type/matcher/v3/metadata.proto | 6 +- api/envoy/type/matcher/v4alpha/metadata.proto | 6 +- api/envoy/type/metadata/v3/metadata.proto | 8 +- api/envoy/type/tracing/v3/custom_tag.proto | 4 +- docs/build.sh | 8 - .../envoy/admin/v3/clusters.proto | 12 +- .../envoy/admin/v3/config_dump.proto | 24 +-- .../envoy/admin/v4alpha/clusters.proto | 12 +- .../envoy/admin/v4alpha/config_dump.proto | 24 +-- .../envoy/config/accesslog/v3/accesslog.proto | 4 +- .../config/accesslog/v4alpha/accesslog.proto | 4 +- .../envoy/config/bootstrap/v3/bootstrap.proto | 42 ++--- .../config/bootstrap/v4alpha/bootstrap.proto | 42 ++--- .../config/cluster/v3/circuit_breaker.proto | 10 +- .../envoy/config/cluster/v3/cluster.proto | 130 ++++++++-------- .../config/cluster/v3/outlier_detection.proto | 18 +-- .../cluster/v4alpha/circuit_breaker.proto | 10 +- .../config/cluster/v4alpha/cluster.proto | 130 ++++++++-------- .../cluster/v4alpha/outlier_detection.proto | 18 +-- .../envoy/config/core/v3/address.proto | 14 +- .../envoy/config/core/v3/backoff.proto | 6 +- .../envoy/config/core/v3/base.proto | 16 +- .../envoy/config/core/v3/config_source.proto | 10 +- .../envoy/config/core/v3/grpc_service.proto | 8 +- .../envoy/config/core/v3/health_check.proto | 20 +-- .../envoy/config/core/v3/protocol.proto | 6 +- .../envoy/config/core/v4alpha/address.proto | 14 +- .../envoy/config/core/v4alpha/backoff.proto | 6 +- .../envoy/config/core/v4alpha/base.proto | 16 +- .../config/core/v4alpha/config_source.proto | 10 +- .../config/core/v4alpha/grpc_service.proto | 8 +- .../config/core/v4alpha/health_check.proto | 20 +-- .../envoy/config/core/v4alpha/protocol.proto | 6 +- .../envoy/config/endpoint/v3/endpoint.proto | 4 +- .../endpoint/v3/endpoint_components.proto | 10 +- .../config/endpoint/v3/load_report.proto | 6 +- .../envoy/config/listener/v3/listener.proto | 36 ++--- .../listener/v3/listener_components.proto | 4 +- .../config/listener/v4alpha/listener.proto | 36 ++--- .../v4alpha/listener_components.proto | 4 +- .../config/metrics/v3/metrics_service.proto | 4 +- .../envoy/config/metrics/v3/stats.proto | 18 +-- .../metrics/v4alpha/metrics_service.proto | 4 +- .../envoy/config/metrics/v4alpha/stats.proto | 18 +-- .../envoy/config/rbac/v3/rbac.proto | 6 +- .../envoy/config/rbac/v4alpha/rbac.proto | 6 +- .../envoy/config/route/v3/route.proto | 20 +-- .../config/route/v3/route_components.proto | 144 +++++++++--------- .../envoy/config/route/v3/scoped_route.proto | 16 +- .../envoy/config/route/v4alpha/route.proto | 20 +-- .../route/v4alpha/route_components.proto | 144 +++++++++--------- .../config/route/v4alpha/scoped_route.proto | 16 +- .../envoy/config/tap/v3/common.proto | 30 ++-- .../envoy/config/tap/v4alpha/common.proto | 30 ++-- .../envoy/config/trace/v3/http_tracer.proto | 6 +- .../config/trace/v4alpha/http_tracer.proto | 6 +- .../cluster/v3/outlier_detection_event.proto | 10 +- .../data/core/v3/health_check_event.proto | 2 +- .../envoy/data/tap/v3/common.proto | 6 +- .../envoy/data/tap/v3/transport.proto | 4 +- .../access_loggers/file/v3/file.proto | 2 +- .../access_loggers/file/v4alpha/file.proto | 2 +- .../access_loggers/grpc/v3/als.proto | 14 +- .../access_loggers/grpc/v4alpha/als.proto | 14 +- .../open_telemetry/v3alpha/logs_service.proto | 2 +- .../open_telemetry/v4alpha/logs_service.proto | 2 +- .../access_loggers/stream/v3/stream.proto | 4 +- .../stream/v4alpha/stream.proto | 4 +- .../access_loggers/wasm/v3/wasm.proto | 2 +- .../dynamic_forward_proxy/v3/cluster.proto | 4 +- .../clusters/redis/v3/redis_cluster.proto | 2 +- .../v3/aws_request_signing.proto | 2 +- .../filters/http/composite/v3/composite.proto | 4 +- .../http/compressor/v3/compressor.proto | 2 +- .../http/compressor/v4alpha/compressor.proto | 2 +- .../filters/http/csrf/v3/csrf.proto | 6 +- .../filters/http/csrf/v4alpha/csrf.proto | 6 +- .../http/decompressor/v3/decompressor.proto | 2 +- .../v3/dynamic_forward_proxy.proto | 6 +- .../filters/http/ext_authz/v3/ext_authz.proto | 38 ++--- .../http/ext_authz/v4alpha/ext_authz.proto | 38 ++--- .../filters/http/fault/v3/fault.proto | 2 +- .../filters/http/fault/v4alpha/fault.proto | 2 +- .../grpc_json_transcoder/v3/transcoder.proto | 10 +- .../filters/http/grpc_stats/v3/config.proto | 4 +- .../local_ratelimit/v3/local_rate_limit.proto | 6 +- .../network/ext_authz/v3/ext_authz.proto | 4 +- .../network/ext_authz/v4alpha/ext_authz.proto | 4 +- .../v3/http_connection_manager.proto | 50 +++--- .../v4alpha/http_connection_manager.proto | 50 +++--- .../local_ratelimit/v3/local_rate_limit.proto | 2 +- .../v3alpha/postgres_proxy.proto | 2 +- .../network/redis_proxy/v3/redis_proxy.proto | 4 +- .../v3alpha/sni_dynamic_forward_proxy.proto | 2 +- .../network/tcp_proxy/v3/tcp_proxy.proto | 2 +- .../network/tcp_proxy/v4alpha/tcp_proxy.proto | 2 +- .../filters/ratelimit/v3/rate_limit.proto | 2 +- .../ratelimit/v4alpha/rate_limit.proto | 2 +- .../network/thrift_proxy/v3/route.proto | 12 +- .../thrift_proxy/v3/thrift_proxy.proto | 10 +- .../network/thrift_proxy/v4alpha/route.proto | 12 +- .../thrift_proxy/v4alpha/thrift_proxy.proto | 10 +- .../v3/allow_listed_routes_config.proto | 2 +- .../transport_sockets/tls/v3/common.proto | 32 ++-- .../transport_sockets/tls/v3/tls.proto | 8 +- .../tls/v3/tls_spiffe_validator_config.proto | 4 +- .../tls/v4alpha/common.proto | 32 ++-- .../transport_sockets/tls/v4alpha/tls.proto | 8 +- .../v4alpha/tls_spiffe_validator_config.proto | 4 +- .../http/v3/http_protocol_options.proto | 2 +- .../http/v4alpha/http_protocol_options.proto | 2 +- .../envoy/service/accesslog/v3/als.proto | 2 +- .../envoy/service/accesslog/v4alpha/als.proto | 2 +- .../service/auth/v3/attribute_context.proto | 2 +- .../auth/v4alpha/attribute_context.proto | 2 +- .../service/discovery/v3/discovery.proto | 4 +- .../service/discovery/v4alpha/discovery.proto | 4 +- .../v3/event_reporting_service.proto | 4 +- .../v4alpha/event_reporting_service.proto | 4 +- .../envoy/service/health/v3/hds.proto | 4 +- .../envoy/service/health/v4alpha/hds.proto | 4 +- .../envoy/service/load_stats/v3/lrs.proto | 8 +- .../service/load_stats/v4alpha/lrs.proto | 8 +- .../envoy/service/route/v3/rds.proto | 4 +- .../envoy/service/route/v3/srds.proto | 4 +- .../envoy/service/tap/v3/tap.proto | 2 +- .../envoy/service/tap/v4alpha/tap.proto | 2 +- .../type/http/v3/path_transformation.proto | 2 +- .../envoy/type/matcher/v3/metadata.proto | 6 +- .../envoy/type/matcher/v4alpha/metadata.proto | 6 +- .../envoy/type/metadata/v3/metadata.proto | 8 +- .../envoy/type/tracing/v3/custom_tag.proto | 4 +- tools/protodoc/protodoc.py | 13 +- 256 files changed, 1756 insertions(+), 1765 deletions(-) diff --git a/api/envoy/admin/v3/clusters.proto b/api/envoy/admin/v3/clusters.proto index b526fa31460f8..509280f466243 100644 --- a/api/envoy/admin/v3/clusters.proto +++ b/api/envoy/admin/v3/clusters.proto @@ -41,10 +41,10 @@ message ClusterStatus { // The success rate threshold used in the last interval. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *false*, all errors: externally and locally generated were used to calculate the threshold. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*, only externally generated errors were used to calculate the threshold. // The threshold is used to eject hosts based on their success rate. See // :ref:`Cluster outlier detection ` documentation for details. @@ -64,7 +64,7 @@ message ClusterStatus { // The success rate threshold used in the last interval when only locally originated failures were // taken into account and externally originated errors were treated as success. // This field should be interpreted only when - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*. The threshold is used to eject hosts based on their success rate. // See :ref:`Cluster outlier detection ` documentation for // details. @@ -101,10 +101,10 @@ message HostStatus { // Request success rate for this host over the last calculated interval. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *false*, all errors: externally and locally generated were used in success rate // calculation. If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*, only externally generated errors were used in success rate calculation. // See :ref:`Cluster outlier detection ` documentation for // details. @@ -127,7 +127,7 @@ message HostStatus { // interval when only locally originated errors are taken into account and externally originated // errors were treated as success. // This field should be interpreted only when - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*. // See :ref:`Cluster outlier detection ` documentation for // details. diff --git a/api/envoy/admin/v3/config_dump.proto b/api/envoy/admin/v3/config_dump.proto index 49c208537cd59..21e3e8c326728 100644 --- a/api/envoy/admin/v3/config_dump.proto +++ b/api/envoy/admin/v3/config_dump.proto @@ -53,11 +53,11 @@ message ConfigDump { // The following configurations are currently supported and will be dumped in the order given // below: // - // * *bootstrap*: :ref:`BootstrapConfigDump ` - // * *clusters*: :ref:`ClustersConfigDump ` - // * *endpoints*: :ref:`EndpointsConfigDump ` - // * *listeners*: :ref:`ListenersConfigDump ` - // * *routes*: :ref:`RoutesConfigDump ` + // * *bootstrap*: :ref:`BootstrapConfigDump ` + // * *clusters*: :ref:`ClustersConfigDump ` + // * *endpoints*: :ref:`EndpointsConfigDump ` + // * *listeners*: :ref:`ListenersConfigDump ` + // * *routes*: :ref:`RoutesConfigDump ` // // EDS Configuration will only be dumped by using parameter `?include_eds` // @@ -126,7 +126,7 @@ message ListenersConfigDump { "envoy.admin.v2alpha.ListenersConfigDump.DynamicListenerState"; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time + // :ref:`version_info ` field at the time // that the listener was loaded. In the future, discrete per-listener versions may be supported // by the API. string version_info = 1; @@ -174,7 +174,7 @@ message ListenersConfigDump { ClientResourceStatus client_status = 6; } - // This is the :ref:`version_info ` in the + // This is the :ref:`version_info ` in the // last processed LDS discovery response. If there are only static bootstrap listeners, this field // will be "". string version_info = 1; @@ -212,7 +212,7 @@ message ClustersConfigDump { "envoy.admin.v2alpha.ClustersConfigDump.DynamicCluster"; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time + // :ref:`version_info ` field at the time // that the cluster was loaded. In the future, discrete per-cluster versions may be supported by // the API. string version_info = 1; @@ -235,7 +235,7 @@ message ClustersConfigDump { ClientResourceStatus client_status = 5; } - // This is the :ref:`version_info ` in the + // This is the :ref:`version_info ` in the // last processed CDS discovery response. If there are only static bootstrap clusters, this field // will be "". string version_info = 1; @@ -280,7 +280,7 @@ message RoutesConfigDump { "envoy.admin.v2alpha.RoutesConfigDump.DynamicRouteConfig"; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time that + // :ref:`version_info ` field at the time that // the route configuration was loaded. string version_info = 1; @@ -340,7 +340,7 @@ message ScopedRoutesConfigDump { string name = 1; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time that + // :ref:`version_info ` field at the time that // the scoped routes configuration was loaded. string version_info = 2; @@ -450,7 +450,7 @@ message EndpointsConfigDump { // [#next-free-field: 6] message DynamicEndpointConfig { // [#not-implemented-hide:] This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time that + // :ref:`version_info ` field at the time that // the endpoint configuration was loaded. string version_info = 1; diff --git a/api/envoy/admin/v4alpha/clusters.proto b/api/envoy/admin/v4alpha/clusters.proto index 116dc226c25e5..12969a28d0082 100644 --- a/api/envoy/admin/v4alpha/clusters.proto +++ b/api/envoy/admin/v4alpha/clusters.proto @@ -41,10 +41,10 @@ message ClusterStatus { // The success rate threshold used in the last interval. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *false*, all errors: externally and locally generated were used to calculate the threshold. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*, only externally generated errors were used to calculate the threshold. // The threshold is used to eject hosts based on their success rate. See // :ref:`Cluster outlier detection ` documentation for details. @@ -64,7 +64,7 @@ message ClusterStatus { // The success rate threshold used in the last interval when only locally originated failures were // taken into account and externally originated errors were treated as success. // This field should be interpreted only when - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*. The threshold is used to eject hosts based on their success rate. // See :ref:`Cluster outlier detection ` documentation for // details. @@ -101,10 +101,10 @@ message HostStatus { // Request success rate for this host over the last calculated interval. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *false*, all errors: externally and locally generated were used in success rate // calculation. If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*, only externally generated errors were used in success rate calculation. // See :ref:`Cluster outlier detection ` documentation for // details. @@ -127,7 +127,7 @@ message HostStatus { // interval when only locally originated errors are taken into account and externally originated // errors were treated as success. // This field should be interpreted only when - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*. // See :ref:`Cluster outlier detection ` documentation for // details. diff --git a/api/envoy/admin/v4alpha/config_dump.proto b/api/envoy/admin/v4alpha/config_dump.proto index 2eb1e73cd382d..32c7e2d07ab39 100644 --- a/api/envoy/admin/v4alpha/config_dump.proto +++ b/api/envoy/admin/v4alpha/config_dump.proto @@ -53,11 +53,11 @@ message ConfigDump { // The following configurations are currently supported and will be dumped in the order given // below: // - // * *bootstrap*: :ref:`BootstrapConfigDump ` - // * *clusters*: :ref:`ClustersConfigDump ` - // * *endpoints*: :ref:`EndpointsConfigDump ` - // * *listeners*: :ref:`ListenersConfigDump ` - // * *routes*: :ref:`RoutesConfigDump ` + // * *bootstrap*: :ref:`BootstrapConfigDump ` + // * *clusters*: :ref:`ClustersConfigDump ` + // * *endpoints*: :ref:`EndpointsConfigDump ` + // * *listeners*: :ref:`ListenersConfigDump ` + // * *routes*: :ref:`RoutesConfigDump ` // // EDS Configuration will only be dumped by using parameter `?include_eds` // @@ -123,7 +123,7 @@ message ListenersConfigDump { "envoy.admin.v3.ListenersConfigDump.DynamicListenerState"; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time + // :ref:`version_info ` field at the time // that the listener was loaded. In the future, discrete per-listener versions may be supported // by the API. string version_info = 1; @@ -171,7 +171,7 @@ message ListenersConfigDump { ClientResourceStatus client_status = 6; } - // This is the :ref:`version_info ` in the + // This is the :ref:`version_info ` in the // last processed LDS discovery response. If there are only static bootstrap listeners, this field // will be "". string version_info = 1; @@ -208,7 +208,7 @@ message ClustersConfigDump { "envoy.admin.v3.ClustersConfigDump.DynamicCluster"; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time + // :ref:`version_info ` field at the time // that the cluster was loaded. In the future, discrete per-cluster versions may be supported by // the API. string version_info = 1; @@ -231,7 +231,7 @@ message ClustersConfigDump { ClientResourceStatus client_status = 5; } - // This is the :ref:`version_info ` in the + // This is the :ref:`version_info ` in the // last processed CDS discovery response. If there are only static bootstrap clusters, this field // will be "". string version_info = 1; @@ -275,7 +275,7 @@ message RoutesConfigDump { "envoy.admin.v3.RoutesConfigDump.DynamicRouteConfig"; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time that + // :ref:`version_info ` field at the time that // the route configuration was loaded. string version_info = 1; @@ -335,7 +335,7 @@ message ScopedRoutesConfigDump { string name = 1; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time that + // :ref:`version_info ` field at the time that // the scoped routes configuration was loaded. string version_info = 2; @@ -452,7 +452,7 @@ message EndpointsConfigDump { "envoy.admin.v3.EndpointsConfigDump.DynamicEndpointConfig"; // [#not-implemented-hide:] This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time that + // :ref:`version_info ` field at the time that // the endpoint configuration was loaded. string version_info = 1; diff --git a/api/envoy/config/accesslog/v3/accesslog.proto b/api/envoy/config/accesslog/v3/accesslog.proto index 883f11274684b..ad129a3ed64be 100644 --- a/api/envoy/config/accesslog/v3/accesslog.proto +++ b/api/envoy/config/accesslog/v3/accesslog.proto @@ -169,8 +169,8 @@ message RuntimeFilter { // randomly sample based on the runtime key value alone. // *use_independent_randomness* can be used for logging kill switches within // complex nested :ref:`AndFilter - // ` and :ref:`OrFilter - // ` blocks that are easier to + // ` and :ref:`OrFilter + // ` blocks that are easier to // reason about from a probability perspective (i.e., setting to true will // cause the filter to behave like an independent random variable when // composed within logical operator filters). diff --git a/api/envoy/config/accesslog/v4alpha/accesslog.proto b/api/envoy/config/accesslog/v4alpha/accesslog.proto index 8aed1a8d4f4a4..7559a3b82c79f 100644 --- a/api/envoy/config/accesslog/v4alpha/accesslog.proto +++ b/api/envoy/config/accesslog/v4alpha/accesslog.proto @@ -169,8 +169,8 @@ message RuntimeFilter { // randomly sample based on the runtime key value alone. // *use_independent_randomness* can be used for logging kill switches within // complex nested :ref:`AndFilter - // ` and :ref:`OrFilter - // ` blocks that are easier to + // ` and :ref:`OrFilter + // ` blocks that are easier to // reason about from a probability perspective (i.e., setting to true will // cause the filter to behave like an independent random variable when // composed within logical operator filters). diff --git a/api/envoy/config/bootstrap/v3/bootstrap.proto b/api/envoy/config/bootstrap/v3/bootstrap.proto index 19784ab2a3517..9404b7988ffaf 100644 --- a/api/envoy/config/bootstrap/v3/bootstrap.proto +++ b/api/envoy/config/bootstrap/v3/bootstrap.proto @@ -48,12 +48,12 @@ message Bootstrap { option (udpa.annotations.versioning).previous_message_type = "envoy.config.bootstrap.v2.Bootstrap.StaticResources"; - // Static :ref:`Listeners `. These listeners are + // Static :ref:`Listeners `. These listeners are // available regardless of LDS configuration. repeated listener.v3.Listener listeners = 1; // If a network based configuration source is specified for :ref:`cds_config - // `, it's necessary + // `, it's necessary // to have some initial cluster definitions available to allow Envoy to know // how to speak to the management server. These cluster definitions may not // use :ref:`EDS ` (i.e. they should be static @@ -61,7 +61,7 @@ message Bootstrap { repeated cluster.v3.Cluster clusters = 2; // These static secrets can be used by :ref:`SdsSecretConfig - // ` + // ` repeated envoy.extensions.transport_sockets.tls.v3.Secret secrets = 3; } @@ -72,7 +72,7 @@ message Bootstrap { reserved 4; - // All :ref:`Listeners ` are provided by a single + // All :ref:`Listeners ` are provided by a single // :ref:`LDS ` configuration source. core.v3.ConfigSource lds_config = 1; @@ -80,7 +80,7 @@ message Bootstrap { // [#not-implemented-hide:] string lds_resources_locator = 5; - // All post-bootstrap :ref:`Cluster ` definitions are + // All post-bootstrap :ref:`Cluster ` definitions are // provided by a single :ref:`CDS ` // configuration source. core.v3.ConfigSource cds_config = 2; @@ -91,10 +91,10 @@ message Bootstrap { // A single :ref:`ADS ` source may be optionally // specified. This must have :ref:`api_type - // ` :ref:`GRPC - // `. Only - // :ref:`ConfigSources ` that have - // the :ref:`ads ` field set will be + // ` :ref:`GRPC + // `. Only + // :ref:`ConfigSources ` that have + // the :ref:`ads ` field set will be // streamed on the ADS channel. core.v3.ApiConfigSource ads_config = 3; } @@ -152,7 +152,7 @@ message Bootstrap { ClusterManager cluster_manager = 4; // Health discovery service config option. - // (:ref:`core.ApiConfigSource `) + // (:ref:`core.ApiConfigSource `) core.v3.ApiConfigSource hds_config = 14; // Optional file system path to search for startup flag files. @@ -200,7 +200,7 @@ message Bootstrap { // // .. attention:: // This field has been deprecated in favor of :ref:`HttpConnectionManager.Tracing.provider - // `. + // `. trace.v3.Tracing tracing = 9 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -221,7 +221,7 @@ message Bootstrap { // Enable :ref:`stats for event dispatcher `, defaults to false. // Note that this records a value for each iteration of the event loop on every thread. This // should normally be minimal overhead, but when using - // :ref:`statsd `, it will send each observed value + // :ref:`statsd `, it will send each observed value // over the wire individually because the statsd protocol doesn't have any way to represent a // histogram summary. Be aware that this can be a very large volume of data. bool enable_dispatcher_stats = 16; @@ -239,13 +239,13 @@ message Bootstrap { // Optional proxy version which will be used to set the value of :ref:`server.version statistic // ` if specified. Envoy will not process this value, it will be sent as is to - // :ref:`stats sinks `. + // :ref:`stats sinks `. google.protobuf.UInt64Value stats_server_version_override = 19; // Always use TCP queries instead of UDP queries for DNS lookups. // This may be overridden on a per-cluster basis in cds_config, - // when :ref:`dns_resolvers ` and - // :ref:`use_tcp_for_dns_lookups ` are + // when :ref:`dns_resolvers ` and + // :ref:`use_tcp_for_dns_lookups ` are // specified. // Setting this value causes failure if the // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during @@ -291,7 +291,7 @@ message Bootstrap { // Global map of CertificateProvider instances. These instances are referred to by name in the // :ref:`CommonTlsContext.CertificateProviderInstance.instance_name - // ` + // ` // field. // [#not-implemented-hide:] map certificate_provider_instances = 25; @@ -309,7 +309,7 @@ message Admin { // The path to write the access log for the administration server. If no // access log is desired specify ‘/dev/null’. This is only required if - // :ref:`address ` is set. + // :ref:`address ` is set. // Deprecated in favor of *access_log* which offers more options. string access_log_path = 1 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -349,9 +349,9 @@ message ClusterManager { // this configuration). In order to enable :ref:`zone aware routing // ` this option must be set. // If *local_cluster_name* is defined then :ref:`clusters - // ` must be defined in the :ref:`Bootstrap + // ` must be defined in the :ref:`Bootstrap // static cluster resources - // `. This is unrelated to + // `. This is unrelated to // the :option:`--service-cluster` option which does not `affect zone aware // routing `_. string local_cluster_name = 1; @@ -365,8 +365,8 @@ message ClusterManager { // A management server endpoint to stream load stats to via // *StreamLoadStats*. This must have :ref:`api_type - // ` :ref:`GRPC - // `. + // ` :ref:`GRPC + // `. core.v3.ApiConfigSource load_stats_config = 4; } diff --git a/api/envoy/config/bootstrap/v4alpha/bootstrap.proto b/api/envoy/config/bootstrap/v4alpha/bootstrap.proto index 7d369b556ea61..78fc749fb42f9 100644 --- a/api/envoy/config/bootstrap/v4alpha/bootstrap.proto +++ b/api/envoy/config/bootstrap/v4alpha/bootstrap.proto @@ -45,12 +45,12 @@ message Bootstrap { option (udpa.annotations.versioning).previous_message_type = "envoy.config.bootstrap.v3.Bootstrap.StaticResources"; - // Static :ref:`Listeners `. These listeners are + // Static :ref:`Listeners `. These listeners are // available regardless of LDS configuration. repeated listener.v4alpha.Listener listeners = 1; // If a network based configuration source is specified for :ref:`cds_config - // `, it's necessary + // `, it's necessary // to have some initial cluster definitions available to allow Envoy to know // how to speak to the management server. These cluster definitions may not // use :ref:`EDS ` (i.e. they should be static @@ -58,7 +58,7 @@ message Bootstrap { repeated cluster.v4alpha.Cluster clusters = 2; // These static secrets can be used by :ref:`SdsSecretConfig - // ` + // ` repeated envoy.extensions.transport_sockets.tls.v4alpha.Secret secrets = 3; } @@ -69,7 +69,7 @@ message Bootstrap { reserved 4; - // All :ref:`Listeners ` are provided by a single + // All :ref:`Listeners ` are provided by a single // :ref:`LDS ` configuration source. core.v4alpha.ConfigSource lds_config = 1; @@ -77,7 +77,7 @@ message Bootstrap { // [#not-implemented-hide:] string lds_resources_locator = 5; - // All post-bootstrap :ref:`Cluster ` definitions are + // All post-bootstrap :ref:`Cluster ` definitions are // provided by a single :ref:`CDS ` // configuration source. core.v4alpha.ConfigSource cds_config = 2; @@ -88,10 +88,10 @@ message Bootstrap { // A single :ref:`ADS ` source may be optionally // specified. This must have :ref:`api_type - // ` :ref:`GRPC - // `. Only - // :ref:`ConfigSources ` that have - // the :ref:`ads ` field set will be + // ` :ref:`GRPC + // `. Only + // :ref:`ConfigSources ` that have + // the :ref:`ads ` field set will be // streamed on the ADS channel. core.v4alpha.ApiConfigSource ads_config = 3; } @@ -149,7 +149,7 @@ message Bootstrap { ClusterManager cluster_manager = 4; // Health discovery service config option. - // (:ref:`core.ApiConfigSource `) + // (:ref:`core.ApiConfigSource `) core.v4alpha.ApiConfigSource hds_config = 14; // Optional file system path to search for startup flag files. @@ -201,7 +201,7 @@ message Bootstrap { // Enable :ref:`stats for event dispatcher `, defaults to false. // Note that this records a value for each iteration of the event loop on every thread. This // should normally be minimal overhead, but when using - // :ref:`statsd `, it will send each observed value + // :ref:`statsd `, it will send each observed value // over the wire individually because the statsd protocol doesn't have any way to represent a // histogram summary. Be aware that this can be a very large volume of data. bool enable_dispatcher_stats = 16; @@ -219,13 +219,13 @@ message Bootstrap { // Optional proxy version which will be used to set the value of :ref:`server.version statistic // ` if specified. Envoy will not process this value, it will be sent as is to - // :ref:`stats sinks `. + // :ref:`stats sinks `. google.protobuf.UInt64Value stats_server_version_override = 19; // Always use TCP queries instead of UDP queries for DNS lookups. // This may be overridden on a per-cluster basis in cds_config, - // when :ref:`dns_resolvers ` and - // :ref:`use_tcp_for_dns_lookups ` are + // when :ref:`dns_resolvers ` and + // :ref:`use_tcp_for_dns_lookups ` are // specified. // Setting this value causes failure if the // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during @@ -271,7 +271,7 @@ message Bootstrap { // Global map of CertificateProvider instances. These instances are referred to by name in the // :ref:`CommonTlsContext.CertificateProviderInstance.instance_name - // ` + // ` // field. // [#not-implemented-hide:] map certificate_provider_instances = 25; @@ -326,9 +326,9 @@ message ClusterManager { // this configuration). In order to enable :ref:`zone aware routing // ` this option must be set. // If *local_cluster_name* is defined then :ref:`clusters - // ` must be defined in the :ref:`Bootstrap + // ` must be defined in the :ref:`Bootstrap // static cluster resources - // `. This is unrelated to + // `. This is unrelated to // the :option:`--service-cluster` option which does not `affect zone aware // routing `_. string local_cluster_name = 1; @@ -342,8 +342,8 @@ message ClusterManager { // A management server endpoint to stream load stats to via // *StreamLoadStats*. This must have :ref:`api_type - // ` :ref:`GRPC - // `. + // ` :ref:`GRPC + // `. core.v4alpha.ApiConfigSource load_stats_config = 4; } diff --git a/api/envoy/config/cluster/v3/circuit_breaker.proto b/api/envoy/config/cluster/v3/circuit_breaker.proto index 96e69701cda21..82cd329b91a72 100644 --- a/api/envoy/config/cluster/v3/circuit_breaker.proto +++ b/api/envoy/config/cluster/v3/circuit_breaker.proto @@ -25,7 +25,7 @@ message CircuitBreakers { "envoy.api.v2.cluster.CircuitBreakers"; // A Thresholds defines CircuitBreaker settings for a - // :ref:`RoutingPriority`. + // :ref:`RoutingPriority`. // [#next-free-field: 9] message Thresholds { option (udpa.annotations.versioning).previous_message_type = @@ -49,7 +49,7 @@ message CircuitBreakers { google.protobuf.UInt32Value min_retry_concurrency = 2; } - // The :ref:`RoutingPriority` + // The :ref:`RoutingPriority` // the specified CircuitBreaker settings apply to. core.v3.RoutingPriority priority = 1 [(validate.rules).enum = {defined_only: true}]; @@ -96,10 +96,10 @@ message CircuitBreakers { google.protobuf.UInt32Value max_connection_pools = 7; } - // If multiple :ref:`Thresholds` - // are defined with the same :ref:`RoutingPriority`, + // If multiple :ref:`Thresholds` + // are defined with the same :ref:`RoutingPriority`, // the first one in the list is used. If no Thresholds is defined for a given - // :ref:`RoutingPriority`, the default values + // :ref:`RoutingPriority`, the default values // are used. repeated Thresholds thresholds = 1; } diff --git a/api/envoy/config/cluster/v3/cluster.proto b/api/envoy/config/cluster/v3/cluster.proto index e72a0aa80e128..7012f4ef5c435 100644 --- a/api/envoy/config/cluster/v3/cluster.proto +++ b/api/envoy/config/cluster/v3/cluster.proto @@ -110,7 +110,7 @@ message Cluster { CLUSTER_PROVIDED = 6; // [#not-implemented-hide:] Use the new :ref:`load_balancing_policy - // ` field to determine the LB policy. + // ` field to determine the LB policy. // [#next-major-version: In the v3 API, we should consider deprecating the lb_policy field // and instead using the new load_balancing_policy field as the one and only mechanism for // configuring this.] @@ -123,8 +123,8 @@ message Cluster { // specified, the DNS resolver will first perform a lookup for addresses in // the IPv6 family and fallback to a lookup for addresses in the IPv4 family. // For cluster types other than - // :ref:`STRICT_DNS` and - // :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS` and + // :ref:`LOGICAL_DNS`, // this setting is // ignored. enum DnsLookupFamily { @@ -135,7 +135,7 @@ message Cluster { enum ClusterProtocolSelection { // Cluster can only operate on one of the possible upstream protocols (HTTP1.1, HTTP2). - // If :ref:`http2_protocol_options ` are + // If :ref:`http2_protocol_options ` are // present, HTTP2 will be used, otherwise HTTP1.1 will be used. USE_CONFIGURED_PROTOCOL = 0; @@ -233,7 +233,7 @@ message Cluster { // If KEYS_SUBSET is selected, subset selector matching is performed again with metadata // keys reduced to - // :ref:`fallback_keys_subset`. + // :ref:`fallback_keys_subset`. // It allows for a fallback to a different, less specific selector if some of the keys of // the selector are considered optional. KEYS_SUBSET = 4; @@ -262,30 +262,30 @@ message Cluster { [(validate.rules).enum = {defined_only: true}]; // Subset of - // :ref:`keys` used by - // :ref:`KEYS_SUBSET` + // :ref:`keys` used by + // :ref:`KEYS_SUBSET` // fallback policy. // It has to be a non empty list if KEYS_SUBSET fallback policy is selected. // For any other fallback policy the parameter is not used and should not be set. // Only values also present in - // :ref:`keys` are allowed, but + // :ref:`keys` are allowed, but // `fallback_keys_subset` cannot be equal to `keys`. repeated string fallback_keys_subset = 3; } // The behavior used when no endpoint subset matches the selected route's // metadata. The value defaults to - // :ref:`NO_FALLBACK`. + // :ref:`NO_FALLBACK`. LbSubsetFallbackPolicy fallback_policy = 1 [(validate.rules).enum = {defined_only: true}]; // Specifies the default subset of endpoints used during fallback if // fallback_policy is - // :ref:`DEFAULT_SUBSET`. + // :ref:`DEFAULT_SUBSET`. // Each field in default_subset is // compared to the matching LbEndpoint.Metadata under the *envoy.lb* // namespace. It is valid for no hosts to match, in which case the behavior // is the same as a fallback_policy of - // :ref:`NO_FALLBACK`. + // :ref:`NO_FALLBACK`. google.protobuf.Struct default_subset = 2; // For each entry, LbEndpoint.Metadata's @@ -393,16 +393,16 @@ message Cluster { // Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each // provided host) the better the request distribution will reflect the desired weights. Defaults // to 1024 entries, and limited to 8M entries. See also - // :ref:`maximum_ring_size`. + // :ref:`maximum_ring_size`. google.protobuf.UInt64Value minimum_ring_size = 1 [(validate.rules).uint64 = {lte: 8388608}]; // The hash function used to hash hosts onto the ketama ring. The value defaults to - // :ref:`XX_HASH`. + // :ref:`XX_HASH`. HashFunction hash_function = 3 [(validate.rules).enum = {defined_only: true}]; // Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered // to further constrain resource use. See also - // :ref:`minimum_ring_size`. + // :ref:`minimum_ring_size`. google.protobuf.UInt64Value maximum_ring_size = 4 [(validate.rules).uint64 = {lte: 8388608}]; } @@ -556,7 +556,7 @@ message Cluster { // Specifies the base interval between refreshes. This parameter is required and must be greater // than zero and less than - // :ref:`max_interval `. + // :ref:`max_interval `. google.protobuf.Duration base_interval = 1 [(validate.rules).duration = { required: true gt {nanos: 1000000} @@ -564,8 +564,8 @@ message Cluster { // Specifies the maximum interval between refreshes. This parameter is optional, but must be // greater than or equal to the - // :ref:`base_interval ` if set. The default - // is 10 times the :ref:`base_interval `. + // :ref:`base_interval ` if set. The default + // is 10 times the :ref:`base_interval `. google.protobuf.Duration max_interval = 2 [(validate.rules).duration = {gt {nanos: 1000000}}]; } @@ -629,9 +629,9 @@ message Cluster { // Configuration to use different transport sockets for different endpoints. // The entry of *envoy.transport_socket_match* in the - // :ref:`LbEndpoint.Metadata ` + // :ref:`LbEndpoint.Metadata ` // is used to match against the transport sockets as they appear in the list. The first - // :ref:`match ` is used. + // :ref:`match ` is used. // For example, with the following match // // .. code-block:: yaml @@ -651,7 +651,7 @@ message Cluster { // Connections to the endpoints whose metadata value under *envoy.transport_socket_match* // having "acceptMTLS"/"true" key/value pair use the "enableMTLS" socket configuration. // - // If a :ref:`socket match ` with empty match + // If a :ref:`socket match ` with empty match // criteria is provided, that always match any endpoint. For example, the "defaultToPlaintext" // socket match in case above. // @@ -673,7 +673,7 @@ message Cluster { // // This field can be used to specify custom transport socket configurations for health // checks by adding matching key/value pairs in a health check's - // :ref:`transport socket match criteria ` field. + // :ref:`transport socket match criteria ` field. // // [#comment:TODO(incfly): add a detailed architecture doc on intended usage.] repeated TransportSocketMatch transport_socket_matches = 43; @@ -681,7 +681,7 @@ message Cluster { // Supplies the name of the cluster which must be unique across all clusters. // The cluster name is used when emitting // :ref:`statistics ` if :ref:`alt_stat_name - // ` is not provided. + // ` is not provided. // Any ``:`` in the cluster name will be converted to ``_`` when emitting statistics. string name = 1 [(validate.rules).string = {min_len: 1}]; @@ -718,19 +718,19 @@ message Cluster { // The :ref:`load balancer type ` to use // when picking a host in the cluster. - // [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] + // [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] LbPolicy lb_policy = 6 [(validate.rules).enum = {defined_only: true not_in: 7}]; // Setting this is required for specifying members of - // :ref:`STATIC`, - // :ref:`STRICT_DNS` - // or :ref:`LOGICAL_DNS` clusters. + // :ref:`STATIC`, + // :ref:`STRICT_DNS` + // or :ref:`LOGICAL_DNS` clusters. // This field supersedes the *hosts* field in the v2 API. // // .. attention:: // // Setting this allows non-EDS cluster types to contain embedded EDS equivalent - // :ref:`endpoint assignments`. + // :ref:`endpoint assignments`. // endpoint.v3.ClusterLoadAssignment load_assignment = 33; @@ -752,12 +752,12 @@ message Cluster { // HTTP protocol options that are applied only to upstream HTTP connections. // These options apply to all HTTP versions. // This has been deprecated in favor of - // :ref:`upstream_http_protocol_options ` - // in the :ref:`http_protocol_options ` message. + // :ref:`upstream_http_protocol_options ` + // in the :ref:`http_protocol_options ` message. // upstream_http_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. // See :ref:`upstream_http_protocol_options - // ` + // ` // for example usage. core.v3.UpstreamHttpProtocolOptions upstream_http_protocol_options = 46 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -765,23 +765,23 @@ message Cluster { // Additional options when handling HTTP requests upstream. These options will be applicable to // both HTTP1 and HTTP2 requests. // This has been deprecated in favor of - // :ref:`common_http_protocol_options ` - // in the :ref:`http_protocol_options ` message. + // :ref:`common_http_protocol_options ` + // in the :ref:`http_protocol_options ` message. // common_http_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. // See :ref:`upstream_http_protocol_options - // ` + // ` // for example usage. core.v3.HttpProtocolOptions common_http_protocol_options = 29 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; // Additional options when handling HTTP1 requests. // This has been deprecated in favor of http_protocol_options fields in the in the - // :ref:`http_protocol_options ` message. + // :ref:`http_protocol_options ` message. // http_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. // See :ref:`upstream_http_protocol_options - // ` + // ` // for example usage. core.v3.Http1ProtocolOptions http_protocol_options = 13 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -793,11 +793,11 @@ message Cluster { // with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 // connections to happen over plain text. // This has been deprecated in favor of http2_protocol_options fields in the in the - // :ref:`http_protocol_options ` + // :ref:`http_protocol_options ` // message. http2_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. // See :ref:`upstream_http_protocol_options - // ` + // ` // for example usage. core.v3.Http2ProtocolOptions http2_protocol_options = 14 [ deprecated = true, @@ -813,24 +813,24 @@ message Cluster { map typed_extension_protocol_options = 36; // If the DNS refresh rate is specified and the cluster type is either - // :ref:`STRICT_DNS`, - // or :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, // this value is used as the cluster’s DNS refresh // rate. The value configured must be at least 1ms. If this setting is not specified, the // value defaults to 5000ms. For cluster types other than - // :ref:`STRICT_DNS` - // and :ref:`LOGICAL_DNS` + // :ref:`STRICT_DNS` + // and :ref:`LOGICAL_DNS` // this setting is ignored. google.protobuf.Duration dns_refresh_rate = 16 [(validate.rules).duration = {gt {nanos: 1000000}}]; // If the DNS failure refresh rate is specified and the cluster type is either - // :ref:`STRICT_DNS`, - // or :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, // this is used as the cluster’s DNS refresh rate when requests are failing. If this setting is // not specified, the failure refresh rate defaults to the DNS refresh rate. For cluster types - // other than :ref:`STRICT_DNS` and - // :ref:`LOGICAL_DNS` this setting is + // other than :ref:`STRICT_DNS` and + // :ref:`LOGICAL_DNS` this setting is // ignored. RefreshRate dns_failure_refresh_rate = 44; @@ -841,18 +841,18 @@ message Cluster { // The DNS IP address resolution policy. If this setting is not specified, the // value defaults to - // :ref:`AUTO`. + // :ref:`AUTO`. DnsLookupFamily dns_lookup_family = 17 [(validate.rules).enum = {defined_only: true}]; // If DNS resolvers are specified and the cluster type is either - // :ref:`STRICT_DNS`, - // or :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, // this value is used to specify the cluster’s dns resolvers. // If this setting is not specified, the value defaults to the default // resolver, which uses /etc/resolv.conf for configuration. For cluster types // other than - // :ref:`STRICT_DNS` - // and :ref:`LOGICAL_DNS` + // :ref:`STRICT_DNS` + // and :ref:`LOGICAL_DNS` // this setting is ignored. // Setting this value causes failure if the // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during @@ -872,7 +872,7 @@ message Cluster { OutlierDetection outlier_detection = 19; // The interval for removing stale hosts from a cluster type - // :ref:`ORIGINAL_DST`. + // :ref:`ORIGINAL_DST`. // Hosts are considered stale if they have not been used // as upstream destinations during this interval. New hosts are added // to original destination clusters on demand as new connections are @@ -882,7 +882,7 @@ message Cluster { // them remain open, saving the latency that would otherwise be spent // on opening new connections. If this setting is not specified, the // value defaults to 5000ms. For cluster types other than - // :ref:`ORIGINAL_DST` + // :ref:`ORIGINAL_DST` // this setting is ignored. google.protobuf.Duration cleanup_interval = 20 [(validate.rules).duration = {gt {}}]; @@ -896,9 +896,9 @@ message Cluster { // Optional configuration for the load balancing algorithm selected by // LbPolicy. Currently only - // :ref:`RING_HASH`, - // :ref:`MAGLEV` and - // :ref:`LEAST_REQUEST` + // :ref:`RING_HASH`, + // :ref:`MAGLEV` and + // :ref:`LEAST_REQUEST` // has additional configuration options. // Specifying ring_hash_lb_config or maglev_lb_config or least_request_lb_config without setting the corresponding // LbPolicy will generate an error at runtime. @@ -921,7 +921,7 @@ message Cluster { // Optional custom transport socket implementation to use for upstream connections. // To setup TLS, set a transport socket with name `tls` and - // :ref:`UpstreamTlsContexts ` in the `typed_config`. + // :ref:`UpstreamTlsContexts ` in the `typed_config`. // If no transport socket configuration is specified, new connections // will be set up with plaintext. core.v3.TransportSocket transport_socket = 24; @@ -936,9 +936,9 @@ message Cluster { // Determines how Envoy selects the protocol used to speak to upstream hosts. // This has been deprecated in favor of setting explicit protocol selection // in the :ref:`http_protocol_options - // ` message. + // ` message. // http_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. ClusterProtocolSelection protocol_selection = 26 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -971,8 +971,8 @@ message Cluster { repeated Filter filters = 40; // [#not-implemented-hide:] New mechanism for LB policy configuration. Used only if the - // :ref:`lb_policy` field has the value - // :ref:`LOAD_BALANCING_POLICY_CONFIG`. + // :ref:`lb_policy` field has the value + // :ref:`LOAD_BALANCING_POLICY_CONFIG`. LoadBalancingPolicy load_balancing_policy = 41; // [#not-implemented-hide:] @@ -1000,7 +1000,7 @@ message Cluster { // .. attention:: // // This field has been deprecated in favor of `timeout_budgets`, part of - // :ref:`track_cluster_stats `. + // :ref:`track_cluster_stats `. bool track_timeout_budgets = 47 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; diff --git a/api/envoy/config/cluster/v3/outlier_detection.proto b/api/envoy/config/cluster/v3/outlier_detection.proto index e69b446918543..b19e95db99b74 100644 --- a/api/envoy/config/cluster/v3/outlier_detection.proto +++ b/api/envoy/config/cluster/v3/outlier_detection.proto @@ -35,7 +35,7 @@ message OutlierDetection { // The base time that a host is ejected for. The real time is equal to the // base time multiplied by the number of times the host has been ejected and is - // capped by :ref:`max_ejection_time`. + // capped by :ref:`max_ejection_time`. // Defaults to 30000ms or 30s. google.protobuf.Duration base_ejection_time = 3 [(validate.rules).duration = {gt {}}]; @@ -87,16 +87,16 @@ message OutlierDetection { // Determines whether to distinguish local origin failures from external errors. If set to true // the following configuration parameters are taken into account: - // :ref:`consecutive_local_origin_failure`, - // :ref:`enforcing_consecutive_local_origin_failure` + // :ref:`consecutive_local_origin_failure`, + // :ref:`enforcing_consecutive_local_origin_failure` // and - // :ref:`enforcing_local_origin_success_rate`. + // :ref:`enforcing_local_origin_success_rate`. // Defaults to false. bool split_external_local_origin_errors = 12; // The number of consecutive locally originated failures before ejection // occurs. Defaults to 5. Parameter takes effect only when - // :ref:`split_external_local_origin_errors` + // :ref:`split_external_local_origin_errors` // is set to true. google.protobuf.UInt32Value consecutive_local_origin_failure = 13; @@ -104,7 +104,7 @@ message OutlierDetection { // is detected through consecutive locally originated failures. This setting can be // used to disable ejection or to ramp it up slowly. Defaults to 100. // Parameter takes effect only when - // :ref:`split_external_local_origin_errors` + // :ref:`split_external_local_origin_errors` // is set to true. google.protobuf.UInt32Value enforcing_consecutive_local_origin_failure = 14 [(validate.rules).uint32 = {lte: 100}]; @@ -113,7 +113,7 @@ message OutlierDetection { // is detected through success rate statistics for locally originated errors. // This setting can be used to disable ejection or to ramp it up slowly. Defaults to 100. // Parameter takes effect only when - // :ref:`split_external_local_origin_errors` + // :ref:`split_external_local_origin_errors` // is set to true. google.protobuf.UInt32Value enforcing_local_origin_success_rate = 15 [(validate.rules).uint32 = {lte: 100}]; @@ -150,8 +150,8 @@ message OutlierDetection { // this host. Defaults to 50. google.protobuf.UInt32Value failure_percentage_request_volume = 20; - // The maximum time that a host is ejected for. See :ref:`base_ejection_time` + // The maximum time that a host is ejected for. See :ref:`base_ejection_time` // for more information. If not specified, the default value (300000ms or 300s) or - // :ref:`base_ejection_time` value is applied, whatever is larger. + // :ref:`base_ejection_time` value is applied, whatever is larger. google.protobuf.Duration max_ejection_time = 21 [(validate.rules).duration = {gt {}}]; } diff --git a/api/envoy/config/cluster/v4alpha/circuit_breaker.proto b/api/envoy/config/cluster/v4alpha/circuit_breaker.proto index 57a263a70d2e1..36aebb8977800 100644 --- a/api/envoy/config/cluster/v4alpha/circuit_breaker.proto +++ b/api/envoy/config/cluster/v4alpha/circuit_breaker.proto @@ -25,7 +25,7 @@ message CircuitBreakers { "envoy.config.cluster.v3.CircuitBreakers"; // A Thresholds defines CircuitBreaker settings for a - // :ref:`RoutingPriority`. + // :ref:`RoutingPriority`. // [#next-free-field: 9] message Thresholds { option (udpa.annotations.versioning).previous_message_type = @@ -49,7 +49,7 @@ message CircuitBreakers { google.protobuf.UInt32Value min_retry_concurrency = 2; } - // The :ref:`RoutingPriority` + // The :ref:`RoutingPriority` // the specified CircuitBreaker settings apply to. core.v4alpha.RoutingPriority priority = 1 [(validate.rules).enum = {defined_only: true}]; @@ -96,10 +96,10 @@ message CircuitBreakers { google.protobuf.UInt32Value max_connection_pools = 7; } - // If multiple :ref:`Thresholds` - // are defined with the same :ref:`RoutingPriority`, + // If multiple :ref:`Thresholds` + // are defined with the same :ref:`RoutingPriority`, // the first one in the list is used. If no Thresholds is defined for a given - // :ref:`RoutingPriority`, the default values + // :ref:`RoutingPriority`, the default values // are used. repeated Thresholds thresholds = 1; } diff --git a/api/envoy/config/cluster/v4alpha/cluster.proto b/api/envoy/config/cluster/v4alpha/cluster.proto index 927bbddaba5e2..807b70a0ce8be 100644 --- a/api/envoy/config/cluster/v4alpha/cluster.proto +++ b/api/envoy/config/cluster/v4alpha/cluster.proto @@ -110,7 +110,7 @@ message Cluster { CLUSTER_PROVIDED = 6; // [#not-implemented-hide:] Use the new :ref:`load_balancing_policy - // ` field to determine the LB policy. + // ` field to determine the LB policy. // [#next-major-version: In the v3 API, we should consider deprecating the lb_policy field // and instead using the new load_balancing_policy field as the one and only mechanism for // configuring this.] @@ -123,8 +123,8 @@ message Cluster { // specified, the DNS resolver will first perform a lookup for addresses in // the IPv6 family and fallback to a lookup for addresses in the IPv4 family. // For cluster types other than - // :ref:`STRICT_DNS` and - // :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS` and + // :ref:`LOGICAL_DNS`, // this setting is // ignored. enum DnsLookupFamily { @@ -135,7 +135,7 @@ message Cluster { enum ClusterProtocolSelection { // Cluster can only operate on one of the possible upstream protocols (HTTP1.1, HTTP2). - // If :ref:`http2_protocol_options ` are + // If :ref:`http2_protocol_options ` are // present, HTTP2 will be used, otherwise HTTP1.1 will be used. USE_CONFIGURED_PROTOCOL = 0; @@ -233,7 +233,7 @@ message Cluster { // If KEYS_SUBSET is selected, subset selector matching is performed again with metadata // keys reduced to - // :ref:`fallback_keys_subset`. + // :ref:`fallback_keys_subset`. // It allows for a fallback to a different, less specific selector if some of the keys of // the selector are considered optional. KEYS_SUBSET = 4; @@ -262,30 +262,30 @@ message Cluster { [(validate.rules).enum = {defined_only: true}]; // Subset of - // :ref:`keys` used by - // :ref:`KEYS_SUBSET` + // :ref:`keys` used by + // :ref:`KEYS_SUBSET` // fallback policy. // It has to be a non empty list if KEYS_SUBSET fallback policy is selected. // For any other fallback policy the parameter is not used and should not be set. // Only values also present in - // :ref:`keys` are allowed, but + // :ref:`keys` are allowed, but // `fallback_keys_subset` cannot be equal to `keys`. repeated string fallback_keys_subset = 3; } // The behavior used when no endpoint subset matches the selected route's // metadata. The value defaults to - // :ref:`NO_FALLBACK`. + // :ref:`NO_FALLBACK`. LbSubsetFallbackPolicy fallback_policy = 1 [(validate.rules).enum = {defined_only: true}]; // Specifies the default subset of endpoints used during fallback if // fallback_policy is - // :ref:`DEFAULT_SUBSET`. + // :ref:`DEFAULT_SUBSET`. // Each field in default_subset is // compared to the matching LbEndpoint.Metadata under the *envoy.lb* // namespace. It is valid for no hosts to match, in which case the behavior // is the same as a fallback_policy of - // :ref:`NO_FALLBACK`. + // :ref:`NO_FALLBACK`. google.protobuf.Struct default_subset = 2; // For each entry, LbEndpoint.Metadata's @@ -393,16 +393,16 @@ message Cluster { // Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each // provided host) the better the request distribution will reflect the desired weights. Defaults // to 1024 entries, and limited to 8M entries. See also - // :ref:`maximum_ring_size`. + // :ref:`maximum_ring_size`. google.protobuf.UInt64Value minimum_ring_size = 1 [(validate.rules).uint64 = {lte: 8388608}]; // The hash function used to hash hosts onto the ketama ring. The value defaults to - // :ref:`XX_HASH`. + // :ref:`XX_HASH`. HashFunction hash_function = 3 [(validate.rules).enum = {defined_only: true}]; // Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered // to further constrain resource use. See also - // :ref:`minimum_ring_size`. + // :ref:`minimum_ring_size`. google.protobuf.UInt64Value maximum_ring_size = 4 [(validate.rules).uint64 = {lte: 8388608}]; } @@ -560,7 +560,7 @@ message Cluster { // Specifies the base interval between refreshes. This parameter is required and must be greater // than zero and less than - // :ref:`max_interval `. + // :ref:`max_interval `. google.protobuf.Duration base_interval = 1 [(validate.rules).duration = { required: true gt {nanos: 1000000} @@ -568,8 +568,8 @@ message Cluster { // Specifies the maximum interval between refreshes. This parameter is optional, but must be // greater than or equal to the - // :ref:`base_interval ` if set. The default - // is 10 times the :ref:`base_interval `. + // :ref:`base_interval ` if set. The default + // is 10 times the :ref:`base_interval `. google.protobuf.Duration max_interval = 2 [(validate.rules).duration = {gt {nanos: 1000000}}]; } @@ -638,9 +638,9 @@ message Cluster { // Configuration to use different transport sockets for different endpoints. // The entry of *envoy.transport_socket_match* in the - // :ref:`LbEndpoint.Metadata ` + // :ref:`LbEndpoint.Metadata ` // is used to match against the transport sockets as they appear in the list. The first - // :ref:`match ` is used. + // :ref:`match ` is used. // For example, with the following match // // .. code-block:: yaml @@ -660,7 +660,7 @@ message Cluster { // Connections to the endpoints whose metadata value under *envoy.transport_socket_match* // having "acceptMTLS"/"true" key/value pair use the "enableMTLS" socket configuration. // - // If a :ref:`socket match ` with empty match + // If a :ref:`socket match ` with empty match // criteria is provided, that always match any endpoint. For example, the "defaultToPlaintext" // socket match in case above. // @@ -682,7 +682,7 @@ message Cluster { // // This field can be used to specify custom transport socket configurations for health // checks by adding matching key/value pairs in a health check's - // :ref:`transport socket match criteria ` field. + // :ref:`transport socket match criteria ` field. // // [#comment:TODO(incfly): add a detailed architecture doc on intended usage.] repeated TransportSocketMatch transport_socket_matches = 43; @@ -690,7 +690,7 @@ message Cluster { // Supplies the name of the cluster which must be unique across all clusters. // The cluster name is used when emitting // :ref:`statistics ` if :ref:`alt_stat_name - // ` is not provided. + // ` is not provided. // Any ``:`` in the cluster name will be converted to ``_`` when emitting statistics. string name = 1 [(validate.rules).string = {min_len: 1}]; @@ -727,19 +727,19 @@ message Cluster { // The :ref:`load balancer type ` to use // when picking a host in the cluster. - // [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] + // [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] LbPolicy lb_policy = 6 [(validate.rules).enum = {defined_only: true not_in: 7}]; // Setting this is required for specifying members of - // :ref:`STATIC`, - // :ref:`STRICT_DNS` - // or :ref:`LOGICAL_DNS` clusters. + // :ref:`STATIC`, + // :ref:`STRICT_DNS` + // or :ref:`LOGICAL_DNS` clusters. // This field supersedes the *hosts* field in the v2 API. // // .. attention:: // // Setting this allows non-EDS cluster types to contain embedded EDS equivalent - // :ref:`endpoint assignments`. + // :ref:`endpoint assignments`. // endpoint.v3.ClusterLoadAssignment load_assignment = 33; @@ -766,24 +766,24 @@ message Cluster { map typed_extension_protocol_options = 36; // If the DNS refresh rate is specified and the cluster type is either - // :ref:`STRICT_DNS`, - // or :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, // this value is used as the cluster’s DNS refresh // rate. The value configured must be at least 1ms. If this setting is not specified, the // value defaults to 5000ms. For cluster types other than - // :ref:`STRICT_DNS` - // and :ref:`LOGICAL_DNS` + // :ref:`STRICT_DNS` + // and :ref:`LOGICAL_DNS` // this setting is ignored. google.protobuf.Duration dns_refresh_rate = 16 [(validate.rules).duration = {gt {nanos: 1000000}}]; // If the DNS failure refresh rate is specified and the cluster type is either - // :ref:`STRICT_DNS`, - // or :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, // this is used as the cluster’s DNS refresh rate when requests are failing. If this setting is // not specified, the failure refresh rate defaults to the DNS refresh rate. For cluster types - // other than :ref:`STRICT_DNS` and - // :ref:`LOGICAL_DNS` this setting is + // other than :ref:`STRICT_DNS` and + // :ref:`LOGICAL_DNS` this setting is // ignored. RefreshRate dns_failure_refresh_rate = 44; @@ -794,18 +794,18 @@ message Cluster { // The DNS IP address resolution policy. If this setting is not specified, the // value defaults to - // :ref:`AUTO`. + // :ref:`AUTO`. DnsLookupFamily dns_lookup_family = 17 [(validate.rules).enum = {defined_only: true}]; // If DNS resolvers are specified and the cluster type is either - // :ref:`STRICT_DNS`, - // or :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, // this value is used to specify the cluster’s dns resolvers. // If this setting is not specified, the value defaults to the default // resolver, which uses /etc/resolv.conf for configuration. For cluster types // other than - // :ref:`STRICT_DNS` - // and :ref:`LOGICAL_DNS` + // :ref:`STRICT_DNS` + // and :ref:`LOGICAL_DNS` // this setting is ignored. // Setting this value causes failure if the // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during @@ -825,7 +825,7 @@ message Cluster { OutlierDetection outlier_detection = 19; // The interval for removing stale hosts from a cluster type - // :ref:`ORIGINAL_DST`. + // :ref:`ORIGINAL_DST`. // Hosts are considered stale if they have not been used // as upstream destinations during this interval. New hosts are added // to original destination clusters on demand as new connections are @@ -835,7 +835,7 @@ message Cluster { // them remain open, saving the latency that would otherwise be spent // on opening new connections. If this setting is not specified, the // value defaults to 5000ms. For cluster types other than - // :ref:`ORIGINAL_DST` + // :ref:`ORIGINAL_DST` // this setting is ignored. google.protobuf.Duration cleanup_interval = 20 [(validate.rules).duration = {gt {}}]; @@ -849,9 +849,9 @@ message Cluster { // Optional configuration for the load balancing algorithm selected by // LbPolicy. Currently only - // :ref:`RING_HASH`, - // :ref:`MAGLEV` and - // :ref:`LEAST_REQUEST` + // :ref:`RING_HASH`, + // :ref:`MAGLEV` and + // :ref:`LEAST_REQUEST` // has additional configuration options. // Specifying ring_hash_lb_config or maglev_lb_config or least_request_lb_config without setting the corresponding // LbPolicy will generate an error at runtime. @@ -874,7 +874,7 @@ message Cluster { // Optional custom transport socket implementation to use for upstream connections. // To setup TLS, set a transport socket with name `tls` and - // :ref:`UpstreamTlsContexts ` in the `typed_config`. + // :ref:`UpstreamTlsContexts ` in the `typed_config`. // If no transport socket configuration is specified, new connections // will be set up with plaintext. core.v4alpha.TransportSocket transport_socket = 24; @@ -915,8 +915,8 @@ message Cluster { repeated Filter filters = 40; // [#not-implemented-hide:] New mechanism for LB policy configuration. Used only if the - // :ref:`lb_policy` field has the value - // :ref:`LOAD_BALANCING_POLICY_CONFIG`. + // :ref:`lb_policy` field has the value + // :ref:`LOAD_BALANCING_POLICY_CONFIG`. LoadBalancingPolicy load_balancing_policy = 41; // [#not-implemented-hide:] diff --git a/api/envoy/config/cluster/v4alpha/outlier_detection.proto b/api/envoy/config/cluster/v4alpha/outlier_detection.proto index d4e76b4f135ae..a64c4b42247fc 100644 --- a/api/envoy/config/cluster/v4alpha/outlier_detection.proto +++ b/api/envoy/config/cluster/v4alpha/outlier_detection.proto @@ -35,7 +35,7 @@ message OutlierDetection { // The base time that a host is ejected for. The real time is equal to the // base time multiplied by the number of times the host has been ejected and is - // capped by :ref:`max_ejection_time`. + // capped by :ref:`max_ejection_time`. // Defaults to 30000ms or 30s. google.protobuf.Duration base_ejection_time = 3 [(validate.rules).duration = {gt {}}]; @@ -87,16 +87,16 @@ message OutlierDetection { // Determines whether to distinguish local origin failures from external errors. If set to true // the following configuration parameters are taken into account: - // :ref:`consecutive_local_origin_failure`, - // :ref:`enforcing_consecutive_local_origin_failure` + // :ref:`consecutive_local_origin_failure`, + // :ref:`enforcing_consecutive_local_origin_failure` // and - // :ref:`enforcing_local_origin_success_rate`. + // :ref:`enforcing_local_origin_success_rate`. // Defaults to false. bool split_external_local_origin_errors = 12; // The number of consecutive locally originated failures before ejection // occurs. Defaults to 5. Parameter takes effect only when - // :ref:`split_external_local_origin_errors` + // :ref:`split_external_local_origin_errors` // is set to true. google.protobuf.UInt32Value consecutive_local_origin_failure = 13; @@ -104,7 +104,7 @@ message OutlierDetection { // is detected through consecutive locally originated failures. This setting can be // used to disable ejection or to ramp it up slowly. Defaults to 100. // Parameter takes effect only when - // :ref:`split_external_local_origin_errors` + // :ref:`split_external_local_origin_errors` // is set to true. google.protobuf.UInt32Value enforcing_consecutive_local_origin_failure = 14 [(validate.rules).uint32 = {lte: 100}]; @@ -113,7 +113,7 @@ message OutlierDetection { // is detected through success rate statistics for locally originated errors. // This setting can be used to disable ejection or to ramp it up slowly. Defaults to 100. // Parameter takes effect only when - // :ref:`split_external_local_origin_errors` + // :ref:`split_external_local_origin_errors` // is set to true. google.protobuf.UInt32Value enforcing_local_origin_success_rate = 15 [(validate.rules).uint32 = {lte: 100}]; @@ -150,8 +150,8 @@ message OutlierDetection { // this host. Defaults to 50. google.protobuf.UInt32Value failure_percentage_request_volume = 20; - // The maximum time that a host is ejected for. See :ref:`base_ejection_time` + // The maximum time that a host is ejected for. See :ref:`base_ejection_time` // for more information. If not specified, the default value (300000ms or 300s) or - // :ref:`base_ejection_time` value is applied, whatever is larger. + // :ref:`base_ejection_time` value is applied, whatever is larger. google.protobuf.Duration max_ejection_time = 21 [(validate.rules).duration = {gt {}}]; } diff --git a/api/envoy/config/core/v3/address.proto b/api/envoy/config/core/v3/address.proto index a6fc6690a351e..06876d5f8e41e 100644 --- a/api/envoy/config/core/v3/address.proto +++ b/api/envoy/config/core/v3/address.proto @@ -37,7 +37,7 @@ message EnvoyInternalAddress { oneof address_name_specifier { option (validate.required) = true; - // [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. + // [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. string server_listener_name = 1; } } @@ -57,13 +57,13 @@ message SocketAddress { // to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` // to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: // It is possible to distinguish a Listener address via the prefix/suffix matching - // in :ref:`FilterChainMatch `.] When used - // within an upstream :ref:`BindConfig `, the address + // in :ref:`FilterChainMatch `.] When used + // within an upstream :ref:`BindConfig `, the address // controls the source address of outbound connections. For :ref:`clusters - // `, the cluster type determines whether the + // `, the cluster type determines whether the // address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS // (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized - // via :ref:`resolver_name `. + // via :ref:`resolver_name `. string address = 2 [(validate.rules).string = {min_len: 1}]; oneof port_specifier { @@ -72,7 +72,7 @@ message SocketAddress { uint32 port_value = 3 [(validate.rules).uint32 = {lte: 65535}]; // This is only valid if :ref:`resolver_name - // ` is specified below and the + // ` is specified below and the // named resolver is capable of named port resolution. string named_port = 4; } @@ -117,7 +117,7 @@ message BindConfig { // Whether to set the *IP_FREEBIND* option when creating the socket. When this // flag is set to true, allows the :ref:`source_address - // ` to be an IP address + // ` to be an IP address // that is not configured on the system running Envoy. When this flag is set // to false, the option *IP_FREEBIND* is disabled on the socket. When this // flag is not set (default), the socket is not modified, i.e. the option is diff --git a/api/envoy/config/core/v3/backoff.proto b/api/envoy/config/core/v3/backoff.proto index 55b504e716577..3ffa97bb0299c 100644 --- a/api/envoy/config/core/v3/backoff.proto +++ b/api/envoy/config/core/v3/backoff.proto @@ -21,7 +21,7 @@ message BackoffStrategy { // The base interval to be used for the next back off computation. It should // be greater than zero and less than or equal to :ref:`max_interval - // `. + // `. google.protobuf.Duration base_interval = 1 [(validate.rules).duration = { required: true gte {nanos: 1000000} @@ -29,8 +29,8 @@ message BackoffStrategy { // Specifies the maximum interval between retries. This parameter is optional, // but must be greater than or equal to the :ref:`base_interval - // ` if set. The default + // ` if set. The default // is 10 times the :ref:`base_interval - // `. + // `. google.protobuf.Duration max_interval = 2 [(validate.rules).duration = {gt {}}]; } diff --git a/api/envoy/config/core/v3/base.proto b/api/envoy/config/core/v3/base.proto index f5e677caf959f..f75d918bfc656 100644 --- a/api/envoy/config/core/v3/base.proto +++ b/api/envoy/config/core/v3/base.proto @@ -69,12 +69,12 @@ enum TrafficDirection { message Locality { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.Locality"; - // Region this :ref:`zone ` belongs to. + // Region this :ref:`zone ` belongs to. string region = 1; // Defines the local service zone where Envoy is running. Though optional, it // should be set if discovery service routing is used and the discovery - // service exposes :ref:`zone data `, + // service exposes :ref:`zone data `, // either in this message or via :option:`--service-zone`. The meaning of zone // is context dependent, e.g. `Availability Zone (AZ) // `_ @@ -154,10 +154,10 @@ message Node { // optional, it should be set if any of the following features are used: // :ref:`statsd `, :ref:`health check cluster // verification - // `, - // :ref:`runtime override directory `, + // `, + // :ref:`runtime override directory `, // :ref:`user agent addition - // `, + // `, // :ref:`HTTP global rate limiting `, // :ref:`CDS `, and :ref:`HTTP tracing // `, either in this message or via @@ -352,7 +352,7 @@ message DataSource { message RetryPolicy { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.RetryPolicy"; - // Specifies parameters that control :ref:`retry backoff strategy `. + // Specifies parameters that control :ref:`retry backoff strategy `. // This parameter is optional, in which case the default base interval is 1000 milliseconds. The // default maximum interval is 10 times the base interval. BackoffStrategy retry_back_off = 1; @@ -393,7 +393,7 @@ message AsyncDataSource { } // Configuration for transport socket in :ref:`listeners ` and -// :ref:`clusters `. If the configuration is +// :ref:`clusters `. If the configuration is // empty, a default transport socket implementation and configuration will be // chosen based on the platform and existence of tls_context. message TransportSocket { @@ -420,7 +420,7 @@ message TransportSocket { // .. note:: // // Parsing of the runtime key's data is implemented such that it may be represented as a -// :ref:`FractionalPercent ` proto represented as JSON/YAML +// :ref:`FractionalPercent ` proto represented as JSON/YAML // and may also be represented as an integer with the assumption that the value is an integral // percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse // as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. diff --git a/api/envoy/config/core/v3/config_source.proto b/api/envoy/config/core/v3/config_source.proto index c83e9125c701e..43519c010b758 100644 --- a/api/envoy/config/core/v3/config_source.proto +++ b/api/envoy/config/core/v3/config_source.proto @@ -109,7 +109,7 @@ message ApiConfigSource { } // Aggregated Discovery Service (ADS) options. This is currently empty, but when -// set in :ref:`ConfigSource ` can be used to +// set in :ref:`ConfigSource ` can be used to // specify that ADS is to be used. message AggregatedConfigSource { option (udpa.annotations.versioning).previous_message_type = @@ -118,7 +118,7 @@ message AggregatedConfigSource { // [#not-implemented-hide:] // Self-referencing config source options. This is currently empty, but when -// set in :ref:`ConfigSource ` can be used to +// set in :ref:`ConfigSource ` can be used to // specify that other data can be obtained from the same server. message SelfConfigSource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.SelfConfigSource"; @@ -144,7 +144,7 @@ message RateLimitSettings { // Configuration for :ref:`listeners `, :ref:`clusters // `, :ref:`routes -// `, :ref:`endpoints +// `, :ref:`endpoints // ` etc. may either be sourced from the // filesystem or from an xDS API source. Filesystem configs are watched with // inotify for updates. @@ -162,7 +162,7 @@ message ConfigSource { option (validate.required) = true; // Path on the filesystem to source and watch for configuration updates. - // When sourcing configuration for :ref:`secret `, + // When sourcing configuration for :ref:`secret `, // the certificate and key files are also watched for updates. // // .. note:: @@ -186,7 +186,7 @@ message ConfigSource { // [#not-implemented-hide:] // When set, the client will access the resources from the same server it got the // ConfigSource from, although not necessarily from the same stream. This is similar to the - // :ref:`ads` field, except that the client may use a + // :ref:`ads` field, except that the client may use a // different stream to the same server. As a result, this field can be used for things // like LRS that cannot be sent on an ADS stream. It can also be used to link from (e.g.) // LDS to RDS on the same server without requiring the management server to know its name diff --git a/api/envoy/config/core/v3/grpc_service.proto b/api/envoy/config/core/v3/grpc_service.proto index 103c8b90f6349..a7f29c8f52988 100644 --- a/api/envoy/config/core/v3/grpc_service.proto +++ b/api/envoy/config/core/v3/grpc_service.proto @@ -23,7 +23,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: gRPC services] // gRPC service configuration. This is used by :ref:`ApiConfigSource -// ` and filter configurations. +// ` and filter configurations. // [#next-free-field: 6] message GrpcService { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.GrpcService"; @@ -33,8 +33,8 @@ message GrpcService { "envoy.api.v2.core.GrpcService.EnvoyGrpc"; // The name of the upstream gRPC cluster. SSL credentials will be supplied - // in the :ref:`Cluster ` :ref:`transport_socket - // `. + // in the :ref:`Cluster ` :ref:`transport_socket + // `. string cluster_name = 1 [(validate.rules).string = {min_len: 1}]; // The `:authority` header in the grpc request. If this field is not set, the authority header value will be `cluster_name`. @@ -230,7 +230,7 @@ message GrpcService { // The target URI when using the `Google C++ gRPC client // `_. SSL credentials will be supplied in - // :ref:`channel_credentials `. + // :ref:`channel_credentials `. string target_uri = 1 [(validate.rules).string = {min_len: 1}]; ChannelCredentials channel_credentials = 2; diff --git a/api/envoy/config/core/v3/health_check.proto b/api/envoy/config/core/v3/health_check.proto index 27710830536c8..304297e7c011c 100644 --- a/api/envoy/config/core/v3/health_check.proto +++ b/api/envoy/config/core/v3/health_check.proto @@ -85,7 +85,7 @@ message HealthCheck { // The value of the host header in the HTTP health check request. If // left empty (default value), the name of the cluster this health check is associated // with will be used. The host header can be customized for a specific endpoint by setting the - // :ref:`hostname ` field. + // :ref:`hostname ` field. string host = 1 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; // Specifies the HTTP path that will be requested during health checking. For example @@ -114,7 +114,7 @@ message HealthCheck { // Specifies a list of HTTP response statuses considered healthy. If provided, replaces default // 200-only policy - 200 must be included explicitly as needed. Ranges follow half-open - // semantics of :ref:`Int64Range `. The start and end of each + // semantics of :ref:`Int64Range `. The start and end of each // range are required. Only statuses in the range [100, 600) are allowed. repeated type.v3.Int64Range expected_statuses = 9; @@ -123,7 +123,7 @@ message HealthCheck { // An optional service name parameter which is used to validate the identity of // the health checked cluster using a :ref:`StringMatcher - // `. See the :ref:`architecture overview + // `. See the :ref:`architecture overview // ` for more information. type.matcher.v3.StringMatcher service_name_matcher = 11; } @@ -170,7 +170,7 @@ message HealthCheck { // The value of the :authority header in the gRPC health check request. If // left empty (default value), the name of the cluster this health check is associated // with will be used. The authority header can be customized for a specific endpoint by setting - // the :ref:`hostname ` field. + // the :ref:`hostname ` field. string authority = 2 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; } @@ -205,7 +205,7 @@ message HealthCheck { // Specifies the ALPN protocols for health check connections. This is useful if the // corresponding upstream is using ALPN-based :ref:`FilterChainMatch - // ` along with different protocols for health checks + // ` along with different protocols for health checks // versus data connections. If empty, no ALPN protocols will be set on health check connections. repeated string alpn_protocols = 1; } @@ -339,7 +339,7 @@ message HealthCheck { TlsOptions tls_options = 21; // Optional key/value pairs that will be used to match a transport socket from those specified in the cluster's - // :ref:`tranport socket matches `. + // :ref:`tranport socket matches `. // For example, the following match criteria // // .. code-block:: yaml @@ -347,7 +347,7 @@ message HealthCheck { // transport_socket_match_criteria: // useMTLS: true // - // Will match the following :ref:`cluster socket match ` + // Will match the following :ref:`cluster socket match ` // // .. code-block:: yaml // @@ -360,13 +360,13 @@ message HealthCheck { // config: { ... } # tls socket configuration // // If this field is set, then for health checks it will supersede an entry of *envoy.transport_socket* in the - // :ref:`LbEndpoint.Metadata `. + // :ref:`LbEndpoint.Metadata `. // This allows using different transport socket capabilities for health checking versus proxying to the // endpoint. // // If the key/values pairs specified do not match any - // :ref:`transport socket matches `, - // the cluster's :ref:`transport socket ` + // :ref:`transport socket matches `, + // the cluster's :ref:`transport socket ` // will be used for health check socket configuration. google.protobuf.Struct transport_socket_match_criteria = 23; } diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index 2c0ee96803dcf..fb7c86245c6c8 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -101,7 +101,7 @@ message HttpProtocolOptions { // idle timeout is reached the connection will be closed. If the connection is an HTTP/2 // downstream connection a drain sequence will occur prior to closing the connection, see // :ref:`drain_timeout - // `. + // `. // Note that request based timeouts mean that HTTP/2 PINGs will not keep the connection alive. // If not specified, this defaults to 1 hour. To disable idle timeouts explicitly set this to 0. // @@ -111,14 +111,14 @@ message HttpProtocolOptions { // // If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" // is configured, this timeout is scaled for downstream connections according to the value for - // :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. + // :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. google.protobuf.Duration idle_timeout = 1; // The maximum duration of a connection. The duration is defined as a period since a connection // was established. If not set, there is no max duration. When max_connection_duration is reached // the connection will be closed. Drain sequence will occur prior to closing the connection if // if's applicable. See :ref:`drain_timeout - // `. + // `. // Note: not implemented for upstream connections. google.protobuf.Duration max_connection_duration = 3; diff --git a/api/envoy/config/core/v4alpha/address.proto b/api/envoy/config/core/v4alpha/address.proto index 0adf459fbb50f..63d4d4a145075 100644 --- a/api/envoy/config/core/v4alpha/address.proto +++ b/api/envoy/config/core/v4alpha/address.proto @@ -40,7 +40,7 @@ message EnvoyInternalAddress { oneof address_name_specifier { option (validate.required) = true; - // [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. + // [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. string server_listener_name = 1; } } @@ -60,13 +60,13 @@ message SocketAddress { // to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` // to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: // It is possible to distinguish a Listener address via the prefix/suffix matching - // in :ref:`FilterChainMatch `.] When used - // within an upstream :ref:`BindConfig `, the address + // in :ref:`FilterChainMatch `.] When used + // within an upstream :ref:`BindConfig `, the address // controls the source address of outbound connections. For :ref:`clusters - // `, the cluster type determines whether the + // `, the cluster type determines whether the // address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS // (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized - // via :ref:`resolver_name `. + // via :ref:`resolver_name `. string address = 2 [(validate.rules).string = {min_len: 1}]; oneof port_specifier { @@ -75,7 +75,7 @@ message SocketAddress { uint32 port_value = 3 [(validate.rules).uint32 = {lte: 65535}]; // This is only valid if :ref:`resolver_name - // ` is specified below and the + // ` is specified below and the // named resolver is capable of named port resolution. string named_port = 4; } @@ -120,7 +120,7 @@ message BindConfig { // Whether to set the *IP_FREEBIND* option when creating the socket. When this // flag is set to true, allows the :ref:`source_address - // ` to be an IP address + // ` to be an IP address // that is not configured on the system running Envoy. When this flag is set // to false, the option *IP_FREEBIND* is disabled on the socket. When this // flag is not set (default), the socket is not modified, i.e. the option is diff --git a/api/envoy/config/core/v4alpha/backoff.proto b/api/envoy/config/core/v4alpha/backoff.proto index 07a2bdff175e9..266d57f84e74a 100644 --- a/api/envoy/config/core/v4alpha/backoff.proto +++ b/api/envoy/config/core/v4alpha/backoff.proto @@ -22,7 +22,7 @@ message BackoffStrategy { // The base interval to be used for the next back off computation. It should // be greater than zero and less than or equal to :ref:`max_interval - // `. + // `. google.protobuf.Duration base_interval = 1 [(validate.rules).duration = { required: true gte {nanos: 1000000} @@ -30,8 +30,8 @@ message BackoffStrategy { // Specifies the maximum interval between retries. This parameter is optional, // but must be greater than or equal to the :ref:`base_interval - // ` if set. The default + // ` if set. The default // is 10 times the :ref:`base_interval - // `. + // `. google.protobuf.Duration max_interval = 2 [(validate.rules).duration = {gt {}}]; } diff --git a/api/envoy/config/core/v4alpha/base.proto b/api/envoy/config/core/v4alpha/base.proto index f81eef3f4486b..1eb3c2c6e0834 100644 --- a/api/envoy/config/core/v4alpha/base.proto +++ b/api/envoy/config/core/v4alpha/base.proto @@ -66,12 +66,12 @@ enum TrafficDirection { message Locality { option (udpa.annotations.versioning).previous_message_type = "envoy.config.core.v3.Locality"; - // Region this :ref:`zone ` belongs to. + // Region this :ref:`zone ` belongs to. string region = 1; // Defines the local service zone where Envoy is running. Though optional, it // should be set if discovery service routing is used and the discovery - // service exposes :ref:`zone data `, + // service exposes :ref:`zone data `, // either in this message or via :option:`--service-zone`. The meaning of zone // is context dependent, e.g. `Availability Zone (AZ) // `_ @@ -151,10 +151,10 @@ message Node { // optional, it should be set if any of the following features are used: // :ref:`statsd `, :ref:`health check cluster // verification - // `, - // :ref:`runtime override directory `, + // `, + // :ref:`runtime override directory `, // :ref:`user agent addition - // `, + // `, // :ref:`HTTP global rate limiting `, // :ref:`CDS `, and :ref:`HTTP tracing // `, either in this message or via @@ -348,7 +348,7 @@ message DataSource { message RetryPolicy { option (udpa.annotations.versioning).previous_message_type = "envoy.config.core.v3.RetryPolicy"; - // Specifies parameters that control :ref:`retry backoff strategy `. + // Specifies parameters that control :ref:`retry backoff strategy `. // This parameter is optional, in which case the default base interval is 1000 milliseconds. The // default maximum interval is 10 times the base interval. BackoffStrategy retry_back_off = 1; @@ -390,7 +390,7 @@ message AsyncDataSource { } // Configuration for transport socket in :ref:`listeners ` and -// :ref:`clusters `. If the configuration is +// :ref:`clusters `. If the configuration is // empty, a default transport socket implementation and configuration will be // chosen based on the platform and existence of tls_context. message TransportSocket { @@ -418,7 +418,7 @@ message TransportSocket { // .. note:: // // Parsing of the runtime key's data is implemented such that it may be represented as a -// :ref:`FractionalPercent ` proto represented as JSON/YAML +// :ref:`FractionalPercent ` proto represented as JSON/YAML // and may also be represented as an integer with the assumption that the value is an integral // percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse // as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. diff --git a/api/envoy/config/core/v4alpha/config_source.proto b/api/envoy/config/core/v4alpha/config_source.proto index 3f761c7e6930a..54b4824315015 100644 --- a/api/envoy/config/core/v4alpha/config_source.proto +++ b/api/envoy/config/core/v4alpha/config_source.proto @@ -112,7 +112,7 @@ message ApiConfigSource { } // Aggregated Discovery Service (ADS) options. This is currently empty, but when -// set in :ref:`ConfigSource ` can be used to +// set in :ref:`ConfigSource ` can be used to // specify that ADS is to be used. message AggregatedConfigSource { option (udpa.annotations.versioning).previous_message_type = @@ -121,7 +121,7 @@ message AggregatedConfigSource { // [#not-implemented-hide:] // Self-referencing config source options. This is currently empty, but when -// set in :ref:`ConfigSource ` can be used to +// set in :ref:`ConfigSource ` can be used to // specify that other data can be obtained from the same server. message SelfConfigSource { option (udpa.annotations.versioning).previous_message_type = @@ -148,7 +148,7 @@ message RateLimitSettings { // Configuration for :ref:`listeners `, :ref:`clusters // `, :ref:`routes -// `, :ref:`endpoints +// `, :ref:`endpoints // ` etc. may either be sourced from the // filesystem or from an xDS API source. Filesystem configs are watched with // inotify for updates. @@ -166,7 +166,7 @@ message ConfigSource { option (validate.required) = true; // Path on the filesystem to source and watch for configuration updates. - // When sourcing configuration for :ref:`secret `, + // When sourcing configuration for :ref:`secret `, // the certificate and key files are also watched for updates. // // .. note:: @@ -190,7 +190,7 @@ message ConfigSource { // [#not-implemented-hide:] // When set, the client will access the resources from the same server it got the // ConfigSource from, although not necessarily from the same stream. This is similar to the - // :ref:`ads` field, except that the client may use a + // :ref:`ads` field, except that the client may use a // different stream to the same server. As a result, this field can be used for things // like LRS that cannot be sent on an ADS stream. It can also be used to link from (e.g.) // LDS to RDS on the same server without requiring the management server to know its name diff --git a/api/envoy/config/core/v4alpha/grpc_service.proto b/api/envoy/config/core/v4alpha/grpc_service.proto index c7c284f1bdcd8..973983386c2e8 100644 --- a/api/envoy/config/core/v4alpha/grpc_service.proto +++ b/api/envoy/config/core/v4alpha/grpc_service.proto @@ -23,7 +23,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: gRPC services] // gRPC service configuration. This is used by :ref:`ApiConfigSource -// ` and filter configurations. +// ` and filter configurations. // [#next-free-field: 6] message GrpcService { option (udpa.annotations.versioning).previous_message_type = "envoy.config.core.v3.GrpcService"; @@ -33,8 +33,8 @@ message GrpcService { "envoy.config.core.v3.GrpcService.EnvoyGrpc"; // The name of the upstream gRPC cluster. SSL credentials will be supplied - // in the :ref:`Cluster ` :ref:`transport_socket - // `. + // in the :ref:`Cluster ` :ref:`transport_socket + // `. string cluster_name = 1 [(validate.rules).string = {min_len: 1}]; // The `:authority` header in the grpc request. If this field is not set, the authority header value will be `cluster_name`. @@ -236,7 +236,7 @@ message GrpcService { // The target URI when using the `Google C++ gRPC client // `_. SSL credentials will be supplied in - // :ref:`channel_credentials `. + // :ref:`channel_credentials `. string target_uri = 1 [(validate.rules).string = {min_len: 1}]; ChannelCredentials channel_credentials = 2; diff --git a/api/envoy/config/core/v4alpha/health_check.proto b/api/envoy/config/core/v4alpha/health_check.proto index ddc7d65e00075..bf86f26e665e3 100644 --- a/api/envoy/config/core/v4alpha/health_check.proto +++ b/api/envoy/config/core/v4alpha/health_check.proto @@ -85,7 +85,7 @@ message HealthCheck { // The value of the host header in the HTTP health check request. If // left empty (default value), the name of the cluster this health check is associated // with will be used. The host header can be customized for a specific endpoint by setting the - // :ref:`hostname ` field. + // :ref:`hostname ` field. string host = 1 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; // Specifies the HTTP path that will be requested during health checking. For example @@ -114,7 +114,7 @@ message HealthCheck { // Specifies a list of HTTP response statuses considered healthy. If provided, replaces default // 200-only policy - 200 must be included explicitly as needed. Ranges follow half-open - // semantics of :ref:`Int64Range `. The start and end of each + // semantics of :ref:`Int64Range `. The start and end of each // range are required. Only statuses in the range [100, 600) are allowed. repeated type.v3.Int64Range expected_statuses = 9; @@ -123,7 +123,7 @@ message HealthCheck { // An optional service name parameter which is used to validate the identity of // the health checked cluster using a :ref:`StringMatcher - // `. See the :ref:`architecture overview + // `. See the :ref:`architecture overview // ` for more information. type.matcher.v4alpha.StringMatcher service_name_matcher = 11; } @@ -170,7 +170,7 @@ message HealthCheck { // The value of the :authority header in the gRPC health check request. If // left empty (default value), the name of the cluster this health check is associated // with will be used. The authority header can be customized for a specific endpoint by setting - // the :ref:`hostname ` field. + // the :ref:`hostname ` field. string authority = 2 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; } @@ -205,7 +205,7 @@ message HealthCheck { // Specifies the ALPN protocols for health check connections. This is useful if the // corresponding upstream is using ALPN-based :ref:`FilterChainMatch - // ` along with different protocols for health checks + // ` along with different protocols for health checks // versus data connections. If empty, no ALPN protocols will be set on health check connections. repeated string alpn_protocols = 1; } @@ -339,7 +339,7 @@ message HealthCheck { TlsOptions tls_options = 21; // Optional key/value pairs that will be used to match a transport socket from those specified in the cluster's - // :ref:`tranport socket matches `. + // :ref:`tranport socket matches `. // For example, the following match criteria // // .. code-block:: yaml @@ -347,7 +347,7 @@ message HealthCheck { // transport_socket_match_criteria: // useMTLS: true // - // Will match the following :ref:`cluster socket match ` + // Will match the following :ref:`cluster socket match ` // // .. code-block:: yaml // @@ -360,13 +360,13 @@ message HealthCheck { // config: { ... } # tls socket configuration // // If this field is set, then for health checks it will supersede an entry of *envoy.transport_socket* in the - // :ref:`LbEndpoint.Metadata `. + // :ref:`LbEndpoint.Metadata `. // This allows using different transport socket capabilities for health checking versus proxying to the // endpoint. // // If the key/values pairs specified do not match any - // :ref:`transport socket matches `, - // the cluster's :ref:`transport socket ` + // :ref:`transport socket matches `, + // the cluster's :ref:`transport socket ` // will be used for health check socket configuration. google.protobuf.Struct transport_socket_match_criteria = 23; } diff --git a/api/envoy/config/core/v4alpha/protocol.proto b/api/envoy/config/core/v4alpha/protocol.proto index 965596136e067..7d09eb016c9aa 100644 --- a/api/envoy/config/core/v4alpha/protocol.proto +++ b/api/envoy/config/core/v4alpha/protocol.proto @@ -103,7 +103,7 @@ message HttpProtocolOptions { // idle timeout is reached the connection will be closed. If the connection is an HTTP/2 // downstream connection a drain sequence will occur prior to closing the connection, see // :ref:`drain_timeout - // `. + // `. // Note that request based timeouts mean that HTTP/2 PINGs will not keep the connection alive. // If not specified, this defaults to 1 hour. To disable idle timeouts explicitly set this to 0. // @@ -113,14 +113,14 @@ message HttpProtocolOptions { // // If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" // is configured, this timeout is scaled for downstream connections according to the value for - // :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. + // :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. google.protobuf.Duration idle_timeout = 1; // The maximum duration of a connection. The duration is defined as a period since a connection // was established. If not set, there is no max duration. When max_connection_duration is reached // the connection will be closed. Drain sequence will occur prior to closing the connection if // if's applicable. See :ref:`drain_timeout - // `. + // `. // Note: not implemented for upstream connections. google.protobuf.Duration max_connection_duration = 3; diff --git a/api/envoy/config/endpoint/v3/endpoint.proto b/api/envoy/config/endpoint/v3/endpoint.proto index 2db0ebcd7cdf0..b22a644eeaeb7 100644 --- a/api/envoy/config/endpoint/v3/endpoint.proto +++ b/api/envoy/config/endpoint/v3/endpoint.proto @@ -101,9 +101,9 @@ message ClusterLoadAssignment { } // Name of the cluster. This will be the :ref:`service_name - // ` value if specified + // ` value if specified // in the cluster :ref:`EdsClusterConfig - // `. + // `. string cluster_name = 1 [(validate.rules).string = {min_len: 1}]; // List of endpoints to load balance to. diff --git a/api/envoy/config/endpoint/v3/endpoint_components.proto b/api/envoy/config/endpoint/v3/endpoint_components.proto index b880a38d1a3ea..0e10ac3b2fca7 100644 --- a/api/envoy/config/endpoint/v3/endpoint_components.proto +++ b/api/envoy/config/endpoint/v3/endpoint_components.proto @@ -37,8 +37,8 @@ message Endpoint { uint32 port_value = 1 [(validate.rules).uint32 = {lte: 65535}]; // By default, the host header for L7 health checks is controlled by cluster level configuration - // (see: :ref:`host ` and - // :ref:`authority `). Setting this + // (see: :ref:`host ` and + // :ref:`authority `). Setting this // to a non-empty value allows overriding the cluster level configuration for a specific // endpoint. string hostname = 2; @@ -50,7 +50,7 @@ message Endpoint { // // The form of host address depends on the given cluster type. For STATIC or EDS, // it is expected to be a direct IP address (or something resolvable by the - // specified :ref:`resolver ` + // specified :ref:`resolver ` // in the Address). For LOGICAL or STRICT DNS, it is expected to be hostname, // and will be resolved via DNS. core.v3.Address address = 1; @@ -67,7 +67,7 @@ message Endpoint { // The hostname associated with this endpoint. This hostname is not used for routing or address // resolution. If provided, it will be associated with the endpoint, and can be used for features // that require a hostname, like - // :ref:`auto_host_rewrite `. + // :ref:`auto_host_rewrite `. string hostname = 3; } @@ -92,7 +92,7 @@ message LbEndpoint { // name should be specified as *envoy.lb*. An example boolean key-value pair // is *canary*, providing the optional canary status of the upstream host. // This may be matched against in a route's - // :ref:`RouteAction ` metadata_match field + // :ref:`RouteAction ` metadata_match field // to subset the endpoints considered in cluster load balancing. core.v3.Metadata metadata = 3; diff --git a/api/envoy/config/endpoint/v3/load_report.proto b/api/envoy/config/endpoint/v3/load_report.proto index 7140ca05afc76..c114fa726622d 100644 --- a/api/envoy/config/endpoint/v3/load_report.proto +++ b/api/envoy/config/endpoint/v3/load_report.proto @@ -20,7 +20,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Load Report] // These are stats Envoy reports to the management server at a frequency defined by -// :ref:`LoadStatsResponse.load_reporting_interval`. +// :ref:`LoadStatsResponse.load_reporting_interval`. // Stats per upstream region/zone and optionally per subzone. // [#next-free-field: 9] message UpstreamLocalityStats { @@ -52,7 +52,7 @@ message UpstreamLocalityStats { // Endpoint granularity stats information for this locality. This information // is populated if the Server requests it by setting - // :ref:`LoadStatsResponse.report_endpoint_granularity`. + // :ref:`LoadStatsResponse.report_endpoint_granularity`. repeated UpstreamEndpointStats upstream_endpoint_stats = 7; // [#not-implemented-hide:] The priority of the endpoint group these metrics @@ -118,7 +118,7 @@ message EndpointLoadMetricStats { } // Per cluster load stats. Envoy reports these stats a management server in a -// :ref:`LoadStatsRequest` +// :ref:`LoadStatsRequest` // Next ID: 7 // [#next-free-field: 7] message ClusterStats { diff --git a/api/envoy/config/listener/v3/listener.proto b/api/envoy/config/listener/v3/listener.proto index 5461318ada01c..b5bda9562cee8 100644 --- a/api/envoy/config/listener/v3/listener.proto +++ b/api/envoy/config/listener/v3/listener.proto @@ -60,7 +60,7 @@ message Listener { // set use_original_dst parameter to true. Default is true. // // This is deprecated. Use :ref:`Listener.bind_to_port - // ` + // ` google.protobuf.BoolValue bind_to_port = 1; } @@ -111,8 +111,8 @@ message Listener { string stat_prefix = 28; // A list of filter chains to consider for this listener. The - // :ref:`FilterChain ` with the most specific - // :ref:`FilterChainMatch ` criteria is used on a + // :ref:`FilterChain ` with the most specific + // :ref:`FilterChainMatch ` criteria is used on a // connection. // // Example using SNI for filter chain selection can be found in the @@ -147,12 +147,12 @@ message Listener { // Listener filters have the opportunity to manipulate and augment the connection metadata that // is used in connection filter chain matching, for example. These filters are run before any in - // :ref:`filter_chains `. Order matters as the + // :ref:`filter_chains `. Order matters as the // filters are processed sequentially right after a socket has been accepted by the listener, and // before a connection is created. // UDP Listener filters can be specified when the protocol in the listener socket address in - // :ref:`protocol ` is :ref:`UDP - // `. + // :ref:`protocol ` is :ref:`UDP + // `. // UDP listeners currently support a single filter. repeated ListenerFilter listener_filters = 9; @@ -176,7 +176,7 @@ message Listener { // *iptables* *TPROXY* target, in which case the original source and destination addresses and // ports are preserved on accepted connections. This flag should be used in combination with // :ref:`an original_dst ` :ref:`listener filter - // ` to mark the connections' local addresses as + // ` to mark the connections' local addresses as // "restored." This can be used to hand off each redirected connection to another listener // associated with the connection's destination address. Direct connections to the socket without // using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are @@ -221,14 +221,14 @@ message Listener { core.v3.TrafficDirection traffic_direction = 16; // If the protocol in the listener socket address in :ref:`protocol - // ` is :ref:`UDP - // `, this field specifies UDP + // ` is :ref:`UDP + // `, this field specifies UDP // listener specific configuration. UdpListenerConfig udp_listener_config = 18; // Used to represent an API listener, which is used in non-proxy clients. The type of API // exposed to the non-proxy application depends on the type of API listener. - // When this field is set, no other field except for :ref:`name` + // When this field is set, no other field except for :ref:`name` // should be set. // // .. note:: @@ -249,8 +249,8 @@ message Listener { // worker threads. // // In the scenario that the listener X redirects all the connections to the listeners Y1 and Y2 - // by setting :ref:`use_original_dst ` in X - // and :ref:`bind_to_port ` to false in Y1 and Y2, + // by setting :ref:`use_original_dst ` in X + // and :ref:`bind_to_port ` to false in Y1 and Y2, // it is recommended to disable the balance config in listener X to avoid the cost of balancing, and // enable the balance config in Y1 and Y2 to balance the connections among the workers. ConnectionBalanceConfig connection_balance_config = 20; @@ -277,7 +277,7 @@ message Listener { // Whether the listener should bind to the port. A listener that doesn't // bind can only receive connections redirected from other listeners that set - // :ref:`use_original_dst ` + // :ref:`use_original_dst ` // to true. Default is true. google.protobuf.BoolValue bind_to_port = 26; @@ -289,16 +289,16 @@ message Listener { // Used to represent an internal listener which does not listen on OSI L4 address but can be used by the // :ref:`envoy cluster ` to create a user space connection to. // The internal listener acts as a tcp listener. It supports listener filters and network filter chains. - // The internal listener require :ref:`address ` has + // The internal listener require :ref:`address ` has // field `envoy_internal_address`. // // There are some limitations are derived from the implementation. The known limitations include // - // * :ref:`ConnectionBalanceConfig ` is not + // * :ref:`ConnectionBalanceConfig ` is not // allowed because both cluster connection and listener connection must be owned by the same dispatcher. - // * :ref:`tcp_backlog_size ` - // * :ref:`freebind ` - // * :ref:`transparent ` + // * :ref:`tcp_backlog_size ` + // * :ref:`freebind ` + // * :ref:`transparent ` // [#not-implemented-hide:] InternalListenerConfig internal_listener = 27; } diff --git a/api/envoy/config/listener/v3/listener_components.proto b/api/envoy/config/listener/v3/listener_components.proto index 55ffcd6490ece..e6d73b791c216 100644 --- a/api/envoy/config/listener/v3/listener_components.proto +++ b/api/envoy/config/listener/v3/listener_components.proto @@ -239,7 +239,7 @@ message FilterChain { // Optional custom transport socket implementation to use for downstream connections. // To setup TLS, set a transport socket with name `tls` and - // :ref:`DownstreamTlsContext ` in the `typed_config`. + // :ref:`DownstreamTlsContext ` in the `typed_config`. // If no transport socket configuration is specified, new connections // will be set up with plaintext. // [#extension-category: envoy.transport_sockets.downstream] @@ -345,7 +345,7 @@ message ListenerFilter { } // Optional match predicate used to disable the filter. The filter is enabled when this field is empty. - // See :ref:`ListenerFilterChainMatchPredicate ` + // See :ref:`ListenerFilterChainMatchPredicate ` // for further examples. ListenerFilterChainMatchPredicate filter_disabled = 4; } diff --git a/api/envoy/config/listener/v4alpha/listener.proto b/api/envoy/config/listener/v4alpha/listener.proto index e40dbf9058af9..b4c353db9f267 100644 --- a/api/envoy/config/listener/v4alpha/listener.proto +++ b/api/envoy/config/listener/v4alpha/listener.proto @@ -62,7 +62,7 @@ message Listener { // set use_original_dst parameter to true. Default is true. // // This is deprecated. Use :ref:`Listener.bind_to_port - // ` + // ` google.protobuf.BoolValue bind_to_port = 1; } @@ -117,8 +117,8 @@ message Listener { string stat_prefix = 28; // A list of filter chains to consider for this listener. The - // :ref:`FilterChain ` with the most specific - // :ref:`FilterChainMatch ` criteria is used on a + // :ref:`FilterChain ` with the most specific + // :ref:`FilterChainMatch ` criteria is used on a // connection. // // Example using SNI for filter chain selection can be found in the @@ -149,12 +149,12 @@ message Listener { // Listener filters have the opportunity to manipulate and augment the connection metadata that // is used in connection filter chain matching, for example. These filters are run before any in - // :ref:`filter_chains `. Order matters as the + // :ref:`filter_chains `. Order matters as the // filters are processed sequentially right after a socket has been accepted by the listener, and // before a connection is created. // UDP Listener filters can be specified when the protocol in the listener socket address in - // :ref:`protocol ` is :ref:`UDP - // `. + // :ref:`protocol ` is :ref:`UDP + // `. // UDP listeners currently support a single filter. repeated ListenerFilter listener_filters = 9; @@ -178,7 +178,7 @@ message Listener { // *iptables* *TPROXY* target, in which case the original source and destination addresses and // ports are preserved on accepted connections. This flag should be used in combination with // :ref:`an original_dst ` :ref:`listener filter - // ` to mark the connections' local addresses as + // ` to mark the connections' local addresses as // "restored." This can be used to hand off each redirected connection to another listener // associated with the connection's destination address. Direct connections to the socket without // using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are @@ -223,14 +223,14 @@ message Listener { core.v4alpha.TrafficDirection traffic_direction = 16; // If the protocol in the listener socket address in :ref:`protocol - // ` is :ref:`UDP - // `, this field specifies UDP + // ` is :ref:`UDP + // `, this field specifies UDP // listener specific configuration. UdpListenerConfig udp_listener_config = 18; // Used to represent an API listener, which is used in non-proxy clients. The type of API // exposed to the non-proxy application depends on the type of API listener. - // When this field is set, no other field except for :ref:`name` + // When this field is set, no other field except for :ref:`name` // should be set. // // .. note:: @@ -251,8 +251,8 @@ message Listener { // worker threads. // // In the scenario that the listener X redirects all the connections to the listeners Y1 and Y2 - // by setting :ref:`use_original_dst ` in X - // and :ref:`bind_to_port ` to false in Y1 and Y2, + // by setting :ref:`use_original_dst ` in X + // and :ref:`bind_to_port ` to false in Y1 and Y2, // it is recommended to disable the balance config in listener X to avoid the cost of balancing, and // enable the balance config in Y1 and Y2 to balance the connections among the workers. ConnectionBalanceConfig connection_balance_config = 20; @@ -279,7 +279,7 @@ message Listener { // Whether the listener should bind to the port. A listener that doesn't // bind can only receive connections redirected from other listeners that set - // :ref:`use_original_dst ` + // :ref:`use_original_dst ` // to true. Default is true. google.protobuf.BoolValue bind_to_port = 26; @@ -291,16 +291,16 @@ message Listener { // Used to represent an internal listener which does not listen on OSI L4 address but can be used by the // :ref:`envoy cluster ` to create a user space connection to. // The internal listener acts as a tcp listener. It supports listener filters and network filter chains. - // The internal listener require :ref:`address ` has + // The internal listener require :ref:`address ` has // field `envoy_internal_address`. // // There are some limitations are derived from the implementation. The known limitations include // - // * :ref:`ConnectionBalanceConfig ` is not + // * :ref:`ConnectionBalanceConfig ` is not // allowed because both cluster connection and listener connection must be owned by the same dispatcher. - // * :ref:`tcp_backlog_size ` - // * :ref:`freebind ` - // * :ref:`transparent ` + // * :ref:`tcp_backlog_size ` + // * :ref:`freebind ` + // * :ref:`transparent ` // [#not-implemented-hide:] InternalListenerConfig internal_listener = 27; } diff --git a/api/envoy/config/listener/v4alpha/listener_components.proto b/api/envoy/config/listener/v4alpha/listener_components.proto index 418129dc72c25..3a13391c0b78f 100644 --- a/api/envoy/config/listener/v4alpha/listener_components.proto +++ b/api/envoy/config/listener/v4alpha/listener_components.proto @@ -229,7 +229,7 @@ message FilterChain { // Optional custom transport socket implementation to use for downstream connections. // To setup TLS, set a transport socket with name `tls` and - // :ref:`DownstreamTlsContext ` in the `typed_config`. + // :ref:`DownstreamTlsContext ` in the `typed_config`. // If no transport socket configuration is specified, new connections // will be set up with plaintext. // [#extension-category: envoy.transport_sockets.downstream] @@ -335,7 +335,7 @@ message ListenerFilter { } // Optional match predicate used to disable the filter. The filter is enabled when this field is empty. - // See :ref:`ListenerFilterChainMatchPredicate ` + // See :ref:`ListenerFilterChainMatchPredicate ` // for further examples. ListenerFilterChainMatchPredicate filter_disabled = 4; } diff --git a/api/envoy/config/metrics/v3/metrics_service.proto b/api/envoy/config/metrics/v3/metrics_service.proto index e5fab870f8ee6..1cdd6d183e9db 100644 --- a/api/envoy/config/metrics/v3/metrics_service.proto +++ b/api/envoy/config/metrics/v3/metrics_service.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Metrics service] // Metrics Service is configured as a built-in *envoy.stat_sinks.metrics_service* :ref:`StatsSink -// `. This opaque configuration will be used to create +// `. This opaque configuration will be used to create // Metrics Service. // [#extension: envoy.stat_sinks.metrics_service] message MetricsServiceConfig { @@ -36,7 +36,7 @@ message MetricsServiceConfig { // If true, counters are reported as the delta between flushing intervals. Otherwise, the current // counter value is reported. Defaults to false. // Eventually (https://github.com/envoyproxy/envoy/issues/10968) if this value is not set, the - // sink will take updates from the :ref:`MetricsResponse `. + // sink will take updates from the :ref:`MetricsResponse `. google.protobuf.BoolValue report_counters_as_deltas = 2; // If true, metrics will have their tags emitted as labels on the metrics objects sent to the MetricsService, diff --git a/api/envoy/config/metrics/v3/stats.proto b/api/envoy/config/metrics/v3/stats.proto index 4893b5504ac18..d442cffe36acf 100644 --- a/api/envoy/config/metrics/v3/stats.proto +++ b/api/envoy/config/metrics/v3/stats.proto @@ -35,7 +35,7 @@ message StatsSink { string name = 1; // Stats sink specific configuration which depends on the sink being instantiated. See - // :ref:`StatsdSink ` for an example. + // :ref:`StatsdSink ` for an example. // [#extension-category: envoy.stats_sinks] oneof config_type { google.protobuf.Any typed_config = 3; @@ -49,13 +49,13 @@ message StatsConfig { // Each stat name is iteratively processed through these tag specifiers. // When a tag is matched, the first capture group is removed from the name so - // later :ref:`TagSpecifiers ` cannot match that + // later :ref:`TagSpecifiers ` cannot match that // same portion of the match. repeated TagSpecifier stats_tags = 1; // Use all default tag regexes specified in Envoy. These can be combined with // custom tags specified in :ref:`stats_tags - // `. They will be processed before + // `. They will be processed before // the custom tags. // // .. note:: @@ -117,7 +117,7 @@ message StatsMatcher { // However, StatsMatcher can be used to limit the creation of families of stats in order to // conserve memory. Stats can either be disabled entirely, or they can be // limited by either an exclusion or an inclusion list of :ref:`StringMatcher - // ` protos: + // ` protos: // // * If `reject_all` is set to `true`, no stats will be instantiated. If `reject_all` is set to // `false`, all stats will be instantiated. @@ -211,9 +211,9 @@ message TagSpecifier { // sink. Envoy has a set of default names and regexes to extract dynamic // portions of existing stats, which can be found in :repo:`well_known_names.h // ` in the Envoy repository. If a :ref:`tag_name - // ` is provided in the config and - // neither :ref:`regex ` or - // :ref:`fixed_value ` were specified, + // ` is provided in the config and + // neither :ref:`regex ` or + // :ref:`fixed_value ` were specified, // Envoy will attempt to find that name in its set of defaults and use the accompanying regex. // // .. note:: @@ -350,7 +350,7 @@ message StatsdSink { // Stats configuration proto schema for built-in *envoy.stat_sinks.dog_statsd* sink. // The sink emits stats with `DogStatsD `_ // compatible tags. Tags are configurable via :ref:`StatsConfig -// `. +// `. // [#extension: envoy.stat_sinks.dog_statsd] message DogStatsdSink { option (udpa.annotations.versioning).previous_message_type = @@ -367,7 +367,7 @@ message DogStatsdSink { } // Optional custom metric name prefix. See :ref:`StatsdSink's prefix field - // ` for more details. + // ` for more details. string prefix = 3; // Optional max datagram size to use when sending UDP messages. By default Envoy diff --git a/api/envoy/config/metrics/v4alpha/metrics_service.proto b/api/envoy/config/metrics/v4alpha/metrics_service.proto index 570bc2e9d7162..fe530b34e6908 100644 --- a/api/envoy/config/metrics/v4alpha/metrics_service.proto +++ b/api/envoy/config/metrics/v4alpha/metrics_service.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: Metrics service] // Metrics Service is configured as a built-in *envoy.stat_sinks.metrics_service* :ref:`StatsSink -// `. This opaque configuration will be used to create +// `. This opaque configuration will be used to create // Metrics Service. // [#extension: envoy.stat_sinks.metrics_service] message MetricsServiceConfig { @@ -36,7 +36,7 @@ message MetricsServiceConfig { // If true, counters are reported as the delta between flushing intervals. Otherwise, the current // counter value is reported. Defaults to false. // Eventually (https://github.com/envoyproxy/envoy/issues/10968) if this value is not set, the - // sink will take updates from the :ref:`MetricsResponse `. + // sink will take updates from the :ref:`MetricsResponse `. google.protobuf.BoolValue report_counters_as_deltas = 2; // If true, metrics will have their tags emitted as labels on the metrics objects sent to the MetricsService, diff --git a/api/envoy/config/metrics/v4alpha/stats.proto b/api/envoy/config/metrics/v4alpha/stats.proto index e3f10f1da4f61..6d8a94050d65a 100644 --- a/api/envoy/config/metrics/v4alpha/stats.proto +++ b/api/envoy/config/metrics/v4alpha/stats.proto @@ -35,7 +35,7 @@ message StatsSink { string name = 1; // Stats sink specific configuration which depends on the sink being instantiated. See - // :ref:`StatsdSink ` for an example. + // :ref:`StatsdSink ` for an example. // [#extension-category: envoy.stats_sinks] oneof config_type { google.protobuf.Any typed_config = 3; @@ -49,13 +49,13 @@ message StatsConfig { // Each stat name is iteratively processed through these tag specifiers. // When a tag is matched, the first capture group is removed from the name so - // later :ref:`TagSpecifiers ` cannot match that + // later :ref:`TagSpecifiers ` cannot match that // same portion of the match. repeated TagSpecifier stats_tags = 1; // Use all default tag regexes specified in Envoy. These can be combined with // custom tags specified in :ref:`stats_tags - // `. They will be processed before + // `. They will be processed before // the custom tags. // // .. note:: @@ -117,7 +117,7 @@ message StatsMatcher { // However, StatsMatcher can be used to limit the creation of families of stats in order to // conserve memory. Stats can either be disabled entirely, or they can be // limited by either an exclusion or an inclusion list of :ref:`StringMatcher - // ` protos: + // ` protos: // // * If `reject_all` is set to `true`, no stats will be instantiated. If `reject_all` is set to // `false`, all stats will be instantiated. @@ -211,9 +211,9 @@ message TagSpecifier { // sink. Envoy has a set of default names and regexes to extract dynamic // portions of existing stats, which can be found in :repo:`well_known_names.h // ` in the Envoy repository. If a :ref:`tag_name - // ` is provided in the config and - // neither :ref:`regex ` or - // :ref:`fixed_value ` were specified, + // ` is provided in the config and + // neither :ref:`regex ` or + // :ref:`fixed_value ` were specified, // Envoy will attempt to find that name in its set of defaults and use the accompanying regex. // // .. note:: @@ -353,7 +353,7 @@ message StatsdSink { // Stats configuration proto schema for built-in *envoy.stat_sinks.dog_statsd* sink. // The sink emits stats with `DogStatsD `_ // compatible tags. Tags are configurable via :ref:`StatsConfig -// `. +// `. // [#extension: envoy.stat_sinks.dog_statsd] message DogStatsdSink { option (udpa.annotations.versioning).previous_message_type = @@ -370,7 +370,7 @@ message DogStatsdSink { } // Optional custom metric name prefix. See :ref:`StatsdSink's prefix field - // ` for more details. + // ` for more details. string prefix = 3; // Optional max datagram size to use when sending UDP messages. By default Envoy diff --git a/api/envoy/config/rbac/v3/rbac.proto b/api/envoy/config/rbac/v3/rbac.proto index 11fc66ee0c168..3d9c9c2627edf 100644 --- a/api/envoy/config/rbac/v3/rbac.proto +++ b/api/envoy/config/rbac/v3/rbac.proto @@ -200,7 +200,7 @@ message Permission { // * If the :ref:`TLS Inspector ` // filter is not added, and if a `FilterChainMatch` is not defined for // the :ref:`server name - // `, + // `, // a TLS connection's requested SNI server name will be treated as if it // wasn't present. // @@ -265,7 +265,7 @@ message Principal { // A CIDR block that describes the downstream remote/origin address. // Note: This is always the physical peer even if the - // :ref:`remote_ip ` is + // :ref:`remote_ip ` is // inferred from for example the x-forwarder-for header, proxy protocol, // etc. core.v3.CidrRange direct_remote_ip = 10; @@ -273,7 +273,7 @@ message Principal { // A CIDR block that describes the downstream remote/origin address. // Note: This may not be the physical peer and could be different from the // :ref:`direct_remote_ip - // `. E.g, if the + // `. E.g, if the // remote ip is inferred from for example the x-forwarder-for header, proxy // protocol, etc. core.v3.CidrRange remote_ip = 11; diff --git a/api/envoy/config/rbac/v4alpha/rbac.proto b/api/envoy/config/rbac/v4alpha/rbac.proto index f54fc477eb833..e68aef9776050 100644 --- a/api/envoy/config/rbac/v4alpha/rbac.proto +++ b/api/envoy/config/rbac/v4alpha/rbac.proto @@ -198,7 +198,7 @@ message Permission { // * If the :ref:`TLS Inspector ` // filter is not added, and if a `FilterChainMatch` is not defined for // the :ref:`server name - // `, + // `, // a TLS connection's requested SNI server name will be treated as if it // wasn't present. // @@ -262,7 +262,7 @@ message Principal { // A CIDR block that describes the downstream remote/origin address. // Note: This is always the physical peer even if the - // :ref:`remote_ip ` is + // :ref:`remote_ip ` is // inferred from for example the x-forwarder-for header, proxy protocol, // etc. core.v4alpha.CidrRange direct_remote_ip = 10; @@ -270,7 +270,7 @@ message Principal { // A CIDR block that describes the downstream remote/origin address. // Note: This may not be the physical peer and could be different from the // :ref:`direct_remote_ip - // `. E.g, if the + // `. E.g, if the // remote ip is inferred from for example the x-forwarder-for header, proxy // protocol, etc. core.v4alpha.CidrRange remote_ip = 11; diff --git a/api/envoy/config/route/v3/route.proto b/api/envoy/config/route/v3/route.proto index 4588af78cb450..80956fdeb4e23 100644 --- a/api/envoy/config/route/v3/route.proto +++ b/api/envoy/config/route/v3/route.proto @@ -27,8 +27,8 @@ message RouteConfiguration { // The name of the route configuration. For example, it might match // :ref:`route_config_name - // ` in - // :ref:`envoy_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. + // ` in + // :ref:`envoy_v3_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. string name = 1; // An array of virtual hosts that make up the route table. @@ -52,8 +52,8 @@ message RouteConfiguration { // Specifies a list of HTTP headers that should be added to each response that // the connection manager encodes. Headers specified at this level are applied - // after headers from any enclosed :ref:`envoy_api_msg_config.route.v3.VirtualHost` or - // :ref:`envoy_api_msg_config.route.v3.RouteAction`. For more information, including details on + // after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + // :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption response_headers_to_add = 4 @@ -67,8 +67,8 @@ message RouteConfiguration { // Specifies a list of HTTP headers that should be added to each request // routed by the HTTP connection manager. Headers specified at this level are - // applied after headers from any enclosed :ref:`envoy_api_msg_config.route.v3.VirtualHost` or - // :ref:`envoy_api_msg_config.route.v3.RouteAction`. For more information, including details on + // applied after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + // :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption request_headers_to_add = 6 @@ -99,22 +99,22 @@ message RouteConfiguration { // route table will load and the router filter will return a 404 if the route // is selected at runtime. This setting defaults to true if the route table // is statically defined via the :ref:`route_config - // ` + // ` // option. This setting default to false if the route table is loaded dynamically via the // :ref:`rds - // ` + // ` // option. Users may wish to override the default behavior in certain cases (for example when // using CDS with a static route table). google.protobuf.BoolValue validate_clusters = 7; // The maximum bytes of the response :ref:`direct response body - // ` size. If not specified the default + // ` size. If not specified the default // is 4096. // // .. warning:: // // Envoy currently holds the content of :ref:`direct response body - // ` in memory. Be careful setting + // ` in memory. Be careful setting // this to be larger than the default 4KB, since the allocated memory for direct response body // is not subject to data plane buffering controls. // diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index 9532757cae487..58ac31d41515d 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -102,8 +102,8 @@ message VirtualHost { // Specifies a list of HTTP headers that should be added to each request // handled by this virtual host. Headers specified at this level are applied - // after headers from enclosed :ref:`envoy_api_msg_config.route.v3.Route` and before headers from the - // enclosing :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including + // after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + // enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption request_headers_to_add = 7 @@ -117,8 +117,8 @@ message VirtualHost { // Specifies a list of HTTP headers that should be added to each response // handled by this virtual host. Headers specified at this level are applied - // after headers from enclosed :ref:`envoy_api_msg_config.route.v3.Route` and before headers from the - // enclosing :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including + // after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + // enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption response_headers_to_add = 10 @@ -139,7 +139,7 @@ message VirtualHost { // specific; see the :ref:`HTTP filter documentation ` // for if and how it is utilized. // [#comment: An entry's value may be wrapped in a - // :ref:`FilterConfig` + // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 15; @@ -150,7 +150,7 @@ message VirtualHost { // will see the attempt count as perceived by the second Envoy. Defaults to false. // This header is unaffected by the // :ref:`suppress_envoy_headers - // ` flag. + // ` flag. // // [#next-major-version: rename to include_attempt_count_in_request.] bool include_request_attempt_count = 14; @@ -162,7 +162,7 @@ message VirtualHost { // will see the attempt count as perceived by the Envoy closest upstream from itself. Defaults to false. // This header is unaffected by the // :ref:`suppress_envoy_headers - // ` flag. + // ` flag. bool include_attempt_count_in_response = 19; // Indicates the retry policy for all routes in this virtual host. Note that setting a @@ -173,7 +173,7 @@ message VirtualHost { // [#not-implemented-hide:] // Specifies the configuration for retry policy extension. Note that setting a route level entry // will take precedence over this config and it'll be treated independently (e.g.: values are not - // inherited). :ref:`Retry policy ` should not be + // inherited). :ref:`Retry policy ` should not be // set if this field is used. google.protobuf.Any retry_policy_typed_config = 20; @@ -201,7 +201,7 @@ message FilterAction { // .. attention:: // // Envoy supports routing on HTTP method via :ref:`header matching -// `. +// `. // [#next-free-field: 19] message Route { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.Route"; @@ -258,14 +258,14 @@ message Route { // specific; see the :ref:`HTTP filter documentation ` for // if and how it is utilized. // [#comment: An entry's value may be wrapped in a - // :ref:`FilterConfig` + // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 13; // Specifies a set of headers that will be added to requests matching this // route. Headers specified at this level are applied before headers from the - // enclosing :ref:`envoy_api_msg_config.route.v3.VirtualHost` and - // :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + // enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption request_headers_to_add = 9 @@ -279,8 +279,8 @@ message Route { // Specifies a set of headers that will be added to responses to requests // matching this route. Headers specified at this level are applied before - // headers from the enclosing :ref:`envoy_api_msg_config.route.v3.VirtualHost` and - // :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including + // headers from the enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on // :ref:`custom request headers `. repeated core.v3.HeaderValueOption response_headers_to_add = 10 @@ -302,9 +302,9 @@ message Route { google.protobuf.UInt32Value per_request_buffer_limit_bytes = 16; } -// Compared to the :ref:`cluster ` field that specifies a +// Compared to the :ref:`cluster ` field that specifies a // single upstream cluster as the target of a request, the :ref:`weighted_clusters -// ` option allows for specification of +// ` option allows for specification of // multiple upstream clusters along with weights that indicate the percentage of // traffic to be forwarded to each cluster. The router selects an upstream cluster based on the // weights. @@ -325,7 +325,7 @@ message WeightedCluster { string name = 1 [(validate.rules).string = {min_len: 1}]; // An integer between 0 and :ref:`total_weight - // `. When a request matches the route, + // `. When a request matches the route, // the choice of an upstream cluster is determined by its weight. The sum of weights across all // entries in the clusters array must add up to the total_weight, which defaults to 100. google.protobuf.UInt32Value weight = 2; @@ -333,38 +333,38 @@ message WeightedCluster { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in // the upstream cluster with metadata matching what is set in this field will be considered for // load balancing. Note that this will be merged with what's provided in - // :ref:`RouteAction.metadata_match `, with + // :ref:`RouteAction.metadata_match `, with // values here taking precedence. The filter name should be specified as *envoy.lb*. core.v3.Metadata metadata_match = 3; // Specifies a list of headers to be added to requests when this cluster is selected - // through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + // through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. // Headers specified at this level are applied before headers from the enclosing - // :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.VirtualHost`, and - // :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + // :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption request_headers_to_add = 4 [(validate.rules).repeated = {max_items: 1000}]; // Specifies a list of HTTP headers that should be removed from each request when - // this cluster is selected through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + // this cluster is selected through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. repeated string request_headers_to_remove = 9 [(validate.rules).repeated = { items {string {well_known_regex: HTTP_HEADER_NAME strict: false}} }]; // Specifies a list of headers to be added to responses when this cluster is selected - // through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + // through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. // Headers specified at this level are applied before headers from the enclosing - // :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.VirtualHost`, and - // :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + // :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption response_headers_to_add = 5 [(validate.rules).repeated = {max_items: 1000}]; // Specifies a list of headers to be removed from responses when this cluster is selected - // through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + // through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. repeated string response_headers_to_remove = 6 [(validate.rules).repeated = { items {string {well_known_regex: HTTP_HEADER_NAME strict: false}} }]; @@ -375,7 +375,7 @@ message WeightedCluster { // specific; see the :ref:`HTTP filter documentation ` // for if and how it is utilized. // [#comment: An entry's value may be wrapped in a - // :ref:`FilterConfig` + // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 10; } @@ -546,7 +546,7 @@ message CorsPolicy { // If neither ``enabled``, ``filter_enabled``, nor ``shadow_enabled`` are specified, the CORS // filter will be enabled for 100% of the requests. // - // If :ref:`runtime_key ` is + // If :ref:`runtime_key ` is // specified, Envoy will lookup the runtime key to get the percentage of requests to filter. core.v3.RuntimeFractionalPercent filter_enabled = 9; } @@ -557,7 +557,7 @@ message CorsPolicy { // This field is intended to be used when ``filter_enabled`` and ``enabled`` are off. One of those // fields have to explicitly disable the filter in order for this setting to take effect. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate // and track the request's *Origin* to determine if it's valid but will not enforce any policies. core.v3.RuntimeFractionalPercent shadow_enabled = 10; @@ -748,7 +748,7 @@ message RouteAction { // This overrides any enabled/disabled upgrade filter chain specified in the // HttpConnectionManager // :ref:`upgrade_configs - // ` + // ` // but does not affect any custom filter chain specified there. message UpgradeConfig { option (udpa.annotations.versioning).previous_message_type = @@ -783,9 +783,9 @@ message RouteAction { message MaxStreamDuration { // Specifies the maximum duration allowed for streams on the route. If not specified, the value // from the :ref:`max_stream_duration - // ` field in + // ` field in // :ref:`HttpConnectionManager.common_http_protocol_options - // ` + // ` // is used. If this field is set explicitly to zero, any // HttpConnectionManager max_stream_duration timeout will be disabled for // this route. @@ -849,7 +849,7 @@ message RouteAction { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints // in the upstream cluster with metadata matching what's set in this field will be considered // for load balancing. If using :ref:`weighted_clusters - // `, metadata will be merged, with values + // `, metadata will be merged, with values // provided there taking precedence. The filter name should be specified as *envoy.lb*. core.v3.Metadata metadata_match = 4; @@ -860,16 +860,16 @@ message RouteAction { // ` header. // // Only one of *prefix_rewrite* or - // :ref:`regex_rewrite ` + // :ref:`regex_rewrite ` // may be specified. // // .. attention:: // // Pay careful attention to the use of trailing slashes in the - // :ref:`route's match ` prefix value. + // :ref:`route's match ` prefix value. // Stripping a prefix from a path requires multiple Routes to handle all cases. For example, // rewriting */prefix* to */* and */prefix/etc* to */etc* cannot be done in a single - // :ref:`Route `, as shown by the below config entries: + // :ref:`Route `, as shown by the below config entries: // // .. code-block:: yaml // @@ -896,7 +896,7 @@ message RouteAction { // before the rewrite into the :ref:`x-envoy-original-path // ` header. // - // Only one of :ref:`prefix_rewrite ` + // Only one of :ref:`prefix_rewrite ` // or *regex_rewrite* may be specified. // // Examples using Google's `RE2 `_ engine: @@ -978,14 +978,14 @@ message RouteAction { // Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, // although the connection manager wide :ref:`stream_idle_timeout - // ` + // ` // will still apply. A value of 0 will completely disable the route's idle timeout, even if a // connection manager stream idle timeout is configured. // // The idle timeout is distinct to :ref:`timeout - // `, which provides an upper bound + // `, which provides an upper bound // on the upstream response time; :ref:`idle_timeout - // ` instead bounds the amount + // ` instead bounds the amount // of time the request's stream may be idle. // // After header decoding, the idle timeout will apply on downstream and @@ -997,7 +997,7 @@ message RouteAction { // // If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" // is configured, this timeout is scaled according to the value for - // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. google.protobuf.Duration idle_timeout = 24; // Indicates that the route has a retry policy. Note that if this is set, @@ -1008,7 +1008,7 @@ message RouteAction { // [#not-implemented-hide:] // Specifies the configuration for retry policy extension. Note that if this is set, it'll take // precedence over the virtual host level retry policy entirely (e.g.: policies are not merged, - // most internal one becomes the enforced policy). :ref:`Retry policy ` + // most internal one becomes the enforced policy). :ref:`Retry policy ` // should not be set if this field is used. google.protobuf.Any retry_policy_typed_config = 33; @@ -1024,7 +1024,7 @@ message RouteAction { // Specifies if the rate limit filter should include the virtual host rate // limits. By default, if the route configured rate limits, the virtual host - // :ref:`rate_limits ` are not applied to the + // :ref:`rate_limits ` are not applied to the // request. // // This field is deprecated. Please use :ref:`vh_rate_limits ` @@ -1048,15 +1048,15 @@ message RouteAction { // Indicates that the route has a CORS policy. CorsPolicy cors = 17; - // Deprecated by :ref:`grpc_timeout_header_max ` + // Deprecated by :ref:`grpc_timeout_header_max ` // If present, and the request is a gRPC request, use the // `grpc-timeout header `_, // or its default value (infinity) instead of - // :ref:`timeout `, but limit the applied timeout + // :ref:`timeout `, but limit the applied timeout // to the maximum value specified here. If configured as 0, the maximum allowed timeout for // gRPC requests is infinity. If not configured at all, the `grpc-timeout` header is not used // and gRPC requests time out like any other requests using - // :ref:`timeout ` or its default. + // :ref:`timeout ` or its default. // This can be used to prevent unexpected upstream request timeouts due to potentially long // time gaps between gRPC request and response in gRPC streaming mode. // @@ -1071,7 +1071,7 @@ message RouteAction { google.protobuf.Duration max_grpc_timeout = 23 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; - // Deprecated by :ref:`grpc_timeout_header_offset `. + // Deprecated by :ref:`grpc_timeout_header_offset `. // If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting // the provided duration from the header. This is useful in allowing Envoy to set its global // timeout to be less than that of the deadline imposed by the calling client, which makes it more @@ -1087,7 +1087,7 @@ message RouteAction { // If present, Envoy will try to follow an upstream redirect response instead of proxying the // response back to the downstream. An upstream redirect response is defined // by :ref:`redirect_response_codes - // `. + // `. InternalRedirectPolicy internal_redirect_policy = 34; InternalRedirectAction internal_redirect_action = 26 @@ -1095,15 +1095,15 @@ message RouteAction { // An internal redirect is handled, iff the number of previous internal redirects that a // downstream request has encountered is lower than this value, and - // :ref:`internal_redirect_action ` + // :ref:`internal_redirect_action ` // is set to :ref:`HANDLE_INTERNAL_REDIRECT - // ` + // ` // In the case where a downstream request is bounced among multiple routes by internal redirect, // the first route that hits this threshold, or has - // :ref:`internal_redirect_action ` + // :ref:`internal_redirect_action ` // set to // :ref:`PASS_THROUGH_INTERNAL_REDIRECT - // ` + // ` // will pass the redirect back to downstream. // // If not specified, at most one redirect will be followed. @@ -1268,7 +1268,7 @@ message RetryPolicy { // .. note:: // // If left unspecified, Envoy will use the global - // :ref:`route timeout ` for the request. + // :ref:`route timeout ` for the request. // Consequently, when using a :ref:`5xx ` based // retry policy, a request that times out will not be retried as the total timeout budget // would have been exhausted. @@ -1343,7 +1343,7 @@ message HedgePolicy { // if there are no more retries left. // * After per-try timeout, an error response would be discarded, as a retry in the form of a hedged request is already in progress. // - // Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least + // Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least // one error code and specifies a maximum number of retries. // // Defaults to false. @@ -1418,7 +1418,7 @@ message RedirectAction { // .. attention:: // // Pay attention to the use of trailing slashes as mentioned in - // :ref:`RouteAction's prefix_rewrite `. + // :ref:`RouteAction's prefix_rewrite `. string prefix_rewrite = 5 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; @@ -1470,8 +1470,8 @@ message DirectResponseAction { // .. note:: // // Headers can be specified using *response_headers_to_add* in the enclosing - // :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` or - // :ref:`envoy_api_msg_config.route.v3.VirtualHost`. + // :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` or + // :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`. core.v3.DataSource body = 2; } @@ -1526,7 +1526,7 @@ message Tracing { // A list of custom tags with unique tag name to create tags for the active span. // It will take effect after merging with the :ref:`corresponding configuration - // ` + // ` // configured in the HTTP connection manager. If two tags with the same name are configured // each in the HTTP connection manager and the route level, the one configured here takes // priority. @@ -1597,14 +1597,14 @@ message RateLimit { // ("destination_cluster", "") // // Once a request matches against a route table rule, a routed cluster is determined by one of - // the following :ref:`route table configuration ` + // the following :ref:`route table configuration ` // settings: // - // * :ref:`cluster ` indicates the upstream cluster + // * :ref:`cluster ` indicates the upstream cluster // to route to. - // * :ref:`weighted_clusters ` + // * :ref:`weighted_clusters ` // chooses a cluster randomly from a set of clusters with attributed weight. - // * :ref:`cluster_header ` indicates which + // * :ref:`cluster_header ` indicates which // header in the request contains the target cluster. message DestinationCluster { option (udpa.annotations.versioning).previous_message_type = @@ -1698,7 +1698,7 @@ message RateLimit { // ("", "") // // .. attention:: - // This action has been deprecated in favor of the :ref:`metadata ` action + // This action has been deprecated in favor of the :ref:`metadata ` action message DynamicMetaData { // The key to use in the descriptor entry. string descriptor_key = 1 [(validate.rules).string = {min_len: 1}]; @@ -1722,7 +1722,7 @@ message RateLimit { // Query :ref:`dynamic metadata ` DYNAMIC = 0; - // Query :ref:`route entry metadata ` + // Query :ref:`route entry metadata ` ROUTE_ENTRY = 1; } @@ -1765,7 +1765,7 @@ message RateLimit { // Rate limit on dynamic metadata. // // .. attention:: - // This field has been deprecated in favor of the :ref:`metadata ` field + // This field has been deprecated in favor of the :ref:`metadata ` field DynamicMetaData dynamic_metadata = 7 [ deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0", @@ -1787,7 +1787,7 @@ message RateLimit { // Metadata struct that defines the key and path to retrieve the struct value. // The value must be a struct containing an integer "requests_per_unit" property // and a "unit" property with a value parseable to :ref:`RateLimitUnit - // enum ` + // enum ` type.metadata.v3.MetadataKey metadata_key = 1 [(validate.rules).message = {required: true}]; } @@ -1845,8 +1845,8 @@ message RateLimit { // // .. attention:: // In the absence of any header match specifier, match will default to :ref:`present_match -// `. i.e, a request that has the :ref:`name -// ` header will match, regardless of the header's +// `. i.e, a request that has the :ref:`name +// ` header will match, regardless of the header's // value. // // [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] @@ -1954,7 +1954,7 @@ message InternalRedirectPolicy { // downstream request has encountered is lower than this value. // In the case where a downstream request is bounced among multiple routes by internal redirect, // the first route that hits this threshold, or does not set :ref:`internal_redirect_policy - // ` + // ` // will pass the redirect back to downstream. // // If not specified, at most one redirect will be followed. @@ -1978,9 +1978,9 @@ message InternalRedirectPolicy { // A simple wrapper for an HTTP filter config. This is intended to be used as a wrapper for the // map value in -// :ref:`VirtualHost.typed_per_filter_config`, -// :ref:`Route.typed_per_filter_config`, -// or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` +// :ref:`VirtualHost.typed_per_filter_config`, +// :ref:`Route.typed_per_filter_config`, +// or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` // to add additional flags to the filter. // [#not-implemented-hide:] message FilterConfig { diff --git a/api/envoy/config/route/v3/scoped_route.proto b/api/envoy/config/route/v3/scoped_route.proto index b7e3aa66e07f2..eb47d7e10898d 100644 --- a/api/envoy/config/route/v3/scoped_route.proto +++ b/api/envoy/config/route/v3/scoped_route.proto @@ -15,13 +15,13 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // * Routing :ref:`architecture overview ` // Specifies a routing scope, which associates a -// :ref:`Key` to a -// :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` (identified by its resource name). +// :ref:`Key` to a +// :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` (identified by its resource name). // // The HTTP connection manager builds up a table consisting of these Key to // RouteConfiguration mappings, and looks up the RouteConfiguration to use per // request according to the algorithm specified in the -// :ref:`scope_key_builder` +// :ref:`scope_key_builder` // assigned to the HttpConnectionManager. // // For example, with the following configurations (in YAML): @@ -43,7 +43,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // key: vip // // ScopedRouteConfiguration resources (specified statically via -// :ref:`scoped_route_configurations_list` +// :ref:`scoped_route_configurations_list` // or obtained dynamically via SRDS): // // .. code:: @@ -78,7 +78,7 @@ message ScopedRouteConfiguration { "envoy.api.v2.ScopedRouteConfiguration"; // Specifies a key which is matched against the output of the - // :ref:`scope_key_builder` + // :ref:`scope_key_builder` // specified in the HttpConnectionManager. The matching is done per HTTP // request and is dependent on the order of the fragments contained in the // Key. @@ -100,7 +100,7 @@ message ScopedRouteConfiguration { // The ordered set of fragments to match against. The order must match the // fragments in the corresponding - // :ref:`scope_key_builder`. + // :ref:`scope_key_builder`. repeated Fragment fragments = 1 [(validate.rules).repeated = {min_items: 1}]; } @@ -110,8 +110,8 @@ message ScopedRouteConfiguration { // The name assigned to the routing scope. string name = 1 [(validate.rules).string = {min_len: 1}]; - // The resource name to use for a :ref:`envoy_api_msg_service.discovery.v3.DiscoveryRequest` to an - // RDS server to fetch the :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` associated + // The resource name to use for a :ref:`envoy_v3_api_msg_service.discovery.v3.DiscoveryRequest` to an + // RDS server to fetch the :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` associated // with this scope. string route_configuration_name = 2 [(validate.rules).string = {min_len: 1}]; diff --git a/api/envoy/config/route/v4alpha/route.proto b/api/envoy/config/route/v4alpha/route.proto index 1bddedd3fce6d..912fc8051556e 100644 --- a/api/envoy/config/route/v4alpha/route.proto +++ b/api/envoy/config/route/v4alpha/route.proto @@ -28,8 +28,8 @@ message RouteConfiguration { // The name of the route configuration. For example, it might match // :ref:`route_config_name - // ` in - // :ref:`envoy_api_msg_extensions.filters.network.http_connection_manager.v4alpha.Rds`. + // ` in + // :ref:`envoy_v3_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. string name = 1; // An array of virtual hosts that make up the route table. @@ -53,8 +53,8 @@ message RouteConfiguration { // Specifies a list of HTTP headers that should be added to each response that // the connection manager encodes. Headers specified at this level are applied - // after headers from any enclosed :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost` or - // :ref:`envoy_api_msg_config.route.v4alpha.RouteAction`. For more information, including details on + // after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + // :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption response_headers_to_add = 4 @@ -68,8 +68,8 @@ message RouteConfiguration { // Specifies a list of HTTP headers that should be added to each request // routed by the HTTP connection manager. Headers specified at this level are - // applied after headers from any enclosed :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost` or - // :ref:`envoy_api_msg_config.route.v4alpha.RouteAction`. For more information, including details on + // applied after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + // :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption request_headers_to_add = 6 @@ -100,22 +100,22 @@ message RouteConfiguration { // route table will load and the router filter will return a 404 if the route // is selected at runtime. This setting defaults to true if the route table // is statically defined via the :ref:`route_config - // ` + // ` // option. This setting default to false if the route table is loaded dynamically via the // :ref:`rds - // ` + // ` // option. Users may wish to override the default behavior in certain cases (for example when // using CDS with a static route table). google.protobuf.BoolValue validate_clusters = 7; // The maximum bytes of the response :ref:`direct response body - // ` size. If not specified the default + // ` size. If not specified the default // is 4096. // // .. warning:: // // Envoy currently holds the content of :ref:`direct response body - // ` in memory. Be careful setting + // ` in memory. Be careful setting // this to be larger than the default 4KB, since the allocated memory for direct response body // is not subject to data plane buffering controls. // diff --git a/api/envoy/config/route/v4alpha/route_components.proto b/api/envoy/config/route/v4alpha/route_components.proto index 1d694986ece6f..ce3b1c479969a 100644 --- a/api/envoy/config/route/v4alpha/route_components.proto +++ b/api/envoy/config/route/v4alpha/route_components.proto @@ -100,8 +100,8 @@ message VirtualHost { // Specifies a list of HTTP headers that should be added to each request // handled by this virtual host. Headers specified at this level are applied - // after headers from enclosed :ref:`envoy_api_msg_config.route.v4alpha.Route` and before headers from the - // enclosing :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration`. For more information, including + // after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + // enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption request_headers_to_add = 7 @@ -115,8 +115,8 @@ message VirtualHost { // Specifies a list of HTTP headers that should be added to each response // handled by this virtual host. Headers specified at this level are applied - // after headers from enclosed :ref:`envoy_api_msg_config.route.v4alpha.Route` and before headers from the - // enclosing :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration`. For more information, including + // after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + // enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption response_headers_to_add = 10 @@ -137,7 +137,7 @@ message VirtualHost { // specific; see the :ref:`HTTP filter documentation ` // for if and how it is utilized. // [#comment: An entry's value may be wrapped in a - // :ref:`FilterConfig` + // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 15; @@ -148,7 +148,7 @@ message VirtualHost { // will see the attempt count as perceived by the second Envoy. Defaults to false. // This header is unaffected by the // :ref:`suppress_envoy_headers - // ` flag. + // ` flag. // // [#next-major-version: rename to include_attempt_count_in_request.] bool include_request_attempt_count = 14; @@ -160,7 +160,7 @@ message VirtualHost { // will see the attempt count as perceived by the Envoy closest upstream from itself. Defaults to false. // This header is unaffected by the // :ref:`suppress_envoy_headers - // ` flag. + // ` flag. bool include_attempt_count_in_response = 19; // Indicates the retry policy for all routes in this virtual host. Note that setting a @@ -171,7 +171,7 @@ message VirtualHost { // [#not-implemented-hide:] // Specifies the configuration for retry policy extension. Note that setting a route level entry // will take precedence over this config and it'll be treated independently (e.g.: values are not - // inherited). :ref:`Retry policy ` should not be + // inherited). :ref:`Retry policy ` should not be // set if this field is used. google.protobuf.Any retry_policy_typed_config = 20; @@ -199,7 +199,7 @@ message FilterAction { // .. attention:: // // Envoy supports routing on HTTP method via :ref:`header matching -// `. +// `. // [#next-free-field: 19] message Route { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.Route"; @@ -256,14 +256,14 @@ message Route { // specific; see the :ref:`HTTP filter documentation ` for // if and how it is utilized. // [#comment: An entry's value may be wrapped in a - // :ref:`FilterConfig` + // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 13; // Specifies a set of headers that will be added to requests matching this // route. Headers specified at this level are applied before headers from the - // enclosing :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost` and - // :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration`. For more information, including details on + // enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption request_headers_to_add = 9 @@ -277,8 +277,8 @@ message Route { // Specifies a set of headers that will be added to responses to requests // matching this route. Headers specified at this level are applied before - // headers from the enclosing :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost` and - // :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration`. For more information, including + // headers from the enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on // :ref:`custom request headers `. repeated core.v4alpha.HeaderValueOption response_headers_to_add = 10 @@ -300,9 +300,9 @@ message Route { google.protobuf.UInt32Value per_request_buffer_limit_bytes = 16; } -// Compared to the :ref:`cluster ` field that specifies a +// Compared to the :ref:`cluster ` field that specifies a // single upstream cluster as the target of a request, the :ref:`weighted_clusters -// ` option allows for specification of +// ` option allows for specification of // multiple upstream clusters along with weights that indicate the percentage of // traffic to be forwarded to each cluster. The router selects an upstream cluster based on the // weights. @@ -324,7 +324,7 @@ message WeightedCluster { string name = 1 [(validate.rules).string = {min_len: 1}]; // An integer between 0 and :ref:`total_weight - // `. When a request matches the route, + // `. When a request matches the route, // the choice of an upstream cluster is determined by its weight. The sum of weights across all // entries in the clusters array must add up to the total_weight, which defaults to 100. google.protobuf.UInt32Value weight = 2; @@ -332,38 +332,38 @@ message WeightedCluster { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in // the upstream cluster with metadata matching what is set in this field will be considered for // load balancing. Note that this will be merged with what's provided in - // :ref:`RouteAction.metadata_match `, with + // :ref:`RouteAction.metadata_match `, with // values here taking precedence. The filter name should be specified as *envoy.lb*. core.v4alpha.Metadata metadata_match = 3; // Specifies a list of headers to be added to requests when this cluster is selected - // through the enclosing :ref:`envoy_api_msg_config.route.v4alpha.RouteAction`. + // through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. // Headers specified at this level are applied before headers from the enclosing - // :ref:`envoy_api_msg_config.route.v4alpha.Route`, :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost`, and - // :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration`. For more information, including details on + // :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption request_headers_to_add = 4 [(validate.rules).repeated = {max_items: 1000}]; // Specifies a list of HTTP headers that should be removed from each request when - // this cluster is selected through the enclosing :ref:`envoy_api_msg_config.route.v4alpha.RouteAction`. + // this cluster is selected through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. repeated string request_headers_to_remove = 9 [(validate.rules).repeated = { items {string {well_known_regex: HTTP_HEADER_NAME strict: false}} }]; // Specifies a list of headers to be added to responses when this cluster is selected - // through the enclosing :ref:`envoy_api_msg_config.route.v4alpha.RouteAction`. + // through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. // Headers specified at this level are applied before headers from the enclosing - // :ref:`envoy_api_msg_config.route.v4alpha.Route`, :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost`, and - // :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration`. For more information, including details on + // :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption response_headers_to_add = 5 [(validate.rules).repeated = {max_items: 1000}]; // Specifies a list of headers to be removed from responses when this cluster is selected - // through the enclosing :ref:`envoy_api_msg_config.route.v4alpha.RouteAction`. + // through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. repeated string response_headers_to_remove = 6 [(validate.rules).repeated = { items {string {well_known_regex: HTTP_HEADER_NAME strict: false}} }]; @@ -374,7 +374,7 @@ message WeightedCluster { // specific; see the :ref:`HTTP filter documentation ` // for if and how it is utilized. // [#comment: An entry's value may be wrapped in a - // :ref:`FilterConfig` + // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 10; } @@ -547,7 +547,7 @@ message CorsPolicy { // If neither ``enabled``, ``filter_enabled``, nor ``shadow_enabled`` are specified, the CORS // filter will be enabled for 100% of the requests. // - // If :ref:`runtime_key ` is + // If :ref:`runtime_key ` is // specified, Envoy will lookup the runtime key to get the percentage of requests to filter. core.v4alpha.RuntimeFractionalPercent filter_enabled = 9; } @@ -558,7 +558,7 @@ message CorsPolicy { // This field is intended to be used when ``filter_enabled`` and ``enabled`` are off. One of those // fields have to explicitly disable the filter in order for this setting to take effect. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate // and track the request's *Origin* to determine if it's valid but will not enforce any policies. core.v4alpha.RuntimeFractionalPercent shadow_enabled = 10; @@ -740,7 +740,7 @@ message RouteAction { // This overrides any enabled/disabled upgrade filter chain specified in the // HttpConnectionManager // :ref:`upgrade_configs - // ` + // ` // but does not affect any custom filter chain specified there. message UpgradeConfig { option (udpa.annotations.versioning).previous_message_type = @@ -781,9 +781,9 @@ message RouteAction { // Specifies the maximum duration allowed for streams on the route. If not specified, the value // from the :ref:`max_stream_duration - // ` field in + // ` field in // :ref:`HttpConnectionManager.common_http_protocol_options - // ` + // ` // is used. If this field is set explicitly to zero, any // HttpConnectionManager max_stream_duration timeout will be disabled for // this route. @@ -848,7 +848,7 @@ message RouteAction { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints // in the upstream cluster with metadata matching what's set in this field will be considered // for load balancing. If using :ref:`weighted_clusters - // `, metadata will be merged, with values + // `, metadata will be merged, with values // provided there taking precedence. The filter name should be specified as *envoy.lb*. core.v4alpha.Metadata metadata_match = 4; @@ -859,16 +859,16 @@ message RouteAction { // ` header. // // Only one of *prefix_rewrite* or - // :ref:`regex_rewrite ` + // :ref:`regex_rewrite ` // may be specified. // // .. attention:: // // Pay careful attention to the use of trailing slashes in the - // :ref:`route's match ` prefix value. + // :ref:`route's match ` prefix value. // Stripping a prefix from a path requires multiple Routes to handle all cases. For example, // rewriting */prefix* to */* and */prefix/etc* to */etc* cannot be done in a single - // :ref:`Route `, as shown by the below config entries: + // :ref:`Route `, as shown by the below config entries: // // .. code-block:: yaml // @@ -895,7 +895,7 @@ message RouteAction { // before the rewrite into the :ref:`x-envoy-original-path // ` header. // - // Only one of :ref:`prefix_rewrite ` + // Only one of :ref:`prefix_rewrite ` // or *regex_rewrite* may be specified. // // Examples using Google's `RE2 `_ engine: @@ -977,14 +977,14 @@ message RouteAction { // Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, // although the connection manager wide :ref:`stream_idle_timeout - // ` + // ` // will still apply. A value of 0 will completely disable the route's idle timeout, even if a // connection manager stream idle timeout is configured. // // The idle timeout is distinct to :ref:`timeout - // `, which provides an upper bound + // `, which provides an upper bound // on the upstream response time; :ref:`idle_timeout - // ` instead bounds the amount + // ` instead bounds the amount // of time the request's stream may be idle. // // After header decoding, the idle timeout will apply on downstream and @@ -996,7 +996,7 @@ message RouteAction { // // If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" // is configured, this timeout is scaled according to the value for - // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. google.protobuf.Duration idle_timeout = 24; // Indicates that the route has a retry policy. Note that if this is set, @@ -1007,7 +1007,7 @@ message RouteAction { // [#not-implemented-hide:] // Specifies the configuration for retry policy extension. Note that if this is set, it'll take // precedence over the virtual host level retry policy entirely (e.g.: policies are not merged, - // most internal one becomes the enforced policy). :ref:`Retry policy ` + // most internal one becomes the enforced policy). :ref:`Retry policy ` // should not be set if this field is used. google.protobuf.Any retry_policy_typed_config = 33; @@ -1043,7 +1043,7 @@ message RouteAction { // If present, Envoy will try to follow an upstream redirect response instead of proxying the // response back to the downstream. An upstream redirect response is defined // by :ref:`redirect_response_codes - // `. + // `. InternalRedirectPolicy internal_redirect_policy = 34; // Indicates that the route has a hedge policy. Note that if this is set, @@ -1209,7 +1209,7 @@ message RetryPolicy { // .. note:: // // If left unspecified, Envoy will use the global - // :ref:`route timeout ` for the request. + // :ref:`route timeout ` for the request. // Consequently, when using a :ref:`5xx ` based // retry policy, a request that times out will not be retried as the total timeout budget // would have been exhausted. @@ -1284,7 +1284,7 @@ message HedgePolicy { // if there are no more retries left. // * After per-try timeout, an error response would be discarded, as a retry in the form of a hedged request is already in progress. // - // Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least + // Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least // one error code and specifies a maximum number of retries. // // Defaults to false. @@ -1360,7 +1360,7 @@ message RedirectAction { // .. attention:: // // Pay attention to the use of trailing slashes as mentioned in - // :ref:`RouteAction's prefix_rewrite `. + // :ref:`RouteAction's prefix_rewrite `. string prefix_rewrite = 5 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; @@ -1412,8 +1412,8 @@ message DirectResponseAction { // .. note:: // // Headers can be specified using *response_headers_to_add* in the enclosing - // :ref:`envoy_api_msg_config.route.v4alpha.Route`, :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration` or - // :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost`. + // :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` or + // :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`. core.v4alpha.DataSource body = 2; } @@ -1470,7 +1470,7 @@ message Tracing { // A list of custom tags with unique tag name to create tags for the active span. // It will take effect after merging with the :ref:`corresponding configuration - // ` + // ` // configured in the HTTP connection manager. If two tags with the same name are configured // each in the HTTP connection manager and the route level, the one configured here takes // priority. @@ -1542,14 +1542,14 @@ message RateLimit { // ("destination_cluster", "") // // Once a request matches against a route table rule, a routed cluster is determined by one of - // the following :ref:`route table configuration ` + // the following :ref:`route table configuration ` // settings: // - // * :ref:`cluster ` indicates the upstream cluster + // * :ref:`cluster ` indicates the upstream cluster // to route to. - // * :ref:`weighted_clusters ` + // * :ref:`weighted_clusters ` // chooses a cluster randomly from a set of clusters with attributed weight. - // * :ref:`cluster_header ` indicates which + // * :ref:`cluster_header ` indicates which // header in the request contains the target cluster. message DestinationCluster { option (udpa.annotations.versioning).previous_message_type = @@ -1643,7 +1643,7 @@ message RateLimit { // ("", "") // // .. attention:: - // This action has been deprecated in favor of the :ref:`metadata ` action + // This action has been deprecated in favor of the :ref:`metadata ` action message DynamicMetaData { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RateLimit.Action.DynamicMetaData"; @@ -1673,7 +1673,7 @@ message RateLimit { // Query :ref:`dynamic metadata ` DYNAMIC = 0; - // Query :ref:`route entry metadata ` + // Query :ref:`route entry metadata ` ROUTE_ENTRY = 1; } @@ -1738,7 +1738,7 @@ message RateLimit { // Metadata struct that defines the key and path to retrieve the struct value. // The value must be a struct containing an integer "requests_per_unit" property // and a "unit" property with a value parseable to :ref:`RateLimitUnit - // enum ` + // enum ` type.metadata.v3.MetadataKey metadata_key = 1 [(validate.rules).message = {required: true}]; } @@ -1796,8 +1796,8 @@ message RateLimit { // // .. attention:: // In the absence of any header match specifier, match will default to :ref:`present_match -// `. i.e, a request that has the :ref:`name -// ` header will match, regardless of the header's +// `. i.e, a request that has the :ref:`name +// ` header will match, regardless of the header's // value. // // [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] @@ -1910,7 +1910,7 @@ message InternalRedirectPolicy { // downstream request has encountered is lower than this value. // In the case where a downstream request is bounced among multiple routes by internal redirect, // the first route that hits this threshold, or does not set :ref:`internal_redirect_policy - // ` + // ` // will pass the redirect back to downstream. // // If not specified, at most one redirect will be followed. @@ -1934,9 +1934,9 @@ message InternalRedirectPolicy { // A simple wrapper for an HTTP filter config. This is intended to be used as a wrapper for the // map value in -// :ref:`VirtualHost.typed_per_filter_config`, -// :ref:`Route.typed_per_filter_config`, -// or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` +// :ref:`VirtualHost.typed_per_filter_config`, +// :ref:`Route.typed_per_filter_config`, +// or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` // to add additional flags to the filter. // [#not-implemented-hide:] message FilterConfig { diff --git a/api/envoy/config/route/v4alpha/scoped_route.proto b/api/envoy/config/route/v4alpha/scoped_route.proto index 0704ceacbbac3..4c640223f701c 100644 --- a/api/envoy/config/route/v4alpha/scoped_route.proto +++ b/api/envoy/config/route/v4alpha/scoped_route.proto @@ -15,13 +15,13 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // * Routing :ref:`architecture overview ` // Specifies a routing scope, which associates a -// :ref:`Key` to a -// :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration` (identified by its resource name). +// :ref:`Key` to a +// :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` (identified by its resource name). // // The HTTP connection manager builds up a table consisting of these Key to // RouteConfiguration mappings, and looks up the RouteConfiguration to use per // request according to the algorithm specified in the -// :ref:`scope_key_builder` +// :ref:`scope_key_builder` // assigned to the HttpConnectionManager. // // For example, with the following configurations (in YAML): @@ -43,7 +43,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // key: vip // // ScopedRouteConfiguration resources (specified statically via -// :ref:`scoped_route_configurations_list` +// :ref:`scoped_route_configurations_list` // or obtained dynamically via SRDS): // // .. code:: @@ -78,7 +78,7 @@ message ScopedRouteConfiguration { "envoy.config.route.v3.ScopedRouteConfiguration"; // Specifies a key which is matched against the output of the - // :ref:`scope_key_builder` + // :ref:`scope_key_builder` // specified in the HttpConnectionManager. The matching is done per HTTP // request and is dependent on the order of the fragments contained in the // Key. @@ -100,7 +100,7 @@ message ScopedRouteConfiguration { // The ordered set of fragments to match against. The order must match the // fragments in the corresponding - // :ref:`scope_key_builder`. + // :ref:`scope_key_builder`. repeated Fragment fragments = 1 [(validate.rules).repeated = {min_items: 1}]; } @@ -110,8 +110,8 @@ message ScopedRouteConfiguration { // The name assigned to the routing scope. string name = 1 [(validate.rules).string = {min_len: 1}]; - // The resource name to use for a :ref:`envoy_api_msg_service.discovery.v4alpha.DiscoveryRequest` to an - // RDS server to fetch the :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration` associated + // The resource name to use for a :ref:`envoy_v3_api_msg_service.discovery.v3.DiscoveryRequest` to an + // RDS server to fetch the :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` associated // with this scope. string route_configuration_name = 2 [(validate.rules).string = {min_len: 1}]; diff --git a/api/envoy/config/tap/v3/common.proto b/api/envoy/config/tap/v3/common.proto index 42189d2aa4256..c25a2af5a3b51 100644 --- a/api/envoy/config/tap/v3/common.proto +++ b/api/envoy/config/tap/v3/common.proto @@ -30,17 +30,17 @@ message TapConfig { // The match configuration. If the configuration matches the data source being tapped, a tap will // occur, with the result written to the configured output. - // Exactly one of :ref:`match ` and - // :ref:`match_config ` must be set. If both - // are set, the :ref:`match ` will be used. + // Exactly one of :ref:`match ` and + // :ref:`match_config ` must be set. If both + // are set, the :ref:`match ` will be used. MatchPredicate match_config = 1 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; // The match configuration. If the configuration matches the data source being tapped, a tap will // occur, with the result written to the configured output. - // Exactly one of :ref:`match ` and - // :ref:`match_config ` must be set. If both - // are set, the :ref:`match ` will be used. + // Exactly one of :ref:`match ` and + // :ref:`match_config ` must be set. If both + // are set, the :ref:`match ` will be used. common.matcher.v3.MatchPredicate match = 4; // The tap output configuration. If a match configuration matches a data source being tapped, @@ -54,7 +54,7 @@ message TapConfig { // .. note:: // // This field defaults to 100/:ref:`HUNDRED - // `. + // `. core.v3.RuntimeFractionalPercent tap_enabled = 3; } @@ -161,19 +161,19 @@ message OutputConfig { // For buffered tapping, the maximum amount of received body that will be buffered prior to // truncation. If truncation occurs, the :ref:`truncated - // ` field will be set. If not specified, the + // ` field will be set. If not specified, the // default is 1KiB. google.protobuf.UInt32Value max_buffered_rx_bytes = 2; // For buffered tapping, the maximum amount of transmitted body that will be buffered prior to // truncation. If truncation occurs, the :ref:`truncated - // ` field will be set. If not specified, the + // ` field will be set. If not specified, the // default is 1KiB. google.protobuf.UInt32Value max_buffered_tx_bytes = 3; // Indicates whether taps produce a single buffered message per tap, or multiple streamed // messages per tap in the emitted :ref:`TraceWrapper - // ` messages. Note that streamed tapping does not + // ` messages. Note that streamed tapping does not // mean that no buffering takes place. Buffering may be required if data is processed before a // match can be determined. See the HTTP tap filter :ref:`streaming // ` documentation for more information. @@ -186,20 +186,20 @@ message OutputSink { "envoy.service.tap.v2alpha.OutputSink"; // Output format. All output is in the form of one or more :ref:`TraceWrapper - // ` messages. This enumeration indicates + // ` messages. This enumeration indicates // how those messages are written. Note that not all sinks support all output formats. See // individual sink documentation for more information. enum Format { - // Each message will be written as JSON. Any :ref:`body ` + // Each message will be written as JSON. Any :ref:`body ` // data will be present in the :ref:`as_bytes - // ` field. This means that body data will be + // ` field. This means that body data will be // base64 encoded as per the `proto3 JSON mappings // `_. JSON_BODY_AS_BYTES = 0; - // Each message will be written as JSON. Any :ref:`body ` + // Each message will be written as JSON. Any :ref:`body ` // data will be present in the :ref:`as_string - // ` field. This means that body data will be + // ` field. This means that body data will be // string encoded as per the `proto3 JSON mappings // `_. This format type is // useful when it is known that that body is human readable (e.g., JSON over HTTP) and the diff --git a/api/envoy/config/tap/v4alpha/common.proto b/api/envoy/config/tap/v4alpha/common.proto index fbee12d7f99d8..a425329be4e9b 100644 --- a/api/envoy/config/tap/v4alpha/common.proto +++ b/api/envoy/config/tap/v4alpha/common.proto @@ -32,9 +32,9 @@ message TapConfig { // The match configuration. If the configuration matches the data source being tapped, a tap will // occur, with the result written to the configured output. - // Exactly one of :ref:`match ` and - // :ref:`match_config ` must be set. If both - // are set, the :ref:`match ` will be used. + // Exactly one of :ref:`match ` and + // :ref:`match_config ` must be set. If both + // are set, the :ref:`match ` will be used. common.matcher.v4alpha.MatchPredicate match = 4; // The tap output configuration. If a match configuration matches a data source being tapped, @@ -48,7 +48,7 @@ message TapConfig { // .. note:: // // This field defaults to 100/:ref:`HUNDRED - // `. + // `. core.v4alpha.RuntimeFractionalPercent tap_enabled = 3; } @@ -159,19 +159,19 @@ message OutputConfig { // For buffered tapping, the maximum amount of received body that will be buffered prior to // truncation. If truncation occurs, the :ref:`truncated - // ` field will be set. If not specified, the + // ` field will be set. If not specified, the // default is 1KiB. google.protobuf.UInt32Value max_buffered_rx_bytes = 2; // For buffered tapping, the maximum amount of transmitted body that will be buffered prior to // truncation. If truncation occurs, the :ref:`truncated - // ` field will be set. If not specified, the + // ` field will be set. If not specified, the // default is 1KiB. google.protobuf.UInt32Value max_buffered_tx_bytes = 3; // Indicates whether taps produce a single buffered message per tap, or multiple streamed // messages per tap in the emitted :ref:`TraceWrapper - // ` messages. Note that streamed tapping does not + // ` messages. Note that streamed tapping does not // mean that no buffering takes place. Buffering may be required if data is processed before a // match can be determined. See the HTTP tap filter :ref:`streaming // ` documentation for more information. @@ -183,20 +183,20 @@ message OutputSink { option (udpa.annotations.versioning).previous_message_type = "envoy.config.tap.v3.OutputSink"; // Output format. All output is in the form of one or more :ref:`TraceWrapper - // ` messages. This enumeration indicates + // ` messages. This enumeration indicates // how those messages are written. Note that not all sinks support all output formats. See // individual sink documentation for more information. enum Format { - // Each message will be written as JSON. Any :ref:`body ` + // Each message will be written as JSON. Any :ref:`body ` // data will be present in the :ref:`as_bytes - // ` field. This means that body data will be + // ` field. This means that body data will be // base64 encoded as per the `proto3 JSON mappings // `_. JSON_BODY_AS_BYTES = 0; - // Each message will be written as JSON. Any :ref:`body ` + // Each message will be written as JSON. Any :ref:`body ` // data will be present in the :ref:`as_string - // ` field. This means that body data will be + // ` field. This means that body data will be // string encoded as per the `proto3 JSON mappings // `_. This format type is // useful when it is known that that body is human readable (e.g., JSON over HTTP) and the diff --git a/api/envoy/config/trace/v3/http_tracer.proto b/api/envoy/config/trace/v3/http_tracer.proto index 8a3e452db5644..d3c59a8cbb00c 100644 --- a/api/envoy/config/trace/v3/http_tracer.proto +++ b/api/envoy/config/trace/v3/http_tracer.proto @@ -24,15 +24,15 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // .. attention:: // // Use of this message type has been deprecated in favor of direct use of -// :ref:`Tracing.Http `. +// :ref:`Tracing.Http `. message Tracing { option (udpa.annotations.versioning).previous_message_type = "envoy.config.trace.v2.Tracing"; // Configuration for an HTTP tracer provider used by Envoy. // // The configuration is defined by the - // :ref:`HttpConnectionManager.Tracing ` - // :ref:`provider ` + // :ref:`HttpConnectionManager.Tracing ` + // :ref:`provider ` // field. message Http { option (udpa.annotations.versioning).previous_message_type = diff --git a/api/envoy/config/trace/v4alpha/http_tracer.proto b/api/envoy/config/trace/v4alpha/http_tracer.proto index 3e896902c99f2..33c8e73d56b9d 100644 --- a/api/envoy/config/trace/v4alpha/http_tracer.proto +++ b/api/envoy/config/trace/v4alpha/http_tracer.proto @@ -24,15 +24,15 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // .. attention:: // // Use of this message type has been deprecated in favor of direct use of -// :ref:`Tracing.Http `. +// :ref:`Tracing.Http `. message Tracing { option (udpa.annotations.versioning).previous_message_type = "envoy.config.trace.v3.Tracing"; // Configuration for an HTTP tracer provider used by Envoy. // // The configuration is defined by the - // :ref:`HttpConnectionManager.Tracing ` - // :ref:`provider ` + // :ref:`HttpConnectionManager.Tracing ` + // :ref:`provider ` // field. message Http { option (udpa.annotations.versioning).previous_message_type = diff --git a/api/envoy/data/cluster/v3/outlier_detection_event.proto b/api/envoy/data/cluster/v3/outlier_detection_event.proto index f87cd1582b090..2ba29d89954bb 100644 --- a/api/envoy/data/cluster/v3/outlier_detection_event.proto +++ b/api/envoy/data/cluster/v3/outlier_detection_event.proto @@ -21,7 +21,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; enum OutlierEjectionType { // In case upstream host returns certain number of consecutive 5xx. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *false*, all type of errors are treated as HTTP 5xx errors. // See :ref:`Cluster outlier detection ` documentation for // details. @@ -34,7 +34,7 @@ enum OutlierEjectionType { // and selects hosts for which ratio of successful replies deviates from other hosts // in the cluster. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *false*, all errors (externally and locally generated) are used to calculate success rate // statistics. See :ref:`Cluster outlier detection ` // documentation for details. @@ -42,7 +42,7 @@ enum OutlierEjectionType { // Consecutive local origin failures: Connection failures, resets, timeouts, etc // This type of ejection happens only when - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is set to *true*. // See :ref:`Cluster outlier detection ` documentation for CONSECUTIVE_LOCAL_ORIGIN_FAILURE = 3; @@ -50,7 +50,7 @@ enum OutlierEjectionType { // Runs over aggregated success rate statistics for local origin failures // for all hosts in the cluster and selects hosts for which success rate deviates from other // hosts in the cluster. This type of ejection happens only when - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is set to *true*. // See :ref:`Cluster outlier detection ` documentation for SUCCESS_RATE_LOCAL_ORIGIN = 4; @@ -87,7 +87,7 @@ message OutlierDetectionEvent { // The time in seconds since the last action (either an ejection or unejection) took place. google.protobuf.UInt64Value secs_since_last_action = 3; - // The :ref:`cluster ` that owns the ejected host. + // The :ref:`cluster ` that owns the ejected host. string cluster_name = 4 [(validate.rules).string = {min_len: 1}]; // The URL of the ejected host. E.g., ``tcp://1.2.3.4:80``. diff --git a/api/envoy/data/core/v3/health_check_event.proto b/api/envoy/data/core/v3/health_check_event.proto index 66624938dc4e9..92e2d68d255da 100644 --- a/api/envoy/data/core/v3/health_check_event.proto +++ b/api/envoy/data/core/v3/health_check_event.proto @@ -79,7 +79,7 @@ message HealthCheckAddHealthy { "envoy.data.core.v2alpha.HealthCheckAddHealthy"; // Whether this addition is the result of the first ever health check on a host, in which case - // the configured :ref:`healthy threshold ` + // the configured :ref:`healthy threshold ` // is bypassed and the host is immediately added. bool first_check = 1; } diff --git a/api/envoy/data/tap/v3/common.proto b/api/envoy/data/tap/v3/common.proto index 861da12e20c1b..2c4fb9c61a555 100644 --- a/api/envoy/data/tap/v3/common.proto +++ b/api/envoy/data/tap/v3/common.proto @@ -23,15 +23,15 @@ message Body { bytes as_bytes = 1; // Body data as string. This field is only used when the :ref:`JSON_BODY_AS_STRING - // ` sink + // ` sink // format type is selected. See the documentation for that option for why this is useful. string as_string = 2; } // Specifies whether body data has been truncated to fit within the specified // :ref:`max_buffered_rx_bytes - // ` and + // ` and // :ref:`max_buffered_tx_bytes - // ` settings. + // ` settings. bool truncated = 3; } diff --git a/api/envoy/data/tap/v3/transport.proto b/api/envoy/data/tap/v3/transport.proto index f596759cb4907..0ff4b7da06043 100644 --- a/api/envoy/data/tap/v3/transport.proto +++ b/api/envoy/data/tap/v3/transport.proto @@ -95,11 +95,11 @@ message SocketBufferedTrace { repeated SocketEvent events = 3; // Set to true if read events were truncated due to the :ref:`max_buffered_rx_bytes - // ` setting. + // ` setting. bool read_truncated = 4; // Set to true if write events were truncated due to the :ref:`max_buffered_tx_bytes - // ` setting. + // ` setting. bool write_truncated = 5; } diff --git a/api/envoy/extensions/access_loggers/file/v3/file.proto b/api/envoy/extensions/access_loggers/file/v3/file.proto index d44a658276a52..bca7c913a65b5 100644 --- a/api/envoy/extensions/access_loggers/file/v3/file.proto +++ b/api/envoy/extensions/access_loggers/file/v3/file.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: File access log] // [#extension: envoy.access_loggers.file] -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that writes log entries directly to a file. Configures the built-in *envoy.access_loggers.file* // AccessLog. // [#next-free-field: 6] diff --git a/api/envoy/extensions/access_loggers/file/v4alpha/file.proto b/api/envoy/extensions/access_loggers/file/v4alpha/file.proto index 03d138585d232..0597b11680598 100644 --- a/api/envoy/extensions/access_loggers/file/v4alpha/file.proto +++ b/api/envoy/extensions/access_loggers/file/v4alpha/file.proto @@ -18,7 +18,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: File access log] // [#extension: envoy.access_loggers.file] -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that writes log entries directly to a file. Configures the built-in *envoy.access_loggers.file* // AccessLog. // [#next-free-field: 6] diff --git a/api/envoy/extensions/access_loggers/grpc/v3/als.proto b/api/envoy/extensions/access_loggers/grpc/v3/als.proto index 968dfbeec0162..fa0a9f0f820d5 100644 --- a/api/envoy/extensions/access_loggers/grpc/v3/als.proto +++ b/api/envoy/extensions/access_loggers/grpc/v3/als.proto @@ -20,9 +20,9 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: gRPC Access Log Service (ALS)] // Configuration for the built-in *envoy.access_loggers.http_grpc* -// :ref:`AccessLog `. This configuration will +// :ref:`AccessLog `. This configuration will // populate :ref:`StreamAccessLogsMessage.http_logs -// `. +// `. // [#extension: envoy.access_loggers.http_grpc] message HttpGrpcAccessLogConfig { option (udpa.annotations.versioning).previous_message_type = @@ -31,15 +31,15 @@ message HttpGrpcAccessLogConfig { CommonGrpcAccessLogConfig common_config = 1 [(validate.rules).message = {required: true}]; // Additional request headers to log in :ref:`HTTPRequestProperties.request_headers - // `. + // `. repeated string additional_request_headers_to_log = 2; // Additional response headers to log in :ref:`HTTPResponseProperties.response_headers - // `. + // `. repeated string additional_response_headers_to_log = 3; // Additional response trailers to log in :ref:`HTTPResponseProperties.response_trailers - // `. + // `. repeated string additional_response_trailers_to_log = 4; } @@ -60,7 +60,7 @@ message CommonGrpcAccessLogConfig { "envoy.config.accesslog.v2.CommonGrpcAccessLogConfig"; // The friendly name of the access log to be returned in :ref:`StreamAccessLogsMessage.Identifier - // `. This allows the + // `. This allows the // access log server to differentiate between different access logs coming from the same Envoy. string log_name = 1 [(validate.rules).string = {min_len: 1}]; @@ -83,7 +83,7 @@ message CommonGrpcAccessLogConfig { google.protobuf.UInt32Value buffer_size_bytes = 4; // Additional filter state objects to log in :ref:`filter_state_objects - // `. + // `. // Logger will call `FilterState::Object::serializeAsProto` to serialize the filter state object. repeated string filter_state_objects_to_log = 5; } diff --git a/api/envoy/extensions/access_loggers/grpc/v4alpha/als.proto b/api/envoy/extensions/access_loggers/grpc/v4alpha/als.proto index c7bf15948b231..9e6fb1e48386e 100644 --- a/api/envoy/extensions/access_loggers/grpc/v4alpha/als.proto +++ b/api/envoy/extensions/access_loggers/grpc/v4alpha/als.proto @@ -20,9 +20,9 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: gRPC Access Log Service (ALS)] // Configuration for the built-in *envoy.access_loggers.http_grpc* -// :ref:`AccessLog `. This configuration will +// :ref:`AccessLog `. This configuration will // populate :ref:`StreamAccessLogsMessage.http_logs -// `. +// `. // [#extension: envoy.access_loggers.http_grpc] message HttpGrpcAccessLogConfig { option (udpa.annotations.versioning).previous_message_type = @@ -31,15 +31,15 @@ message HttpGrpcAccessLogConfig { CommonGrpcAccessLogConfig common_config = 1 [(validate.rules).message = {required: true}]; // Additional request headers to log in :ref:`HTTPRequestProperties.request_headers - // `. + // `. repeated string additional_request_headers_to_log = 2; // Additional response headers to log in :ref:`HTTPResponseProperties.response_headers - // `. + // `. repeated string additional_response_headers_to_log = 3; // Additional response trailers to log in :ref:`HTTPResponseProperties.response_trailers - // `. + // `. repeated string additional_response_trailers_to_log = 4; } @@ -60,7 +60,7 @@ message CommonGrpcAccessLogConfig { "envoy.extensions.access_loggers.grpc.v3.CommonGrpcAccessLogConfig"; // The friendly name of the access log to be returned in :ref:`StreamAccessLogsMessage.Identifier - // `. This allows the + // `. This allows the // access log server to differentiate between different access logs coming from the same Envoy. string log_name = 1 [(validate.rules).string = {min_len: 1}]; @@ -83,7 +83,7 @@ message CommonGrpcAccessLogConfig { google.protobuf.UInt32Value buffer_size_bytes = 4; // Additional filter state objects to log in :ref:`filter_state_objects - // `. + // `. // Logger will call `FilterState::Object::serializeAsProto` to serialize the filter state object. repeated string filter_state_objects_to_log = 5; } diff --git a/api/envoy/extensions/access_loggers/open_telemetry/v3alpha/logs_service.proto b/api/envoy/extensions/access_loggers/open_telemetry/v3alpha/logs_service.proto index 5a3bdce5e8bf6..1b7027133e153 100644 --- a/api/envoy/extensions/access_loggers/open_telemetry/v3alpha/logs_service.proto +++ b/api/envoy/extensions/access_loggers/open_telemetry/v3alpha/logs_service.proto @@ -18,7 +18,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: OpenTelemetry (gRPC) Access Log] // Configuration for the built-in *envoy.access_loggers.open_telemetry* -// :ref:`AccessLog `. This configuration will +// :ref:`AccessLog `. This configuration will // populate `opentelemetry.proto.collector.v1.logs.ExportLogsServiceRequest.resource_logs `_. // OpenTelemetry `Resource `_ // attributes are filled with Envoy node info. In addition, the request start time is set in the diff --git a/api/envoy/extensions/access_loggers/open_telemetry/v4alpha/logs_service.proto b/api/envoy/extensions/access_loggers/open_telemetry/v4alpha/logs_service.proto index 6fc4bcb7ea6c2..ceecd924e19d9 100644 --- a/api/envoy/extensions/access_loggers/open_telemetry/v4alpha/logs_service.proto +++ b/api/envoy/extensions/access_loggers/open_telemetry/v4alpha/logs_service.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: OpenTelemetry (gRPC) Access Log] // Configuration for the built-in *envoy.access_loggers.open_telemetry* -// :ref:`AccessLog `. This configuration will +// :ref:`AccessLog `. This configuration will // populate `opentelemetry.proto.collector.v1.logs.ExportLogsServiceRequest.resource_logs `_. // OpenTelemetry `Resource `_ // attributes are filled with Envoy node info. In addition, the request start time is set in the diff --git a/api/envoy/extensions/access_loggers/stream/v3/stream.proto b/api/envoy/extensions/access_loggers/stream/v3/stream.proto index cd683b67e2f6b..bd704ccdb6768 100644 --- a/api/envoy/extensions/access_loggers/stream/v3/stream.proto +++ b/api/envoy/extensions/access_loggers/stream/v3/stream.proto @@ -15,7 +15,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Standard Streams Access loggers] // [#extension: envoy.access_loggers.stream] -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that writes log entries directly to the operating system's standard output. message StdoutAccessLog { oneof access_log_format { @@ -26,7 +26,7 @@ message StdoutAccessLog { } } -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that writes log entries directly to the operating system's standard error. message StderrAccessLog { oneof access_log_format { diff --git a/api/envoy/extensions/access_loggers/stream/v4alpha/stream.proto b/api/envoy/extensions/access_loggers/stream/v4alpha/stream.proto index a0dbeb7b4b51d..5be54ad4721dd 100644 --- a/api/envoy/extensions/access_loggers/stream/v4alpha/stream.proto +++ b/api/envoy/extensions/access_loggers/stream/v4alpha/stream.proto @@ -16,7 +16,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: Standard Streams Access loggers] // [#extension: envoy.access_loggers.stream] -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that writes log entries directly to the operating system's standard output. message StdoutAccessLog { option (udpa.annotations.versioning).previous_message_type = @@ -30,7 +30,7 @@ message StdoutAccessLog { } } -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that writes log entries directly to the operating system's standard error. message StderrAccessLog { option (udpa.annotations.versioning).previous_message_type = diff --git a/api/envoy/extensions/access_loggers/wasm/v3/wasm.proto b/api/envoy/extensions/access_loggers/wasm/v3/wasm.proto index 6c3ac617636b8..44e96345dfee5 100644 --- a/api/envoy/extensions/access_loggers/wasm/v3/wasm.proto +++ b/api/envoy/extensions/access_loggers/wasm/v3/wasm.proto @@ -14,7 +14,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Wasm access log] // [#extension: envoy.access_loggers.wasm] -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that calls into a WASM VM. Configures the built-in *envoy.access_loggers.wasm* // AccessLog. message WasmAccessLog { diff --git a/api/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto b/api/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto index 869e8c42caba1..c4fc8285ee597 100644 --- a/api/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto +++ b/api/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto @@ -24,12 +24,12 @@ message ClusterConfig { // The DNS cache configuration that the cluster will attach to. Note this configuration must // match that of associated :ref:`dynamic forward proxy HTTP filter configuration - // `. + // `. common.dynamic_forward_proxy.v3.DnsCacheConfig dns_cache_config = 1 [(validate.rules).message = {required: true}]; // If true allow the cluster configuration to disable the auto_sni and auto_san_validation options // in the :ref:`cluster's upstream_http_protocol_options - // ` + // ` bool allow_insecure_cluster_options = 2; } diff --git a/api/envoy/extensions/clusters/redis/v3/redis_cluster.proto b/api/envoy/extensions/clusters/redis/v3/redis_cluster.proto index afc19777edf2b..73598eafbe9d2 100644 --- a/api/envoy/extensions/clusters/redis/v3/redis_cluster.proto +++ b/api/envoy/extensions/clusters/redis/v3/redis_cluster.proto @@ -27,7 +27,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // updated at user-configured intervals. // // Additionally, if -// :ref:`enable_redirection` +// :ref:`enable_redirection` // is true, then moved and ask redirection errors from upstream servers will trigger a topology // refresh when they exceed a user-configured error threshold. // diff --git a/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto b/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto index 6a516b4300283..215c7414c5a2d 100644 --- a/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto +++ b/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto @@ -39,7 +39,7 @@ message AwsRequestSigning { // // Note: this rewrite affects both signing and host header forwarding. However, this // option shouldn't be used with - // :ref:`HCM host rewrite ` given that the + // :ref:`HCM host rewrite ` given that the // value set here would be used for signing whereas the value set in the HCM would be used // for host header forwarding which is not the desired outcome. string host_rewrite = 3; diff --git a/api/envoy/extensions/filters/http/composite/v3/composite.proto b/api/envoy/extensions/filters/http/composite/v3/composite.proto index 706e7a5b24668..3afc136904c76 100644 --- a/api/envoy/extensions/filters/http/composite/v3/composite.proto +++ b/api/envoy/extensions/filters/http/composite/v3/composite.proto @@ -21,9 +21,9 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // incoming request. // // This is intended to be used with -// :ref:`ExtensionWithMatcher ` +// :ref:`ExtensionWithMatcher ` // where a match tree is specified that indicates (via -// :ref:`ExecuteFilterAction `) +// :ref:`ExecuteFilterAction `) // which filter configuration to create and delegate to. // message Composite { diff --git a/api/envoy/extensions/filters/http/compressor/v3/compressor.proto b/api/envoy/extensions/filters/http/compressor/v3/compressor.proto index 1c08706b055c7..ab1d426be4540 100644 --- a/api/envoy/extensions/filters/http/compressor/v3/compressor.proto +++ b/api/envoy/extensions/filters/http/compressor/v3/compressor.proto @@ -99,7 +99,7 @@ message Compressor { [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; // A compressor library to use for compression. Currently only - // :ref:`envoy.compression.gzip.compressor` + // :ref:`envoy.compression.gzip.compressor` // is included in Envoy. // This field is ignored if used in the context of the gzip http-filter, but is mandatory otherwise. // [#extension-category: envoy.compression.compressor] diff --git a/api/envoy/extensions/filters/http/compressor/v4alpha/compressor.proto b/api/envoy/extensions/filters/http/compressor/v4alpha/compressor.proto index d379bf48ce699..adae49fcb443d 100644 --- a/api/envoy/extensions/filters/http/compressor/v4alpha/compressor.proto +++ b/api/envoy/extensions/filters/http/compressor/v4alpha/compressor.proto @@ -80,7 +80,7 @@ message Compressor { "remove_accept_encoding_header", "runtime_enabled"; // A compressor library to use for compression. Currently only - // :ref:`envoy.compression.gzip.compressor` + // :ref:`envoy.compression.gzip.compressor` // is included in Envoy. // This field is ignored if used in the context of the gzip http-filter, but is mandatory otherwise. // [#extension-category: envoy.compression.compressor] diff --git a/api/envoy/extensions/filters/http/csrf/v3/csrf.proto b/api/envoy/extensions/filters/http/csrf/v3/csrf.proto index 263d705e3f545..39b0455bd7981 100644 --- a/api/envoy/extensions/filters/http/csrf/v3/csrf.proto +++ b/api/envoy/extensions/filters/http/csrf/v3/csrf.proto @@ -25,13 +25,13 @@ message CsrfPolicy { // Specifies the % of requests for which the CSRF filter is enabled. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests to filter. // // .. note:: // // This field defaults to 100/:ref:`HUNDRED - // `. + // `. config.core.v3.RuntimeFractionalPercent filter_enabled = 1 [(validate.rules).message = {required: true}]; @@ -39,7 +39,7 @@ message CsrfPolicy { // // This is intended to be used when ``filter_enabled`` is off and will be ignored otherwise. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate // and track the request's *Origin* and *Destination* to determine if it's valid, but will not // enforce any policies. diff --git a/api/envoy/extensions/filters/http/csrf/v4alpha/csrf.proto b/api/envoy/extensions/filters/http/csrf/v4alpha/csrf.proto index dda915a059af5..3de55da6be8cf 100644 --- a/api/envoy/extensions/filters/http/csrf/v4alpha/csrf.proto +++ b/api/envoy/extensions/filters/http/csrf/v4alpha/csrf.proto @@ -25,13 +25,13 @@ message CsrfPolicy { // Specifies the % of requests for which the CSRF filter is enabled. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests to filter. // // .. note:: // // This field defaults to 100/:ref:`HUNDRED - // `. + // `. config.core.v4alpha.RuntimeFractionalPercent filter_enabled = 1 [(validate.rules).message = {required: true}]; @@ -39,7 +39,7 @@ message CsrfPolicy { // // This is intended to be used when ``filter_enabled`` is off and will be ignored otherwise. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate // and track the request's *Origin* and *Destination* to determine if it's valid, but will not // enforce any policies. diff --git a/api/envoy/extensions/filters/http/decompressor/v3/decompressor.proto b/api/envoy/extensions/filters/http/decompressor/v3/decompressor.proto index 54d5f23bc449e..c4cca44020f6d 100644 --- a/api/envoy/extensions/filters/http/decompressor/v3/decompressor.proto +++ b/api/envoy/extensions/filters/http/decompressor/v3/decompressor.proto @@ -41,7 +41,7 @@ message Decompressor { } // A decompressor library to use for both request and response decompression. Currently only - // :ref:`envoy.compression.gzip.compressor` + // :ref:`envoy.compression.gzip.compressor` // is included in Envoy. // [#extension-category: envoy.compression.decompressor] config.core.v3.TypedExtensionConfig decompressor_library = 1 diff --git a/api/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto b/api/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto index 70dd21a324b37..a5d7223b98d28 100644 --- a/api/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto +++ b/api/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto @@ -24,7 +24,7 @@ message FilterConfig { // The DNS cache configuration that the filter will attach to. Note this configuration must // match that of associated :ref:`dynamic forward proxy cluster configuration - // `. + // `. common.dynamic_forward_proxy.v3.DnsCacheConfig dns_cache_config = 1 [(validate.rules).message = {required: true}]; } @@ -41,7 +41,7 @@ message PerRouteConfig { // // Note: this rewrite affects both DNS lookup and host header forwarding. However, this // option shouldn't be used with - // :ref:`HCM host rewrite ` given that the + // :ref:`HCM host rewrite ` given that the // value set here would be used for DNS lookups whereas the value set in the HCM would be used // for host header forwarding which is not the desired outcome. string host_rewrite_literal = 1; @@ -52,7 +52,7 @@ message PerRouteConfig { // // Note: this rewrite affects both DNS lookup and host header forwarding. However, this // option shouldn't be used with - // :ref:`HCM host rewrite header ` + // :ref:`HCM host rewrite header ` // given that the value set here would be used for DNS lookups whereas the value set in the HCM // would be used for host header forwarding which is not the desired outcome. // diff --git a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto index 705629496d942..1effca694d005 100644 --- a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto +++ b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto @@ -85,7 +85,7 @@ message ExtAuthz { // ext_authz service as an opaque *protobuf::Struct*. // // For example, if the *jwt_authn* filter is used and :ref:`payload_in_metadata - // ` is set, + // ` is set, // then the following will pass the jwt payload to the authorization server. // // .. code-block:: yaml @@ -97,7 +97,7 @@ message ExtAuthz { // Specifies if the filter is enabled. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests to filter. // // If this field is not specified, the filter will be enabled for all requests. @@ -108,7 +108,7 @@ message ExtAuthz { type.matcher.v3.MetadataMatcher filter_enabled_metadata = 14; // Specifies whether to deny the requests, when the filter is disabled. - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to determine whether to deny request for // filter protected path at filter disabling. If filter is disabled in // typed_per_filter_config for the path, requests will not be denied. @@ -119,7 +119,7 @@ message ExtAuthz { // Specifies if the peer certificate is sent to the external service. // // When this field is true, Envoy will include the peer X.509 certificate, if available, in the - // :ref:`certificate`. + // :ref:`certificate`. bool include_peer_certificate = 10; // Optional additional prefix to use when emitting statistics. This allows to distinguish @@ -148,7 +148,7 @@ message BufferSettings { // Sets the maximum size of a message body that the filter will hold in memory. Envoy will return // *HTTP 413* and will *not* initiate the authorization process when buffer reaches the number // set in this field. Note that this setting will have precedence over :ref:`failure_mode_allow - // `. + // `. uint32 max_request_bytes = 1 [(validate.rules).uint32 = {gt: 0}]; // When this field is true, Envoy will buffer the message until *max_request_bytes* is reached. @@ -172,24 +172,24 @@ message BufferSettings { // // *On authorization request*, a list of allowed request headers may be supplied. See // :ref:`allowed_headers -// ` +// ` // for details. Additional headers metadata may be added to the authorization request. See // :ref:`headers_to_add -// ` for +// ` for // details. // // On authorization response status HTTP 200 OK, the filter will allow traffic to the upstream and // additional headers metadata may be added to the original client request. See // :ref:`allowed_upstream_headers -// ` +// ` // for details. Additionally, the filter may add additional headers to the client's response. See // :ref:`allowed_client_headers_on_success -// ` +// ` // for details. // // On other authorization response statuses, the filter will not allow traffic. Additional headers // metadata as well as body may be added to the client's response. See :ref:`allowed_client_headers -// ` +// ` // for details. // [#next-free-field: 9] message HttpService { @@ -216,7 +216,7 @@ message AuthorizationRequest { "envoy.config.filter.http.ext_authz.v2.AuthorizationRequest"; // Authorization request will include the client request headers that have a correspondent match - // in the :ref:`list `. Note that in addition to the + // in the :ref:`list `. Note that in addition to the // user's supplied matchers: // // 1. *Host*, *Method*, *Path* and *Content-Length* are automatically included to the list. @@ -224,7 +224,7 @@ message AuthorizationRequest { // 2. *Content-Length* will be set to 0 and the request to the authorization service will not have // a message body. However, the authorization request can include the buffered client request body // (controlled by :ref:`with_request_body - // ` setting), + // ` setting), // consequently the value of *Content-Length* of the authorization request reflects the size of // its payload size. // @@ -239,24 +239,24 @@ message AuthorizationResponse { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.ext_authz.v2.AuthorizationResponse"; - // When this :ref:`list ` is set, authorization + // When this :ref:`list ` is set, authorization // response headers that have a correspondent match will be added to the original client request. // Note that coexistent headers will be overridden. type.matcher.v3.ListStringMatcher allowed_upstream_headers = 1; - // When this :ref:`list ` is set, authorization + // When this :ref:`list ` is set, authorization // response headers that have a correspondent match will be added to the client's response. Note // that coexistent headers will be appended. type.matcher.v3.ListStringMatcher allowed_upstream_headers_to_append = 3; - // When this :ref:`list `. is set, authorization + // When this :ref:`list `. is set, authorization // response headers that have a correspondent match will be added to the client's response. Note // that when this list is *not* set, all the authorization response headers, except *Authority // (Host)* will be in the response to the client. When a header is included in this list, *Path*, // *Status*, *Content-Length*, *WWWAuthenticate* and *Location* are automatically added. type.matcher.v3.ListStringMatcher allowed_client_headers = 2; - // When this :ref:`list `. is set, authorization + // When this :ref:`list `. is set, authorization // response headers that have a correspondent match will be added to the client's response when // the authorization response itself is successful, i.e. not failed or denied. When this list is // *not* set, no additional headers will be added to the client's response on success. @@ -286,7 +286,7 @@ message CheckSettings { "envoy.config.filter.http.ext_authz.v2.CheckSettings"; // Context extensions to set on the CheckRequest's - // :ref:`AttributeContext.context_extensions` + // :ref:`AttributeContext.context_extensions` // // You can use this to provide extra context for the external authorization server on specific // virtual hosts/routes. For example, adding a context extension on the virtual host level can @@ -299,10 +299,10 @@ message CheckSettings { // .. note:: // // These settings are only applied to a filter configured with a - // :ref:`grpc_service`. + // :ref:`grpc_service`. map context_extensions = 1; // When set to true, disable the configured :ref:`with_request_body - // ` for a route. + // ` for a route. bool disable_request_body_buffering = 2; } diff --git a/api/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto b/api/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto index 014c8263e61c3..90f003b0a137c 100644 --- a/api/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto +++ b/api/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto @@ -85,7 +85,7 @@ message ExtAuthz { // ext_authz service as an opaque *protobuf::Struct*. // // For example, if the *jwt_authn* filter is used and :ref:`payload_in_metadata - // ` is set, + // ` is set, // then the following will pass the jwt payload to the authorization server. // // .. code-block:: yaml @@ -97,7 +97,7 @@ message ExtAuthz { // Specifies if the filter is enabled. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests to filter. // // If this field is not specified, the filter will be enabled for all requests. @@ -108,7 +108,7 @@ message ExtAuthz { type.matcher.v4alpha.MetadataMatcher filter_enabled_metadata = 14; // Specifies whether to deny the requests, when the filter is disabled. - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to determine whether to deny request for // filter protected path at filter disabling. If filter is disabled in // typed_per_filter_config for the path, requests will not be denied. @@ -119,7 +119,7 @@ message ExtAuthz { // Specifies if the peer certificate is sent to the external service. // // When this field is true, Envoy will include the peer X.509 certificate, if available, in the - // :ref:`certificate`. + // :ref:`certificate`. bool include_peer_certificate = 10; // Optional additional prefix to use when emitting statistics. This allows to distinguish @@ -148,7 +148,7 @@ message BufferSettings { // Sets the maximum size of a message body that the filter will hold in memory. Envoy will return // *HTTP 413* and will *not* initiate the authorization process when buffer reaches the number // set in this field. Note that this setting will have precedence over :ref:`failure_mode_allow - // `. + // `. uint32 max_request_bytes = 1 [(validate.rules).uint32 = {gt: 0}]; // When this field is true, Envoy will buffer the message until *max_request_bytes* is reached. @@ -172,24 +172,24 @@ message BufferSettings { // // *On authorization request*, a list of allowed request headers may be supplied. See // :ref:`allowed_headers -// ` +// ` // for details. Additional headers metadata may be added to the authorization request. See // :ref:`headers_to_add -// ` for +// ` for // details. // // On authorization response status HTTP 200 OK, the filter will allow traffic to the upstream and // additional headers metadata may be added to the original client request. See // :ref:`allowed_upstream_headers -// ` +// ` // for details. Additionally, the filter may add additional headers to the client's response. See // :ref:`allowed_client_headers_on_success -// ` +// ` // for details. // // On other authorization response statuses, the filter will not allow traffic. Additional headers // metadata as well as body may be added to the client's response. See :ref:`allowed_client_headers -// ` +// ` // for details. // [#next-free-field: 9] message HttpService { @@ -216,7 +216,7 @@ message AuthorizationRequest { "envoy.extensions.filters.http.ext_authz.v3.AuthorizationRequest"; // Authorization request will include the client request headers that have a correspondent match - // in the :ref:`list `. Note that in addition to the + // in the :ref:`list `. Note that in addition to the // user's supplied matchers: // // 1. *Host*, *Method*, *Path* and *Content-Length* are automatically included to the list. @@ -224,7 +224,7 @@ message AuthorizationRequest { // 2. *Content-Length* will be set to 0 and the request to the authorization service will not have // a message body. However, the authorization request can include the buffered client request body // (controlled by :ref:`with_request_body - // ` setting), + // ` setting), // consequently the value of *Content-Length* of the authorization request reflects the size of // its payload size. // @@ -239,24 +239,24 @@ message AuthorizationResponse { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.ext_authz.v3.AuthorizationResponse"; - // When this :ref:`list ` is set, authorization + // When this :ref:`list ` is set, authorization // response headers that have a correspondent match will be added to the original client request. // Note that coexistent headers will be overridden. type.matcher.v4alpha.ListStringMatcher allowed_upstream_headers = 1; - // When this :ref:`list ` is set, authorization + // When this :ref:`list ` is set, authorization // response headers that have a correspondent match will be added to the client's response. Note // that coexistent headers will be appended. type.matcher.v4alpha.ListStringMatcher allowed_upstream_headers_to_append = 3; - // When this :ref:`list `. is set, authorization + // When this :ref:`list `. is set, authorization // response headers that have a correspondent match will be added to the client's response. Note // that when this list is *not* set, all the authorization response headers, except *Authority // (Host)* will be in the response to the client. When a header is included in this list, *Path*, // *Status*, *Content-Length*, *WWWAuthenticate* and *Location* are automatically added. type.matcher.v4alpha.ListStringMatcher allowed_client_headers = 2; - // When this :ref:`list `. is set, authorization + // When this :ref:`list `. is set, authorization // response headers that have a correspondent match will be added to the client's response when // the authorization response itself is successful, i.e. not failed or denied. When this list is // *not* set, no additional headers will be added to the client's response on success. @@ -286,7 +286,7 @@ message CheckSettings { "envoy.extensions.filters.http.ext_authz.v3.CheckSettings"; // Context extensions to set on the CheckRequest's - // :ref:`AttributeContext.context_extensions` + // :ref:`AttributeContext.context_extensions` // // You can use this to provide extra context for the external authorization server on specific // virtual hosts/routes. For example, adding a context extension on the virtual host level can @@ -299,10 +299,10 @@ message CheckSettings { // .. note:: // // These settings are only applied to a filter configured with a - // :ref:`grpc_service`. + // :ref:`grpc_service`. map context_extensions = 1; // When set to true, disable the configured :ref:`with_request_body - // ` for a route. + // ` for a route. bool disable_request_body_buffering = 2; } diff --git a/api/envoy/extensions/filters/http/fault/v3/fault.proto b/api/envoy/extensions/filters/http/fault/v3/fault.proto index fb3c51cca9d00..0c7fbb4480cfe 100644 --- a/api/envoy/extensions/filters/http/fault/v3/fault.proto +++ b/api/envoy/extensions/filters/http/fault/v3/fault.proto @@ -76,7 +76,7 @@ message HTTPFault { // injection filter can be applied selectively to requests that match a set of // headers specified in the fault filter config. The chances of actual fault // injection further depend on the value of the :ref:`percentage - // ` field. + // ` field. // The filter will check the request's headers against all the specified // headers in the filter config. A match will happen if all the headers in the // config are present in the request with the same values (or based on diff --git a/api/envoy/extensions/filters/http/fault/v4alpha/fault.proto b/api/envoy/extensions/filters/http/fault/v4alpha/fault.proto index 5155007268456..da8b8b48ad3f5 100644 --- a/api/envoy/extensions/filters/http/fault/v4alpha/fault.proto +++ b/api/envoy/extensions/filters/http/fault/v4alpha/fault.proto @@ -76,7 +76,7 @@ message HTTPFault { // injection filter can be applied selectively to requests that match a set of // headers specified in the fault filter config. The chances of actual fault // injection further depend on the value of the :ref:`percentage - // ` field. + // ` field. // The filter will check the request's headers against all the specified // headers in the filter config. A match will happen if all the headers in the // config are present in the request with the same values (or based on diff --git a/api/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto b/api/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto index 5b233a476bf4a..a4feeff31f158 100644 --- a/api/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto +++ b/api/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto @@ -71,7 +71,7 @@ message GrpcJsonTranscoder { message RequestValidationOptions { // By default, a request that cannot be mapped to any specified gRPC - // :ref:`services ` + // :ref:`services ` // will pass-through this filter. // When set to true, the request will be rejected with a ``HTTP 404 Not Found``. bool reject_unknown_method = 1; @@ -81,9 +81,9 @@ message GrpcJsonTranscoder { // When set to true, the request will be rejected with a ``HTTP 400 Bad Request``. // // The fields - // :ref:`ignore_unknown_query_parameters ` + // :ref:`ignore_unknown_query_parameters ` // and - // :ref:`ignored_query_parameters ` + // :ref:`ignored_query_parameters ` // have priority over this strict validation behavior. bool reject_unknown_query_parameters = 2; } @@ -111,7 +111,7 @@ message GrpcJsonTranscoder { // By default, the filter will pass through requests that do not map to any specified services. // If the list of services is empty, filter is considered disabled. // However, this behavior changes if - // :ref:`reject_unknown_method ` + // :ref:`reject_unknown_method ` // is enabled. repeated string services = 2; @@ -214,7 +214,7 @@ message GrpcJsonTranscoder { // This spec is only applied when extracting variable with multiple segments. // For example, in case of `/foo/{x=*}/bar/{y=prefix/*}/{z=**}` `x` variable is single segment and `y` and `z` are multiple segments. // For a path with `/foo/first/bar/prefix/second/third/fourth`, `x=first`, `y=prefix/second`, `z=third/fourth`. - // If this setting is not specified, the value defaults to :ref:`ALL_CHARACTERS_EXCEPT_RESERVED`. + // If this setting is not specified, the value defaults to :ref:`ALL_CHARACTERS_EXCEPT_RESERVED`. UrlUnescapeSpec url_unescape_spec = 10 [(validate.rules).enum = {defined_only: true}]; // Configure the behavior when handling requests that cannot be transcoded. diff --git a/api/envoy/extensions/filters/http/grpc_stats/v3/config.proto b/api/envoy/extensions/filters/http/grpc_stats/v3/config.proto index 244bfb607235d..79ecb7a92b706 100644 --- a/api/envoy/extensions/filters/http/grpc_stats/v3/config.proto +++ b/api/envoy/extensions/filters/http/grpc_stats/v3/config.proto @@ -54,9 +54,9 @@ message FilterConfig { // If true, the filter will gather a histogram for the request time of the upstream. // It works with :ref:`stats_for_all_methods - // ` + // ` // and :ref:`individual_method_stats_allowlist - // ` the same way + // ` the same way // request_message_count and response_message_count works. bool enable_upstream_stats = 4; } diff --git a/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto b/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto index 59c61d281aa0b..7766ee2573d00 100644 --- a/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto +++ b/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto @@ -47,7 +47,7 @@ message LocalRateLimit { // // .. note:: // In the current implementation the token bucket's :ref:`fill_interval - // ` must be >= 50ms to avoid too aggressive + // ` must be >= 50ms to avoid too aggressive // refills. type.v3.TokenBucket token_bucket = 3; @@ -83,8 +83,8 @@ message LocalRateLimit { // .. note:: // // In the current implementation the descriptor's token bucket :ref:`fill_interval - // ` must be a multiple - // global :ref:`token bucket's` fill interval. + // ` must be a multiple + // global :ref:`token bucket's` fill interval. // // The descriptors must match verbatim for rate limiting to apply. There is no partial // match by a subset of descriptor entries in the current implementation. diff --git a/api/envoy/extensions/filters/network/ext_authz/v3/ext_authz.proto b/api/envoy/extensions/filters/network/ext_authz/v3/ext_authz.proto index 78f4167ccc337..673d46ea1d4bd 100644 --- a/api/envoy/extensions/filters/network/ext_authz/v3/ext_authz.proto +++ b/api/envoy/extensions/filters/network/ext_authz/v3/ext_authz.proto @@ -22,7 +22,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // External Authorization filter calls out to an external service over the // gRPC Authorization API defined by -// :ref:`CheckRequest `. +// :ref:`CheckRequest `. // A failed check will cause this filter to close the TCP connection. // [#next-free-field: 7] message ExtAuthz { @@ -45,7 +45,7 @@ message ExtAuthz { // Specifies if the peer certificate is sent to the external service. // // When this field is true, Envoy will include the peer X.509 certificate, if available, in the - // :ref:`certificate`. + // :ref:`certificate`. bool include_peer_certificate = 4; // API version for ext_authz transport protocol. This describes the ext_authz gRPC endpoint and diff --git a/api/envoy/extensions/filters/network/ext_authz/v4alpha/ext_authz.proto b/api/envoy/extensions/filters/network/ext_authz/v4alpha/ext_authz.proto index f877a3ed85027..c3c5aad9b639b 100644 --- a/api/envoy/extensions/filters/network/ext_authz/v4alpha/ext_authz.proto +++ b/api/envoy/extensions/filters/network/ext_authz/v4alpha/ext_authz.proto @@ -22,7 +22,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // External Authorization filter calls out to an external service over the // gRPC Authorization API defined by -// :ref:`CheckRequest `. +// :ref:`CheckRequest `. // A failed check will cause this filter to close the TCP connection. // [#next-free-field: 7] message ExtAuthz { @@ -45,7 +45,7 @@ message ExtAuthz { // Specifies if the peer certificate is sent to the external service. // // When this field is true, Envoy will include the peer X.509 certificate, if available, in the - // :ref:`certificate`. + // :ref:`certificate`. bool include_peer_certificate = 4; // API version for ext_authz transport protocol. This describes the ext_authz gRPC endpoint and diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 87d826262b941..13b4755f07f68 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -234,7 +234,7 @@ message HttpConnectionManager { // Determines if upgrades are enabled or disabled by default. Defaults to true. // This can be overridden on a per-route basis with :ref:`cluster - // ` as documented in the + // ` as documented in the // :ref:`upgrade documentation `. google.protobuf.BoolValue enabled = 3; } @@ -243,8 +243,8 @@ message HttpConnectionManager { // before any processing of requests by HTTP filters, routing, and matching. Only the normalized // path will be visible internally if a transformation is enabled. Any path rewrites that the // router performs (e.g. :ref:`regex_rewrite - // ` or :ref:`prefix_rewrite - // `) will apply to the *:path* header + // ` or :ref:`prefix_rewrite + // `) will apply to the *:path* header // destined for the upstream. // // Note: access logging and tracing will show the original *:path* header. @@ -252,7 +252,7 @@ message HttpConnectionManager { // [#not-implemented-hide:] Normalization applies internally before any processing of requests by // HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults // to :ref:`NormalizePathRFC3986 - // `. When not + // `. When not // specified, this value may be overridden by the runtime variable // :ref:`http_connection_manager.normalize_path`. // Envoy will respond with 400 to paths that are malformed (e.g. for paths that fail RFC 3986 @@ -310,7 +310,7 @@ message HttpConnectionManager { // Presence of the object defines whether the connection manager // emits :ref:`tracing ` data to the :ref:`configured tracing provider - // `. + // `. Tracing tracing = 7; // Additional settings for HTTP requests handled by the connection manager. These will be @@ -354,10 +354,10 @@ message HttpConnectionManager { // // This idle timeout applies to new streams and is overridable by the // :ref:`route-level idle_timeout - // `. Even on a stream in + // `. Even on a stream in // which the override applies, prior to receipt of the initial request // headers, the :ref:`stream_idle_timeout - // ` + // ` // applies. Each time an encode/decode event for headers or data is processed // for the stream, the timer will be reset. If the timeout fires, the stream // is terminated with a 408 Request Timeout error code if no upstream response @@ -370,12 +370,12 @@ message HttpConnectionManager { // data has been proxied within available flow control windows. If the timeout is hit in this // case, the :ref:`tx_flush_timeout ` counter will be // incremented. Note that :ref:`max_stream_duration - // ` does not apply to + // ` does not apply to // this corner case. // // If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" // is configured, this timeout is scaled according to the value for - // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. // // Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due // to the granularity of events presented to the connection manager. For example, while receiving @@ -477,7 +477,7 @@ message HttpConnectionManager { // :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. This may be used in // conjunction with HTTP filters that explicitly manipulate XFF after the HTTP connection manager // has mutated the request headers. While :ref:`use_remote_address - // ` + // ` // will also suppress XFF addition, it has consequences for logging and other // Envoy uses of the remote address, so *skip_xff_append* should be used // when only an elision of XFF addition is intended. @@ -510,7 +510,7 @@ message HttpConnectionManager { [(validate.rules).enum = {defined_only: true}]; // This field is valid only when :ref:`forward_client_cert_details - // ` + // ` // is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in // the client certificate to be forwarded. Note that in the // :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and @@ -526,7 +526,7 @@ message HttpConnectionManager { // If // :ref:`use_remote_address - // ` + // ` // is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is // an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. // This is useful for testing compatibility of upstream services that parse the header value. For @@ -585,12 +585,12 @@ message HttpConnectionManager { LocalReplyConfig local_reply_config = 38; // Determines if the port part should be removed from host/authority header before any processing - // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` + // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` // local port. This affects the upstream host header unless the method is // CONNECT in which case if no filter adds a port the original port will be restored before headers are // sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against - // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. // Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. bool strip_matching_host_port = 39 @@ -602,7 +602,7 @@ message HttpConnectionManager { // This affects the upstream host header unless the method is CONNECT in // which case if no filter adds a port the original port will be restored before headers are sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against - // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. // Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. bool strip_any_host_port = 42; @@ -632,7 +632,7 @@ message HttpConnectionManager { // whether transformations affect the forwarded *:path* header. RFC 3986 path // normalization is enabled by default and the default policy is that the // normalized header will be forwarded. See :ref:`PathNormalizationOptions - // ` + // ` // for details. PathNormalizationOptions path_normalization_options = 43; } @@ -736,14 +736,14 @@ message ScopedRoutes { "envoy.config.filter.network.http_connection_manager.v2.ScopedRoutes"; // Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These - // keys are matched against a set of :ref:`Key` - // objects assembled from :ref:`ScopedRouteConfiguration` + // keys are matched against a set of :ref:`Key` + // objects assembled from :ref:`ScopedRouteConfiguration` // messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via - // :ref:`scoped_route_configurations_list`. + // :ref:`scoped_route_configurations_list`. // // Upon receiving a request's headers, the Router will build a key using the algorithm specified // by this message. This key will be used to look up the routing table (i.e., the - // :ref:`RouteConfiguration`) to use for the request. + // :ref:`RouteConfiguration`) to use for the request. message ScopeKeyBuilder { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.ScopedRoutes.ScopeKeyBuilder"; @@ -826,7 +826,7 @@ message ScopedRoutes { } // The final(built) scope key consists of the ordered union of these fragments, which are compared in order with the - // fragments of a :ref:`ScopedRouteConfiguration`. + // fragments of a :ref:`ScopedRouteConfiguration`. // A missing fragment during comparison will make the key invalid, i.e., the computed key doesn't match any key. repeated FragmentBuilder fragments = 1 [(validate.rules).repeated = {min_items: 1}]; } @@ -848,14 +848,14 @@ message ScopedRoutes { // The set of routing scopes corresponding to the HCM. A scope is assigned to a request by // matching a key constructed from the request's attributes according to the algorithm specified // by the - // :ref:`ScopeKeyBuilder` + // :ref:`ScopeKeyBuilder` // in this message. ScopedRouteConfigurationsList scoped_route_configurations_list = 4; // The set of routing scopes associated with the HCM will be dynamically loaded via the SRDS // API. A scope is assigned to a request by matching a key constructed from the request's // attributes according to the algorithm specified by the - // :ref:`ScopeKeyBuilder` + // :ref:`ScopeKeyBuilder` // in this message. ScopedRds scoped_rds = 5; } @@ -893,7 +893,7 @@ message HttpFilter { // filters for further documentation. // // To support configuring a :ref:`match tree `, use an - // :ref:`ExtensionWithMatcher ` + // :ref:`ExtensionWithMatcher ` // with the desired HTTP filter. // [#extension-category: envoy.filters.http] google.protobuf.Any typed_config = 4; @@ -903,7 +903,7 @@ message HttpFilter { // Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). // // To support configuring a :ref:`match tree `, use an - // :ref:`ExtensionWithMatcher ` + // :ref:`ExtensionWithMatcher ` // with the desired HTTP filter. This works for both the default filter configuration as well // as for filters provided via the API. config.core.v3.ExtensionConfigSource config_discovery = 5; diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index ba9b3c461b58b..a30e21de96ba1 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -233,7 +233,7 @@ message HttpConnectionManager { // Determines if upgrades are enabled or disabled by default. Defaults to true. // This can be overridden on a per-route basis with :ref:`cluster - // ` as documented in the + // ` as documented in the // :ref:`upgrade documentation `. google.protobuf.BoolValue enabled = 3; } @@ -242,8 +242,8 @@ message HttpConnectionManager { // before any processing of requests by HTTP filters, routing, and matching. Only the normalized // path will be visible internally if a transformation is enabled. Any path rewrites that the // router performs (e.g. :ref:`regex_rewrite - // ` or :ref:`prefix_rewrite - // `) will apply to the *:path* header + // ` or :ref:`prefix_rewrite + // `) will apply to the *:path* header // destined for the upstream. // // Note: access logging and tracing will show the original *:path* header. @@ -255,7 +255,7 @@ message HttpConnectionManager { // [#not-implemented-hide:] Normalization applies internally before any processing of requests by // HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults // to :ref:`NormalizePathRFC3986 - // `. When not + // `. When not // specified, this value may be overridden by the runtime variable // :ref:`http_connection_manager.normalize_path`. // Envoy will respond with 400 to paths that are malformed (e.g. for paths that fail RFC 3986 @@ -313,7 +313,7 @@ message HttpConnectionManager { // Presence of the object defines whether the connection manager // emits :ref:`tracing ` data to the :ref:`configured tracing provider - // `. + // `. Tracing tracing = 7; // Additional settings for HTTP requests handled by the connection manager. These will be @@ -357,10 +357,10 @@ message HttpConnectionManager { // // This idle timeout applies to new streams and is overridable by the // :ref:`route-level idle_timeout - // `. Even on a stream in + // `. Even on a stream in // which the override applies, prior to receipt of the initial request // headers, the :ref:`stream_idle_timeout - // ` + // ` // applies. Each time an encode/decode event for headers or data is processed // for the stream, the timer will be reset. If the timeout fires, the stream // is terminated with a 408 Request Timeout error code if no upstream response @@ -373,12 +373,12 @@ message HttpConnectionManager { // data has been proxied within available flow control windows. If the timeout is hit in this // case, the :ref:`tx_flush_timeout ` counter will be // incremented. Note that :ref:`max_stream_duration - // ` does not apply to + // ` does not apply to // this corner case. // // If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" // is configured, this timeout is scaled according to the value for - // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. // // Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due // to the granularity of events presented to the connection manager. For example, while receiving @@ -480,7 +480,7 @@ message HttpConnectionManager { // :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. This may be used in // conjunction with HTTP filters that explicitly manipulate XFF after the HTTP connection manager // has mutated the request headers. While :ref:`use_remote_address - // ` + // ` // will also suppress XFF addition, it has consequences for logging and other // Envoy uses of the remote address, so *skip_xff_append* should be used // when only an elision of XFF addition is intended. @@ -513,7 +513,7 @@ message HttpConnectionManager { [(validate.rules).enum = {defined_only: true}]; // This field is valid only when :ref:`forward_client_cert_details - // ` + // ` // is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in // the client certificate to be forwarded. Note that in the // :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and @@ -529,7 +529,7 @@ message HttpConnectionManager { // If // :ref:`use_remote_address - // ` + // ` // is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is // an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. // This is useful for testing compatibility of upstream services that parse the header value. For @@ -589,12 +589,12 @@ message HttpConnectionManager { oneof strip_port_mode { // Determines if the port part should be removed from host/authority header before any processing - // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` + // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` // local port. This affects the upstream host header unless the method is // CONNECT in which case if no filter adds a port the original port will be restored before headers are // sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against - // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. // Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. bool strip_matching_host_port = 39; @@ -604,7 +604,7 @@ message HttpConnectionManager { // This affects the upstream host header unless the method is CONNECT in // which case if no filter adds a port the original port will be restored before headers are sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against - // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. // Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. bool strip_any_host_port = 42; @@ -634,7 +634,7 @@ message HttpConnectionManager { // whether transformations affect the forwarded *:path* header. RFC 3986 path // normalization is enabled by default and the default policy is that the // normalized header will be forwarded. See :ref:`PathNormalizationOptions - // ` + // ` // for details. PathNormalizationOptions path_normalization_options = 43; } @@ -744,14 +744,14 @@ message ScopedRoutes { "envoy.extensions.filters.network.http_connection_manager.v3.ScopedRoutes"; // Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These - // keys are matched against a set of :ref:`Key` - // objects assembled from :ref:`ScopedRouteConfiguration` + // keys are matched against a set of :ref:`Key` + // objects assembled from :ref:`ScopedRouteConfiguration` // messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via - // :ref:`scoped_route_configurations_list`. + // :ref:`scoped_route_configurations_list`. // // Upon receiving a request's headers, the Router will build a key using the algorithm specified // by this message. This key will be used to look up the routing table (i.e., the - // :ref:`RouteConfiguration`) to use for the request. + // :ref:`RouteConfiguration`) to use for the request. message ScopeKeyBuilder { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.network.http_connection_manager.v3.ScopedRoutes.ScopeKeyBuilder"; @@ -834,7 +834,7 @@ message ScopedRoutes { } // The final(built) scope key consists of the ordered union of these fragments, which are compared in order with the - // fragments of a :ref:`ScopedRouteConfiguration`. + // fragments of a :ref:`ScopedRouteConfiguration`. // A missing fragment during comparison will make the key invalid, i.e., the computed key doesn't match any key. repeated FragmentBuilder fragments = 1 [(validate.rules).repeated = {min_items: 1}]; } @@ -857,14 +857,14 @@ message ScopedRoutes { // The set of routing scopes corresponding to the HCM. A scope is assigned to a request by // matching a key constructed from the request's attributes according to the algorithm specified // by the - // :ref:`ScopeKeyBuilder` + // :ref:`ScopeKeyBuilder` // in this message. ScopedRouteConfigurationsList scoped_route_configurations_list = 4; // The set of routing scopes associated with the HCM will be dynamically loaded via the SRDS // API. A scope is assigned to a request by matching a key constructed from the request's // attributes according to the algorithm specified by the - // :ref:`ScopeKeyBuilder` + // :ref:`ScopeKeyBuilder` // in this message. ScopedRds scoped_rds = 5; } @@ -902,7 +902,7 @@ message HttpFilter { // filters for further documentation. // // To support configuring a :ref:`match tree `, use an - // :ref:`ExtensionWithMatcher ` + // :ref:`ExtensionWithMatcher ` // with the desired HTTP filter. // [#extension-category: envoy.filters.http] google.protobuf.Any typed_config = 4; @@ -912,7 +912,7 @@ message HttpFilter { // Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). // // To support configuring a :ref:`match tree `, use an - // :ref:`ExtensionWithMatcher ` + // :ref:`ExtensionWithMatcher ` // with the desired HTTP filter. This works for both the default filter configuration as well // as for filters provided via the API. config.core.v4alpha.ExtensionConfigSource config_discovery = 5; diff --git a/api/envoy/extensions/filters/network/local_ratelimit/v3/local_rate_limit.proto b/api/envoy/extensions/filters/network/local_ratelimit/v3/local_rate_limit.proto index 37eb8c62d0e29..3ee3655b7c3c9 100644 --- a/api/envoy/extensions/filters/network/local_ratelimit/v3/local_rate_limit.proto +++ b/api/envoy/extensions/filters/network/local_ratelimit/v3/local_rate_limit.proto @@ -36,7 +36,7 @@ message LocalRateLimit { // // .. note:: // In the current implementation the token bucket's :ref:`fill_interval - // ` must be >= 50ms to avoid too aggressive + // ` must be >= 50ms to avoid too aggressive // refills. type.v3.TokenBucket token_bucket = 2 [(validate.rules).message = {required: true}]; diff --git a/api/envoy/extensions/filters/network/postgres_proxy/v3alpha/postgres_proxy.proto b/api/envoy/extensions/filters/network/postgres_proxy/v3alpha/postgres_proxy.proto index 6efe3e0ccf88a..8fe98f269626d 100644 --- a/api/envoy/extensions/filters/network/postgres_proxy/v3alpha/postgres_proxy.proto +++ b/api/envoy/extensions/filters/network/postgres_proxy/v3alpha/postgres_proxy.proto @@ -33,7 +33,7 @@ message PostgresProxy { // terminate SSL session, but will pass all the packets to the upstream server. // If the value is true, the Postgres proxy filter will try to terminate SSL // session. In order to do that, the filter chain must use :ref:`starttls transport socket - // `. + // `. // If the filter does not manage to terminate the SSL session, it will close the connection from the client. // Refer to official documentation for details // `SSL Session Encryption Message Flow `_. diff --git a/api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto b/api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto index 746fea3e37190..df67ccf009f44 100644 --- a/api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto +++ b/api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto @@ -242,7 +242,7 @@ message RedisProxy { // * ``get abc:users`` would retrieve the key 'abc:users' from cluster_b. // * ``get ab:users`` would retrieve the key 'ab:users' from cluster_a. // * ``get z:users`` would return a NoUpstreamHost error. A :ref:`catch-all - // route` + // route` // would have retrieved the key from that cluster instead. // // See the :ref:`configuration section @@ -303,7 +303,7 @@ message RedisProxy { } // RedisProtocolOptions specifies Redis upstream protocol options. This object is used in -// :ref:`typed_extension_protocol_options`, +// :ref:`typed_extension_protocol_options`, // keyed by the name `envoy.filters.network.redis_proxy`. message RedisProtocolOptions { option (udpa.annotations.versioning).previous_message_type = diff --git a/api/envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3alpha/sni_dynamic_forward_proxy.proto b/api/envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3alpha/sni_dynamic_forward_proxy.proto index 23b07f8a218d5..7f7eb57d5be64 100644 --- a/api/envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3alpha/sni_dynamic_forward_proxy.proto +++ b/api/envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3alpha/sni_dynamic_forward_proxy.proto @@ -25,7 +25,7 @@ message FilterConfig { // The DNS cache configuration that the filter will attach to. Note this // configuration must match that of associated :ref:`dynamic forward proxy // cluster configuration - // `. + // `. common.dynamic_forward_proxy.v3.DnsCacheConfig dns_cache_config = 1 [(validate.rules).message = {required: true}]; diff --git a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto index 2fe3c8d10ec92..ac2cc7ef0256b 100644 --- a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto +++ b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto @@ -50,7 +50,7 @@ message TcpProxy { // in the upstream cluster with metadata matching what is set in this field will be considered // for load balancing. Note that this will be merged with what's provided in // :ref:`TcpProxy.metadata_match - // `, with values + // `, with values // here taking precedence. The filter name should be specified as *envoy.lb*. config.core.v3.Metadata metadata_match = 3; } diff --git a/api/envoy/extensions/filters/network/tcp_proxy/v4alpha/tcp_proxy.proto b/api/envoy/extensions/filters/network/tcp_proxy/v4alpha/tcp_proxy.proto index 4d4b6e83bac52..95f2c26c888ca 100644 --- a/api/envoy/extensions/filters/network/tcp_proxy/v4alpha/tcp_proxy.proto +++ b/api/envoy/extensions/filters/network/tcp_proxy/v4alpha/tcp_proxy.proto @@ -50,7 +50,7 @@ message TcpProxy { // in the upstream cluster with metadata matching what is set in this field will be considered // for load balancing. Note that this will be merged with what's provided in // :ref:`TcpProxy.metadata_match - // `, with values + // `, with values // here taking precedence. The filter name should be specified as *envoy.lb*. config.core.v4alpha.Metadata metadata_match = 3; } diff --git a/api/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v3/rate_limit.proto b/api/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v3/rate_limit.proto index c93b4d1e8e5af..8583bbe4b468c 100644 --- a/api/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v3/rate_limit.proto +++ b/api/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v3/rate_limit.proto @@ -29,7 +29,7 @@ message RateLimit { // Specifies the rate limit configuration stage. Each configured rate limit filter performs a // rate limit check using descriptors configured in the - // :ref:`envoy_api_msg_extensions.filters.network.thrift_proxy.v3.RouteAction` for the request. + // :ref:`envoy_v3_api_msg_extensions.filters.network.thrift_proxy.v3.RouteAction` for the request. // Only those entries with a matching stage number are used for a given filter. If not set, the // default stage number is 0. // diff --git a/api/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v4alpha/rate_limit.proto b/api/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v4alpha/rate_limit.proto index ed49380f83181..ed2a33290268e 100644 --- a/api/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v4alpha/rate_limit.proto +++ b/api/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v4alpha/rate_limit.proto @@ -29,7 +29,7 @@ message RateLimit { // Specifies the rate limit configuration stage. Each configured rate limit filter performs a // rate limit check using descriptors configured in the - // :ref:`envoy_api_msg_extensions.filters.network.thrift_proxy.v4alpha.RouteAction` for the request. + // :ref:`envoy_v3_api_msg_extensions.filters.network.thrift_proxy.v3.RouteAction` for the request. // Only those entries with a matching stage number are used for a given filter. If not set, the // default stage number is 0. // diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v3/route.proto b/api/envoy/extensions/filters/network/thrift_proxy/v3/route.proto index f00b0e6983d19..cf4c06ae1f19e 100644 --- a/api/envoy/extensions/filters/network/thrift_proxy/v3/route.proto +++ b/api/envoy/extensions/filters/network/thrift_proxy/v3/route.proto @@ -60,17 +60,17 @@ message RouteMatch { } // Inverts whatever matching is done in the :ref:`method_name - // ` or + // ` or // :ref:`service_name - // ` fields. + // ` fields. // Cannot be combined with wildcard matching as that would result in routes never being matched. // // .. note:: // // This does not invert matching done as part of the :ref:`headers field - // ` field. To + // ` field. To // invert header matching, see :ref:`invert_match - // `. + // `. bool invert = 3; // Specifies a set of headers that the route should match on. The router will check the request’s @@ -110,7 +110,7 @@ message RouteAction { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in // the upstream cluster with metadata matching what is set in this field will be considered. // Note that this will be merged with what's provided in :ref:`WeightedCluster.metadata_match - // `, + // `, // with values there taking precedence. Keys and values should be provided under the "envoy.lb" // metadata key. config.core.v3.Metadata metadata_match = 3; @@ -147,7 +147,7 @@ message WeightedCluster { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in // the upstream cluster with metadata matching what is set in this field, combined with what's // provided in :ref:`RouteAction's metadata_match - // `, + // `, // will be considered. Values here will take precedence. Keys and values should be provided // under the "envoy.lb" metadata key. config.core.v3.Metadata metadata_match = 3; diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v3/thrift_proxy.proto b/api/envoy/extensions/filters/network/thrift_proxy/v3/thrift_proxy.proto index 512f810e41398..4916330ec5f3a 100644 --- a/api/envoy/extensions/filters/network/thrift_proxy/v3/thrift_proxy.proto +++ b/api/envoy/extensions/filters/network/thrift_proxy/v3/thrift_proxy.proto @@ -64,11 +64,11 @@ message ThriftProxy { "envoy.config.filter.network.thrift_proxy.v2alpha1.ThriftProxy"; // Supplies the type of transport that the Thrift proxy should use. Defaults to - // :ref:`AUTO_TRANSPORT`. + // :ref:`AUTO_TRANSPORT`. TransportType transport = 2 [(validate.rules).enum = {defined_only: true}]; // Supplies the type of protocol that the Thrift proxy should use. Defaults to - // :ref:`AUTO_PROTOCOL`. + // :ref:`AUTO_PROTOCOL`. ProtocolType protocol = 3 [(validate.rules).enum = {defined_only: true}]; // The human readable prefix to use when emitting statistics. @@ -120,7 +120,7 @@ message ThriftFilter { // ThriftProtocolOptions specifies Thrift upstream protocol options. This object is used in // in -// :ref:`typed_extension_protocol_options`, +// :ref:`typed_extension_protocol_options`, // keyed by the name `envoy.filters.network.thrift_proxy`. message ThriftProtocolOptions { option (udpa.annotations.versioning).previous_message_type = @@ -128,13 +128,13 @@ message ThriftProtocolOptions { // Supplies the type of transport that the Thrift proxy should use for upstream connections. // Selecting - // :ref:`AUTO_TRANSPORT`, + // :ref:`AUTO_TRANSPORT`, // which is the default, causes the proxy to use the same transport as the downstream connection. TransportType transport = 1 [(validate.rules).enum = {defined_only: true}]; // Supplies the type of protocol that the Thrift proxy should use for upstream connections. // Selecting - // :ref:`AUTO_PROTOCOL`, + // :ref:`AUTO_PROTOCOL`, // which is the default, causes the proxy to use the same protocol as the downstream connection. ProtocolType protocol = 2 [(validate.rules).enum = {defined_only: true}]; } diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v4alpha/route.proto b/api/envoy/extensions/filters/network/thrift_proxy/v4alpha/route.proto index b73a78c4f2cc9..e638e9b8a2be8 100644 --- a/api/envoy/extensions/filters/network/thrift_proxy/v4alpha/route.proto +++ b/api/envoy/extensions/filters/network/thrift_proxy/v4alpha/route.proto @@ -60,17 +60,17 @@ message RouteMatch { } // Inverts whatever matching is done in the :ref:`method_name - // ` or + // ` or // :ref:`service_name - // ` fields. + // ` fields. // Cannot be combined with wildcard matching as that would result in routes never being matched. // // .. note:: // // This does not invert matching done as part of the :ref:`headers field - // ` field. To + // ` field. To // invert header matching, see :ref:`invert_match - // `. + // `. bool invert = 3; // Specifies a set of headers that the route should match on. The router will check the request’s @@ -110,7 +110,7 @@ message RouteAction { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in // the upstream cluster with metadata matching what is set in this field will be considered. // Note that this will be merged with what's provided in :ref:`WeightedCluster.metadata_match - // `, + // `, // with values there taking precedence. Keys and values should be provided under the "envoy.lb" // metadata key. config.core.v4alpha.Metadata metadata_match = 3; @@ -147,7 +147,7 @@ message WeightedCluster { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in // the upstream cluster with metadata matching what is set in this field, combined with what's // provided in :ref:`RouteAction's metadata_match - // `, + // `, // will be considered. Values here will take precedence. Keys and values should be provided // under the "envoy.lb" metadata key. config.core.v4alpha.Metadata metadata_match = 3; diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v4alpha/thrift_proxy.proto b/api/envoy/extensions/filters/network/thrift_proxy/v4alpha/thrift_proxy.proto index 4ae4e26e4dbfe..de399582869a0 100644 --- a/api/envoy/extensions/filters/network/thrift_proxy/v4alpha/thrift_proxy.proto +++ b/api/envoy/extensions/filters/network/thrift_proxy/v4alpha/thrift_proxy.proto @@ -64,11 +64,11 @@ message ThriftProxy { "envoy.extensions.filters.network.thrift_proxy.v3.ThriftProxy"; // Supplies the type of transport that the Thrift proxy should use. Defaults to - // :ref:`AUTO_TRANSPORT`. + // :ref:`AUTO_TRANSPORT`. TransportType transport = 2 [(validate.rules).enum = {defined_only: true}]; // Supplies the type of protocol that the Thrift proxy should use. Defaults to - // :ref:`AUTO_PROTOCOL`. + // :ref:`AUTO_PROTOCOL`. ProtocolType protocol = 3 [(validate.rules).enum = {defined_only: true}]; // The human readable prefix to use when emitting statistics. @@ -120,7 +120,7 @@ message ThriftFilter { // ThriftProtocolOptions specifies Thrift upstream protocol options. This object is used in // in -// :ref:`typed_extension_protocol_options`, +// :ref:`typed_extension_protocol_options`, // keyed by the name `envoy.filters.network.thrift_proxy`. message ThriftProtocolOptions { option (udpa.annotations.versioning).previous_message_type = @@ -128,13 +128,13 @@ message ThriftProtocolOptions { // Supplies the type of transport that the Thrift proxy should use for upstream connections. // Selecting - // :ref:`AUTO_TRANSPORT`, + // :ref:`AUTO_TRANSPORT`, // which is the default, causes the proxy to use the same transport as the downstream connection. TransportType transport = 1 [(validate.rules).enum = {defined_only: true}]; // Supplies the type of protocol that the Thrift proxy should use for upstream connections. // Selecting - // :ref:`AUTO_PROTOCOL`, + // :ref:`AUTO_PROTOCOL`, // which is the default, causes the proxy to use the same protocol as the downstream connection. ProtocolType protocol = 2 [(validate.rules).enum = {defined_only: true}]; } diff --git a/api/envoy/extensions/internal_redirect/allow_listed_routes/v3/allow_listed_routes_config.proto b/api/envoy/extensions/internal_redirect/allow_listed_routes/v3/allow_listed_routes_config.proto index 6880029e36ff5..90da16095fa95 100644 --- a/api/envoy/extensions/internal_redirect/allow_listed_routes/v3/allow_listed_routes_config.proto +++ b/api/envoy/extensions/internal_redirect/allow_listed_routes/v3/allow_listed_routes_config.proto @@ -16,7 +16,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.internal_redirect_predicates.allow_listed_routes] message AllowListedRoutesConfig { // The list of routes that's allowed as redirect target by this predicate, - // identified by the route's :ref:`name `. + // identified by the route's :ref:`name `. // Empty route names are not allowed. repeated string allowed_route_names = 1 [(validate.rules).repeated = {items {string {min_len: 1}}}]; diff --git a/api/envoy/extensions/transport_sockets/tls/v3/common.proto b/api/envoy/extensions/transport_sockets/tls/v3/common.proto index 46b9ad5c4337c..182dc4b81a5fc 100644 --- a/api/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/api/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -160,11 +160,11 @@ message TlsCertificate { config.core.v3.WatchedDirectory watched_directory = 7; // BoringSSL private key method provider. This is an alternative to :ref:`private_key - // ` field. This can't be + // ` field. This can't be // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key - // ` and + // ` and // :ref:`private_key_provider - // ` fields will result in an + // ` fields will result in an // error. PrivateKeyProvider private_key_provider = 6; @@ -190,7 +190,7 @@ message TlsSessionTicketKeys { // All keys are candidates for decrypting received tickets. This allows for easy rotation of keys // by, for example, putting the new key first, and the previous key second. // - // If :ref:`session_ticket_keys ` + // If :ref:`session_ticket_keys ` // is not specified, the TLS library will still support resuming sessions via tickets, but it will // use an internally-generated and managed key, so sessions cannot be resumed across hot restarts // or on different hosts. @@ -224,7 +224,7 @@ message CertificateValidationContext { // Connections where the certificate fails verification will be permitted. // For HTTP connections, the result of certificate verification can be used in route matching. ( - // see :ref:`validated ` ). + // see :ref:`validated ` ). ACCEPT_UNTRUSTED = 1; } @@ -237,13 +237,13 @@ message CertificateValidationContext { // for listeners). If not specified and a peer certificate is presented it will not be // verified. By default, a client certificate is optional, unless one of the additional // options (:ref:`require_client_certificate - // `, + // `, // :ref:`verify_certificate_spki - // `, + // `, // :ref:`verify_certificate_hash - // `, or + // `, or // :ref:`match_subject_alt_names - // `) is also + // `) is also // specified. // // It can optionally contain certificate revocation lists, in which case Envoy will verify @@ -289,15 +289,15 @@ message CertificateValidationContext { // // When both: // :ref:`verify_certificate_hash - // ` and + // ` and // :ref:`verify_certificate_spki - // ` are specified, + // ` are specified, // a hash matching value from either of the lists will result in the certificate being accepted. // // .. attention:: // // This option is preferred over :ref:`verify_certificate_hash - // `, + // `, // because SPKI is tied to a private key, so it doesn't change when the certificate // is renewed using the same private key. repeated string verify_certificate_spki = 3 @@ -325,9 +325,9 @@ message CertificateValidationContext { // // When both: // :ref:`verify_certificate_hash - // ` and + // ` and // :ref:`verify_certificate_spki - // ` are specified, + // ` are specified, // a hash matching value from either of the lists will result in the certificate being accepted. repeated string verify_certificate_hash = 2 [(validate.rules).repeated = {items {string {min_len: 64 max_bytes: 95}}}]; @@ -336,7 +336,7 @@ message CertificateValidationContext { // Subject Alternative Name of the presented certificate matches one of the specified matchers. // // When a certificate has wildcard DNS SAN entries, to match a specific client, it should be - // configured with exact match type in the :ref:`string matcher `. + // configured with exact match type in the :ref:`string matcher `. // For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com", // it should be configured as shown below. // @@ -349,7 +349,7 @@ message CertificateValidationContext { // // Subject Alternative Names are easily spoofable and verifying only them is insecure, // therefore this option must be used together with :ref:`trusted_ca - // `. + // `. repeated type.matcher.v3.StringMatcher match_subject_alt_names = 9; // [#not-implemented-hide:] Must present signed certificate time-stamp. diff --git a/api/envoy/extensions/transport_sockets/tls/v3/tls.proto b/api/envoy/extensions/transport_sockets/tls/v3/tls.proto index 2c5a8bf21d355..44325bdbee6a4 100644 --- a/api/envoy/extensions/transport_sockets/tls/v3/tls.proto +++ b/api/envoy/extensions/transport_sockets/tls/v3/tls.proto @@ -32,7 +32,7 @@ message UpstreamTlsContext { // .. attention:: // // Server certificate verification is not enabled by default. Configure - // :ref:`trusted_ca` to enable + // :ref:`trusted_ca` to enable // verification. CommonTlsContext common_tls_context = 1; @@ -101,8 +101,8 @@ message DownstreamTlsContext { // Config for controlling stateless TLS session resumption: setting this to true will cause the TLS // server to not issue TLS session tickets for the purposes of stateless TLS session resumption. // If set to false, the TLS server will issue TLS session tickets and encrypt/decrypt them using - // the keys specified through either :ref:`session_ticket_keys ` - // or :ref:`session_ticket_keys_sds_secret_config `. + // the keys specified through either :ref:`session_ticket_keys ` + // or :ref:`session_ticket_keys_sds_secret_config `. // If this config is set to false and no keys are explicitly configured, the TLS server will issue // TLS session tickets and encrypt/decrypt them using an internally-generated and managed key, with the // implication that sessions cannot be resumed across hot restarts or on different hosts. @@ -256,7 +256,7 @@ message CommonTlsContext { // Supplies the list of ALPN protocols that the listener should expose. In // practice this is likely to be set to one of two values (see the // :ref:`codec_type - // ` + // ` // parameter in the HTTP connection manager for more information): // // * "h2,http/1.1" If the listener is going to support both HTTP/2 and HTTP/1.1. diff --git a/api/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto b/api/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto index 3ee921e3f5dcd..cfb5e5c07e90c 100644 --- a/api/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto +++ b/api/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto @@ -41,8 +41,8 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // // Note that SPIFFE validator inherits and uses the following options from :ref:`CertificateValidationContext `. // -// - :ref:`allow_expired_certificate ` to allow expired certificates. -// - :ref:`match_subject_alt_names ` to match **URI** SAN of certificates. Unlike the default validator, SPIFFE validator only matches **URI** SAN (which equals to SVID in SPIFFE terminology) and ignore other SAN types. +// - :ref:`allow_expired_certificate ` to allow expired certificates. +// - :ref:`match_subject_alt_names ` to match **URI** SAN of certificates. Unlike the default validator, SPIFFE validator only matches **URI** SAN (which equals to SVID in SPIFFE terminology) and ignore other SAN types. // message SPIFFECertValidatorConfig { message TrustDomain { diff --git a/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto b/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto index 1f2e1acf10b16..0bc4bf9e963fa 100644 --- a/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto +++ b/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto @@ -162,11 +162,11 @@ message TlsCertificate { config.core.v4alpha.WatchedDirectory watched_directory = 7; // BoringSSL private key method provider. This is an alternative to :ref:`private_key - // ` field. This can't be + // ` field. This can't be // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key - // ` and + // ` and // :ref:`private_key_provider - // ` fields will result in an + // ` fields will result in an // error. PrivateKeyProvider private_key_provider = 6; @@ -192,7 +192,7 @@ message TlsSessionTicketKeys { // All keys are candidates for decrypting received tickets. This allows for easy rotation of keys // by, for example, putting the new key first, and the previous key second. // - // If :ref:`session_ticket_keys ` + // If :ref:`session_ticket_keys ` // is not specified, the TLS library will still support resuming sessions via tickets, but it will // use an internally-generated and managed key, so sessions cannot be resumed across hot restarts // or on different hosts. @@ -226,7 +226,7 @@ message CertificateValidationContext { // Connections where the certificate fails verification will be permitted. // For HTTP connections, the result of certificate verification can be used in route matching. ( - // see :ref:`validated ` ). + // see :ref:`validated ` ). ACCEPT_UNTRUSTED = 1; } @@ -239,13 +239,13 @@ message CertificateValidationContext { // for listeners). If not specified and a peer certificate is presented it will not be // verified. By default, a client certificate is optional, unless one of the additional // options (:ref:`require_client_certificate - // `, + // `, // :ref:`verify_certificate_spki - // `, + // `, // :ref:`verify_certificate_hash - // `, or + // `, or // :ref:`match_subject_alt_names - // `) is also + // `) is also // specified. // // It can optionally contain certificate revocation lists, in which case Envoy will verify @@ -291,15 +291,15 @@ message CertificateValidationContext { // // When both: // :ref:`verify_certificate_hash - // ` and + // ` and // :ref:`verify_certificate_spki - // ` are specified, + // ` are specified, // a hash matching value from either of the lists will result in the certificate being accepted. // // .. attention:: // // This option is preferred over :ref:`verify_certificate_hash - // `, + // `, // because SPKI is tied to a private key, so it doesn't change when the certificate // is renewed using the same private key. repeated string verify_certificate_spki = 3 @@ -327,9 +327,9 @@ message CertificateValidationContext { // // When both: // :ref:`verify_certificate_hash - // ` and + // ` and // :ref:`verify_certificate_spki - // ` are specified, + // ` are specified, // a hash matching value from either of the lists will result in the certificate being accepted. repeated string verify_certificate_hash = 2 [(validate.rules).repeated = {items {string {min_len: 64 max_bytes: 95}}}]; @@ -338,7 +338,7 @@ message CertificateValidationContext { // Subject Alternative Name of the presented certificate matches one of the specified matchers. // // When a certificate has wildcard DNS SAN entries, to match a specific client, it should be - // configured with exact match type in the :ref:`string matcher `. + // configured with exact match type in the :ref:`string matcher `. // For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com", // it should be configured as shown below. // @@ -351,7 +351,7 @@ message CertificateValidationContext { // // Subject Alternative Names are easily spoofable and verifying only them is insecure, // therefore this option must be used together with :ref:`trusted_ca - // `. + // `. repeated type.matcher.v4alpha.StringMatcher match_subject_alt_names = 9; // [#not-implemented-hide:] Must present signed certificate time-stamp. diff --git a/api/envoy/extensions/transport_sockets/tls/v4alpha/tls.proto b/api/envoy/extensions/transport_sockets/tls/v4alpha/tls.proto index 1a169a3476e59..7ddf55dfa6fab 100644 --- a/api/envoy/extensions/transport_sockets/tls/v4alpha/tls.proto +++ b/api/envoy/extensions/transport_sockets/tls/v4alpha/tls.proto @@ -31,7 +31,7 @@ message UpstreamTlsContext { // .. attention:: // // Server certificate verification is not enabled by default. Configure - // :ref:`trusted_ca` to enable + // :ref:`trusted_ca` to enable // verification. CommonTlsContext common_tls_context = 1; @@ -100,8 +100,8 @@ message DownstreamTlsContext { // Config for controlling stateless TLS session resumption: setting this to true will cause the TLS // server to not issue TLS session tickets for the purposes of stateless TLS session resumption. // If set to false, the TLS server will issue TLS session tickets and encrypt/decrypt them using - // the keys specified through either :ref:`session_ticket_keys ` - // or :ref:`session_ticket_keys_sds_secret_config `. + // the keys specified through either :ref:`session_ticket_keys ` + // or :ref:`session_ticket_keys_sds_secret_config `. // If this config is set to false and no keys are explicitly configured, the TLS server will issue // TLS session tickets and encrypt/decrypt them using an internally-generated and managed key, with the // implication that sessions cannot be resumed across hot restarts or on different hosts. @@ -261,7 +261,7 @@ message CommonTlsContext { // Supplies the list of ALPN protocols that the listener should expose. In // practice this is likely to be set to one of two values (see the // :ref:`codec_type - // ` + // ` // parameter in the HTTP connection manager for more information): // // * "h2,http/1.1" If the listener is going to support both HTTP/2 and HTTP/1.1. diff --git a/api/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto b/api/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto index 276b8ad6875be..8191318930be6 100644 --- a/api/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto +++ b/api/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto @@ -42,8 +42,8 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // // Note that SPIFFE validator inherits and uses the following options from :ref:`CertificateValidationContext `. // -// - :ref:`allow_expired_certificate ` to allow expired certificates. -// - :ref:`match_subject_alt_names ` to match **URI** SAN of certificates. Unlike the default validator, SPIFFE validator only matches **URI** SAN (which equals to SVID in SPIFFE terminology) and ignore other SAN types. +// - :ref:`allow_expired_certificate ` to allow expired certificates. +// - :ref:`match_subject_alt_names ` to match **URI** SAN of certificates. Unlike the default validator, SPIFFE validator only matches **URI** SAN (which equals to SVID in SPIFFE terminology) and ignore other SAN types. // message SPIFFECertValidatorConfig { option (udpa.annotations.versioning).previous_message_type = diff --git a/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto b/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto index 6f28a4f4b2340..2c8790c84faf5 100644 --- a/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto +++ b/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto @@ -17,7 +17,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HttpProtocolOptions specifies Http upstream protocol options. This object // is used in -// :ref:`typed_extension_protocol_options`, +// :ref:`typed_extension_protocol_options`, // keyed by the name `envoy.extensions.upstreams.http.v3.HttpProtocolOptions`. // // This controls what protocol(s) should be used for upstream and how said protocol(s) are configured. diff --git a/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto b/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto index 8545b77c1f0b2..9ceadcec907c6 100644 --- a/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto +++ b/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto @@ -18,7 +18,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // HttpProtocolOptions specifies Http upstream protocol options. This object // is used in -// :ref:`typed_extension_protocol_options`, +// :ref:`typed_extension_protocol_options`, // keyed by the name `envoy.extensions.upstreams.http.v3.HttpProtocolOptions`. // // This controls what protocol(s) should be used for upstream and how said protocol(s) are configured. diff --git a/api/envoy/service/accesslog/v3/als.proto b/api/envoy/service/accesslog/v3/als.proto index 5421c23049182..94a290ad4a325 100644 --- a/api/envoy/service/accesslog/v3/als.proto +++ b/api/envoy/service/accesslog/v3/als.proto @@ -49,7 +49,7 @@ message StreamAccessLogsMessage { config.core.v3.Node node = 1 [(validate.rules).message = {required: true}]; // The friendly name of the log configured in :ref:`CommonGrpcAccessLogConfig - // `. + // `. string log_name = 2 [(validate.rules).string = {min_len: 1}]; } diff --git a/api/envoy/service/accesslog/v4alpha/als.proto b/api/envoy/service/accesslog/v4alpha/als.proto index e2c8bbbc80689..ab0ba0e15213e 100644 --- a/api/envoy/service/accesslog/v4alpha/als.proto +++ b/api/envoy/service/accesslog/v4alpha/als.proto @@ -49,7 +49,7 @@ message StreamAccessLogsMessage { config.core.v4alpha.Node node = 1 [(validate.rules).message = {required: true}]; // The friendly name of the log configured in :ref:`CommonGrpcAccessLogConfig - // `. + // `. string log_name = 2 [(validate.rules).string = {min_len: 1}]; } diff --git a/api/envoy/service/auth/v3/attribute_context.proto b/api/envoy/service/auth/v3/attribute_context.proto index cdf3ee9f96e4b..452a1e1ad9a5f 100644 --- a/api/envoy/service/auth/v3/attribute_context.proto +++ b/api/envoy/service/auth/v3/attribute_context.proto @@ -148,7 +148,7 @@ message AttributeContext { // The HTTP request body in bytes. This is used instead of // :ref:`body ` when - // :ref:`pack_as_bytes ` + // :ref:`pack_as_bytes ` // is set to true. bytes raw_body = 12; } diff --git a/api/envoy/service/auth/v4alpha/attribute_context.proto b/api/envoy/service/auth/v4alpha/attribute_context.proto index a1bf9c9c62cb2..eed7a2e704ad0 100644 --- a/api/envoy/service/auth/v4alpha/attribute_context.proto +++ b/api/envoy/service/auth/v4alpha/attribute_context.proto @@ -148,7 +148,7 @@ message AttributeContext { // The HTTP request body in bytes. This is used instead of // :ref:`body ` when - // :ref:`pack_as_bytes ` + // :ref:`pack_as_bytes ` // is set to true. bytes raw_body = 12; } diff --git a/api/envoy/service/discovery/v3/discovery.proto b/api/envoy/service/discovery/v3/discovery.proto index 4a2547df39ff1..4a474d0fe2608 100644 --- a/api/envoy/service/discovery/v3/discovery.proto +++ b/api/envoy/service/discovery/v3/discovery.proto @@ -57,7 +57,7 @@ message DiscoveryRequest { // delta, where it is populated only for new explicit ACKs). string response_nonce = 5; - // This is populated when the previous :ref:`DiscoveryResponse ` + // This is populated when the previous :ref:`DiscoveryResponse ` // failed to update configuration. The *message* field in *error_details* provides the Envoy // internal exception related to the failure. It is only intended for consumption during manual // debugging, the string provided is not guaranteed to be stable across Envoy versions. @@ -195,7 +195,7 @@ message DeltaDiscoveryRequest { // Otherwise (unlike in DiscoveryRequest) response_nonce must be omitted. string response_nonce = 6; - // This is populated when the previous :ref:`DiscoveryResponse ` + // This is populated when the previous :ref:`DiscoveryResponse ` // failed to update configuration. The *message* field in *error_details* // provides the Envoy internal exception related to the failure. google.rpc.Status error_detail = 7; diff --git a/api/envoy/service/discovery/v4alpha/discovery.proto b/api/envoy/service/discovery/v4alpha/discovery.proto index 514eefb3e8894..bf8d48fc7a374 100644 --- a/api/envoy/service/discovery/v4alpha/discovery.proto +++ b/api/envoy/service/discovery/v4alpha/discovery.proto @@ -58,7 +58,7 @@ message DiscoveryRequest { // delta, where it is populated only for new explicit ACKs). string response_nonce = 5; - // This is populated when the previous :ref:`DiscoveryResponse ` + // This is populated when the previous :ref:`DiscoveryResponse ` // failed to update configuration. The *message* field in *error_details* provides the Envoy // internal exception related to the failure. It is only intended for consumption during manual // debugging, the string provided is not guaranteed to be stable across Envoy versions. @@ -198,7 +198,7 @@ message DeltaDiscoveryRequest { // Otherwise (unlike in DiscoveryRequest) response_nonce must be omitted. string response_nonce = 6; - // This is populated when the previous :ref:`DiscoveryResponse ` + // This is populated when the previous :ref:`DiscoveryResponse ` // failed to update configuration. The *message* field in *error_details* // provides the Envoy internal exception related to the failure. google.rpc.Status error_detail = 7; diff --git a/api/envoy/service/event_reporting/v3/event_reporting_service.proto b/api/envoy/service/event_reporting/v3/event_reporting_service.proto index 6f0b325902fb2..30c161a1c5309 100644 --- a/api/envoy/service/event_reporting/v3/event_reporting_service.proto +++ b/api/envoy/service/event_reporting/v3/event_reporting_service.proto @@ -53,8 +53,8 @@ message StreamEventsRequest { // // The following events are supported: // - // * :ref:`HealthCheckEvent ` - // * :ref:`OutlierDetectionEvent ` + // * :ref:`HealthCheckEvent ` + // * :ref:`OutlierDetectionEvent ` repeated google.protobuf.Any events = 2 [(validate.rules).repeated = {min_items: 1}]; } diff --git a/api/envoy/service/event_reporting/v4alpha/event_reporting_service.proto b/api/envoy/service/event_reporting/v4alpha/event_reporting_service.proto index 00755fddfac5b..6bff2a09c25ba 100644 --- a/api/envoy/service/event_reporting/v4alpha/event_reporting_service.proto +++ b/api/envoy/service/event_reporting/v4alpha/event_reporting_service.proto @@ -53,8 +53,8 @@ message StreamEventsRequest { // // The following events are supported: // - // * :ref:`HealthCheckEvent ` - // * :ref:`OutlierDetectionEvent ` + // * :ref:`HealthCheckEvent ` + // * :ref:`OutlierDetectionEvent ` repeated google.protobuf.Any events = 2 [(validate.rules).repeated = {min_items: 1}]; } diff --git a/api/envoy/service/health/v3/hds.proto b/api/envoy/service/health/v3/hds.proto index b8c231d062a7c..bb8781d5c3958 100644 --- a/api/envoy/service/health/v3/hds.proto +++ b/api/envoy/service/health/v3/hds.proto @@ -171,9 +171,9 @@ message ClusterHealthCheck { repeated LocalityEndpoints locality_endpoints = 3; - // Optional map that gets filtered by :ref:`health_checks.transport_socket_match_criteria ` + // Optional map that gets filtered by :ref:`health_checks.transport_socket_match_criteria ` // on connection when health checking. For more details, see - // :ref:`config.cluster.v3.Cluster.transport_socket_matches `. + // :ref:`config.cluster.v3.Cluster.transport_socket_matches `. repeated config.cluster.v3.Cluster.TransportSocketMatch transport_socket_matches = 4; } diff --git a/api/envoy/service/health/v4alpha/hds.proto b/api/envoy/service/health/v4alpha/hds.proto index 537d20b58cbb3..9ce239f5e9cf9 100644 --- a/api/envoy/service/health/v4alpha/hds.proto +++ b/api/envoy/service/health/v4alpha/hds.proto @@ -175,9 +175,9 @@ message ClusterHealthCheck { repeated LocalityEndpoints locality_endpoints = 3; - // Optional map that gets filtered by :ref:`health_checks.transport_socket_match_criteria ` + // Optional map that gets filtered by :ref:`health_checks.transport_socket_match_criteria ` // on connection when health checking. For more details, see - // :ref:`config.cluster.v3.Cluster.transport_socket_matches `. + // :ref:`config.cluster.v3.Cluster.transport_socket_matches `. repeated config.cluster.v4alpha.Cluster.TransportSocketMatch transport_socket_matches = 4; } diff --git a/api/envoy/service/load_stats/v3/lrs.proto b/api/envoy/service/load_stats/v3/lrs.proto index ca8377e1ca64c..0b565ebe72368 100644 --- a/api/envoy/service/load_stats/v3/lrs.proto +++ b/api/envoy/service/load_stats/v3/lrs.proto @@ -20,10 +20,10 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Load Reporting Service is an Envoy API to emit load reports. Envoy will initiate a bi-directional // stream with a management server. Upon connecting, the management server can send a -// :ref:`LoadStatsResponse ` to a node it is +// :ref:`LoadStatsResponse ` to a node it is // interested in getting the load reports for. Envoy in this node will start sending -// :ref:`LoadStatsRequest `. This is done periodically -// based on the :ref:`load reporting interval ` +// :ref:`LoadStatsRequest `. This is done periodically +// based on the :ref:`load reporting interval ` // For details, take a look at the :ref:`Load Reporting Service sandbox example `. service LoadReportingService { @@ -83,7 +83,7 @@ message LoadStatsResponse { // If true, the client should send all clusters it knows about. // Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their - // :ref:`client_features` field will honor this field. + // :ref:`client_features` field will honor this field. bool send_all_clusters = 4; // The minimum interval of time to collect stats over. This is only a minimum for two reasons: diff --git a/api/envoy/service/load_stats/v4alpha/lrs.proto b/api/envoy/service/load_stats/v4alpha/lrs.proto index 69eb3d707d5e1..f99b6555f4a17 100644 --- a/api/envoy/service/load_stats/v4alpha/lrs.proto +++ b/api/envoy/service/load_stats/v4alpha/lrs.proto @@ -20,10 +20,10 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // Load Reporting Service is an Envoy API to emit load reports. Envoy will initiate a bi-directional // stream with a management server. Upon connecting, the management server can send a -// :ref:`LoadStatsResponse ` to a node it is +// :ref:`LoadStatsResponse ` to a node it is // interested in getting the load reports for. Envoy in this node will start sending -// :ref:`LoadStatsRequest `. This is done periodically -// based on the :ref:`load reporting interval ` +// :ref:`LoadStatsRequest `. This is done periodically +// based on the :ref:`load reporting interval ` // For details, take a look at the :ref:`Load Reporting Service sandbox example `. service LoadReportingService { @@ -83,7 +83,7 @@ message LoadStatsResponse { // If true, the client should send all clusters it knows about. // Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their - // :ref:`client_features` field will honor this field. + // :ref:`client_features` field will honor this field. bool send_all_clusters = 4; // The minimum interval of time to collect stats over. This is only a minimum for two reasons: diff --git a/api/envoy/service/route/v3/rds.proto b/api/envoy/service/route/v3/rds.proto index 33ee1e6f8db56..62a7da4094936 100644 --- a/api/envoy/service/route/v3/rds.proto +++ b/api/envoy/service/route/v3/rds.proto @@ -43,11 +43,11 @@ service RouteDiscoveryService { // Virtual Host Discovery Service (VHDS) is used to dynamically update the list of virtual hosts for // a given RouteConfiguration. If VHDS is configured a virtual host list update will be triggered // during the processing of an HTTP request if a route for the request cannot be resolved. The -// :ref:`resource_names_subscribe ` +// :ref:`resource_names_subscribe ` // field contains a list of virtual host names or aliases to track. The contents of an alias would // be the contents of a *host* or *authority* header used to make an http request. An xDS server // will match an alias to a virtual host based on the content of :ref:`domains' -// ` field. The *resource_names_unsubscribe* field +// ` field. The *resource_names_unsubscribe* field // contains a list of virtual host names that have been :ref:`unsubscribed // ` from the routing table associated with the RouteConfiguration. service VirtualHostDiscoveryService { diff --git a/api/envoy/service/route/v3/srds.proto b/api/envoy/service/route/v3/srds.proto index 7a7f8f7d3a3fa..64fe45fee1fab 100644 --- a/api/envoy/service/route/v3/srds.proto +++ b/api/envoy/service/route/v3/srds.proto @@ -20,11 +20,11 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // * Routing :ref:`architecture overview ` // The Scoped Routes Discovery Service (SRDS) API distributes -// :ref:`ScopedRouteConfiguration` +// :ref:`ScopedRouteConfiguration` // resources. Each ScopedRouteConfiguration resource represents a "routing // scope" containing a mapping that allows the HTTP connection manager to // dynamically assign a routing table (specified via a -// :ref:`RouteConfiguration` message) to each +// :ref:`RouteConfiguration` message) to each // HTTP request. service ScopedRoutesDiscoveryService { option (envoy.annotations.resource).type = "envoy.config.route.v3.ScopedRouteConfiguration"; diff --git a/api/envoy/service/tap/v3/tap.proto b/api/envoy/service/tap/v3/tap.proto index 080aba215c10d..5d9866e570747 100644 --- a/api/envoy/service/tap/v3/tap.proto +++ b/api/envoy/service/tap/v3/tap.proto @@ -41,7 +41,7 @@ message StreamTapsRequest { config.core.v3.Node node = 1 [(validate.rules).message = {required: true}]; // The opaque identifier that was set in the :ref:`output config - // `. + // `. string tap_id = 2; } diff --git a/api/envoy/service/tap/v4alpha/tap.proto b/api/envoy/service/tap/v4alpha/tap.proto index a1654d18bebbf..4ef38d1bae983 100644 --- a/api/envoy/service/tap/v4alpha/tap.proto +++ b/api/envoy/service/tap/v4alpha/tap.proto @@ -41,7 +41,7 @@ message StreamTapsRequest { config.core.v4alpha.Node node = 1 [(validate.rules).message = {required: true}]; // The opaque identifier that was set in the :ref:`output config - // `. + // `. string tap_id = 2; } diff --git a/api/envoy/type/http/v3/path_transformation.proto b/api/envoy/type/http/v3/path_transformation.proto index 8a3c9ef5aaf80..0b3d72009f5ff 100644 --- a/api/envoy/type/http/v3/path_transformation.proto +++ b/api/envoy/type/http/v3/path_transformation.proto @@ -34,7 +34,7 @@ message PathTransformation { // Determines if adjacent slashes are merged into one. A common use case is for a request path // header. Using this option in `:ref: PathNormalizationOptions - // ` + // ` // will allow incoming requests with path `//dir///file` to match against route with `prefix` // match set to `/dir`. When using for header transformations, note that slash merging is not // part of `HTTP spec `_ and is provided for convenience. diff --git a/api/envoy/type/matcher/v3/metadata.proto b/api/envoy/type/matcher/v3/metadata.proto index a7184ee980508..68710dc718546 100644 --- a/api/envoy/type/matcher/v3/metadata.proto +++ b/api/envoy/type/matcher/v3/metadata.proto @@ -16,7 +16,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Metadata matcher] // MetadataMatcher provides a general interface to check if a given value is matched in -// :ref:`Metadata `. It uses `filter` and `path` to retrieve the value +// :ref:`Metadata `. It uses `filter` and `path` to retrieve the value // from the Metadata and then check if it's matched to the specified value. // // For example, for the following Metadata: @@ -71,8 +71,8 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // // An example use of MetadataMatcher is specifying additional metadata in envoy.filters.http.rbac to // enforce access control based on dynamic metadata in a request. See :ref:`Permission -// ` and :ref:`Principal -// `. +// ` and :ref:`Principal +// `. // [#next-major-version: MetadataMatcher should use StructMatcher] message MetadataMatcher { diff --git a/api/envoy/type/matcher/v4alpha/metadata.proto b/api/envoy/type/matcher/v4alpha/metadata.proto index 35af650391ff5..e61ba2754337b 100644 --- a/api/envoy/type/matcher/v4alpha/metadata.proto +++ b/api/envoy/type/matcher/v4alpha/metadata.proto @@ -16,7 +16,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: Metadata matcher] // MetadataMatcher provides a general interface to check if a given value is matched in -// :ref:`Metadata `. It uses `filter` and `path` to retrieve the value +// :ref:`Metadata `. It uses `filter` and `path` to retrieve the value // from the Metadata and then check if it's matched to the specified value. // // For example, for the following Metadata: @@ -71,8 +71,8 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // // An example use of MetadataMatcher is specifying additional metadata in envoy.filters.http.rbac to // enforce access control based on dynamic metadata in a request. See :ref:`Permission -// ` and :ref:`Principal -// `. +// ` and :ref:`Principal +// `. // [#next-major-version: MetadataMatcher should use StructMatcher] message MetadataMatcher { diff --git a/api/envoy/type/metadata/v3/metadata.proto b/api/envoy/type/metadata/v3/metadata.proto index b971d8debbe51..5dd58b23c6231 100644 --- a/api/envoy/type/metadata/v3/metadata.proto +++ b/api/envoy/type/metadata/v3/metadata.proto @@ -14,7 +14,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Metadata] // MetadataKey provides a general interface using `key` and `path` to retrieve value from -// :ref:`Metadata `. +// :ref:`Metadata `. // // For example, for the following Metadata: // @@ -77,20 +77,20 @@ message MetadataKind { "envoy.type.metadata.v2.MetadataKind.Request"; } - // Represents metadata from :ref:`the route`. + // Represents metadata from :ref:`the route`. message Route { option (udpa.annotations.versioning).previous_message_type = "envoy.type.metadata.v2.MetadataKind.Route"; } - // Represents metadata from :ref:`the upstream cluster`. + // Represents metadata from :ref:`the upstream cluster`. message Cluster { option (udpa.annotations.versioning).previous_message_type = "envoy.type.metadata.v2.MetadataKind.Cluster"; } // Represents metadata from :ref:`the upstream - // host`. + // host`. message Host { option (udpa.annotations.versioning).previous_message_type = "envoy.type.metadata.v2.MetadataKind.Host"; diff --git a/api/envoy/type/tracing/v3/custom_tag.proto b/api/envoy/type/tracing/v3/custom_tag.proto index bcebe5779ba15..ad99cafb22bf4 100644 --- a/api/envoy/type/tracing/v3/custom_tag.proto +++ b/api/envoy/type/tracing/v3/custom_tag.proto @@ -59,8 +59,8 @@ message CustomTag { } // Metadata type custom tag using - // :ref:`MetadataKey ` to retrieve the protobuf value - // from :ref:`Metadata `, and populate the tag value with + // :ref:`MetadataKey ` to retrieve the protobuf value + // from :ref:`Metadata `, and populate the tag value with // `the canonical JSON `_ // representation of it. message Metadata { diff --git a/docs/build.sh b/docs/build.sh index 8d2d79911df1b..df9c9c44f278f 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -132,14 +132,6 @@ function generate_api_rst() { generate_api_rst v3 -# Fixup anchors and references in v3 so they form a distinct namespace. -# TODO(htuch): Do this in protodoc generation in the future. -find "${GENERATED_RST_DIR}"/api-v3 -name "*.rst" -print0 | xargs -0 sed -i -e "s#envoy_api_#envoy_v3_api_#g" -find "${GENERATED_RST_DIR}"/api-v3 -name "*.rst" -print0 | xargs -0 sed -i -e "s#config_resource_monitors#v3_config_resource_monitors#g" - -# TODO(phlax): Remove this once above is removed -find "${GENERATED_RST_DIR}"/api-v3 -name "*.rst" -print0 | xargs -0 sed -i -e "s#envoy_v2_api_#envoy_api_#g" - # xDS protocol spec. mkdir -p "${GENERATED_RST_DIR}/api-docs" cp -f "${API_DIR}"/xds_protocol.rst "${GENERATED_RST_DIR}/api-docs/xds_protocol.rst" diff --git a/generated_api_shadow/envoy/admin/v3/clusters.proto b/generated_api_shadow/envoy/admin/v3/clusters.proto index b526fa31460f8..509280f466243 100644 --- a/generated_api_shadow/envoy/admin/v3/clusters.proto +++ b/generated_api_shadow/envoy/admin/v3/clusters.proto @@ -41,10 +41,10 @@ message ClusterStatus { // The success rate threshold used in the last interval. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *false*, all errors: externally and locally generated were used to calculate the threshold. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*, only externally generated errors were used to calculate the threshold. // The threshold is used to eject hosts based on their success rate. See // :ref:`Cluster outlier detection ` documentation for details. @@ -64,7 +64,7 @@ message ClusterStatus { // The success rate threshold used in the last interval when only locally originated failures were // taken into account and externally originated errors were treated as success. // This field should be interpreted only when - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*. The threshold is used to eject hosts based on their success rate. // See :ref:`Cluster outlier detection ` documentation for // details. @@ -101,10 +101,10 @@ message HostStatus { // Request success rate for this host over the last calculated interval. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *false*, all errors: externally and locally generated were used in success rate // calculation. If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*, only externally generated errors were used in success rate calculation. // See :ref:`Cluster outlier detection ` documentation for // details. @@ -127,7 +127,7 @@ message HostStatus { // interval when only locally originated errors are taken into account and externally originated // errors were treated as success. // This field should be interpreted only when - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*. // See :ref:`Cluster outlier detection ` documentation for // details. diff --git a/generated_api_shadow/envoy/admin/v3/config_dump.proto b/generated_api_shadow/envoy/admin/v3/config_dump.proto index 49c208537cd59..21e3e8c326728 100644 --- a/generated_api_shadow/envoy/admin/v3/config_dump.proto +++ b/generated_api_shadow/envoy/admin/v3/config_dump.proto @@ -53,11 +53,11 @@ message ConfigDump { // The following configurations are currently supported and will be dumped in the order given // below: // - // * *bootstrap*: :ref:`BootstrapConfigDump ` - // * *clusters*: :ref:`ClustersConfigDump ` - // * *endpoints*: :ref:`EndpointsConfigDump ` - // * *listeners*: :ref:`ListenersConfigDump ` - // * *routes*: :ref:`RoutesConfigDump ` + // * *bootstrap*: :ref:`BootstrapConfigDump ` + // * *clusters*: :ref:`ClustersConfigDump ` + // * *endpoints*: :ref:`EndpointsConfigDump ` + // * *listeners*: :ref:`ListenersConfigDump ` + // * *routes*: :ref:`RoutesConfigDump ` // // EDS Configuration will only be dumped by using parameter `?include_eds` // @@ -126,7 +126,7 @@ message ListenersConfigDump { "envoy.admin.v2alpha.ListenersConfigDump.DynamicListenerState"; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time + // :ref:`version_info ` field at the time // that the listener was loaded. In the future, discrete per-listener versions may be supported // by the API. string version_info = 1; @@ -174,7 +174,7 @@ message ListenersConfigDump { ClientResourceStatus client_status = 6; } - // This is the :ref:`version_info ` in the + // This is the :ref:`version_info ` in the // last processed LDS discovery response. If there are only static bootstrap listeners, this field // will be "". string version_info = 1; @@ -212,7 +212,7 @@ message ClustersConfigDump { "envoy.admin.v2alpha.ClustersConfigDump.DynamicCluster"; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time + // :ref:`version_info ` field at the time // that the cluster was loaded. In the future, discrete per-cluster versions may be supported by // the API. string version_info = 1; @@ -235,7 +235,7 @@ message ClustersConfigDump { ClientResourceStatus client_status = 5; } - // This is the :ref:`version_info ` in the + // This is the :ref:`version_info ` in the // last processed CDS discovery response. If there are only static bootstrap clusters, this field // will be "". string version_info = 1; @@ -280,7 +280,7 @@ message RoutesConfigDump { "envoy.admin.v2alpha.RoutesConfigDump.DynamicRouteConfig"; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time that + // :ref:`version_info ` field at the time that // the route configuration was loaded. string version_info = 1; @@ -340,7 +340,7 @@ message ScopedRoutesConfigDump { string name = 1; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time that + // :ref:`version_info ` field at the time that // the scoped routes configuration was loaded. string version_info = 2; @@ -450,7 +450,7 @@ message EndpointsConfigDump { // [#next-free-field: 6] message DynamicEndpointConfig { // [#not-implemented-hide:] This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time that + // :ref:`version_info ` field at the time that // the endpoint configuration was loaded. string version_info = 1; diff --git a/generated_api_shadow/envoy/admin/v4alpha/clusters.proto b/generated_api_shadow/envoy/admin/v4alpha/clusters.proto index 116dc226c25e5..12969a28d0082 100644 --- a/generated_api_shadow/envoy/admin/v4alpha/clusters.proto +++ b/generated_api_shadow/envoy/admin/v4alpha/clusters.proto @@ -41,10 +41,10 @@ message ClusterStatus { // The success rate threshold used in the last interval. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *false*, all errors: externally and locally generated were used to calculate the threshold. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*, only externally generated errors were used to calculate the threshold. // The threshold is used to eject hosts based on their success rate. See // :ref:`Cluster outlier detection ` documentation for details. @@ -64,7 +64,7 @@ message ClusterStatus { // The success rate threshold used in the last interval when only locally originated failures were // taken into account and externally originated errors were treated as success. // This field should be interpreted only when - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*. The threshold is used to eject hosts based on their success rate. // See :ref:`Cluster outlier detection ` documentation for // details. @@ -101,10 +101,10 @@ message HostStatus { // Request success rate for this host over the last calculated interval. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *false*, all errors: externally and locally generated were used in success rate // calculation. If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*, only externally generated errors were used in success rate calculation. // See :ref:`Cluster outlier detection ` documentation for // details. @@ -127,7 +127,7 @@ message HostStatus { // interval when only locally originated errors are taken into account and externally originated // errors were treated as success. // This field should be interpreted only when - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *true*. // See :ref:`Cluster outlier detection ` documentation for // details. diff --git a/generated_api_shadow/envoy/admin/v4alpha/config_dump.proto b/generated_api_shadow/envoy/admin/v4alpha/config_dump.proto index 2eb1e73cd382d..32c7e2d07ab39 100644 --- a/generated_api_shadow/envoy/admin/v4alpha/config_dump.proto +++ b/generated_api_shadow/envoy/admin/v4alpha/config_dump.proto @@ -53,11 +53,11 @@ message ConfigDump { // The following configurations are currently supported and will be dumped in the order given // below: // - // * *bootstrap*: :ref:`BootstrapConfigDump ` - // * *clusters*: :ref:`ClustersConfigDump ` - // * *endpoints*: :ref:`EndpointsConfigDump ` - // * *listeners*: :ref:`ListenersConfigDump ` - // * *routes*: :ref:`RoutesConfigDump ` + // * *bootstrap*: :ref:`BootstrapConfigDump ` + // * *clusters*: :ref:`ClustersConfigDump ` + // * *endpoints*: :ref:`EndpointsConfigDump ` + // * *listeners*: :ref:`ListenersConfigDump ` + // * *routes*: :ref:`RoutesConfigDump ` // // EDS Configuration will only be dumped by using parameter `?include_eds` // @@ -123,7 +123,7 @@ message ListenersConfigDump { "envoy.admin.v3.ListenersConfigDump.DynamicListenerState"; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time + // :ref:`version_info ` field at the time // that the listener was loaded. In the future, discrete per-listener versions may be supported // by the API. string version_info = 1; @@ -171,7 +171,7 @@ message ListenersConfigDump { ClientResourceStatus client_status = 6; } - // This is the :ref:`version_info ` in the + // This is the :ref:`version_info ` in the // last processed LDS discovery response. If there are only static bootstrap listeners, this field // will be "". string version_info = 1; @@ -208,7 +208,7 @@ message ClustersConfigDump { "envoy.admin.v3.ClustersConfigDump.DynamicCluster"; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time + // :ref:`version_info ` field at the time // that the cluster was loaded. In the future, discrete per-cluster versions may be supported by // the API. string version_info = 1; @@ -231,7 +231,7 @@ message ClustersConfigDump { ClientResourceStatus client_status = 5; } - // This is the :ref:`version_info ` in the + // This is the :ref:`version_info ` in the // last processed CDS discovery response. If there are only static bootstrap clusters, this field // will be "". string version_info = 1; @@ -275,7 +275,7 @@ message RoutesConfigDump { "envoy.admin.v3.RoutesConfigDump.DynamicRouteConfig"; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time that + // :ref:`version_info ` field at the time that // the route configuration was loaded. string version_info = 1; @@ -335,7 +335,7 @@ message ScopedRoutesConfigDump { string name = 1; // This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time that + // :ref:`version_info ` field at the time that // the scoped routes configuration was loaded. string version_info = 2; @@ -452,7 +452,7 @@ message EndpointsConfigDump { "envoy.admin.v3.EndpointsConfigDump.DynamicEndpointConfig"; // [#not-implemented-hide:] This is the per-resource version information. This version is currently taken from the - // :ref:`version_info ` field at the time that + // :ref:`version_info ` field at the time that // the endpoint configuration was loaded. string version_info = 1; diff --git a/generated_api_shadow/envoy/config/accesslog/v3/accesslog.proto b/generated_api_shadow/envoy/config/accesslog/v3/accesslog.proto index 0c53fe5d82457..dc3e611b6c1a0 100644 --- a/generated_api_shadow/envoy/config/accesslog/v3/accesslog.proto +++ b/generated_api_shadow/envoy/config/accesslog/v3/accesslog.proto @@ -170,8 +170,8 @@ message RuntimeFilter { // randomly sample based on the runtime key value alone. // *use_independent_randomness* can be used for logging kill switches within // complex nested :ref:`AndFilter - // ` and :ref:`OrFilter - // ` blocks that are easier to + // ` and :ref:`OrFilter + // ` blocks that are easier to // reason about from a probability perspective (i.e., setting to true will // cause the filter to behave like an independent random variable when // composed within logical operator filters). diff --git a/generated_api_shadow/envoy/config/accesslog/v4alpha/accesslog.proto b/generated_api_shadow/envoy/config/accesslog/v4alpha/accesslog.proto index 8aed1a8d4f4a4..7559a3b82c79f 100644 --- a/generated_api_shadow/envoy/config/accesslog/v4alpha/accesslog.proto +++ b/generated_api_shadow/envoy/config/accesslog/v4alpha/accesslog.proto @@ -169,8 +169,8 @@ message RuntimeFilter { // randomly sample based on the runtime key value alone. // *use_independent_randomness* can be used for logging kill switches within // complex nested :ref:`AndFilter - // ` and :ref:`OrFilter - // ` blocks that are easier to + // ` and :ref:`OrFilter + // ` blocks that are easier to // reason about from a probability perspective (i.e., setting to true will // cause the filter to behave like an independent random variable when // composed within logical operator filters). diff --git a/generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto b/generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto index 100ca31aa16c4..2294fe1290714 100644 --- a/generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto +++ b/generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto @@ -48,12 +48,12 @@ message Bootstrap { option (udpa.annotations.versioning).previous_message_type = "envoy.config.bootstrap.v2.Bootstrap.StaticResources"; - // Static :ref:`Listeners `. These listeners are + // Static :ref:`Listeners `. These listeners are // available regardless of LDS configuration. repeated listener.v3.Listener listeners = 1; // If a network based configuration source is specified for :ref:`cds_config - // `, it's necessary + // `, it's necessary // to have some initial cluster definitions available to allow Envoy to know // how to speak to the management server. These cluster definitions may not // use :ref:`EDS ` (i.e. they should be static @@ -61,7 +61,7 @@ message Bootstrap { repeated cluster.v3.Cluster clusters = 2; // These static secrets can be used by :ref:`SdsSecretConfig - // ` + // ` repeated envoy.extensions.transport_sockets.tls.v3.Secret secrets = 3; } @@ -72,7 +72,7 @@ message Bootstrap { reserved 4; - // All :ref:`Listeners ` are provided by a single + // All :ref:`Listeners ` are provided by a single // :ref:`LDS ` configuration source. core.v3.ConfigSource lds_config = 1; @@ -80,7 +80,7 @@ message Bootstrap { // [#not-implemented-hide:] string lds_resources_locator = 5; - // All post-bootstrap :ref:`Cluster ` definitions are + // All post-bootstrap :ref:`Cluster ` definitions are // provided by a single :ref:`CDS ` // configuration source. core.v3.ConfigSource cds_config = 2; @@ -91,10 +91,10 @@ message Bootstrap { // A single :ref:`ADS ` source may be optionally // specified. This must have :ref:`api_type - // ` :ref:`GRPC - // `. Only - // :ref:`ConfigSources ` that have - // the :ref:`ads ` field set will be + // ` :ref:`GRPC + // `. Only + // :ref:`ConfigSources ` that have + // the :ref:`ads ` field set will be // streamed on the ADS channel. core.v3.ApiConfigSource ads_config = 3; } @@ -150,7 +150,7 @@ message Bootstrap { ClusterManager cluster_manager = 4; // Health discovery service config option. - // (:ref:`core.ApiConfigSource `) + // (:ref:`core.ApiConfigSource `) core.v3.ApiConfigSource hds_config = 14; // Optional file system path to search for startup flag files. @@ -198,7 +198,7 @@ message Bootstrap { // // .. attention:: // This field has been deprecated in favor of :ref:`HttpConnectionManager.Tracing.provider - // `. + // `. trace.v3.Tracing tracing = 9 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -219,7 +219,7 @@ message Bootstrap { // Enable :ref:`stats for event dispatcher `, defaults to false. // Note that this records a value for each iteration of the event loop on every thread. This // should normally be minimal overhead, but when using - // :ref:`statsd `, it will send each observed value + // :ref:`statsd `, it will send each observed value // over the wire individually because the statsd protocol doesn't have any way to represent a // histogram summary. Be aware that this can be a very large volume of data. bool enable_dispatcher_stats = 16; @@ -237,13 +237,13 @@ message Bootstrap { // Optional proxy version which will be used to set the value of :ref:`server.version statistic // ` if specified. Envoy will not process this value, it will be sent as is to - // :ref:`stats sinks `. + // :ref:`stats sinks `. google.protobuf.UInt64Value stats_server_version_override = 19; // Always use TCP queries instead of UDP queries for DNS lookups. // This may be overridden on a per-cluster basis in cds_config, - // when :ref:`dns_resolvers ` and - // :ref:`use_tcp_for_dns_lookups ` are + // when :ref:`dns_resolvers ` and + // :ref:`use_tcp_for_dns_lookups ` are // specified. // Setting this value causes failure if the // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during @@ -289,7 +289,7 @@ message Bootstrap { // Global map of CertificateProvider instances. These instances are referred to by name in the // :ref:`CommonTlsContext.CertificateProviderInstance.instance_name - // ` + // ` // field. // [#not-implemented-hide:] map certificate_provider_instances = 25; @@ -313,7 +313,7 @@ message Admin { // The path to write the access log for the administration server. If no // access log is desired specify ‘/dev/null’. This is only required if - // :ref:`address ` is set. + // :ref:`address ` is set. // Deprecated in favor of *access_log* which offers more options. string access_log_path = 1 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -353,9 +353,9 @@ message ClusterManager { // this configuration). In order to enable :ref:`zone aware routing // ` this option must be set. // If *local_cluster_name* is defined then :ref:`clusters - // ` must be defined in the :ref:`Bootstrap + // ` must be defined in the :ref:`Bootstrap // static cluster resources - // `. This is unrelated to + // `. This is unrelated to // the :option:`--service-cluster` option which does not `affect zone aware // routing `_. string local_cluster_name = 1; @@ -369,8 +369,8 @@ message ClusterManager { // A management server endpoint to stream load stats to via // *StreamLoadStats*. This must have :ref:`api_type - // ` :ref:`GRPC - // `. + // ` :ref:`GRPC + // `. core.v3.ApiConfigSource load_stats_config = 4; } diff --git a/generated_api_shadow/envoy/config/bootstrap/v4alpha/bootstrap.proto b/generated_api_shadow/envoy/config/bootstrap/v4alpha/bootstrap.proto index 232e3135c4f06..328599cd7bb35 100644 --- a/generated_api_shadow/envoy/config/bootstrap/v4alpha/bootstrap.proto +++ b/generated_api_shadow/envoy/config/bootstrap/v4alpha/bootstrap.proto @@ -47,12 +47,12 @@ message Bootstrap { option (udpa.annotations.versioning).previous_message_type = "envoy.config.bootstrap.v3.Bootstrap.StaticResources"; - // Static :ref:`Listeners `. These listeners are + // Static :ref:`Listeners `. These listeners are // available regardless of LDS configuration. repeated listener.v4alpha.Listener listeners = 1; // If a network based configuration source is specified for :ref:`cds_config - // `, it's necessary + // `, it's necessary // to have some initial cluster definitions available to allow Envoy to know // how to speak to the management server. These cluster definitions may not // use :ref:`EDS ` (i.e. they should be static @@ -60,7 +60,7 @@ message Bootstrap { repeated cluster.v4alpha.Cluster clusters = 2; // These static secrets can be used by :ref:`SdsSecretConfig - // ` + // ` repeated envoy.extensions.transport_sockets.tls.v4alpha.Secret secrets = 3; } @@ -71,7 +71,7 @@ message Bootstrap { reserved 4; - // All :ref:`Listeners ` are provided by a single + // All :ref:`Listeners ` are provided by a single // :ref:`LDS ` configuration source. core.v4alpha.ConfigSource lds_config = 1; @@ -79,7 +79,7 @@ message Bootstrap { // [#not-implemented-hide:] string lds_resources_locator = 5; - // All post-bootstrap :ref:`Cluster ` definitions are + // All post-bootstrap :ref:`Cluster ` definitions are // provided by a single :ref:`CDS ` // configuration source. core.v4alpha.ConfigSource cds_config = 2; @@ -90,10 +90,10 @@ message Bootstrap { // A single :ref:`ADS ` source may be optionally // specified. This must have :ref:`api_type - // ` :ref:`GRPC - // `. Only - // :ref:`ConfigSources ` that have - // the :ref:`ads ` field set will be + // ` :ref:`GRPC + // `. Only + // :ref:`ConfigSources ` that have + // the :ref:`ads ` field set will be // streamed on the ADS channel. core.v4alpha.ApiConfigSource ads_config = 3; } @@ -151,7 +151,7 @@ message Bootstrap { ClusterManager cluster_manager = 4; // Health discovery service config option. - // (:ref:`core.ApiConfigSource `) + // (:ref:`core.ApiConfigSource `) core.v4alpha.ApiConfigSource hds_config = 14; // Optional file system path to search for startup flag files. @@ -196,7 +196,7 @@ message Bootstrap { // // .. attention:: // This field has been deprecated in favor of :ref:`HttpConnectionManager.Tracing.provider - // `. + // `. trace.v4alpha.Tracing hidden_envoy_deprecated_tracing = 9 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -217,7 +217,7 @@ message Bootstrap { // Enable :ref:`stats for event dispatcher `, defaults to false. // Note that this records a value for each iteration of the event loop on every thread. This // should normally be minimal overhead, but when using - // :ref:`statsd `, it will send each observed value + // :ref:`statsd `, it will send each observed value // over the wire individually because the statsd protocol doesn't have any way to represent a // histogram summary. Be aware that this can be a very large volume of data. bool enable_dispatcher_stats = 16; @@ -235,13 +235,13 @@ message Bootstrap { // Optional proxy version which will be used to set the value of :ref:`server.version statistic // ` if specified. Envoy will not process this value, it will be sent as is to - // :ref:`stats sinks `. + // :ref:`stats sinks `. google.protobuf.UInt64Value stats_server_version_override = 19; // Always use TCP queries instead of UDP queries for DNS lookups. // This may be overridden on a per-cluster basis in cds_config, - // when :ref:`dns_resolvers ` and - // :ref:`use_tcp_for_dns_lookups ` are + // when :ref:`dns_resolvers ` and + // :ref:`use_tcp_for_dns_lookups ` are // specified. // Setting this value causes failure if the // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during @@ -287,7 +287,7 @@ message Bootstrap { // Global map of CertificateProvider instances. These instances are referred to by name in the // :ref:`CommonTlsContext.CertificateProviderInstance.instance_name - // ` + // ` // field. // [#not-implemented-hide:] map certificate_provider_instances = 25; @@ -305,7 +305,7 @@ message Admin { // The path to write the access log for the administration server. If no // access log is desired specify ‘/dev/null’. This is only required if - // :ref:`address ` is set. + // :ref:`address ` is set. // Deprecated in favor of *access_log* which offers more options. string hidden_envoy_deprecated_access_log_path = 1 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -345,9 +345,9 @@ message ClusterManager { // this configuration). In order to enable :ref:`zone aware routing // ` this option must be set. // If *local_cluster_name* is defined then :ref:`clusters - // ` must be defined in the :ref:`Bootstrap + // ` must be defined in the :ref:`Bootstrap // static cluster resources - // `. This is unrelated to + // `. This is unrelated to // the :option:`--service-cluster` option which does not `affect zone aware // routing `_. string local_cluster_name = 1; @@ -361,8 +361,8 @@ message ClusterManager { // A management server endpoint to stream load stats to via // *StreamLoadStats*. This must have :ref:`api_type - // ` :ref:`GRPC - // `. + // ` :ref:`GRPC + // `. core.v4alpha.ApiConfigSource load_stats_config = 4; } diff --git a/generated_api_shadow/envoy/config/cluster/v3/circuit_breaker.proto b/generated_api_shadow/envoy/config/cluster/v3/circuit_breaker.proto index 96e69701cda21..82cd329b91a72 100644 --- a/generated_api_shadow/envoy/config/cluster/v3/circuit_breaker.proto +++ b/generated_api_shadow/envoy/config/cluster/v3/circuit_breaker.proto @@ -25,7 +25,7 @@ message CircuitBreakers { "envoy.api.v2.cluster.CircuitBreakers"; // A Thresholds defines CircuitBreaker settings for a - // :ref:`RoutingPriority`. + // :ref:`RoutingPriority`. // [#next-free-field: 9] message Thresholds { option (udpa.annotations.versioning).previous_message_type = @@ -49,7 +49,7 @@ message CircuitBreakers { google.protobuf.UInt32Value min_retry_concurrency = 2; } - // The :ref:`RoutingPriority` + // The :ref:`RoutingPriority` // the specified CircuitBreaker settings apply to. core.v3.RoutingPriority priority = 1 [(validate.rules).enum = {defined_only: true}]; @@ -96,10 +96,10 @@ message CircuitBreakers { google.protobuf.UInt32Value max_connection_pools = 7; } - // If multiple :ref:`Thresholds` - // are defined with the same :ref:`RoutingPriority`, + // If multiple :ref:`Thresholds` + // are defined with the same :ref:`RoutingPriority`, // the first one in the list is used. If no Thresholds is defined for a given - // :ref:`RoutingPriority`, the default values + // :ref:`RoutingPriority`, the default values // are used. repeated Thresholds thresholds = 1; } diff --git a/generated_api_shadow/envoy/config/cluster/v3/cluster.proto b/generated_api_shadow/envoy/config/cluster/v3/cluster.proto index 9a56980d7aa79..0e8a711900dd7 100644 --- a/generated_api_shadow/envoy/config/cluster/v3/cluster.proto +++ b/generated_api_shadow/envoy/config/cluster/v3/cluster.proto @@ -107,7 +107,7 @@ message Cluster { CLUSTER_PROVIDED = 6; // [#not-implemented-hide:] Use the new :ref:`load_balancing_policy - // ` field to determine the LB policy. + // ` field to determine the LB policy. // [#next-major-version: In the v3 API, we should consider deprecating the lb_policy field // and instead using the new load_balancing_policy field as the one and only mechanism for // configuring this.] @@ -126,8 +126,8 @@ message Cluster { // specified, the DNS resolver will first perform a lookup for addresses in // the IPv6 family and fallback to a lookup for addresses in the IPv4 family. // For cluster types other than - // :ref:`STRICT_DNS` and - // :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS` and + // :ref:`LOGICAL_DNS`, // this setting is // ignored. enum DnsLookupFamily { @@ -138,7 +138,7 @@ message Cluster { enum ClusterProtocolSelection { // Cluster can only operate on one of the possible upstream protocols (HTTP1.1, HTTP2). - // If :ref:`http2_protocol_options ` are + // If :ref:`http2_protocol_options ` are // present, HTTP2 will be used, otherwise HTTP1.1 will be used. USE_CONFIGURED_PROTOCOL = 0; @@ -236,7 +236,7 @@ message Cluster { // If KEYS_SUBSET is selected, subset selector matching is performed again with metadata // keys reduced to - // :ref:`fallback_keys_subset`. + // :ref:`fallback_keys_subset`. // It allows for a fallback to a different, less specific selector if some of the keys of // the selector are considered optional. KEYS_SUBSET = 4; @@ -265,30 +265,30 @@ message Cluster { [(validate.rules).enum = {defined_only: true}]; // Subset of - // :ref:`keys` used by - // :ref:`KEYS_SUBSET` + // :ref:`keys` used by + // :ref:`KEYS_SUBSET` // fallback policy. // It has to be a non empty list if KEYS_SUBSET fallback policy is selected. // For any other fallback policy the parameter is not used and should not be set. // Only values also present in - // :ref:`keys` are allowed, but + // :ref:`keys` are allowed, but // `fallback_keys_subset` cannot be equal to `keys`. repeated string fallback_keys_subset = 3; } // The behavior used when no endpoint subset matches the selected route's // metadata. The value defaults to - // :ref:`NO_FALLBACK`. + // :ref:`NO_FALLBACK`. LbSubsetFallbackPolicy fallback_policy = 1 [(validate.rules).enum = {defined_only: true}]; // Specifies the default subset of endpoints used during fallback if // fallback_policy is - // :ref:`DEFAULT_SUBSET`. + // :ref:`DEFAULT_SUBSET`. // Each field in default_subset is // compared to the matching LbEndpoint.Metadata under the *envoy.lb* // namespace. It is valid for no hosts to match, in which case the behavior // is the same as a fallback_policy of - // :ref:`NO_FALLBACK`. + // :ref:`NO_FALLBACK`. google.protobuf.Struct default_subset = 2; // For each entry, LbEndpoint.Metadata's @@ -396,16 +396,16 @@ message Cluster { // Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each // provided host) the better the request distribution will reflect the desired weights. Defaults // to 1024 entries, and limited to 8M entries. See also - // :ref:`maximum_ring_size`. + // :ref:`maximum_ring_size`. google.protobuf.UInt64Value minimum_ring_size = 1 [(validate.rules).uint64 = {lte: 8388608}]; // The hash function used to hash hosts onto the ketama ring. The value defaults to - // :ref:`XX_HASH`. + // :ref:`XX_HASH`. HashFunction hash_function = 3 [(validate.rules).enum = {defined_only: true}]; // Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered // to further constrain resource use. See also - // :ref:`minimum_ring_size`. + // :ref:`minimum_ring_size`. google.protobuf.UInt64Value maximum_ring_size = 4 [(validate.rules).uint64 = {lte: 8388608}]; } @@ -559,7 +559,7 @@ message Cluster { // Specifies the base interval between refreshes. This parameter is required and must be greater // than zero and less than - // :ref:`max_interval `. + // :ref:`max_interval `. google.protobuf.Duration base_interval = 1 [(validate.rules).duration = { required: true gt {nanos: 1000000} @@ -567,8 +567,8 @@ message Cluster { // Specifies the maximum interval between refreshes. This parameter is optional, but must be // greater than or equal to the - // :ref:`base_interval ` if set. The default - // is 10 times the :ref:`base_interval `. + // :ref:`base_interval ` if set. The default + // is 10 times the :ref:`base_interval `. google.protobuf.Duration max_interval = 2 [(validate.rules).duration = {gt {nanos: 1000000}}]; } @@ -630,9 +630,9 @@ message Cluster { // Configuration to use different transport sockets for different endpoints. // The entry of *envoy.transport_socket_match* in the - // :ref:`LbEndpoint.Metadata ` + // :ref:`LbEndpoint.Metadata ` // is used to match against the transport sockets as they appear in the list. The first - // :ref:`match ` is used. + // :ref:`match ` is used. // For example, with the following match // // .. code-block:: yaml @@ -652,7 +652,7 @@ message Cluster { // Connections to the endpoints whose metadata value under *envoy.transport_socket_match* // having "acceptMTLS"/"true" key/value pair use the "enableMTLS" socket configuration. // - // If a :ref:`socket match ` with empty match + // If a :ref:`socket match ` with empty match // criteria is provided, that always match any endpoint. For example, the "defaultToPlaintext" // socket match in case above. // @@ -674,7 +674,7 @@ message Cluster { // // This field can be used to specify custom transport socket configurations for health // checks by adding matching key/value pairs in a health check's - // :ref:`transport socket match criteria ` field. + // :ref:`transport socket match criteria ` field. // // [#comment:TODO(incfly): add a detailed architecture doc on intended usage.] repeated TransportSocketMatch transport_socket_matches = 43; @@ -682,7 +682,7 @@ message Cluster { // Supplies the name of the cluster which must be unique across all clusters. // The cluster name is used when emitting // :ref:`statistics ` if :ref:`alt_stat_name - // ` is not provided. + // ` is not provided. // Any ``:`` in the cluster name will be converted to ``_`` when emitting statistics. string name = 1 [(validate.rules).string = {min_len: 1}]; @@ -719,19 +719,19 @@ message Cluster { // The :ref:`load balancer type ` to use // when picking a host in the cluster. - // [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] + // [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] LbPolicy lb_policy = 6 [(validate.rules).enum = {defined_only: true not_in: 7}]; // Setting this is required for specifying members of - // :ref:`STATIC`, - // :ref:`STRICT_DNS` - // or :ref:`LOGICAL_DNS` clusters. + // :ref:`STATIC`, + // :ref:`STRICT_DNS` + // or :ref:`LOGICAL_DNS` clusters. // This field supersedes the *hosts* field in the v2 API. // // .. attention:: // // Setting this allows non-EDS cluster types to contain embedded EDS equivalent - // :ref:`endpoint assignments`. + // :ref:`endpoint assignments`. // endpoint.v3.ClusterLoadAssignment load_assignment = 33; @@ -753,12 +753,12 @@ message Cluster { // HTTP protocol options that are applied only to upstream HTTP connections. // These options apply to all HTTP versions. // This has been deprecated in favor of - // :ref:`upstream_http_protocol_options ` - // in the :ref:`http_protocol_options ` message. + // :ref:`upstream_http_protocol_options ` + // in the :ref:`http_protocol_options ` message. // upstream_http_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. // See :ref:`upstream_http_protocol_options - // ` + // ` // for example usage. core.v3.UpstreamHttpProtocolOptions upstream_http_protocol_options = 46 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -766,23 +766,23 @@ message Cluster { // Additional options when handling HTTP requests upstream. These options will be applicable to // both HTTP1 and HTTP2 requests. // This has been deprecated in favor of - // :ref:`common_http_protocol_options ` - // in the :ref:`http_protocol_options ` message. + // :ref:`common_http_protocol_options ` + // in the :ref:`http_protocol_options ` message. // common_http_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. // See :ref:`upstream_http_protocol_options - // ` + // ` // for example usage. core.v3.HttpProtocolOptions common_http_protocol_options = 29 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; // Additional options when handling HTTP1 requests. // This has been deprecated in favor of http_protocol_options fields in the in the - // :ref:`http_protocol_options ` message. + // :ref:`http_protocol_options ` message. // http_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. // See :ref:`upstream_http_protocol_options - // ` + // ` // for example usage. core.v3.Http1ProtocolOptions http_protocol_options = 13 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -794,11 +794,11 @@ message Cluster { // with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 // connections to happen over plain text. // This has been deprecated in favor of http2_protocol_options fields in the in the - // :ref:`http_protocol_options ` + // :ref:`http_protocol_options ` // message. http2_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. // See :ref:`upstream_http_protocol_options - // ` + // ` // for example usage. core.v3.Http2ProtocolOptions http2_protocol_options = 14 [ deprecated = true, @@ -814,24 +814,24 @@ message Cluster { map typed_extension_protocol_options = 36; // If the DNS refresh rate is specified and the cluster type is either - // :ref:`STRICT_DNS`, - // or :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, // this value is used as the cluster’s DNS refresh // rate. The value configured must be at least 1ms. If this setting is not specified, the // value defaults to 5000ms. For cluster types other than - // :ref:`STRICT_DNS` - // and :ref:`LOGICAL_DNS` + // :ref:`STRICT_DNS` + // and :ref:`LOGICAL_DNS` // this setting is ignored. google.protobuf.Duration dns_refresh_rate = 16 [(validate.rules).duration = {gt {nanos: 1000000}}]; // If the DNS failure refresh rate is specified and the cluster type is either - // :ref:`STRICT_DNS`, - // or :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, // this is used as the cluster’s DNS refresh rate when requests are failing. If this setting is // not specified, the failure refresh rate defaults to the DNS refresh rate. For cluster types - // other than :ref:`STRICT_DNS` and - // :ref:`LOGICAL_DNS` this setting is + // other than :ref:`STRICT_DNS` and + // :ref:`LOGICAL_DNS` this setting is // ignored. RefreshRate dns_failure_refresh_rate = 44; @@ -842,18 +842,18 @@ message Cluster { // The DNS IP address resolution policy. If this setting is not specified, the // value defaults to - // :ref:`AUTO`. + // :ref:`AUTO`. DnsLookupFamily dns_lookup_family = 17 [(validate.rules).enum = {defined_only: true}]; // If DNS resolvers are specified and the cluster type is either - // :ref:`STRICT_DNS`, - // or :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, // this value is used to specify the cluster’s dns resolvers. // If this setting is not specified, the value defaults to the default // resolver, which uses /etc/resolv.conf for configuration. For cluster types // other than - // :ref:`STRICT_DNS` - // and :ref:`LOGICAL_DNS` + // :ref:`STRICT_DNS` + // and :ref:`LOGICAL_DNS` // this setting is ignored. // Setting this value causes failure if the // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during @@ -873,7 +873,7 @@ message Cluster { OutlierDetection outlier_detection = 19; // The interval for removing stale hosts from a cluster type - // :ref:`ORIGINAL_DST`. + // :ref:`ORIGINAL_DST`. // Hosts are considered stale if they have not been used // as upstream destinations during this interval. New hosts are added // to original destination clusters on demand as new connections are @@ -883,7 +883,7 @@ message Cluster { // them remain open, saving the latency that would otherwise be spent // on opening new connections. If this setting is not specified, the // value defaults to 5000ms. For cluster types other than - // :ref:`ORIGINAL_DST` + // :ref:`ORIGINAL_DST` // this setting is ignored. google.protobuf.Duration cleanup_interval = 20 [(validate.rules).duration = {gt {}}]; @@ -897,9 +897,9 @@ message Cluster { // Optional configuration for the load balancing algorithm selected by // LbPolicy. Currently only - // :ref:`RING_HASH`, - // :ref:`MAGLEV` and - // :ref:`LEAST_REQUEST` + // :ref:`RING_HASH`, + // :ref:`MAGLEV` and + // :ref:`LEAST_REQUEST` // has additional configuration options. // Specifying ring_hash_lb_config or maglev_lb_config or least_request_lb_config without setting the corresponding // LbPolicy will generate an error at runtime. @@ -922,7 +922,7 @@ message Cluster { // Optional custom transport socket implementation to use for upstream connections. // To setup TLS, set a transport socket with name `tls` and - // :ref:`UpstreamTlsContexts ` in the `typed_config`. + // :ref:`UpstreamTlsContexts ` in the `typed_config`. // If no transport socket configuration is specified, new connections // will be set up with plaintext. core.v3.TransportSocket transport_socket = 24; @@ -937,9 +937,9 @@ message Cluster { // Determines how Envoy selects the protocol used to speak to upstream hosts. // This has been deprecated in favor of setting explicit protocol selection // in the :ref:`http_protocol_options - // ` message. + // ` message. // http_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. ClusterProtocolSelection protocol_selection = 26 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -972,8 +972,8 @@ message Cluster { repeated Filter filters = 40; // [#not-implemented-hide:] New mechanism for LB policy configuration. Used only if the - // :ref:`lb_policy` field has the value - // :ref:`LOAD_BALANCING_POLICY_CONFIG`. + // :ref:`lb_policy` field has the value + // :ref:`LOAD_BALANCING_POLICY_CONFIG`. LoadBalancingPolicy load_balancing_policy = 41; // [#not-implemented-hide:] @@ -1001,7 +1001,7 @@ message Cluster { // .. attention:: // // This field has been deprecated in favor of `timeout_budgets`, part of - // :ref:`track_cluster_stats `. + // :ref:`track_cluster_stats `. bool track_timeout_budgets = 47 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; diff --git a/generated_api_shadow/envoy/config/cluster/v3/outlier_detection.proto b/generated_api_shadow/envoy/config/cluster/v3/outlier_detection.proto index e69b446918543..b19e95db99b74 100644 --- a/generated_api_shadow/envoy/config/cluster/v3/outlier_detection.proto +++ b/generated_api_shadow/envoy/config/cluster/v3/outlier_detection.proto @@ -35,7 +35,7 @@ message OutlierDetection { // The base time that a host is ejected for. The real time is equal to the // base time multiplied by the number of times the host has been ejected and is - // capped by :ref:`max_ejection_time`. + // capped by :ref:`max_ejection_time`. // Defaults to 30000ms or 30s. google.protobuf.Duration base_ejection_time = 3 [(validate.rules).duration = {gt {}}]; @@ -87,16 +87,16 @@ message OutlierDetection { // Determines whether to distinguish local origin failures from external errors. If set to true // the following configuration parameters are taken into account: - // :ref:`consecutive_local_origin_failure`, - // :ref:`enforcing_consecutive_local_origin_failure` + // :ref:`consecutive_local_origin_failure`, + // :ref:`enforcing_consecutive_local_origin_failure` // and - // :ref:`enforcing_local_origin_success_rate`. + // :ref:`enforcing_local_origin_success_rate`. // Defaults to false. bool split_external_local_origin_errors = 12; // The number of consecutive locally originated failures before ejection // occurs. Defaults to 5. Parameter takes effect only when - // :ref:`split_external_local_origin_errors` + // :ref:`split_external_local_origin_errors` // is set to true. google.protobuf.UInt32Value consecutive_local_origin_failure = 13; @@ -104,7 +104,7 @@ message OutlierDetection { // is detected through consecutive locally originated failures. This setting can be // used to disable ejection or to ramp it up slowly. Defaults to 100. // Parameter takes effect only when - // :ref:`split_external_local_origin_errors` + // :ref:`split_external_local_origin_errors` // is set to true. google.protobuf.UInt32Value enforcing_consecutive_local_origin_failure = 14 [(validate.rules).uint32 = {lte: 100}]; @@ -113,7 +113,7 @@ message OutlierDetection { // is detected through success rate statistics for locally originated errors. // This setting can be used to disable ejection or to ramp it up slowly. Defaults to 100. // Parameter takes effect only when - // :ref:`split_external_local_origin_errors` + // :ref:`split_external_local_origin_errors` // is set to true. google.protobuf.UInt32Value enforcing_local_origin_success_rate = 15 [(validate.rules).uint32 = {lte: 100}]; @@ -150,8 +150,8 @@ message OutlierDetection { // this host. Defaults to 50. google.protobuf.UInt32Value failure_percentage_request_volume = 20; - // The maximum time that a host is ejected for. See :ref:`base_ejection_time` + // The maximum time that a host is ejected for. See :ref:`base_ejection_time` // for more information. If not specified, the default value (300000ms or 300s) or - // :ref:`base_ejection_time` value is applied, whatever is larger. + // :ref:`base_ejection_time` value is applied, whatever is larger. google.protobuf.Duration max_ejection_time = 21 [(validate.rules).duration = {gt {}}]; } diff --git a/generated_api_shadow/envoy/config/cluster/v4alpha/circuit_breaker.proto b/generated_api_shadow/envoy/config/cluster/v4alpha/circuit_breaker.proto index 57a263a70d2e1..36aebb8977800 100644 --- a/generated_api_shadow/envoy/config/cluster/v4alpha/circuit_breaker.proto +++ b/generated_api_shadow/envoy/config/cluster/v4alpha/circuit_breaker.proto @@ -25,7 +25,7 @@ message CircuitBreakers { "envoy.config.cluster.v3.CircuitBreakers"; // A Thresholds defines CircuitBreaker settings for a - // :ref:`RoutingPriority`. + // :ref:`RoutingPriority`. // [#next-free-field: 9] message Thresholds { option (udpa.annotations.versioning).previous_message_type = @@ -49,7 +49,7 @@ message CircuitBreakers { google.protobuf.UInt32Value min_retry_concurrency = 2; } - // The :ref:`RoutingPriority` + // The :ref:`RoutingPriority` // the specified CircuitBreaker settings apply to. core.v4alpha.RoutingPriority priority = 1 [(validate.rules).enum = {defined_only: true}]; @@ -96,10 +96,10 @@ message CircuitBreakers { google.protobuf.UInt32Value max_connection_pools = 7; } - // If multiple :ref:`Thresholds` - // are defined with the same :ref:`RoutingPriority`, + // If multiple :ref:`Thresholds` + // are defined with the same :ref:`RoutingPriority`, // the first one in the list is used. If no Thresholds is defined for a given - // :ref:`RoutingPriority`, the default values + // :ref:`RoutingPriority`, the default values // are used. repeated Thresholds thresholds = 1; } diff --git a/generated_api_shadow/envoy/config/cluster/v4alpha/cluster.proto b/generated_api_shadow/envoy/config/cluster/v4alpha/cluster.proto index bad0bc43faaf7..6cbc477cee886 100644 --- a/generated_api_shadow/envoy/config/cluster/v4alpha/cluster.proto +++ b/generated_api_shadow/envoy/config/cluster/v4alpha/cluster.proto @@ -112,7 +112,7 @@ message Cluster { CLUSTER_PROVIDED = 6; // [#not-implemented-hide:] Use the new :ref:`load_balancing_policy - // ` field to determine the LB policy. + // ` field to determine the LB policy. // [#next-major-version: In the v3 API, we should consider deprecating the lb_policy field // and instead using the new load_balancing_policy field as the one and only mechanism for // configuring this.] @@ -125,8 +125,8 @@ message Cluster { // specified, the DNS resolver will first perform a lookup for addresses in // the IPv6 family and fallback to a lookup for addresses in the IPv4 family. // For cluster types other than - // :ref:`STRICT_DNS` and - // :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS` and + // :ref:`LOGICAL_DNS`, // this setting is // ignored. enum DnsLookupFamily { @@ -137,7 +137,7 @@ message Cluster { enum ClusterProtocolSelection { // Cluster can only operate on one of the possible upstream protocols (HTTP1.1, HTTP2). - // If :ref:`http2_protocol_options ` are + // If :ref:`http2_protocol_options ` are // present, HTTP2 will be used, otherwise HTTP1.1 will be used. USE_CONFIGURED_PROTOCOL = 0; @@ -235,7 +235,7 @@ message Cluster { // If KEYS_SUBSET is selected, subset selector matching is performed again with metadata // keys reduced to - // :ref:`fallback_keys_subset`. + // :ref:`fallback_keys_subset`. // It allows for a fallback to a different, less specific selector if some of the keys of // the selector are considered optional. KEYS_SUBSET = 4; @@ -264,30 +264,30 @@ message Cluster { [(validate.rules).enum = {defined_only: true}]; // Subset of - // :ref:`keys` used by - // :ref:`KEYS_SUBSET` + // :ref:`keys` used by + // :ref:`KEYS_SUBSET` // fallback policy. // It has to be a non empty list if KEYS_SUBSET fallback policy is selected. // For any other fallback policy the parameter is not used and should not be set. // Only values also present in - // :ref:`keys` are allowed, but + // :ref:`keys` are allowed, but // `fallback_keys_subset` cannot be equal to `keys`. repeated string fallback_keys_subset = 3; } // The behavior used when no endpoint subset matches the selected route's // metadata. The value defaults to - // :ref:`NO_FALLBACK`. + // :ref:`NO_FALLBACK`. LbSubsetFallbackPolicy fallback_policy = 1 [(validate.rules).enum = {defined_only: true}]; // Specifies the default subset of endpoints used during fallback if // fallback_policy is - // :ref:`DEFAULT_SUBSET`. + // :ref:`DEFAULT_SUBSET`. // Each field in default_subset is // compared to the matching LbEndpoint.Metadata under the *envoy.lb* // namespace. It is valid for no hosts to match, in which case the behavior // is the same as a fallback_policy of - // :ref:`NO_FALLBACK`. + // :ref:`NO_FALLBACK`. google.protobuf.Struct default_subset = 2; // For each entry, LbEndpoint.Metadata's @@ -395,16 +395,16 @@ message Cluster { // Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each // provided host) the better the request distribution will reflect the desired weights. Defaults // to 1024 entries, and limited to 8M entries. See also - // :ref:`maximum_ring_size`. + // :ref:`maximum_ring_size`. google.protobuf.UInt64Value minimum_ring_size = 1 [(validate.rules).uint64 = {lte: 8388608}]; // The hash function used to hash hosts onto the ketama ring. The value defaults to - // :ref:`XX_HASH`. + // :ref:`XX_HASH`. HashFunction hash_function = 3 [(validate.rules).enum = {defined_only: true}]; // Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered // to further constrain resource use. See also - // :ref:`minimum_ring_size`. + // :ref:`minimum_ring_size`. google.protobuf.UInt64Value maximum_ring_size = 4 [(validate.rules).uint64 = {lte: 8388608}]; } @@ -562,7 +562,7 @@ message Cluster { // Specifies the base interval between refreshes. This parameter is required and must be greater // than zero and less than - // :ref:`max_interval `. + // :ref:`max_interval `. google.protobuf.Duration base_interval = 1 [(validate.rules).duration = { required: true gt {nanos: 1000000} @@ -570,8 +570,8 @@ message Cluster { // Specifies the maximum interval between refreshes. This parameter is optional, but must be // greater than or equal to the - // :ref:`base_interval ` if set. The default - // is 10 times the :ref:`base_interval `. + // :ref:`base_interval ` if set. The default + // is 10 times the :ref:`base_interval `. google.protobuf.Duration max_interval = 2 [(validate.rules).duration = {gt {nanos: 1000000}}]; } @@ -638,9 +638,9 @@ message Cluster { // Configuration to use different transport sockets for different endpoints. // The entry of *envoy.transport_socket_match* in the - // :ref:`LbEndpoint.Metadata ` + // :ref:`LbEndpoint.Metadata ` // is used to match against the transport sockets as they appear in the list. The first - // :ref:`match ` is used. + // :ref:`match ` is used. // For example, with the following match // // .. code-block:: yaml @@ -660,7 +660,7 @@ message Cluster { // Connections to the endpoints whose metadata value under *envoy.transport_socket_match* // having "acceptMTLS"/"true" key/value pair use the "enableMTLS" socket configuration. // - // If a :ref:`socket match ` with empty match + // If a :ref:`socket match ` with empty match // criteria is provided, that always match any endpoint. For example, the "defaultToPlaintext" // socket match in case above. // @@ -682,7 +682,7 @@ message Cluster { // // This field can be used to specify custom transport socket configurations for health // checks by adding matching key/value pairs in a health check's - // :ref:`transport socket match criteria ` field. + // :ref:`transport socket match criteria ` field. // // [#comment:TODO(incfly): add a detailed architecture doc on intended usage.] repeated TransportSocketMatch transport_socket_matches = 43; @@ -690,7 +690,7 @@ message Cluster { // Supplies the name of the cluster which must be unique across all clusters. // The cluster name is used when emitting // :ref:`statistics ` if :ref:`alt_stat_name - // ` is not provided. + // ` is not provided. // Any ``:`` in the cluster name will be converted to ``_`` when emitting statistics. string name = 1 [(validate.rules).string = {min_len: 1}]; @@ -727,19 +727,19 @@ message Cluster { // The :ref:`load balancer type ` to use // when picking a host in the cluster. - // [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] + // [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] LbPolicy lb_policy = 6 [(validate.rules).enum = {defined_only: true not_in: 7}]; // Setting this is required for specifying members of - // :ref:`STATIC`, - // :ref:`STRICT_DNS` - // or :ref:`LOGICAL_DNS` clusters. + // :ref:`STATIC`, + // :ref:`STRICT_DNS` + // or :ref:`LOGICAL_DNS` clusters. // This field supersedes the *hosts* field in the v2 API. // // .. attention:: // // Setting this allows non-EDS cluster types to contain embedded EDS equivalent - // :ref:`endpoint assignments`. + // :ref:`endpoint assignments`. // endpoint.v3.ClusterLoadAssignment load_assignment = 33; @@ -761,12 +761,12 @@ message Cluster { // HTTP protocol options that are applied only to upstream HTTP connections. // These options apply to all HTTP versions. // This has been deprecated in favor of - // :ref:`upstream_http_protocol_options ` - // in the :ref:`http_protocol_options ` message. + // :ref:`upstream_http_protocol_options ` + // in the :ref:`http_protocol_options ` message. // upstream_http_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. // See :ref:`upstream_http_protocol_options - // ` + // ` // for example usage. core.v4alpha.UpstreamHttpProtocolOptions hidden_envoy_deprecated_upstream_http_protocol_options = 46 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -774,23 +774,23 @@ message Cluster { // Additional options when handling HTTP requests upstream. These options will be applicable to // both HTTP1 and HTTP2 requests. // This has been deprecated in favor of - // :ref:`common_http_protocol_options ` - // in the :ref:`http_protocol_options ` message. + // :ref:`common_http_protocol_options ` + // in the :ref:`http_protocol_options ` message. // common_http_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. // See :ref:`upstream_http_protocol_options - // ` + // ` // for example usage. core.v4alpha.HttpProtocolOptions hidden_envoy_deprecated_common_http_protocol_options = 29 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; // Additional options when handling HTTP1 requests. // This has been deprecated in favor of http_protocol_options fields in the in the - // :ref:`http_protocol_options ` message. + // :ref:`http_protocol_options ` message. // http_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. // See :ref:`upstream_http_protocol_options - // ` + // ` // for example usage. core.v4alpha.Http1ProtocolOptions hidden_envoy_deprecated_http_protocol_options = 13 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -802,11 +802,11 @@ message Cluster { // with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 // connections to happen over plain text. // This has been deprecated in favor of http2_protocol_options fields in the in the - // :ref:`http_protocol_options ` + // :ref:`http_protocol_options ` // message. http2_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. // See :ref:`upstream_http_protocol_options - // ` + // ` // for example usage. core.v4alpha.Http2ProtocolOptions hidden_envoy_deprecated_http2_protocol_options = 14 [ deprecated = true, @@ -822,24 +822,24 @@ message Cluster { map typed_extension_protocol_options = 36; // If the DNS refresh rate is specified and the cluster type is either - // :ref:`STRICT_DNS`, - // or :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, // this value is used as the cluster’s DNS refresh // rate. The value configured must be at least 1ms. If this setting is not specified, the // value defaults to 5000ms. For cluster types other than - // :ref:`STRICT_DNS` - // and :ref:`LOGICAL_DNS` + // :ref:`STRICT_DNS` + // and :ref:`LOGICAL_DNS` // this setting is ignored. google.protobuf.Duration dns_refresh_rate = 16 [(validate.rules).duration = {gt {nanos: 1000000}}]; // If the DNS failure refresh rate is specified and the cluster type is either - // :ref:`STRICT_DNS`, - // or :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, // this is used as the cluster’s DNS refresh rate when requests are failing. If this setting is // not specified, the failure refresh rate defaults to the DNS refresh rate. For cluster types - // other than :ref:`STRICT_DNS` and - // :ref:`LOGICAL_DNS` this setting is + // other than :ref:`STRICT_DNS` and + // :ref:`LOGICAL_DNS` this setting is // ignored. RefreshRate dns_failure_refresh_rate = 44; @@ -850,18 +850,18 @@ message Cluster { // The DNS IP address resolution policy. If this setting is not specified, the // value defaults to - // :ref:`AUTO`. + // :ref:`AUTO`. DnsLookupFamily dns_lookup_family = 17 [(validate.rules).enum = {defined_only: true}]; // If DNS resolvers are specified and the cluster type is either - // :ref:`STRICT_DNS`, - // or :ref:`LOGICAL_DNS`, + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, // this value is used to specify the cluster’s dns resolvers. // If this setting is not specified, the value defaults to the default // resolver, which uses /etc/resolv.conf for configuration. For cluster types // other than - // :ref:`STRICT_DNS` - // and :ref:`LOGICAL_DNS` + // :ref:`STRICT_DNS` + // and :ref:`LOGICAL_DNS` // this setting is ignored. // Setting this value causes failure if the // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during @@ -881,7 +881,7 @@ message Cluster { OutlierDetection outlier_detection = 19; // The interval for removing stale hosts from a cluster type - // :ref:`ORIGINAL_DST`. + // :ref:`ORIGINAL_DST`. // Hosts are considered stale if they have not been used // as upstream destinations during this interval. New hosts are added // to original destination clusters on demand as new connections are @@ -891,7 +891,7 @@ message Cluster { // them remain open, saving the latency that would otherwise be spent // on opening new connections. If this setting is not specified, the // value defaults to 5000ms. For cluster types other than - // :ref:`ORIGINAL_DST` + // :ref:`ORIGINAL_DST` // this setting is ignored. google.protobuf.Duration cleanup_interval = 20 [(validate.rules).duration = {gt {}}]; @@ -905,9 +905,9 @@ message Cluster { // Optional configuration for the load balancing algorithm selected by // LbPolicy. Currently only - // :ref:`RING_HASH`, - // :ref:`MAGLEV` and - // :ref:`LEAST_REQUEST` + // :ref:`RING_HASH`, + // :ref:`MAGLEV` and + // :ref:`LEAST_REQUEST` // has additional configuration options. // Specifying ring_hash_lb_config or maglev_lb_config or least_request_lb_config without setting the corresponding // LbPolicy will generate an error at runtime. @@ -930,7 +930,7 @@ message Cluster { // Optional custom transport socket implementation to use for upstream connections. // To setup TLS, set a transport socket with name `tls` and - // :ref:`UpstreamTlsContexts ` in the `typed_config`. + // :ref:`UpstreamTlsContexts ` in the `typed_config`. // If no transport socket configuration is specified, new connections // will be set up with plaintext. core.v4alpha.TransportSocket transport_socket = 24; @@ -945,9 +945,9 @@ message Cluster { // Determines how Envoy selects the protocol used to speak to upstream hosts. // This has been deprecated in favor of setting explicit protocol selection // in the :ref:`http_protocol_options - // ` message. + // ` message. // http_protocol_options can be set via the cluster's - // :ref:`extension_protocol_options`. + // :ref:`extension_protocol_options`. ClusterProtocolSelection hidden_envoy_deprecated_protocol_selection = 26 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; @@ -980,8 +980,8 @@ message Cluster { repeated Filter filters = 40; // [#not-implemented-hide:] New mechanism for LB policy configuration. Used only if the - // :ref:`lb_policy` field has the value - // :ref:`LOAD_BALANCING_POLICY_CONFIG`. + // :ref:`lb_policy` field has the value + // :ref:`LOAD_BALANCING_POLICY_CONFIG`. LoadBalancingPolicy load_balancing_policy = 41; // [#not-implemented-hide:] @@ -1009,7 +1009,7 @@ message Cluster { // .. attention:: // // This field has been deprecated in favor of `timeout_budgets`, part of - // :ref:`track_cluster_stats `. + // :ref:`track_cluster_stats `. bool hidden_envoy_deprecated_track_timeout_budgets = 47 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; diff --git a/generated_api_shadow/envoy/config/cluster/v4alpha/outlier_detection.proto b/generated_api_shadow/envoy/config/cluster/v4alpha/outlier_detection.proto index d4e76b4f135ae..a64c4b42247fc 100644 --- a/generated_api_shadow/envoy/config/cluster/v4alpha/outlier_detection.proto +++ b/generated_api_shadow/envoy/config/cluster/v4alpha/outlier_detection.proto @@ -35,7 +35,7 @@ message OutlierDetection { // The base time that a host is ejected for. The real time is equal to the // base time multiplied by the number of times the host has been ejected and is - // capped by :ref:`max_ejection_time`. + // capped by :ref:`max_ejection_time`. // Defaults to 30000ms or 30s. google.protobuf.Duration base_ejection_time = 3 [(validate.rules).duration = {gt {}}]; @@ -87,16 +87,16 @@ message OutlierDetection { // Determines whether to distinguish local origin failures from external errors. If set to true // the following configuration parameters are taken into account: - // :ref:`consecutive_local_origin_failure`, - // :ref:`enforcing_consecutive_local_origin_failure` + // :ref:`consecutive_local_origin_failure`, + // :ref:`enforcing_consecutive_local_origin_failure` // and - // :ref:`enforcing_local_origin_success_rate`. + // :ref:`enforcing_local_origin_success_rate`. // Defaults to false. bool split_external_local_origin_errors = 12; // The number of consecutive locally originated failures before ejection // occurs. Defaults to 5. Parameter takes effect only when - // :ref:`split_external_local_origin_errors` + // :ref:`split_external_local_origin_errors` // is set to true. google.protobuf.UInt32Value consecutive_local_origin_failure = 13; @@ -104,7 +104,7 @@ message OutlierDetection { // is detected through consecutive locally originated failures. This setting can be // used to disable ejection or to ramp it up slowly. Defaults to 100. // Parameter takes effect only when - // :ref:`split_external_local_origin_errors` + // :ref:`split_external_local_origin_errors` // is set to true. google.protobuf.UInt32Value enforcing_consecutive_local_origin_failure = 14 [(validate.rules).uint32 = {lte: 100}]; @@ -113,7 +113,7 @@ message OutlierDetection { // is detected through success rate statistics for locally originated errors. // This setting can be used to disable ejection or to ramp it up slowly. Defaults to 100. // Parameter takes effect only when - // :ref:`split_external_local_origin_errors` + // :ref:`split_external_local_origin_errors` // is set to true. google.protobuf.UInt32Value enforcing_local_origin_success_rate = 15 [(validate.rules).uint32 = {lte: 100}]; @@ -150,8 +150,8 @@ message OutlierDetection { // this host. Defaults to 50. google.protobuf.UInt32Value failure_percentage_request_volume = 20; - // The maximum time that a host is ejected for. See :ref:`base_ejection_time` + // The maximum time that a host is ejected for. See :ref:`base_ejection_time` // for more information. If not specified, the default value (300000ms or 300s) or - // :ref:`base_ejection_time` value is applied, whatever is larger. + // :ref:`base_ejection_time` value is applied, whatever is larger. google.protobuf.Duration max_ejection_time = 21 [(validate.rules).duration = {gt {}}]; } diff --git a/generated_api_shadow/envoy/config/core/v3/address.proto b/generated_api_shadow/envoy/config/core/v3/address.proto index a6fc6690a351e..06876d5f8e41e 100644 --- a/generated_api_shadow/envoy/config/core/v3/address.proto +++ b/generated_api_shadow/envoy/config/core/v3/address.proto @@ -37,7 +37,7 @@ message EnvoyInternalAddress { oneof address_name_specifier { option (validate.required) = true; - // [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. + // [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. string server_listener_name = 1; } } @@ -57,13 +57,13 @@ message SocketAddress { // to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` // to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: // It is possible to distinguish a Listener address via the prefix/suffix matching - // in :ref:`FilterChainMatch `.] When used - // within an upstream :ref:`BindConfig `, the address + // in :ref:`FilterChainMatch `.] When used + // within an upstream :ref:`BindConfig `, the address // controls the source address of outbound connections. For :ref:`clusters - // `, the cluster type determines whether the + // `, the cluster type determines whether the // address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS // (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized - // via :ref:`resolver_name `. + // via :ref:`resolver_name `. string address = 2 [(validate.rules).string = {min_len: 1}]; oneof port_specifier { @@ -72,7 +72,7 @@ message SocketAddress { uint32 port_value = 3 [(validate.rules).uint32 = {lte: 65535}]; // This is only valid if :ref:`resolver_name - // ` is specified below and the + // ` is specified below and the // named resolver is capable of named port resolution. string named_port = 4; } @@ -117,7 +117,7 @@ message BindConfig { // Whether to set the *IP_FREEBIND* option when creating the socket. When this // flag is set to true, allows the :ref:`source_address - // ` to be an IP address + // ` to be an IP address // that is not configured on the system running Envoy. When this flag is set // to false, the option *IP_FREEBIND* is disabled on the socket. When this // flag is not set (default), the socket is not modified, i.e. the option is diff --git a/generated_api_shadow/envoy/config/core/v3/backoff.proto b/generated_api_shadow/envoy/config/core/v3/backoff.proto index 55b504e716577..3ffa97bb0299c 100644 --- a/generated_api_shadow/envoy/config/core/v3/backoff.proto +++ b/generated_api_shadow/envoy/config/core/v3/backoff.proto @@ -21,7 +21,7 @@ message BackoffStrategy { // The base interval to be used for the next back off computation. It should // be greater than zero and less than or equal to :ref:`max_interval - // `. + // `. google.protobuf.Duration base_interval = 1 [(validate.rules).duration = { required: true gte {nanos: 1000000} @@ -29,8 +29,8 @@ message BackoffStrategy { // Specifies the maximum interval between retries. This parameter is optional, // but must be greater than or equal to the :ref:`base_interval - // ` if set. The default + // ` if set. The default // is 10 times the :ref:`base_interval - // `. + // `. google.protobuf.Duration max_interval = 2 [(validate.rules).duration = {gt {}}]; } diff --git a/generated_api_shadow/envoy/config/core/v3/base.proto b/generated_api_shadow/envoy/config/core/v3/base.proto index 3f2697a6fcd9e..69d019daeecc2 100644 --- a/generated_api_shadow/envoy/config/core/v3/base.proto +++ b/generated_api_shadow/envoy/config/core/v3/base.proto @@ -69,12 +69,12 @@ enum TrafficDirection { message Locality { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.Locality"; - // Region this :ref:`zone ` belongs to. + // Region this :ref:`zone ` belongs to. string region = 1; // Defines the local service zone where Envoy is running. Though optional, it // should be set if discovery service routing is used and the discovery - // service exposes :ref:`zone data `, + // service exposes :ref:`zone data `, // either in this message or via :option:`--service-zone`. The meaning of zone // is context dependent, e.g. `Availability Zone (AZ) // `_ @@ -150,10 +150,10 @@ message Node { // optional, it should be set if any of the following features are used: // :ref:`statsd `, :ref:`health check cluster // verification - // `, - // :ref:`runtime override directory `, + // `, + // :ref:`runtime override directory `, // :ref:`user agent addition - // `, + // `, // :ref:`HTTP global rate limiting `, // :ref:`CDS `, and :ref:`HTTP tracing // `, either in this message or via @@ -351,7 +351,7 @@ message DataSource { message RetryPolicy { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.RetryPolicy"; - // Specifies parameters that control :ref:`retry backoff strategy `. + // Specifies parameters that control :ref:`retry backoff strategy `. // This parameter is optional, in which case the default base interval is 1000 milliseconds. The // default maximum interval is 10 times the base interval. BackoffStrategy retry_back_off = 1; @@ -392,7 +392,7 @@ message AsyncDataSource { } // Configuration for transport socket in :ref:`listeners ` and -// :ref:`clusters `. If the configuration is +// :ref:`clusters `. If the configuration is // empty, a default transport socket implementation and configuration will be // chosen based on the platform and existence of tls_context. message TransportSocket { @@ -418,7 +418,7 @@ message TransportSocket { // .. note:: // // Parsing of the runtime key's data is implemented such that it may be represented as a -// :ref:`FractionalPercent ` proto represented as JSON/YAML +// :ref:`FractionalPercent ` proto represented as JSON/YAML // and may also be represented as an integer with the assumption that the value is an integral // percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse // as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. diff --git a/generated_api_shadow/envoy/config/core/v3/config_source.proto b/generated_api_shadow/envoy/config/core/v3/config_source.proto index ec284d40aa17d..c24a0a6537d85 100644 --- a/generated_api_shadow/envoy/config/core/v3/config_source.proto +++ b/generated_api_shadow/envoy/config/core/v3/config_source.proto @@ -112,7 +112,7 @@ message ApiConfigSource { } // Aggregated Discovery Service (ADS) options. This is currently empty, but when -// set in :ref:`ConfigSource ` can be used to +// set in :ref:`ConfigSource ` can be used to // specify that ADS is to be used. message AggregatedConfigSource { option (udpa.annotations.versioning).previous_message_type = @@ -121,7 +121,7 @@ message AggregatedConfigSource { // [#not-implemented-hide:] // Self-referencing config source options. This is currently empty, but when -// set in :ref:`ConfigSource ` can be used to +// set in :ref:`ConfigSource ` can be used to // specify that other data can be obtained from the same server. message SelfConfigSource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.SelfConfigSource"; @@ -147,7 +147,7 @@ message RateLimitSettings { // Configuration for :ref:`listeners `, :ref:`clusters // `, :ref:`routes -// `, :ref:`endpoints +// `, :ref:`endpoints // ` etc. may either be sourced from the // filesystem or from an xDS API source. Filesystem configs are watched with // inotify for updates. @@ -165,7 +165,7 @@ message ConfigSource { option (validate.required) = true; // Path on the filesystem to source and watch for configuration updates. - // When sourcing configuration for :ref:`secret `, + // When sourcing configuration for :ref:`secret `, // the certificate and key files are also watched for updates. // // .. note:: @@ -189,7 +189,7 @@ message ConfigSource { // [#not-implemented-hide:] // When set, the client will access the resources from the same server it got the // ConfigSource from, although not necessarily from the same stream. This is similar to the - // :ref:`ads` field, except that the client may use a + // :ref:`ads` field, except that the client may use a // different stream to the same server. As a result, this field can be used for things // like LRS that cannot be sent on an ADS stream. It can also be used to link from (e.g.) // LDS to RDS on the same server without requiring the management server to know its name diff --git a/generated_api_shadow/envoy/config/core/v3/grpc_service.proto b/generated_api_shadow/envoy/config/core/v3/grpc_service.proto index 6bc05009e42f3..b8e033da93830 100644 --- a/generated_api_shadow/envoy/config/core/v3/grpc_service.proto +++ b/generated_api_shadow/envoy/config/core/v3/grpc_service.proto @@ -24,7 +24,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: gRPC services] // gRPC service configuration. This is used by :ref:`ApiConfigSource -// ` and filter configurations. +// ` and filter configurations. // [#next-free-field: 6] message GrpcService { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.GrpcService"; @@ -34,8 +34,8 @@ message GrpcService { "envoy.api.v2.core.GrpcService.EnvoyGrpc"; // The name of the upstream gRPC cluster. SSL credentials will be supplied - // in the :ref:`Cluster ` :ref:`transport_socket - // `. + // in the :ref:`Cluster ` :ref:`transport_socket + // `. string cluster_name = 1 [(validate.rules).string = {min_len: 1}]; // The `:authority` header in the grpc request. If this field is not set, the authority header value will be `cluster_name`. @@ -230,7 +230,7 @@ message GrpcService { // The target URI when using the `Google C++ gRPC client // `_. SSL credentials will be supplied in - // :ref:`channel_credentials `. + // :ref:`channel_credentials `. string target_uri = 1 [(validate.rules).string = {min_len: 1}]; ChannelCredentials channel_credentials = 2; diff --git a/generated_api_shadow/envoy/config/core/v3/health_check.proto b/generated_api_shadow/envoy/config/core/v3/health_check.proto index 1927877b28f6a..dc7adc97a3257 100644 --- a/generated_api_shadow/envoy/config/core/v3/health_check.proto +++ b/generated_api_shadow/envoy/config/core/v3/health_check.proto @@ -82,7 +82,7 @@ message HealthCheck { // The value of the host header in the HTTP health check request. If // left empty (default value), the name of the cluster this health check is associated // with will be used. The host header can be customized for a specific endpoint by setting the - // :ref:`hostname ` field. + // :ref:`hostname ` field. string host = 1 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; // Specifies the HTTP path that will be requested during health checking. For example @@ -111,7 +111,7 @@ message HealthCheck { // Specifies a list of HTTP response statuses considered healthy. If provided, replaces default // 200-only policy - 200 must be included explicitly as needed. Ranges follow half-open - // semantics of :ref:`Int64Range `. The start and end of each + // semantics of :ref:`Int64Range `. The start and end of each // range are required. Only statuses in the range [100, 600) are allowed. repeated type.v3.Int64Range expected_statuses = 9; @@ -120,7 +120,7 @@ message HealthCheck { // An optional service name parameter which is used to validate the identity of // the health checked cluster using a :ref:`StringMatcher - // `. See the :ref:`architecture overview + // `. See the :ref:`architecture overview // ` for more information. type.matcher.v3.StringMatcher service_name_matcher = 11; @@ -176,7 +176,7 @@ message HealthCheck { // The value of the :authority header in the gRPC health check request. If // left empty (default value), the name of the cluster this health check is associated // with will be used. The authority header can be customized for a specific endpoint by setting - // the :ref:`hostname ` field. + // the :ref:`hostname ` field. string authority = 2 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; } @@ -210,7 +210,7 @@ message HealthCheck { // Specifies the ALPN protocols for health check connections. This is useful if the // corresponding upstream is using ALPN-based :ref:`FilterChainMatch - // ` along with different protocols for health checks + // ` along with different protocols for health checks // versus data connections. If empty, no ALPN protocols will be set on health check connections. repeated string alpn_protocols = 1; } @@ -344,7 +344,7 @@ message HealthCheck { TlsOptions tls_options = 21; // Optional key/value pairs that will be used to match a transport socket from those specified in the cluster's - // :ref:`tranport socket matches `. + // :ref:`tranport socket matches `. // For example, the following match criteria // // .. code-block:: yaml @@ -352,7 +352,7 @@ message HealthCheck { // transport_socket_match_criteria: // useMTLS: true // - // Will match the following :ref:`cluster socket match ` + // Will match the following :ref:`cluster socket match ` // // .. code-block:: yaml // @@ -365,13 +365,13 @@ message HealthCheck { // config: { ... } # tls socket configuration // // If this field is set, then for health checks it will supersede an entry of *envoy.transport_socket* in the - // :ref:`LbEndpoint.Metadata `. + // :ref:`LbEndpoint.Metadata `. // This allows using different transport socket capabilities for health checking versus proxying to the // endpoint. // // If the key/values pairs specified do not match any - // :ref:`transport socket matches `, - // the cluster's :ref:`transport socket ` + // :ref:`transport socket matches `, + // the cluster's :ref:`transport socket ` // will be used for health check socket configuration. google.protobuf.Struct transport_socket_match_criteria = 23; } diff --git a/generated_api_shadow/envoy/config/core/v3/protocol.proto b/generated_api_shadow/envoy/config/core/v3/protocol.proto index 2c0ee96803dcf..fb7c86245c6c8 100644 --- a/generated_api_shadow/envoy/config/core/v3/protocol.proto +++ b/generated_api_shadow/envoy/config/core/v3/protocol.proto @@ -101,7 +101,7 @@ message HttpProtocolOptions { // idle timeout is reached the connection will be closed. If the connection is an HTTP/2 // downstream connection a drain sequence will occur prior to closing the connection, see // :ref:`drain_timeout - // `. + // `. // Note that request based timeouts mean that HTTP/2 PINGs will not keep the connection alive. // If not specified, this defaults to 1 hour. To disable idle timeouts explicitly set this to 0. // @@ -111,14 +111,14 @@ message HttpProtocolOptions { // // If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" // is configured, this timeout is scaled for downstream connections according to the value for - // :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. + // :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. google.protobuf.Duration idle_timeout = 1; // The maximum duration of a connection. The duration is defined as a period since a connection // was established. If not set, there is no max duration. When max_connection_duration is reached // the connection will be closed. Drain sequence will occur prior to closing the connection if // if's applicable. See :ref:`drain_timeout - // `. + // `. // Note: not implemented for upstream connections. google.protobuf.Duration max_connection_duration = 3; diff --git a/generated_api_shadow/envoy/config/core/v4alpha/address.proto b/generated_api_shadow/envoy/config/core/v4alpha/address.proto index 0adf459fbb50f..63d4d4a145075 100644 --- a/generated_api_shadow/envoy/config/core/v4alpha/address.proto +++ b/generated_api_shadow/envoy/config/core/v4alpha/address.proto @@ -40,7 +40,7 @@ message EnvoyInternalAddress { oneof address_name_specifier { option (validate.required) = true; - // [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. + // [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. string server_listener_name = 1; } } @@ -60,13 +60,13 @@ message SocketAddress { // to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` // to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: // It is possible to distinguish a Listener address via the prefix/suffix matching - // in :ref:`FilterChainMatch `.] When used - // within an upstream :ref:`BindConfig `, the address + // in :ref:`FilterChainMatch `.] When used + // within an upstream :ref:`BindConfig `, the address // controls the source address of outbound connections. For :ref:`clusters - // `, the cluster type determines whether the + // `, the cluster type determines whether the // address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS // (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized - // via :ref:`resolver_name `. + // via :ref:`resolver_name `. string address = 2 [(validate.rules).string = {min_len: 1}]; oneof port_specifier { @@ -75,7 +75,7 @@ message SocketAddress { uint32 port_value = 3 [(validate.rules).uint32 = {lte: 65535}]; // This is only valid if :ref:`resolver_name - // ` is specified below and the + // ` is specified below and the // named resolver is capable of named port resolution. string named_port = 4; } @@ -120,7 +120,7 @@ message BindConfig { // Whether to set the *IP_FREEBIND* option when creating the socket. When this // flag is set to true, allows the :ref:`source_address - // ` to be an IP address + // ` to be an IP address // that is not configured on the system running Envoy. When this flag is set // to false, the option *IP_FREEBIND* is disabled on the socket. When this // flag is not set (default), the socket is not modified, i.e. the option is diff --git a/generated_api_shadow/envoy/config/core/v4alpha/backoff.proto b/generated_api_shadow/envoy/config/core/v4alpha/backoff.proto index 07a2bdff175e9..266d57f84e74a 100644 --- a/generated_api_shadow/envoy/config/core/v4alpha/backoff.proto +++ b/generated_api_shadow/envoy/config/core/v4alpha/backoff.proto @@ -22,7 +22,7 @@ message BackoffStrategy { // The base interval to be used for the next back off computation. It should // be greater than zero and less than or equal to :ref:`max_interval - // `. + // `. google.protobuf.Duration base_interval = 1 [(validate.rules).duration = { required: true gte {nanos: 1000000} @@ -30,8 +30,8 @@ message BackoffStrategy { // Specifies the maximum interval between retries. This parameter is optional, // but must be greater than or equal to the :ref:`base_interval - // ` if set. The default + // ` if set. The default // is 10 times the :ref:`base_interval - // `. + // `. google.protobuf.Duration max_interval = 2 [(validate.rules).duration = {gt {}}]; } diff --git a/generated_api_shadow/envoy/config/core/v4alpha/base.proto b/generated_api_shadow/envoy/config/core/v4alpha/base.proto index f8749144f07df..6c23e0e8022e1 100644 --- a/generated_api_shadow/envoy/config/core/v4alpha/base.proto +++ b/generated_api_shadow/envoy/config/core/v4alpha/base.proto @@ -68,12 +68,12 @@ enum TrafficDirection { message Locality { option (udpa.annotations.versioning).previous_message_type = "envoy.config.core.v3.Locality"; - // Region this :ref:`zone ` belongs to. + // Region this :ref:`zone ` belongs to. string region = 1; // Defines the local service zone where Envoy is running. Though optional, it // should be set if discovery service routing is used and the discovery - // service exposes :ref:`zone data `, + // service exposes :ref:`zone data `, // either in this message or via :option:`--service-zone`. The meaning of zone // is context dependent, e.g. `Availability Zone (AZ) // `_ @@ -153,10 +153,10 @@ message Node { // optional, it should be set if any of the following features are used: // :ref:`statsd `, :ref:`health check cluster // verification - // `, - // :ref:`runtime override directory `, + // `, + // :ref:`runtime override directory `, // :ref:`user agent addition - // `, + // `, // :ref:`HTTP global rate limiting `, // :ref:`CDS `, and :ref:`HTTP tracing // `, either in this message or via @@ -357,7 +357,7 @@ message DataSource { message RetryPolicy { option (udpa.annotations.versioning).previous_message_type = "envoy.config.core.v3.RetryPolicy"; - // Specifies parameters that control :ref:`retry backoff strategy `. + // Specifies parameters that control :ref:`retry backoff strategy `. // This parameter is optional, in which case the default base interval is 1000 milliseconds. The // default maximum interval is 10 times the base interval. BackoffStrategy retry_back_off = 1; @@ -399,7 +399,7 @@ message AsyncDataSource { } // Configuration for transport socket in :ref:`listeners ` and -// :ref:`clusters `. If the configuration is +// :ref:`clusters `. If the configuration is // empty, a default transport socket implementation and configuration will be // chosen based on the platform and existence of tls_context. message TransportSocket { @@ -427,7 +427,7 @@ message TransportSocket { // .. note:: // // Parsing of the runtime key's data is implemented such that it may be represented as a -// :ref:`FractionalPercent ` proto represented as JSON/YAML +// :ref:`FractionalPercent ` proto represented as JSON/YAML // and may also be represented as an integer with the assumption that the value is an integral // percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse // as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. diff --git a/generated_api_shadow/envoy/config/core/v4alpha/config_source.proto b/generated_api_shadow/envoy/config/core/v4alpha/config_source.proto index 4f7521c2de873..34f8a8bdb7a26 100644 --- a/generated_api_shadow/envoy/config/core/v4alpha/config_source.proto +++ b/generated_api_shadow/envoy/config/core/v4alpha/config_source.proto @@ -115,7 +115,7 @@ message ApiConfigSource { } // Aggregated Discovery Service (ADS) options. This is currently empty, but when -// set in :ref:`ConfigSource ` can be used to +// set in :ref:`ConfigSource ` can be used to // specify that ADS is to be used. message AggregatedConfigSource { option (udpa.annotations.versioning).previous_message_type = @@ -124,7 +124,7 @@ message AggregatedConfigSource { // [#not-implemented-hide:] // Self-referencing config source options. This is currently empty, but when -// set in :ref:`ConfigSource ` can be used to +// set in :ref:`ConfigSource ` can be used to // specify that other data can be obtained from the same server. message SelfConfigSource { option (udpa.annotations.versioning).previous_message_type = @@ -151,7 +151,7 @@ message RateLimitSettings { // Configuration for :ref:`listeners `, :ref:`clusters // `, :ref:`routes -// `, :ref:`endpoints +// `, :ref:`endpoints // ` etc. may either be sourced from the // filesystem or from an xDS API source. Filesystem configs are watched with // inotify for updates. @@ -169,7 +169,7 @@ message ConfigSource { option (validate.required) = true; // Path on the filesystem to source and watch for configuration updates. - // When sourcing configuration for :ref:`secret `, + // When sourcing configuration for :ref:`secret `, // the certificate and key files are also watched for updates. // // .. note:: @@ -193,7 +193,7 @@ message ConfigSource { // [#not-implemented-hide:] // When set, the client will access the resources from the same server it got the // ConfigSource from, although not necessarily from the same stream. This is similar to the - // :ref:`ads` field, except that the client may use a + // :ref:`ads` field, except that the client may use a // different stream to the same server. As a result, this field can be used for things // like LRS that cannot be sent on an ADS stream. It can also be used to link from (e.g.) // LDS to RDS on the same server without requiring the management server to know its name diff --git a/generated_api_shadow/envoy/config/core/v4alpha/grpc_service.proto b/generated_api_shadow/envoy/config/core/v4alpha/grpc_service.proto index c7c284f1bdcd8..973983386c2e8 100644 --- a/generated_api_shadow/envoy/config/core/v4alpha/grpc_service.proto +++ b/generated_api_shadow/envoy/config/core/v4alpha/grpc_service.proto @@ -23,7 +23,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: gRPC services] // gRPC service configuration. This is used by :ref:`ApiConfigSource -// ` and filter configurations. +// ` and filter configurations. // [#next-free-field: 6] message GrpcService { option (udpa.annotations.versioning).previous_message_type = "envoy.config.core.v3.GrpcService"; @@ -33,8 +33,8 @@ message GrpcService { "envoy.config.core.v3.GrpcService.EnvoyGrpc"; // The name of the upstream gRPC cluster. SSL credentials will be supplied - // in the :ref:`Cluster ` :ref:`transport_socket - // `. + // in the :ref:`Cluster ` :ref:`transport_socket + // `. string cluster_name = 1 [(validate.rules).string = {min_len: 1}]; // The `:authority` header in the grpc request. If this field is not set, the authority header value will be `cluster_name`. @@ -236,7 +236,7 @@ message GrpcService { // The target URI when using the `Google C++ gRPC client // `_. SSL credentials will be supplied in - // :ref:`channel_credentials `. + // :ref:`channel_credentials `. string target_uri = 1 [(validate.rules).string = {min_len: 1}]; ChannelCredentials channel_credentials = 2; diff --git a/generated_api_shadow/envoy/config/core/v4alpha/health_check.proto b/generated_api_shadow/envoy/config/core/v4alpha/health_check.proto index ddc7d65e00075..bf86f26e665e3 100644 --- a/generated_api_shadow/envoy/config/core/v4alpha/health_check.proto +++ b/generated_api_shadow/envoy/config/core/v4alpha/health_check.proto @@ -85,7 +85,7 @@ message HealthCheck { // The value of the host header in the HTTP health check request. If // left empty (default value), the name of the cluster this health check is associated // with will be used. The host header can be customized for a specific endpoint by setting the - // :ref:`hostname ` field. + // :ref:`hostname ` field. string host = 1 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; // Specifies the HTTP path that will be requested during health checking. For example @@ -114,7 +114,7 @@ message HealthCheck { // Specifies a list of HTTP response statuses considered healthy. If provided, replaces default // 200-only policy - 200 must be included explicitly as needed. Ranges follow half-open - // semantics of :ref:`Int64Range `. The start and end of each + // semantics of :ref:`Int64Range `. The start and end of each // range are required. Only statuses in the range [100, 600) are allowed. repeated type.v3.Int64Range expected_statuses = 9; @@ -123,7 +123,7 @@ message HealthCheck { // An optional service name parameter which is used to validate the identity of // the health checked cluster using a :ref:`StringMatcher - // `. See the :ref:`architecture overview + // `. See the :ref:`architecture overview // ` for more information. type.matcher.v4alpha.StringMatcher service_name_matcher = 11; } @@ -170,7 +170,7 @@ message HealthCheck { // The value of the :authority header in the gRPC health check request. If // left empty (default value), the name of the cluster this health check is associated // with will be used. The authority header can be customized for a specific endpoint by setting - // the :ref:`hostname ` field. + // the :ref:`hostname ` field. string authority = 2 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; } @@ -205,7 +205,7 @@ message HealthCheck { // Specifies the ALPN protocols for health check connections. This is useful if the // corresponding upstream is using ALPN-based :ref:`FilterChainMatch - // ` along with different protocols for health checks + // ` along with different protocols for health checks // versus data connections. If empty, no ALPN protocols will be set on health check connections. repeated string alpn_protocols = 1; } @@ -339,7 +339,7 @@ message HealthCheck { TlsOptions tls_options = 21; // Optional key/value pairs that will be used to match a transport socket from those specified in the cluster's - // :ref:`tranport socket matches `. + // :ref:`tranport socket matches `. // For example, the following match criteria // // .. code-block:: yaml @@ -347,7 +347,7 @@ message HealthCheck { // transport_socket_match_criteria: // useMTLS: true // - // Will match the following :ref:`cluster socket match ` + // Will match the following :ref:`cluster socket match ` // // .. code-block:: yaml // @@ -360,13 +360,13 @@ message HealthCheck { // config: { ... } # tls socket configuration // // If this field is set, then for health checks it will supersede an entry of *envoy.transport_socket* in the - // :ref:`LbEndpoint.Metadata `. + // :ref:`LbEndpoint.Metadata `. // This allows using different transport socket capabilities for health checking versus proxying to the // endpoint. // // If the key/values pairs specified do not match any - // :ref:`transport socket matches `, - // the cluster's :ref:`transport socket ` + // :ref:`transport socket matches `, + // the cluster's :ref:`transport socket ` // will be used for health check socket configuration. google.protobuf.Struct transport_socket_match_criteria = 23; } diff --git a/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto b/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto index 199503fce37b3..e682602c350c1 100644 --- a/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto +++ b/generated_api_shadow/envoy/config/core/v4alpha/protocol.proto @@ -104,7 +104,7 @@ message HttpProtocolOptions { // idle timeout is reached the connection will be closed. If the connection is an HTTP/2 // downstream connection a drain sequence will occur prior to closing the connection, see // :ref:`drain_timeout - // `. + // `. // Note that request based timeouts mean that HTTP/2 PINGs will not keep the connection alive. // If not specified, this defaults to 1 hour. To disable idle timeouts explicitly set this to 0. // @@ -114,14 +114,14 @@ message HttpProtocolOptions { // // If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" // is configured, this timeout is scaled for downstream connections according to the value for - // :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. + // :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. google.protobuf.Duration idle_timeout = 1; // The maximum duration of a connection. The duration is defined as a period since a connection // was established. If not set, there is no max duration. When max_connection_duration is reached // the connection will be closed. Drain sequence will occur prior to closing the connection if // if's applicable. See :ref:`drain_timeout - // `. + // `. // Note: not implemented for upstream connections. google.protobuf.Duration max_connection_duration = 3; diff --git a/generated_api_shadow/envoy/config/endpoint/v3/endpoint.proto b/generated_api_shadow/envoy/config/endpoint/v3/endpoint.proto index 9192bff18cb59..afcaa41134c41 100644 --- a/generated_api_shadow/envoy/config/endpoint/v3/endpoint.proto +++ b/generated_api_shadow/envoy/config/endpoint/v3/endpoint.proto @@ -103,9 +103,9 @@ message ClusterLoadAssignment { } // Name of the cluster. This will be the :ref:`service_name - // ` value if specified + // ` value if specified // in the cluster :ref:`EdsClusterConfig - // `. + // `. string cluster_name = 1 [(validate.rules).string = {min_len: 1}]; // List of endpoints to load balance to. diff --git a/generated_api_shadow/envoy/config/endpoint/v3/endpoint_components.proto b/generated_api_shadow/envoy/config/endpoint/v3/endpoint_components.proto index b880a38d1a3ea..0e10ac3b2fca7 100644 --- a/generated_api_shadow/envoy/config/endpoint/v3/endpoint_components.proto +++ b/generated_api_shadow/envoy/config/endpoint/v3/endpoint_components.proto @@ -37,8 +37,8 @@ message Endpoint { uint32 port_value = 1 [(validate.rules).uint32 = {lte: 65535}]; // By default, the host header for L7 health checks is controlled by cluster level configuration - // (see: :ref:`host ` and - // :ref:`authority `). Setting this + // (see: :ref:`host ` and + // :ref:`authority `). Setting this // to a non-empty value allows overriding the cluster level configuration for a specific // endpoint. string hostname = 2; @@ -50,7 +50,7 @@ message Endpoint { // // The form of host address depends on the given cluster type. For STATIC or EDS, // it is expected to be a direct IP address (or something resolvable by the - // specified :ref:`resolver ` + // specified :ref:`resolver ` // in the Address). For LOGICAL or STRICT DNS, it is expected to be hostname, // and will be resolved via DNS. core.v3.Address address = 1; @@ -67,7 +67,7 @@ message Endpoint { // The hostname associated with this endpoint. This hostname is not used for routing or address // resolution. If provided, it will be associated with the endpoint, and can be used for features // that require a hostname, like - // :ref:`auto_host_rewrite `. + // :ref:`auto_host_rewrite `. string hostname = 3; } @@ -92,7 +92,7 @@ message LbEndpoint { // name should be specified as *envoy.lb*. An example boolean key-value pair // is *canary*, providing the optional canary status of the upstream host. // This may be matched against in a route's - // :ref:`RouteAction ` metadata_match field + // :ref:`RouteAction ` metadata_match field // to subset the endpoints considered in cluster load balancing. core.v3.Metadata metadata = 3; diff --git a/generated_api_shadow/envoy/config/endpoint/v3/load_report.proto b/generated_api_shadow/envoy/config/endpoint/v3/load_report.proto index 7140ca05afc76..c114fa726622d 100644 --- a/generated_api_shadow/envoy/config/endpoint/v3/load_report.proto +++ b/generated_api_shadow/envoy/config/endpoint/v3/load_report.proto @@ -20,7 +20,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Load Report] // These are stats Envoy reports to the management server at a frequency defined by -// :ref:`LoadStatsResponse.load_reporting_interval`. +// :ref:`LoadStatsResponse.load_reporting_interval`. // Stats per upstream region/zone and optionally per subzone. // [#next-free-field: 9] message UpstreamLocalityStats { @@ -52,7 +52,7 @@ message UpstreamLocalityStats { // Endpoint granularity stats information for this locality. This information // is populated if the Server requests it by setting - // :ref:`LoadStatsResponse.report_endpoint_granularity`. + // :ref:`LoadStatsResponse.report_endpoint_granularity`. repeated UpstreamEndpointStats upstream_endpoint_stats = 7; // [#not-implemented-hide:] The priority of the endpoint group these metrics @@ -118,7 +118,7 @@ message EndpointLoadMetricStats { } // Per cluster load stats. Envoy reports these stats a management server in a -// :ref:`LoadStatsRequest` +// :ref:`LoadStatsRequest` // Next ID: 7 // [#next-free-field: 7] message ClusterStats { diff --git a/generated_api_shadow/envoy/config/listener/v3/listener.proto b/generated_api_shadow/envoy/config/listener/v3/listener.proto index 5461318ada01c..b5bda9562cee8 100644 --- a/generated_api_shadow/envoy/config/listener/v3/listener.proto +++ b/generated_api_shadow/envoy/config/listener/v3/listener.proto @@ -60,7 +60,7 @@ message Listener { // set use_original_dst parameter to true. Default is true. // // This is deprecated. Use :ref:`Listener.bind_to_port - // ` + // ` google.protobuf.BoolValue bind_to_port = 1; } @@ -111,8 +111,8 @@ message Listener { string stat_prefix = 28; // A list of filter chains to consider for this listener. The - // :ref:`FilterChain ` with the most specific - // :ref:`FilterChainMatch ` criteria is used on a + // :ref:`FilterChain ` with the most specific + // :ref:`FilterChainMatch ` criteria is used on a // connection. // // Example using SNI for filter chain selection can be found in the @@ -147,12 +147,12 @@ message Listener { // Listener filters have the opportunity to manipulate and augment the connection metadata that // is used in connection filter chain matching, for example. These filters are run before any in - // :ref:`filter_chains `. Order matters as the + // :ref:`filter_chains `. Order matters as the // filters are processed sequentially right after a socket has been accepted by the listener, and // before a connection is created. // UDP Listener filters can be specified when the protocol in the listener socket address in - // :ref:`protocol ` is :ref:`UDP - // `. + // :ref:`protocol ` is :ref:`UDP + // `. // UDP listeners currently support a single filter. repeated ListenerFilter listener_filters = 9; @@ -176,7 +176,7 @@ message Listener { // *iptables* *TPROXY* target, in which case the original source and destination addresses and // ports are preserved on accepted connections. This flag should be used in combination with // :ref:`an original_dst ` :ref:`listener filter - // ` to mark the connections' local addresses as + // ` to mark the connections' local addresses as // "restored." This can be used to hand off each redirected connection to another listener // associated with the connection's destination address. Direct connections to the socket without // using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are @@ -221,14 +221,14 @@ message Listener { core.v3.TrafficDirection traffic_direction = 16; // If the protocol in the listener socket address in :ref:`protocol - // ` is :ref:`UDP - // `, this field specifies UDP + // ` is :ref:`UDP + // `, this field specifies UDP // listener specific configuration. UdpListenerConfig udp_listener_config = 18; // Used to represent an API listener, which is used in non-proxy clients. The type of API // exposed to the non-proxy application depends on the type of API listener. - // When this field is set, no other field except for :ref:`name` + // When this field is set, no other field except for :ref:`name` // should be set. // // .. note:: @@ -249,8 +249,8 @@ message Listener { // worker threads. // // In the scenario that the listener X redirects all the connections to the listeners Y1 and Y2 - // by setting :ref:`use_original_dst ` in X - // and :ref:`bind_to_port ` to false in Y1 and Y2, + // by setting :ref:`use_original_dst ` in X + // and :ref:`bind_to_port ` to false in Y1 and Y2, // it is recommended to disable the balance config in listener X to avoid the cost of balancing, and // enable the balance config in Y1 and Y2 to balance the connections among the workers. ConnectionBalanceConfig connection_balance_config = 20; @@ -277,7 +277,7 @@ message Listener { // Whether the listener should bind to the port. A listener that doesn't // bind can only receive connections redirected from other listeners that set - // :ref:`use_original_dst ` + // :ref:`use_original_dst ` // to true. Default is true. google.protobuf.BoolValue bind_to_port = 26; @@ -289,16 +289,16 @@ message Listener { // Used to represent an internal listener which does not listen on OSI L4 address but can be used by the // :ref:`envoy cluster ` to create a user space connection to. // The internal listener acts as a tcp listener. It supports listener filters and network filter chains. - // The internal listener require :ref:`address ` has + // The internal listener require :ref:`address ` has // field `envoy_internal_address`. // // There are some limitations are derived from the implementation. The known limitations include // - // * :ref:`ConnectionBalanceConfig ` is not + // * :ref:`ConnectionBalanceConfig ` is not // allowed because both cluster connection and listener connection must be owned by the same dispatcher. - // * :ref:`tcp_backlog_size ` - // * :ref:`freebind ` - // * :ref:`transparent ` + // * :ref:`tcp_backlog_size ` + // * :ref:`freebind ` + // * :ref:`transparent ` // [#not-implemented-hide:] InternalListenerConfig internal_listener = 27; } diff --git a/generated_api_shadow/envoy/config/listener/v3/listener_components.proto b/generated_api_shadow/envoy/config/listener/v3/listener_components.proto index 3091295ddffc8..e8950d13c2c41 100644 --- a/generated_api_shadow/envoy/config/listener/v3/listener_components.proto +++ b/generated_api_shadow/envoy/config/listener/v3/listener_components.proto @@ -238,7 +238,7 @@ message FilterChain { // Optional custom transport socket implementation to use for downstream connections. // To setup TLS, set a transport socket with name `tls` and - // :ref:`DownstreamTlsContext ` in the `typed_config`. + // :ref:`DownstreamTlsContext ` in the `typed_config`. // If no transport socket configuration is specified, new connections // will be set up with plaintext. // [#extension-category: envoy.transport_sockets.downstream] @@ -347,7 +347,7 @@ message ListenerFilter { } // Optional match predicate used to disable the filter. The filter is enabled when this field is empty. - // See :ref:`ListenerFilterChainMatchPredicate ` + // See :ref:`ListenerFilterChainMatchPredicate ` // for further examples. ListenerFilterChainMatchPredicate filter_disabled = 4; } diff --git a/generated_api_shadow/envoy/config/listener/v4alpha/listener.proto b/generated_api_shadow/envoy/config/listener/v4alpha/listener.proto index 47611a615efbc..7618d5a229f4f 100644 --- a/generated_api_shadow/envoy/config/listener/v4alpha/listener.proto +++ b/generated_api_shadow/envoy/config/listener/v4alpha/listener.proto @@ -63,7 +63,7 @@ message Listener { // set use_original_dst parameter to true. Default is true. // // This is deprecated. Use :ref:`Listener.bind_to_port - // ` + // ` google.protobuf.BoolValue bind_to_port = 1; } @@ -116,8 +116,8 @@ message Listener { string stat_prefix = 28; // A list of filter chains to consider for this listener. The - // :ref:`FilterChain ` with the most specific - // :ref:`FilterChainMatch ` criteria is used on a + // :ref:`FilterChain ` with the most specific + // :ref:`FilterChainMatch ` criteria is used on a // connection. // // Example using SNI for filter chain selection can be found in the @@ -152,12 +152,12 @@ message Listener { // Listener filters have the opportunity to manipulate and augment the connection metadata that // is used in connection filter chain matching, for example. These filters are run before any in - // :ref:`filter_chains `. Order matters as the + // :ref:`filter_chains `. Order matters as the // filters are processed sequentially right after a socket has been accepted by the listener, and // before a connection is created. // UDP Listener filters can be specified when the protocol in the listener socket address in - // :ref:`protocol ` is :ref:`UDP - // `. + // :ref:`protocol ` is :ref:`UDP + // `. // UDP listeners currently support a single filter. repeated ListenerFilter listener_filters = 9; @@ -181,7 +181,7 @@ message Listener { // *iptables* *TPROXY* target, in which case the original source and destination addresses and // ports are preserved on accepted connections. This flag should be used in combination with // :ref:`an original_dst ` :ref:`listener filter - // ` to mark the connections' local addresses as + // ` to mark the connections' local addresses as // "restored." This can be used to hand off each redirected connection to another listener // associated with the connection's destination address. Direct connections to the socket without // using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are @@ -226,14 +226,14 @@ message Listener { core.v4alpha.TrafficDirection traffic_direction = 16; // If the protocol in the listener socket address in :ref:`protocol - // ` is :ref:`UDP - // `, this field specifies UDP + // ` is :ref:`UDP + // `, this field specifies UDP // listener specific configuration. UdpListenerConfig udp_listener_config = 18; // Used to represent an API listener, which is used in non-proxy clients. The type of API // exposed to the non-proxy application depends on the type of API listener. - // When this field is set, no other field except for :ref:`name` + // When this field is set, no other field except for :ref:`name` // should be set. // // .. note:: @@ -254,8 +254,8 @@ message Listener { // worker threads. // // In the scenario that the listener X redirects all the connections to the listeners Y1 and Y2 - // by setting :ref:`use_original_dst ` in X - // and :ref:`bind_to_port ` to false in Y1 and Y2, + // by setting :ref:`use_original_dst ` in X + // and :ref:`bind_to_port ` to false in Y1 and Y2, // it is recommended to disable the balance config in listener X to avoid the cost of balancing, and // enable the balance config in Y1 and Y2 to balance the connections among the workers. ConnectionBalanceConfig connection_balance_config = 20; @@ -282,7 +282,7 @@ message Listener { // Whether the listener should bind to the port. A listener that doesn't // bind can only receive connections redirected from other listeners that set - // :ref:`use_original_dst ` + // :ref:`use_original_dst ` // to true. Default is true. google.protobuf.BoolValue bind_to_port = 26; @@ -294,16 +294,16 @@ message Listener { // Used to represent an internal listener which does not listen on OSI L4 address but can be used by the // :ref:`envoy cluster ` to create a user space connection to. // The internal listener acts as a tcp listener. It supports listener filters and network filter chains. - // The internal listener require :ref:`address ` has + // The internal listener require :ref:`address ` has // field `envoy_internal_address`. // // There are some limitations are derived from the implementation. The known limitations include // - // * :ref:`ConnectionBalanceConfig ` is not + // * :ref:`ConnectionBalanceConfig ` is not // allowed because both cluster connection and listener connection must be owned by the same dispatcher. - // * :ref:`tcp_backlog_size ` - // * :ref:`freebind ` - // * :ref:`transparent ` + // * :ref:`tcp_backlog_size ` + // * :ref:`freebind ` + // * :ref:`transparent ` // [#not-implemented-hide:] InternalListenerConfig internal_listener = 27; } diff --git a/generated_api_shadow/envoy/config/listener/v4alpha/listener_components.proto b/generated_api_shadow/envoy/config/listener/v4alpha/listener_components.proto index ecc3bae752577..a261d36c70cf1 100644 --- a/generated_api_shadow/envoy/config/listener/v4alpha/listener_components.proto +++ b/generated_api_shadow/envoy/config/listener/v4alpha/listener_components.proto @@ -243,7 +243,7 @@ message FilterChain { // Optional custom transport socket implementation to use for downstream connections. // To setup TLS, set a transport socket with name `tls` and - // :ref:`DownstreamTlsContext ` in the `typed_config`. + // :ref:`DownstreamTlsContext ` in the `typed_config`. // If no transport socket configuration is specified, new connections // will be set up with plaintext. // [#extension-category: envoy.transport_sockets.downstream] @@ -349,7 +349,7 @@ message ListenerFilter { } // Optional match predicate used to disable the filter. The filter is enabled when this field is empty. - // See :ref:`ListenerFilterChainMatchPredicate ` + // See :ref:`ListenerFilterChainMatchPredicate ` // for further examples. ListenerFilterChainMatchPredicate filter_disabled = 4; } diff --git a/generated_api_shadow/envoy/config/metrics/v3/metrics_service.proto b/generated_api_shadow/envoy/config/metrics/v3/metrics_service.proto index e5fab870f8ee6..1cdd6d183e9db 100644 --- a/generated_api_shadow/envoy/config/metrics/v3/metrics_service.proto +++ b/generated_api_shadow/envoy/config/metrics/v3/metrics_service.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Metrics service] // Metrics Service is configured as a built-in *envoy.stat_sinks.metrics_service* :ref:`StatsSink -// `. This opaque configuration will be used to create +// `. This opaque configuration will be used to create // Metrics Service. // [#extension: envoy.stat_sinks.metrics_service] message MetricsServiceConfig { @@ -36,7 +36,7 @@ message MetricsServiceConfig { // If true, counters are reported as the delta between flushing intervals. Otherwise, the current // counter value is reported. Defaults to false. // Eventually (https://github.com/envoyproxy/envoy/issues/10968) if this value is not set, the - // sink will take updates from the :ref:`MetricsResponse `. + // sink will take updates from the :ref:`MetricsResponse `. google.protobuf.BoolValue report_counters_as_deltas = 2; // If true, metrics will have their tags emitted as labels on the metrics objects sent to the MetricsService, diff --git a/generated_api_shadow/envoy/config/metrics/v3/stats.proto b/generated_api_shadow/envoy/config/metrics/v3/stats.proto index 8c25109fd1f64..1b5e833e2bede 100644 --- a/generated_api_shadow/envoy/config/metrics/v3/stats.proto +++ b/generated_api_shadow/envoy/config/metrics/v3/stats.proto @@ -33,7 +33,7 @@ message StatsSink { string name = 1; // Stats sink specific configuration which depends on the sink being instantiated. See - // :ref:`StatsdSink ` for an example. + // :ref:`StatsdSink ` for an example. // [#extension-category: envoy.stats_sinks] oneof config_type { google.protobuf.Any typed_config = 3; @@ -50,13 +50,13 @@ message StatsConfig { // Each stat name is iteratively processed through these tag specifiers. // When a tag is matched, the first capture group is removed from the name so - // later :ref:`TagSpecifiers ` cannot match that + // later :ref:`TagSpecifiers ` cannot match that // same portion of the match. repeated TagSpecifier stats_tags = 1; // Use all default tag regexes specified in Envoy. These can be combined with // custom tags specified in :ref:`stats_tags - // `. They will be processed before + // `. They will be processed before // the custom tags. // // .. note:: @@ -118,7 +118,7 @@ message StatsMatcher { // However, StatsMatcher can be used to limit the creation of families of stats in order to // conserve memory. Stats can either be disabled entirely, or they can be // limited by either an exclusion or an inclusion list of :ref:`StringMatcher - // ` protos: + // ` protos: // // * If `reject_all` is set to `true`, no stats will be instantiated. If `reject_all` is set to // `false`, all stats will be instantiated. @@ -212,9 +212,9 @@ message TagSpecifier { // sink. Envoy has a set of default names and regexes to extract dynamic // portions of existing stats, which can be found in :repo:`well_known_names.h // ` in the Envoy repository. If a :ref:`tag_name - // ` is provided in the config and - // neither :ref:`regex ` or - // :ref:`fixed_value ` were specified, + // ` is provided in the config and + // neither :ref:`regex ` or + // :ref:`fixed_value ` were specified, // Envoy will attempt to find that name in its set of defaults and use the accompanying regex. // // .. note:: @@ -351,7 +351,7 @@ message StatsdSink { // Stats configuration proto schema for built-in *envoy.stat_sinks.dog_statsd* sink. // The sink emits stats with `DogStatsD `_ // compatible tags. Tags are configurable via :ref:`StatsConfig -// `. +// `. // [#extension: envoy.stat_sinks.dog_statsd] message DogStatsdSink { option (udpa.annotations.versioning).previous_message_type = @@ -368,7 +368,7 @@ message DogStatsdSink { } // Optional custom metric name prefix. See :ref:`StatsdSink's prefix field - // ` for more details. + // ` for more details. string prefix = 3; // Optional max datagram size to use when sending UDP messages. By default Envoy diff --git a/generated_api_shadow/envoy/config/metrics/v4alpha/metrics_service.proto b/generated_api_shadow/envoy/config/metrics/v4alpha/metrics_service.proto index 570bc2e9d7162..fe530b34e6908 100644 --- a/generated_api_shadow/envoy/config/metrics/v4alpha/metrics_service.proto +++ b/generated_api_shadow/envoy/config/metrics/v4alpha/metrics_service.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: Metrics service] // Metrics Service is configured as a built-in *envoy.stat_sinks.metrics_service* :ref:`StatsSink -// `. This opaque configuration will be used to create +// `. This opaque configuration will be used to create // Metrics Service. // [#extension: envoy.stat_sinks.metrics_service] message MetricsServiceConfig { @@ -36,7 +36,7 @@ message MetricsServiceConfig { // If true, counters are reported as the delta between flushing intervals. Otherwise, the current // counter value is reported. Defaults to false. // Eventually (https://github.com/envoyproxy/envoy/issues/10968) if this value is not set, the - // sink will take updates from the :ref:`MetricsResponse `. + // sink will take updates from the :ref:`MetricsResponse `. google.protobuf.BoolValue report_counters_as_deltas = 2; // If true, metrics will have their tags emitted as labels on the metrics objects sent to the MetricsService, diff --git a/generated_api_shadow/envoy/config/metrics/v4alpha/stats.proto b/generated_api_shadow/envoy/config/metrics/v4alpha/stats.proto index e3f10f1da4f61..6d8a94050d65a 100644 --- a/generated_api_shadow/envoy/config/metrics/v4alpha/stats.proto +++ b/generated_api_shadow/envoy/config/metrics/v4alpha/stats.proto @@ -35,7 +35,7 @@ message StatsSink { string name = 1; // Stats sink specific configuration which depends on the sink being instantiated. See - // :ref:`StatsdSink ` for an example. + // :ref:`StatsdSink ` for an example. // [#extension-category: envoy.stats_sinks] oneof config_type { google.protobuf.Any typed_config = 3; @@ -49,13 +49,13 @@ message StatsConfig { // Each stat name is iteratively processed through these tag specifiers. // When a tag is matched, the first capture group is removed from the name so - // later :ref:`TagSpecifiers ` cannot match that + // later :ref:`TagSpecifiers ` cannot match that // same portion of the match. repeated TagSpecifier stats_tags = 1; // Use all default tag regexes specified in Envoy. These can be combined with // custom tags specified in :ref:`stats_tags - // `. They will be processed before + // `. They will be processed before // the custom tags. // // .. note:: @@ -117,7 +117,7 @@ message StatsMatcher { // However, StatsMatcher can be used to limit the creation of families of stats in order to // conserve memory. Stats can either be disabled entirely, or they can be // limited by either an exclusion or an inclusion list of :ref:`StringMatcher - // ` protos: + // ` protos: // // * If `reject_all` is set to `true`, no stats will be instantiated. If `reject_all` is set to // `false`, all stats will be instantiated. @@ -211,9 +211,9 @@ message TagSpecifier { // sink. Envoy has a set of default names and regexes to extract dynamic // portions of existing stats, which can be found in :repo:`well_known_names.h // ` in the Envoy repository. If a :ref:`tag_name - // ` is provided in the config and - // neither :ref:`regex ` or - // :ref:`fixed_value ` were specified, + // ` is provided in the config and + // neither :ref:`regex ` or + // :ref:`fixed_value ` were specified, // Envoy will attempt to find that name in its set of defaults and use the accompanying regex. // // .. note:: @@ -353,7 +353,7 @@ message StatsdSink { // Stats configuration proto schema for built-in *envoy.stat_sinks.dog_statsd* sink. // The sink emits stats with `DogStatsD `_ // compatible tags. Tags are configurable via :ref:`StatsConfig -// `. +// `. // [#extension: envoy.stat_sinks.dog_statsd] message DogStatsdSink { option (udpa.annotations.versioning).previous_message_type = @@ -370,7 +370,7 @@ message DogStatsdSink { } // Optional custom metric name prefix. See :ref:`StatsdSink's prefix field - // ` for more details. + // ` for more details. string prefix = 3; // Optional max datagram size to use when sending UDP messages. By default Envoy diff --git a/generated_api_shadow/envoy/config/rbac/v3/rbac.proto b/generated_api_shadow/envoy/config/rbac/v3/rbac.proto index 11fc66ee0c168..3d9c9c2627edf 100644 --- a/generated_api_shadow/envoy/config/rbac/v3/rbac.proto +++ b/generated_api_shadow/envoy/config/rbac/v3/rbac.proto @@ -200,7 +200,7 @@ message Permission { // * If the :ref:`TLS Inspector ` // filter is not added, and if a `FilterChainMatch` is not defined for // the :ref:`server name - // `, + // `, // a TLS connection's requested SNI server name will be treated as if it // wasn't present. // @@ -265,7 +265,7 @@ message Principal { // A CIDR block that describes the downstream remote/origin address. // Note: This is always the physical peer even if the - // :ref:`remote_ip ` is + // :ref:`remote_ip ` is // inferred from for example the x-forwarder-for header, proxy protocol, // etc. core.v3.CidrRange direct_remote_ip = 10; @@ -273,7 +273,7 @@ message Principal { // A CIDR block that describes the downstream remote/origin address. // Note: This may not be the physical peer and could be different from the // :ref:`direct_remote_ip - // `. E.g, if the + // `. E.g, if the // remote ip is inferred from for example the x-forwarder-for header, proxy // protocol, etc. core.v3.CidrRange remote_ip = 11; diff --git a/generated_api_shadow/envoy/config/rbac/v4alpha/rbac.proto b/generated_api_shadow/envoy/config/rbac/v4alpha/rbac.proto index f6b1079380c79..e373d0a3ee34f 100644 --- a/generated_api_shadow/envoy/config/rbac/v4alpha/rbac.proto +++ b/generated_api_shadow/envoy/config/rbac/v4alpha/rbac.proto @@ -199,7 +199,7 @@ message Permission { // * If the :ref:`TLS Inspector ` // filter is not added, and if a `FilterChainMatch` is not defined for // the :ref:`server name - // `, + // `, // a TLS connection's requested SNI server name will be treated as if it // wasn't present. // @@ -264,7 +264,7 @@ message Principal { // A CIDR block that describes the downstream remote/origin address. // Note: This is always the physical peer even if the - // :ref:`remote_ip ` is + // :ref:`remote_ip ` is // inferred from for example the x-forwarder-for header, proxy protocol, // etc. core.v4alpha.CidrRange direct_remote_ip = 10; @@ -272,7 +272,7 @@ message Principal { // A CIDR block that describes the downstream remote/origin address. // Note: This may not be the physical peer and could be different from the // :ref:`direct_remote_ip - // `. E.g, if the + // `. E.g, if the // remote ip is inferred from for example the x-forwarder-for header, proxy // protocol, etc. core.v4alpha.CidrRange remote_ip = 11; diff --git a/generated_api_shadow/envoy/config/route/v3/route.proto b/generated_api_shadow/envoy/config/route/v3/route.proto index 4588af78cb450..80956fdeb4e23 100644 --- a/generated_api_shadow/envoy/config/route/v3/route.proto +++ b/generated_api_shadow/envoy/config/route/v3/route.proto @@ -27,8 +27,8 @@ message RouteConfiguration { // The name of the route configuration. For example, it might match // :ref:`route_config_name - // ` in - // :ref:`envoy_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. + // ` in + // :ref:`envoy_v3_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. string name = 1; // An array of virtual hosts that make up the route table. @@ -52,8 +52,8 @@ message RouteConfiguration { // Specifies a list of HTTP headers that should be added to each response that // the connection manager encodes. Headers specified at this level are applied - // after headers from any enclosed :ref:`envoy_api_msg_config.route.v3.VirtualHost` or - // :ref:`envoy_api_msg_config.route.v3.RouteAction`. For more information, including details on + // after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + // :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption response_headers_to_add = 4 @@ -67,8 +67,8 @@ message RouteConfiguration { // Specifies a list of HTTP headers that should be added to each request // routed by the HTTP connection manager. Headers specified at this level are - // applied after headers from any enclosed :ref:`envoy_api_msg_config.route.v3.VirtualHost` or - // :ref:`envoy_api_msg_config.route.v3.RouteAction`. For more information, including details on + // applied after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + // :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption request_headers_to_add = 6 @@ -99,22 +99,22 @@ message RouteConfiguration { // route table will load and the router filter will return a 404 if the route // is selected at runtime. This setting defaults to true if the route table // is statically defined via the :ref:`route_config - // ` + // ` // option. This setting default to false if the route table is loaded dynamically via the // :ref:`rds - // ` + // ` // option. Users may wish to override the default behavior in certain cases (for example when // using CDS with a static route table). google.protobuf.BoolValue validate_clusters = 7; // The maximum bytes of the response :ref:`direct response body - // ` size. If not specified the default + // ` size. If not specified the default // is 4096. // // .. warning:: // // Envoy currently holds the content of :ref:`direct response body - // ` in memory. Be careful setting + // ` in memory. Be careful setting // this to be larger than the default 4KB, since the allocated memory for direct response body // is not subject to data plane buffering controls. // diff --git a/generated_api_shadow/envoy/config/route/v3/route_components.proto b/generated_api_shadow/envoy/config/route/v3/route_components.proto index c8644ab050eba..1b70f539c2c0a 100644 --- a/generated_api_shadow/envoy/config/route/v3/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v3/route_components.proto @@ -101,8 +101,8 @@ message VirtualHost { // Specifies a list of HTTP headers that should be added to each request // handled by this virtual host. Headers specified at this level are applied - // after headers from enclosed :ref:`envoy_api_msg_config.route.v3.Route` and before headers from the - // enclosing :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including + // after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + // enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption request_headers_to_add = 7 @@ -116,8 +116,8 @@ message VirtualHost { // Specifies a list of HTTP headers that should be added to each response // handled by this virtual host. Headers specified at this level are applied - // after headers from enclosed :ref:`envoy_api_msg_config.route.v3.Route` and before headers from the - // enclosing :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including + // after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + // enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption response_headers_to_add = 10 @@ -138,7 +138,7 @@ message VirtualHost { // specific; see the :ref:`HTTP filter documentation ` // for if and how it is utilized. // [#comment: An entry's value may be wrapped in a - // :ref:`FilterConfig` + // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 15; @@ -149,7 +149,7 @@ message VirtualHost { // will see the attempt count as perceived by the second Envoy. Defaults to false. // This header is unaffected by the // :ref:`suppress_envoy_headers - // ` flag. + // ` flag. // // [#next-major-version: rename to include_attempt_count_in_request.] bool include_request_attempt_count = 14; @@ -161,7 +161,7 @@ message VirtualHost { // will see the attempt count as perceived by the Envoy closest upstream from itself. Defaults to false. // This header is unaffected by the // :ref:`suppress_envoy_headers - // ` flag. + // ` flag. bool include_attempt_count_in_response = 19; // Indicates the retry policy for all routes in this virtual host. Note that setting a @@ -172,7 +172,7 @@ message VirtualHost { // [#not-implemented-hide:] // Specifies the configuration for retry policy extension. Note that setting a route level entry // will take precedence over this config and it'll be treated independently (e.g.: values are not - // inherited). :ref:`Retry policy ` should not be + // inherited). :ref:`Retry policy ` should not be // set if this field is used. google.protobuf.Any retry_policy_typed_config = 20; @@ -203,7 +203,7 @@ message FilterAction { // .. attention:: // // Envoy supports routing on HTTP method via :ref:`header matching -// `. +// `. // [#next-free-field: 19] message Route { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.Route"; @@ -258,14 +258,14 @@ message Route { // specific; see the :ref:`HTTP filter documentation ` for // if and how it is utilized. // [#comment: An entry's value may be wrapped in a - // :ref:`FilterConfig` + // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 13; // Specifies a set of headers that will be added to requests matching this // route. Headers specified at this level are applied before headers from the - // enclosing :ref:`envoy_api_msg_config.route.v3.VirtualHost` and - // :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + // enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption request_headers_to_add = 9 @@ -279,8 +279,8 @@ message Route { // Specifies a set of headers that will be added to responses to requests // matching this route. Headers specified at this level are applied before - // headers from the enclosing :ref:`envoy_api_msg_config.route.v3.VirtualHost` and - // :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including + // headers from the enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on // :ref:`custom request headers `. repeated core.v3.HeaderValueOption response_headers_to_add = 10 @@ -305,9 +305,9 @@ message Route { [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; } -// Compared to the :ref:`cluster ` field that specifies a +// Compared to the :ref:`cluster ` field that specifies a // single upstream cluster as the target of a request, the :ref:`weighted_clusters -// ` option allows for specification of +// ` option allows for specification of // multiple upstream clusters along with weights that indicate the percentage of // traffic to be forwarded to each cluster. The router selects an upstream cluster based on the // weights. @@ -326,7 +326,7 @@ message WeightedCluster { string name = 1 [(validate.rules).string = {min_len: 1}]; // An integer between 0 and :ref:`total_weight - // `. When a request matches the route, + // `. When a request matches the route, // the choice of an upstream cluster is determined by its weight. The sum of weights across all // entries in the clusters array must add up to the total_weight, which defaults to 100. google.protobuf.UInt32Value weight = 2; @@ -334,38 +334,38 @@ message WeightedCluster { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in // the upstream cluster with metadata matching what is set in this field will be considered for // load balancing. Note that this will be merged with what's provided in - // :ref:`RouteAction.metadata_match `, with + // :ref:`RouteAction.metadata_match `, with // values here taking precedence. The filter name should be specified as *envoy.lb*. core.v3.Metadata metadata_match = 3; // Specifies a list of headers to be added to requests when this cluster is selected - // through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + // through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. // Headers specified at this level are applied before headers from the enclosing - // :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.VirtualHost`, and - // :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + // :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption request_headers_to_add = 4 [(validate.rules).repeated = {max_items: 1000}]; // Specifies a list of HTTP headers that should be removed from each request when - // this cluster is selected through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + // this cluster is selected through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. repeated string request_headers_to_remove = 9 [(validate.rules).repeated = { items {string {well_known_regex: HTTP_HEADER_NAME strict: false}} }]; // Specifies a list of headers to be added to responses when this cluster is selected - // through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + // through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. // Headers specified at this level are applied before headers from the enclosing - // :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.VirtualHost`, and - // :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + // :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v3.HeaderValueOption response_headers_to_add = 5 [(validate.rules).repeated = {max_items: 1000}]; // Specifies a list of headers to be removed from responses when this cluster is selected - // through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + // through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. repeated string response_headers_to_remove = 6 [(validate.rules).repeated = { items {string {well_known_regex: HTTP_HEADER_NAME strict: false}} }]; @@ -376,7 +376,7 @@ message WeightedCluster { // specific; see the :ref:`HTTP filter documentation ` // for if and how it is utilized. // [#comment: An entry's value may be wrapped in a - // :ref:`FilterConfig` + // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 10; @@ -551,7 +551,7 @@ message CorsPolicy { // If neither ``enabled``, ``filter_enabled``, nor ``shadow_enabled`` are specified, the CORS // filter will be enabled for 100% of the requests. // - // If :ref:`runtime_key ` is + // If :ref:`runtime_key ` is // specified, Envoy will lookup the runtime key to get the percentage of requests to filter. core.v3.RuntimeFractionalPercent filter_enabled = 9; @@ -568,7 +568,7 @@ message CorsPolicy { // This field is intended to be used when ``filter_enabled`` and ``enabled`` are off. One of those // fields have to explicitly disable the filter in order for this setting to take effect. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate // and track the request's *Origin* to determine if it's valid but will not enforce any policies. core.v3.RuntimeFractionalPercent shadow_enabled = 10; @@ -773,7 +773,7 @@ message RouteAction { // This overrides any enabled/disabled upgrade filter chain specified in the // HttpConnectionManager // :ref:`upgrade_configs - // ` + // ` // but does not affect any custom filter chain specified there. message UpgradeConfig { option (udpa.annotations.versioning).previous_message_type = @@ -808,9 +808,9 @@ message RouteAction { message MaxStreamDuration { // Specifies the maximum duration allowed for streams on the route. If not specified, the value // from the :ref:`max_stream_duration - // ` field in + // ` field in // :ref:`HttpConnectionManager.common_http_protocol_options - // ` + // ` // is used. If this field is set explicitly to zero, any // HttpConnectionManager max_stream_duration timeout will be disabled for // this route. @@ -872,7 +872,7 @@ message RouteAction { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints // in the upstream cluster with metadata matching what's set in this field will be considered // for load balancing. If using :ref:`weighted_clusters - // `, metadata will be merged, with values + // `, metadata will be merged, with values // provided there taking precedence. The filter name should be specified as *envoy.lb*. core.v3.Metadata metadata_match = 4; @@ -883,16 +883,16 @@ message RouteAction { // ` header. // // Only one of *prefix_rewrite* or - // :ref:`regex_rewrite ` + // :ref:`regex_rewrite ` // may be specified. // // .. attention:: // // Pay careful attention to the use of trailing slashes in the - // :ref:`route's match ` prefix value. + // :ref:`route's match ` prefix value. // Stripping a prefix from a path requires multiple Routes to handle all cases. For example, // rewriting */prefix* to */* and */prefix/etc* to */etc* cannot be done in a single - // :ref:`Route `, as shown by the below config entries: + // :ref:`Route `, as shown by the below config entries: // // .. code-block:: yaml // @@ -919,7 +919,7 @@ message RouteAction { // before the rewrite into the :ref:`x-envoy-original-path // ` header. // - // Only one of :ref:`prefix_rewrite ` + // Only one of :ref:`prefix_rewrite ` // or *regex_rewrite* may be specified. // // Examples using Google's `RE2 `_ engine: @@ -1001,14 +1001,14 @@ message RouteAction { // Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, // although the connection manager wide :ref:`stream_idle_timeout - // ` + // ` // will still apply. A value of 0 will completely disable the route's idle timeout, even if a // connection manager stream idle timeout is configured. // // The idle timeout is distinct to :ref:`timeout - // `, which provides an upper bound + // `, which provides an upper bound // on the upstream response time; :ref:`idle_timeout - // ` instead bounds the amount + // ` instead bounds the amount // of time the request's stream may be idle. // // After header decoding, the idle timeout will apply on downstream and @@ -1020,7 +1020,7 @@ message RouteAction { // // If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" // is configured, this timeout is scaled according to the value for - // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. google.protobuf.Duration idle_timeout = 24; // Indicates that the route has a retry policy. Note that if this is set, @@ -1031,7 +1031,7 @@ message RouteAction { // [#not-implemented-hide:] // Specifies the configuration for retry policy extension. Note that if this is set, it'll take // precedence over the virtual host level retry policy entirely (e.g.: policies are not merged, - // most internal one becomes the enforced policy). :ref:`Retry policy ` + // most internal one becomes the enforced policy). :ref:`Retry policy ` // should not be set if this field is used. google.protobuf.Any retry_policy_typed_config = 33; @@ -1047,7 +1047,7 @@ message RouteAction { // Specifies if the rate limit filter should include the virtual host rate // limits. By default, if the route configured rate limits, the virtual host - // :ref:`rate_limits ` are not applied to the + // :ref:`rate_limits ` are not applied to the // request. // // This field is deprecated. Please use :ref:`vh_rate_limits ` @@ -1071,15 +1071,15 @@ message RouteAction { // Indicates that the route has a CORS policy. CorsPolicy cors = 17; - // Deprecated by :ref:`grpc_timeout_header_max ` + // Deprecated by :ref:`grpc_timeout_header_max ` // If present, and the request is a gRPC request, use the // `grpc-timeout header `_, // or its default value (infinity) instead of - // :ref:`timeout `, but limit the applied timeout + // :ref:`timeout `, but limit the applied timeout // to the maximum value specified here. If configured as 0, the maximum allowed timeout for // gRPC requests is infinity. If not configured at all, the `grpc-timeout` header is not used // and gRPC requests time out like any other requests using - // :ref:`timeout ` or its default. + // :ref:`timeout ` or its default. // This can be used to prevent unexpected upstream request timeouts due to potentially long // time gaps between gRPC request and response in gRPC streaming mode. // @@ -1094,7 +1094,7 @@ message RouteAction { google.protobuf.Duration max_grpc_timeout = 23 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; - // Deprecated by :ref:`grpc_timeout_header_offset `. + // Deprecated by :ref:`grpc_timeout_header_offset `. // If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting // the provided duration from the header. This is useful in allowing Envoy to set its global // timeout to be less than that of the deadline imposed by the calling client, which makes it more @@ -1110,7 +1110,7 @@ message RouteAction { // If present, Envoy will try to follow an upstream redirect response instead of proxying the // response back to the downstream. An upstream redirect response is defined // by :ref:`redirect_response_codes - // `. + // `. InternalRedirectPolicy internal_redirect_policy = 34; InternalRedirectAction internal_redirect_action = 26 @@ -1118,15 +1118,15 @@ message RouteAction { // An internal redirect is handled, iff the number of previous internal redirects that a // downstream request has encountered is lower than this value, and - // :ref:`internal_redirect_action ` + // :ref:`internal_redirect_action ` // is set to :ref:`HANDLE_INTERNAL_REDIRECT - // ` + // ` // In the case where a downstream request is bounced among multiple routes by internal redirect, // the first route that hits this threshold, or has - // :ref:`internal_redirect_action ` + // :ref:`internal_redirect_action ` // set to // :ref:`PASS_THROUGH_INTERNAL_REDIRECT - // ` + // ` // will pass the redirect back to downstream. // // If not specified, at most one redirect will be followed. @@ -1292,7 +1292,7 @@ message RetryPolicy { // .. note:: // // If left unspecified, Envoy will use the global - // :ref:`route timeout ` for the request. + // :ref:`route timeout ` for the request. // Consequently, when using a :ref:`5xx ` based // retry policy, a request that times out will not be retried as the total timeout budget // would have been exhausted. @@ -1367,7 +1367,7 @@ message HedgePolicy { // if there are no more retries left. // * After per-try timeout, an error response would be discarded, as a retry in the form of a hedged request is already in progress. // - // Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least + // Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least // one error code and specifies a maximum number of retries. // // Defaults to false. @@ -1442,7 +1442,7 @@ message RedirectAction { // .. attention:: // // Pay attention to the use of trailing slashes as mentioned in - // :ref:`RouteAction's prefix_rewrite `. + // :ref:`RouteAction's prefix_rewrite `. string prefix_rewrite = 5 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; @@ -1494,8 +1494,8 @@ message DirectResponseAction { // .. note:: // // Headers can be specified using *response_headers_to_add* in the enclosing - // :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` or - // :ref:`envoy_api_msg_config.route.v3.VirtualHost`. + // :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` or + // :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`. core.v3.DataSource body = 2; } @@ -1550,7 +1550,7 @@ message Tracing { // A list of custom tags with unique tag name to create tags for the active span. // It will take effect after merging with the :ref:`corresponding configuration - // ` + // ` // configured in the HTTP connection manager. If two tags with the same name are configured // each in the HTTP connection manager and the route level, the one configured here takes // priority. @@ -1630,14 +1630,14 @@ message RateLimit { // ("destination_cluster", "") // // Once a request matches against a route table rule, a routed cluster is determined by one of - // the following :ref:`route table configuration ` + // the following :ref:`route table configuration ` // settings: // - // * :ref:`cluster ` indicates the upstream cluster + // * :ref:`cluster ` indicates the upstream cluster // to route to. - // * :ref:`weighted_clusters ` + // * :ref:`weighted_clusters ` // chooses a cluster randomly from a set of clusters with attributed weight. - // * :ref:`cluster_header ` indicates which + // * :ref:`cluster_header ` indicates which // header in the request contains the target cluster. message DestinationCluster { option (udpa.annotations.versioning).previous_message_type = @@ -1731,7 +1731,7 @@ message RateLimit { // ("", "") // // .. attention:: - // This action has been deprecated in favor of the :ref:`metadata ` action + // This action has been deprecated in favor of the :ref:`metadata ` action message DynamicMetaData { // The key to use in the descriptor entry. string descriptor_key = 1 [(validate.rules).string = {min_len: 1}]; @@ -1755,7 +1755,7 @@ message RateLimit { // Query :ref:`dynamic metadata ` DYNAMIC = 0; - // Query :ref:`route entry metadata ` + // Query :ref:`route entry metadata ` ROUTE_ENTRY = 1; } @@ -1798,7 +1798,7 @@ message RateLimit { // Rate limit on dynamic metadata. // // .. attention:: - // This field has been deprecated in favor of the :ref:`metadata ` field + // This field has been deprecated in favor of the :ref:`metadata ` field DynamicMetaData dynamic_metadata = 7 [ deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0", @@ -1820,7 +1820,7 @@ message RateLimit { // Metadata struct that defines the key and path to retrieve the struct value. // The value must be a struct containing an integer "requests_per_unit" property // and a "unit" property with a value parseable to :ref:`RateLimitUnit - // enum ` + // enum ` type.metadata.v3.MetadataKey metadata_key = 1 [(validate.rules).message = {required: true}]; } @@ -1878,8 +1878,8 @@ message RateLimit { // // .. attention:: // In the absence of any header match specifier, match will default to :ref:`present_match -// `. i.e, a request that has the :ref:`name -// ` header will match, regardless of the header's +// `. i.e, a request that has the :ref:`name +// ` header will match, regardless of the header's // value. // // [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] @@ -2000,7 +2000,7 @@ message InternalRedirectPolicy { // downstream request has encountered is lower than this value. // In the case where a downstream request is bounced among multiple routes by internal redirect, // the first route that hits this threshold, or does not set :ref:`internal_redirect_policy - // ` + // ` // will pass the redirect back to downstream. // // If not specified, at most one redirect will be followed. @@ -2024,9 +2024,9 @@ message InternalRedirectPolicy { // A simple wrapper for an HTTP filter config. This is intended to be used as a wrapper for the // map value in -// :ref:`VirtualHost.typed_per_filter_config`, -// :ref:`Route.typed_per_filter_config`, -// or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` +// :ref:`VirtualHost.typed_per_filter_config`, +// :ref:`Route.typed_per_filter_config`, +// or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` // to add additional flags to the filter. // [#not-implemented-hide:] message FilterConfig { diff --git a/generated_api_shadow/envoy/config/route/v3/scoped_route.proto b/generated_api_shadow/envoy/config/route/v3/scoped_route.proto index b7e3aa66e07f2..eb47d7e10898d 100644 --- a/generated_api_shadow/envoy/config/route/v3/scoped_route.proto +++ b/generated_api_shadow/envoy/config/route/v3/scoped_route.proto @@ -15,13 +15,13 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // * Routing :ref:`architecture overview ` // Specifies a routing scope, which associates a -// :ref:`Key` to a -// :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` (identified by its resource name). +// :ref:`Key` to a +// :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` (identified by its resource name). // // The HTTP connection manager builds up a table consisting of these Key to // RouteConfiguration mappings, and looks up the RouteConfiguration to use per // request according to the algorithm specified in the -// :ref:`scope_key_builder` +// :ref:`scope_key_builder` // assigned to the HttpConnectionManager. // // For example, with the following configurations (in YAML): @@ -43,7 +43,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // key: vip // // ScopedRouteConfiguration resources (specified statically via -// :ref:`scoped_route_configurations_list` +// :ref:`scoped_route_configurations_list` // or obtained dynamically via SRDS): // // .. code:: @@ -78,7 +78,7 @@ message ScopedRouteConfiguration { "envoy.api.v2.ScopedRouteConfiguration"; // Specifies a key which is matched against the output of the - // :ref:`scope_key_builder` + // :ref:`scope_key_builder` // specified in the HttpConnectionManager. The matching is done per HTTP // request and is dependent on the order of the fragments contained in the // Key. @@ -100,7 +100,7 @@ message ScopedRouteConfiguration { // The ordered set of fragments to match against. The order must match the // fragments in the corresponding - // :ref:`scope_key_builder`. + // :ref:`scope_key_builder`. repeated Fragment fragments = 1 [(validate.rules).repeated = {min_items: 1}]; } @@ -110,8 +110,8 @@ message ScopedRouteConfiguration { // The name assigned to the routing scope. string name = 1 [(validate.rules).string = {min_len: 1}]; - // The resource name to use for a :ref:`envoy_api_msg_service.discovery.v3.DiscoveryRequest` to an - // RDS server to fetch the :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` associated + // The resource name to use for a :ref:`envoy_v3_api_msg_service.discovery.v3.DiscoveryRequest` to an + // RDS server to fetch the :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` associated // with this scope. string route_configuration_name = 2 [(validate.rules).string = {min_len: 1}]; diff --git a/generated_api_shadow/envoy/config/route/v4alpha/route.proto b/generated_api_shadow/envoy/config/route/v4alpha/route.proto index 1bddedd3fce6d..912fc8051556e 100644 --- a/generated_api_shadow/envoy/config/route/v4alpha/route.proto +++ b/generated_api_shadow/envoy/config/route/v4alpha/route.proto @@ -28,8 +28,8 @@ message RouteConfiguration { // The name of the route configuration. For example, it might match // :ref:`route_config_name - // ` in - // :ref:`envoy_api_msg_extensions.filters.network.http_connection_manager.v4alpha.Rds`. + // ` in + // :ref:`envoy_v3_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. string name = 1; // An array of virtual hosts that make up the route table. @@ -53,8 +53,8 @@ message RouteConfiguration { // Specifies a list of HTTP headers that should be added to each response that // the connection manager encodes. Headers specified at this level are applied - // after headers from any enclosed :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost` or - // :ref:`envoy_api_msg_config.route.v4alpha.RouteAction`. For more information, including details on + // after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + // :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption response_headers_to_add = 4 @@ -68,8 +68,8 @@ message RouteConfiguration { // Specifies a list of HTTP headers that should be added to each request // routed by the HTTP connection manager. Headers specified at this level are - // applied after headers from any enclosed :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost` or - // :ref:`envoy_api_msg_config.route.v4alpha.RouteAction`. For more information, including details on + // applied after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + // :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption request_headers_to_add = 6 @@ -100,22 +100,22 @@ message RouteConfiguration { // route table will load and the router filter will return a 404 if the route // is selected at runtime. This setting defaults to true if the route table // is statically defined via the :ref:`route_config - // ` + // ` // option. This setting default to false if the route table is loaded dynamically via the // :ref:`rds - // ` + // ` // option. Users may wish to override the default behavior in certain cases (for example when // using CDS with a static route table). google.protobuf.BoolValue validate_clusters = 7; // The maximum bytes of the response :ref:`direct response body - // ` size. If not specified the default + // ` size. If not specified the default // is 4096. // // .. warning:: // // Envoy currently holds the content of :ref:`direct response body - // ` in memory. Be careful setting + // ` in memory. Be careful setting // this to be larger than the default 4KB, since the allocated memory for direct response body // is not subject to data plane buffering controls. // diff --git a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto index edb91bc832e2e..34d3762bfdde9 100644 --- a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto @@ -101,8 +101,8 @@ message VirtualHost { // Specifies a list of HTTP headers that should be added to each request // handled by this virtual host. Headers specified at this level are applied - // after headers from enclosed :ref:`envoy_api_msg_config.route.v4alpha.Route` and before headers from the - // enclosing :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration`. For more information, including + // after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + // enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption request_headers_to_add = 7 @@ -116,8 +116,8 @@ message VirtualHost { // Specifies a list of HTTP headers that should be added to each response // handled by this virtual host. Headers specified at this level are applied - // after headers from enclosed :ref:`envoy_api_msg_config.route.v4alpha.Route` and before headers from the - // enclosing :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration`. For more information, including + // after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + // enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption response_headers_to_add = 10 @@ -138,7 +138,7 @@ message VirtualHost { // specific; see the :ref:`HTTP filter documentation ` // for if and how it is utilized. // [#comment: An entry's value may be wrapped in a - // :ref:`FilterConfig` + // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 15; @@ -149,7 +149,7 @@ message VirtualHost { // will see the attempt count as perceived by the second Envoy. Defaults to false. // This header is unaffected by the // :ref:`suppress_envoy_headers - // ` flag. + // ` flag. // // [#next-major-version: rename to include_attempt_count_in_request.] bool include_request_attempt_count = 14; @@ -161,7 +161,7 @@ message VirtualHost { // will see the attempt count as perceived by the Envoy closest upstream from itself. Defaults to false. // This header is unaffected by the // :ref:`suppress_envoy_headers - // ` flag. + // ` flag. bool include_attempt_count_in_response = 19; // Indicates the retry policy for all routes in this virtual host. Note that setting a @@ -172,7 +172,7 @@ message VirtualHost { // [#not-implemented-hide:] // Specifies the configuration for retry policy extension. Note that setting a route level entry // will take precedence over this config and it'll be treated independently (e.g.: values are not - // inherited). :ref:`Retry policy ` should not be + // inherited). :ref:`Retry policy ` should not be // set if this field is used. google.protobuf.Any retry_policy_typed_config = 20; @@ -200,7 +200,7 @@ message FilterAction { // .. attention:: // // Envoy supports routing on HTTP method via :ref:`header matching -// `. +// `. // [#next-free-field: 19] message Route { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.Route"; @@ -257,14 +257,14 @@ message Route { // specific; see the :ref:`HTTP filter documentation ` for // if and how it is utilized. // [#comment: An entry's value may be wrapped in a - // :ref:`FilterConfig` + // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 13; // Specifies a set of headers that will be added to requests matching this // route. Headers specified at this level are applied before headers from the - // enclosing :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost` and - // :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration`. For more information, including details on + // enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption request_headers_to_add = 9 @@ -278,8 +278,8 @@ message Route { // Specifies a set of headers that will be added to responses to requests // matching this route. Headers specified at this level are applied before - // headers from the enclosing :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost` and - // :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration`. For more information, including + // headers from the enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on // :ref:`custom request headers `. repeated core.v4alpha.HeaderValueOption response_headers_to_add = 10 @@ -301,9 +301,9 @@ message Route { google.protobuf.UInt32Value per_request_buffer_limit_bytes = 16; } -// Compared to the :ref:`cluster ` field that specifies a +// Compared to the :ref:`cluster ` field that specifies a // single upstream cluster as the target of a request, the :ref:`weighted_clusters -// ` option allows for specification of +// ` option allows for specification of // multiple upstream clusters along with weights that indicate the percentage of // traffic to be forwarded to each cluster. The router selects an upstream cluster based on the // weights. @@ -325,7 +325,7 @@ message WeightedCluster { string name = 1 [(validate.rules).string = {min_len: 1}]; // An integer between 0 and :ref:`total_weight - // `. When a request matches the route, + // `. When a request matches the route, // the choice of an upstream cluster is determined by its weight. The sum of weights across all // entries in the clusters array must add up to the total_weight, which defaults to 100. google.protobuf.UInt32Value weight = 2; @@ -333,38 +333,38 @@ message WeightedCluster { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in // the upstream cluster with metadata matching what is set in this field will be considered for // load balancing. Note that this will be merged with what's provided in - // :ref:`RouteAction.metadata_match `, with + // :ref:`RouteAction.metadata_match `, with // values here taking precedence. The filter name should be specified as *envoy.lb*. core.v4alpha.Metadata metadata_match = 3; // Specifies a list of headers to be added to requests when this cluster is selected - // through the enclosing :ref:`envoy_api_msg_config.route.v4alpha.RouteAction`. + // through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. // Headers specified at this level are applied before headers from the enclosing - // :ref:`envoy_api_msg_config.route.v4alpha.Route`, :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost`, and - // :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration`. For more information, including details on + // :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption request_headers_to_add = 4 [(validate.rules).repeated = {max_items: 1000}]; // Specifies a list of HTTP headers that should be removed from each request when - // this cluster is selected through the enclosing :ref:`envoy_api_msg_config.route.v4alpha.RouteAction`. + // this cluster is selected through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. repeated string request_headers_to_remove = 9 [(validate.rules).repeated = { items {string {well_known_regex: HTTP_HEADER_NAME strict: false}} }]; // Specifies a list of headers to be added to responses when this cluster is selected - // through the enclosing :ref:`envoy_api_msg_config.route.v4alpha.RouteAction`. + // through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. // Headers specified at this level are applied before headers from the enclosing - // :ref:`envoy_api_msg_config.route.v4alpha.Route`, :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost`, and - // :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration`. For more information, including details on + // :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + // :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. repeated core.v4alpha.HeaderValueOption response_headers_to_add = 5 [(validate.rules).repeated = {max_items: 1000}]; // Specifies a list of headers to be removed from responses when this cluster is selected - // through the enclosing :ref:`envoy_api_msg_config.route.v4alpha.RouteAction`. + // through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. repeated string response_headers_to_remove = 6 [(validate.rules).repeated = { items {string {well_known_regex: HTTP_HEADER_NAME strict: false}} }]; @@ -375,7 +375,7 @@ message WeightedCluster { // specific; see the :ref:`HTTP filter documentation ` // for if and how it is utilized. // [#comment: An entry's value may be wrapped in a - // :ref:`FilterConfig` + // :ref:`FilterConfig` // message to specify additional options.] map typed_per_filter_config = 10; } @@ -548,7 +548,7 @@ message CorsPolicy { // If neither ``enabled``, ``filter_enabled``, nor ``shadow_enabled`` are specified, the CORS // filter will be enabled for 100% of the requests. // - // If :ref:`runtime_key ` is + // If :ref:`runtime_key ` is // specified, Envoy will lookup the runtime key to get the percentage of requests to filter. core.v4alpha.RuntimeFractionalPercent filter_enabled = 9; } @@ -559,7 +559,7 @@ message CorsPolicy { // This field is intended to be used when ``filter_enabled`` and ``enabled`` are off. One of those // fields have to explicitly disable the filter in order for this setting to take effect. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate // and track the request's *Origin* to determine if it's valid but will not enforce any policies. core.v4alpha.RuntimeFractionalPercent shadow_enabled = 10; @@ -750,7 +750,7 @@ message RouteAction { // This overrides any enabled/disabled upgrade filter chain specified in the // HttpConnectionManager // :ref:`upgrade_configs - // ` + // ` // but does not affect any custom filter chain specified there. message UpgradeConfig { option (udpa.annotations.versioning).previous_message_type = @@ -791,9 +791,9 @@ message RouteAction { // Specifies the maximum duration allowed for streams on the route. If not specified, the value // from the :ref:`max_stream_duration - // ` field in + // ` field in // :ref:`HttpConnectionManager.common_http_protocol_options - // ` + // ` // is used. If this field is set explicitly to zero, any // HttpConnectionManager max_stream_duration timeout will be disabled for // this route. @@ -857,7 +857,7 @@ message RouteAction { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints // in the upstream cluster with metadata matching what's set in this field will be considered // for load balancing. If using :ref:`weighted_clusters - // `, metadata will be merged, with values + // `, metadata will be merged, with values // provided there taking precedence. The filter name should be specified as *envoy.lb*. core.v4alpha.Metadata metadata_match = 4; @@ -868,16 +868,16 @@ message RouteAction { // ` header. // // Only one of *prefix_rewrite* or - // :ref:`regex_rewrite ` + // :ref:`regex_rewrite ` // may be specified. // // .. attention:: // // Pay careful attention to the use of trailing slashes in the - // :ref:`route's match ` prefix value. + // :ref:`route's match ` prefix value. // Stripping a prefix from a path requires multiple Routes to handle all cases. For example, // rewriting */prefix* to */* and */prefix/etc* to */etc* cannot be done in a single - // :ref:`Route `, as shown by the below config entries: + // :ref:`Route `, as shown by the below config entries: // // .. code-block:: yaml // @@ -904,7 +904,7 @@ message RouteAction { // before the rewrite into the :ref:`x-envoy-original-path // ` header. // - // Only one of :ref:`prefix_rewrite ` + // Only one of :ref:`prefix_rewrite ` // or *regex_rewrite* may be specified. // // Examples using Google's `RE2 `_ engine: @@ -986,14 +986,14 @@ message RouteAction { // Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, // although the connection manager wide :ref:`stream_idle_timeout - // ` + // ` // will still apply. A value of 0 will completely disable the route's idle timeout, even if a // connection manager stream idle timeout is configured. // // The idle timeout is distinct to :ref:`timeout - // `, which provides an upper bound + // `, which provides an upper bound // on the upstream response time; :ref:`idle_timeout - // ` instead bounds the amount + // ` instead bounds the amount // of time the request's stream may be idle. // // After header decoding, the idle timeout will apply on downstream and @@ -1005,7 +1005,7 @@ message RouteAction { // // If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" // is configured, this timeout is scaled according to the value for - // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. google.protobuf.Duration idle_timeout = 24; // Indicates that the route has a retry policy. Note that if this is set, @@ -1016,7 +1016,7 @@ message RouteAction { // [#not-implemented-hide:] // Specifies the configuration for retry policy extension. Note that if this is set, it'll take // precedence over the virtual host level retry policy entirely (e.g.: policies are not merged, - // most internal one becomes the enforced policy). :ref:`Retry policy ` + // most internal one becomes the enforced policy). :ref:`Retry policy ` // should not be set if this field is used. google.protobuf.Any retry_policy_typed_config = 33; @@ -1032,7 +1032,7 @@ message RouteAction { // Specifies if the rate limit filter should include the virtual host rate // limits. By default, if the route configured rate limits, the virtual host - // :ref:`rate_limits ` are not applied to the + // :ref:`rate_limits ` are not applied to the // request. // // This field is deprecated. Please use :ref:`vh_rate_limits ` @@ -1056,15 +1056,15 @@ message RouteAction { // Indicates that the route has a CORS policy. CorsPolicy cors = 17; - // Deprecated by :ref:`grpc_timeout_header_max ` + // Deprecated by :ref:`grpc_timeout_header_max ` // If present, and the request is a gRPC request, use the // `grpc-timeout header `_, // or its default value (infinity) instead of - // :ref:`timeout `, but limit the applied timeout + // :ref:`timeout `, but limit the applied timeout // to the maximum value specified here. If configured as 0, the maximum allowed timeout for // gRPC requests is infinity. If not configured at all, the `grpc-timeout` header is not used // and gRPC requests time out like any other requests using - // :ref:`timeout ` or its default. + // :ref:`timeout ` or its default. // This can be used to prevent unexpected upstream request timeouts due to potentially long // time gaps between gRPC request and response in gRPC streaming mode. // @@ -1079,7 +1079,7 @@ message RouteAction { google.protobuf.Duration hidden_envoy_deprecated_max_grpc_timeout = 23 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; - // Deprecated by :ref:`grpc_timeout_header_offset `. + // Deprecated by :ref:`grpc_timeout_header_offset `. // If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting // the provided duration from the header. This is useful in allowing Envoy to set its global // timeout to be less than that of the deadline imposed by the calling client, which makes it more @@ -1095,7 +1095,7 @@ message RouteAction { // If present, Envoy will try to follow an upstream redirect response instead of proxying the // response back to the downstream. An upstream redirect response is defined // by :ref:`redirect_response_codes - // `. + // `. InternalRedirectPolicy internal_redirect_policy = 34; InternalRedirectAction hidden_envoy_deprecated_internal_redirect_action = 26 @@ -1103,15 +1103,15 @@ message RouteAction { // An internal redirect is handled, iff the number of previous internal redirects that a // downstream request has encountered is lower than this value, and - // :ref:`internal_redirect_action ` + // :ref:`internal_redirect_action ` // is set to :ref:`HANDLE_INTERNAL_REDIRECT - // ` + // ` // In the case where a downstream request is bounced among multiple routes by internal redirect, // the first route that hits this threshold, or has - // :ref:`internal_redirect_action ` + // :ref:`internal_redirect_action ` // set to // :ref:`PASS_THROUGH_INTERNAL_REDIRECT - // ` + // ` // will pass the redirect back to downstream. // // If not specified, at most one redirect will be followed. @@ -1281,7 +1281,7 @@ message RetryPolicy { // .. note:: // // If left unspecified, Envoy will use the global - // :ref:`route timeout ` for the request. + // :ref:`route timeout ` for the request. // Consequently, when using a :ref:`5xx ` based // retry policy, a request that times out will not be retried as the total timeout budget // would have been exhausted. @@ -1356,7 +1356,7 @@ message HedgePolicy { // if there are no more retries left. // * After per-try timeout, an error response would be discarded, as a retry in the form of a hedged request is already in progress. // - // Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least + // Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least // one error code and specifies a maximum number of retries. // // Defaults to false. @@ -1432,7 +1432,7 @@ message RedirectAction { // .. attention:: // // Pay attention to the use of trailing slashes as mentioned in - // :ref:`RouteAction's prefix_rewrite `. + // :ref:`RouteAction's prefix_rewrite `. string prefix_rewrite = 5 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; @@ -1484,8 +1484,8 @@ message DirectResponseAction { // .. note:: // // Headers can be specified using *response_headers_to_add* in the enclosing - // :ref:`envoy_api_msg_config.route.v4alpha.Route`, :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration` or - // :ref:`envoy_api_msg_config.route.v4alpha.VirtualHost`. + // :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` or + // :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`. core.v4alpha.DataSource body = 2; } @@ -1542,7 +1542,7 @@ message Tracing { // A list of custom tags with unique tag name to create tags for the active span. // It will take effect after merging with the :ref:`corresponding configuration - // ` + // ` // configured in the HTTP connection manager. If two tags with the same name are configured // each in the HTTP connection manager and the route level, the one configured here takes // priority. @@ -1614,14 +1614,14 @@ message RateLimit { // ("destination_cluster", "") // // Once a request matches against a route table rule, a routed cluster is determined by one of - // the following :ref:`route table configuration ` + // the following :ref:`route table configuration ` // settings: // - // * :ref:`cluster ` indicates the upstream cluster + // * :ref:`cluster ` indicates the upstream cluster // to route to. - // * :ref:`weighted_clusters ` + // * :ref:`weighted_clusters ` // chooses a cluster randomly from a set of clusters with attributed weight. - // * :ref:`cluster_header ` indicates which + // * :ref:`cluster_header ` indicates which // header in the request contains the target cluster. message DestinationCluster { option (udpa.annotations.versioning).previous_message_type = @@ -1715,7 +1715,7 @@ message RateLimit { // ("", "") // // .. attention:: - // This action has been deprecated in favor of the :ref:`metadata ` action + // This action has been deprecated in favor of the :ref:`metadata ` action message DynamicMetaData { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RateLimit.Action.DynamicMetaData"; @@ -1745,7 +1745,7 @@ message RateLimit { // Query :ref:`dynamic metadata ` DYNAMIC = 0; - // Query :ref:`route entry metadata ` + // Query :ref:`route entry metadata ` ROUTE_ENTRY = 1; } @@ -1788,7 +1788,7 @@ message RateLimit { // Rate limit on dynamic metadata. // // .. attention:: - // This field has been deprecated in favor of the :ref:`metadata ` field + // This field has been deprecated in favor of the :ref:`metadata ` field DynamicMetaData hidden_envoy_deprecated_dynamic_metadata = 7 [ deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0", @@ -1816,7 +1816,7 @@ message RateLimit { // Metadata struct that defines the key and path to retrieve the struct value. // The value must be a struct containing an integer "requests_per_unit" property // and a "unit" property with a value parseable to :ref:`RateLimitUnit - // enum ` + // enum ` type.metadata.v3.MetadataKey metadata_key = 1 [(validate.rules).message = {required: true}]; } @@ -1874,8 +1874,8 @@ message RateLimit { // // .. attention:: // In the absence of any header match specifier, match will default to :ref:`present_match -// `. i.e, a request that has the :ref:`name -// ` header will match, regardless of the header's +// `. i.e, a request that has the :ref:`name +// ` header will match, regardless of the header's // value. // // [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] @@ -1988,7 +1988,7 @@ message InternalRedirectPolicy { // downstream request has encountered is lower than this value. // In the case where a downstream request is bounced among multiple routes by internal redirect, // the first route that hits this threshold, or does not set :ref:`internal_redirect_policy - // ` + // ` // will pass the redirect back to downstream. // // If not specified, at most one redirect will be followed. @@ -2012,9 +2012,9 @@ message InternalRedirectPolicy { // A simple wrapper for an HTTP filter config. This is intended to be used as a wrapper for the // map value in -// :ref:`VirtualHost.typed_per_filter_config`, -// :ref:`Route.typed_per_filter_config`, -// or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` +// :ref:`VirtualHost.typed_per_filter_config`, +// :ref:`Route.typed_per_filter_config`, +// or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` // to add additional flags to the filter. // [#not-implemented-hide:] message FilterConfig { diff --git a/generated_api_shadow/envoy/config/route/v4alpha/scoped_route.proto b/generated_api_shadow/envoy/config/route/v4alpha/scoped_route.proto index 0704ceacbbac3..4c640223f701c 100644 --- a/generated_api_shadow/envoy/config/route/v4alpha/scoped_route.proto +++ b/generated_api_shadow/envoy/config/route/v4alpha/scoped_route.proto @@ -15,13 +15,13 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // * Routing :ref:`architecture overview ` // Specifies a routing scope, which associates a -// :ref:`Key` to a -// :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration` (identified by its resource name). +// :ref:`Key` to a +// :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` (identified by its resource name). // // The HTTP connection manager builds up a table consisting of these Key to // RouteConfiguration mappings, and looks up the RouteConfiguration to use per // request according to the algorithm specified in the -// :ref:`scope_key_builder` +// :ref:`scope_key_builder` // assigned to the HttpConnectionManager. // // For example, with the following configurations (in YAML): @@ -43,7 +43,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // key: vip // // ScopedRouteConfiguration resources (specified statically via -// :ref:`scoped_route_configurations_list` +// :ref:`scoped_route_configurations_list` // or obtained dynamically via SRDS): // // .. code:: @@ -78,7 +78,7 @@ message ScopedRouteConfiguration { "envoy.config.route.v3.ScopedRouteConfiguration"; // Specifies a key which is matched against the output of the - // :ref:`scope_key_builder` + // :ref:`scope_key_builder` // specified in the HttpConnectionManager. The matching is done per HTTP // request and is dependent on the order of the fragments contained in the // Key. @@ -100,7 +100,7 @@ message ScopedRouteConfiguration { // The ordered set of fragments to match against. The order must match the // fragments in the corresponding - // :ref:`scope_key_builder`. + // :ref:`scope_key_builder`. repeated Fragment fragments = 1 [(validate.rules).repeated = {min_items: 1}]; } @@ -110,8 +110,8 @@ message ScopedRouteConfiguration { // The name assigned to the routing scope. string name = 1 [(validate.rules).string = {min_len: 1}]; - // The resource name to use for a :ref:`envoy_api_msg_service.discovery.v4alpha.DiscoveryRequest` to an - // RDS server to fetch the :ref:`envoy_api_msg_config.route.v4alpha.RouteConfiguration` associated + // The resource name to use for a :ref:`envoy_v3_api_msg_service.discovery.v3.DiscoveryRequest` to an + // RDS server to fetch the :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` associated // with this scope. string route_configuration_name = 2 [(validate.rules).string = {min_len: 1}]; diff --git a/generated_api_shadow/envoy/config/tap/v3/common.proto b/generated_api_shadow/envoy/config/tap/v3/common.proto index 42189d2aa4256..c25a2af5a3b51 100644 --- a/generated_api_shadow/envoy/config/tap/v3/common.proto +++ b/generated_api_shadow/envoy/config/tap/v3/common.proto @@ -30,17 +30,17 @@ message TapConfig { // The match configuration. If the configuration matches the data source being tapped, a tap will // occur, with the result written to the configured output. - // Exactly one of :ref:`match ` and - // :ref:`match_config ` must be set. If both - // are set, the :ref:`match ` will be used. + // Exactly one of :ref:`match ` and + // :ref:`match_config ` must be set. If both + // are set, the :ref:`match ` will be used. MatchPredicate match_config = 1 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; // The match configuration. If the configuration matches the data source being tapped, a tap will // occur, with the result written to the configured output. - // Exactly one of :ref:`match ` and - // :ref:`match_config ` must be set. If both - // are set, the :ref:`match ` will be used. + // Exactly one of :ref:`match ` and + // :ref:`match_config ` must be set. If both + // are set, the :ref:`match ` will be used. common.matcher.v3.MatchPredicate match = 4; // The tap output configuration. If a match configuration matches a data source being tapped, @@ -54,7 +54,7 @@ message TapConfig { // .. note:: // // This field defaults to 100/:ref:`HUNDRED - // `. + // `. core.v3.RuntimeFractionalPercent tap_enabled = 3; } @@ -161,19 +161,19 @@ message OutputConfig { // For buffered tapping, the maximum amount of received body that will be buffered prior to // truncation. If truncation occurs, the :ref:`truncated - // ` field will be set. If not specified, the + // ` field will be set. If not specified, the // default is 1KiB. google.protobuf.UInt32Value max_buffered_rx_bytes = 2; // For buffered tapping, the maximum amount of transmitted body that will be buffered prior to // truncation. If truncation occurs, the :ref:`truncated - // ` field will be set. If not specified, the + // ` field will be set. If not specified, the // default is 1KiB. google.protobuf.UInt32Value max_buffered_tx_bytes = 3; // Indicates whether taps produce a single buffered message per tap, or multiple streamed // messages per tap in the emitted :ref:`TraceWrapper - // ` messages. Note that streamed tapping does not + // ` messages. Note that streamed tapping does not // mean that no buffering takes place. Buffering may be required if data is processed before a // match can be determined. See the HTTP tap filter :ref:`streaming // ` documentation for more information. @@ -186,20 +186,20 @@ message OutputSink { "envoy.service.tap.v2alpha.OutputSink"; // Output format. All output is in the form of one or more :ref:`TraceWrapper - // ` messages. This enumeration indicates + // ` messages. This enumeration indicates // how those messages are written. Note that not all sinks support all output formats. See // individual sink documentation for more information. enum Format { - // Each message will be written as JSON. Any :ref:`body ` + // Each message will be written as JSON. Any :ref:`body ` // data will be present in the :ref:`as_bytes - // ` field. This means that body data will be + // ` field. This means that body data will be // base64 encoded as per the `proto3 JSON mappings // `_. JSON_BODY_AS_BYTES = 0; - // Each message will be written as JSON. Any :ref:`body ` + // Each message will be written as JSON. Any :ref:`body ` // data will be present in the :ref:`as_string - // ` field. This means that body data will be + // ` field. This means that body data will be // string encoded as per the `proto3 JSON mappings // `_. This format type is // useful when it is known that that body is human readable (e.g., JSON over HTTP) and the diff --git a/generated_api_shadow/envoy/config/tap/v4alpha/common.proto b/generated_api_shadow/envoy/config/tap/v4alpha/common.proto index 0deb5b48d27b3..f436c7947d6e7 100644 --- a/generated_api_shadow/envoy/config/tap/v4alpha/common.proto +++ b/generated_api_shadow/envoy/config/tap/v4alpha/common.proto @@ -29,17 +29,17 @@ message TapConfig { // The match configuration. If the configuration matches the data source being tapped, a tap will // occur, with the result written to the configured output. - // Exactly one of :ref:`match ` and - // :ref:`match_config ` must be set. If both - // are set, the :ref:`match ` will be used. + // Exactly one of :ref:`match ` and + // :ref:`match_config ` must be set. If both + // are set, the :ref:`match ` will be used. MatchPredicate hidden_envoy_deprecated_match_config = 1 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; // The match configuration. If the configuration matches the data source being tapped, a tap will // occur, with the result written to the configured output. - // Exactly one of :ref:`match ` and - // :ref:`match_config ` must be set. If both - // are set, the :ref:`match ` will be used. + // Exactly one of :ref:`match ` and + // :ref:`match_config ` must be set. If both + // are set, the :ref:`match ` will be used. common.matcher.v4alpha.MatchPredicate match = 4; // The tap output configuration. If a match configuration matches a data source being tapped, @@ -53,7 +53,7 @@ message TapConfig { // .. note:: // // This field defaults to 100/:ref:`HUNDRED - // `. + // `. core.v4alpha.RuntimeFractionalPercent tap_enabled = 3; } @@ -164,19 +164,19 @@ message OutputConfig { // For buffered tapping, the maximum amount of received body that will be buffered prior to // truncation. If truncation occurs, the :ref:`truncated - // ` field will be set. If not specified, the + // ` field will be set. If not specified, the // default is 1KiB. google.protobuf.UInt32Value max_buffered_rx_bytes = 2; // For buffered tapping, the maximum amount of transmitted body that will be buffered prior to // truncation. If truncation occurs, the :ref:`truncated - // ` field will be set. If not specified, the + // ` field will be set. If not specified, the // default is 1KiB. google.protobuf.UInt32Value max_buffered_tx_bytes = 3; // Indicates whether taps produce a single buffered message per tap, or multiple streamed // messages per tap in the emitted :ref:`TraceWrapper - // ` messages. Note that streamed tapping does not + // ` messages. Note that streamed tapping does not // mean that no buffering takes place. Buffering may be required if data is processed before a // match can be determined. See the HTTP tap filter :ref:`streaming // ` documentation for more information. @@ -188,20 +188,20 @@ message OutputSink { option (udpa.annotations.versioning).previous_message_type = "envoy.config.tap.v3.OutputSink"; // Output format. All output is in the form of one or more :ref:`TraceWrapper - // ` messages. This enumeration indicates + // ` messages. This enumeration indicates // how those messages are written. Note that not all sinks support all output formats. See // individual sink documentation for more information. enum Format { - // Each message will be written as JSON. Any :ref:`body ` + // Each message will be written as JSON. Any :ref:`body ` // data will be present in the :ref:`as_bytes - // ` field. This means that body data will be + // ` field. This means that body data will be // base64 encoded as per the `proto3 JSON mappings // `_. JSON_BODY_AS_BYTES = 0; - // Each message will be written as JSON. Any :ref:`body ` + // Each message will be written as JSON. Any :ref:`body ` // data will be present in the :ref:`as_string - // ` field. This means that body data will be + // ` field. This means that body data will be // string encoded as per the `proto3 JSON mappings // `_. This format type is // useful when it is known that that body is human readable (e.g., JSON over HTTP) and the diff --git a/generated_api_shadow/envoy/config/trace/v3/http_tracer.proto b/generated_api_shadow/envoy/config/trace/v3/http_tracer.proto index a936524a4d8c9..5ec74646e79be 100644 --- a/generated_api_shadow/envoy/config/trace/v3/http_tracer.proto +++ b/generated_api_shadow/envoy/config/trace/v3/http_tracer.proto @@ -26,15 +26,15 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // .. attention:: // // Use of this message type has been deprecated in favor of direct use of -// :ref:`Tracing.Http `. +// :ref:`Tracing.Http `. message Tracing { option (udpa.annotations.versioning).previous_message_type = "envoy.config.trace.v2.Tracing"; // Configuration for an HTTP tracer provider used by Envoy. // // The configuration is defined by the - // :ref:`HttpConnectionManager.Tracing ` - // :ref:`provider ` + // :ref:`HttpConnectionManager.Tracing ` + // :ref:`provider ` // field. message Http { option (udpa.annotations.versioning).previous_message_type = diff --git a/generated_api_shadow/envoy/config/trace/v4alpha/http_tracer.proto b/generated_api_shadow/envoy/config/trace/v4alpha/http_tracer.proto index 3e896902c99f2..33c8e73d56b9d 100644 --- a/generated_api_shadow/envoy/config/trace/v4alpha/http_tracer.proto +++ b/generated_api_shadow/envoy/config/trace/v4alpha/http_tracer.proto @@ -24,15 +24,15 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // .. attention:: // // Use of this message type has been deprecated in favor of direct use of -// :ref:`Tracing.Http `. +// :ref:`Tracing.Http `. message Tracing { option (udpa.annotations.versioning).previous_message_type = "envoy.config.trace.v3.Tracing"; // Configuration for an HTTP tracer provider used by Envoy. // // The configuration is defined by the - // :ref:`HttpConnectionManager.Tracing ` - // :ref:`provider ` + // :ref:`HttpConnectionManager.Tracing ` + // :ref:`provider ` // field. message Http { option (udpa.annotations.versioning).previous_message_type = diff --git a/generated_api_shadow/envoy/data/cluster/v3/outlier_detection_event.proto b/generated_api_shadow/envoy/data/cluster/v3/outlier_detection_event.proto index f87cd1582b090..2ba29d89954bb 100644 --- a/generated_api_shadow/envoy/data/cluster/v3/outlier_detection_event.proto +++ b/generated_api_shadow/envoy/data/cluster/v3/outlier_detection_event.proto @@ -21,7 +21,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; enum OutlierEjectionType { // In case upstream host returns certain number of consecutive 5xx. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *false*, all type of errors are treated as HTTP 5xx errors. // See :ref:`Cluster outlier detection ` documentation for // details. @@ -34,7 +34,7 @@ enum OutlierEjectionType { // and selects hosts for which ratio of successful replies deviates from other hosts // in the cluster. // If - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is *false*, all errors (externally and locally generated) are used to calculate success rate // statistics. See :ref:`Cluster outlier detection ` // documentation for details. @@ -42,7 +42,7 @@ enum OutlierEjectionType { // Consecutive local origin failures: Connection failures, resets, timeouts, etc // This type of ejection happens only when - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is set to *true*. // See :ref:`Cluster outlier detection ` documentation for CONSECUTIVE_LOCAL_ORIGIN_FAILURE = 3; @@ -50,7 +50,7 @@ enum OutlierEjectionType { // Runs over aggregated success rate statistics for local origin failures // for all hosts in the cluster and selects hosts for which success rate deviates from other // hosts in the cluster. This type of ejection happens only when - // :ref:`outlier_detection.split_external_local_origin_errors` + // :ref:`outlier_detection.split_external_local_origin_errors` // is set to *true*. // See :ref:`Cluster outlier detection ` documentation for SUCCESS_RATE_LOCAL_ORIGIN = 4; @@ -87,7 +87,7 @@ message OutlierDetectionEvent { // The time in seconds since the last action (either an ejection or unejection) took place. google.protobuf.UInt64Value secs_since_last_action = 3; - // The :ref:`cluster ` that owns the ejected host. + // The :ref:`cluster ` that owns the ejected host. string cluster_name = 4 [(validate.rules).string = {min_len: 1}]; // The URL of the ejected host. E.g., ``tcp://1.2.3.4:80``. diff --git a/generated_api_shadow/envoy/data/core/v3/health_check_event.proto b/generated_api_shadow/envoy/data/core/v3/health_check_event.proto index 66624938dc4e9..92e2d68d255da 100644 --- a/generated_api_shadow/envoy/data/core/v3/health_check_event.proto +++ b/generated_api_shadow/envoy/data/core/v3/health_check_event.proto @@ -79,7 +79,7 @@ message HealthCheckAddHealthy { "envoy.data.core.v2alpha.HealthCheckAddHealthy"; // Whether this addition is the result of the first ever health check on a host, in which case - // the configured :ref:`healthy threshold ` + // the configured :ref:`healthy threshold ` // is bypassed and the host is immediately added. bool first_check = 1; } diff --git a/generated_api_shadow/envoy/data/tap/v3/common.proto b/generated_api_shadow/envoy/data/tap/v3/common.proto index 861da12e20c1b..2c4fb9c61a555 100644 --- a/generated_api_shadow/envoy/data/tap/v3/common.proto +++ b/generated_api_shadow/envoy/data/tap/v3/common.proto @@ -23,15 +23,15 @@ message Body { bytes as_bytes = 1; // Body data as string. This field is only used when the :ref:`JSON_BODY_AS_STRING - // ` sink + // ` sink // format type is selected. See the documentation for that option for why this is useful. string as_string = 2; } // Specifies whether body data has been truncated to fit within the specified // :ref:`max_buffered_rx_bytes - // ` and + // ` and // :ref:`max_buffered_tx_bytes - // ` settings. + // ` settings. bool truncated = 3; } diff --git a/generated_api_shadow/envoy/data/tap/v3/transport.proto b/generated_api_shadow/envoy/data/tap/v3/transport.proto index f596759cb4907..0ff4b7da06043 100644 --- a/generated_api_shadow/envoy/data/tap/v3/transport.proto +++ b/generated_api_shadow/envoy/data/tap/v3/transport.proto @@ -95,11 +95,11 @@ message SocketBufferedTrace { repeated SocketEvent events = 3; // Set to true if read events were truncated due to the :ref:`max_buffered_rx_bytes - // ` setting. + // ` setting. bool read_truncated = 4; // Set to true if write events were truncated due to the :ref:`max_buffered_tx_bytes - // ` setting. + // ` setting. bool write_truncated = 5; } diff --git a/generated_api_shadow/envoy/extensions/access_loggers/file/v3/file.proto b/generated_api_shadow/envoy/extensions/access_loggers/file/v3/file.proto index d44a658276a52..bca7c913a65b5 100644 --- a/generated_api_shadow/envoy/extensions/access_loggers/file/v3/file.proto +++ b/generated_api_shadow/envoy/extensions/access_loggers/file/v3/file.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: File access log] // [#extension: envoy.access_loggers.file] -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that writes log entries directly to a file. Configures the built-in *envoy.access_loggers.file* // AccessLog. // [#next-free-field: 6] diff --git a/generated_api_shadow/envoy/extensions/access_loggers/file/v4alpha/file.proto b/generated_api_shadow/envoy/extensions/access_loggers/file/v4alpha/file.proto index 1a2ceff22288d..62afb2040fdae 100644 --- a/generated_api_shadow/envoy/extensions/access_loggers/file/v4alpha/file.proto +++ b/generated_api_shadow/envoy/extensions/access_loggers/file/v4alpha/file.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: File access log] // [#extension: envoy.access_loggers.file] -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that writes log entries directly to a file. Configures the built-in *envoy.access_loggers.file* // AccessLog. // [#next-free-field: 6] diff --git a/generated_api_shadow/envoy/extensions/access_loggers/grpc/v3/als.proto b/generated_api_shadow/envoy/extensions/access_loggers/grpc/v3/als.proto index 968dfbeec0162..fa0a9f0f820d5 100644 --- a/generated_api_shadow/envoy/extensions/access_loggers/grpc/v3/als.proto +++ b/generated_api_shadow/envoy/extensions/access_loggers/grpc/v3/als.proto @@ -20,9 +20,9 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: gRPC Access Log Service (ALS)] // Configuration for the built-in *envoy.access_loggers.http_grpc* -// :ref:`AccessLog `. This configuration will +// :ref:`AccessLog `. This configuration will // populate :ref:`StreamAccessLogsMessage.http_logs -// `. +// `. // [#extension: envoy.access_loggers.http_grpc] message HttpGrpcAccessLogConfig { option (udpa.annotations.versioning).previous_message_type = @@ -31,15 +31,15 @@ message HttpGrpcAccessLogConfig { CommonGrpcAccessLogConfig common_config = 1 [(validate.rules).message = {required: true}]; // Additional request headers to log in :ref:`HTTPRequestProperties.request_headers - // `. + // `. repeated string additional_request_headers_to_log = 2; // Additional response headers to log in :ref:`HTTPResponseProperties.response_headers - // `. + // `. repeated string additional_response_headers_to_log = 3; // Additional response trailers to log in :ref:`HTTPResponseProperties.response_trailers - // `. + // `. repeated string additional_response_trailers_to_log = 4; } @@ -60,7 +60,7 @@ message CommonGrpcAccessLogConfig { "envoy.config.accesslog.v2.CommonGrpcAccessLogConfig"; // The friendly name of the access log to be returned in :ref:`StreamAccessLogsMessage.Identifier - // `. This allows the + // `. This allows the // access log server to differentiate between different access logs coming from the same Envoy. string log_name = 1 [(validate.rules).string = {min_len: 1}]; @@ -83,7 +83,7 @@ message CommonGrpcAccessLogConfig { google.protobuf.UInt32Value buffer_size_bytes = 4; // Additional filter state objects to log in :ref:`filter_state_objects - // `. + // `. // Logger will call `FilterState::Object::serializeAsProto` to serialize the filter state object. repeated string filter_state_objects_to_log = 5; } diff --git a/generated_api_shadow/envoy/extensions/access_loggers/grpc/v4alpha/als.proto b/generated_api_shadow/envoy/extensions/access_loggers/grpc/v4alpha/als.proto index c7bf15948b231..9e6fb1e48386e 100644 --- a/generated_api_shadow/envoy/extensions/access_loggers/grpc/v4alpha/als.proto +++ b/generated_api_shadow/envoy/extensions/access_loggers/grpc/v4alpha/als.proto @@ -20,9 +20,9 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: gRPC Access Log Service (ALS)] // Configuration for the built-in *envoy.access_loggers.http_grpc* -// :ref:`AccessLog `. This configuration will +// :ref:`AccessLog `. This configuration will // populate :ref:`StreamAccessLogsMessage.http_logs -// `. +// `. // [#extension: envoy.access_loggers.http_grpc] message HttpGrpcAccessLogConfig { option (udpa.annotations.versioning).previous_message_type = @@ -31,15 +31,15 @@ message HttpGrpcAccessLogConfig { CommonGrpcAccessLogConfig common_config = 1 [(validate.rules).message = {required: true}]; // Additional request headers to log in :ref:`HTTPRequestProperties.request_headers - // `. + // `. repeated string additional_request_headers_to_log = 2; // Additional response headers to log in :ref:`HTTPResponseProperties.response_headers - // `. + // `. repeated string additional_response_headers_to_log = 3; // Additional response trailers to log in :ref:`HTTPResponseProperties.response_trailers - // `. + // `. repeated string additional_response_trailers_to_log = 4; } @@ -60,7 +60,7 @@ message CommonGrpcAccessLogConfig { "envoy.extensions.access_loggers.grpc.v3.CommonGrpcAccessLogConfig"; // The friendly name of the access log to be returned in :ref:`StreamAccessLogsMessage.Identifier - // `. This allows the + // `. This allows the // access log server to differentiate between different access logs coming from the same Envoy. string log_name = 1 [(validate.rules).string = {min_len: 1}]; @@ -83,7 +83,7 @@ message CommonGrpcAccessLogConfig { google.protobuf.UInt32Value buffer_size_bytes = 4; // Additional filter state objects to log in :ref:`filter_state_objects - // `. + // `. // Logger will call `FilterState::Object::serializeAsProto` to serialize the filter state object. repeated string filter_state_objects_to_log = 5; } diff --git a/generated_api_shadow/envoy/extensions/access_loggers/open_telemetry/v3alpha/logs_service.proto b/generated_api_shadow/envoy/extensions/access_loggers/open_telemetry/v3alpha/logs_service.proto index 5a3bdce5e8bf6..1b7027133e153 100644 --- a/generated_api_shadow/envoy/extensions/access_loggers/open_telemetry/v3alpha/logs_service.proto +++ b/generated_api_shadow/envoy/extensions/access_loggers/open_telemetry/v3alpha/logs_service.proto @@ -18,7 +18,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: OpenTelemetry (gRPC) Access Log] // Configuration for the built-in *envoy.access_loggers.open_telemetry* -// :ref:`AccessLog `. This configuration will +// :ref:`AccessLog `. This configuration will // populate `opentelemetry.proto.collector.v1.logs.ExportLogsServiceRequest.resource_logs `_. // OpenTelemetry `Resource `_ // attributes are filled with Envoy node info. In addition, the request start time is set in the diff --git a/generated_api_shadow/envoy/extensions/access_loggers/open_telemetry/v4alpha/logs_service.proto b/generated_api_shadow/envoy/extensions/access_loggers/open_telemetry/v4alpha/logs_service.proto index 6fc4bcb7ea6c2..ceecd924e19d9 100644 --- a/generated_api_shadow/envoy/extensions/access_loggers/open_telemetry/v4alpha/logs_service.proto +++ b/generated_api_shadow/envoy/extensions/access_loggers/open_telemetry/v4alpha/logs_service.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: OpenTelemetry (gRPC) Access Log] // Configuration for the built-in *envoy.access_loggers.open_telemetry* -// :ref:`AccessLog `. This configuration will +// :ref:`AccessLog `. This configuration will // populate `opentelemetry.proto.collector.v1.logs.ExportLogsServiceRequest.resource_logs `_. // OpenTelemetry `Resource `_ // attributes are filled with Envoy node info. In addition, the request start time is set in the diff --git a/generated_api_shadow/envoy/extensions/access_loggers/stream/v3/stream.proto b/generated_api_shadow/envoy/extensions/access_loggers/stream/v3/stream.proto index cd683b67e2f6b..bd704ccdb6768 100644 --- a/generated_api_shadow/envoy/extensions/access_loggers/stream/v3/stream.proto +++ b/generated_api_shadow/envoy/extensions/access_loggers/stream/v3/stream.proto @@ -15,7 +15,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Standard Streams Access loggers] // [#extension: envoy.access_loggers.stream] -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that writes log entries directly to the operating system's standard output. message StdoutAccessLog { oneof access_log_format { @@ -26,7 +26,7 @@ message StdoutAccessLog { } } -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that writes log entries directly to the operating system's standard error. message StderrAccessLog { oneof access_log_format { diff --git a/generated_api_shadow/envoy/extensions/access_loggers/stream/v4alpha/stream.proto b/generated_api_shadow/envoy/extensions/access_loggers/stream/v4alpha/stream.proto index a0dbeb7b4b51d..5be54ad4721dd 100644 --- a/generated_api_shadow/envoy/extensions/access_loggers/stream/v4alpha/stream.proto +++ b/generated_api_shadow/envoy/extensions/access_loggers/stream/v4alpha/stream.proto @@ -16,7 +16,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: Standard Streams Access loggers] // [#extension: envoy.access_loggers.stream] -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that writes log entries directly to the operating system's standard output. message StdoutAccessLog { option (udpa.annotations.versioning).previous_message_type = @@ -30,7 +30,7 @@ message StdoutAccessLog { } } -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that writes log entries directly to the operating system's standard error. message StderrAccessLog { option (udpa.annotations.versioning).previous_message_type = diff --git a/generated_api_shadow/envoy/extensions/access_loggers/wasm/v3/wasm.proto b/generated_api_shadow/envoy/extensions/access_loggers/wasm/v3/wasm.proto index 6c3ac617636b8..44e96345dfee5 100644 --- a/generated_api_shadow/envoy/extensions/access_loggers/wasm/v3/wasm.proto +++ b/generated_api_shadow/envoy/extensions/access_loggers/wasm/v3/wasm.proto @@ -14,7 +14,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Wasm access log] // [#extension: envoy.access_loggers.wasm] -// Custom configuration for an :ref:`AccessLog ` +// Custom configuration for an :ref:`AccessLog ` // that calls into a WASM VM. Configures the built-in *envoy.access_loggers.wasm* // AccessLog. message WasmAccessLog { diff --git a/generated_api_shadow/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto b/generated_api_shadow/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto index 869e8c42caba1..c4fc8285ee597 100644 --- a/generated_api_shadow/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto +++ b/generated_api_shadow/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto @@ -24,12 +24,12 @@ message ClusterConfig { // The DNS cache configuration that the cluster will attach to. Note this configuration must // match that of associated :ref:`dynamic forward proxy HTTP filter configuration - // `. + // `. common.dynamic_forward_proxy.v3.DnsCacheConfig dns_cache_config = 1 [(validate.rules).message = {required: true}]; // If true allow the cluster configuration to disable the auto_sni and auto_san_validation options // in the :ref:`cluster's upstream_http_protocol_options - // ` + // ` bool allow_insecure_cluster_options = 2; } diff --git a/generated_api_shadow/envoy/extensions/clusters/redis/v3/redis_cluster.proto b/generated_api_shadow/envoy/extensions/clusters/redis/v3/redis_cluster.proto index afc19777edf2b..73598eafbe9d2 100644 --- a/generated_api_shadow/envoy/extensions/clusters/redis/v3/redis_cluster.proto +++ b/generated_api_shadow/envoy/extensions/clusters/redis/v3/redis_cluster.proto @@ -27,7 +27,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // updated at user-configured intervals. // // Additionally, if -// :ref:`enable_redirection` +// :ref:`enable_redirection` // is true, then moved and ask redirection errors from upstream servers will trigger a topology // refresh when they exceed a user-configured error threshold. // diff --git a/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto b/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto index 6a516b4300283..215c7414c5a2d 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto @@ -39,7 +39,7 @@ message AwsRequestSigning { // // Note: this rewrite affects both signing and host header forwarding. However, this // option shouldn't be used with - // :ref:`HCM host rewrite ` given that the + // :ref:`HCM host rewrite ` given that the // value set here would be used for signing whereas the value set in the HCM would be used // for host header forwarding which is not the desired outcome. string host_rewrite = 3; diff --git a/generated_api_shadow/envoy/extensions/filters/http/composite/v3/composite.proto b/generated_api_shadow/envoy/extensions/filters/http/composite/v3/composite.proto index 706e7a5b24668..3afc136904c76 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/composite/v3/composite.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/composite/v3/composite.proto @@ -21,9 +21,9 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // incoming request. // // This is intended to be used with -// :ref:`ExtensionWithMatcher ` +// :ref:`ExtensionWithMatcher ` // where a match tree is specified that indicates (via -// :ref:`ExecuteFilterAction `) +// :ref:`ExecuteFilterAction `) // which filter configuration to create and delegate to. // message Composite { diff --git a/generated_api_shadow/envoy/extensions/filters/http/compressor/v3/compressor.proto b/generated_api_shadow/envoy/extensions/filters/http/compressor/v3/compressor.proto index 1c08706b055c7..ab1d426be4540 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/compressor/v3/compressor.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/compressor/v3/compressor.proto @@ -99,7 +99,7 @@ message Compressor { [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; // A compressor library to use for compression. Currently only - // :ref:`envoy.compression.gzip.compressor` + // :ref:`envoy.compression.gzip.compressor` // is included in Envoy. // This field is ignored if used in the context of the gzip http-filter, but is mandatory otherwise. // [#extension-category: envoy.compression.compressor] diff --git a/generated_api_shadow/envoy/extensions/filters/http/compressor/v4alpha/compressor.proto b/generated_api_shadow/envoy/extensions/filters/http/compressor/v4alpha/compressor.proto index 419d37d180189..2dbcf3917a713 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/compressor/v4alpha/compressor.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/compressor/v4alpha/compressor.proto @@ -108,7 +108,7 @@ message Compressor { [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; // A compressor library to use for compression. Currently only - // :ref:`envoy.compression.gzip.compressor` + // :ref:`envoy.compression.gzip.compressor` // is included in Envoy. // This field is ignored if used in the context of the gzip http-filter, but is mandatory otherwise. // [#extension-category: envoy.compression.compressor] diff --git a/generated_api_shadow/envoy/extensions/filters/http/csrf/v3/csrf.proto b/generated_api_shadow/envoy/extensions/filters/http/csrf/v3/csrf.proto index 263d705e3f545..39b0455bd7981 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/csrf/v3/csrf.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/csrf/v3/csrf.proto @@ -25,13 +25,13 @@ message CsrfPolicy { // Specifies the % of requests for which the CSRF filter is enabled. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests to filter. // // .. note:: // // This field defaults to 100/:ref:`HUNDRED - // `. + // `. config.core.v3.RuntimeFractionalPercent filter_enabled = 1 [(validate.rules).message = {required: true}]; @@ -39,7 +39,7 @@ message CsrfPolicy { // // This is intended to be used when ``filter_enabled`` is off and will be ignored otherwise. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate // and track the request's *Origin* and *Destination* to determine if it's valid, but will not // enforce any policies. diff --git a/generated_api_shadow/envoy/extensions/filters/http/csrf/v4alpha/csrf.proto b/generated_api_shadow/envoy/extensions/filters/http/csrf/v4alpha/csrf.proto index dda915a059af5..3de55da6be8cf 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/csrf/v4alpha/csrf.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/csrf/v4alpha/csrf.proto @@ -25,13 +25,13 @@ message CsrfPolicy { // Specifies the % of requests for which the CSRF filter is enabled. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests to filter. // // .. note:: // // This field defaults to 100/:ref:`HUNDRED - // `. + // `. config.core.v4alpha.RuntimeFractionalPercent filter_enabled = 1 [(validate.rules).message = {required: true}]; @@ -39,7 +39,7 @@ message CsrfPolicy { // // This is intended to be used when ``filter_enabled`` is off and will be ignored otherwise. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate // and track the request's *Origin* and *Destination* to determine if it's valid, but will not // enforce any policies. diff --git a/generated_api_shadow/envoy/extensions/filters/http/decompressor/v3/decompressor.proto b/generated_api_shadow/envoy/extensions/filters/http/decompressor/v3/decompressor.proto index 54d5f23bc449e..c4cca44020f6d 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/decompressor/v3/decompressor.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/decompressor/v3/decompressor.proto @@ -41,7 +41,7 @@ message Decompressor { } // A decompressor library to use for both request and response decompression. Currently only - // :ref:`envoy.compression.gzip.compressor` + // :ref:`envoy.compression.gzip.compressor` // is included in Envoy. // [#extension-category: envoy.compression.decompressor] config.core.v3.TypedExtensionConfig decompressor_library = 1 diff --git a/generated_api_shadow/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto b/generated_api_shadow/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto index 70dd21a324b37..a5d7223b98d28 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto @@ -24,7 +24,7 @@ message FilterConfig { // The DNS cache configuration that the filter will attach to. Note this configuration must // match that of associated :ref:`dynamic forward proxy cluster configuration - // `. + // `. common.dynamic_forward_proxy.v3.DnsCacheConfig dns_cache_config = 1 [(validate.rules).message = {required: true}]; } @@ -41,7 +41,7 @@ message PerRouteConfig { // // Note: this rewrite affects both DNS lookup and host header forwarding. However, this // option shouldn't be used with - // :ref:`HCM host rewrite ` given that the + // :ref:`HCM host rewrite ` given that the // value set here would be used for DNS lookups whereas the value set in the HCM would be used // for host header forwarding which is not the desired outcome. string host_rewrite_literal = 1; @@ -52,7 +52,7 @@ message PerRouteConfig { // // Note: this rewrite affects both DNS lookup and host header forwarding. However, this // option shouldn't be used with - // :ref:`HCM host rewrite header ` + // :ref:`HCM host rewrite header ` // given that the value set here would be used for DNS lookups whereas the value set in the HCM // would be used for host header forwarding which is not the desired outcome. // diff --git a/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto b/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto index 5eec89b6c9d35..ee9be861fc073 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto @@ -82,7 +82,7 @@ message ExtAuthz { // ext_authz service as an opaque *protobuf::Struct*. // // For example, if the *jwt_authn* filter is used and :ref:`payload_in_metadata - // ` is set, + // ` is set, // then the following will pass the jwt payload to the authorization server. // // .. code-block:: yaml @@ -94,7 +94,7 @@ message ExtAuthz { // Specifies if the filter is enabled. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests to filter. // // If this field is not specified, the filter will be enabled for all requests. @@ -105,7 +105,7 @@ message ExtAuthz { type.matcher.v3.MetadataMatcher filter_enabled_metadata = 14; // Specifies whether to deny the requests, when the filter is disabled. - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to determine whether to deny request for // filter protected path at filter disabling. If filter is disabled in // typed_per_filter_config for the path, requests will not be denied. @@ -116,7 +116,7 @@ message ExtAuthz { // Specifies if the peer certificate is sent to the external service. // // When this field is true, Envoy will include the peer X.509 certificate, if available, in the - // :ref:`certificate`. + // :ref:`certificate`. bool include_peer_certificate = 10; // Optional additional prefix to use when emitting statistics. This allows to distinguish @@ -151,7 +151,7 @@ message BufferSettings { // Sets the maximum size of a message body that the filter will hold in memory. Envoy will return // *HTTP 413* and will *not* initiate the authorization process when buffer reaches the number // set in this field. Note that this setting will have precedence over :ref:`failure_mode_allow - // `. + // `. uint32 max_request_bytes = 1 [(validate.rules).uint32 = {gt: 0}]; // When this field is true, Envoy will buffer the message until *max_request_bytes* is reached. @@ -175,24 +175,24 @@ message BufferSettings { // // *On authorization request*, a list of allowed request headers may be supplied. See // :ref:`allowed_headers -// ` +// ` // for details. Additional headers metadata may be added to the authorization request. See // :ref:`headers_to_add -// ` for +// ` for // details. // // On authorization response status HTTP 200 OK, the filter will allow traffic to the upstream and // additional headers metadata may be added to the original client request. See // :ref:`allowed_upstream_headers -// ` +// ` // for details. Additionally, the filter may add additional headers to the client's response. See // :ref:`allowed_client_headers_on_success -// ` +// ` // for details. // // On other authorization response statuses, the filter will not allow traffic. Additional headers // metadata as well as body may be added to the client's response. See :ref:`allowed_client_headers -// ` +// ` // for details. // [#next-free-field: 9] message HttpService { @@ -219,7 +219,7 @@ message AuthorizationRequest { "envoy.config.filter.http.ext_authz.v2.AuthorizationRequest"; // Authorization request will include the client request headers that have a correspondent match - // in the :ref:`list `. Note that in addition to the + // in the :ref:`list `. Note that in addition to the // user's supplied matchers: // // 1. *Host*, *Method*, *Path* and *Content-Length* are automatically included to the list. @@ -227,7 +227,7 @@ message AuthorizationRequest { // 2. *Content-Length* will be set to 0 and the request to the authorization service will not have // a message body. However, the authorization request can include the buffered client request body // (controlled by :ref:`with_request_body - // ` setting), + // ` setting), // consequently the value of *Content-Length* of the authorization request reflects the size of // its payload size. // @@ -242,24 +242,24 @@ message AuthorizationResponse { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.ext_authz.v2.AuthorizationResponse"; - // When this :ref:`list ` is set, authorization + // When this :ref:`list ` is set, authorization // response headers that have a correspondent match will be added to the original client request. // Note that coexistent headers will be overridden. type.matcher.v3.ListStringMatcher allowed_upstream_headers = 1; - // When this :ref:`list ` is set, authorization + // When this :ref:`list ` is set, authorization // response headers that have a correspondent match will be added to the client's response. Note // that coexistent headers will be appended. type.matcher.v3.ListStringMatcher allowed_upstream_headers_to_append = 3; - // When this :ref:`list `. is set, authorization + // When this :ref:`list `. is set, authorization // response headers that have a correspondent match will be added to the client's response. Note // that when this list is *not* set, all the authorization response headers, except *Authority // (Host)* will be in the response to the client. When a header is included in this list, *Path*, // *Status*, *Content-Length*, *WWWAuthenticate* and *Location* are automatically added. type.matcher.v3.ListStringMatcher allowed_client_headers = 2; - // When this :ref:`list `. is set, authorization + // When this :ref:`list `. is set, authorization // response headers that have a correspondent match will be added to the client's response when // the authorization response itself is successful, i.e. not failed or denied. When this list is // *not* set, no additional headers will be added to the client's response on success. @@ -289,7 +289,7 @@ message CheckSettings { "envoy.config.filter.http.ext_authz.v2.CheckSettings"; // Context extensions to set on the CheckRequest's - // :ref:`AttributeContext.context_extensions` + // :ref:`AttributeContext.context_extensions` // // You can use this to provide extra context for the external authorization server on specific // virtual hosts/routes. For example, adding a context extension on the virtual host level can @@ -302,10 +302,10 @@ message CheckSettings { // .. note:: // // These settings are only applied to a filter configured with a - // :ref:`grpc_service`. + // :ref:`grpc_service`. map context_extensions = 1; // When set to true, disable the configured :ref:`with_request_body - // ` for a route. + // ` for a route. bool disable_request_body_buffering = 2; } diff --git a/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto b/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto index 014c8263e61c3..90f003b0a137c 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/ext_authz/v4alpha/ext_authz.proto @@ -85,7 +85,7 @@ message ExtAuthz { // ext_authz service as an opaque *protobuf::Struct*. // // For example, if the *jwt_authn* filter is used and :ref:`payload_in_metadata - // ` is set, + // ` is set, // then the following will pass the jwt payload to the authorization server. // // .. code-block:: yaml @@ -97,7 +97,7 @@ message ExtAuthz { // Specifies if the filter is enabled. // - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to get the percentage of requests to filter. // // If this field is not specified, the filter will be enabled for all requests. @@ -108,7 +108,7 @@ message ExtAuthz { type.matcher.v4alpha.MetadataMatcher filter_enabled_metadata = 14; // Specifies whether to deny the requests, when the filter is disabled. - // If :ref:`runtime_key ` is specified, + // If :ref:`runtime_key ` is specified, // Envoy will lookup the runtime key to determine whether to deny request for // filter protected path at filter disabling. If filter is disabled in // typed_per_filter_config for the path, requests will not be denied. @@ -119,7 +119,7 @@ message ExtAuthz { // Specifies if the peer certificate is sent to the external service. // // When this field is true, Envoy will include the peer X.509 certificate, if available, in the - // :ref:`certificate`. + // :ref:`certificate`. bool include_peer_certificate = 10; // Optional additional prefix to use when emitting statistics. This allows to distinguish @@ -148,7 +148,7 @@ message BufferSettings { // Sets the maximum size of a message body that the filter will hold in memory. Envoy will return // *HTTP 413* and will *not* initiate the authorization process when buffer reaches the number // set in this field. Note that this setting will have precedence over :ref:`failure_mode_allow - // `. + // `. uint32 max_request_bytes = 1 [(validate.rules).uint32 = {gt: 0}]; // When this field is true, Envoy will buffer the message until *max_request_bytes* is reached. @@ -172,24 +172,24 @@ message BufferSettings { // // *On authorization request*, a list of allowed request headers may be supplied. See // :ref:`allowed_headers -// ` +// ` // for details. Additional headers metadata may be added to the authorization request. See // :ref:`headers_to_add -// ` for +// ` for // details. // // On authorization response status HTTP 200 OK, the filter will allow traffic to the upstream and // additional headers metadata may be added to the original client request. See // :ref:`allowed_upstream_headers -// ` +// ` // for details. Additionally, the filter may add additional headers to the client's response. See // :ref:`allowed_client_headers_on_success -// ` +// ` // for details. // // On other authorization response statuses, the filter will not allow traffic. Additional headers // metadata as well as body may be added to the client's response. See :ref:`allowed_client_headers -// ` +// ` // for details. // [#next-free-field: 9] message HttpService { @@ -216,7 +216,7 @@ message AuthorizationRequest { "envoy.extensions.filters.http.ext_authz.v3.AuthorizationRequest"; // Authorization request will include the client request headers that have a correspondent match - // in the :ref:`list `. Note that in addition to the + // in the :ref:`list `. Note that in addition to the // user's supplied matchers: // // 1. *Host*, *Method*, *Path* and *Content-Length* are automatically included to the list. @@ -224,7 +224,7 @@ message AuthorizationRequest { // 2. *Content-Length* will be set to 0 and the request to the authorization service will not have // a message body. However, the authorization request can include the buffered client request body // (controlled by :ref:`with_request_body - // ` setting), + // ` setting), // consequently the value of *Content-Length* of the authorization request reflects the size of // its payload size. // @@ -239,24 +239,24 @@ message AuthorizationResponse { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.ext_authz.v3.AuthorizationResponse"; - // When this :ref:`list ` is set, authorization + // When this :ref:`list ` is set, authorization // response headers that have a correspondent match will be added to the original client request. // Note that coexistent headers will be overridden. type.matcher.v4alpha.ListStringMatcher allowed_upstream_headers = 1; - // When this :ref:`list ` is set, authorization + // When this :ref:`list ` is set, authorization // response headers that have a correspondent match will be added to the client's response. Note // that coexistent headers will be appended. type.matcher.v4alpha.ListStringMatcher allowed_upstream_headers_to_append = 3; - // When this :ref:`list `. is set, authorization + // When this :ref:`list `. is set, authorization // response headers that have a correspondent match will be added to the client's response. Note // that when this list is *not* set, all the authorization response headers, except *Authority // (Host)* will be in the response to the client. When a header is included in this list, *Path*, // *Status*, *Content-Length*, *WWWAuthenticate* and *Location* are automatically added. type.matcher.v4alpha.ListStringMatcher allowed_client_headers = 2; - // When this :ref:`list `. is set, authorization + // When this :ref:`list `. is set, authorization // response headers that have a correspondent match will be added to the client's response when // the authorization response itself is successful, i.e. not failed or denied. When this list is // *not* set, no additional headers will be added to the client's response on success. @@ -286,7 +286,7 @@ message CheckSettings { "envoy.extensions.filters.http.ext_authz.v3.CheckSettings"; // Context extensions to set on the CheckRequest's - // :ref:`AttributeContext.context_extensions` + // :ref:`AttributeContext.context_extensions` // // You can use this to provide extra context for the external authorization server on specific // virtual hosts/routes. For example, adding a context extension on the virtual host level can @@ -299,10 +299,10 @@ message CheckSettings { // .. note:: // // These settings are only applied to a filter configured with a - // :ref:`grpc_service`. + // :ref:`grpc_service`. map context_extensions = 1; // When set to true, disable the configured :ref:`with_request_body - // ` for a route. + // ` for a route. bool disable_request_body_buffering = 2; } diff --git a/generated_api_shadow/envoy/extensions/filters/http/fault/v3/fault.proto b/generated_api_shadow/envoy/extensions/filters/http/fault/v3/fault.proto index fb3c51cca9d00..0c7fbb4480cfe 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/fault/v3/fault.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/fault/v3/fault.proto @@ -76,7 +76,7 @@ message HTTPFault { // injection filter can be applied selectively to requests that match a set of // headers specified in the fault filter config. The chances of actual fault // injection further depend on the value of the :ref:`percentage - // ` field. + // ` field. // The filter will check the request's headers against all the specified // headers in the filter config. A match will happen if all the headers in the // config are present in the request with the same values (or based on diff --git a/generated_api_shadow/envoy/extensions/filters/http/fault/v4alpha/fault.proto b/generated_api_shadow/envoy/extensions/filters/http/fault/v4alpha/fault.proto index 5155007268456..da8b8b48ad3f5 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/fault/v4alpha/fault.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/fault/v4alpha/fault.proto @@ -76,7 +76,7 @@ message HTTPFault { // injection filter can be applied selectively to requests that match a set of // headers specified in the fault filter config. The chances of actual fault // injection further depend on the value of the :ref:`percentage - // ` field. + // ` field. // The filter will check the request's headers against all the specified // headers in the filter config. A match will happen if all the headers in the // config are present in the request with the same values (or based on diff --git a/generated_api_shadow/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto b/generated_api_shadow/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto index 5b233a476bf4a..a4feeff31f158 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto @@ -71,7 +71,7 @@ message GrpcJsonTranscoder { message RequestValidationOptions { // By default, a request that cannot be mapped to any specified gRPC - // :ref:`services ` + // :ref:`services ` // will pass-through this filter. // When set to true, the request will be rejected with a ``HTTP 404 Not Found``. bool reject_unknown_method = 1; @@ -81,9 +81,9 @@ message GrpcJsonTranscoder { // When set to true, the request will be rejected with a ``HTTP 400 Bad Request``. // // The fields - // :ref:`ignore_unknown_query_parameters ` + // :ref:`ignore_unknown_query_parameters ` // and - // :ref:`ignored_query_parameters ` + // :ref:`ignored_query_parameters ` // have priority over this strict validation behavior. bool reject_unknown_query_parameters = 2; } @@ -111,7 +111,7 @@ message GrpcJsonTranscoder { // By default, the filter will pass through requests that do not map to any specified services. // If the list of services is empty, filter is considered disabled. // However, this behavior changes if - // :ref:`reject_unknown_method ` + // :ref:`reject_unknown_method ` // is enabled. repeated string services = 2; @@ -214,7 +214,7 @@ message GrpcJsonTranscoder { // This spec is only applied when extracting variable with multiple segments. // For example, in case of `/foo/{x=*}/bar/{y=prefix/*}/{z=**}` `x` variable is single segment and `y` and `z` are multiple segments. // For a path with `/foo/first/bar/prefix/second/third/fourth`, `x=first`, `y=prefix/second`, `z=third/fourth`. - // If this setting is not specified, the value defaults to :ref:`ALL_CHARACTERS_EXCEPT_RESERVED`. + // If this setting is not specified, the value defaults to :ref:`ALL_CHARACTERS_EXCEPT_RESERVED`. UrlUnescapeSpec url_unescape_spec = 10 [(validate.rules).enum = {defined_only: true}]; // Configure the behavior when handling requests that cannot be transcoded. diff --git a/generated_api_shadow/envoy/extensions/filters/http/grpc_stats/v3/config.proto b/generated_api_shadow/envoy/extensions/filters/http/grpc_stats/v3/config.proto index 244bfb607235d..79ecb7a92b706 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/grpc_stats/v3/config.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/grpc_stats/v3/config.proto @@ -54,9 +54,9 @@ message FilterConfig { // If true, the filter will gather a histogram for the request time of the upstream. // It works with :ref:`stats_for_all_methods - // ` + // ` // and :ref:`individual_method_stats_allowlist - // ` the same way + // ` the same way // request_message_count and response_message_count works. bool enable_upstream_stats = 4; } diff --git a/generated_api_shadow/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto b/generated_api_shadow/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto index 59c61d281aa0b..7766ee2573d00 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto @@ -47,7 +47,7 @@ message LocalRateLimit { // // .. note:: // In the current implementation the token bucket's :ref:`fill_interval - // ` must be >= 50ms to avoid too aggressive + // ` must be >= 50ms to avoid too aggressive // refills. type.v3.TokenBucket token_bucket = 3; @@ -83,8 +83,8 @@ message LocalRateLimit { // .. note:: // // In the current implementation the descriptor's token bucket :ref:`fill_interval - // ` must be a multiple - // global :ref:`token bucket's` fill interval. + // ` must be a multiple + // global :ref:`token bucket's` fill interval. // // The descriptors must match verbatim for rate limiting to apply. There is no partial // match by a subset of descriptor entries in the current implementation. diff --git a/generated_api_shadow/envoy/extensions/filters/network/ext_authz/v3/ext_authz.proto b/generated_api_shadow/envoy/extensions/filters/network/ext_authz/v3/ext_authz.proto index 78f4167ccc337..673d46ea1d4bd 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/ext_authz/v3/ext_authz.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/ext_authz/v3/ext_authz.proto @@ -22,7 +22,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // External Authorization filter calls out to an external service over the // gRPC Authorization API defined by -// :ref:`CheckRequest `. +// :ref:`CheckRequest `. // A failed check will cause this filter to close the TCP connection. // [#next-free-field: 7] message ExtAuthz { @@ -45,7 +45,7 @@ message ExtAuthz { // Specifies if the peer certificate is sent to the external service. // // When this field is true, Envoy will include the peer X.509 certificate, if available, in the - // :ref:`certificate`. + // :ref:`certificate`. bool include_peer_certificate = 4; // API version for ext_authz transport protocol. This describes the ext_authz gRPC endpoint and diff --git a/generated_api_shadow/envoy/extensions/filters/network/ext_authz/v4alpha/ext_authz.proto b/generated_api_shadow/envoy/extensions/filters/network/ext_authz/v4alpha/ext_authz.proto index f877a3ed85027..c3c5aad9b639b 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/ext_authz/v4alpha/ext_authz.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/ext_authz/v4alpha/ext_authz.proto @@ -22,7 +22,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // External Authorization filter calls out to an external service over the // gRPC Authorization API defined by -// :ref:`CheckRequest `. +// :ref:`CheckRequest `. // A failed check will cause this filter to close the TCP connection. // [#next-free-field: 7] message ExtAuthz { @@ -45,7 +45,7 @@ message ExtAuthz { // Specifies if the peer certificate is sent to the external service. // // When this field is true, Envoy will include the peer X.509 certificate, if available, in the - // :ref:`certificate`. + // :ref:`certificate`. bool include_peer_certificate = 4; // API version for ext_authz transport protocol. This describes the ext_authz gRPC endpoint and diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index da461ab777d48..b700545585005 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -242,7 +242,7 @@ message HttpConnectionManager { // Determines if upgrades are enabled or disabled by default. Defaults to true. // This can be overridden on a per-route basis with :ref:`cluster - // ` as documented in the + // ` as documented in the // :ref:`upgrade documentation `. google.protobuf.BoolValue enabled = 3; } @@ -251,8 +251,8 @@ message HttpConnectionManager { // before any processing of requests by HTTP filters, routing, and matching. Only the normalized // path will be visible internally if a transformation is enabled. Any path rewrites that the // router performs (e.g. :ref:`regex_rewrite - // ` or :ref:`prefix_rewrite - // `) will apply to the *:path* header + // ` or :ref:`prefix_rewrite + // `) will apply to the *:path* header // destined for the upstream. // // Note: access logging and tracing will show the original *:path* header. @@ -260,7 +260,7 @@ message HttpConnectionManager { // [#not-implemented-hide:] Normalization applies internally before any processing of requests by // HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults // to :ref:`NormalizePathRFC3986 - // `. When not + // `. When not // specified, this value may be overridden by the runtime variable // :ref:`http_connection_manager.normalize_path`. // Envoy will respond with 400 to paths that are malformed (e.g. for paths that fail RFC 3986 @@ -316,7 +316,7 @@ message HttpConnectionManager { // Presence of the object defines whether the connection manager // emits :ref:`tracing ` data to the :ref:`configured tracing provider - // `. + // `. Tracing tracing = 7; // Additional settings for HTTP requests handled by the connection manager. These will be @@ -360,10 +360,10 @@ message HttpConnectionManager { // // This idle timeout applies to new streams and is overridable by the // :ref:`route-level idle_timeout - // `. Even on a stream in + // `. Even on a stream in // which the override applies, prior to receipt of the initial request // headers, the :ref:`stream_idle_timeout - // ` + // ` // applies. Each time an encode/decode event for headers or data is processed // for the stream, the timer will be reset. If the timeout fires, the stream // is terminated with a 408 Request Timeout error code if no upstream response @@ -376,12 +376,12 @@ message HttpConnectionManager { // data has been proxied within available flow control windows. If the timeout is hit in this // case, the :ref:`tx_flush_timeout ` counter will be // incremented. Note that :ref:`max_stream_duration - // ` does not apply to + // ` does not apply to // this corner case. // // If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" // is configured, this timeout is scaled according to the value for - // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. // // Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due // to the granularity of events presented to the connection manager. For example, while receiving @@ -483,7 +483,7 @@ message HttpConnectionManager { // :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. This may be used in // conjunction with HTTP filters that explicitly manipulate XFF after the HTTP connection manager // has mutated the request headers. While :ref:`use_remote_address - // ` + // ` // will also suppress XFF addition, it has consequences for logging and other // Envoy uses of the remote address, so *skip_xff_append* should be used // when only an elision of XFF addition is intended. @@ -516,7 +516,7 @@ message HttpConnectionManager { [(validate.rules).enum = {defined_only: true}]; // This field is valid only when :ref:`forward_client_cert_details - // ` + // ` // is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in // the client certificate to be forwarded. Note that in the // :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and @@ -532,7 +532,7 @@ message HttpConnectionManager { // If // :ref:`use_remote_address - // ` + // ` // is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is // an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. // This is useful for testing compatibility of upstream services that parse the header value. For @@ -591,12 +591,12 @@ message HttpConnectionManager { LocalReplyConfig local_reply_config = 38; // Determines if the port part should be removed from host/authority header before any processing - // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` + // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` // local port. This affects the upstream host header unless the method is // CONNECT in which case if no filter adds a port the original port will be restored before headers are // sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against - // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. // Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. bool strip_matching_host_port = 39 @@ -608,7 +608,7 @@ message HttpConnectionManager { // This affects the upstream host header unless the method is CONNECT in // which case if no filter adds a port the original port will be restored before headers are sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against - // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. // Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. bool strip_any_host_port = 42; @@ -638,7 +638,7 @@ message HttpConnectionManager { // whether transformations affect the forwarded *:path* header. RFC 3986 path // normalization is enabled by default and the default policy is that the // normalized header will be forwarded. See :ref:`PathNormalizationOptions - // ` + // ` // for details. PathNormalizationOptions path_normalization_options = 43; @@ -748,14 +748,14 @@ message ScopedRoutes { "envoy.config.filter.network.http_connection_manager.v2.ScopedRoutes"; // Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These - // keys are matched against a set of :ref:`Key` - // objects assembled from :ref:`ScopedRouteConfiguration` + // keys are matched against a set of :ref:`Key` + // objects assembled from :ref:`ScopedRouteConfiguration` // messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via - // :ref:`scoped_route_configurations_list`. + // :ref:`scoped_route_configurations_list`. // // Upon receiving a request's headers, the Router will build a key using the algorithm specified // by this message. This key will be used to look up the routing table (i.e., the - // :ref:`RouteConfiguration`) to use for the request. + // :ref:`RouteConfiguration`) to use for the request. message ScopeKeyBuilder { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.ScopedRoutes.ScopeKeyBuilder"; @@ -838,7 +838,7 @@ message ScopedRoutes { } // The final(built) scope key consists of the ordered union of these fragments, which are compared in order with the - // fragments of a :ref:`ScopedRouteConfiguration`. + // fragments of a :ref:`ScopedRouteConfiguration`. // A missing fragment during comparison will make the key invalid, i.e., the computed key doesn't match any key. repeated FragmentBuilder fragments = 1 [(validate.rules).repeated = {min_items: 1}]; } @@ -860,14 +860,14 @@ message ScopedRoutes { // The set of routing scopes corresponding to the HCM. A scope is assigned to a request by // matching a key constructed from the request's attributes according to the algorithm specified // by the - // :ref:`ScopeKeyBuilder` + // :ref:`ScopeKeyBuilder` // in this message. ScopedRouteConfigurationsList scoped_route_configurations_list = 4; // The set of routing scopes associated with the HCM will be dynamically loaded via the SRDS // API. A scope is assigned to a request by matching a key constructed from the request's // attributes according to the algorithm specified by the - // :ref:`ScopeKeyBuilder` + // :ref:`ScopeKeyBuilder` // in this message. ScopedRds scoped_rds = 5; } @@ -903,7 +903,7 @@ message HttpFilter { // filters for further documentation. // // To support configuring a :ref:`match tree `, use an - // :ref:`ExtensionWithMatcher ` + // :ref:`ExtensionWithMatcher ` // with the desired HTTP filter. // [#extension-category: envoy.filters.http] google.protobuf.Any typed_config = 4; @@ -913,7 +913,7 @@ message HttpFilter { // Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). // // To support configuring a :ref:`match tree `, use an - // :ref:`ExtensionWithMatcher ` + // :ref:`ExtensionWithMatcher ` // with the desired HTTP filter. This works for both the default filter configuration as well // as for filters provided via the API. config.core.v3.ExtensionConfigSource config_discovery = 5; diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index ba9b3c461b58b..a30e21de96ba1 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -233,7 +233,7 @@ message HttpConnectionManager { // Determines if upgrades are enabled or disabled by default. Defaults to true. // This can be overridden on a per-route basis with :ref:`cluster - // ` as documented in the + // ` as documented in the // :ref:`upgrade documentation `. google.protobuf.BoolValue enabled = 3; } @@ -242,8 +242,8 @@ message HttpConnectionManager { // before any processing of requests by HTTP filters, routing, and matching. Only the normalized // path will be visible internally if a transformation is enabled. Any path rewrites that the // router performs (e.g. :ref:`regex_rewrite - // ` or :ref:`prefix_rewrite - // `) will apply to the *:path* header + // ` or :ref:`prefix_rewrite + // `) will apply to the *:path* header // destined for the upstream. // // Note: access logging and tracing will show the original *:path* header. @@ -255,7 +255,7 @@ message HttpConnectionManager { // [#not-implemented-hide:] Normalization applies internally before any processing of requests by // HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults // to :ref:`NormalizePathRFC3986 - // `. When not + // `. When not // specified, this value may be overridden by the runtime variable // :ref:`http_connection_manager.normalize_path`. // Envoy will respond with 400 to paths that are malformed (e.g. for paths that fail RFC 3986 @@ -313,7 +313,7 @@ message HttpConnectionManager { // Presence of the object defines whether the connection manager // emits :ref:`tracing ` data to the :ref:`configured tracing provider - // `. + // `. Tracing tracing = 7; // Additional settings for HTTP requests handled by the connection manager. These will be @@ -357,10 +357,10 @@ message HttpConnectionManager { // // This idle timeout applies to new streams and is overridable by the // :ref:`route-level idle_timeout - // `. Even on a stream in + // `. Even on a stream in // which the override applies, prior to receipt of the initial request // headers, the :ref:`stream_idle_timeout - // ` + // ` // applies. Each time an encode/decode event for headers or data is processed // for the stream, the timer will be reset. If the timeout fires, the stream // is terminated with a 408 Request Timeout error code if no upstream response @@ -373,12 +373,12 @@ message HttpConnectionManager { // data has been proxied within available flow control windows. If the timeout is hit in this // case, the :ref:`tx_flush_timeout ` counter will be // incremented. Note that :ref:`max_stream_duration - // ` does not apply to + // ` does not apply to // this corner case. // // If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" // is configured, this timeout is scaled according to the value for - // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + // :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. // // Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due // to the granularity of events presented to the connection manager. For example, while receiving @@ -480,7 +480,7 @@ message HttpConnectionManager { // :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. This may be used in // conjunction with HTTP filters that explicitly manipulate XFF after the HTTP connection manager // has mutated the request headers. While :ref:`use_remote_address - // ` + // ` // will also suppress XFF addition, it has consequences for logging and other // Envoy uses of the remote address, so *skip_xff_append* should be used // when only an elision of XFF addition is intended. @@ -513,7 +513,7 @@ message HttpConnectionManager { [(validate.rules).enum = {defined_only: true}]; // This field is valid only when :ref:`forward_client_cert_details - // ` + // ` // is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in // the client certificate to be forwarded. Note that in the // :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and @@ -529,7 +529,7 @@ message HttpConnectionManager { // If // :ref:`use_remote_address - // ` + // ` // is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is // an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. // This is useful for testing compatibility of upstream services that parse the header value. For @@ -589,12 +589,12 @@ message HttpConnectionManager { oneof strip_port_mode { // Determines if the port part should be removed from host/authority header before any processing - // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` + // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` // local port. This affects the upstream host header unless the method is // CONNECT in which case if no filter adds a port the original port will be restored before headers are // sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against - // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. // Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. bool strip_matching_host_port = 39; @@ -604,7 +604,7 @@ message HttpConnectionManager { // This affects the upstream host header unless the method is CONNECT in // which case if no filter adds a port the original port will be restored before headers are sent upstream. // Without setting this option, incoming requests with host `example:443` will not match against - // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part // of `HTTP spec `_ and is provided for convenience. // Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. bool strip_any_host_port = 42; @@ -634,7 +634,7 @@ message HttpConnectionManager { // whether transformations affect the forwarded *:path* header. RFC 3986 path // normalization is enabled by default and the default policy is that the // normalized header will be forwarded. See :ref:`PathNormalizationOptions - // ` + // ` // for details. PathNormalizationOptions path_normalization_options = 43; } @@ -744,14 +744,14 @@ message ScopedRoutes { "envoy.extensions.filters.network.http_connection_manager.v3.ScopedRoutes"; // Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These - // keys are matched against a set of :ref:`Key` - // objects assembled from :ref:`ScopedRouteConfiguration` + // keys are matched against a set of :ref:`Key` + // objects assembled from :ref:`ScopedRouteConfiguration` // messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via - // :ref:`scoped_route_configurations_list`. + // :ref:`scoped_route_configurations_list`. // // Upon receiving a request's headers, the Router will build a key using the algorithm specified // by this message. This key will be used to look up the routing table (i.e., the - // :ref:`RouteConfiguration`) to use for the request. + // :ref:`RouteConfiguration`) to use for the request. message ScopeKeyBuilder { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.network.http_connection_manager.v3.ScopedRoutes.ScopeKeyBuilder"; @@ -834,7 +834,7 @@ message ScopedRoutes { } // The final(built) scope key consists of the ordered union of these fragments, which are compared in order with the - // fragments of a :ref:`ScopedRouteConfiguration`. + // fragments of a :ref:`ScopedRouteConfiguration`. // A missing fragment during comparison will make the key invalid, i.e., the computed key doesn't match any key. repeated FragmentBuilder fragments = 1 [(validate.rules).repeated = {min_items: 1}]; } @@ -857,14 +857,14 @@ message ScopedRoutes { // The set of routing scopes corresponding to the HCM. A scope is assigned to a request by // matching a key constructed from the request's attributes according to the algorithm specified // by the - // :ref:`ScopeKeyBuilder` + // :ref:`ScopeKeyBuilder` // in this message. ScopedRouteConfigurationsList scoped_route_configurations_list = 4; // The set of routing scopes associated with the HCM will be dynamically loaded via the SRDS // API. A scope is assigned to a request by matching a key constructed from the request's // attributes according to the algorithm specified by the - // :ref:`ScopeKeyBuilder` + // :ref:`ScopeKeyBuilder` // in this message. ScopedRds scoped_rds = 5; } @@ -902,7 +902,7 @@ message HttpFilter { // filters for further documentation. // // To support configuring a :ref:`match tree `, use an - // :ref:`ExtensionWithMatcher ` + // :ref:`ExtensionWithMatcher ` // with the desired HTTP filter. // [#extension-category: envoy.filters.http] google.protobuf.Any typed_config = 4; @@ -912,7 +912,7 @@ message HttpFilter { // Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). // // To support configuring a :ref:`match tree `, use an - // :ref:`ExtensionWithMatcher ` + // :ref:`ExtensionWithMatcher ` // with the desired HTTP filter. This works for both the default filter configuration as well // as for filters provided via the API. config.core.v4alpha.ExtensionConfigSource config_discovery = 5; diff --git a/generated_api_shadow/envoy/extensions/filters/network/local_ratelimit/v3/local_rate_limit.proto b/generated_api_shadow/envoy/extensions/filters/network/local_ratelimit/v3/local_rate_limit.proto index 37eb8c62d0e29..3ee3655b7c3c9 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/local_ratelimit/v3/local_rate_limit.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/local_ratelimit/v3/local_rate_limit.proto @@ -36,7 +36,7 @@ message LocalRateLimit { // // .. note:: // In the current implementation the token bucket's :ref:`fill_interval - // ` must be >= 50ms to avoid too aggressive + // ` must be >= 50ms to avoid too aggressive // refills. type.v3.TokenBucket token_bucket = 2 [(validate.rules).message = {required: true}]; diff --git a/generated_api_shadow/envoy/extensions/filters/network/postgres_proxy/v3alpha/postgres_proxy.proto b/generated_api_shadow/envoy/extensions/filters/network/postgres_proxy/v3alpha/postgres_proxy.proto index 6efe3e0ccf88a..8fe98f269626d 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/postgres_proxy/v3alpha/postgres_proxy.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/postgres_proxy/v3alpha/postgres_proxy.proto @@ -33,7 +33,7 @@ message PostgresProxy { // terminate SSL session, but will pass all the packets to the upstream server. // If the value is true, the Postgres proxy filter will try to terminate SSL // session. In order to do that, the filter chain must use :ref:`starttls transport socket - // `. + // `. // If the filter does not manage to terminate the SSL session, it will close the connection from the client. // Refer to official documentation for details // `SSL Session Encryption Message Flow `_. diff --git a/generated_api_shadow/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto b/generated_api_shadow/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto index 271084101a344..2df7c3e3f6104 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto @@ -241,7 +241,7 @@ message RedisProxy { // * ``get abc:users`` would retrieve the key 'abc:users' from cluster_b. // * ``get ab:users`` would retrieve the key 'ab:users' from cluster_a. // * ``get z:users`` would return a NoUpstreamHost error. A :ref:`catch-all - // route` + // route` // would have retrieved the key from that cluster instead. // // See the :ref:`configuration section @@ -308,7 +308,7 @@ message RedisProxy { } // RedisProtocolOptions specifies Redis upstream protocol options. This object is used in -// :ref:`typed_extension_protocol_options`, +// :ref:`typed_extension_protocol_options`, // keyed by the name `envoy.filters.network.redis_proxy`. message RedisProtocolOptions { option (udpa.annotations.versioning).previous_message_type = diff --git a/generated_api_shadow/envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3alpha/sni_dynamic_forward_proxy.proto b/generated_api_shadow/envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3alpha/sni_dynamic_forward_proxy.proto index 23b07f8a218d5..7f7eb57d5be64 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3alpha/sni_dynamic_forward_proxy.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3alpha/sni_dynamic_forward_proxy.proto @@ -25,7 +25,7 @@ message FilterConfig { // The DNS cache configuration that the filter will attach to. Note this // configuration must match that of associated :ref:`dynamic forward proxy // cluster configuration - // `. + // `. common.dynamic_forward_proxy.v3.DnsCacheConfig dns_cache_config = 1 [(validate.rules).message = {required: true}]; diff --git a/generated_api_shadow/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto b/generated_api_shadow/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto index da99bd0503870..f00298a3edd4e 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto @@ -52,7 +52,7 @@ message TcpProxy { // in the upstream cluster with metadata matching what is set in this field will be considered // for load balancing. Note that this will be merged with what's provided in // :ref:`TcpProxy.metadata_match - // `, with values + // `, with values // here taking precedence. The filter name should be specified as *envoy.lb*. config.core.v3.Metadata metadata_match = 3; } diff --git a/generated_api_shadow/envoy/extensions/filters/network/tcp_proxy/v4alpha/tcp_proxy.proto b/generated_api_shadow/envoy/extensions/filters/network/tcp_proxy/v4alpha/tcp_proxy.proto index 4d4b6e83bac52..95f2c26c888ca 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/tcp_proxy/v4alpha/tcp_proxy.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/tcp_proxy/v4alpha/tcp_proxy.proto @@ -50,7 +50,7 @@ message TcpProxy { // in the upstream cluster with metadata matching what is set in this field will be considered // for load balancing. Note that this will be merged with what's provided in // :ref:`TcpProxy.metadata_match - // `, with values + // `, with values // here taking precedence. The filter name should be specified as *envoy.lb*. config.core.v4alpha.Metadata metadata_match = 3; } diff --git a/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v3/rate_limit.proto b/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v3/rate_limit.proto index c93b4d1e8e5af..8583bbe4b468c 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v3/rate_limit.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v3/rate_limit.proto @@ -29,7 +29,7 @@ message RateLimit { // Specifies the rate limit configuration stage. Each configured rate limit filter performs a // rate limit check using descriptors configured in the - // :ref:`envoy_api_msg_extensions.filters.network.thrift_proxy.v3.RouteAction` for the request. + // :ref:`envoy_v3_api_msg_extensions.filters.network.thrift_proxy.v3.RouteAction` for the request. // Only those entries with a matching stage number are used for a given filter. If not set, the // default stage number is 0. // diff --git a/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v4alpha/rate_limit.proto b/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v4alpha/rate_limit.proto index ed49380f83181..ed2a33290268e 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v4alpha/rate_limit.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v4alpha/rate_limit.proto @@ -29,7 +29,7 @@ message RateLimit { // Specifies the rate limit configuration stage. Each configured rate limit filter performs a // rate limit check using descriptors configured in the - // :ref:`envoy_api_msg_extensions.filters.network.thrift_proxy.v4alpha.RouteAction` for the request. + // :ref:`envoy_v3_api_msg_extensions.filters.network.thrift_proxy.v3.RouteAction` for the request. // Only those entries with a matching stage number are used for a given filter. If not set, the // default stage number is 0. // diff --git a/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v3/route.proto b/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v3/route.proto index f00b0e6983d19..cf4c06ae1f19e 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v3/route.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v3/route.proto @@ -60,17 +60,17 @@ message RouteMatch { } // Inverts whatever matching is done in the :ref:`method_name - // ` or + // ` or // :ref:`service_name - // ` fields. + // ` fields. // Cannot be combined with wildcard matching as that would result in routes never being matched. // // .. note:: // // This does not invert matching done as part of the :ref:`headers field - // ` field. To + // ` field. To // invert header matching, see :ref:`invert_match - // `. + // `. bool invert = 3; // Specifies a set of headers that the route should match on. The router will check the request’s @@ -110,7 +110,7 @@ message RouteAction { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in // the upstream cluster with metadata matching what is set in this field will be considered. // Note that this will be merged with what's provided in :ref:`WeightedCluster.metadata_match - // `, + // `, // with values there taking precedence. Keys and values should be provided under the "envoy.lb" // metadata key. config.core.v3.Metadata metadata_match = 3; @@ -147,7 +147,7 @@ message WeightedCluster { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in // the upstream cluster with metadata matching what is set in this field, combined with what's // provided in :ref:`RouteAction's metadata_match - // `, + // `, // will be considered. Values here will take precedence. Keys and values should be provided // under the "envoy.lb" metadata key. config.core.v3.Metadata metadata_match = 3; diff --git a/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v3/thrift_proxy.proto b/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v3/thrift_proxy.proto index a1e9f7fd64650..a03251a2ee3b0 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v3/thrift_proxy.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v3/thrift_proxy.proto @@ -66,11 +66,11 @@ message ThriftProxy { "envoy.config.filter.network.thrift_proxy.v2alpha1.ThriftProxy"; // Supplies the type of transport that the Thrift proxy should use. Defaults to - // :ref:`AUTO_TRANSPORT`. + // :ref:`AUTO_TRANSPORT`. TransportType transport = 2 [(validate.rules).enum = {defined_only: true}]; // Supplies the type of protocol that the Thrift proxy should use. Defaults to - // :ref:`AUTO_PROTOCOL`. + // :ref:`AUTO_PROTOCOL`. ProtocolType protocol = 3 [(validate.rules).enum = {defined_only: true}]; // The human readable prefix to use when emitting statistics. @@ -121,7 +121,7 @@ message ThriftFilter { // ThriftProtocolOptions specifies Thrift upstream protocol options. This object is used in // in -// :ref:`typed_extension_protocol_options`, +// :ref:`typed_extension_protocol_options`, // keyed by the name `envoy.filters.network.thrift_proxy`. message ThriftProtocolOptions { option (udpa.annotations.versioning).previous_message_type = @@ -129,13 +129,13 @@ message ThriftProtocolOptions { // Supplies the type of transport that the Thrift proxy should use for upstream connections. // Selecting - // :ref:`AUTO_TRANSPORT`, + // :ref:`AUTO_TRANSPORT`, // which is the default, causes the proxy to use the same transport as the downstream connection. TransportType transport = 1 [(validate.rules).enum = {defined_only: true}]; // Supplies the type of protocol that the Thrift proxy should use for upstream connections. // Selecting - // :ref:`AUTO_PROTOCOL`, + // :ref:`AUTO_PROTOCOL`, // which is the default, causes the proxy to use the same protocol as the downstream connection. ProtocolType protocol = 2 [(validate.rules).enum = {defined_only: true}]; } diff --git a/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v4alpha/route.proto b/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v4alpha/route.proto index b73a78c4f2cc9..e638e9b8a2be8 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v4alpha/route.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v4alpha/route.proto @@ -60,17 +60,17 @@ message RouteMatch { } // Inverts whatever matching is done in the :ref:`method_name - // ` or + // ` or // :ref:`service_name - // ` fields. + // ` fields. // Cannot be combined with wildcard matching as that would result in routes never being matched. // // .. note:: // // This does not invert matching done as part of the :ref:`headers field - // ` field. To + // ` field. To // invert header matching, see :ref:`invert_match - // `. + // `. bool invert = 3; // Specifies a set of headers that the route should match on. The router will check the request’s @@ -110,7 +110,7 @@ message RouteAction { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in // the upstream cluster with metadata matching what is set in this field will be considered. // Note that this will be merged with what's provided in :ref:`WeightedCluster.metadata_match - // `, + // `, // with values there taking precedence. Keys and values should be provided under the "envoy.lb" // metadata key. config.core.v4alpha.Metadata metadata_match = 3; @@ -147,7 +147,7 @@ message WeightedCluster { // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in // the upstream cluster with metadata matching what is set in this field, combined with what's // provided in :ref:`RouteAction's metadata_match - // `, + // `, // will be considered. Values here will take precedence. Keys and values should be provided // under the "envoy.lb" metadata key. config.core.v4alpha.Metadata metadata_match = 3; diff --git a/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v4alpha/thrift_proxy.proto b/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v4alpha/thrift_proxy.proto index 4ae4e26e4dbfe..de399582869a0 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v4alpha/thrift_proxy.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/thrift_proxy/v4alpha/thrift_proxy.proto @@ -64,11 +64,11 @@ message ThriftProxy { "envoy.extensions.filters.network.thrift_proxy.v3.ThriftProxy"; // Supplies the type of transport that the Thrift proxy should use. Defaults to - // :ref:`AUTO_TRANSPORT`. + // :ref:`AUTO_TRANSPORT`. TransportType transport = 2 [(validate.rules).enum = {defined_only: true}]; // Supplies the type of protocol that the Thrift proxy should use. Defaults to - // :ref:`AUTO_PROTOCOL`. + // :ref:`AUTO_PROTOCOL`. ProtocolType protocol = 3 [(validate.rules).enum = {defined_only: true}]; // The human readable prefix to use when emitting statistics. @@ -120,7 +120,7 @@ message ThriftFilter { // ThriftProtocolOptions specifies Thrift upstream protocol options. This object is used in // in -// :ref:`typed_extension_protocol_options`, +// :ref:`typed_extension_protocol_options`, // keyed by the name `envoy.filters.network.thrift_proxy`. message ThriftProtocolOptions { option (udpa.annotations.versioning).previous_message_type = @@ -128,13 +128,13 @@ message ThriftProtocolOptions { // Supplies the type of transport that the Thrift proxy should use for upstream connections. // Selecting - // :ref:`AUTO_TRANSPORT`, + // :ref:`AUTO_TRANSPORT`, // which is the default, causes the proxy to use the same transport as the downstream connection. TransportType transport = 1 [(validate.rules).enum = {defined_only: true}]; // Supplies the type of protocol that the Thrift proxy should use for upstream connections. // Selecting - // :ref:`AUTO_PROTOCOL`, + // :ref:`AUTO_PROTOCOL`, // which is the default, causes the proxy to use the same protocol as the downstream connection. ProtocolType protocol = 2 [(validate.rules).enum = {defined_only: true}]; } diff --git a/generated_api_shadow/envoy/extensions/internal_redirect/allow_listed_routes/v3/allow_listed_routes_config.proto b/generated_api_shadow/envoy/extensions/internal_redirect/allow_listed_routes/v3/allow_listed_routes_config.proto index 6880029e36ff5..90da16095fa95 100644 --- a/generated_api_shadow/envoy/extensions/internal_redirect/allow_listed_routes/v3/allow_listed_routes_config.proto +++ b/generated_api_shadow/envoy/extensions/internal_redirect/allow_listed_routes/v3/allow_listed_routes_config.proto @@ -16,7 +16,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.internal_redirect_predicates.allow_listed_routes] message AllowListedRoutesConfig { // The list of routes that's allowed as redirect target by this predicate, - // identified by the route's :ref:`name `. + // identified by the route's :ref:`name `. // Empty route names are not allowed. repeated string allowed_route_names = 1 [(validate.rules).repeated = {items {string {min_len: 1}}}]; diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto index f660d734ee84c..0c5c199510766 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -164,11 +164,11 @@ message TlsCertificate { config.core.v3.WatchedDirectory watched_directory = 7; // BoringSSL private key method provider. This is an alternative to :ref:`private_key - // ` field. This can't be + // ` field. This can't be // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key - // ` and + // ` and // :ref:`private_key_provider - // ` fields will result in an + // ` fields will result in an // error. PrivateKeyProvider private_key_provider = 6; @@ -194,7 +194,7 @@ message TlsSessionTicketKeys { // All keys are candidates for decrypting received tickets. This allows for easy rotation of keys // by, for example, putting the new key first, and the previous key second. // - // If :ref:`session_ticket_keys ` + // If :ref:`session_ticket_keys ` // is not specified, the TLS library will still support resuming sessions via tickets, but it will // use an internally-generated and managed key, so sessions cannot be resumed across hot restarts // or on different hosts. @@ -228,7 +228,7 @@ message CertificateValidationContext { // Connections where the certificate fails verification will be permitted. // For HTTP connections, the result of certificate verification can be used in route matching. ( - // see :ref:`validated ` ). + // see :ref:`validated ` ). ACCEPT_UNTRUSTED = 1; } @@ -239,13 +239,13 @@ message CertificateValidationContext { // for listeners). If not specified and a peer certificate is presented it will not be // verified. By default, a client certificate is optional, unless one of the additional // options (:ref:`require_client_certificate - // `, + // `, // :ref:`verify_certificate_spki - // `, + // `, // :ref:`verify_certificate_hash - // `, or + // `, or // :ref:`match_subject_alt_names - // `) is also + // `) is also // specified. // // It can optionally contain certificate revocation lists, in which case Envoy will verify @@ -291,15 +291,15 @@ message CertificateValidationContext { // // When both: // :ref:`verify_certificate_hash - // ` and + // ` and // :ref:`verify_certificate_spki - // ` are specified, + // ` are specified, // a hash matching value from either of the lists will result in the certificate being accepted. // // .. attention:: // // This option is preferred over :ref:`verify_certificate_hash - // `, + // `, // because SPKI is tied to a private key, so it doesn't change when the certificate // is renewed using the same private key. repeated string verify_certificate_spki = 3 @@ -327,9 +327,9 @@ message CertificateValidationContext { // // When both: // :ref:`verify_certificate_hash - // ` and + // ` and // :ref:`verify_certificate_spki - // ` are specified, + // ` are specified, // a hash matching value from either of the lists will result in the certificate being accepted. repeated string verify_certificate_hash = 2 [(validate.rules).repeated = {items {string {min_len: 64 max_bytes: 95}}}]; @@ -338,7 +338,7 @@ message CertificateValidationContext { // Subject Alternative Name of the presented certificate matches one of the specified matchers. // // When a certificate has wildcard DNS SAN entries, to match a specific client, it should be - // configured with exact match type in the :ref:`string matcher `. + // configured with exact match type in the :ref:`string matcher `. // For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com", // it should be configured as shown below. // @@ -351,7 +351,7 @@ message CertificateValidationContext { // // Subject Alternative Names are easily spoofable and verifying only them is insecure, // therefore this option must be used together with :ref:`trusted_ca - // `. + // `. repeated type.matcher.v3.StringMatcher match_subject_alt_names = 9; // [#not-implemented-hide:] Must present signed certificate time-stamp. diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/tls.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/tls.proto index 2c5a8bf21d355..44325bdbee6a4 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/tls.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/tls.proto @@ -32,7 +32,7 @@ message UpstreamTlsContext { // .. attention:: // // Server certificate verification is not enabled by default. Configure - // :ref:`trusted_ca` to enable + // :ref:`trusted_ca` to enable // verification. CommonTlsContext common_tls_context = 1; @@ -101,8 +101,8 @@ message DownstreamTlsContext { // Config for controlling stateless TLS session resumption: setting this to true will cause the TLS // server to not issue TLS session tickets for the purposes of stateless TLS session resumption. // If set to false, the TLS server will issue TLS session tickets and encrypt/decrypt them using - // the keys specified through either :ref:`session_ticket_keys ` - // or :ref:`session_ticket_keys_sds_secret_config `. + // the keys specified through either :ref:`session_ticket_keys ` + // or :ref:`session_ticket_keys_sds_secret_config `. // If this config is set to false and no keys are explicitly configured, the TLS server will issue // TLS session tickets and encrypt/decrypt them using an internally-generated and managed key, with the // implication that sessions cannot be resumed across hot restarts or on different hosts. @@ -256,7 +256,7 @@ message CommonTlsContext { // Supplies the list of ALPN protocols that the listener should expose. In // practice this is likely to be set to one of two values (see the // :ref:`codec_type - // ` + // ` // parameter in the HTTP connection manager for more information): // // * "h2,http/1.1" If the listener is going to support both HTTP/2 and HTTP/1.1. diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto index 3ee921e3f5dcd..cfb5e5c07e90c 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto @@ -41,8 +41,8 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // // Note that SPIFFE validator inherits and uses the following options from :ref:`CertificateValidationContext `. // -// - :ref:`allow_expired_certificate ` to allow expired certificates. -// - :ref:`match_subject_alt_names ` to match **URI** SAN of certificates. Unlike the default validator, SPIFFE validator only matches **URI** SAN (which equals to SVID in SPIFFE terminology) and ignore other SAN types. +// - :ref:`allow_expired_certificate ` to allow expired certificates. +// - :ref:`match_subject_alt_names ` to match **URI** SAN of certificates. Unlike the default validator, SPIFFE validator only matches **URI** SAN (which equals to SVID in SPIFFE terminology) and ignore other SAN types. // message SPIFFECertValidatorConfig { message TrustDomain { diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto index 1f2e1acf10b16..0bc4bf9e963fa 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto @@ -162,11 +162,11 @@ message TlsCertificate { config.core.v4alpha.WatchedDirectory watched_directory = 7; // BoringSSL private key method provider. This is an alternative to :ref:`private_key - // ` field. This can't be + // ` field. This can't be // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key - // ` and + // ` and // :ref:`private_key_provider - // ` fields will result in an + // ` fields will result in an // error. PrivateKeyProvider private_key_provider = 6; @@ -192,7 +192,7 @@ message TlsSessionTicketKeys { // All keys are candidates for decrypting received tickets. This allows for easy rotation of keys // by, for example, putting the new key first, and the previous key second. // - // If :ref:`session_ticket_keys ` + // If :ref:`session_ticket_keys ` // is not specified, the TLS library will still support resuming sessions via tickets, but it will // use an internally-generated and managed key, so sessions cannot be resumed across hot restarts // or on different hosts. @@ -226,7 +226,7 @@ message CertificateValidationContext { // Connections where the certificate fails verification will be permitted. // For HTTP connections, the result of certificate verification can be used in route matching. ( - // see :ref:`validated ` ). + // see :ref:`validated ` ). ACCEPT_UNTRUSTED = 1; } @@ -239,13 +239,13 @@ message CertificateValidationContext { // for listeners). If not specified and a peer certificate is presented it will not be // verified. By default, a client certificate is optional, unless one of the additional // options (:ref:`require_client_certificate - // `, + // `, // :ref:`verify_certificate_spki - // `, + // `, // :ref:`verify_certificate_hash - // `, or + // `, or // :ref:`match_subject_alt_names - // `) is also + // `) is also // specified. // // It can optionally contain certificate revocation lists, in which case Envoy will verify @@ -291,15 +291,15 @@ message CertificateValidationContext { // // When both: // :ref:`verify_certificate_hash - // ` and + // ` and // :ref:`verify_certificate_spki - // ` are specified, + // ` are specified, // a hash matching value from either of the lists will result in the certificate being accepted. // // .. attention:: // // This option is preferred over :ref:`verify_certificate_hash - // `, + // `, // because SPKI is tied to a private key, so it doesn't change when the certificate // is renewed using the same private key. repeated string verify_certificate_spki = 3 @@ -327,9 +327,9 @@ message CertificateValidationContext { // // When both: // :ref:`verify_certificate_hash - // ` and + // ` and // :ref:`verify_certificate_spki - // ` are specified, + // ` are specified, // a hash matching value from either of the lists will result in the certificate being accepted. repeated string verify_certificate_hash = 2 [(validate.rules).repeated = {items {string {min_len: 64 max_bytes: 95}}}]; @@ -338,7 +338,7 @@ message CertificateValidationContext { // Subject Alternative Name of the presented certificate matches one of the specified matchers. // // When a certificate has wildcard DNS SAN entries, to match a specific client, it should be - // configured with exact match type in the :ref:`string matcher `. + // configured with exact match type in the :ref:`string matcher `. // For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com", // it should be configured as shown below. // @@ -351,7 +351,7 @@ message CertificateValidationContext { // // Subject Alternative Names are easily spoofable and verifying only them is insecure, // therefore this option must be used together with :ref:`trusted_ca - // `. + // `. repeated type.matcher.v4alpha.StringMatcher match_subject_alt_names = 9; // [#not-implemented-hide:] Must present signed certificate time-stamp. diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/tls.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/tls.proto index 1a169a3476e59..7ddf55dfa6fab 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/tls.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/tls.proto @@ -31,7 +31,7 @@ message UpstreamTlsContext { // .. attention:: // // Server certificate verification is not enabled by default. Configure - // :ref:`trusted_ca` to enable + // :ref:`trusted_ca` to enable // verification. CommonTlsContext common_tls_context = 1; @@ -100,8 +100,8 @@ message DownstreamTlsContext { // Config for controlling stateless TLS session resumption: setting this to true will cause the TLS // server to not issue TLS session tickets for the purposes of stateless TLS session resumption. // If set to false, the TLS server will issue TLS session tickets and encrypt/decrypt them using - // the keys specified through either :ref:`session_ticket_keys ` - // or :ref:`session_ticket_keys_sds_secret_config `. + // the keys specified through either :ref:`session_ticket_keys ` + // or :ref:`session_ticket_keys_sds_secret_config `. // If this config is set to false and no keys are explicitly configured, the TLS server will issue // TLS session tickets and encrypt/decrypt them using an internally-generated and managed key, with the // implication that sessions cannot be resumed across hot restarts or on different hosts. @@ -261,7 +261,7 @@ message CommonTlsContext { // Supplies the list of ALPN protocols that the listener should expose. In // practice this is likely to be set to one of two values (see the // :ref:`codec_type - // ` + // ` // parameter in the HTTP connection manager for more information): // // * "h2,http/1.1" If the listener is going to support both HTTP/2 and HTTP/1.1. diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto index 276b8ad6875be..8191318930be6 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/tls_spiffe_validator_config.proto @@ -42,8 +42,8 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // // Note that SPIFFE validator inherits and uses the following options from :ref:`CertificateValidationContext `. // -// - :ref:`allow_expired_certificate ` to allow expired certificates. -// - :ref:`match_subject_alt_names ` to match **URI** SAN of certificates. Unlike the default validator, SPIFFE validator only matches **URI** SAN (which equals to SVID in SPIFFE terminology) and ignore other SAN types. +// - :ref:`allow_expired_certificate ` to allow expired certificates. +// - :ref:`match_subject_alt_names ` to match **URI** SAN of certificates. Unlike the default validator, SPIFFE validator only matches **URI** SAN (which equals to SVID in SPIFFE terminology) and ignore other SAN types. // message SPIFFECertValidatorConfig { option (udpa.annotations.versioning).previous_message_type = diff --git a/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto b/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto index 6f28a4f4b2340..2c8790c84faf5 100644 --- a/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto +++ b/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto @@ -17,7 +17,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HttpProtocolOptions specifies Http upstream protocol options. This object // is used in -// :ref:`typed_extension_protocol_options`, +// :ref:`typed_extension_protocol_options`, // keyed by the name `envoy.extensions.upstreams.http.v3.HttpProtocolOptions`. // // This controls what protocol(s) should be used for upstream and how said protocol(s) are configured. diff --git a/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto b/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto index 8545b77c1f0b2..9ceadcec907c6 100644 --- a/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto +++ b/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto @@ -18,7 +18,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // HttpProtocolOptions specifies Http upstream protocol options. This object // is used in -// :ref:`typed_extension_protocol_options`, +// :ref:`typed_extension_protocol_options`, // keyed by the name `envoy.extensions.upstreams.http.v3.HttpProtocolOptions`. // // This controls what protocol(s) should be used for upstream and how said protocol(s) are configured. diff --git a/generated_api_shadow/envoy/service/accesslog/v3/als.proto b/generated_api_shadow/envoy/service/accesslog/v3/als.proto index 5421c23049182..94a290ad4a325 100644 --- a/generated_api_shadow/envoy/service/accesslog/v3/als.proto +++ b/generated_api_shadow/envoy/service/accesslog/v3/als.proto @@ -49,7 +49,7 @@ message StreamAccessLogsMessage { config.core.v3.Node node = 1 [(validate.rules).message = {required: true}]; // The friendly name of the log configured in :ref:`CommonGrpcAccessLogConfig - // `. + // `. string log_name = 2 [(validate.rules).string = {min_len: 1}]; } diff --git a/generated_api_shadow/envoy/service/accesslog/v4alpha/als.proto b/generated_api_shadow/envoy/service/accesslog/v4alpha/als.proto index e2c8bbbc80689..ab0ba0e15213e 100644 --- a/generated_api_shadow/envoy/service/accesslog/v4alpha/als.proto +++ b/generated_api_shadow/envoy/service/accesslog/v4alpha/als.proto @@ -49,7 +49,7 @@ message StreamAccessLogsMessage { config.core.v4alpha.Node node = 1 [(validate.rules).message = {required: true}]; // The friendly name of the log configured in :ref:`CommonGrpcAccessLogConfig - // `. + // `. string log_name = 2 [(validate.rules).string = {min_len: 1}]; } diff --git a/generated_api_shadow/envoy/service/auth/v3/attribute_context.proto b/generated_api_shadow/envoy/service/auth/v3/attribute_context.proto index cdf3ee9f96e4b..452a1e1ad9a5f 100644 --- a/generated_api_shadow/envoy/service/auth/v3/attribute_context.proto +++ b/generated_api_shadow/envoy/service/auth/v3/attribute_context.proto @@ -148,7 +148,7 @@ message AttributeContext { // The HTTP request body in bytes. This is used instead of // :ref:`body ` when - // :ref:`pack_as_bytes ` + // :ref:`pack_as_bytes ` // is set to true. bytes raw_body = 12; } diff --git a/generated_api_shadow/envoy/service/auth/v4alpha/attribute_context.proto b/generated_api_shadow/envoy/service/auth/v4alpha/attribute_context.proto index a1bf9c9c62cb2..eed7a2e704ad0 100644 --- a/generated_api_shadow/envoy/service/auth/v4alpha/attribute_context.proto +++ b/generated_api_shadow/envoy/service/auth/v4alpha/attribute_context.proto @@ -148,7 +148,7 @@ message AttributeContext { // The HTTP request body in bytes. This is used instead of // :ref:`body ` when - // :ref:`pack_as_bytes ` + // :ref:`pack_as_bytes ` // is set to true. bytes raw_body = 12; } diff --git a/generated_api_shadow/envoy/service/discovery/v3/discovery.proto b/generated_api_shadow/envoy/service/discovery/v3/discovery.proto index 4a2547df39ff1..4a474d0fe2608 100644 --- a/generated_api_shadow/envoy/service/discovery/v3/discovery.proto +++ b/generated_api_shadow/envoy/service/discovery/v3/discovery.proto @@ -57,7 +57,7 @@ message DiscoveryRequest { // delta, where it is populated only for new explicit ACKs). string response_nonce = 5; - // This is populated when the previous :ref:`DiscoveryResponse ` + // This is populated when the previous :ref:`DiscoveryResponse ` // failed to update configuration. The *message* field in *error_details* provides the Envoy // internal exception related to the failure. It is only intended for consumption during manual // debugging, the string provided is not guaranteed to be stable across Envoy versions. @@ -195,7 +195,7 @@ message DeltaDiscoveryRequest { // Otherwise (unlike in DiscoveryRequest) response_nonce must be omitted. string response_nonce = 6; - // This is populated when the previous :ref:`DiscoveryResponse ` + // This is populated when the previous :ref:`DiscoveryResponse ` // failed to update configuration. The *message* field in *error_details* // provides the Envoy internal exception related to the failure. google.rpc.Status error_detail = 7; diff --git a/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto b/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto index 514eefb3e8894..bf8d48fc7a374 100644 --- a/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto +++ b/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto @@ -58,7 +58,7 @@ message DiscoveryRequest { // delta, where it is populated only for new explicit ACKs). string response_nonce = 5; - // This is populated when the previous :ref:`DiscoveryResponse ` + // This is populated when the previous :ref:`DiscoveryResponse ` // failed to update configuration. The *message* field in *error_details* provides the Envoy // internal exception related to the failure. It is only intended for consumption during manual // debugging, the string provided is not guaranteed to be stable across Envoy versions. @@ -198,7 +198,7 @@ message DeltaDiscoveryRequest { // Otherwise (unlike in DiscoveryRequest) response_nonce must be omitted. string response_nonce = 6; - // This is populated when the previous :ref:`DiscoveryResponse ` + // This is populated when the previous :ref:`DiscoveryResponse ` // failed to update configuration. The *message* field in *error_details* // provides the Envoy internal exception related to the failure. google.rpc.Status error_detail = 7; diff --git a/generated_api_shadow/envoy/service/event_reporting/v3/event_reporting_service.proto b/generated_api_shadow/envoy/service/event_reporting/v3/event_reporting_service.proto index 6f0b325902fb2..30c161a1c5309 100644 --- a/generated_api_shadow/envoy/service/event_reporting/v3/event_reporting_service.proto +++ b/generated_api_shadow/envoy/service/event_reporting/v3/event_reporting_service.proto @@ -53,8 +53,8 @@ message StreamEventsRequest { // // The following events are supported: // - // * :ref:`HealthCheckEvent ` - // * :ref:`OutlierDetectionEvent ` + // * :ref:`HealthCheckEvent ` + // * :ref:`OutlierDetectionEvent ` repeated google.protobuf.Any events = 2 [(validate.rules).repeated = {min_items: 1}]; } diff --git a/generated_api_shadow/envoy/service/event_reporting/v4alpha/event_reporting_service.proto b/generated_api_shadow/envoy/service/event_reporting/v4alpha/event_reporting_service.proto index 00755fddfac5b..6bff2a09c25ba 100644 --- a/generated_api_shadow/envoy/service/event_reporting/v4alpha/event_reporting_service.proto +++ b/generated_api_shadow/envoy/service/event_reporting/v4alpha/event_reporting_service.proto @@ -53,8 +53,8 @@ message StreamEventsRequest { // // The following events are supported: // - // * :ref:`HealthCheckEvent ` - // * :ref:`OutlierDetectionEvent ` + // * :ref:`HealthCheckEvent ` + // * :ref:`OutlierDetectionEvent ` repeated google.protobuf.Any events = 2 [(validate.rules).repeated = {min_items: 1}]; } diff --git a/generated_api_shadow/envoy/service/health/v3/hds.proto b/generated_api_shadow/envoy/service/health/v3/hds.proto index b8c231d062a7c..bb8781d5c3958 100644 --- a/generated_api_shadow/envoy/service/health/v3/hds.proto +++ b/generated_api_shadow/envoy/service/health/v3/hds.proto @@ -171,9 +171,9 @@ message ClusterHealthCheck { repeated LocalityEndpoints locality_endpoints = 3; - // Optional map that gets filtered by :ref:`health_checks.transport_socket_match_criteria ` + // Optional map that gets filtered by :ref:`health_checks.transport_socket_match_criteria ` // on connection when health checking. For more details, see - // :ref:`config.cluster.v3.Cluster.transport_socket_matches `. + // :ref:`config.cluster.v3.Cluster.transport_socket_matches `. repeated config.cluster.v3.Cluster.TransportSocketMatch transport_socket_matches = 4; } diff --git a/generated_api_shadow/envoy/service/health/v4alpha/hds.proto b/generated_api_shadow/envoy/service/health/v4alpha/hds.proto index bf3a5133f1193..1b2446b109d8b 100644 --- a/generated_api_shadow/envoy/service/health/v4alpha/hds.proto +++ b/generated_api_shadow/envoy/service/health/v4alpha/hds.proto @@ -176,9 +176,9 @@ message ClusterHealthCheck { repeated LocalityEndpoints locality_endpoints = 3; - // Optional map that gets filtered by :ref:`health_checks.transport_socket_match_criteria ` + // Optional map that gets filtered by :ref:`health_checks.transport_socket_match_criteria ` // on connection when health checking. For more details, see - // :ref:`config.cluster.v3.Cluster.transport_socket_matches `. + // :ref:`config.cluster.v3.Cluster.transport_socket_matches `. repeated config.cluster.v4alpha.Cluster.TransportSocketMatch transport_socket_matches = 4; } diff --git a/generated_api_shadow/envoy/service/load_stats/v3/lrs.proto b/generated_api_shadow/envoy/service/load_stats/v3/lrs.proto index ca8377e1ca64c..0b565ebe72368 100644 --- a/generated_api_shadow/envoy/service/load_stats/v3/lrs.proto +++ b/generated_api_shadow/envoy/service/load_stats/v3/lrs.proto @@ -20,10 +20,10 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Load Reporting Service is an Envoy API to emit load reports. Envoy will initiate a bi-directional // stream with a management server. Upon connecting, the management server can send a -// :ref:`LoadStatsResponse ` to a node it is +// :ref:`LoadStatsResponse ` to a node it is // interested in getting the load reports for. Envoy in this node will start sending -// :ref:`LoadStatsRequest `. This is done periodically -// based on the :ref:`load reporting interval ` +// :ref:`LoadStatsRequest `. This is done periodically +// based on the :ref:`load reporting interval ` // For details, take a look at the :ref:`Load Reporting Service sandbox example `. service LoadReportingService { @@ -83,7 +83,7 @@ message LoadStatsResponse { // If true, the client should send all clusters it knows about. // Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their - // :ref:`client_features` field will honor this field. + // :ref:`client_features` field will honor this field. bool send_all_clusters = 4; // The minimum interval of time to collect stats over. This is only a minimum for two reasons: diff --git a/generated_api_shadow/envoy/service/load_stats/v4alpha/lrs.proto b/generated_api_shadow/envoy/service/load_stats/v4alpha/lrs.proto index 69eb3d707d5e1..f99b6555f4a17 100644 --- a/generated_api_shadow/envoy/service/load_stats/v4alpha/lrs.proto +++ b/generated_api_shadow/envoy/service/load_stats/v4alpha/lrs.proto @@ -20,10 +20,10 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // Load Reporting Service is an Envoy API to emit load reports. Envoy will initiate a bi-directional // stream with a management server. Upon connecting, the management server can send a -// :ref:`LoadStatsResponse ` to a node it is +// :ref:`LoadStatsResponse ` to a node it is // interested in getting the load reports for. Envoy in this node will start sending -// :ref:`LoadStatsRequest `. This is done periodically -// based on the :ref:`load reporting interval ` +// :ref:`LoadStatsRequest `. This is done periodically +// based on the :ref:`load reporting interval ` // For details, take a look at the :ref:`Load Reporting Service sandbox example `. service LoadReportingService { @@ -83,7 +83,7 @@ message LoadStatsResponse { // If true, the client should send all clusters it knows about. // Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their - // :ref:`client_features` field will honor this field. + // :ref:`client_features` field will honor this field. bool send_all_clusters = 4; // The minimum interval of time to collect stats over. This is only a minimum for two reasons: diff --git a/generated_api_shadow/envoy/service/route/v3/rds.proto b/generated_api_shadow/envoy/service/route/v3/rds.proto index 33ee1e6f8db56..62a7da4094936 100644 --- a/generated_api_shadow/envoy/service/route/v3/rds.proto +++ b/generated_api_shadow/envoy/service/route/v3/rds.proto @@ -43,11 +43,11 @@ service RouteDiscoveryService { // Virtual Host Discovery Service (VHDS) is used to dynamically update the list of virtual hosts for // a given RouteConfiguration. If VHDS is configured a virtual host list update will be triggered // during the processing of an HTTP request if a route for the request cannot be resolved. The -// :ref:`resource_names_subscribe ` +// :ref:`resource_names_subscribe ` // field contains a list of virtual host names or aliases to track. The contents of an alias would // be the contents of a *host* or *authority* header used to make an http request. An xDS server // will match an alias to a virtual host based on the content of :ref:`domains' -// ` field. The *resource_names_unsubscribe* field +// ` field. The *resource_names_unsubscribe* field // contains a list of virtual host names that have been :ref:`unsubscribed // ` from the routing table associated with the RouteConfiguration. service VirtualHostDiscoveryService { diff --git a/generated_api_shadow/envoy/service/route/v3/srds.proto b/generated_api_shadow/envoy/service/route/v3/srds.proto index 7a7f8f7d3a3fa..64fe45fee1fab 100644 --- a/generated_api_shadow/envoy/service/route/v3/srds.proto +++ b/generated_api_shadow/envoy/service/route/v3/srds.proto @@ -20,11 +20,11 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // * Routing :ref:`architecture overview ` // The Scoped Routes Discovery Service (SRDS) API distributes -// :ref:`ScopedRouteConfiguration` +// :ref:`ScopedRouteConfiguration` // resources. Each ScopedRouteConfiguration resource represents a "routing // scope" containing a mapping that allows the HTTP connection manager to // dynamically assign a routing table (specified via a -// :ref:`RouteConfiguration` message) to each +// :ref:`RouteConfiguration` message) to each // HTTP request. service ScopedRoutesDiscoveryService { option (envoy.annotations.resource).type = "envoy.config.route.v3.ScopedRouteConfiguration"; diff --git a/generated_api_shadow/envoy/service/tap/v3/tap.proto b/generated_api_shadow/envoy/service/tap/v3/tap.proto index 080aba215c10d..5d9866e570747 100644 --- a/generated_api_shadow/envoy/service/tap/v3/tap.proto +++ b/generated_api_shadow/envoy/service/tap/v3/tap.proto @@ -41,7 +41,7 @@ message StreamTapsRequest { config.core.v3.Node node = 1 [(validate.rules).message = {required: true}]; // The opaque identifier that was set in the :ref:`output config - // `. + // `. string tap_id = 2; } diff --git a/generated_api_shadow/envoy/service/tap/v4alpha/tap.proto b/generated_api_shadow/envoy/service/tap/v4alpha/tap.proto index a1654d18bebbf..4ef38d1bae983 100644 --- a/generated_api_shadow/envoy/service/tap/v4alpha/tap.proto +++ b/generated_api_shadow/envoy/service/tap/v4alpha/tap.proto @@ -41,7 +41,7 @@ message StreamTapsRequest { config.core.v4alpha.Node node = 1 [(validate.rules).message = {required: true}]; // The opaque identifier that was set in the :ref:`output config - // `. + // `. string tap_id = 2; } diff --git a/generated_api_shadow/envoy/type/http/v3/path_transformation.proto b/generated_api_shadow/envoy/type/http/v3/path_transformation.proto index 8a3c9ef5aaf80..0b3d72009f5ff 100644 --- a/generated_api_shadow/envoy/type/http/v3/path_transformation.proto +++ b/generated_api_shadow/envoy/type/http/v3/path_transformation.proto @@ -34,7 +34,7 @@ message PathTransformation { // Determines if adjacent slashes are merged into one. A common use case is for a request path // header. Using this option in `:ref: PathNormalizationOptions - // ` + // ` // will allow incoming requests with path `//dir///file` to match against route with `prefix` // match set to `/dir`. When using for header transformations, note that slash merging is not // part of `HTTP spec `_ and is provided for convenience. diff --git a/generated_api_shadow/envoy/type/matcher/v3/metadata.proto b/generated_api_shadow/envoy/type/matcher/v3/metadata.proto index a7184ee980508..68710dc718546 100644 --- a/generated_api_shadow/envoy/type/matcher/v3/metadata.proto +++ b/generated_api_shadow/envoy/type/matcher/v3/metadata.proto @@ -16,7 +16,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Metadata matcher] // MetadataMatcher provides a general interface to check if a given value is matched in -// :ref:`Metadata `. It uses `filter` and `path` to retrieve the value +// :ref:`Metadata `. It uses `filter` and `path` to retrieve the value // from the Metadata and then check if it's matched to the specified value. // // For example, for the following Metadata: @@ -71,8 +71,8 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // // An example use of MetadataMatcher is specifying additional metadata in envoy.filters.http.rbac to // enforce access control based on dynamic metadata in a request. See :ref:`Permission -// ` and :ref:`Principal -// `. +// ` and :ref:`Principal +// `. // [#next-major-version: MetadataMatcher should use StructMatcher] message MetadataMatcher { diff --git a/generated_api_shadow/envoy/type/matcher/v4alpha/metadata.proto b/generated_api_shadow/envoy/type/matcher/v4alpha/metadata.proto index 35af650391ff5..e61ba2754337b 100644 --- a/generated_api_shadow/envoy/type/matcher/v4alpha/metadata.proto +++ b/generated_api_shadow/envoy/type/matcher/v4alpha/metadata.proto @@ -16,7 +16,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: Metadata matcher] // MetadataMatcher provides a general interface to check if a given value is matched in -// :ref:`Metadata `. It uses `filter` and `path` to retrieve the value +// :ref:`Metadata `. It uses `filter` and `path` to retrieve the value // from the Metadata and then check if it's matched to the specified value. // // For example, for the following Metadata: @@ -71,8 +71,8 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // // An example use of MetadataMatcher is specifying additional metadata in envoy.filters.http.rbac to // enforce access control based on dynamic metadata in a request. See :ref:`Permission -// ` and :ref:`Principal -// `. +// ` and :ref:`Principal +// `. // [#next-major-version: MetadataMatcher should use StructMatcher] message MetadataMatcher { diff --git a/generated_api_shadow/envoy/type/metadata/v3/metadata.proto b/generated_api_shadow/envoy/type/metadata/v3/metadata.proto index b971d8debbe51..5dd58b23c6231 100644 --- a/generated_api_shadow/envoy/type/metadata/v3/metadata.proto +++ b/generated_api_shadow/envoy/type/metadata/v3/metadata.proto @@ -14,7 +14,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Metadata] // MetadataKey provides a general interface using `key` and `path` to retrieve value from -// :ref:`Metadata `. +// :ref:`Metadata `. // // For example, for the following Metadata: // @@ -77,20 +77,20 @@ message MetadataKind { "envoy.type.metadata.v2.MetadataKind.Request"; } - // Represents metadata from :ref:`the route`. + // Represents metadata from :ref:`the route`. message Route { option (udpa.annotations.versioning).previous_message_type = "envoy.type.metadata.v2.MetadataKind.Route"; } - // Represents metadata from :ref:`the upstream cluster`. + // Represents metadata from :ref:`the upstream cluster`. message Cluster { option (udpa.annotations.versioning).previous_message_type = "envoy.type.metadata.v2.MetadataKind.Cluster"; } // Represents metadata from :ref:`the upstream - // host`. + // host`. message Host { option (udpa.annotations.versioning).previous_message_type = "envoy.type.metadata.v2.MetadataKind.Host"; diff --git a/generated_api_shadow/envoy/type/tracing/v3/custom_tag.proto b/generated_api_shadow/envoy/type/tracing/v3/custom_tag.proto index bcebe5779ba15..ad99cafb22bf4 100644 --- a/generated_api_shadow/envoy/type/tracing/v3/custom_tag.proto +++ b/generated_api_shadow/envoy/type/tracing/v3/custom_tag.proto @@ -59,8 +59,8 @@ message CustomTag { } // Metadata type custom tag using - // :ref:`MetadataKey ` to retrieve the protobuf value - // from :ref:`Metadata `, and populate the tag value with + // :ref:`MetadataKey ` to retrieve the protobuf value + // from :ref:`Metadata `, and populate the tag value with // `the canonical JSON `_ // representation of it. message Metadata { diff --git a/tools/protodoc/protodoc.py b/tools/protodoc/protodoc.py index 0dbfe44db6642..417f8b43be12c 100755 --- a/tools/protodoc/protodoc.py +++ b/tools/protodoc/protodoc.py @@ -448,27 +448,27 @@ def strip_leading_space(s): def file_cross_ref_label(msg_name): """File cross reference label.""" - return 'envoy_api_file_%s' % msg_name + return 'envoy_v3_api_file_%s' % msg_name def message_cross_ref_label(msg_name): """Message cross reference label.""" - return 'envoy_api_msg_%s' % msg_name + return 'envoy_v3_api_msg_%s' % msg_name def enum_cross_ref_label(enum_name): """Enum cross reference label.""" - return 'envoy_api_enum_%s' % enum_name + return 'envoy_v3_api_enum_%s' % enum_name def field_cross_ref_label(field_name): """Field cross reference label.""" - return 'envoy_api_field_%s' % field_name + return 'envoy_v3_api_field_%s' % field_name def enum_value_cross_ref_label(enum_value_name): """Enum value cross reference label.""" - return 'envoy_api_enum_value_%s' % enum_value_name + return 'envoy_v3_api_enum_value_%s' % enum_value_name def format_anchor(label): @@ -714,8 +714,7 @@ def visit_file(self, file_proto, type_context, services, msgs, enums): v2_link = "" if file_proto.name in self.v2_mapping: - # TODO(phlax): remove _v2_ from filepath once sed mangling is removed - v2_filepath = f"envoy_v2_api_file_{self.v2_mapping[file_proto.name]}" + v2_filepath = f"envoy_api_file_{self.v2_mapping[file_proto.name]}" v2_text = v2_filepath.split('/', 1)[1] v2_url = f"v{ENVOY_LAST_V2_VERSION}:{v2_filepath}" v2_link = V2_LINK_TEMPLATE.render(v2_url=v2_url, v2_text=v2_text) From 93f9d168e54606e02f319ec13ca36d50e421df1b Mon Sep 17 00:00:00 2001 From: chaoqin-li1123 <55518381+chaoqin-li1123@users.noreply.github.com> Date: Sun, 9 May 2021 19:01:25 -0500 Subject: [PATCH 179/209] remove support for type url downgrade and upgrade conversion (#16372) Remove support for mixed v2/v3 type url introduced by pull request(#12913) Fixes #16015 Signed-off-by: chaoqin-li1123 --- include/envoy/config/grpc_mux.h | 3 - source/common/config/BUILD | 1 - source/common/config/api_type_oracle.cc | 9 -- source/common/config/api_type_oracle.h | 3 - source/common/config/grpc_mux_impl.cc | 34 +------ source/common/config/grpc_mux_impl.h | 3 - .../common/config/grpc_subscription_impl.cc | 1 - source/common/config/new_grpc_mux_impl.cc | 31 +----- source/common/config/new_grpc_mux_impl.h | 4 - .../config/subscription_factory_impl.cc | 1 + source/common/protobuf/BUILD | 11 --- source/common/protobuf/type_util.cc | 17 ---- source/common/protobuf/type_util.h | 17 ---- source/common/protobuf/utility.cc | 13 +++ source/common/protobuf/utility.h | 7 ++ source/common/runtime/runtime_features.cc | 3 - test/common/config/api_type_oracle_test.cc | 3 - test/common/config/grpc_mux_impl_test.cc | 84 ---------------- test/common/config/new_grpc_mux_impl_test.cc | 99 ------------------- test/common/protobuf/BUILD | 8 -- test/common/protobuf/utility_test.cc | 9 ++ test/integration/ads_integration_test.cc | 32 ------ 22 files changed, 35 insertions(+), 358 deletions(-) delete mode 100644 source/common/protobuf/type_util.cc delete mode 100644 source/common/protobuf/type_util.h diff --git a/include/envoy/config/grpc_mux.h b/include/envoy/config/grpc_mux.h index 620b217b2216f..57befc93e01ca 100644 --- a/include/envoy/config/grpc_mux.h +++ b/include/envoy/config/grpc_mux.h @@ -105,9 +105,6 @@ class GrpcMux { virtual void requestOnDemandUpdate(const std::string& type_url, const absl::flat_hash_set& for_update) PURE; - - using TypeUrlMap = absl::flat_hash_map; - static TypeUrlMap& typeUrlMap() { MUTABLE_CONSTRUCT_ON_FIRST_USE(TypeUrlMap, {}); } }; using GrpcMuxPtr = std::unique_ptr; diff --git a/source/common/config/BUILD b/source/common/config/BUILD index de123c9f86c5b..e5e73d9e9d4da 100644 --- a/source/common/config/BUILD +++ b/source/common/config/BUILD @@ -14,7 +14,6 @@ envoy_cc_library( hdrs = ["api_type_oracle.h"], deps = [ "//source/common/protobuf", - "//source/common/protobuf:type_util_lib", "@com_github_cncf_udpa//udpa/annotations:pkg_cc_proto", ], ) diff --git a/source/common/config/api_type_oracle.cc b/source/common/config/api_type_oracle.cc index f6feba09828a1..8762392af5cbd 100644 --- a/source/common/config/api_type_oracle.cc +++ b/source/common/config/api_type_oracle.cc @@ -32,14 +32,5 @@ ApiTypeOracle::getEarlierVersionMessageTypeName(const std::string& message_type) return absl::nullopt; } -const absl::optional ApiTypeOracle::getEarlierTypeUrl(const std::string& type_url) { - const std::string type{TypeUtil::typeUrlToDescriptorFullName(type_url)}; - absl::optional old_type = ApiTypeOracle::getEarlierVersionMessageTypeName(type); - if (old_type.has_value()) { - return TypeUtil::descriptorFullNameToTypeUrl(old_type.value()); - } - return {}; -} - } // namespace Config } // namespace Envoy diff --git a/source/common/config/api_type_oracle.h b/source/common/config/api_type_oracle.h index ab5e75b113d78..bde8186fa555c 100644 --- a/source/common/config/api_type_oracle.h +++ b/source/common/config/api_type_oracle.h @@ -1,7 +1,6 @@ #pragma once #include "common/protobuf/protobuf.h" -#include "common/protobuf/type_util.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" @@ -24,8 +23,6 @@ class ApiTypeOracle { static const absl::optional getEarlierVersionMessageTypeName(const std::string& message_type); - - static const absl::optional getEarlierTypeUrl(const std::string& type_url); }; } // namespace Config diff --git a/source/common/config/grpc_mux_impl.cc b/source/common/config/grpc_mux_impl.cc index 66352fe9a0a20..eab59e846bdac 100644 --- a/source/common/config/grpc_mux_impl.cc +++ b/source/common/config/grpc_mux_impl.cc @@ -25,8 +25,6 @@ GrpcMuxImpl::GrpcMuxImpl(const LocalInfo::LocalInfo& local_info, local_info_(local_info), skip_subsequent_node_(skip_subsequent_node), first_stream_request_(true), transport_api_version_(transport_api_version), dispatcher_(dispatcher), - enable_type_url_downgrade_and_upgrade_(Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.enable_type_url_downgrade_and_upgrade")), dynamic_update_callback_handle_(local_info.contextProvider().addDynamicContextUpdateCallback( [this](absl::string_view resource_type_url) { onDynamicContextUpdate(resource_type_url); @@ -97,9 +95,6 @@ GrpcMuxWatchPtr GrpcMuxImpl::addWatch(const std::string& type_url, apiStateFor(type_url).request_.mutable_node()->MergeFrom(local_info_.node()); apiStateFor(type_url).subscribed_ = true; subscriptions_.emplace_back(type_url); - if (enable_type_url_downgrade_and_upgrade_) { - registerVersionedTypeUrl(type_url); - } } // This will send an updated request on each subscription. @@ -137,36 +132,15 @@ ScopedResume GrpcMuxImpl::pause(const std::vector type_urls) { }); } -void GrpcMuxImpl::registerVersionedTypeUrl(const std::string& type_url) { - TypeUrlMap& type_url_map = typeUrlMap(); - if (type_url_map.find(type_url) != type_url_map.end()) { - return; - } - // If type_url is v3, earlier_type_url will contain v2 type url. - const absl::optional earlier_type_url = ApiTypeOracle::getEarlierTypeUrl(type_url); - // Register v2 to v3 and v3 to v2 type_url mapping in the hash map. - if (earlier_type_url.has_value()) { - type_url_map[earlier_type_url.value()] = type_url; - type_url_map[type_url] = earlier_type_url.value(); - } -} - void GrpcMuxImpl::onDiscoveryResponse( std::unique_ptr&& message, ControlPlaneStats& control_plane_stats) { - std::string type_url = message->type_url(); + const std::string type_url = message->type_url(); ENVOY_LOG(debug, "Received gRPC message for {} at version {}", type_url, message->version_info()); if (message->has_control_plane()) { control_plane_stats.identifier_.set(message->control_plane().identifier()); } - // If this type url is not watched(no subscriber or no watcher), try another version of type url. - if (enable_type_url_downgrade_and_upgrade_ && api_state_.count(type_url) == 0) { - registerVersionedTypeUrl(type_url); - TypeUrlMap& type_url_map = typeUrlMap(); - if (type_url_map.find(type_url) != type_url_map.end()) { - type_url = type_url_map[type_url]; - } - } + if (api_state_.count(type_url) == 0) { // TODO(yuval-k): This should never happen. consider dropping the stream as this is a // protocol violation @@ -216,10 +190,10 @@ void GrpcMuxImpl::onDiscoveryResponse( for (const auto& resource : message->resources()) { // TODO(snowp): Check the underlying type when the resource is a Resource. if (!resource.Is() && - message->type_url() != resource.type_url()) { + type_url != resource.type_url()) { throw EnvoyException( fmt::format("{} does not match the message-wide type URL {} in DiscoveryResponse {}", - resource.type_url(), message->type_url(), message->DebugString())); + resource.type_url(), type_url, message->DebugString())); } auto decoded_resource = diff --git a/source/common/config/grpc_mux_impl.h b/source/common/config/grpc_mux_impl.h index 3600e78111aeb..3c4b973b8d9d1 100644 --- a/source/common/config/grpc_mux_impl.h +++ b/source/common/config/grpc_mux_impl.h @@ -21,7 +21,6 @@ #include "common/config/grpc_stream.h" #include "common/config/ttl.h" #include "common/config/utility.h" -#include "common/runtime/runtime_features.h" #include "absl/container/node_hash_map.h" @@ -62,7 +61,6 @@ class GrpcMuxImpl : public GrpcMux, // Config::GrpcStreamCallbacks void onStreamEstablished() override; void onEstablishmentFailure() override; - void registerVersionedTypeUrl(const std::string& type_url); void onDiscoveryResponse(std::unique_ptr&& message, ControlPlaneStats& control_plane_stats) override; @@ -177,7 +175,6 @@ class GrpcMuxImpl : public GrpcMux, const envoy::config::core::v3::ApiVersion transport_api_version_; Event::Dispatcher& dispatcher_; - bool enable_type_url_downgrade_and_upgrade_; Common::CallbackHandlePtr dynamic_update_callback_handle_; }; diff --git a/source/common/config/grpc_subscription_impl.cc b/source/common/config/grpc_subscription_impl.cc index 602b5df7dc91a..3b6412d111dc3 100644 --- a/source/common/config/grpc_subscription_impl.cc +++ b/source/common/config/grpc_subscription_impl.cc @@ -8,7 +8,6 @@ #include "common/config/xds_resource.h" #include "common/grpc/common.h" #include "common/protobuf/protobuf.h" -#include "common/protobuf/type_util.h" #include "common/protobuf/utility.h" namespace Envoy { diff --git a/source/common/config/new_grpc_mux_impl.cc b/source/common/config/new_grpc_mux_impl.cc index 75fb14f4c6f66..62a2ba950d155 100644 --- a/source/common/config/new_grpc_mux_impl.cc +++ b/source/common/config/new_grpc_mux_impl.cc @@ -30,9 +30,7 @@ NewGrpcMuxImpl::NewGrpcMuxImpl(Grpc::RawAsyncClientPtr&& async_client, [this](absl::string_view resource_type_url) { onDynamicContextUpdate(resource_type_url); })), - transport_api_version_(transport_api_version), dispatcher_(dispatcher), - enable_type_url_downgrade_and_upgrade_(Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.enable_type_url_downgrade_and_upgrade")) {} + transport_api_version_(transport_api_version), dispatcher_(dispatcher) {} void NewGrpcMuxImpl::onDynamicContextUpdate(absl::string_view resource_type_url) { auto sub = subscriptions_.find(resource_type_url); @@ -62,20 +60,6 @@ ScopedResume NewGrpcMuxImpl::pause(const std::vector type_urls) { }); } -void NewGrpcMuxImpl::registerVersionedTypeUrl(const std::string& type_url) { - TypeUrlMap& type_url_map = typeUrlMap(); - if (type_url_map.find(type_url) != type_url_map.end()) { - return; - } - // If type_url is v3, earlier_type_url will contain v2 type url. - absl::optional earlier_type_url = ApiTypeOracle::getEarlierTypeUrl(type_url); - // Register v2 to v3 and v3 to v2 type_url mapping in the hash map. - if (earlier_type_url.has_value()) { - type_url_map[earlier_type_url.value()] = type_url; - type_url_map[type_url] = earlier_type_url.value(); - } -} - void NewGrpcMuxImpl::onDiscoveryResponse( std::unique_ptr&& message, ControlPlaneStats& control_plane_stats) { @@ -85,15 +69,6 @@ void NewGrpcMuxImpl::onDiscoveryResponse( control_plane_stats.identifier_.set(message->control_plane().identifier()); } auto sub = subscriptions_.find(message->type_url()); - // If this type url is not watched, try another version type url. - if (enable_type_url_downgrade_and_upgrade_ && sub == subscriptions_.end()) { - const std::string& type_url = message->type_url(); - registerVersionedTypeUrl(type_url); - TypeUrlMap& type_url_map = typeUrlMap(); - if (type_url_map.find(type_url) != type_url_map.end()) { - sub = subscriptions_.find(type_url_map[type_url]); - } - } if (sub == subscriptions_.end()) { ENVOY_LOG(warn, "Dropping received DeltaDiscoveryResponse (with version {}) for non-existent " @@ -153,10 +128,6 @@ GrpcMuxWatchPtr NewGrpcMuxImpl::addWatch(const std::string& type_url, auto entry = subscriptions_.find(type_url); if (entry == subscriptions_.end()) { // We don't yet have a subscription for type_url! Make one! - if (enable_type_url_downgrade_and_upgrade_) { - registerVersionedTypeUrl(type_url); - } - // No resources implies that this is a wildcard request subscription. addSubscription(type_url, options.use_namespace_matching_, resources.empty()); return addWatch(type_url, resources, callbacks, resource_decoder, options); } diff --git a/source/common/config/new_grpc_mux_impl.h b/source/common/config/new_grpc_mux_impl.h index 71664cb30a13c..570cc7dfafc44 100644 --- a/source/common/config/new_grpc_mux_impl.h +++ b/source/common/config/new_grpc_mux_impl.h @@ -50,8 +50,6 @@ class NewGrpcMuxImpl ScopedResume pause(const std::string& type_url) override; ScopedResume pause(const std::vector type_urls) override; - void registerVersionedTypeUrl(const std::string& type_url); - void onDiscoveryResponse( std::unique_ptr&& message, ControlPlaneStats& control_plane_stats) override; @@ -171,8 +169,6 @@ class NewGrpcMuxImpl Common::CallbackHandlePtr dynamic_update_callback_handle_; const envoy::config::core::v3::ApiVersion transport_api_version_; Event::Dispatcher& dispatcher_; - - const bool enable_type_url_downgrade_and_upgrade_; }; using NewGrpcMuxImplPtr = std::unique_ptr; diff --git a/source/common/config/subscription_factory_impl.cc b/source/common/config/subscription_factory_impl.cc index cf53c93cfd53e..277a3524e7282 100644 --- a/source/common/config/subscription_factory_impl.cc +++ b/source/common/config/subscription_factory_impl.cc @@ -12,6 +12,7 @@ #include "common/config/xds_resource.h" #include "common/http/utility.h" #include "common/protobuf/protobuf.h" +#include "common/protobuf/utility.h" namespace Envoy { namespace Config { diff --git a/source/common/protobuf/BUILD b/source/common/protobuf/BUILD index 8fb417d7c5512..d8f3c67a06c95 100644 --- a/source/common/protobuf/BUILD +++ b/source/common/protobuf/BUILD @@ -61,7 +61,6 @@ envoy_cc_library( deps = [ ":message_validator_lib", ":protobuf", - ":type_util_lib", ":well_known_lib", "//include/envoy/api:api_interface", "//include/envoy/protobuf:message_validator_interface", @@ -81,16 +80,6 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "type_util_lib", - srcs = ["type_util.cc"], - hdrs = ["type_util.h"], - deps = [ - "//source/common/protobuf", - "@com_github_cncf_udpa//udpa/annotations:pkg_cc_proto", - ], -) - envoy_cc_library( name = "visitor_lib", srcs = ["visitor.cc"], diff --git a/source/common/protobuf/type_util.cc b/source/common/protobuf/type_util.cc deleted file mode 100644 index 03b5c1f01b343..0000000000000 --- a/source/common/protobuf/type_util.cc +++ /dev/null @@ -1,17 +0,0 @@ -#include "common/protobuf/type_util.h" - -namespace Envoy { - -absl::string_view TypeUtil::typeUrlToDescriptorFullName(absl::string_view type_url) { - const size_t pos = type_url.rfind('/'); - if (pos != absl::string_view::npos) { - type_url = type_url.substr(pos + 1); - } - return type_url; -} - -std::string TypeUtil::descriptorFullNameToTypeUrl(absl::string_view type) { - return "type.googleapis.com/" + std::string(type); -} - -} // namespace Envoy diff --git a/source/common/protobuf/type_util.h b/source/common/protobuf/type_util.h deleted file mode 100644 index 9bcfa0f8c2f62..0000000000000 --- a/source/common/protobuf/type_util.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "common/protobuf/protobuf.h" - -#include "absl/strings/string_view.h" -#include "absl/types/optional.h" - -namespace Envoy { - -class TypeUtil { -public: - static absl::string_view typeUrlToDescriptorFullName(absl::string_view type_url); - - static std::string descriptorFullNameToTypeUrl(absl::string_view type); -}; - -} // namespace Envoy diff --git a/source/common/protobuf/utility.cc b/source/common/protobuf/utility.cc index 0b49ad7d92736..a194d72e2f146 100644 --- a/source/common/protobuf/utility.cc +++ b/source/common/protobuf/utility.cc @@ -1043,4 +1043,17 @@ void TimestampUtil::systemClockToTimestamp(const SystemTime system_clock_time, .time_since_epoch() .count())); } + +absl::string_view TypeUtil::typeUrlToDescriptorFullName(absl::string_view type_url) { + const size_t pos = type_url.rfind('/'); + if (pos != absl::string_view::npos) { + type_url = type_url.substr(pos + 1); + } + return type_url; +} + +std::string TypeUtil::descriptorFullNameToTypeUrl(absl::string_view type) { + return "type.googleapis.com/" + std::string(type); +} + } // namespace Envoy diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index 577a068581787..0b08d49458b9f 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -145,6 +145,13 @@ class MissingFieldException : public EnvoyException { MissingFieldException(const std::string& field_name, const Protobuf::Message& message); }; +class TypeUtil { +public: + static absl::string_view typeUrlToDescriptorFullName(absl::string_view type_url); + + static std::string descriptorFullNameToTypeUrl(absl::string_view type); +}; + class RepeatedPtrUtil { public: static std::string join(const Protobuf::RepeatedPtrField& source, diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 01e83ce35caeb..208f79c5eccc0 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -106,9 +106,6 @@ constexpr const char* runtime_features[] = { constexpr const char* disabled_runtime_features[] = { // v2 is fatal-by-default. "envoy.test_only.broken_in_production.enable_deprecated_v2_api", - // Allow Envoy to upgrade or downgrade version of type url, should be removed when support for - // v2 url is removed from codebase. - "envoy.reloadable_features.enable_type_url_downgrade_and_upgrade", // TODO(alyssawilk) flip true after the release. "envoy.reloadable_features.new_tcp_connection_pool", // TODO(asraa) flip to true in a separate PR to enable the new JSON by default. diff --git a/test/common/config/api_type_oracle_test.cc b/test/common/config/api_type_oracle_test.cc index a2454953c3bb8..327d4dc32e541 100644 --- a/test/common/config/api_type_oracle_test.cc +++ b/test/common/config/api_type_oracle_test.cc @@ -27,9 +27,6 @@ TEST(ApiTypeOracleTest, All) { EXPECT_EQ(envoy::config::filter::http::ip_tagging::v2::IPTagging::descriptor()->full_name(), ApiTypeOracle::getEarlierVersionMessageTypeName(v3_config.GetDescriptor()->full_name()) .value()); - EXPECT_EQ("envoy.config.filter.http.ip_tagging.v2.IPTagging", - TypeUtil::typeUrlToDescriptorFullName( - "type.googleapis.com/envoy.config.filter.http.ip_tagging.v2.IPTagging")); } } // namespace diff --git a/test/common/config/grpc_mux_impl_test.cc b/test/common/config/grpc_mux_impl_test.cc index a3701c919fcca..8e1ac0945f698 100644 --- a/test/common/config/grpc_mux_impl_test.cc +++ b/test/common/config/grpc_mux_impl_test.cc @@ -24,7 +24,6 @@ #include "test/test_common/logging.h" #include "test/test_common/resources.h" #include "test/test_common/simulated_time_system.h" -#include "test/test_common/test_runtime.h" #include "test/test_common/test_time.h" #include "test/test_common/utility.h" @@ -870,89 +869,6 @@ TEST_F(GrpcMuxImplTest, BadLocalInfoEmptyNodeName) { "--service-node and --service-cluster options."); } -// Send discovery request with v2 resource type_url, receive discovery response with v3 resource -// type_url. -TEST_F(GrpcMuxImplTest, WatchV2ResourceV3) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.enable_type_url_downgrade_and_upgrade", "true"}}); - setup(); - - InSequence s; - const std::string& v2_type_url = Config::TypeUrl::get().ClusterLoadAssignment; - const std::string& v3_type_url = - Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); - TestUtility::TestOpaqueResourceDecoderImpl - resource_decoder("cluster_name"); - auto foo_sub = grpc_mux_->addWatch(v2_type_url, {}, callbacks_, resource_decoder, {}); - EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); - expectSendMessage(v2_type_url, {}, "", true); - grpc_mux_->start(); - - { - auto response = std::make_unique(); - response->set_type_url(v3_type_url); - response->set_version_info("1"); - envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; - load_assignment.set_cluster_name("x"); - response->add_resources()->PackFrom(load_assignment); - EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) - .WillOnce(Invoke([&load_assignment](const std::vector& resources, - const std::string&) { - EXPECT_EQ(1, resources.size()); - const auto& expected_assignment = - dynamic_cast( - resources[0].get().resource()); - EXPECT_TRUE(TestUtility::protoEqual(expected_assignment, load_assignment)); - })); - expectSendMessage(v2_type_url, {}, "1"); - grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); - } -} - -// Send discovery request with v3 resource type_url, receive discovery response with v2 resource -// type_url. -TEST_F(GrpcMuxImplTest, WatchV3ResourceV2) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.enable_type_url_downgrade_and_upgrade", "true"}}); - setup(); - - InSequence s; - const std::string& v2_type_url = Config::TypeUrl::get().ClusterLoadAssignment; - const std::string& v3_type_url = - Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); - TestUtility::TestOpaqueResourceDecoderImpl - resource_decoder("cluster_name"); - auto foo_sub = grpc_mux_->addWatch(v3_type_url, {}, callbacks_, resource_decoder, {}); - EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); - expectSendMessage(v3_type_url, {}, "", true); - grpc_mux_->start(); - - { - - auto response = std::make_unique(); - response->set_type_url(v2_type_url); - response->set_version_info("1"); - envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; - load_assignment.set_cluster_name("x"); - response->add_resources()->PackFrom(load_assignment); - EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) - .WillOnce(Invoke([&load_assignment](const std::vector& resources, - const std::string&) { - EXPECT_EQ(1, resources.size()); - const auto& expected_assignment = - dynamic_cast( - resources[0].get().resource()); - EXPECT_TRUE(TestUtility::protoEqual(expected_assignment, load_assignment)); - })); - expectSendMessage(v3_type_url, {}, "1"); - grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); - } -} - } // namespace } // namespace Config } // namespace Envoy diff --git a/test/common/config/new_grpc_mux_impl_test.cc b/test/common/config/new_grpc_mux_impl_test.cc index 00fb2770273a3..3240986c1075e 100644 --- a/test/common/config/new_grpc_mux_impl_test.cc +++ b/test/common/config/new_grpc_mux_impl_test.cc @@ -22,7 +22,6 @@ #include "test/test_common/logging.h" #include "test/test_common/resources.h" #include "test/test_common/simulated_time_system.h" -#include "test/test_common/test_runtime.h" #include "test/test_common/test_time.h" #include "test/test_common/utility.h" @@ -362,104 +361,6 @@ TEST_F(NewGrpcMuxImplTest, ConfigUpdateWithNotFoundResponse) { response->mutable_resources()->at(0).add_aliases("prefix/domain1.test"); } -// Watch v2 resource type_url, receive discovery response with v3 resource type_url. -TEST_F(NewGrpcMuxImplTest, V3ResourceResponseV2ResourceWatch) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.enable_type_url_downgrade_and_upgrade", "true"}}); - setup(); - - // Watch for v2 resource type_url. - const std::string& v2_type_url = Config::TypeUrl::get().ClusterLoadAssignment; - const std::string& v3_type_url = - Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); - auto watch = grpc_mux_->addWatch(v2_type_url, {}, callbacks_, resource_decoder_, {}); - - EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); - // Cluster is not watched, v3 resource is rejected. - grpc_mux_->start(); - { - auto unexpected_response = - std::make_unique(); - envoy::config::cluster::v3::Cluster cluster; - unexpected_response->set_type_url(Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)); - unexpected_response->set_system_version_info("0"); - unexpected_response->add_resources()->mutable_resource()->PackFrom(cluster); - EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "0")).Times(0); - grpc_mux_->onDiscoveryResponse(std::move(unexpected_response), control_plane_stats_); - } - // Cluster is not watched, v2 resource is rejected. - { - auto unexpected_response = - std::make_unique(); - envoy::config::cluster::v3::Cluster cluster; - unexpected_response->set_type_url(Config::TypeUrl::get().Cluster); - unexpected_response->set_system_version_info("0"); - unexpected_response->add_resources()->mutable_resource()->PackFrom(cluster); - EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "0")).Times(0); - grpc_mux_->onDiscoveryResponse(std::move(unexpected_response), control_plane_stats_); - } - // ClusterLoadAssignment v2 is watched, v3 resource will be accepted. - { - auto response = std::make_unique(); - response->set_system_version_info("1"); - envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; - load_assignment.set_cluster_name("x"); - response->add_resources()->mutable_resource()->PackFrom(load_assignment); - // Send response that contains resource with v3 type url. - response->set_type_url(v3_type_url); - EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "1")) - .WillOnce(Invoke([&load_assignment](const std::vector& added_resources, - const Protobuf::RepeatedPtrField&, - const std::string&) { - EXPECT_EQ(1, added_resources.size()); - EXPECT_TRUE( - TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); - })); - grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); - } -} - -// Watch v3 resource type_url, receive discovery response with v2 resource type_url. -TEST_F(NewGrpcMuxImplTest, V2ResourceResponseV3ResourceWatch) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.enable_type_url_downgrade_and_upgrade", "true"}}); - setup(); - - // Watch for v3 resource type_url. - const std::string& v3_type_url = - Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); - const std::string& v2_type_url = Config::TypeUrl::get().ClusterLoadAssignment; - auto watch = grpc_mux_->addWatch(v3_type_url, {}, callbacks_, resource_decoder_, {}); - - EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); - - grpc_mux_->start(); - // ClusterLoadAssignment v3 is watched, v2 resource will be accepted. - { - auto response = std::make_unique(); - response->set_system_version_info("1"); - envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; - load_assignment.set_cluster_name("x"); - response->add_resources()->mutable_resource()->PackFrom(load_assignment); - // Send response that contains resource with v3 type url. - response->set_type_url(v2_type_url); - EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "1")) - .WillOnce(Invoke([&load_assignment](const std::vector& added_resources, - const Protobuf::RepeatedPtrField&, - const std::string&) { - EXPECT_EQ(1, added_resources.size()); - EXPECT_TRUE( - TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); - })); - grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); - } -} - // Validate basic gRPC mux subscriptions to xdstp:// glob collections. TEST_F(NewGrpcMuxImplTest, XdsTpGlobCollection) { setup(); diff --git a/test/common/protobuf/BUILD b/test/common/protobuf/BUILD index e022b00f52b01..1118273fdbac0 100644 --- a/test/common/protobuf/BUILD +++ b/test/common/protobuf/BUILD @@ -49,14 +49,6 @@ envoy_cc_test( ], ) -envoy_cc_test( - name = "type_util_test", - srcs = ["type_util_test.cc"], - deps = [ - "//source/common/protobuf:type_util_lib", - ], -) - envoy_cc_fuzz_test( name = "value_util_fuzz_test", srcs = ["value_util_fuzz_test.cc"], diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index c183339fe5285..52071d16a7bf9 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -2009,4 +2009,13 @@ TEST(StatusCode, Strings) { ASSERT_EQ("OK", MessageUtil::CodeEnumToString(ProtobufUtil::error::OK)); } +TEST(TypeUtilTest, TypeUrlHelperFunction) { + EXPECT_EQ("envoy.config.filter.http.ip_tagging.v2.IPTagging", + TypeUtil::typeUrlToDescriptorFullName( + "type.googleapis.com/envoy.config.filter.http.ip_tagging.v2.IPTagging")); + EXPECT_EQ( + "type.googleapis.com/envoy.config.filter.http.ip_tagging.v2.IPTagging", + TypeUtil::descriptorFullNameToTypeUrl("envoy.config.filter.http.ip_tagging.v2.IPTagging")); +} + } // namespace Envoy diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index c577f065af332..08e6d8661a9b8 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -368,38 +368,6 @@ TEST_P(AdsIntegrationTest, ResourceNamesOnStreamReset) { compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "1", {"cluster_0"}, {}, {}, true)); } -// Validate that xds can support a mix of v2 and v3 type url. -TEST_P(AdsIntegrationTest, MixV2V3TypeUrlInDiscoveryResponse) { - config_helper_.addRuntimeOverride( - "envoy.reloadable_features.enable_type_url_downgrade_and_upgrade", "true"); - initialize(); - - // Send initial configuration. - // Discovery response with v3 type url. - sendDiscoveryResponse( - Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3), - {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", false); - // Discovery response with v2 type url. - sendDiscoveryResponse( - Config::TypeUrl::get().ClusterLoadAssignment, {buildClusterLoadAssignment("cluster_0")}, - {buildClusterLoadAssignment("cluster_0")}, {}, "1"); - // Discovery response with v3 type url. - sendDiscoveryResponse( - Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3), - {buildListener("listener_0", "route_config_0")}, - {buildListener("listener_0", "route_config_0")}, {}, "1", false); - // Discovery response with v2 type url. - sendDiscoveryResponse( - Config::TypeUrl::get().RouteConfiguration, {buildRouteConfig("route_config_0", "cluster_0")}, - {buildRouteConfig("route_config_0", "cluster_0")}, {}, "1"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); - - // Validate that we can process a request. - makeSingleRequest(); -} - // Validate that the request with duplicate listeners is rejected. TEST_P(AdsIntegrationTest, DuplicateWarmingListeners) { initialize(); From 93753017cb8c64ba92536b755ddf756df377c6c3 Mon Sep 17 00:00:00 2001 From: Long Dai Date: Mon, 10 May 2021 18:47:06 +0800 Subject: [PATCH 180/209] docs: record distroless images (#16359) * docs: record distroless images Signed-off-by: Long Dai --- docs/conf.py | 10 +++++++--- docs/root/start/install.rst | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6524873d43aa9..08acd7a903beb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -82,11 +82,15 @@ def setup(app): # Setup global substitutions if 'pre-release' in release_level: - substitutions = [('|envoy_docker_image|', 'envoy-dev:{}'.format(blob_sha)), - ('|envoy_windows_docker_image|', 'envoy-windows-dev:{}'.format(blob_sha))] + substitutions = [ + ('|envoy_docker_image|', 'envoy-dev:{}'.format(blob_sha)), + ('|envoy_windows_docker_image|', 'envoy-windows-dev:{}'.format(blob_sha)), + ('|envoy_distroless_docker_image|', 'envoy-distroless-dev:{}'.format(blob_sha)) + ] else: substitutions = [('|envoy_docker_image|', 'envoy:{}'.format(blob_sha)), - ('|envoy_windows_docker_image|', 'envoy-windows:{}'.format(blob_sha))] + ('|envoy_windows_docker_image|', 'envoy-windows:{}'.format(blob_sha)), + ('|envoy_distroless_docker_image|', 'envoy-distroless:{}'.format(blob_sha))] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/root/start/install.rst b/docs/root/start/install.rst index d83d4643fd4ec..599883fe54aba 100644 --- a/docs/root/start/install.rst +++ b/docs/root/start/install.rst @@ -159,6 +159,13 @@ The following commands will pull and show the Envoy version of current images. $ docker pull envoyproxy/|envoy_docker_image| $ docker run --rm envoyproxy/|envoy_docker_image| --version + .. tab:: Envoy (distroless) + + .. substitution-code-block:: console + + $ docker pull envoyproxy/|envoy_distroless_docker_image| + $ docker run --rm envoyproxy/|envoy_distroless_docker_image| --version + .. tab:: Get Envoy .. code-block:: console @@ -201,6 +208,12 @@ The following table shows the available Docker images - |DOCKER_IMAGE_TAG_NAME| - - + * - `envoyproxy/envoy-distroless `_ + - Release binary with symbols stripped on top of a distroless base. + - |DOCKER_IMAGE_TAG_NAME| + - + - + - * - `envoyproxy/envoy-alpine `_ - Release binary with symbols stripped on top of a **glibc** alpine base. - |DOCKER_IMAGE_TAG_NAME| @@ -225,6 +238,12 @@ The following table shows the available Docker images - - latest - latest + * - `envoyproxy/envoy-distroless-dev `_ + - Release binary with symbols stripped on top of a distroless base. + - + - + - latest + - * - `envoyproxy/envoy-alpine-dev `_ - Release binary with symbols stripped on top of a **glibc** alpine base. - From 049bdb3aaa14d80016ac121c13550d1a5e0e3fd3 Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Mon, 10 May 2021 17:47:43 -0700 Subject: [PATCH 181/209] grid: Rename alternate_protocols_cache.{h,cc} to alternate_protocols_cache_impl.{h,cc} (#16424) grid: Rename alternate_protocols_cache.{h,cc} to alternate_protocols_cache_impl.{h,cc} Risk Level: Low Testing: N/A - rename only Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Ryan Hamilton --- include/envoy/http/BUILD | 6 ++ .../envoy}/http/alternate_protocols_cache.h | 74 ++++++++++--------- source/common/http/BUILD | 5 +- .../common/http/alternate_protocols_cache.cc | 51 ------------- .../http/alternate_protocols_cache_impl.cc | 53 +++++++++++++ .../http/alternate_protocols_cache_impl.h | 44 +++++++++++ source/common/http/conn_pool_grid.cc | 8 +- source/common/http/conn_pool_grid.h | 6 +- .../common/upstream/cluster_manager_impl.cc | 2 +- test/common/http/BUILD | 4 +- ...=> alternate_protocols_cache_impl_test.cc} | 40 +++++----- test/common/http/conn_pool_grid_test.cc | 47 ++++++------ 12 files changed, 200 insertions(+), 140 deletions(-) rename {source/common => include/envoy}/http/alternate_protocols_cache.h (56%) delete mode 100644 source/common/http/alternate_protocols_cache.cc create mode 100644 source/common/http/alternate_protocols_cache_impl.cc create mode 100644 source/common/http/alternate_protocols_cache_impl.h rename test/common/http/{alternate_protocols_cache_test.cc => alternate_protocols_cache_impl_test.cc} (54%) diff --git a/include/envoy/http/BUILD b/include/envoy/http/BUILD index 0e41b02ffd76f..7b97f7089cc8a 100644 --- a/include/envoy/http/BUILD +++ b/include/envoy/http/BUILD @@ -8,6 +8,12 @@ licenses(["notice"]) # Apache 2 envoy_package() +envoy_cc_library( + name = "alternate_protocols_cache_interface", + hdrs = ["alternate_protocols_cache.h"], + deps = ["//include/envoy/common:time_interface"], +) + envoy_cc_library( name = "api_listener_interface", hdrs = ["api_listener.h"], diff --git a/source/common/http/alternate_protocols_cache.h b/include/envoy/http/alternate_protocols_cache.h similarity index 56% rename from source/common/http/alternate_protocols_cache.h rename to include/envoy/http/alternate_protocols_cache.h index d879ff7f23b13..667a2163a9ab9 100644 --- a/source/common/http/alternate_protocols_cache.h +++ b/include/envoy/http/alternate_protocols_cache.h @@ -13,13 +13,17 @@ namespace Envoy { namespace Http { -// Tracks alternate protocols that can be used to make an HTTP connection to an origin server. -// See https://tools.ietf.org/html/rfc7838 for HTTP Alternate Services and -// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04 for the -// "HTTPS" DNS resource record. +/** + * Tracks alternate protocols that can be used to make an HTTP connection to an origin server. + * See https://tools.ietf.org/html/rfc7838 for HTTP Alternate Services and + * https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04 for the + * "HTTPS" DNS resource record. + */ class AlternateProtocolsCache { public: - // Represents an HTTP origin to be connected too. + /** + * Represents an HTTP origin to be connected too. + */ struct Origin { public: Origin(absl::string_view scheme, absl::string_view hostname, uint32_t port); @@ -56,7 +60,9 @@ class AlternateProtocolsCache { uint32_t port_{}; }; - // Represents an alternative protocol that can be used to connect to an origin. + /** + * Represents an alternative protocol that can be used to connect to an origin. + */ struct AlternateProtocol { public: AlternateProtocol(absl::string_view alpn, absl::string_view hostname, uint32_t port); @@ -73,34 +79,34 @@ class AlternateProtocolsCache { uint32_t port_; }; - explicit AlternateProtocolsCache(TimeSource& time_source); - - // Sets the possible alternative protocols which can be used to connect to the - // specified origin. Expires after the specified expiration time. - void setAlternatives(const Origin& origin, const std::vector& protocols, - const MonotonicTime& expiration); - - // Returns the possible alternative protocols which can be used to connect to the - // specified origin, or nullptr if not alternatives are found. The returned pointer - // is owned by the AlternateProtocolsCache and is valid until the next operation on - // AlternateProtocolsCache. - OptRef> findAlternatives(const Origin& origin); - - // Returns the number of entries in the map. - size_t size() const; - -private: - struct Entry { - std::vector protocols_; - MonotonicTime expiration_; - }; - - // Time source used to check expiration of entries. - TimeSource& time_source_; - - // Map from hostname to list of alternate protocols. - // TODO(RyanTheOptimist): Add a limit to the size of this map and evict based on usage. - std::map protocols_; + virtual ~AlternateProtocolsCache() = default; + + /** + * Sets the possible alternative protocols which can be used to connect to the + * specified origin. Expires after the specified expiration time. + * @param origin The origin to set alternate protocols for. + * @param protocols A list of alternate protocols. + * @param expiration The time after which the alternatives are no longer valid. + */ + virtual void setAlternatives(const Origin& origin, + const std::vector& protocols, + const MonotonicTime& expiration) PURE; + + /** + * Returns the possible alternative protocols which can be used to connect to the + * specified origin, or nullptr if not alternatives are found. The returned pointer + * is owned by the AlternateProtocolsCacheImpl and is valid until the next operation on + * AlternateProtocolsCacheImpl. + * @param origin The origin to find alternate protocols for. + * @return An optional list of alternate protocols for the given origin. + */ + virtual OptRef> findAlternatives(const Origin& origin) PURE; + + /** + * Returns the number of entries in the map. + * @return the number if entries in the map. + */ + virtual size_t size() const PURE; }; } // namespace Http diff --git a/source/common/http/BUILD b/source/common/http/BUILD index ab7fd3ba3c6c9..f6e23941dfbc4 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -161,10 +161,11 @@ envoy_cc_library( envoy_cc_library( name = "alternate_protocols_cache", - srcs = ["alternate_protocols_cache.cc"], - hdrs = ["alternate_protocols_cache.h"], + srcs = ["alternate_protocols_cache_impl.cc"], + hdrs = ["alternate_protocols_cache_impl.h"], deps = [ "//include/envoy/common:time_interface", + "//include/envoy/http:alternate_protocols_cache_interface", ], ) diff --git a/source/common/http/alternate_protocols_cache.cc b/source/common/http/alternate_protocols_cache.cc deleted file mode 100644 index dfb5deb1fc95b..0000000000000 --- a/source/common/http/alternate_protocols_cache.cc +++ /dev/null @@ -1,51 +0,0 @@ -#include "common/http/alternate_protocols_cache.h" - -namespace Envoy { -namespace Http { - -AlternateProtocolsCache::AlternateProtocol::AlternateProtocol(absl::string_view alpn, - absl::string_view hostname, - uint32_t port) - : alpn_(alpn), hostname_(hostname), port_(port) {} - -AlternateProtocolsCache::Origin::Origin(absl::string_view scheme, absl::string_view hostname, - uint32_t port) - : scheme_(scheme), hostname_(hostname), port_(port) {} - -AlternateProtocolsCache::AlternateProtocolsCache(TimeSource& time_source) - : time_source_(time_source) {} - -void AlternateProtocolsCache::setAlternatives(const Origin& origin, - const std::vector& protocols, - const MonotonicTime& expiration) { - Entry& entry = protocols_[origin]; - if (entry.protocols_ != protocols) { - entry.protocols_ = protocols; - } - if (entry.expiration_ != expiration) { - entry.expiration_ = expiration; - } -} - -OptRef> -AlternateProtocolsCache::findAlternatives(const Origin& origin) { - auto entry_it = protocols_.find(origin); - if (entry_it == protocols_.end()) { - return makeOptRefFromPtr>( - nullptr); - } - - const Entry& entry = entry_it->second; - if (time_source_.monotonicTime() > entry.expiration_) { - // Expire the entry. - protocols_.erase(entry_it); - return makeOptRefFromPtr>( - nullptr); - } - return makeOptRef(entry.protocols_); -} - -size_t AlternateProtocolsCache::size() const { return protocols_.size(); } - -} // namespace Http -} // namespace Envoy diff --git a/source/common/http/alternate_protocols_cache_impl.cc b/source/common/http/alternate_protocols_cache_impl.cc new file mode 100644 index 0000000000000..cab402de191e1 --- /dev/null +++ b/source/common/http/alternate_protocols_cache_impl.cc @@ -0,0 +1,53 @@ +#include "common/http/alternate_protocols_cache_impl.h" + +namespace Envoy { +namespace Http { + +AlternateProtocolsCacheImpl::AlternateProtocol::AlternateProtocol(absl::string_view alpn, + absl::string_view hostname, + uint32_t port) + : alpn_(alpn), hostname_(hostname), port_(port) {} + +AlternateProtocolsCacheImpl::Origin::Origin(absl::string_view scheme, absl::string_view hostname, + uint32_t port) + : scheme_(scheme), hostname_(hostname), port_(port) {} + +AlternateProtocolsCacheImpl::AlternateProtocolsCacheImpl(TimeSource& time_source) + : time_source_(time_source) {} + +AlternateProtocolsCacheImpl::~AlternateProtocolsCacheImpl() = default; + +void AlternateProtocolsCacheImpl::setAlternatives(const Origin& origin, + const std::vector& protocols, + const MonotonicTime& expiration) { + Entry& entry = protocols_[origin]; + if (entry.protocols_ != protocols) { + entry.protocols_ = protocols; + } + if (entry.expiration_ != expiration) { + entry.expiration_ = expiration; + } +} + +OptRef> +AlternateProtocolsCacheImpl::findAlternatives(const Origin& origin) { + auto entry_it = protocols_.find(origin); + if (entry_it == protocols_.end()) { + return makeOptRefFromPtr>( + nullptr); + } + + const Entry& entry = entry_it->second; + if (time_source_.monotonicTime() > entry.expiration_) { + // Expire the entry. + protocols_.erase(entry_it); + return makeOptRefFromPtr>( + nullptr); + } + return makeOptRef(entry.protocols_); +} + +size_t AlternateProtocolsCacheImpl::size() const { return protocols_.size(); } + +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/alternate_protocols_cache_impl.h b/source/common/http/alternate_protocols_cache_impl.h new file mode 100644 index 0000000000000..17f67545c3e15 --- /dev/null +++ b/source/common/http/alternate_protocols_cache_impl.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include + +#include "envoy/common/optref.h" +#include "envoy/common/time.h" +#include "envoy/http/alternate_protocols_cache.h" + +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Http { + +// An implementation of AlternateProtocolsCache. +class AlternateProtocolsCacheImpl : public AlternateProtocolsCache { +public: + explicit AlternateProtocolsCacheImpl(TimeSource& time_source); + ~AlternateProtocolsCacheImpl() override; + + // AlternateProtocolsCache + void setAlternatives(const Origin& origin, const std::vector& protocols, + const MonotonicTime& expiration) override; + OptRef> findAlternatives(const Origin& origin) override; + size_t size() const override; + +private: + struct Entry { + std::vector protocols_; + MonotonicTime expiration_; + }; + + // Time source used to check expiration of entries. + TimeSource& time_source_; + + // Map from hostname to list of alternate protocols. + // TODO(RyanTheOptimist): Add a limit to the size of this map and evict based on usage. + std::map protocols_; +}; + +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc index 3613d787a4959..e8e5f53e72b77 100644 --- a/source/common/http/conn_pool_grid.cc +++ b/source/common/http/conn_pool_grid.cc @@ -192,7 +192,7 @@ ConnectivityGrid::ConnectivityGrid( const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options, Upstream::ClusterConnectivityState& state, TimeSource& time_source, - OptRef alternate_protocols, + OptRef alternate_protocols, std::chrono::milliseconds next_attempt_duration, ConnectivityOptions connectivity_options) : dispatcher_(dispatcher), random_generator_(random_generator), host_(host), priority_(priority), options_(options), transport_socket_options_(transport_socket_options), @@ -351,8 +351,8 @@ bool ConnectivityGrid::shouldAttemptHttp3() { } uint32_t port = host_->address()->ip()->port(); // TODO(RyanTheOptimist): Figure out how scheme gets plumbed in here. - AlternateProtocolsCache::Origin origin("https", host_->hostname(), port); - OptRef> protocols = + AlternateProtocolsCacheImpl::Origin origin("https", host_->hostname(), port); + OptRef> protocols = alternate_protocols_->findAlternatives(origin); if (!protocols.has_value()) { ENVOY_LOG(trace, "No alternate protocols available for host '{}', skipping HTTP/3.", @@ -360,7 +360,7 @@ bool ConnectivityGrid::shouldAttemptHttp3() { return false; } - for (const AlternateProtocolsCache::AlternateProtocol& protocol : protocols.ref()) { + for (const AlternateProtocolsCacheImpl::AlternateProtocol& protocol : protocols.ref()) { // TODO(RyanTheOptimist): Handle alternate protocols which change hostname or port. if (!protocol.hostname_.empty() || protocol.port_ != port) { ENVOY_LOG(trace, diff --git a/source/common/http/conn_pool_grid.h b/source/common/http/conn_pool_grid.h index 6f3903ed966c4..d9391d8087187 100644 --- a/source/common/http/conn_pool_grid.h +++ b/source/common/http/conn_pool_grid.h @@ -1,6 +1,6 @@ #pragma once -#include "common/http/alternate_protocols_cache.h" +#include "common/http/alternate_protocols_cache_impl.h" #include "common/http/conn_pool_base.h" #include "common/http/http3_status_tracker.h" @@ -132,7 +132,7 @@ class ConnectivityGrid : public ConnectionPool::Instance, const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options, Upstream::ClusterConnectivityState& state, TimeSource& time_source, - OptRef alternate_protocols, + OptRef alternate_protocols, std::chrono::milliseconds next_attempt_duration, ConnectivityOptions connectivity_options); ~ConnectivityGrid() override; @@ -192,7 +192,7 @@ class ConnectivityGrid : public ConnectionPool::Instance, TimeSource& time_source_; Http3StatusTracker http3_status_tracker_; // TODO(RyanTheOptimist): Make the alternate_protocols_ member non-optional. - OptRef alternate_protocols_; + OptRef alternate_protocols_; // Tracks how many drains are needed before calling drain callbacks. This is // set to the number of pools when the first drain callbacks are added, and diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index b029e4e94d126..84825e941ab29 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -1523,7 +1523,7 @@ Http::ConnectionPool::InstancePtr ProdClusterManagerFactory::allocateConnPool( {Http::Protocol::Http11, Http::Protocol::Http2, Http::Protocol::Http3})); #ifdef ENVOY_ENABLE_QUIC // TODO(RyanTheOptimist): Plumb an actual alternate protocols cache. - auto alternate_protocols = makeOptRefFromPtr(nullptr); + auto alternate_protocols = makeOptRefFromPtr(nullptr); Envoy::Http::ConnectivityGrid::ConnectivityOptions coptions{protocols}; return std::make_unique( dispatcher, api_.randomGenerator(), host, priority, options, transport_socket_options, diff --git a/test/common/http/BUILD b/test/common/http/BUILD index f4beec13a489b..6a4aae6d35d62 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -451,8 +451,8 @@ envoy_cc_test( ) envoy_cc_test( - name = "alternate_protocols_cache_test", - srcs = ["alternate_protocols_cache_test.cc"], + name = "alternate_protocols_cache_impl_test", + srcs = ["alternate_protocols_cache_impl_test.cc"], deps = [ ":common_lib", "//source/common/http:alternate_protocols_cache", diff --git a/test/common/http/alternate_protocols_cache_test.cc b/test/common/http/alternate_protocols_cache_impl_test.cc similarity index 54% rename from test/common/http/alternate_protocols_cache_test.cc rename to test/common/http/alternate_protocols_cache_impl_test.cc index 8891827fa3d1d..dc0f95848d2b3 100644 --- a/test/common/http/alternate_protocols_cache_test.cc +++ b/test/common/http/alternate_protocols_cache_impl_test.cc @@ -1,4 +1,4 @@ -#include "common/http/alternate_protocols_cache.h" +#include "common/http/alternate_protocols_cache_impl.h" #include "test/test_common/simulated_time_system.h" @@ -8,11 +8,11 @@ namespace Envoy { namespace Http { namespace { -class AlternateProtocolsCacheTest : public testing::Test, public Event::TestUsingSimulatedTime { +class AlternateProtocolsCacheImplTest : public testing::Test, public Event::TestUsingSimulatedTime { public: - AlternateProtocolsCacheTest() : protocols_(simTime()) {} + AlternateProtocolsCacheImplTest() : protocols_(simTime()) {} - AlternateProtocolsCache protocols_; + AlternateProtocolsCacheImpl protocols_; const std::string hostname1_ = "hostname1"; const std::string hostname2_ = "hostname2"; const uint32_t port1_ = 1; @@ -23,49 +23,49 @@ class AlternateProtocolsCacheTest : public testing::Test, public Event::TestUsin const std::string alpn1_ = "alpn1"; const std::string alpn2_ = "alpn2"; - const AlternateProtocolsCache::Origin origin1_ = {https_, hostname1_, port1_}; - const AlternateProtocolsCache::Origin origin2_ = {https_, hostname2_, port2_}; + const AlternateProtocolsCacheImpl::Origin origin1_ = {https_, hostname1_, port1_}; + const AlternateProtocolsCacheImpl::Origin origin2_ = {https_, hostname2_, port2_}; - const AlternateProtocolsCache::AlternateProtocol protocol1_ = {alpn1_, hostname1_, port1_}; - const AlternateProtocolsCache::AlternateProtocol protocol2_ = {alpn2_, hostname2_, port2_}; + const AlternateProtocolsCacheImpl::AlternateProtocol protocol1_ = {alpn1_, hostname1_, port1_}; + const AlternateProtocolsCacheImpl::AlternateProtocol protocol2_ = {alpn2_, hostname2_, port2_}; - const std::vector protocols1_ = {protocol1_}; - const std::vector protocols2_ = {protocol2_}; + const std::vector protocols1_ = {protocol1_}; + const std::vector protocols2_ = {protocol2_}; const MonotonicTime expiration1_ = simTime().monotonicTime() + Seconds(5); const MonotonicTime expiration2_ = simTime().monotonicTime() + Seconds(10); }; -TEST_F(AlternateProtocolsCacheTest, Init) { EXPECT_EQ(0, protocols_.size()); } +TEST_F(AlternateProtocolsCacheImplTest, Init) { EXPECT_EQ(0, protocols_.size()); } -TEST_F(AlternateProtocolsCacheTest, SetAlternatives) { +TEST_F(AlternateProtocolsCacheImplTest, SetAlternatives) { EXPECT_EQ(0, protocols_.size()); protocols_.setAlternatives(origin1_, protocols1_, expiration1_); EXPECT_EQ(1, protocols_.size()); } -TEST_F(AlternateProtocolsCacheTest, FindAlternatives) { +TEST_F(AlternateProtocolsCacheImplTest, FindAlternatives) { protocols_.setAlternatives(origin1_, protocols1_, expiration1_); - OptRef> protocols = + OptRef> protocols = protocols_.findAlternatives(origin1_); ASSERT_TRUE(protocols.has_value()); EXPECT_EQ(protocols1_, protocols.ref()); } -TEST_F(AlternateProtocolsCacheTest, FindAlternativesAfterReplacement) { +TEST_F(AlternateProtocolsCacheImplTest, FindAlternativesAfterReplacement) { protocols_.setAlternatives(origin1_, protocols1_, expiration1_); protocols_.setAlternatives(origin1_, protocols2_, expiration2_); - OptRef> protocols = + OptRef> protocols = protocols_.findAlternatives(origin1_); ASSERT_TRUE(protocols.has_value()); EXPECT_EQ(protocols2_, protocols.ref()); EXPECT_NE(protocols1_, protocols.ref()); } -TEST_F(AlternateProtocolsCacheTest, FindAlternativesForMultipleOrigins) { +TEST_F(AlternateProtocolsCacheImplTest, FindAlternativesForMultipleOrigins) { protocols_.setAlternatives(origin1_, protocols1_, expiration1_); protocols_.setAlternatives(origin2_, protocols2_, expiration2_); - OptRef> protocols = + OptRef> protocols = protocols_.findAlternatives(origin1_); ASSERT_TRUE(protocols.has_value()); EXPECT_EQ(protocols1_, protocols.ref()); @@ -74,10 +74,10 @@ TEST_F(AlternateProtocolsCacheTest, FindAlternativesForMultipleOrigins) { EXPECT_EQ(protocols2_, protocols.ref()); } -TEST_F(AlternateProtocolsCacheTest, FindAlternativesAfterExpiration) { +TEST_F(AlternateProtocolsCacheImplTest, FindAlternativesAfterExpiration) { protocols_.setAlternatives(origin1_, protocols1_, expiration1_); simTime().setMonotonicTime(expiration1_ + Seconds(1)); - OptRef> protocols = + OptRef> protocols = protocols_.findAlternatives(origin1_); ASSERT_FALSE(protocols.has_value()); EXPECT_EQ(0, protocols_.size()); diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index 4350e46a9efc1..d2cd280a3bd85 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -1,4 +1,4 @@ -#include "common/http/alternate_protocols_cache.h" +#include "common/http/alternate_protocols_cache_impl.h" #include "common/http/conn_pool_grid.h" #include "test/common/http/common.h" @@ -102,23 +102,24 @@ class ConnectivityGridTestBase : public Event::TestUsingSimulatedTime, public te grid_(dispatcher_, random_, Upstream::makeTestHost(cluster_, "hostname", "tcp://127.0.0.1:9000", simTime()), Upstream::ResourcePriority::Default, socket_options_, transport_socket_options_, - state_, simTime(), maybeCreateAlternateProtocolsCache(use_alternate_protocols), + state_, simTime(), maybeCreateAlternateProtocolsCacheImpl(use_alternate_protocols), std::chrono::milliseconds(300), options_), host_(grid_.host()) { grid_.info_ = &info_; grid_.encoder_ = &encoder_; } - OptRef maybeCreateAlternateProtocolsCache(bool use_alternate_protocols) { + OptRef + maybeCreateAlternateProtocolsCacheImpl(bool use_alternate_protocols) { if (!use_alternate_protocols) { - return makeOptRefFromPtr(nullptr); + return makeOptRefFromPtr(nullptr); } return alternate_protocols_; } void addHttp3AlternateProtocol() { - AlternateProtocolsCache::Origin origin("https", "hostname", 9000); - const std::vector protocols = { + AlternateProtocolsCacheImpl::Origin origin("https", "hostname", 9000); + const std::vector protocols = { {"h3-29", "", origin.port_}}; alternate_protocols_.setAlternatives(origin, protocols, simTime().monotonicTime() + Seconds(5)); } @@ -130,7 +131,7 @@ class ConnectivityGridTestBase : public Event::TestUsingSimulatedTime, public te NiceMock dispatcher_; std::shared_ptr cluster_{new NiceMock()}; NiceMock random_; - AlternateProtocolsCache alternate_protocols_; + AlternateProtocolsCacheImpl alternate_protocols_; ConnectivityGridForTest grid_; Upstream::HostDescriptionConstSharedPtr host_; @@ -148,9 +149,9 @@ class ConnectivityGridTest : public ConnectivityGridTestBase { }; // Tests of the Grid in which an alternate protocols cache is configured. -class ConnectivityGridWithAlternateProtocolsCacheTest : public ConnectivityGridTestBase { +class ConnectivityGridWithAlternateProtocolsCacheImplTest : public ConnectivityGridTestBase { public: - ConnectivityGridWithAlternateProtocolsCacheTest() : ConnectivityGridTestBase(true) {} + ConnectivityGridWithAlternateProtocolsCacheImplTest() : ConnectivityGridTestBase(true) {} }; // Test the first pool successfully connecting. @@ -474,7 +475,7 @@ TEST_F(ConnectivityGridTest, SuccessAfterBroken) { } // Test the HTTP/3 pool successfully connecting when HTTP/3 is available. -TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, Success) { +TEST_F(ConnectivityGridWithAlternateProtocolsCacheImplTest, Success) { addHttp3AlternateProtocol(); EXPECT_EQ(grid_.first(), nullptr); @@ -490,7 +491,7 @@ TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, Success) { } // Test that when HTTP/3 is not available then the HTTP/3 pool is skipped. -TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithoutHttp3) { +TEST_F(ConnectivityGridWithAlternateProtocolsCacheImplTest, SuccessWithoutHttp3) { EXPECT_EQ(grid_.first(), nullptr); EXPECT_LOG_CONTAINS("trace", @@ -506,9 +507,9 @@ TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithoutHttp3) { } // Test that when HTTP/3 is not available then the HTTP/3 pool is skipped. -TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithExpiredHttp3) { - AlternateProtocolsCache::Origin origin("https", "hostname", 9000); - const std::vector protocols = { +TEST_F(ConnectivityGridWithAlternateProtocolsCacheImplTest, SuccessWithExpiredHttp3) { + AlternateProtocolsCacheImpl::Origin origin("https", "hostname", 9000); + const std::vector protocols = { {"h3-29", "", origin.port_}}; alternate_protocols_.setAlternatives(origin, protocols, simTime().monotonicTime() + Seconds(5)); simTime().setMonotonicTime(simTime().monotonicTime() + Seconds(10)); @@ -529,9 +530,9 @@ TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithExpiredHttp3) // Test that when the alternate protocol specifies a different host, then the HTTP/3 pool is // skipped. -TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithoutHttp3NoMatchingHostname) { - AlternateProtocolsCache::Origin origin("https", "hostname", 9000); - const std::vector protocols = { +TEST_F(ConnectivityGridWithAlternateProtocolsCacheImplTest, SuccessWithoutHttp3NoMatchingHostname) { + AlternateProtocolsCacheImpl::Origin origin("https", "hostname", 9000); + const std::vector protocols = { {"h3-29", "otherhostname", origin.port_}}; alternate_protocols_.setAlternatives(origin, protocols, simTime().monotonicTime() + Seconds(5)); @@ -550,9 +551,9 @@ TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithoutHttp3NoMat // Test that when the alternate protocol specifies a different port, then the HTTP/3 pool is // skipped. -TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithoutHttp3NoMatchingPort) { - AlternateProtocolsCache::Origin origin("https", "hostname", 9000); - const std::vector protocols = { +TEST_F(ConnectivityGridWithAlternateProtocolsCacheImplTest, SuccessWithoutHttp3NoMatchingPort) { + AlternateProtocolsCacheImpl::Origin origin("https", "hostname", 9000); + const std::vector protocols = { {"h3-29", "", origin.port_ + 1}}; alternate_protocols_.setAlternatives(origin, protocols, simTime().monotonicTime() + Seconds(5)); @@ -570,9 +571,9 @@ TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithoutHttp3NoMat } // Test that when the alternate protocol specifies an invalid ALPN, then the HTTP/3 pool is skipped. -TEST_F(ConnectivityGridWithAlternateProtocolsCacheTest, SuccessWithoutHttp3NoMatchingAlpn) { - AlternateProtocolsCache::Origin origin("https", "hostname", 9000); - const std::vector protocols = { +TEST_F(ConnectivityGridWithAlternateProtocolsCacheImplTest, SuccessWithoutHttp3NoMatchingAlpn) { + AlternateProtocolsCacheImpl::Origin origin("https", "hostname", 9000); + const std::vector protocols = { {"http/2", "", origin.port_}}; alternate_protocols_.setAlternatives(origin, protocols, simTime().monotonicTime() + Seconds(5)); From 5afbb4dd36afed5c94e1b62386851045fe3fdb1a Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 11 May 2021 02:54:52 +0100 Subject: [PATCH 182/209] docs: Simplify api build (/cont) (#16417) Signed-off-by: Ryan Northey --- docs/build.sh | 6 ------ {api => docs/root/api-docs}/diagrams/ads.svg | 0 {api => docs/root/api-docs}/diagrams/cds-eds-resources.svg | 0 .../root/api-docs}/diagrams/eds-distinct-stream.svg | 0 {api => docs/root/api-docs}/diagrams/eds-same-stream.svg | 0 {api => docs/root/api-docs}/diagrams/envoy-perf-script.svg | 0 {api => docs/root/api-docs}/diagrams/error-detail-nack.svg | 0 .../root/api-docs}/diagrams/incremental-reconnect.svg | 0 {api => docs/root/api-docs}/diagrams/incremental.svg | 0 {api => docs/root/api-docs}/diagrams/later-ack.svg | 0 {api => docs/root/api-docs}/diagrams/simple-ack.svg | 0 {api => docs/root/api-docs}/diagrams/simple-nack.svg | 0 {api => docs/root/api-docs}/diagrams/stale-requests.svg | 0 {api => docs/root/api-docs}/diagrams/update-race.svg | 0 {api => docs/root/api-docs}/xds_protocol.rst | 0 15 files changed, 6 deletions(-) rename {api => docs/root/api-docs}/diagrams/ads.svg (100%) rename {api => docs/root/api-docs}/diagrams/cds-eds-resources.svg (100%) rename {api => docs/root/api-docs}/diagrams/eds-distinct-stream.svg (100%) rename {api => docs/root/api-docs}/diagrams/eds-same-stream.svg (100%) rename {api => docs/root/api-docs}/diagrams/envoy-perf-script.svg (100%) rename {api => docs/root/api-docs}/diagrams/error-detail-nack.svg (100%) rename {api => docs/root/api-docs}/diagrams/incremental-reconnect.svg (100%) rename {api => docs/root/api-docs}/diagrams/incremental.svg (100%) rename {api => docs/root/api-docs}/diagrams/later-ack.svg (100%) rename {api => docs/root/api-docs}/diagrams/simple-ack.svg (100%) rename {api => docs/root/api-docs}/diagrams/simple-nack.svg (100%) rename {api => docs/root/api-docs}/diagrams/stale-requests.svg (100%) rename {api => docs/root/api-docs}/diagrams/update-race.svg (100%) rename {api => docs/root/api-docs}/xds_protocol.rst (100%) diff --git a/docs/build.sh b/docs/build.sh index df9c9c44f278f..6e0b83731ba28 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -43,7 +43,6 @@ fi SCRIPT_DIR="$(dirname "$0")" SRC_DIR="$(dirname "$SCRIPT_DIR")" ENVOY_SRCDIR="$(realpath "$SRC_DIR")" -API_DIR="${SRC_DIR}"/api CONFIGS_DIR="${SRC_DIR}"/configs BUILD_DIR=build_docs [[ -z "${DOCS_OUTPUT_DIR}" ]] && DOCS_OUTPUT_DIR=generated/docs @@ -132,9 +131,6 @@ function generate_api_rst() { generate_api_rst v3 -# xDS protocol spec. -mkdir -p "${GENERATED_RST_DIR}/api-docs" -cp -f "${API_DIR}"/xds_protocol.rst "${GENERATED_RST_DIR}/api-docs/xds_protocol.rst" # Edge hardening example YAML. mkdir -p "${GENERATED_RST_DIR}"/configuration/best_practices cp -f "${CONFIGS_DIR}"/google-vrp/envoy-edge.yaml "${GENERATED_RST_DIR}"/configuration/best_practices @@ -146,8 +142,6 @@ copy_example_configs () { copy_example_configs -rsync -rav "${API_DIR}/diagrams" "${GENERATED_RST_DIR}/api-docs" - rsync -av \ "${SCRIPT_DIR}"/root/ \ "${SCRIPT_DIR}"/conf.py \ diff --git a/api/diagrams/ads.svg b/docs/root/api-docs/diagrams/ads.svg similarity index 100% rename from api/diagrams/ads.svg rename to docs/root/api-docs/diagrams/ads.svg diff --git a/api/diagrams/cds-eds-resources.svg b/docs/root/api-docs/diagrams/cds-eds-resources.svg similarity index 100% rename from api/diagrams/cds-eds-resources.svg rename to docs/root/api-docs/diagrams/cds-eds-resources.svg diff --git a/api/diagrams/eds-distinct-stream.svg b/docs/root/api-docs/diagrams/eds-distinct-stream.svg similarity index 100% rename from api/diagrams/eds-distinct-stream.svg rename to docs/root/api-docs/diagrams/eds-distinct-stream.svg diff --git a/api/diagrams/eds-same-stream.svg b/docs/root/api-docs/diagrams/eds-same-stream.svg similarity index 100% rename from api/diagrams/eds-same-stream.svg rename to docs/root/api-docs/diagrams/eds-same-stream.svg diff --git a/api/diagrams/envoy-perf-script.svg b/docs/root/api-docs/diagrams/envoy-perf-script.svg similarity index 100% rename from api/diagrams/envoy-perf-script.svg rename to docs/root/api-docs/diagrams/envoy-perf-script.svg diff --git a/api/diagrams/error-detail-nack.svg b/docs/root/api-docs/diagrams/error-detail-nack.svg similarity index 100% rename from api/diagrams/error-detail-nack.svg rename to docs/root/api-docs/diagrams/error-detail-nack.svg diff --git a/api/diagrams/incremental-reconnect.svg b/docs/root/api-docs/diagrams/incremental-reconnect.svg similarity index 100% rename from api/diagrams/incremental-reconnect.svg rename to docs/root/api-docs/diagrams/incremental-reconnect.svg diff --git a/api/diagrams/incremental.svg b/docs/root/api-docs/diagrams/incremental.svg similarity index 100% rename from api/diagrams/incremental.svg rename to docs/root/api-docs/diagrams/incremental.svg diff --git a/api/diagrams/later-ack.svg b/docs/root/api-docs/diagrams/later-ack.svg similarity index 100% rename from api/diagrams/later-ack.svg rename to docs/root/api-docs/diagrams/later-ack.svg diff --git a/api/diagrams/simple-ack.svg b/docs/root/api-docs/diagrams/simple-ack.svg similarity index 100% rename from api/diagrams/simple-ack.svg rename to docs/root/api-docs/diagrams/simple-ack.svg diff --git a/api/diagrams/simple-nack.svg b/docs/root/api-docs/diagrams/simple-nack.svg similarity index 100% rename from api/diagrams/simple-nack.svg rename to docs/root/api-docs/diagrams/simple-nack.svg diff --git a/api/diagrams/stale-requests.svg b/docs/root/api-docs/diagrams/stale-requests.svg similarity index 100% rename from api/diagrams/stale-requests.svg rename to docs/root/api-docs/diagrams/stale-requests.svg diff --git a/api/diagrams/update-race.svg b/docs/root/api-docs/diagrams/update-race.svg similarity index 100% rename from api/diagrams/update-race.svg rename to docs/root/api-docs/diagrams/update-race.svg diff --git a/api/xds_protocol.rst b/docs/root/api-docs/xds_protocol.rst similarity index 100% rename from api/xds_protocol.rst rename to docs/root/api-docs/xds_protocol.rst From 5333b928d8bcffa26ab19bf018369a835f697585 Mon Sep 17 00:00:00 2001 From: Yan Avlasov Date: Mon, 19 Apr 2021 14:39:55 -0400 Subject: [PATCH 183/209] Implement handling of escaped slash characters in URL path Fixes: CVE-2021-29492 Signed-off-by: Yan Avlasov --- .../v3/http_connection_manager.proto | 39 +++- .../v4alpha/http_connection_manager.proto | 39 +++- .../best_practices/_include/edge.yaml | 3 + .../configuration/best_practices/edge.rst | 12 ++ .../http/http_conn_man/runtime.rst | 19 ++ .../http/http_conn_man/stats.rst | 2 + docs/root/version_history/current.rst | 1 + .../v3/http_connection_manager.proto | 39 +++- .../v4alpha/http_connection_manager.proto | 39 +++- source/common/http/conn_manager_config.h | 10 + source/common/http/conn_manager_impl.cc | 21 +- source/common/http/conn_manager_utility.cc | 42 +++- source/common/http/conn_manager_utility.h | 13 +- source/common/http/path_utility.cc | 35 ++++ source/common/http/path_utility.h | 11 ++ .../network/http_connection_manager/config.cc | 48 ++++- .../network/http_connection_manager/config.h | 7 + source/server/admin/admin.h | 6 + .../http/conn_manager_impl_fuzz_test.cc | 6 + test/common/http/conn_manager_impl_test.cc | 97 ++++++++++ .../http/conn_manager_impl_test_base.cc | 25 +++ .../common/http/conn_manager_impl_test_base.h | 11 ++ test/common/http/conn_manager_utility_test.cc | 177 ++++++++++++++++- test/common/http/path_utility_test.cc | 55 ++++++ .../http_connection_manager/config_test.cc | 182 +++++++++++++++++- test/integration/header_integration_test.cc | 169 +++++++++++++++- test/integration/protocol_integration_test.cc | 19 ++ test/mocks/http/mocks.h | 3 + 28 files changed, 1100 insertions(+), 30 deletions(-) diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 13b4755f07f68..e46032daa4269 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -97,6 +97,36 @@ message HttpConnectionManager { ALWAYS_FORWARD_ONLY = 4; } + // Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. + // This operation occurs before URL normalization and the merge slashes transformations if they were enabled. + enum PathWithEscapedSlashesAction { + // Default behavior specific to implementation (i.e. Envoy) of this configuration option. + // Envoy, by default, takes the KEEP_UNCHANGED action. + // NOTE: the implementation may change the default behavior at-will. + IMPLEMENTATION_SPECIFIC_DEFAULT = 0; + + // Keep escaped slashes. + KEEP_UNCHANGED = 1; + + // Reject client request with the 400 status. gRPC requests will be rejected with the INTERNAL (13) error code. + // The "httpN.downstream_rq_failed_path_normalization" counter is incremented for each rejected request. + REJECT_REQUEST = 2; + + // Unescape %2F and %5C sequences and redirect request to the new path if these sequences were present. + // Redirect occurs after path normalization and merge slashes transformations if they were configured. + // NOTE: gRPC requests will be rejected with the INTERNAL (13) error code. + // This option minimizes possibility of path confusion exploits by forcing request with unescaped slashes to + // traverse all parties: downstream client, intermediate proxies, Envoy and upstream server. + // The "httpN.downstream_rq_redirected_with_normalized_path" counter is incremented for each + // redirected request. + UNESCAPE_AND_REDIRECT = 3; + + // Unescape %2F and %5C sequences. + // Note: this option should not be enabled if intermediaries perform path based access control as + // it may lead to path confusion vulnerabilities. + UNESCAPE_AND_FORWARD = 4; + } + // [#next-free-field: 10] message Tracing { option (udpa.annotations.versioning).previous_message_type = @@ -271,7 +301,7 @@ message HttpConnectionManager { type.http.v3.PathTransformation http_filter_transformation = 2; } - reserved 27, 11, 45; + reserved 27, 11; reserved "idle_timeout"; @@ -561,6 +591,13 @@ message HttpConnectionManager { // `HTTP spec `_ and is provided for convenience. bool merge_slashes = 33; + // Action to take when request URL path contains escaped slash sequences (%2F, %2f, %5C and %5c). + // The default value can be overridden by the :ref:`http_connection_manager.path_with_escaped_slashes_action` + // runtime variable. + // The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime + // variable can be used to apply the action to a portion of all requests. + PathWithEscapedSlashesAction path_with_escaped_slashes_action = 45; + // The configuration of the request ID extension. This includes operations such as // generation, validation, and associated tracing operations. If empty, the // :ref:`UuidRequestIdConfig ` diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index a30e21de96ba1..59d0d5c74984a 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -96,6 +96,36 @@ message HttpConnectionManager { ALWAYS_FORWARD_ONLY = 4; } + // Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. + // This operation occurs before URL normalization and the merge slashes transformations if they were enabled. + enum PathWithEscapedSlashesAction { + // Default behavior specific to implementation (i.e. Envoy) of this configuration option. + // Envoy, by default, takes the KEEP_UNCHANGED action. + // NOTE: the implementation may change the default behavior at-will. + IMPLEMENTATION_SPECIFIC_DEFAULT = 0; + + // Keep escaped slashes. + KEEP_UNCHANGED = 1; + + // Reject client request with the 400 status. gRPC requests will be rejected with the INTERNAL (13) error code. + // The "httpN.downstream_rq_failed_path_normalization" counter is incremented for each rejected request. + REJECT_REQUEST = 2; + + // Unescape %2F and %5C sequences and redirect request to the new path if these sequences were present. + // Redirect occurs after path normalization and merge slashes transformations if they were configured. + // NOTE: gRPC requests will be rejected with the INTERNAL (13) error code. + // This option minimizes possibility of path confusion exploits by forcing request with unescaped slashes to + // traverse all parties: downstream client, intermediate proxies, Envoy and upstream server. + // The "httpN.downstream_rq_redirected_with_normalized_path" counter is incremented for each + // redirected request. + UNESCAPE_AND_REDIRECT = 3; + + // Unescape %2F and %5C sequences. + // Note: this option should not be enabled if intermediaries perform path based access control as + // it may lead to path confusion vulnerabilities. + UNESCAPE_AND_FORWARD = 4; + } + // [#next-free-field: 10] message Tracing { option (udpa.annotations.versioning).previous_message_type = @@ -274,7 +304,7 @@ message HttpConnectionManager { type.http.v3.PathTransformation http_filter_transformation = 2; } - reserved 27, 11, 45; + reserved 27, 11; reserved "idle_timeout"; @@ -564,6 +594,13 @@ message HttpConnectionManager { // `HTTP spec `_ and is provided for convenience. bool merge_slashes = 33; + // Action to take when request URL path contains escaped slash sequences (%2F, %2f, %5C and %5c). + // The default value can be overridden by the :ref:`http_connection_manager.path_with_escaped_slashes_action` + // runtime variable. + // The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime + // variable can be used to apply the action to a portion of all requests. + PathWithEscapedSlashesAction path_with_escaped_slashes_action = 45; + // The configuration of the request ID extension. This includes operations such as // generation, validation, and associated tracing operations. If empty, the // :ref:`UuidRequestIdConfig ` diff --git a/docs/root/configuration/best_practices/_include/edge.yaml b/docs/root/configuration/best_practices/_include/edge.yaml index f792ff30b7b7e..d74f572c3625d 100644 --- a/docs/root/configuration/best_practices/_include/edge.yaml +++ b/docs/root/configuration/best_practices/_include/edge.yaml @@ -55,6 +55,9 @@ static_resources: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http use_remote_address: true + normalize_path: true + merge_slashes: true + path_with_escaped_slashes_action: UNESCAPE_AND_REDIRECT common_http_protocol_options: idle_timeout: 3600s # 1 hour headers_with_underscores_action: REJECT_REQUEST diff --git a/docs/root/configuration/best_practices/edge.rst b/docs/root/configuration/best_practices/edge.rst index fdeb376d23b44..293c49b20b30c 100644 --- a/docs/root/configuration/best_practices/edge.rst +++ b/docs/root/configuration/best_practices/edge.rst @@ -27,6 +27,18 @@ HTTP proxies should additionally configure: * :ref:`Listener connection limits. ` * :ref:`Global downstream connection limits `. +If Envoy is configured with RBAC filter or makes route selection based on URL path it is recommended to enable the following path normalization options to minimize probability of path confusion vulnerabilities. Path confusion vulnerabilities occur when parties participating in request use different path representations. + +* Enable :ref:`normalize_path setting `. +* Enable :ref:`merge_slashes setting `. + +Additionally the :ref:`path_with_escaped_slashes_action setting ` should be set according to following recommendations: + +* REJECT_REQUEST if dowstream clients are expected to use rfc3986 compliant normalized paths (i.e. gRPC clients). +* UNESCAPE_AND_REDIRECT if downstream client supports HTTP redirect (i.e. a browser). This option minimizes possibility of path confusion by forcing request to be re-issued with the same path across all parties: downstream client, Envoy and upstream server. Note that gRPC requests will still be rejected with the INTERNAL (13) error code, as gRPC clients do not support redirect. +* KEEP_UNCHANGED for servers that are not rfc3986 compliant and require encoded slashes. +* UNESCAPE_AND_FORWARD for servers that are known to treat escaped and unescaped slashes equivalently. Choosing this option may increase probablity of path confusion vulnerabilities if intermediaries perform path based access control. + The following is a YAML example of the above recommendation (taken from the :ref:`Google VRP ` edge server configuration): diff --git a/docs/root/configuration/http/http_conn_man/runtime.rst b/docs/root/configuration/http/http_conn_man/runtime.rst index 2c104c8065080..e19d3d89acf70 100644 --- a/docs/root/configuration/http/http_conn_man/runtime.rst +++ b/docs/root/configuration/http/http_conn_man/runtime.rst @@ -31,3 +31,22 @@ tracing.random_sampling % of requests that will be randomly traced. See :ref:`here ` for more information. This runtime control is specified in the range 0-10000 and defaults to 10000. Thus, trace sampling can be specified in 0.01% increments. + +.. _config_http_conn_man_runtime_path_with_escaped_slashes_action: + +http_connection_manager.path_with_escaped_slashes_action + Overrides Envoy's default action taken when the + :ref:`path_with_escaped_slashes_action `. + was not specified or set to the IMPLEMENTATION_SPECIFIC_DEFAULT value. Possible values: + + - 2 sets action to the REJECT_REQUEST. + - 3 sets action to the UNESCAPE_AND_REDIRECT. + - 4 sets action to the UNESCAPE_AND_FORWARD. + - all other values set the action to KEEP_UNCHANGED. + +.. _config_http_conn_man_runtime_path_with_escaped_slashes_action_enabled: + +http_connection_manager.path_with_escaped_slashes_action_enabled + % of requests that will be subject to the + :ref:`path_with_escaped_slashes_action `. + action. For all other requests the KEEP_UNCHANGED action will be applied. Defaults to 100. diff --git a/docs/root/configuration/http/http_conn_man/stats.rst b/docs/root/configuration/http/http_conn_man/stats.rst index 21e561f920707..b8d81e7ce7a89 100644 --- a/docs/root/configuration/http/http_conn_man/stats.rst +++ b/docs/root/configuration/http/http_conn_man/stats.rst @@ -51,6 +51,7 @@ statistics: downstream_rq_non_relative_path, Counter, Total requests with a non-relative HTTP path downstream_rq_too_large, Counter, Total requests resulting in a 413 due to buffering an overly large body downstream_rq_completed, Counter, Total requests that resulted in a response (e.g. does not include aborted requests) + downstream_rq_failed_path_normalization, Counter, Total requests redirected due to different original and normalized URL paths or when path normalization failed. This action is configured by setting the :ref:`path_with_escaped_slashes_action ` config option. downstream_rq_1xx, Counter, Total 1xx responses downstream_rq_2xx, Counter, Total 2xx responses downstream_rq_3xx, Counter, Total 3xx responses @@ -62,6 +63,7 @@ statistics: downstream_rq_max_duration_reached, Counter, Total requests closed due to max duration reached downstream_rq_timeout, Counter, Total requests closed due to a timeout on the request path downstream_rq_overload_close, Counter, Total requests closed due to Envoy overload + downstream_rq_redirected_with_normalized_path, Counter, Total requests redirected due to different original and normalized URL paths. This action is configured by setting the :ref:`path_with_escaped_slashes_action ` config option. rs_too_large, Counter, Total response errors due to buffering an overly large body Per user agent statistics diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 10ec44499448b..8719794938235 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -52,6 +52,7 @@ Removed Config or Runtime New Features ------------ +* http: added the ability to :ref:`unescape slash sequences` in the path. Requests with unescaped slashes can be proxied, rejected or redirected to the new unescaped path. By default this feature is disabled. The default behavior can be overridden through :ref:`http_connection_manager.path_with_escaped_slashes_action` runtime variable. This action can be selectively enabled for a portion of requests by setting the :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime variable. * http: added upstream and downstream alpha HTTP/3 support! See :ref:`quic_options ` for downstream and the new http3_protocol_options in :ref:`http_protocol_options ` for upstream HTTP/3. * listener: added ability to change an existing listener's address. * metric service: added support for sending metric tags as labels. This can be enabled by setting the :ref:`emit_tags_as_labels ` field to true. diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index b700545585005..3714c7cca8aa5 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -99,6 +99,36 @@ message HttpConnectionManager { ALWAYS_FORWARD_ONLY = 4; } + // Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. + // This operation occurs before URL normalization and the merge slashes transformations if they were enabled. + enum PathWithEscapedSlashesAction { + // Default behavior specific to implementation (i.e. Envoy) of this configuration option. + // Envoy, by default, takes the KEEP_UNCHANGED action. + // NOTE: the implementation may change the default behavior at-will. + IMPLEMENTATION_SPECIFIC_DEFAULT = 0; + + // Keep escaped slashes. + KEEP_UNCHANGED = 1; + + // Reject client request with the 400 status. gRPC requests will be rejected with the INTERNAL (13) error code. + // The "httpN.downstream_rq_failed_path_normalization" counter is incremented for each rejected request. + REJECT_REQUEST = 2; + + // Unescape %2F and %5C sequences and redirect request to the new path if these sequences were present. + // Redirect occurs after path normalization and merge slashes transformations if they were configured. + // NOTE: gRPC requests will be rejected with the INTERNAL (13) error code. + // This option minimizes possibility of path confusion exploits by forcing request with unescaped slashes to + // traverse all parties: downstream client, intermediate proxies, Envoy and upstream server. + // The "httpN.downstream_rq_redirected_with_normalized_path" counter is incremented for each + // redirected request. + UNESCAPE_AND_REDIRECT = 3; + + // Unescape %2F and %5C sequences. + // Note: this option should not be enabled if intermediaries perform path based access control as + // it may lead to path confusion vulnerabilities. + UNESCAPE_AND_FORWARD = 4; + } + // [#next-free-field: 10] message Tracing { option (udpa.annotations.versioning).previous_message_type = @@ -279,7 +309,7 @@ message HttpConnectionManager { type.http.v3.PathTransformation http_filter_transformation = 2; } - reserved 27, 45; + reserved 27; // Supplies the type of codec that the connection manager should use. CodecType codec_type = 1 [(validate.rules).enum = {defined_only: true}]; @@ -567,6 +597,13 @@ message HttpConnectionManager { // `HTTP spec `_ and is provided for convenience. bool merge_slashes = 33; + // Action to take when request URL path contains escaped slash sequences (%2F, %2f, %5C and %5c). + // The default value can be overridden by the :ref:`http_connection_manager.path_with_escaped_slashes_action` + // runtime variable. + // The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime + // variable can be used to apply the action to a portion of all requests. + PathWithEscapedSlashesAction path_with_escaped_slashes_action = 45; + // The configuration of the request ID extension. This includes operations such as // generation, validation, and associated tracing operations. If empty, the // :ref:`UuidRequestIdConfig ` diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index a30e21de96ba1..59d0d5c74984a 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -96,6 +96,36 @@ message HttpConnectionManager { ALWAYS_FORWARD_ONLY = 4; } + // Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. + // This operation occurs before URL normalization and the merge slashes transformations if they were enabled. + enum PathWithEscapedSlashesAction { + // Default behavior specific to implementation (i.e. Envoy) of this configuration option. + // Envoy, by default, takes the KEEP_UNCHANGED action. + // NOTE: the implementation may change the default behavior at-will. + IMPLEMENTATION_SPECIFIC_DEFAULT = 0; + + // Keep escaped slashes. + KEEP_UNCHANGED = 1; + + // Reject client request with the 400 status. gRPC requests will be rejected with the INTERNAL (13) error code. + // The "httpN.downstream_rq_failed_path_normalization" counter is incremented for each rejected request. + REJECT_REQUEST = 2; + + // Unescape %2F and %5C sequences and redirect request to the new path if these sequences were present. + // Redirect occurs after path normalization and merge slashes transformations if they were configured. + // NOTE: gRPC requests will be rejected with the INTERNAL (13) error code. + // This option minimizes possibility of path confusion exploits by forcing request with unescaped slashes to + // traverse all parties: downstream client, intermediate proxies, Envoy and upstream server. + // The "httpN.downstream_rq_redirected_with_normalized_path" counter is incremented for each + // redirected request. + UNESCAPE_AND_REDIRECT = 3; + + // Unescape %2F and %5C sequences. + // Note: this option should not be enabled if intermediaries perform path based access control as + // it may lead to path confusion vulnerabilities. + UNESCAPE_AND_FORWARD = 4; + } + // [#next-free-field: 10] message Tracing { option (udpa.annotations.versioning).previous_message_type = @@ -274,7 +304,7 @@ message HttpConnectionManager { type.http.v3.PathTransformation http_filter_transformation = 2; } - reserved 27, 11, 45; + reserved 27, 11; reserved "idle_timeout"; @@ -564,6 +594,13 @@ message HttpConnectionManager { // `HTTP spec `_ and is provided for convenience. bool merge_slashes = 33; + // Action to take when request URL path contains escaped slash sequences (%2F, %2f, %5C and %5c). + // The default value can be overridden by the :ref:`http_connection_manager.path_with_escaped_slashes_action` + // runtime variable. + // The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime + // variable can be used to apply the action to a portion of all requests. + PathWithEscapedSlashesAction path_with_escaped_slashes_action = 45; + // The configuration of the request ID extension. This includes operations such as // generation, validation, and associated tracing operations. If empty, the // :ref:`UuidRequestIdConfig ` diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index 1d77ff43474a9..2033da5c321c9 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -49,12 +49,14 @@ namespace Http { COUNTER(downstream_rq_4xx) \ COUNTER(downstream_rq_5xx) \ COUNTER(downstream_rq_completed) \ + COUNTER(downstream_rq_failed_path_normalization) \ COUNTER(downstream_rq_http1_total) \ COUNTER(downstream_rq_http2_total) \ COUNTER(downstream_rq_http3_total) \ COUNTER(downstream_rq_idle_timeout) \ COUNTER(downstream_rq_non_relative_path) \ COUNTER(downstream_rq_overload_close) \ + COUNTER(downstream_rq_redirected_with_normalized_path) \ COUNTER(downstream_rq_response_before_rq_complete) \ COUNTER(downstream_rq_rx_reset) \ COUNTER(downstream_rq_timeout) \ @@ -466,6 +468,14 @@ class ConnectionManagerConfig { * @return LocalReply configuration which supplies mapping for local reply generated by Envoy. */ virtual const LocalReply::LocalReply& localReply() const PURE; + + /** + * @return the action HttpConnectionManager should take when receiving client request + * with URI path containing %2F, %2f, %5c or %5C sequences. + */ + virtual envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + pathWithEscapedSlashesAction() const PURE; }; } // namespace Http } // namespace Envoy diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 44b9016c2ee8e..a9e7205651b6e 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -965,14 +965,31 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapPtr&& he } // Path sanitization should happen before any path access other than the above sanity check. - if (!ConnectionManagerUtility::maybeNormalizePath(*request_headers_, - connection_manager_.config_)) { + const auto action = + ConnectionManagerUtility::maybeNormalizePath(*request_headers_, connection_manager_.config_); + // gRPC requests are rejected if Envoy is configured to redirect post-normalization. This is + // because gRPC clients do not support redirect. + if (action == ConnectionManagerUtility::NormalizePathAction::Reject || + (action == ConnectionManagerUtility::NormalizePathAction::Redirect && + Grpc::Common::hasGrpcContentType(*request_headers_))) { + connection_manager_.stats_.named_.downstream_rq_failed_path_normalization_.inc(); sendLocalReply(Grpc::Common::hasGrpcContentType(*request_headers_), Code::BadRequest, "", nullptr, absl::nullopt, StreamInfo::ResponseCodeDetails::get().PathNormalizationFailed); return; + } else if (action == ConnectionManagerUtility::NormalizePathAction::Redirect) { + connection_manager_.stats_.named_.downstream_rq_redirected_with_normalized_path_.inc(); + sendLocalReply( + false, Code::TemporaryRedirect, "", + [new_path = request_headers_->Path()->value().getStringView()]( + Http::ResponseHeaderMap& response_headers) -> void { + response_headers.addReferenceKey(Http::Headers::get().Location, new_path); + }, + absl::nullopt, StreamInfo::ResponseCodeDetails::get().PathNormalizationFailed); + return; } + ASSERT(action == ConnectionManagerUtility::NormalizePathAction::Continue); auto optional_port = ConnectionManagerUtility::maybeNormalizeHost( *request_headers_, connection_manager_.config_, localPort()); if (optional_port.has_value() && diff --git a/source/common/http/conn_manager_utility.cc b/source/common/http/conn_manager_utility.cc index edfc6dc2a9b47..38747b89f63bd 100644 --- a/source/common/http/conn_manager_utility.cc +++ b/source/common/http/conn_manager_utility.cc @@ -433,20 +433,46 @@ void ConnectionManagerUtility::mutateResponseHeaders(ResponseHeaderMap& response } } -bool ConnectionManagerUtility::maybeNormalizePath(RequestHeaderMap& request_headers, - const ConnectionManagerConfig& config) { +ConnectionManagerUtility::NormalizePathAction +ConnectionManagerUtility::maybeNormalizePath(RequestHeaderMap& request_headers, + const ConnectionManagerConfig& config) { if (!request_headers.Path()) { - return true; // It's as valid as it is going to get. + return NormalizePathAction::Continue; // It's as valid as it is going to get. } - bool is_valid_path = true; - if (config.shouldNormalizePath()) { - is_valid_path = PathUtil::canonicalPath(request_headers); + + NormalizePathAction final_action = NormalizePathAction::Continue; + const auto escaped_slashes_action = config.pathWithEscapedSlashesAction(); + ASSERT(escaped_slashes_action != envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::IMPLEMENTATION_SPECIFIC_DEFAULT); + if (escaped_slashes_action != envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::KEEP_UNCHANGED) { + auto escaped_slashes_result = PathUtil::unescapeSlashes(request_headers); + if (escaped_slashes_result == PathUtil::UnescapeSlashesResult::FoundAndUnescaped) { + if (escaped_slashes_action == envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::REJECT_REQUEST) { + return NormalizePathAction::Reject; + } else if (escaped_slashes_action == + envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT) { + final_action = NormalizePathAction::Redirect; + } else { + ASSERT(escaped_slashes_action == + envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_FORWARD); + } + } } + + if (config.shouldNormalizePath() && !PathUtil::canonicalPath(request_headers)) { + return NormalizePathAction::Reject; + } + // Merge slashes after path normalization to catch potential edge cases with percent encoding. - if (is_valid_path && config.shouldMergeSlashes()) { + if (config.shouldMergeSlashes()) { PathUtil::mergeSlashes(request_headers); } - return is_valid_path; + + return final_action; } absl::optional diff --git a/source/common/http/conn_manager_utility.h b/source/common/http/conn_manager_utility.h index c952b99c2fbb8..d85af74ce570e 100644 --- a/source/common/http/conn_manager_utility.h +++ b/source/common/http/conn_manager_utility.h @@ -67,12 +67,17 @@ class ConnectionManagerUtility { const RequestHeaderMap* request_headers, ConnectionManagerConfig& config, const std::string& via); + enum class NormalizePathAction { + Continue = 0, + Reject = 1, + Redirect = 2, + }; + // Sanitize the path in the header map if the path exists and it is forced by config. // Side affect: the string view of Path header is invalidated. - // Return false if error happens during the sanitization. - // Returns true if there is no path. - static bool maybeNormalizePath(RequestHeaderMap& request_headers, - const ConnectionManagerConfig& config); + // Returns the action that should taken based on the results of path normalization. + static NormalizePathAction maybeNormalizePath(RequestHeaderMap& request_headers, + const ConnectionManagerConfig& config); static absl::optional maybeNormalizeHost(RequestHeaderMap& request_headers, const ConnectionManagerConfig& config, diff --git a/source/common/http/path_utility.cc b/source/common/http/path_utility.cc index 988e82b9be82a..3967520be305d 100644 --- a/source/common/http/path_utility.cc +++ b/source/common/http/path_utility.cc @@ -29,6 +29,16 @@ absl::optional canonicalizePath(absl::string_view original_path) { } return LegacyPathCanonicalizer::canonicalizePath(original_path); } + +void unescapeInPath(std::string& path, absl::string_view escape_sequence, + absl::string_view substitution) { + std::vector split = absl::StrSplit(path, escape_sequence); + if (split.size() == 1) { + return; + } + path = absl::StrJoin(split, substitution); +} + } // namespace /* static */ @@ -75,6 +85,31 @@ void PathUtil::mergeSlashes(RequestHeaderMap& headers) { path_suffix, query)); } +PathUtil::UnescapeSlashesResult PathUtil::unescapeSlashes(RequestHeaderMap& headers) { + ASSERT(headers.Path()); + const auto original_path = headers.getPathValue(); + const auto original_length = original_path.length(); + // Only operate on path component in URL. + const absl::string_view::size_type query_start = original_path.find('?'); + const absl::string_view path = original_path.substr(0, query_start); + if (path.find('%') == absl::string_view::npos) { + return UnescapeSlashesResult::NotFound; + } + const absl::string_view query = absl::ClippedSubstr(original_path, query_start); + + // TODO(yanavlasov): optimize this by adding case insensitive matcher + std::string decoded_path{path}; + unescapeInPath(decoded_path, "%2F", "/"); + unescapeInPath(decoded_path, "%2f", "/"); + unescapeInPath(decoded_path, "%5C", "\\"); + unescapeInPath(decoded_path, "%5c", "\\"); + headers.setPath(absl::StrCat(decoded_path, query)); + // Path length will not match if there were unescaped %2f or %5c + return headers.getPathValue().length() != original_length + ? UnescapeSlashesResult::FoundAndUnescaped + : UnescapeSlashesResult::NotFound; +} + absl::string_view PathUtil::removeQueryAndFragment(const absl::string_view path) { absl::string_view ret = path; // Trim query parameters and/or fragment if present. diff --git a/source/common/http/path_utility.h b/source/common/http/path_utility.h index a6a99aaef78d7..1771ccaaa63fd 100644 --- a/source/common/http/path_utility.h +++ b/source/common/http/path_utility.h @@ -19,6 +19,17 @@ class PathUtil { // Merges two or more adjacent slashes in path part of URI into one. // Requires the Path header be present. static void mergeSlashes(RequestHeaderMap& headers); + + enum class UnescapeSlashesResult { + // No escaped slash sequences were found and URL path has not been modified. + NotFound = 0, + // Escaped slash sequences were found and URL path has been modified. + FoundAndUnescaped = 1, + }; + // Unescape %2F, %2f, %5C and %5c sequences. + // Requires the Path header be present. + // Returns the result of unescaping slashes. + static UnescapeSlashesResult unescapeSlashes(RequestHeaderMap& headers); // Removes the query and/or fragment string (if present) from the input path. // For example, this function returns "/data" for the input path "/data?param=value#fragment". static absl::string_view removeQueryAndFragment(const absl::string_view path); diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 0cf301d93ead1..f6b416fde5e26 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -92,6 +92,51 @@ class MissingConfigFilter : public Http::PassThroughDecoderFilter { } }; +envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + getPathWithEscapedSlashesActionRuntimeOverride(Server::Configuration::FactoryContext& context) { + // The default behavior is to leave escaped slashes unchanged. + uint64_t runtime_override = context.runtime().snapshot().getInteger( + "http_connection_manager.path_with_escaped_slashes_action", 0); + switch (runtime_override) { + default: + // Also includes runtime override values of 0 and 1 + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + KEEP_UNCHANGED; + case 2: + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + REJECT_REQUEST; + case 3: + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + UNESCAPE_AND_REDIRECT; + case 4: + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + UNESCAPE_AND_FORWARD; + } +} + +envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + getPathWithEscapedSlashesAction(const envoy::extensions::filters::network:: + http_connection_manager::v3::HttpConnectionManager& config, + Server::Configuration::FactoryContext& context) { + envoy::type::v3::FractionalPercent default_fraction; + default_fraction.set_numerator(100); + default_fraction.set_denominator(envoy::type::v3::FractionalPercent::HUNDRED); + if (context.runtime().snapshot().featureEnabled( + "http_connection_manager.path_with_escaped_slashes_action_enabled", default_fraction)) { + return config.path_with_escaped_slashes_action() == + envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::IMPLEMENTATION_SPECIFIC_DEFAULT + ? getPathWithEscapedSlashesActionRuntimeOverride(context) + : config.path_with_escaped_slashes_action(); + } + + // When action is disabled through runtime the behavior is to keep escaped slashes unchanged. + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + KEEP_UNCHANGED; +} + } // namespace // Singleton registration via macro defined in envoy/singleton/manager.h @@ -260,7 +305,8 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( merge_slashes_(config.merge_slashes()), headers_with_underscores_action_( config.common_http_protocol_options().headers_with_underscores_action()), - local_reply_(LocalReply::Factory::create(config.local_reply_config(), context)) { + local_reply_(LocalReply::Factory::create(config.local_reply_config(), context)), + path_with_escaped_slashes_action_(getPathWithEscapedSlashesAction(config, context)) { // If idle_timeout_ was not configured in common_http_protocol_options, use value in deprecated // idle_timeout field. // TODO(asraa): Remove when idle_timeout is removed. diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index fdabd465a6fc7..6b023bafb6fe9 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -181,6 +181,11 @@ class HttpConnectionManagerConfig : Logger::Loggable, } std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } const LocalReply::LocalReply& localReply() const override { return *local_reply_; } + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + pathWithEscapedSlashesAction() const override { + return path_with_escaped_slashes_action_; + } private: enum class CodecType { HTTP1, HTTP2, HTTP3, AUTO }; @@ -270,6 +275,8 @@ class HttpConnectionManagerConfig : Logger::Loggable, static const uint64_t RequestTimeoutMs = 0; // request header timeout is disabled by default static const uint64_t RequestHeaderTimeoutMs = 0; + const envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction path_with_escaped_slashes_action_; }; /** diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index 94ce28ba64661..c9339f6925e9b 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -183,6 +183,12 @@ class AdminImpl : public Admin, return envoy::config::core::v3::HttpProtocolOptions::ALLOW; } const LocalReply::LocalReply& localReply() const override { return *local_reply_; } + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + pathWithEscapedSlashesAction() const override { + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + KEEP_UNCHANGED; + } Http::Code request(absl::string_view path_and_query, absl::string_view method, Http::ResponseHeaderMap& response_headers, std::string& body) override; void closeSocket(); diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index ed9f08f86a162..56873a2b14a07 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -204,6 +204,12 @@ class FuzzConfig : public ConnectionManagerConfig { return envoy::config::core::v3::HttpProtocolOptions::ALLOW; } const LocalReply::LocalReply& localReply() const override { return *local_reply_; } + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + pathWithEscapedSlashesAction() const override { + return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + KEEP_UNCHANGED; + } const envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager config_; diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 1a071d2b38089..7f34287cf2ec0 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -522,6 +522,103 @@ TEST_F(HttpConnectionManagerImplTest, RouteShouldUseSantizedPath) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } +// Paths with escaped slashes rejected with 400 when configured. +TEST_F(HttpConnectionManagerImplTest, PathWithEscapedSlashesRejected) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::REJECT_REQUEST; + testPathNormalization( + TestRequestHeaderMapImpl{{":authority", "host"}, {":path", "/abc%5c../"}, {":method", "GET"}}, + TestResponseHeaderMapImpl{{":status", "400"}, {"connection", "close"}}); + EXPECT_EQ(1U, stats_.named_.downstream_rq_failed_path_normalization_.value()); +} + +// Paths with escaped slashes redirected when configured. +TEST_F(HttpConnectionManagerImplTest, PathWithEscapedSlashesRedirected) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::UNESCAPE_AND_REDIRECT; + testPathNormalization( + TestRequestHeaderMapImpl{{":authority", "host"}, {":path", "/abc%2f../"}, {":method", "GET"}}, + TestResponseHeaderMapImpl{{":status", "307"}, {"location", "/abc/../"}}); + EXPECT_EQ(1U, stats_.named_.downstream_rq_redirected_with_normalized_path_.value()); +} + +// Paths with escaped slashes rejected with 400 instead of redirected for gRPC request. +TEST_F(HttpConnectionManagerImplTest, PathWithEscapedSlashesRejectedIfGRPC) { + // This test is slightly weird as it sends gRPC "request" over H/1 client of the + // HttpConnectionManagerImplTest. However it is sufficient to test the behavior of path + // normalization as it is determined by the content type only. + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::UNESCAPE_AND_REDIRECT; + testPathNormalization(TestRequestHeaderMapImpl{{":authority", "host"}, + {":path", "/abc%2fdef"}, + {":method", "GET"}, + {"content-type", "application/grpc"}}, + TestResponseHeaderMapImpl{{":status", "200"}, + {"connection", "close"}, + {"grpc-status", "13"}, + {"content-type", "application/grpc"}}); + EXPECT_EQ(1U, stats_.named_.downstream_rq_failed_path_normalization_.value()); +} + +// Test that requests with escaped slashes are redirected when configured. Redirection +// occurs after Chromium URL normalization or merge slashes operations. +TEST_F(HttpConnectionManagerImplTest, EscapedSlashesRedirectedAfterOtherNormalizations) { + normalize_path_ = true; + merge_slashes_ = true; + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::UNESCAPE_AND_REDIRECT; + // Both Chromium URL normalization and merge slashes should happen if request is redirected + // due to escaped slash sequences. + testPathNormalization(TestRequestHeaderMapImpl{{":authority", "host"}, + {":path", "/abc%2f../%5cdef//"}, + {":method", "GET"}}, + TestResponseHeaderMapImpl{{":status", "307"}, {"location", "/def/"}}); + EXPECT_EQ(1U, stats_.named_.downstream_rq_redirected_with_normalized_path_.value()); +} + +TEST_F(HttpConnectionManagerImplTest, AllNormalizationsWithEscapedSlashesForwarded) { + setup(false, ""); + // Enable path sanitizer + normalize_path_ = true; + merge_slashes_ = true; + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::UNESCAPE_AND_FORWARD; + const std::string original_path = "/x/%2E%2e/z%2f%2Fabc%5C../def"; + const std::string normalized_path = "/z/def"; + + auto* filter = new MockStreamFilter(); + + EXPECT_CALL(filter_factory_, createFilterChain(_)) + .WillOnce(Invoke([&](FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamDecoderFilter(StreamDecoderFilterSharedPtr{filter}); + })); + + EXPECT_CALL(*filter, decodeComplete()); + EXPECT_CALL(*filter, decodeHeaders(_, true)) + .WillRepeatedly(Invoke([&](RequestHeaderMap& header_map, bool) -> FilterHeadersStatus { + EXPECT_EQ(normalized_path, header_map.getPathValue()); + return FilterHeadersStatus::StopIteration; + })); + + EXPECT_CALL(*filter, setDecoderFilterCallbacks(_)); + + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> Http::Status { + decoder_ = &conn_manager_->newStream(response_encoder_); + RequestHeaderMapPtr headers{new TestRequestHeaderMapImpl{ + {":authority", "host"}, {":path", original_path}, {":method", "GET"}}}; + decoder_->decodeHeaders(std::move(headers), true); + return Http::okStatus(); + })); + + // Kick off the incoming data. + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); + + EXPECT_CALL(*filter, onStreamComplete()); + EXPECT_CALL(*filter, onDestroy()); + filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); +} + TEST_F(HttpConnectionManagerImplTest, RouteOverride) { setup(false, ""); diff --git a/test/common/http/conn_manager_impl_test_base.cc b/test/common/http/conn_manager_impl_test_base.cc index ee8a2e1627d64..a38d613068583 100644 --- a/test/common/http/conn_manager_impl_test_base.cc +++ b/test/common/http/conn_manager_impl_test_base.cc @@ -261,5 +261,30 @@ void HttpConnectionManagerImplTest::doRemoteClose(bool deferred) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } +void HttpConnectionManagerImplTest::testPathNormalization( + const RequestHeaderMap& request_headers, const ResponseHeaderMap& expected_response) { + InSequence s; + setup(false, ""); + + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { + decoder_ = &conn_manager_->newStream(response_encoder_); + RequestHeaderMapPtr headers{std::make_unique(request_headers)}; + decoder_->decodeHeaders(std::move(headers), true); + data.drain(4); + return Http::okStatus(); + })); + + EXPECT_CALL(response_encoder_, encodeHeaders(_, true)) + .WillOnce(Invoke([&](const ResponseHeaderMap& headers, bool) -> void { + TestResponseHeaderMapImpl copy{headers}; + copy.remove(Envoy::Http::LowerCaseString{"date"}); + copy.remove(Envoy::Http::LowerCaseString{"server"}); + EXPECT_THAT(©, HeaderMapEqualIgnoreOrder(&expected_response)); + })); + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); +} + } // namespace Http } // namespace Envoy diff --git a/test/common/http/conn_manager_impl_test_base.h b/test/common/http/conn_manager_impl_test_base.h index 76f9fc86740ea..7029b8414be1f 100644 --- a/test/common/http/conn_manager_impl_test_base.h +++ b/test/common/http/conn_manager_impl_test_base.h @@ -62,6 +62,8 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan ResponseHeaderMap* sendResponseHeaders(ResponseHeaderMapPtr&& response_headers); void expectOnDestroy(bool deferred = true); void doRemoteClose(bool deferred = true); + void testPathNormalization(const RequestHeaderMap& request_headers, + const ResponseHeaderMap& expected_response); // Http::ConnectionManagerConfig const std::list& accessLogs() override { return access_logs_; } @@ -141,6 +143,11 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan return headers_with_underscores_action_; } const LocalReply::LocalReply& localReply() const override { return *local_reply_; } + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction + pathWithEscapedSlashesAction() const override { + return path_with_escaped_slashes_action_; + } Envoy::Event::SimulatedTimeSystem test_time_; NiceMock route_config_provider_; @@ -213,6 +220,10 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan std::vector decoder_filters_; std::vector encoder_filters_; std::shared_ptr log_handler_; + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction path_with_escaped_slashes_action_{ + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + KEEP_UNCHANGED}; }; } // namespace Http diff --git a/test/common/http/conn_manager_utility_test.cc b/test/common/http/conn_manager_utility_test.cc index 6541303b9638b..7226c2a00d7ff 100644 --- a/test/common/http/conn_manager_utility_test.cc +++ b/test/common/http/conn_manager_utility_test.cc @@ -105,6 +105,9 @@ class ConnectionManagerUtilityTest : public testing::Test { ON_CALL(config_, via()).WillByDefault(ReturnRef(via_)); ON_CALL(config_, requestIDExtension()) .WillByDefault(ReturnRef(request_id_extension_to_return_)); + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::KEEP_UNCHANGED)); } struct MutateRequestRet { @@ -1380,7 +1383,8 @@ TEST_F(ConnectionManagerUtilityTest, SanitizeEmptyPath) { TestRequestHeaderMapImpl original_headers; TestRequestHeaderMapImpl header_map(original_headers); - EXPECT_TRUE(ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); EXPECT_EQ(original_headers, header_map); } @@ -1480,6 +1484,177 @@ TEST_F(ConnectionManagerUtilityTest, RemovePort) { EXPECT_EQ(header_map_none.getHostValue(), "host:9999"); } +// maybeNormalizePath() does not touch escaped slashes when configured to KEEP_UNCHANGED. +TEST_F(ConnectionManagerUtilityTest, KeepEscapedSlashesWhenConfigured) { + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::KEEP_UNCHANGED)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2fabc%5Cqrt"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/xyz%2fabc%5Cqrt"); +} + +// maybeNormalizePath() returns REJECT if %2F or %5C was detected and configured to REJECT. +TEST_F(ConnectionManagerUtilityTest, RejectIfEscapedSlashesPresentAndConfiguredToReject) { + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::REJECT_REQUEST)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2F..//abc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Reject, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + + original_headers.setPath("/xyz%5c..//abc"); + header_map = original_headers; + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Reject, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); +} + +// maybeNormalizePath() returns CONTINUE if escaped slashes were NOT present and configured to +// REJECT. +TEST_F(ConnectionManagerUtilityTest, RejectIfEscapedSlashesNotPresentAndConfiguredToReject) { + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::REJECT_REQUEST)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%EA/abc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/xyz%EA/abc"); +} + +// maybeNormalizePath() returns REDIRECT if escaped slashes were detected and configured to +// REDIRECT. +TEST_F(ConnectionManagerUtilityTest, RedirectIfEscapedSlashesPresentAndConfiguredToRedirect) { + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2F../%5cabc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Redirect, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/xyz/../\\abc"); +} + +// maybeNormalizePath() returns CONTINUE if escaped slashes were NOT present and configured to +// REDIRECT. +TEST_F(ConnectionManagerUtilityTest, ContinueIfEscapedSlashesNotFoundAndConfiguredToRedirect) { + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%30..//abc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/xyz%30..//abc"); +} + +// maybeNormalizePath() returns CONTINUE if escaped slashes were detected and configured to +// UNESCAPE_AND_FORWARD. +TEST_F(ConnectionManagerUtilityTest, ContinueIfEscapedSlashesPresentAndConfiguredToUnescape) { + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_FORWARD)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2F../%5Cabc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/xyz/../\\abc"); +} + +// maybeNormalizePath() performs both slash unescaping and Chromium URL normalization. +TEST_F(ConnectionManagerUtilityTest, UnescapeSlashesAndChromiumNormalization) { + ON_CALL(config_, shouldNormalizePath()).WillByDefault(Return(true)); + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_FORWARD)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2f../%5Cabc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + // Chromium URL path normalization converts \ to / + EXPECT_EQ(header_map.getPathValue(), "//abc"); +} + +// maybeNormalizePath() rejects request when chromium normalization fails after unescaping slashes. +TEST_F(ConnectionManagerUtilityTest, UnescapeSlashesRedirectAndChromiumNormalizationFailure) { + ON_CALL(config_, shouldNormalizePath()).WillByDefault(Return(true)); + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT)); + TestRequestHeaderMapImpl original_headers; + // %00 is an invalid sequence in URL path and causes path normalization to fail. + original_headers.setPath("/xyz%2f../%5Cabc%00"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Reject, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); +} + +// maybeNormalizePath() performs both unescaping and merging slashes when configured. +TEST_F(ConnectionManagerUtilityTest, UnescapeAndMergeSlashes) { + ON_CALL(config_, shouldMergeSlashes()).WillByDefault(Return(true)); + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2f/..//abc%5C%5c"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Redirect, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + // Envoy does not merge back slashes + EXPECT_EQ(header_map.getPathValue(), "/xyz/../abc\\\\"); +} + +// maybeNormalizePath() performs all path transformations. +TEST_F(ConnectionManagerUtilityTest, AllNormalizations) { + ON_CALL(config_, shouldNormalizePath()).WillByDefault(Return(true)); + ON_CALL(config_, shouldMergeSlashes()).WillByDefault(Return(true)); + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_FORWARD)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2f..%5c/%2Fabc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Continue, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/abc"); +} + +// maybeNormalizePath() redirects because of escaped slashes after all other transformations. +TEST_F(ConnectionManagerUtilityTest, RedirectAfterAllOtherNormalizations) { + ON_CALL(config_, shouldNormalizePath()).WillByDefault(Return(true)); + ON_CALL(config_, shouldMergeSlashes()).WillByDefault(Return(true)); + ON_CALL(config_, pathWithEscapedSlashesAction()) + .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT)); + TestRequestHeaderMapImpl original_headers; + original_headers.setPath("/xyz%2f..%5c/%2Fabc"); + + TestRequestHeaderMapImpl header_map(original_headers); + EXPECT_EQ(ConnectionManagerUtility::NormalizePathAction::Redirect, + ConnectionManagerUtility::maybeNormalizePath(header_map, config_)); + EXPECT_EQ(header_map.getPathValue(), "/abc"); +} + // test preserve_external_request_id true does not reset the passed requestId if passed TEST_F(ConnectionManagerUtilityTest, PreserveExternalRequestId) { connection_.stream_info_.downstream_address_provider_->setRemoteAddress( diff --git a/test/common/http/path_utility_test.cc b/test/common/http/path_utility_test.cc index d7c6399341362..3934b1469ed5d 100644 --- a/test/common/http/path_utility_test.cc +++ b/test/common/http/path_utility_test.cc @@ -1,3 +1,4 @@ +#include #include #include @@ -133,5 +134,59 @@ TEST_F(PathUtilityTest, RemoveQueryAndFragment) { EXPECT_EQ("/abc", PathUtil::removeQueryAndFragment("/abc?param=value#fragment")); } +TEST_F(PathUtilityTest, UnescapeSlashes) { + using UnescapeResult = std::tuple; + auto unescapeSlashes = [this](const std::string& path_value) { + auto& path_header = pathHeaderEntry(path_value); + auto result = PathUtil::unescapeSlashes(headers_); + auto sanitized_path_value = path_header.value().getStringView(); + return UnescapeResult(std::string(sanitized_path_value), result); + }; + EXPECT_EQ(UnescapeResult("", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("")); // empty + EXPECT_EQ(UnescapeResult("//", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%2f%2F")); // case-insensitive + EXPECT_EQ(UnescapeResult("/a/b/c/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("/a%2Fb%2fc/")); // between other characters + EXPECT_EQ(UnescapeResult("%2b", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("%2b")); // not %2f + EXPECT_EQ(UnescapeResult("/a/b/c", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("/a/b/c")); // not %2f + EXPECT_EQ(UnescapeResult("%2", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("%2")); // incomplete + EXPECT_EQ(UnescapeResult("%", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("%")); // incomplete + EXPECT_EQ(UnescapeResult("/abc%2", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("/abc%2")); // incomplete + EXPECT_EQ(UnescapeResult("foo%", PathUtil::UnescapeSlashesResult::NotFound), + unescapeSlashes("foo%")); // incomplete + EXPECT_EQ(UnescapeResult("/a/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("/a%2F")); // prefixed + EXPECT_EQ(UnescapeResult("/a/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%2fa/")); // suffixed + EXPECT_EQ(UnescapeResult("%/a/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%%2fa/")); // double escape + EXPECT_EQ(UnescapeResult("%2/a/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%2%2fa/")); // incomplete escape + + EXPECT_EQ(UnescapeResult("\\\\", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%5c%5C")); // case-insensitive + EXPECT_EQ(UnescapeResult("/a\\b\\c/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("/a%5Cb%5cc/")); // between other characters + EXPECT_EQ(UnescapeResult("/a\\", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("/a%5C")); // prefixed + EXPECT_EQ(UnescapeResult("\\a/", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%5ca/")); // suffixed + EXPECT_EQ(UnescapeResult("/x/%2E%2e/z//abc\\../def", + PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("/x/%2E%2e/z%2f%2Fabc%5C../def")); + + EXPECT_EQ(UnescapeResult("/a\\b/c\\", PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%2fa%5Cb%2fc%5c")); // %5c and %2f together + EXPECT_EQ(UnescapeResult("/a\\b/c\\?%2fabcd%5C%%2f%", + PathUtil::UnescapeSlashesResult::FoundAndUnescaped), + unescapeSlashes("%2fa%5Cb%2fc%5c?%2fabcd%5C%%2f%")); // query is untouched +} + } // namespace Http } // namespace Envoy diff --git a/test/extensions/filters/network/http_connection_manager/config_test.cc b/test/extensions/filters/network/http_connection_manager/config_test.cc index 89cd004b8dd96..90bc34105da58 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_test.cc @@ -27,6 +27,7 @@ using testing::_; using testing::An; +using testing::AnyNumber; using testing::Eq; using testing::NotNull; using testing::Pointee; @@ -840,8 +841,8 @@ TEST_F(HttpConnectionManagerConfigTest, ServerOverwrite) { )EOF"; EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) - .WillOnce(Invoke(&context_.runtime_loader_.snapshot_, - &Runtime::MockSnapshot::featureEnabledDefault)); + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, date_provider_, route_config_provider_manager_, scoped_routes_config_provider_manager_, http_tracer_manager_, @@ -861,8 +862,8 @@ TEST_F(HttpConnectionManagerConfigTest, ServerAppendIfAbsent) { )EOF"; EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) - .WillOnce(Invoke(&context_.runtime_loader_.snapshot_, - &Runtime::MockSnapshot::featureEnabledDefault)); + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, date_provider_, route_config_provider_manager_, scoped_routes_config_provider_manager_, http_tracer_manager_, @@ -882,8 +883,8 @@ TEST_F(HttpConnectionManagerConfigTest, ServerPassThrough) { )EOF"; EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) - .WillOnce(Invoke(&context_.runtime_loader_.snapshot_, - &Runtime::MockSnapshot::featureEnabledDefault)); + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, date_provider_, route_config_provider_manager_, scoped_routes_config_provider_manager_, http_tracer_manager_, @@ -904,8 +905,8 @@ TEST_F(HttpConnectionManagerConfigTest, NormalizePathDefault) { )EOF"; EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) - .WillOnce(Invoke(&context_.runtime_loader_.snapshot_, - &Runtime::MockSnapshot::featureEnabledDefault)); + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, date_provider_, route_config_provider_manager_, scoped_routes_config_provider_manager_, http_tracer_manager_, @@ -927,6 +928,9 @@ TEST_F(HttpConnectionManagerConfigTest, NormalizePathRuntime) { - name: envoy.filters.http.router )EOF"; + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled("http_connection_manager.normalize_path", An())) .WillOnce(Return(true)); @@ -948,6 +952,9 @@ TEST_F(HttpConnectionManagerConfigTest, NormalizePathTrue) { - name: envoy.filters.http.router )EOF"; + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled("http_connection_manager.normalize_path", An())) .Times(0); @@ -969,6 +976,9 @@ TEST_F(HttpConnectionManagerConfigTest, NormalizePathFalse) { - name: envoy.filters.http.router )EOF"; + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled("http_connection_manager.normalize_path", An())) .Times(0); @@ -1980,6 +1990,162 @@ stat_prefix: router createHttpConnectionManagerConfig(yaml_string); } +TEST_F(HttpConnectionManagerConfigTest, PathWithEscapedSlashesActionDefault) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + route_config: + name: local_route + http_filters: + - name: envoy.filters.http.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, + featureEnabled(_, An())) + .WillOnce(Return(true)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); + EXPECT_CALL(context_.runtime_loader_.snapshot_, + getInteger("http_connection_manager.path_with_escaped_slashes_action", 0)) + .WillOnce(Return(0)); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + EXPECT_EQ(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::KEEP_UNCHANGED, + config.pathWithEscapedSlashesAction()); +} + +TEST_F(HttpConnectionManagerConfigTest, PathWithEscapedSlashesActionDefaultOverriden) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + path_with_escaped_slashes_action: IMPLEMENTATION_SPECIFIC_DEFAULT + route_config: + name: local_route + http_filters: + - name: envoy.filters.http.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, + featureEnabled(_, An())) + .WillRepeatedly(Return(true)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); + EXPECT_CALL(context_.runtime_loader_.snapshot_, + getInteger("http_connection_manager.path_with_escaped_slashes_action", 0)) + .WillOnce(Return(3)); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + EXPECT_EQ(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_REDIRECT, + config.pathWithEscapedSlashesAction()); + + // Check the UNESCAPE_AND_FORWARD override to mollify coverage check + EXPECT_CALL(context_.runtime_loader_.snapshot_, + getInteger("http_connection_manager.path_with_escaped_slashes_action", 0)) + .WillOnce(Return(4)); + HttpConnectionManagerConfig config1(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + EXPECT_EQ(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::UNESCAPE_AND_FORWARD, + config1.pathWithEscapedSlashesAction()); +} + +// Verify that runtime override does not affect non default configuration value. +TEST_F(HttpConnectionManagerConfigTest, + PathWithEscapedSlashesActionRuntimeOverrideDoesNotChangeNonDefaultConfigValue) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + path_with_escaped_slashes_action: REJECT_REQUEST + route_config: + name: local_route + http_filters: + - name: envoy.filters.http.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, + featureEnabled(_, An())) + .WillOnce(Return(true)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); + // When configuration value is not the IMPLEMENTATION_SPECIFIC_DEFAULT the runtime override should + // not even be considered. + EXPECT_CALL(context_.runtime_loader_.snapshot_, + getInteger("http_connection_manager.path_with_escaped_slashes_action", 0)) + .Times(0); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + EXPECT_EQ(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::REJECT_REQUEST, + config.pathWithEscapedSlashesAction()); +} + +// Verify that disabling unescaping slashes results in the KEEP_UNCHANGED action when config is +// value is not set. +TEST_F(HttpConnectionManagerConfigTest, PathWithEscapedSlashesActionDefaultOverridenAndDisabled) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + route_config: + name: local_route + http_filters: + - name: envoy.filters.http.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, + featureEnabled("http_connection_manager.path_with_escaped_slashes_action_enabled", + An())) + .WillOnce(Return(false)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); + EXPECT_CALL(context_.runtime_loader_.snapshot_, + getInteger("http_connection_manager.path_with_escaped_slashes_action", 0)) + .Times(0); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + EXPECT_EQ(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::KEEP_UNCHANGED, + config.pathWithEscapedSlashesAction()); +} + +// Verify that disabling unescaping slashes results in the KEEP_UNCHANGED action when config is +// value is set. +TEST_F(HttpConnectionManagerConfigTest, PathWithEscapedSlashesActionSetAndDisabled) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + path_with_escaped_slashes_action: UNESCAPE_AND_REDIRECT + route_config: + name: local_route + http_filters: + - name: envoy.filters.http.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, + featureEnabled("http_connection_manager.path_with_escaped_slashes_action_enabled", + An())) + .WillOnce(Return(false)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); + EXPECT_CALL(context_.runtime_loader_.snapshot_, + getInteger("http_connection_manager.path_with_escaped_slashes_action", 0)) + .Times(0); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + EXPECT_EQ(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::KEEP_UNCHANGED, + config.pathWithEscapedSlashesAction()); +} + class HcmUtilityTest : public testing::Test { public: HcmUtilityTest() { diff --git a/test/integration/header_integration_test.cc b/test/integration/header_integration_test.cc index d10b64b2b7dfc..0045426314907 100644 --- a/test/integration/header_integration_test.cc +++ b/test/integration/header_integration_test.cc @@ -329,6 +329,7 @@ class HeaderIntegrationTest } hcm.mutable_normalize_path()->set_value(normalize_path_); + hcm.set_path_with_escaped_slashes_action(path_with_escaped_slashes_action_); if (append) { // The config specifies append by default: no modifications needed. @@ -433,7 +434,7 @@ class HeaderIntegrationTest } template - void compareHeaders(Headers&& headers, ExpectedHeaders& expected_headers) { + void compareHeaders(Headers&& headers, const ExpectedHeaders& expected_headers) { headers.remove(Envoy::Http::LowerCaseString{"content-length"}); headers.remove(Envoy::Http::LowerCaseString{"date"}); if (!routerSuppressEnvoyHeaders()) { @@ -444,11 +445,15 @@ class HeaderIntegrationTest headers.remove(Envoy::Http::LowerCaseString{"x-request-id"}); headers.remove(Envoy::Http::LowerCaseString{"x-envoy-internal"}); - EXPECT_EQ(expected_headers, headers); + EXPECT_THAT(&headers, HeaderMapEqualIgnoreOrder(&expected_headers)); } bool use_eds_{false}; bool normalize_path_{false}; + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PathWithEscapedSlashesAction path_with_escaped_slashes_action_{ + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + KEEP_UNCHANGED}; FakeHttpConnectionPtr eds_connection_; FakeStreamPtr eds_stream_; }; @@ -1085,6 +1090,166 @@ TEST_P(HeaderIntegrationTest, TestPathAndRouteOnNormalizedPath) { }); } +// Validates that Envoy by default does not modify escaped slashes. +TEST_P(HeaderIntegrationTest, PathWithEscapedSlashesByDefaultUnchanghed) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::IMPLEMENTATION_SPECIFIC_DEFAULT; + normalize_path_ = true; + initializeFilter(HeaderMode::Append, false); + performRequest( + Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/private/..%2Fpublic%5c"}, + {":scheme", "http"}, + {":authority", "path-sanitization.com"}, + }, + Http::TestRequestHeaderMapImpl{{":authority", "path-sanitization.com"}, + {":path", "/private/..%2Fpublic%5c"}, + {":method", "GET"}, + {"x-site", "private"}}, + Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"content-length", "0"}, + {":status", "200"}, + {"x-unmodified", "response"}, + }, + Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"x-unmodified", "response"}, + {":status", "200"}, + }); +} + +// Validates that default action can be overridden through runtime. +TEST_P(HeaderIntegrationTest, PathWithEscapedSlashesDefaultOverriden) { + // Override the default action to REJECT + config_helper_.addRuntimeOverride("http_connection_manager.path_with_escaped_slashes_action", + "2"); + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::IMPLEMENTATION_SPECIFIC_DEFAULT; + initializeFilter(HeaderMode::Append, false); + registerTestServerPorts({"http"}); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + IntegrationStreamDecoderPtr response = + codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/private%2f../public"}, + {":scheme", "http"}, + {":authority", "path-sanitization.com"}, + }); + EXPECT_TRUE(response->waitForEndStream()); + Http::TestResponseHeaderMapImpl response_headers{response->headers()}; + compareHeaders(response_headers, Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"connection", "close"}, + {":status", "400"}, + }); +} + +TEST_P(HeaderIntegrationTest, PathWithEscapedSlashesRejected) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::REJECT_REQUEST; + initializeFilter(HeaderMode::Append, false); + registerTestServerPorts({"http"}); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + IntegrationStreamDecoderPtr response = + codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/private%2f../public"}, + {":scheme", "http"}, + {":authority", "path-sanitization.com"}, + }); + EXPECT_TRUE(response->waitForEndStream()); + Http::TestResponseHeaderMapImpl response_headers{response->headers()}; + compareHeaders(response_headers, Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"connection", "close"}, + {":status", "400"}, + }); +} + +// Validates that Envoy does not modify escaped slashes when configured. +TEST_P(HeaderIntegrationTest, PathWithEscapedSlashesUnmodified) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::KEEP_UNCHANGED; + normalize_path_ = true; + initializeFilter(HeaderMode::Append, false); + performRequest( + Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/private/..%2Fpublic%5c"}, + {":scheme", "http"}, + {":authority", "path-sanitization.com"}, + }, + Http::TestRequestHeaderMapImpl{{":authority", "path-sanitization.com"}, + {":path", "/private/..%2Fpublic%5c"}, + {":method", "GET"}, + {"x-site", "private"}}, + Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"content-length", "0"}, + {":status", "200"}, + {"x-unmodified", "response"}, + }, + Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"x-unmodified", "response"}, + {":status", "200"}, + }); +} + +// Validates that Envoy forwards unescaped slashes when configured. +TEST_P(HeaderIntegrationTest, PathWithEscapedSlashesAndNormalizationForwarded) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::UNESCAPE_AND_FORWARD; + normalize_path_ = true; + initializeFilter(HeaderMode::Append, false); + performRequest( + Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/private/..%2Fpublic%5c%2e%2Fabc"}, + {":scheme", "http"}, + {":authority", "path-sanitization.com"}, + }, + Http::TestRequestHeaderMapImpl{{":authority", "path-sanitization.com"}, + {":path", "/public/abc"}, + {":method", "GET"}, + {"x-site", "public"}}, + Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"content-length", "0"}, + {":status", "200"}, + {"x-unmodified", "response"}, + }, + Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"x-unmodified", "response"}, + {":status", "200"}, + }); +} + +TEST_P(HeaderIntegrationTest, PathWithEscapedSlashesRedirected) { + path_with_escaped_slashes_action_ = envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager::UNESCAPE_AND_REDIRECT; + initializeFilter(HeaderMode::Append, false); + registerTestServerPorts({"http"}); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + IntegrationStreamDecoderPtr response = + codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/private%2f../%2e%5Cpublic"}, + {":scheme", "http"}, + {":authority", "path-sanitization.com"}, + }); + EXPECT_TRUE(response->waitForEndStream()); + Http::TestResponseHeaderMapImpl response_headers{response->headers()}; + compareHeaders(response_headers, Http::TestResponseHeaderMapImpl{ + {"server", "envoy"}, + {"location", "/private/../%2e\\public"}, + {":status", "307"}, + }); +} + // Validates TE header is forwarded if it contains a supported value TEST_P(HeaderIntegrationTest, TestTeHeaderPassthrough) { initializeFilter(HeaderMode::Append, false); diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index da18aa66e82b1..b8ab19fda6c9e 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -2295,4 +2295,23 @@ TEST_P(DownstreamProtocolIntegrationTest, Test100AndDisconnect) { EXPECT_EQ("503", response->headers().getStatusValue()); } +TEST_P(DownstreamProtocolIntegrationTest, HeaderNormalizationRejection) { + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { + hcm.set_path_with_escaped_slashes_action( + envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::REJECT_REQUEST); + }); + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + default_request_headers_.setPath("/test/long%2Furl"); + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + + EXPECT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("400", response->headers().getStatusValue()); +} + } // namespace Envoy diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index 4a2d9997932cf..31a40da3a3540 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -586,6 +586,9 @@ class MockConnectionManagerConfig : public ConnectionManagerConfig { MOCK_METHOD(envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction, headersWithUnderscoresAction, (), (const)); MOCK_METHOD(const LocalReply::LocalReply&, localReply, (), (const)); + MOCK_METHOD(envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager::PathWithEscapedSlashesAction, + pathWithEscapedSlashesAction, (), (const)); std::unique_ptr internal_address_config_ = std::make_unique(); From 06c8d41d28d1bcb00d9961fe51aa4a73c00bfd8c Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 12 May 2021 08:46:36 -0400 Subject: [PATCH 184/209] docs: follow up to #15926 (#16421) Signed-off-by: Alyssa Wilk --- docs/root/intro/arch_overview/http/http3.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/root/intro/arch_overview/http/http3.rst b/docs/root/intro/arch_overview/http/http3.rst index 60ef56adc9cf6..6c1da39eca24d 100644 --- a/docs/root/intro/arch_overview/http/http3.rst +++ b/docs/root/intro/arch_overview/http/http3.rst @@ -19,8 +19,9 @@ HTTP3 downstream ---------------- Downstream Envoy HTTP/3 support can be turned up via adding -:ref:`quic_options ` and -ensuring the downstream transport socket is a QuicDownstreamTransport. +:ref:`quic_options `, +ensuring the downstream transport socket is a QuicDownstreamTransport, and setting the codec +to HTTP/3. See example :repo:`downstream HTTP/3 configuration ` for example configuration. From c24cea7b345309d61a5e61618dbdddcec23838fe Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 12 May 2021 13:49:22 +0100 Subject: [PATCH 185/209] dependabot: Updates (#16379) * build(deps): bump protobuf in /examples/grpc-bridge/client Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 3.15.8 to 3.16.0. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.15.8...v3.16.0) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey * build(deps): bump attrs from 20.3.0 to 21.2.0 in /ci/flaky_test Bumps [attrs](https://github.com/python-attrs/attrs) from 20.3.0 to 21.2.0. - [Release notes](https://github.com/python-attrs/attrs/releases) - [Changelog](https://github.com/python-attrs/attrs/blob/main/CHANGELOG.rst) - [Commits](https://github.com/python-attrs/attrs/compare/20.3.0...21.2.0) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey * build(deps): bump attrs from 20.3.0 to 21.2.0 in /tools/testing Bumps [attrs](https://github.com/python-attrs/attrs) from 20.3.0 to 21.2.0. - [Release notes](https://github.com/python-attrs/attrs/releases) - [Changelog](https://github.com/python-attrs/attrs/blob/main/CHANGELOG.rst) - [Commits](https://github.com/python-attrs/attrs/compare/20.3.0...21.2.0) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey * build(deps): bump flake8 from 3.9.1 to 3.9.2 in /tools/code_format Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.9.1 to 3.9.2. - [Release notes](https://gitlab.com/pycqa/flake8/tags) - [Commits](https://gitlab.com/pycqa/flake8/compare/3.9.1...3.9.2) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ci/flaky_test/requirements.txt | 6 +++--- examples/grpc-bridge/client/requirements.txt | 2 +- tools/code_format/requirements.txt | 6 +++--- tools/testing/requirements.txt | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ci/flaky_test/requirements.txt b/ci/flaky_test/requirements.txt index 95bac14fdd655..9fe42375ba22f 100644 --- a/ci/flaky_test/requirements.txt +++ b/ci/flaky_test/requirements.txt @@ -39,9 +39,9 @@ aiohttp==3.7.4.post0 \ async-timeout==3.0.1 \ --hash=sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f \ --hash=sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3 -attrs==20.3.0 \ - --hash=sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6 \ - --hash=sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700 +attrs==21.2.0 \ + --hash=sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1 \ + --hash=sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb chardet==4.0.0 \ --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \ --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa diff --git a/examples/grpc-bridge/client/requirements.txt b/examples/grpc-bridge/client/requirements.txt index 436a97af70862..327544f83036d 100644 --- a/examples/grpc-bridge/client/requirements.txt +++ b/examples/grpc-bridge/client/requirements.txt @@ -1,4 +1,4 @@ requests>=2.22.0 grpcio grpcio-tools -protobuf==3.15.8 +protobuf==3.16.0 diff --git a/tools/code_format/requirements.txt b/tools/code_format/requirements.txt index 5fce0f0c10e10..15ba3b2f5e01d 100644 --- a/tools/code_format/requirements.txt +++ b/tools/code_format/requirements.txt @@ -1,6 +1,6 @@ -flake8==3.9.1 \ - --hash=sha256:3b9f848952dddccf635be78098ca75010f073bfe14d2c6bda867154bea728d2a \ - --hash=sha256:1aa8990be1e689d96c745c5682b687ea49f2e05a443aff1f8251092b0014e378 +flake8==3.9.2 \ + --hash=sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907 \ + --hash=sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b importlib-metadata==4.0.1 \ --hash=sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d \ --hash=sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581 diff --git a/tools/testing/requirements.txt b/tools/testing/requirements.txt index 075ebbd7a2247..2d5b619ca3e43 100644 --- a/tools/testing/requirements.txt +++ b/tools/testing/requirements.txt @@ -4,9 +4,9 @@ # # pip-compile --generate-hashes tools/testing/requirements.txt # -attrs==20.3.0 \ - --hash=sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6 \ - --hash=sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700 +attrs==21.2.0 \ + --hash=sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1 \ + --hash=sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb # via # -r tools/testing/requirements.txt # pytest From b57f187c8cc7449ab01c70a8f5a483fec127bf3b Mon Sep 17 00:00:00 2001 From: James Peach Date: Wed, 12 May 2021 22:51:00 +1000 Subject: [PATCH 186/209] Document that python3-pip is a build dependency. (#16409) The python3-pip package is needed on both Ubuntu and Fedora. Signed-off-by: James Peach --- bazel/README.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/bazel/README.md b/bazel/README.md index 46835c18a8a35..1260fc1d36feb 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -54,22 +54,33 @@ for how to update or override dependencies. On Ubuntu, run the following: ```console sudo apt-get install \ - libtool \ - cmake \ - automake \ autoconf \ + automake \ + cmake \ + curl \ + libtool \ make \ ninja-build \ - curl \ + patch \ + python3-pip \ unzip \ - virtualenv \ - patch + virtualenv ``` ### Fedora On Fedora (maybe also other red hat distros), run the following: ```console - dnf install cmake libtool libstdc++ libstdc++-static libatomic ninja-build lld patch aspell-en + dnf install \ + aspell-en \ + cmake \ + libatomic \ + libstdc++ \ + libstdc++-static \ + libtool \ + lld \ + ninja-build \ + patch \ + python3-pip ``` ### Linux From 955f15d3210975ae701486a8b7c67e0b6930e6a1 Mon Sep 17 00:00:00 2001 From: Long Dai Date: Wed, 12 May 2021 23:33:30 +0800 Subject: [PATCH 187/209] bazel: unify msys (#16394) Conform to default C:\msys64 path name. Signed-off-by: Long Dai --- bazel/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bazel/README.md b/bazel/README.md index 1260fc1d36feb..87060090a61c1 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -197,7 +197,7 @@ for how to update or override dependencies. set PATH=%PATH%;%USERPROFILE%\VSBT2019\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja ``` - [MSYS2 shell](https://msys2.github.io/): Install to a path with no spaces, e.g. C:\msys32. + [MSYS2 shell](https://msys2.github.io/): Install to a path with no spaces, e.g. C:\msys64. Set the `BAZEL_SH` environment variable to the path of the installed MSYS2 `bash.exe` executable. Additionally, setting the `MSYS2_ARG_CONV_EXCL` environment variable to a value From db066659049b03eca33452c4e07e65f622d1c4f5 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 12 May 2021 16:40:09 +0100 Subject: [PATCH 188/209] docs: Update sphinx (#16413) * build(deps): bump sphinx-tabs from 2.1.0 to 3.0.0 in /docs Bumps [sphinx-tabs](https://github.com/executablebooks/sphinx-tabs) from 2.1.0 to 3.0.0. - [Release notes](https://github.com/executablebooks/sphinx-tabs/releases) - [Changelog](https://github.com/executablebooks/sphinx-tabs/blob/master/CHANGELOG.md) - [Commits](https://github.com/executablebooks/sphinx-tabs/compare/v2.1.0...v3.0.0) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey * build(deps): bump sphinx from 3.5.4 to 4.0.1 in /docs Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.5.4 to 4.0.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.5.4...v4.0.1) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey * update-intersphinx Signed-off-by: Ryan Northey Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/_ext/intersphinx_custom.py | 29 +++++++++++++++++++++-------- docs/requirements.txt | 12 ++++++------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/docs/_ext/intersphinx_custom.py b/docs/_ext/intersphinx_custom.py index 2ce91ca74bdd7..3cd869548e5bc 100644 --- a/docs/_ext/intersphinx_custom.py +++ b/docs/_ext/intersphinx_custom.py @@ -46,10 +46,11 @@ from urllib.parse import urlsplit, urlunsplit from docutils import nodes -from docutils.nodes import Element, TextElement +from docutils.nodes import TextElement from docutils.utils import relative_path import sphinx +from sphinx.addnodes import pending_xref from sphinx.application import Sphinx from sphinx.builders.html import INVENTORY_FILENAME from sphinx.config import Config @@ -57,6 +58,7 @@ from sphinx.locale import _, __ from sphinx.util import logging, requests from sphinx.util.inventory import InventoryFile +from sphinx.util.nodes import find_pending_xref_condition from sphinx.util.typing import Inventory logger = logging.getLogger(__name__) @@ -270,12 +272,12 @@ def load_mappings(app: Sphinx) -> None: inventories.main_inventory.setdefault(type, {}).update(objects) -def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: TextElement - ) -> nodes.reference: +def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref, + contnode: TextElement) -> nodes.reference: """Attempt to resolve a missing reference via intersphinx references.""" target = node['reftarget'] inventories = InventoryAdapter(env) - objtypes = None # type: List[str] + objtypes: List[str] = None if node['reftype'] == 'any': # we search anything! objtypes = ['%s:%s' % (domain.name, objtype) @@ -297,6 +299,17 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnod if 'py:attribute' in objtypes: # Since Sphinx-2.1, properties are stored as py:method objtypes.append('py:method') + + # determine the contnode by pending_xref_condition + content = find_pending_xref_condition(node, 'resolved') + if content: + # resolved condition found. + contnodes = content.children + contnode = content.children[0] # type: ignore + else: + # not resolved. Use the given contnode + contnodes = [contnode] + to_try = [(inventories.main_inventory, target)] if domain: full_qualified_name = env.get_domain(domain).get_full_qualified_name(node) @@ -331,7 +344,7 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnod newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle) if node.get('refexplicit'): # use whatever title was given - newnode.append(contnode) + newnode.extend(contnodes) elif dispname == '-' or \ (domain == 'std' and node['reftype'] == 'keyword'): # use whatever title was given, but strip prefix @@ -340,7 +353,7 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnod newnode.append(contnode.__class__(title[len(in_set) + 1:], title[len(in_set) + 1:])) else: - newnode.append(contnode) + newnode.extend(contnodes) else: # else use the given display name (used for :ref:) newnode.append(contnode.__class__(dispname, dispname)) @@ -401,8 +414,8 @@ def inspect_main(argv: List[str]) -> None: sys.exit(1) class MockConfig: - intersphinx_timeout = None # type: int - intersphinx_strict_prefix = True + intersphinx_timeout: int = None + intersphinx_strict_prefix: bool = True tls_verify = False user_agent = None diff --git a/docs/requirements.txt b/docs/requirements.txt index 12f2d494a08ef..9ae3006b4a475 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -157,13 +157,13 @@ sphinx-rtd-theme==0.5.2 \ --hash=sha256:32bd3b5d13dc8186d7a42fc816a23d32e83a4827d7d9882948e7b837c232da5a \ --hash=sha256:4a05bdbe8b1446d77a01e20a23ebc6777c74f43237035e76be89699308987d6f # via -r docs/requirements.txt -sphinx-tabs==2.1.0 \ - --hash=sha256:0b5a8dd71d87197a01eef3b9d1e1a70513f4dd45e8af7783d1ab74c3fb2cbc6c \ - --hash=sha256:9d8db61afe9499aa84b33bc1c0d891f8a0df88ae513739f5babde15430c1fdaf +sphinx-tabs==3.0.0 \ + --hash=sha256:2abbcaaa3b8a857de06f3db31762a7bdd17aba1b8979d000f193debe6f917c2c \ + --hash=sha256:3f766762fffacc99828cb877a9e4cb8ac0ba3582f2a054ea68248e5e026e5612 # via -r docs/requirements.txt -sphinx==3.5.4 \ - --hash=sha256:19010b7b9fa0dc7756a6e105b2aacd3a80f798af3c25c273be64d7beeb482cb1 \ - --hash=sha256:2320d4e994a191f4b4be27da514e46b3d6b420f2ff895d064f52415d342461e8 +sphinx==4.0.1 \ + --hash=sha256:b2566f5f339737a6ef37198c47d56de1f4a746c722bebdb2fe045c34bfd8b9d0 \ + --hash=sha256:cf5104777571b2b7f06fa88ee08fade24563f4a0594cf4bd17d31c47b8740b4c # via # -r docs/requirements.txt # sphinx-copybutton From 38cec234e6647631be4fe879ef014537ad65ffc6 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 12 May 2021 16:46:50 +0100 Subject: [PATCH 189/209] docs: Use repo role for repo links (#16455) Signed-off-by: Ryan Northey --- tools/protodoc/protodoc.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/tools/protodoc/protodoc.py b/tools/protodoc/protodoc.py index 417f8b43be12c..74141e011c6fe 100755 --- a/tools/protodoc/protodoc.py +++ b/tools/protodoc/protodoc.py @@ -51,10 +51,6 @@ # http://www.fileformat.info/info/unicode/char/2063/index.htm UNICODE_INVISIBLE_SEPARATOR = u'\u2063' -# Template for data plane API URLs. -DATA_PLANE_API_URL_FMT = 'https://github.com/envoyproxy/envoy/blob/{}/api/%s#L%d'.format( - os.environ['ENVOY_BLOB_SHA']) - # Template for formating extension descriptions. EXTENSION_TEMPLATE = Template( """ @@ -149,7 +145,7 @@ def hide_not_implemented(comment): return annotations.NOT_IMPLEMENTED_HIDE_ANNOTATION in comment.annotations -def github_url(type_context): +def github_url(text, type_context): """Obtain data plane API Github URL by path from a TypeContext. Args: @@ -158,10 +154,7 @@ def github_url(type_context): Returns: A string with a corresponding data plane API GitHub Url. """ - if type_context.location is not None: - return DATA_PLANE_API_URL_FMT % ( - type_context.source_code_info.name, type_context.location.span[0]) - return '' + return f":repo:`{text} `" def format_comment_with_annotations(comment, type_name=''): @@ -679,8 +672,7 @@ def visit_enum(self, enum_proto, type_context): normal_enum_type = normalize_type_context_name(type_context.name) anchor = format_anchor(enum_cross_ref_label(normal_enum_type)) header = format_header('-', 'Enum %s' % normal_enum_type) - _github_url = github_url(type_context) - proto_link = format_external_link('[%s proto]' % normal_enum_type, _github_url) + '\n\n' + proto_link = github_url("f[{normal_enum_type} proto]", type_context) + '\n\n' leading_comment = type_context.leading_comment formatted_leading_comment = format_comment_with_annotations(leading_comment, 'enum') if hide_not_implemented(leading_comment): @@ -695,8 +687,7 @@ def visit_message(self, msg_proto, type_context, nested_msgs, nested_enums): normal_msg_type = normalize_type_context_name(type_context.name) anchor = format_anchor(message_cross_ref_label(normal_msg_type)) header = format_header('-', normal_msg_type) - _github_url = github_url(type_context) - proto_link = format_external_link('[%s proto]' % normal_msg_type, _github_url) + '\n\n' + proto_link = github_url(f"[{normal_msg_type} proto]", type_context) + '\n\n' leading_comment = type_context.leading_comment formatted_leading_comment = format_comment_with_annotations(leading_comment, 'message') if hide_not_implemented(leading_comment): From 55a23b2c6b38506f6f3f68e00aacd78afaab214c Mon Sep 17 00:00:00 2001 From: Mike Schore Date: Wed, 12 May 2021 23:48:28 +0800 Subject: [PATCH 190/209] bugfix: test fails to build on MacOS due to unused parameters (#16454) Signed-off-by: Mike Schore --- .../filters/listener/common/fuzz/listener_filter_fakes.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/extensions/filters/listener/common/fuzz/listener_filter_fakes.cc b/test/extensions/filters/listener/common/fuzz/listener_filter_fakes.cc index 3ca713522d419..2dede890dd85b 100644 --- a/test/extensions/filters/listener/common/fuzz/listener_filter_fakes.cc +++ b/test/extensions/filters/listener/common/fuzz/listener_filter_fakes.cc @@ -46,7 +46,8 @@ void FakeConnectionSocket::setRequestedServerName(absl::string_view server_name) absl::string_view FakeConnectionSocket::requestedServerName() const { return server_name_; } -Api::SysCallIntResult FakeConnectionSocket::getSocketOption(int level, int, void* optval, +Api::SysCallIntResult FakeConnectionSocket::getSocketOption([[maybe_unused]] int level, int, + [[maybe_unused]] void* optval, socklen_t*) const { #ifdef SOL_IP switch (level) { From aaebda209130a633e244bba5cc830ef6efbe96ef Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Wed, 12 May 2021 09:46:37 -0700 Subject: [PATCH 191/209] matching: disable hcm integration by default (#16387) Signed-off-by: Snow Pettersen --- .../http/http_filters/_include/composite.yaml | 9 ++++- .../matching/_include/complicated.yaml | 8 ++++ .../matching/_include/request_response.yaml | 8 ++++ .../advanced/matching/_include/simple.yaml | 8 ++++ docs/root/version_history/current.rst | 3 ++ source/common/http/match_wrapper/config.cc | 4 ++ source/common/runtime/runtime_features.cc | 2 + test/common/http/match_wrapper/BUILD | 1 + test/common/http/match_wrapper/config_test.cc | 40 +++++++++++++++++++ test/config_test/config_test.cc | 11 ++++- .../composite_filter_integration_test.cc | 2 + .../extension_discovery_integration_test.cc | 3 ++ test/integration/integration_test.cc | 1 + 13 files changed, 98 insertions(+), 2 deletions(-) diff --git a/docs/root/configuration/http/http_filters/_include/composite.yaml b/docs/root/configuration/http/http_filters/_include/composite.yaml index 28f441bd73071..48109818710bf 100644 --- a/docs/root/configuration/http/http_filters/_include/composite.yaml +++ b/docs/root/configuration/http/http_filters/_include/composite.yaml @@ -2,7 +2,6 @@ admin: access_log_path: /tmp/admin_access.log address: socket_address: { address: 0.0.0.0, port_value: 9901 } - static_resources: listeners: - name: listener1 @@ -92,3 +91,11 @@ static_resources: socket_address: address: 127.0.0.1 port_value: 50051 + +layered_runtime: + layers: + - name: static-layer + static_layer: + envoy: + reloadable_features: + experimental_matching_api: true diff --git a/docs/root/intro/arch_overview/advanced/matching/_include/complicated.yaml b/docs/root/intro/arch_overview/advanced/matching/_include/complicated.yaml index c68d020f9c9df..0c385de903e42 100644 --- a/docs/root/intro/arch_overview/advanced/matching/_include/complicated.yaml +++ b/docs/root/intro/arch_overview/advanced/matching/_include/complicated.yaml @@ -91,3 +91,11 @@ static_resources: socket_address: address: 127.0.0.1 port_value: 8080 + +layered_runtime: + layers: + - name: static-layer + static_layer: + envoy: + reloadable_features: + experimental_matching_api: true diff --git a/docs/root/intro/arch_overview/advanced/matching/_include/request_response.yaml b/docs/root/intro/arch_overview/advanced/matching/_include/request_response.yaml index 4a9eaedf23e42..78ae0b18b1b79 100644 --- a/docs/root/intro/arch_overview/advanced/matching/_include/request_response.yaml +++ b/docs/root/intro/arch_overview/advanced/matching/_include/request_response.yaml @@ -77,3 +77,11 @@ static_resources: socket_address: address: 127.0.0.1 port_value: 8080 + +layered_runtime: + layers: + - name: static-layer + static_layer: + envoy: + reloadable_features: + experimental_matching_api: true diff --git a/docs/root/intro/arch_overview/advanced/matching/_include/simple.yaml b/docs/root/intro/arch_overview/advanced/matching/_include/simple.yaml index afd0aea63a8bb..9e00c6dba57d9 100644 --- a/docs/root/intro/arch_overview/advanced/matching/_include/simple.yaml +++ b/docs/root/intro/arch_overview/advanced/matching/_include/simple.yaml @@ -65,3 +65,11 @@ static_resources: socket_address: address: 127.0.0.1 port_value: 8080 + +layered_runtime: + layers: + - name: static-layer + static_layer: + envoy: + reloadable_features: + experimental_matching_api: true diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 8719794938235..2630383773a96 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -14,6 +14,9 @@ Minor Behavior Changes *Changes that may cause incompatibilities for some users, but should not for most* * access_log: add new access_log command operator ``%REQUEST_TX_DURATION%``. +* http: disable the integration between :ref:`ExtensionWithMatcher ` + and HTTP filters by default to reflects its experimental status. This feature can be enabled by seting + ``envoy.reloadable_features.experimental_matching_api`` to true. * http: replaced setting ``envoy.reloadable_features.strict_1xx_and_204_response_headers`` with settings ``envoy.reloadable_features.require_strict_1xx_and_204_response_headers`` (require upstream 1xx or 204 responses to not have Transfer-Encoding or non-zero Content-Length headers) and diff --git a/source/common/http/match_wrapper/config.cc b/source/common/http/match_wrapper/config.cc index 85e394a56fe2a..fe1bf7efd34f0 100644 --- a/source/common/http/match_wrapper/config.cc +++ b/source/common/http/match_wrapper/config.cc @@ -87,6 +87,10 @@ Envoy::Http::FilterFactoryCb MatchWrapperConfig::createFilterFactoryFromProtoTyp const envoy::extensions::common::matching::v3::ExtensionWithMatcher& proto_config, const std::string& prefix, Server::Configuration::FactoryContext& context) { + if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.experimental_matching_api")) { + throw EnvoyException("Experimental matching API is not enabled"); + } + ASSERT(proto_config.has_extension_config()); auto& factory = Config::Utility::getAndCheckFactory( diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 208f79c5eccc0..935961c287696 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -112,6 +112,8 @@ constexpr const char* disabled_runtime_features[] = { "envoy.reloadable_features.remove_legacy_json", // Sentinel and test flag. "envoy.reloadable_features.test_feature_false", + // Allows the use of ExtensionWithMatcher to wrap a HTTP filter with a match tree. + "envoy.reloadable_features.experimental_matching_api", }; RuntimeFeatures::RuntimeFeatures() { diff --git a/test/common/http/match_wrapper/BUILD b/test/common/http/match_wrapper/BUILD index 52425ab72baa7..241c2b9d33fb1 100644 --- a/test/common/http/match_wrapper/BUILD +++ b/test/common/http/match_wrapper/BUILD @@ -15,5 +15,6 @@ envoy_cc_test( "//source/common/http/match_wrapper:config", "//test/mocks/server:factory_context_mocks", "//test/test_common:registry_lib", + "//test/test_common:test_runtime_lib", ], ) diff --git a/test/common/http/match_wrapper/config_test.cc b/test/common/http/match_wrapper/config_test.cc index 13cfb40df4e52..3a79a21b01aee 100644 --- a/test/common/http/match_wrapper/config_test.cc +++ b/test/common/http/match_wrapper/config_test.cc @@ -6,6 +6,7 @@ #include "test/mocks/server/factory_context.h" #include "test/test_common/registry.h" +#include "test/test_common/test_runtime.h" #include "gtest/gtest.h" @@ -47,7 +48,42 @@ struct TestFactory : public Envoy::Server::Configuration::NamedHttpFilterConfigF } }; +TEST(MatchWrapper, DisabledByDefault) { + NiceMock factory_context; + + const auto config = + TestUtility::parseYaml(R"EOF( +extension_config: + name: test + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue +matcher: + matcher_tree: + input: + name: request-headers + typed_config: + "@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput + header_name: default-matcher-header + exact_match_map: + map: + match: + action: + name: skip + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.common.matcher.action.v3.SkipFilter +)EOF"); + + MatchWrapperConfig match_wrapper_config; + EXPECT_THROW_WITH_MESSAGE( + match_wrapper_config.createFilterFactoryFromProto(config, "", factory_context), + EnvoyException, "Experimental matching API is not enabled"); +} + TEST(MatchWrapper, WithMatcher) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.experimental_matching_api", "true"}}); + TestFactory test_factory; Envoy::Registry::InjectFactory inject_factory(test_factory); @@ -95,6 +131,10 @@ TEST(MatchWrapper, WithMatcher) { } TEST(MatchWrapper, WithMatcherInvalidDataInput) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.experimental_matching_api", "true"}}); + TestFactory test_factory; Envoy::Registry::InjectFactory inject_factory(test_factory); diff --git a/test/config_test/config_test.cc b/test/config_test/config_test.cc index 705811dc29e15..61cb500c1253f 100644 --- a/test/config_test/config_test.cc +++ b/test/config_test/config_test.cc @@ -85,6 +85,14 @@ class ConfigTest { ScopedRuntimeInjector scoped_runtime(server_.runtime()); ON_CALL(server_.runtime_loader_.snapshot_, deprecatedFeatureEnabled(_, _)) .WillByDefault(Invoke([](absl::string_view, bool default_value) { return default_value; })); + + // TODO(snowp): There's no way to override runtime flags per example file (since we mock out the + // runtime loader), so temporarily enable this flag explicitly here until we flip the default. + // This should allow the existing configuration examples to continue working despite the feature + // being disabled by default. + ON_CALL(*snapshot_, + runtimeFeatureEnabled("envoy.reloadable_features.experimental_matching_api")) + .WillByDefault(Return(true)); ON_CALL(server_.runtime_loader_, threadsafeSnapshot()).WillByDefault(Invoke([this]() { return snapshot_; })); @@ -156,7 +164,8 @@ class ConfigTest { Server::ListenerManagerImpl listener_manager_{server_, component_factory_, worker_factory_, false}; Random::RandomGeneratorImpl random_; - Runtime::SnapshotConstSharedPtr snapshot_{std::make_shared>()}; + std::shared_ptr snapshot_{ + std::make_shared>()}; NiceMock os_sys_calls_; TestThreadsafeSingletonInjector os_calls{&os_sys_calls_}; NiceMock file_system_; diff --git a/test/extensions/filters/http/composite/composite_filter_integration_test.cc b/test/extensions/filters/http/composite/composite_filter_integration_test.cc index 8a290b633f963..de0abba85a078 100644 --- a/test/extensions/filters/http/composite/composite_filter_integration_test.cc +++ b/test/extensions/filters/http/composite/composite_filter_integration_test.cc @@ -15,6 +15,8 @@ class CompositeFilterIntegrationTest : public testing::TestWithParamadd_clusters(); diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index e6481e7e89613..c75bb2c497b74 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -368,6 +368,7 @@ TEST_P(IntegrationTest, EnvoyProxying100ContinueWithDecodeDataPause) { // Verifies that we can construct a match tree with a filter, and that we are able to skip // filter invocation through the match tree. TEST_P(IntegrationTest, MatchingHttpFilterConstruction) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.experimental_matching_api", "true"); config_helper_.addFilter(R"EOF( name: matcher typed_config: From f97112c94c20a67542e01a741aec13a21449920a Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 12 May 2021 09:59:11 -0700 Subject: [PATCH 192/209] bazel: add --config=docker-asan and --config=remote-tsan. (#16451) Signed-off-by: Piotr Sikora --- .bazelrc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.bazelrc b/.bazelrc index 19c8eae5d70fe..95330f8ddbbd5 100644 --- a/.bazelrc +++ b/.bazelrc @@ -237,6 +237,10 @@ build:remote-msan --config=remote build:remote-msan --config=rbe-toolchain-clang-libc++ build:remote-msan --config=rbe-toolchain-msan +build:remote-tsan --config=remote +build:remote-tsan --config=rbe-toolchain-clang-libc++ +build:remote-tsan --config=rbe-toolchain-tsan + build:remote-msvc-cl --config=remote-windows build:remote-msvc-cl --config=msvc-cl build:remote-msvc-cl --config=rbe-toolchain-msvc-cl @@ -265,6 +269,10 @@ build:docker-clang-libc++ --config=rbe-toolchain-clang-libc++ build:docker-gcc --config=docker-sandbox build:docker-gcc --config=rbe-toolchain-gcc +build:docker-asan --config=docker-sandbox +build:docker-asan --config=rbe-toolchain-clang-libc++ +build:docker-asan --config=rbe-toolchain-asan + build:docker-msan --config=docker-sandbox build:docker-msan --config=rbe-toolchain-clang-libc++ build:docker-msan --config=rbe-toolchain-msan From ce071f6a7f5524e09c5d7bac75c9951e1b827a50 Mon Sep 17 00:00:00 2001 From: Vladimir Moskva Date: Wed, 12 May 2021 19:01:15 +0200 Subject: [PATCH 193/209] Update rules_apple (#16404) Signed-off-by: Vladimir Moskva --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 2a8cad76c73da..1e981bb96c883 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -39,10 +39,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Apple Rules for Bazel", project_desc = "Bazel rules for Apple platforms", project_url = "https://github.com/bazelbuild/rules_apple", - version = "0.19.0", - sha256 = "7a7afdd4869bb201c9352eed2daf37294d42b093579b70423490c1b4d4f6ce42", + version = "0.31.2", + sha256 = "c84962b64d9ae4472adfb01ec2cf1aa73cb2ee8308242add55fa7cc38602d882", urls = ["https://github.com/bazelbuild/rules_apple/releases/download/{version}/rules_apple.{version}.tar.gz"], - release_date = "2019-10-10", + release_date = "2021-05-04", use_category = ["build"], ), rules_fuzzing = dict( From c784d892dff532a2d6f03d4eea85110090d3a52f Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Wed, 12 May 2021 11:52:15 -0700 Subject: [PATCH 194/209] server: avoid flushing while a flush is in progress (#16370) In order to support calls to Server::Instance::flushStats while also having a flush timer activated we need to handle a flush being requested while in the process of doing a periodic flush. This PR avoids triggering a flush in these cases under the assumption that it is not needed since we're already flushing. Signed-off-by: Snow Pettersen --- source/server/server.cc | 11 +++++++- source/server/server.h | 3 +++ test/integration/server.h | 5 +++- test/server/server_test.cc | 51 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/source/server/server.cc b/source/server/server.cc index 1a94860f3b170..920bd37f55e41 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -89,7 +89,7 @@ InstanceImpl::InstanceImpl( : nullptr), grpc_context_(store.symbolTable()), http_context_(store.symbolTable()), router_context_(store.symbolTable()), process_context_(std::move(process_context)), - hooks_(hooks), server_contexts_(*this) { + hooks_(hooks), server_contexts_(*this), stats_flush_in_progress_(false) { TRY_ASSERT_MAIN_THREAD { if (!options.logPath().empty()) { TRY_ASSERT_MAIN_THREAD { @@ -206,6 +206,13 @@ void InstanceUtil::flushMetricsToSinks(const std::list& sinks, S } void InstanceImpl::flushStats() { + if (stats_flush_in_progress_) { + ENVOY_LOG(debug, "skipping stats flush as flush is already in progress"); + server_stats_->dropped_stat_flushes_.inc(); + return; + } + + stats_flush_in_progress_ = true; ENVOY_LOG(debug, "flushing stats"); // If Envoy is not fully initialized, workers will not be started and mergeHistograms // completion callback is not called immediately. As a result of this server stats will @@ -256,6 +263,8 @@ void InstanceImpl::flushStatsInternal() { if (stat_flush_timer_ != nullptr) { stat_flush_timer_->enableTimer(stats_config.flushInterval()); } + + stats_flush_in_progress_ = false; } bool InstanceImpl::healthCheckFailed() { return !live_.load(); } diff --git a/source/server/server.h b/source/server/server.h index fb8e6805fbd1a..0c6e027d58ecd 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -71,6 +71,7 @@ struct ServerCompilationSettingsStats { COUNTER(envoy_bug_failures) \ COUNTER(dynamic_unknown_fields) \ COUNTER(static_unknown_fields) \ + COUNTER(dropped_stat_flushes) \ GAUGE(concurrency, NeverImport) \ GAUGE(days_until_first_cert_expiring, Accumulate) \ GAUGE(seconds_until_first_ocsp_response_expiring, Accumulate) \ @@ -385,6 +386,8 @@ class InstanceImpl final : Logger::Loggable, ServerFactoryContextImpl server_contexts_; + bool stats_flush_in_progress_ : 1; + template class LifecycleCallbackHandle : public ServerLifecycleNotifier::Handle, RaiiListElement { public: diff --git a/test/integration/server.h b/test/integration/server.h index 6542afd361f33..d62e3f7dd5f48 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -373,11 +373,14 @@ class TestIsolatedStoreImpl : public StoreRoot { void setHistogramSettings(HistogramSettingsConstPtr&&) override {} void initializeThreading(Event::Dispatcher&, ThreadLocal::Instance&) override {} void shutdownThreading() override {} - void mergeHistograms(PostMergeCb) override {} + void mergeHistograms(PostMergeCb cb) override { merge_cb_ = cb; } + + void runMergeCallback() { merge_cb_(); } private: mutable Thread::MutexBasicLockable lock_; IsolatedStoreImpl store_; + PostMergeCb merge_cb_; }; } // namespace Stats diff --git a/test/server/server_test.cc b/test/server/server_test.cc index b885422ebad90..c7d8ef0692e6a 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -712,6 +712,57 @@ TEST_P(ServerInstanceImplTest, FlushStatsOnAdmin) { server_thread->join(); } +TEST_P(ServerInstanceImplTest, ConcurrentFlushes) { + CustomStatsSinkFactory factory; + Registry::InjectFactory registered(factory); + options_.bootstrap_version_ = 3; + + bool workers_started = false; + absl::Notification workers_started_fired; + // Run the server in a separate thread so we can test different lifecycle stages. + auto server_thread = Thread::threadFactoryForTest().createThread([&] { + auto hooks = CustomListenerHooks([&]() { + workers_started = true; + workers_started_fired.Notify(); + }); + initialize("test/server/test_data/server/stats_sink_manual_flush_bootstrap.yaml", false, hooks); + server_->run(); + server_ = nullptr; + thread_local_ = nullptr; + }); + + workers_started_fired.WaitForNotification(); + EXPECT_TRUE(workers_started); + + // Flush three times in a row. Two of these should get dropped. + server_->dispatcher().post([&] { + server_->flushStats(); + server_->flushStats(); + server_->flushStats(); + }); + + EXPECT_TRUE( + TestUtility::waitForCounterEq(stats_store_, "server.dropped_stat_flushes", 2, time_system_)); + + server_->dispatcher().post([&] { stats_store_.runMergeCallback(); }); + + EXPECT_TRUE(TestUtility::waitForCounterEq(stats_store_, "stats.flushed", 1, time_system_)); + + // Trigger another flush after the first one finished. This should go through an no drops should + // be recorded. + server_->dispatcher().post([&] { server_->flushStats(); }); + + server_->dispatcher().post([&] { stats_store_.runMergeCallback(); }); + + EXPECT_TRUE(TestUtility::waitForCounterEq(stats_store_, "stats.flushed", 2, time_system_)); + + EXPECT_TRUE( + TestUtility::waitForCounterEq(stats_store_, "server.dropped_stat_flushes", 2, time_system_)); + + server_->dispatcher().post([&] { server_->shutdown(); }); + server_thread->join(); +} + // Default validation mode TEST_P(ServerInstanceImplTest, ValidationDefault) { options_.service_cluster_name_ = "some_cluster_name"; From e7ddd61359a33c168a965242e5264b36fe57a885 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 12 May 2021 22:46:29 +0100 Subject: [PATCH 195/209] docs: Fix bootstrap docs version link (#16457) Signed-off-by: Ryan Northey --- api/envoy/config/bootstrap/v3/bootstrap.proto | 2 +- api/envoy/config/bootstrap/v4alpha/bootstrap.proto | 2 +- generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto | 2 +- .../envoy/config/bootstrap/v4alpha/bootstrap.proto | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/envoy/config/bootstrap/v3/bootstrap.proto b/api/envoy/config/bootstrap/v3/bootstrap.proto index 9404b7988ffaf..b179d3f4f6645 100644 --- a/api/envoy/config/bootstrap/v3/bootstrap.proto +++ b/api/envoy/config/bootstrap/v3/bootstrap.proto @@ -35,7 +35,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Bootstrap] // This proto is supplied via the :option:`-c` CLI flag and acts as the root -// of the Envoy v2 configuration. See the :ref:`v2 configuration overview +// of the Envoy v3 configuration. See the :ref:`v3 configuration overview // ` for more detail. // Bootstrap :ref:`configuration overview `. diff --git a/api/envoy/config/bootstrap/v4alpha/bootstrap.proto b/api/envoy/config/bootstrap/v4alpha/bootstrap.proto index 78fc749fb42f9..a6ec388d54c54 100644 --- a/api/envoy/config/bootstrap/v4alpha/bootstrap.proto +++ b/api/envoy/config/bootstrap/v4alpha/bootstrap.proto @@ -32,7 +32,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: Bootstrap] // This proto is supplied via the :option:`-c` CLI flag and acts as the root -// of the Envoy v2 configuration. See the :ref:`v2 configuration overview +// of the Envoy v3 configuration. See the :ref:`v3 configuration overview // ` for more detail. // Bootstrap :ref:`configuration overview `. diff --git a/generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto b/generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto index 2294fe1290714..20470800201d3 100644 --- a/generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto +++ b/generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto @@ -35,7 +35,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Bootstrap] // This proto is supplied via the :option:`-c` CLI flag and acts as the root -// of the Envoy v2 configuration. See the :ref:`v2 configuration overview +// of the Envoy v3 configuration. See the :ref:`v3 configuration overview // ` for more detail. // Bootstrap :ref:`configuration overview `. diff --git a/generated_api_shadow/envoy/config/bootstrap/v4alpha/bootstrap.proto b/generated_api_shadow/envoy/config/bootstrap/v4alpha/bootstrap.proto index 328599cd7bb35..b572cd301959d 100644 --- a/generated_api_shadow/envoy/config/bootstrap/v4alpha/bootstrap.proto +++ b/generated_api_shadow/envoy/config/bootstrap/v4alpha/bootstrap.proto @@ -34,7 +34,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: Bootstrap] // This proto is supplied via the :option:`-c` CLI flag and acts as the root -// of the Envoy v2 configuration. See the :ref:`v2 configuration overview +// of the Envoy v3 configuration. See the :ref:`v3 configuration overview // ` for more detail. // Bootstrap :ref:`configuration overview `. From bacffe49e9bd7d0f3b764c3ffbd280ab756f86ff Mon Sep 17 00:00:00 2001 From: code Date: Thu, 13 May 2021 05:53:06 +0800 Subject: [PATCH 196/209] remove trace drivers' dependency on HttpTracerImpl (#16244) This PR is part of #16049 to support general tracing. Please check #16049 get more details. Commit Message: remove trace drivers' dependency on HttpTracerImpl Additional Description: Now all tracers (zipkin, skywalking, etc.) will depend on HttpTracerImpl, making it difficult for Tracers to be reused by other protocols (Dubbo, Thrift, etc.). The purpose of this PR is to change this dependency. Risk Level: Low (No new logic). Testing: Add. Docs Changes: N/A Release Notes: N/A Signed-off-by: wbpcode --- include/envoy/server/tracer_config.h | 12 +- include/envoy/tracing/BUILD | 11 +- include/envoy/tracing/http_tracer.h | 167 +--------------- include/envoy/tracing/trace_driver.h | 183 ++++++++++++++++++ source/common/tracing/BUILD | 27 ++- source/common/tracing/common_values.h | 84 ++++++++ source/common/tracing/http_tracer_impl.cc | 2 +- source/common/tracing/http_tracer_impl.h | 104 +--------- .../tracing/http_tracer_manager_impl.cc | 4 +- source/common/tracing/null_span_impl.h | 36 ++++ ...cer_config_impl.h => tracer_config_impl.h} | 0 .../network/http_connection_manager/BUILD | 2 +- .../network/http_connection_manager/config.cc | 2 +- .../extensions/tracers/common/factory_base.h | 18 +- source/extensions/tracers/common/ot/BUILD | 11 +- .../common/ot/opentracing_driver_impl.cc | 4 +- .../common/ot/opentracing_driver_impl.h | 2 +- source/extensions/tracers/datadog/BUILD | 1 - source/extensions/tracers/datadog/config.cc | 13 +- source/extensions/tracers/datadog/config.h | 6 +- .../tracers/datadog/datadog_tracer_impl.cc | 1 - .../tracers/datadog/datadog_tracer_impl.h | 1 - .../extensions/tracers/dynamic_ot/config.cc | 9 +- source/extensions/tracers/dynamic_ot/config.h | 6 +- .../dynamic_opentracing_driver_impl.h | 1 - source/extensions/tracers/lightstep/config.cc | 7 +- source/extensions/tracers/lightstep/config.h | 6 +- .../tracers/lightstep/lightstep_tracer_impl.h | 1 - .../extensions/tracers/opencensus/config.cc | 18 +- source/extensions/tracers/opencensus/config.h | 8 +- .../opencensus/opencensus_tracer_impl.h | 2 +- .../extensions/tracers/skywalking/config.cc | 8 +- source/extensions/tracers/skywalking/config.h | 6 +- .../skywalking/skywalking_tracer_impl.h | 4 +- source/extensions/tracers/skywalking/tracer.h | 5 +- source/extensions/tracers/xray/BUILD | 5 +- source/extensions/tracers/xray/config.cc | 12 +- source/extensions/tracers/xray/config.h | 6 +- .../tracers/xray/xray_tracer_impl.h | 4 +- source/extensions/tracers/zipkin/config.cc | 8 +- source/extensions/tracers/zipkin/config.h | 6 +- .../tracers/zipkin/zipkin_tracer_impl.h | 4 +- test/common/tracing/BUILD | 2 +- test/common/tracing/http_tracer_impl_test.cc | 77 +++++++- .../tracing/http_tracer_manager_impl_test.cc | 46 +++-- .../extensions/tracers/datadog/config_test.cc | 2 +- .../tracers/dynamic_ot/config_test.cc | 2 +- .../tracers/lightstep/config_test.cc | 4 +- .../tracers/opencensus/config_test.cc | 36 ++-- .../tracers/skywalking/config_test.cc | 4 +- test/extensions/tracers/xray/config_test.cc | 10 +- test/extensions/tracers/zipkin/config_test.cc | 4 +- test/mocks/server/tracer_factory.h | 2 +- 53 files changed, 570 insertions(+), 436 deletions(-) create mode 100644 include/envoy/tracing/trace_driver.h create mode 100644 source/common/tracing/common_values.h create mode 100644 source/common/tracing/null_span_impl.h rename source/common/tracing/{http_tracer_config_impl.h => tracer_config_impl.h} (100%) diff --git a/include/envoy/server/tracer_config.h b/include/envoy/server/tracer_config.h index 25f8b664d9491..4af4caf45bc9a 100644 --- a/include/envoy/server/tracer_config.h +++ b/include/envoy/server/tracer_config.h @@ -41,13 +41,13 @@ class TracerFactory : public Config::TypedFactory { ~TracerFactory() override = default; /** - * Create a particular HttpTracer implementation. If the implementation is unable to produce an - * HttpTracer with the provided parameters, it should throw an EnvoyException in the case of - * general error or a Json::Exception if the json configuration is erroneous. The returned + * Create a particular trace driver implementation. If the implementation is unable to produce + * a trace driver with the provided parameters, it should throw an EnvoyException in the case + * of general error or a Json::Exception if the json configuration is erroneous. The returned * pointer should always be valid. * * NOTE: Due to the corner case of OpenCensus, who can only support a single tracing - * configuration per entire process, the returned HttpTracer instance is not guaranteed + * configuration per entire process, the returned Driver instance is not guaranteed * to be unique. * That is why the return type has been changed to std::shared_ptr<> instead of a more * idiomatic std::unique_ptr<>. @@ -55,8 +55,8 @@ class TracerFactory : public Config::TypedFactory { * @param config supplies the proto configuration for the HttpTracer * @param context supplies the factory context */ - virtual Tracing::HttpTracerSharedPtr createHttpTracer(const Protobuf::Message& config, - TracerFactoryContext& context) PURE; + virtual Tracing::DriverSharedPtr createTracerDriver(const Protobuf::Message& config, + TracerFactoryContext& context) PURE; std::string category() const override { return "envoy.tracers"; } }; diff --git a/include/envoy/tracing/BUILD b/include/envoy/tracing/BUILD index cd539650e0ea2..9bed839d05ae9 100644 --- a/include/envoy/tracing/BUILD +++ b/include/envoy/tracing/BUILD @@ -12,7 +12,7 @@ envoy_cc_library( name = "http_tracer_interface", hdrs = ["http_tracer.h"], deps = [ - ":trace_reason_interface", + ":trace_driver_interface", "//include/envoy/access_log:access_log_interface", "//include/envoy/http:header_map_interface", ], @@ -31,3 +31,12 @@ envoy_cc_library( name = "trace_reason_interface", hdrs = ["trace_reason.h"], ) + +envoy_cc_library( + name = "trace_driver_interface", + hdrs = ["trace_driver.h"], + deps = [ + ":trace_reason_interface", + "//include/envoy/stream_info:stream_info_interface", + ], +) diff --git a/include/envoy/tracing/http_tracer.h b/include/envoy/tracing/http_tracer.h index bfc29d370291f..34409fac312e4 100644 --- a/include/envoy/tracing/http_tracer.h +++ b/include/envoy/tracing/http_tracer.h @@ -7,177 +7,12 @@ #include "envoy/access_log/access_log.h" #include "envoy/common/pure.h" #include "envoy/http/header_map.h" +#include "envoy/tracing/trace_driver.h" #include "envoy/tracing/trace_reason.h" namespace Envoy { namespace Tracing { -class Span; -using SpanPtr = std::unique_ptr; - -constexpr uint32_t DefaultMaxPathTagLength = 256; - -enum class OperationName { Ingress, Egress }; - -/** - * The context for the custom tag to obtain the tag value. - */ -struct CustomTagContext { - const Http::RequestHeaderMap* request_headers; - const StreamInfo::StreamInfo& stream_info; -}; - -/** - * Tracing custom tag, with tag name and how it would be applied to the span. - */ -class CustomTag { -public: - virtual ~CustomTag() = default; - - /** - * @return the tag name view. - */ - virtual absl::string_view tag() const PURE; - - /** - * The way how to apply the custom tag to the span, - * generally obtain the tag value from the context and attached it to the span. - * @param span the active span. - * @param ctx the custom tag context. - */ - virtual void apply(Span& span, const CustomTagContext& ctx) const PURE; -}; - -using CustomTagConstSharedPtr = std::shared_ptr; -using CustomTagMap = absl::flat_hash_map; - -/** - * Tracing configuration, it carries additional data needed to populate the span. - */ -class Config { -public: - virtual ~Config() = default; - - /** - * @return operation name for tracing, e.g., ingress. - */ - virtual OperationName operationName() const PURE; - - /** - * @return custom tags to be attached to the active span. - */ - virtual const CustomTagMap* customTags() const PURE; - - /** - * @return true if spans should be annotated with more detailed information. - */ - virtual bool verbose() const PURE; - - /** - * @return the maximum length allowed for paths in the extracted HttpUrl tag. - */ - virtual uint32_t maxPathTagLength() const PURE; -}; - -/** - * Basic abstraction for span. - */ -class Span { -public: - virtual ~Span() = default; - - /** - * Set the operation name. - * @param operation the operation name - */ - virtual void setOperation(absl::string_view operation) PURE; - - /** - * Attach metadata to a Span, to be handled in an implementation-dependent fashion. - * @param name the name of the tag - * @param value the value to associate with the tag - */ - virtual void setTag(absl::string_view name, absl::string_view value) PURE; - - /** - * Record an event associated with a span, to be handled in an implementation-dependent fashion. - * @param timestamp the time of the event. - * @param event the name of the event. - */ - virtual void log(SystemTime timestamp, const std::string& event) PURE; - - /** - * Capture the final duration for this Span and carry out any work necessary to complete it. - * Once this method is called, the Span may be safely discarded. - */ - virtual void finishSpan() PURE; - - /** - * Mutate the provided headers with the context necessary to propagate this - * (implementation-specific) trace. - * @param request_headers the headers to which propagation context will be added - */ - virtual void injectContext(Http::RequestHeaderMap& request_headers) PURE; - - /** - * Create and start a child Span, with this Span as its parent in the trace. - * @param config the tracing configuration - * @param name operation name captured by the spawned child - * @param start_time initial start time for the operation captured by the child - */ - virtual SpanPtr spawnChild(const Config& config, const std::string& name, - SystemTime start_time) PURE; - - /** - * This method overrides any previous sampling decision associated with the trace instance. - * If the sampled parameter is false, this span and any subsequent child spans - * are not reported to the tracing system. - * @param sampled whether the span and any subsequent child spans should be sampled - */ - virtual void setSampled(bool sampled) PURE; - - /** - * Retrieve a key's value from the span's baggage. - * This baggage data could've been set by this span or any parent spans. - * @param key baggage key - * @return the baggage's value for the given input key - */ - virtual std::string getBaggage(absl::string_view key) PURE; - - /** - * Set a key/value pair in the current span's baggage. - * All subsequent child spans will have access to this baggage. - * @param key baggage key - * @param key baggage value - */ - virtual void setBaggage(absl::string_view key, absl::string_view value) PURE; - - /** - * Retrieve the trace ID associated with this span. - * The trace id may be generated for this span, propagated by parent spans, or - * not created yet. - * @return trace ID as a hex string - */ - virtual std::string getTraceIdAsHex() const PURE; -}; - -/** - * Tracing driver is responsible for span creation. - */ -class Driver { -public: - virtual ~Driver() = default; - - /** - * Start driver specific span. - */ - virtual SpanPtr startSpan(const Config& config, Http::RequestHeaderMap& request_headers, - const std::string& operation_name, SystemTime start_time, - const Tracing::Decision tracing_decision) PURE; -}; - -using DriverPtr = std::unique_ptr; - /** * HttpTracer is responsible for handling traces and delegate actions to the * corresponding drivers. diff --git a/include/envoy/tracing/trace_driver.h b/include/envoy/tracing/trace_driver.h new file mode 100644 index 0000000000000..7adb2222cacd4 --- /dev/null +++ b/include/envoy/tracing/trace_driver.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#include +#include + +#include "envoy/common/pure.h" +#include "envoy/stream_info/stream_info.h" +#include "envoy/tracing/trace_reason.h" + +namespace Envoy { +namespace Tracing { + +class Span; +using SpanPtr = std::unique_ptr; + +constexpr uint32_t DefaultMaxPathTagLength = 256; + +enum class OperationName { Ingress, Egress }; + +/** + * The context for the custom tag to obtain the tag value. + */ +struct CustomTagContext { + const Http::RequestHeaderMap* request_headers; + const StreamInfo::StreamInfo& stream_info; +}; + +/** + * Tracing custom tag, with tag name and how it would be applied to the span. + */ +class CustomTag { +public: + virtual ~CustomTag() = default; + + /** + * @return the tag name view. + */ + virtual absl::string_view tag() const PURE; + + /** + * The way how to apply the custom tag to the span, + * generally obtain the tag value from the context and attached it to the span. + * @param span the active span. + * @param ctx the custom tag context. + */ + virtual void apply(Span& span, const CustomTagContext& ctx) const PURE; +}; + +using CustomTagConstSharedPtr = std::shared_ptr; +using CustomTagMap = absl::flat_hash_map; + +/** + * Tracing configuration, it carries additional data needed to populate the span. + */ +class Config { +public: + virtual ~Config() = default; + + /** + * @return operation name for tracing, e.g., ingress. + */ + virtual OperationName operationName() const PURE; + + /** + * @return custom tags to be attached to the active span. + */ + virtual const CustomTagMap* customTags() const PURE; + + /** + * @return true if spans should be annotated with more detailed information. + */ + virtual bool verbose() const PURE; + + /** + * @return the maximum length allowed for paths in the extracted HttpUrl tag. This is only used + * for HTTP protocol tracing. + */ + virtual uint32_t maxPathTagLength() const PURE; +}; + +/** + * Basic abstraction for span. + */ +class Span { +public: + virtual ~Span() = default; + + /** + * Set the operation name. + * @param operation the operation name + */ + virtual void setOperation(absl::string_view operation) PURE; + + /** + * Attach metadata to a Span, to be handled in an implementation-dependent fashion. + * @param name the name of the tag + * @param value the value to associate with the tag + */ + virtual void setTag(absl::string_view name, absl::string_view value) PURE; + + /** + * Record an event associated with a span, to be handled in an implementation-dependent fashion. + * @param timestamp the time of the event. + * @param event the name of the event. + */ + virtual void log(SystemTime timestamp, const std::string& event) PURE; + + /** + * Capture the final duration for this Span and carry out any work necessary to complete it. + * Once this method is called, the Span may be safely discarded. + */ + virtual void finishSpan() PURE; + + /** + * Mutate the provided headers with the context necessary to propagate this + * (implementation-specific) trace. + * @param request_headers the headers to which propagation context will be added + */ + virtual void injectContext(Http::RequestHeaderMap& request_headers) PURE; + + /** + * Create and start a child Span, with this Span as its parent in the trace. + * @param config the tracing configuration + * @param name operation name captured by the spawned child + * @param start_time initial start time for the operation captured by the child + */ + virtual SpanPtr spawnChild(const Config& config, const std::string& name, + SystemTime start_time) PURE; + + /** + * This method overrides any previous sampling decision associated with the trace instance. + * If the sampled parameter is false, this span and any subsequent child spans + * are not reported to the tracing system. + * @param sampled whether the span and any subsequent child spans should be sampled + */ + virtual void setSampled(bool sampled) PURE; + + /** + * Retrieve a key's value from the span's baggage. + * This baggage data could've been set by this span or any parent spans. + * @param key baggage key + * @return the baggage's value for the given input key + */ + virtual std::string getBaggage(absl::string_view key) PURE; + + /** + * Set a key/value pair in the current span's baggage. + * All subsequent child spans will have access to this baggage. + * @param key baggage key + * @param key baggage value + */ + virtual void setBaggage(absl::string_view key, absl::string_view value) PURE; + + /** + * Retrieve the trace ID associated with this span. + * The trace id may be generated for this span, propagated by parent spans, or + * not created yet. + * @return trace ID as a hex string + */ + virtual std::string getTraceIdAsHex() const PURE; +}; + +/** + * Tracing driver is responsible for span creation. + */ +class Driver { +public: + virtual ~Driver() = default; + + /** + * Start driver specific span. + */ + virtual SpanPtr startSpan(const Config& config, Http::RequestHeaderMap& request_headers, + const std::string& operation_name, SystemTime start_time, + const Tracing::Decision tracing_decision) PURE; +}; + +using DriverPtr = std::unique_ptr; +using DriverSharedPtr = std::shared_ptr; + +} // namespace Tracing +} // namespace Envoy diff --git a/source/common/tracing/BUILD b/source/common/tracing/BUILD index ef703c964a904..8dce4bdd7b64f 100644 --- a/source/common/tracing/BUILD +++ b/source/common/tracing/BUILD @@ -8,6 +8,27 @@ licenses(["notice"]) # Apache 2 envoy_package() +envoy_cc_library( + name = "null_span_lib", + hdrs = [ + "null_span_impl.h", + ], + deps = [ + "//include/envoy/tracing:trace_driver_interface", + "//source/common/common:empty_string", + ], +) + +envoy_cc_library( + name = "common_values_lib", + hdrs = [ + "common_values.h", + ], + deps = [ + "//source/common/singleton:const_singleton", + ], +) + envoy_cc_library( name = "http_tracer_lib", srcs = [ @@ -17,6 +38,8 @@ envoy_cc_library( "http_tracer_impl.h", ], deps = [ + ":common_values_lib", + ":null_span_lib", "//include/envoy/http:request_id_extension_interface", "//include/envoy/local_info:local_info_interface", "//include/envoy/runtime:runtime_interface", @@ -45,9 +68,9 @@ envoy_cc_library( ) envoy_cc_library( - name = "http_tracer_config_lib", + name = "tracer_config_lib", hdrs = [ - "http_tracer_config_impl.h", + "tracer_config_impl.h", ], deps = [ "//include/envoy/server:tracer_config_interface", diff --git a/source/common/tracing/common_values.h b/source/common/tracing/common_values.h new file mode 100644 index 0000000000000..41cb3947772f4 --- /dev/null +++ b/source/common/tracing/common_values.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +#include "common/singleton/const_singleton.h" + +namespace Envoy { +namespace Tracing { + +/** + * Tracing tag names. + */ +class TracingTagValues { +public: + // OpenTracing standard tag names. + const std::string Component = "component"; + const std::string DbInstance = "db.instance"; + const std::string DbStatement = "db.statement"; + const std::string DbUser = "db.user"; + const std::string DbType = "db.type"; + const std::string Error = "error"; + const std::string HttpMethod = "http.method"; + const std::string HttpStatusCode = "http.status_code"; + const std::string HttpUrl = "http.url"; + const std::string MessageBusDestination = "message_bus.destination"; + const std::string PeerAddress = "peer.address"; + const std::string PeerHostname = "peer.hostname"; + const std::string PeerIpv4 = "peer.ipv4"; + const std::string PeerIpv6 = "peer.ipv6"; + const std::string PeerPort = "peer.port"; + const std::string PeerService = "peer.service"; + const std::string SpanKind = "span.kind"; + + // Non-standard tag names. + const std::string DownstreamCluster = "downstream_cluster"; + const std::string ErrorReason = "error.reason"; + const std::string GrpcAuthority = "grpc.authority"; + const std::string GrpcContentType = "grpc.content_type"; + const std::string GrpcMessage = "grpc.message"; + const std::string GrpcPath = "grpc.path"; + const std::string GrpcStatusCode = "grpc.status_code"; + const std::string GrpcTimeout = "grpc.timeout"; + const std::string GuidXClientTraceId = "guid:x-client-trace-id"; + const std::string GuidXRequestId = "guid:x-request-id"; + const std::string HttpProtocol = "http.protocol"; + const std::string NodeId = "node_id"; + const std::string RequestSize = "request_size"; + const std::string ResponseFlags = "response_flags"; + const std::string ResponseSize = "response_size"; + const std::string RetryCount = "retry.count"; + const std::string Status = "status"; + const std::string UpstreamAddress = "upstream_address"; + const std::string UpstreamCluster = "upstream_cluster"; + const std::string UpstreamClusterName = "upstream_cluster.name"; + const std::string UserAgent = "user_agent"; + const std::string Zone = "zone"; + + // Tag values. + const std::string Canceled = "canceled"; + const std::string Proxy = "proxy"; + const std::string True = "true"; +}; + +using Tags = ConstSingleton; + +class TracingLogValues { +public: + // OpenTracing standard key names. + const std::string EventKey = "event"; + + // Event names + const std::string LastDownstreamRxByteReceived = "last_downstream_rx_byte_received"; + const std::string FirstUpstreamTxByteSent = "first_upstream_tx_byte_sent"; + const std::string LastUpstreamTxByteSent = "last_upstream_tx_byte_sent"; + const std::string FirstUpstreamRxByteReceived = "first_upstream_rx_byte_received"; + const std::string LastUpstreamRxByteReceived = "last_upstream_rx_byte_received"; + const std::string FirstDownstreamTxByteSent = "first_downstream_tx_byte_sent"; + const std::string LastDownstreamTxByteSent = "last_downstream_tx_byte_sent"; +}; + +using Logs = ConstSingleton; + +} // namespace Tracing +} // namespace Envoy diff --git a/source/common/tracing/http_tracer_impl.cc b/source/common/tracing/http_tracer_impl.cc index 32c3a5224aa50..df9d29b4a72a1 100644 --- a/source/common/tracing/http_tracer_impl.cc +++ b/source/common/tracing/http_tracer_impl.cc @@ -272,7 +272,7 @@ HttpTracerUtility::createCustomTag(const envoy::type::tracing::v3::CustomTag& ta } } -HttpTracerImpl::HttpTracerImpl(DriverPtr&& driver, const LocalInfo::LocalInfo& local_info) +HttpTracerImpl::HttpTracerImpl(DriverSharedPtr driver, const LocalInfo::LocalInfo& local_info) : driver_(std::move(driver)), local_info_(local_info) {} SpanPtr HttpTracerImpl::startSpan(const Config& config, Http::RequestHeaderMap& request_headers, diff --git a/source/common/tracing/http_tracer_impl.h b/source/common/tracing/http_tracer_impl.h index 58950449adc2d..4c3b733ab0904 100644 --- a/source/common/tracing/http_tracer_impl.h +++ b/source/common/tracing/http_tracer_impl.h @@ -13,87 +13,15 @@ #include "envoy/type/tracing/v3/custom_tag.pb.h" #include "envoy/upstream/cluster_manager.h" -#include "common/common/empty_string.h" #include "common/config/metadata.h" #include "common/http/header_map_impl.h" #include "common/json/json_loader.h" +#include "common/tracing/common_values.h" +#include "common/tracing/null_span_impl.h" namespace Envoy { namespace Tracing { -/** - * Tracing tag names. - */ -class TracingTagValues { -public: - // OpenTracing standard tag names. - const std::string Component = "component"; - const std::string DbInstance = "db.instance"; - const std::string DbStatement = "db.statement"; - const std::string DbUser = "db.user"; - const std::string DbType = "db.type"; - const std::string Error = "error"; - const std::string HttpMethod = "http.method"; - const std::string HttpStatusCode = "http.status_code"; - const std::string HttpUrl = "http.url"; - const std::string MessageBusDestination = "message_bus.destination"; - const std::string PeerAddress = "peer.address"; - const std::string PeerHostname = "peer.hostname"; - const std::string PeerIpv4 = "peer.ipv4"; - const std::string PeerIpv6 = "peer.ipv6"; - const std::string PeerPort = "peer.port"; - const std::string PeerService = "peer.service"; - const std::string SpanKind = "span.kind"; - - // Non-standard tag names. - const std::string DownstreamCluster = "downstream_cluster"; - const std::string ErrorReason = "error.reason"; - const std::string GrpcAuthority = "grpc.authority"; - const std::string GrpcContentType = "grpc.content_type"; - const std::string GrpcMessage = "grpc.message"; - const std::string GrpcPath = "grpc.path"; - const std::string GrpcStatusCode = "grpc.status_code"; - const std::string GrpcTimeout = "grpc.timeout"; - const std::string GuidXClientTraceId = "guid:x-client-trace-id"; - const std::string GuidXRequestId = "guid:x-request-id"; - const std::string HttpProtocol = "http.protocol"; - const std::string NodeId = "node_id"; - const std::string RequestSize = "request_size"; - const std::string ResponseFlags = "response_flags"; - const std::string ResponseSize = "response_size"; - const std::string RetryCount = "retry.count"; - const std::string Status = "status"; - const std::string UpstreamAddress = "upstream_address"; - const std::string UpstreamCluster = "upstream_cluster"; - const std::string UpstreamClusterName = "upstream_cluster.name"; - const std::string UserAgent = "user_agent"; - const std::string Zone = "zone"; - - // Tag values. - const std::string Canceled = "canceled"; - const std::string Proxy = "proxy"; - const std::string True = "true"; -}; - -using Tags = ConstSingleton; - -class TracingLogValues { -public: - // OpenTracing standard key names. - const std::string EventKey = "event"; - - // Event names - const std::string LastDownstreamRxByteReceived = "last_downstream_rx_byte_received"; - const std::string FirstUpstreamTxByteSent = "first_upstream_tx_byte_sent"; - const std::string LastUpstreamTxByteSent = "last_upstream_tx_byte_sent"; - const std::string FirstUpstreamRxByteReceived = "first_upstream_rx_byte_received"; - const std::string LastUpstreamRxByteReceived = "last_upstream_rx_byte_received"; - const std::string FirstDownstreamTxByteSent = "first_downstream_tx_byte_sent"; - const std::string LastDownstreamTxByteSent = "last_downstream_tx_byte_sent"; -}; - -using Logs = ConstSingleton; - class HttpTracerUtility { public: /** @@ -157,28 +85,6 @@ class EgressConfigImpl : public Config { using EgressConfig = ConstSingleton; -class NullSpan : public Span { -public: - static NullSpan& instance() { - static NullSpan* instance = new NullSpan(); - return *instance; - } - - // Tracing::Span - void setOperation(absl::string_view) override {} - void setTag(absl::string_view, absl::string_view) override {} - void log(SystemTime, const std::string&) override {} - void finishSpan() override {} - void injectContext(Http::RequestHeaderMap&) override {} - void setBaggage(absl::string_view, absl::string_view) override {} - std::string getBaggage(absl::string_view) override { return EMPTY_STRING; } - std::string getTraceIdAsHex() const override { return EMPTY_STRING; } - SpanPtr spawnChild(const Config&, const std::string&, SystemTime) override { - return SpanPtr{new NullSpan()}; - } - void setSampled(bool) override {} -}; - class HttpNullTracer : public HttpTracer { public: // Tracing::HttpTracer @@ -190,15 +96,17 @@ class HttpNullTracer : public HttpTracer { class HttpTracerImpl : public HttpTracer { public: - HttpTracerImpl(DriverPtr&& driver, const LocalInfo::LocalInfo& local_info); + HttpTracerImpl(DriverSharedPtr driver, const LocalInfo::LocalInfo& local_info); // Tracing::HttpTracer SpanPtr startSpan(const Config& config, Http::RequestHeaderMap& request_headers, const StreamInfo::StreamInfo& stream_info, const Tracing::Decision tracing_decision) override; + DriverSharedPtr driverForTest() const { return driver_; } + private: - DriverPtr driver_; + DriverSharedPtr driver_; const LocalInfo::LocalInfo& local_info_; }; diff --git a/source/common/tracing/http_tracer_manager_impl.cc b/source/common/tracing/http_tracer_manager_impl.cc index 125e2a4e4e8bf..e25f5d99ba2d9 100644 --- a/source/common/tracing/http_tracer_manager_impl.cc +++ b/source/common/tracing/http_tracer_manager_impl.cc @@ -46,7 +46,9 @@ HttpTracerManagerImpl::getOrCreateHttpTracer(const envoy::config::trace::v3::Tra ProtobufTypes::MessagePtr message = Envoy::Config::Utility::translateToFactoryConfig( *config, factory_context_->messageValidationVisitor(), factory); - HttpTracerSharedPtr http_tracer = factory.createHttpTracer(*message, *factory_context_); + HttpTracerSharedPtr http_tracer = std::make_shared( + factory.createTracerDriver(*message, *factory_context_), + factory_context_->serverFactoryContext().localInfo()); http_tracers_.emplace(cache_key, http_tracer); // cache a weak reference return http_tracer; } diff --git a/source/common/tracing/null_span_impl.h b/source/common/tracing/null_span_impl.h new file mode 100644 index 0000000000000..18677818dfcaa --- /dev/null +++ b/source/common/tracing/null_span_impl.h @@ -0,0 +1,36 @@ +#pragma once + +#include "envoy/tracing/trace_driver.h" + +#include "common/common/empty_string.h" + +namespace Envoy { +namespace Tracing { + +/** + * Null implementation of Span. + */ +class NullSpan : public Span { +public: + static NullSpan& instance() { + static NullSpan* instance = new NullSpan(); + return *instance; + } + + // Tracing::Span + void setOperation(absl::string_view) override {} + void setTag(absl::string_view, absl::string_view) override {} + void log(SystemTime, const std::string&) override {} + void finishSpan() override {} + void injectContext(Http::RequestHeaderMap&) override {} + void setBaggage(absl::string_view, absl::string_view) override {} + std::string getBaggage(absl::string_view) override { return EMPTY_STRING; } + std::string getTraceIdAsHex() const override { return EMPTY_STRING; } + SpanPtr spawnChild(const Config&, const std::string&, SystemTime) override { + return SpanPtr{new NullSpan()}; + } + void setSampled(bool) override {} +}; + +} // namespace Tracing +} // namespace Envoy diff --git a/source/common/tracing/http_tracer_config_impl.h b/source/common/tracing/tracer_config_impl.h similarity index 100% rename from source/common/tracing/http_tracer_config_impl.h rename to source/common/tracing/tracer_config_impl.h diff --git a/source/extensions/filters/network/http_connection_manager/BUILD b/source/extensions/filters/network/http_connection_manager/BUILD index fd9234f3ea93a..60c8ce180e04e 100644 --- a/source/extensions/filters/network/http_connection_manager/BUILD +++ b/source/extensions/filters/network/http_connection_manager/BUILD @@ -52,9 +52,9 @@ envoy_cc_extension( "//source/common/router:rds_lib", "//source/common/router:scoped_rds_lib", "//source/common/runtime:runtime_lib", - "//source/common/tracing:http_tracer_config_lib", "//source/common/tracing:http_tracer_lib", "//source/common/tracing:http_tracer_manager_lib", + "//source/common/tracing:tracer_config_lib", "//source/extensions/filters/http/common:pass_through_filter_lib", "//source/extensions/filters/network:well_known_names", "//source/extensions/filters/network/common:factory_base_lib", diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index f6b416fde5e26..03669895e655f 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -33,8 +33,8 @@ #include "common/router/rds_impl.h" #include "common/router/scoped_rds.h" #include "common/runtime/runtime_impl.h" -#include "common/tracing/http_tracer_config_impl.h" #include "common/tracing/http_tracer_manager_impl.h" +#include "common/tracing/tracer_config_impl.h" #ifdef ENVOY_ENABLE_QUIC #include "common/quic/codec_impl.h" diff --git a/source/extensions/tracers/common/factory_base.h b/source/extensions/tracers/common/factory_base.h index 03e45d7615595..b31d22c3c28cc 100644 --- a/source/extensions/tracers/common/factory_base.h +++ b/source/extensions/tracers/common/factory_base.h @@ -14,12 +14,12 @@ namespace Common { template class FactoryBase : public Server::Configuration::TracerFactory { public: // Server::Configuration::TracerFactory - Tracing::HttpTracerSharedPtr - createHttpTracer(const Protobuf::Message& config, - Server::Configuration::TracerFactoryContext& context) override { - return createHttpTracerTyped(MessageUtil::downcastAndValidate( - config, context.messageValidationVisitor()), - context); + Tracing::DriverSharedPtr + createTracerDriver(const Protobuf::Message& config, + Server::Configuration::TracerFactoryContext& context) override { + return createTracerDriverTyped(MessageUtil::downcastAndValidate( + config, context.messageValidationVisitor()), + context); } ProtobufTypes::MessagePtr createEmptyConfigProto() override { @@ -32,9 +32,9 @@ template class FactoryBase : public Server::Configuration::T FactoryBase(const std::string& name) : name_(name) {} private: - virtual Tracing::HttpTracerSharedPtr - createHttpTracerTyped(const ConfigProto& proto_config, - Server::Configuration::TracerFactoryContext& context) PURE; + virtual Tracing::DriverSharedPtr + createTracerDriverTyped(const ConfigProto& proto_config, + Server::Configuration::TracerFactoryContext& context) PURE; const std::string name_; }; diff --git a/source/extensions/tracers/common/ot/BUILD b/source/extensions/tracers/common/ot/BUILD index beced5b3f219e..c8b386d0c3c87 100644 --- a/source/extensions/tracers/common/ot/BUILD +++ b/source/extensions/tracers/common/ot/BUILD @@ -18,6 +18,15 @@ envoy_cc_library( ], external_deps = ["opentracing"], deps = [ - "//source/common/tracing:http_tracer_lib", + "//include/envoy/runtime:runtime_interface", + "//include/envoy/thread_local:thread_local_interface", + "//include/envoy/tracing:trace_driver_interface", + "//include/envoy/upstream:cluster_manager_interface", + "//source/common/common:base64_lib", + "//source/common/common:empty_string", + "//source/common/http:header_map_lib", + "//source/common/json:json_loader_lib", + "//source/common/tracing:common_values_lib", + "//source/common/tracing:null_span_lib", ], ) diff --git a/source/extensions/tracers/common/ot/opentracing_driver_impl.cc b/source/extensions/tracers/common/ot/opentracing_driver_impl.cc index 18bf1e828dcc1..92a6b07d634f6 100644 --- a/source/extensions/tracers/common/ot/opentracing_driver_impl.cc +++ b/source/extensions/tracers/common/ot/opentracing_driver_impl.cc @@ -7,7 +7,9 @@ #include "common/common/assert.h" #include "common/common/base64.h" #include "common/common/utility.h" -#include "common/tracing/http_tracer_impl.h" +#include "common/http/header_map_impl.h" +#include "common/tracing/common_values.h" +#include "common/tracing/null_span_impl.h" namespace Envoy { namespace Extensions { diff --git a/source/extensions/tracers/common/ot/opentracing_driver_impl.h b/source/extensions/tracers/common/ot/opentracing_driver_impl.h index ef10c15926677..d214d967a287e 100644 --- a/source/extensions/tracers/common/ot/opentracing_driver_impl.h +++ b/source/extensions/tracers/common/ot/opentracing_driver_impl.h @@ -3,7 +3,7 @@ #include #include "envoy/stats/scope.h" -#include "envoy/tracing/http_tracer.h" +#include "envoy/tracing/trace_driver.h" #include "common/common/empty_string.h" #include "common/common/logger.h" diff --git a/source/extensions/tracers/datadog/BUILD b/source/extensions/tracers/datadog/BUILD index d294f3e56a41e..164a1d73c1f94 100644 --- a/source/extensions/tracers/datadog/BUILD +++ b/source/extensions/tracers/datadog/BUILD @@ -23,7 +23,6 @@ envoy_cc_library( deps = [ "//source/common/config:utility_lib", "//source/common/http:async_client_utility_lib", - "//source/common/tracing:http_tracer_lib", "//source/common/upstream:cluster_update_tracker_lib", "//source/common/version:version_lib", "//source/extensions/tracers/common/ot:opentracing_driver_lib", diff --git a/source/extensions/tracers/datadog/config.cc b/source/extensions/tracers/datadog/config.cc index 1113708d5fde2..cdfcafdddf38f 100644 --- a/source/extensions/tracers/datadog/config.cc +++ b/source/extensions/tracers/datadog/config.cc @@ -5,7 +5,6 @@ #include "envoy/registry/registry.h" #include "common/common/utility.h" -#include "common/tracing/http_tracer_impl.h" #include "extensions/tracers/datadog/datadog_tracer_impl.h" @@ -18,15 +17,13 @@ namespace Datadog { DatadogTracerFactory::DatadogTracerFactory() : FactoryBase("envoy.tracers.datadog") {} -Tracing::HttpTracerSharedPtr DatadogTracerFactory::createHttpTracerTyped( +Tracing::DriverSharedPtr DatadogTracerFactory::createTracerDriverTyped( const envoy::config::trace::v3::DatadogConfig& proto_config, Server::Configuration::TracerFactoryContext& context) { - Tracing::DriverPtr datadog_driver = std::make_unique( - proto_config, context.serverFactoryContext().clusterManager(), - context.serverFactoryContext().scope(), context.serverFactoryContext().threadLocal(), - context.serverFactoryContext().runtime()); - return std::make_shared(std::move(datadog_driver), - context.serverFactoryContext().localInfo()); + return std::make_shared(proto_config, context.serverFactoryContext().clusterManager(), + context.serverFactoryContext().scope(), + context.serverFactoryContext().threadLocal(), + context.serverFactoryContext().runtime()); } /** diff --git a/source/extensions/tracers/datadog/config.h b/source/extensions/tracers/datadog/config.h index 324c82e85f9b1..1808fddff0033 100644 --- a/source/extensions/tracers/datadog/config.h +++ b/source/extensions/tracers/datadog/config.h @@ -21,9 +21,9 @@ class DatadogTracerFactory : public Common::FactoryBase( - context.serverFactoryContext().scope(), library, config); - return std::make_shared(std::move(dynamic_driver), - context.serverFactoryContext().localInfo()); + return std::make_shared(context.serverFactoryContext().scope(), library, + config); } /** diff --git a/source/extensions/tracers/dynamic_ot/config.h b/source/extensions/tracers/dynamic_ot/config.h index 05fd2873132b8..4b9a761a262ea 100644 --- a/source/extensions/tracers/dynamic_ot/config.h +++ b/source/extensions/tracers/dynamic_ot/config.h @@ -20,9 +20,9 @@ class DynamicOpenTracingTracerFactory private: // FactoryBase - Tracing::HttpTracerSharedPtr - createHttpTracerTyped(const envoy::config::trace::v3::DynamicOtConfig& configuration, - Server::Configuration::TracerFactoryContext& context) override; + Tracing::DriverSharedPtr + createTracerDriverTyped(const envoy::config::trace::v3::DynamicOtConfig& configuration, + Server::Configuration::TracerFactoryContext& context) override; }; } // namespace DynamicOt diff --git a/source/extensions/tracers/dynamic_ot/dynamic_opentracing_driver_impl.h b/source/extensions/tracers/dynamic_ot/dynamic_opentracing_driver_impl.h index a872088b8e111..668e3f99ad739 100644 --- a/source/extensions/tracers/dynamic_ot/dynamic_opentracing_driver_impl.h +++ b/source/extensions/tracers/dynamic_ot/dynamic_opentracing_driver_impl.h @@ -2,7 +2,6 @@ #include "envoy/runtime/runtime.h" #include "envoy/thread_local/thread_local.h" -#include "envoy/tracing/http_tracer.h" #include "envoy/upstream/cluster_manager.h" #include "extensions/tracers/common/ot/opentracing_driver_impl.h" diff --git a/source/extensions/tracers/lightstep/config.cc b/source/extensions/tracers/lightstep/config.cc index 04a5de828390d..49aa98ccaf4a5 100644 --- a/source/extensions/tracers/lightstep/config.cc +++ b/source/extensions/tracers/lightstep/config.cc @@ -6,7 +6,6 @@ #include "common/common/utility.h" #include "common/config/datasource.h" -#include "common/tracing/http_tracer_impl.h" #include "extensions/tracers/lightstep/lightstep_tracer_impl.h" @@ -19,7 +18,7 @@ namespace Lightstep { LightstepTracerFactory::LightstepTracerFactory() : FactoryBase("envoy.tracers.lightstep") {} -Tracing::HttpTracerSharedPtr LightstepTracerFactory::createHttpTracerTyped( +Tracing::DriverSharedPtr LightstepTracerFactory::createTracerDriverTyped( const envoy::config::trace::v3::LightstepConfig& proto_config, Server::Configuration::TracerFactoryContext& context) { auto opts = std::make_unique(); @@ -34,14 +33,12 @@ Tracing::HttpTracerSharedPtr LightstepTracerFactory::createHttpTracerTyped( } opts->component_name = context.serverFactoryContext().localInfo().clusterName(); - Tracing::DriverPtr lightstep_driver = std::make_unique( + return std::make_shared( proto_config, context.serverFactoryContext().clusterManager(), context.serverFactoryContext().scope(), context.serverFactoryContext().threadLocal(), context.serverFactoryContext().runtime(), std::move(opts), Common::Ot::OpenTracingDriver::PropagationMode::TracerNative, context.serverFactoryContext().grpcContext()); - return std::make_shared(std::move(lightstep_driver), - context.serverFactoryContext().localInfo()); } /** diff --git a/source/extensions/tracers/lightstep/config.h b/source/extensions/tracers/lightstep/config.h index f14a45470ecf1..33cec8da3d605 100644 --- a/source/extensions/tracers/lightstep/config.h +++ b/source/extensions/tracers/lightstep/config.h @@ -20,9 +20,9 @@ class LightstepTracerFactory private: // FactoryBase - Tracing::HttpTracerSharedPtr - createHttpTracerTyped(const envoy::config::trace::v3::LightstepConfig& proto_config, - Server::Configuration::TracerFactoryContext& context) override; + Tracing::DriverSharedPtr + createTracerDriverTyped(const envoy::config::trace::v3::LightstepConfig& proto_config, + Server::Configuration::TracerFactoryContext& context) override; }; } // namespace Lightstep diff --git a/source/extensions/tracers/lightstep/lightstep_tracer_impl.h b/source/extensions/tracers/lightstep/lightstep_tracer_impl.h index e99d92b5346e9..10820e4a3601e 100644 --- a/source/extensions/tracers/lightstep/lightstep_tracer_impl.h +++ b/source/extensions/tracers/lightstep/lightstep_tracer_impl.h @@ -7,7 +7,6 @@ #include "envoy/config/trace/v3/lightstep.pb.h" #include "envoy/runtime/runtime.h" #include "envoy/thread_local/thread_local.h" -#include "envoy/tracing/http_tracer.h" #include "envoy/upstream/cluster_manager.h" #include "common/buffer/buffer_impl.h" diff --git a/source/extensions/tracers/opencensus/config.cc b/source/extensions/tracers/opencensus/config.cc index 24a439a98a650..ae6756e9bbfff 100644 --- a/source/extensions/tracers/opencensus/config.cc +++ b/source/extensions/tracers/opencensus/config.cc @@ -4,8 +4,6 @@ #include "envoy/config/trace/v3/opencensus.pb.validate.h" #include "envoy/registry/registry.h" -#include "common/tracing/http_tracer_impl.h" - #include "extensions/tracers/opencensus/opencensus_tracer_impl.h" namespace Envoy { @@ -15,25 +13,23 @@ namespace OpenCensus { OpenCensusTracerFactory::OpenCensusTracerFactory() : FactoryBase("envoy.tracers.opencensus") {} -Tracing::HttpTracerSharedPtr OpenCensusTracerFactory::createHttpTracerTyped( +Tracing::DriverSharedPtr OpenCensusTracerFactory::createTracerDriverTyped( const envoy::config::trace::v3::OpenCensusConfig& proto_config, Server::Configuration::TracerFactoryContext& context) { // Since OpenCensus can only support a single tracing configuration per entire process, // we need to make sure that it is configured at most once. - if (tracer_) { + if (driver_) { if (Envoy::Protobuf::util::MessageDifferencer::Equals(config_, proto_config)) { - return tracer_; + return driver_; } else { throw EnvoyException("Opencensus has already been configured with a different config."); } } - Tracing::DriverPtr driver = - std::make_unique(proto_config, context.serverFactoryContext().localInfo(), - context.serverFactoryContext().api()); - tracer_ = std::make_shared(std::move(driver), - context.serverFactoryContext().localInfo()); + + driver_ = std::make_shared(proto_config, context.serverFactoryContext().localInfo(), + context.serverFactoryContext().api()); config_ = proto_config; - return tracer_; + return driver_; } /** diff --git a/source/extensions/tracers/opencensus/config.h b/source/extensions/tracers/opencensus/config.h index a5270a45cb865..5b853f9b9e1dd 100644 --- a/source/extensions/tracers/opencensus/config.h +++ b/source/extensions/tracers/opencensus/config.h @@ -22,13 +22,13 @@ class OpenCensusTracerFactory private: // FactoryBase - Tracing::HttpTracerSharedPtr - createHttpTracerTyped(const envoy::config::trace::v3::OpenCensusConfig& proto_config, - Server::Configuration::TracerFactoryContext& context) override; + Tracing::DriverSharedPtr + createTracerDriverTyped(const envoy::config::trace::v3::OpenCensusConfig& proto_config, + Server::Configuration::TracerFactoryContext& context) override; // Since OpenCensus can only support a single tracing configuration per entire process, // we need to make sure that it is configured at most once. - Tracing::HttpTracerSharedPtr tracer_; + Tracing::DriverSharedPtr driver_; envoy::config::trace::v3::OpenCensusConfig config_; }; diff --git a/source/extensions/tracers/opencensus/opencensus_tracer_impl.h b/source/extensions/tracers/opencensus/opencensus_tracer_impl.h index 2c06d0c49a5db..3d4cbf7cdae91 100644 --- a/source/extensions/tracers/opencensus/opencensus_tracer_impl.h +++ b/source/extensions/tracers/opencensus/opencensus_tracer_impl.h @@ -3,7 +3,7 @@ #include "envoy/api/api.h" #include "envoy/config/trace/v3/opencensus.pb.h" #include "envoy/local_info/local_info.h" -#include "envoy/tracing/http_tracer.h" +#include "envoy/tracing/trace_driver.h" #include "common/common/logger.h" diff --git a/source/extensions/tracers/skywalking/config.cc b/source/extensions/tracers/skywalking/config.cc index 4f9e15b12f2dc..12e1b43c01579 100644 --- a/source/extensions/tracers/skywalking/config.cc +++ b/source/extensions/tracers/skywalking/config.cc @@ -5,7 +5,6 @@ #include "envoy/registry/registry.h" #include "common/common/utility.h" -#include "common/tracing/http_tracer_impl.h" #include "extensions/tracers/skywalking/skywalking_tracer_impl.h" @@ -16,13 +15,10 @@ namespace SkyWalking { SkyWalkingTracerFactory::SkyWalkingTracerFactory() : FactoryBase("envoy.tracers.skywalking") {} -Tracing::HttpTracerSharedPtr SkyWalkingTracerFactory::createHttpTracerTyped( +Tracing::DriverSharedPtr SkyWalkingTracerFactory::createTracerDriverTyped( const envoy::config::trace::v3::SkyWalkingConfig& proto_config, Server::Configuration::TracerFactoryContext& context) { - Tracing::DriverPtr skywalking_driver = - std::make_unique(proto_config, context); - return std::make_shared(std::move(skywalking_driver), - context.serverFactoryContext().localInfo()); + return std::make_shared(proto_config, context); } /** diff --git a/source/extensions/tracers/skywalking/config.h b/source/extensions/tracers/skywalking/config.h index abeffe373e5d7..0fc1ad8dc9671 100644 --- a/source/extensions/tracers/skywalking/config.h +++ b/source/extensions/tracers/skywalking/config.h @@ -20,9 +20,9 @@ class SkyWalkingTracerFactory private: // FactoryBase - Tracing::HttpTracerSharedPtr - createHttpTracerTyped(const envoy::config::trace::v3::SkyWalkingConfig& proto_config, - Server::Configuration::TracerFactoryContext& context) override; + Tracing::DriverSharedPtr + createTracerDriverTyped(const envoy::config::trace::v3::SkyWalkingConfig& proto_config, + Server::Configuration::TracerFactoryContext& context) override; }; } // namespace SkyWalking diff --git a/source/extensions/tracers/skywalking/skywalking_tracer_impl.h b/source/extensions/tracers/skywalking/skywalking_tracer_impl.h index 0b64b37efe16c..a2443f10d2ba8 100644 --- a/source/extensions/tracers/skywalking/skywalking_tracer_impl.h +++ b/source/extensions/tracers/skywalking/skywalking_tracer_impl.h @@ -3,9 +3,7 @@ #include "envoy/config/trace/v3/skywalking.pb.h" #include "envoy/server/tracer_config.h" #include "envoy/thread_local/thread_local.h" -#include "envoy/tracing/http_tracer.h" - -#include "common/tracing/http_tracer_impl.h" +#include "envoy/tracing/trace_driver.h" #include "source/tracing_context_impl.h" diff --git a/source/extensions/tracers/skywalking/tracer.h b/source/extensions/tracers/skywalking/tracer.h index 3852d1718b90a..52e2a24fb18d1 100644 --- a/source/extensions/tracers/skywalking/tracer.h +++ b/source/extensions/tracers/skywalking/tracer.h @@ -2,7 +2,10 @@ #include -#include "common/tracing/http_tracer_impl.h" +#include "envoy/tracing/trace_driver.h" + +#include "common/tracing/common_values.h" +#include "common/tracing/null_span_impl.h" #include "extensions/tracers/skywalking/trace_segment_reporter.h" diff --git a/source/extensions/tracers/xray/BUILD b/source/extensions/tracers/xray/BUILD index d8779c3bf2f2c..797e8a84e407d 100644 --- a/source/extensions/tracers/xray/BUILD +++ b/source/extensions/tracers/xray/BUILD @@ -41,7 +41,7 @@ envoy_cc_library( ":daemon_cc_proto", "//include/envoy/common:time_interface", "//include/envoy/server:tracer_config_interface", - "//include/envoy/tracing:http_tracer_interface", + "//include/envoy/tracing:trace_driver_interface", "//source/common/common:hex_lib", "//source/common/common:macros", "//source/common/common:random_generator_lib", @@ -49,7 +49,8 @@ envoy_cc_library( "//source/common/json:json_loader_lib", "//source/common/protobuf:utility_lib", "//source/common/runtime:runtime_lib", - "//source/common/tracing:http_tracer_lib", + "//source/common/tracing:common_values_lib", + "//source/common/tracing:null_span_lib", ], ) diff --git a/source/extensions/tracers/xray/config.cc b/source/extensions/tracers/xray/config.cc index 96c2c5a10dbd9..ff9bd156ea7f3 100644 --- a/source/extensions/tracers/xray/config.cc +++ b/source/extensions/tracers/xray/config.cc @@ -9,7 +9,6 @@ #include "common/common/utility.h" #include "common/config/datasource.h" -#include "common/tracing/http_tracer_impl.h" #include "extensions/tracers/xray/xray_tracer_impl.h" @@ -20,9 +19,9 @@ namespace XRay { XRayTracerFactory::XRayTracerFactory() : FactoryBase("envoy.tracers.xray") {} -Tracing::HttpTracerSharedPtr -XRayTracerFactory::createHttpTracerTyped(const envoy::config::trace::v3::XRayConfig& proto_config, - Server::Configuration::TracerFactoryContext& context) { +Tracing::DriverSharedPtr +XRayTracerFactory::createTracerDriverTyped(const envoy::config::trace::v3::XRayConfig& proto_config, + Server::Configuration::TracerFactoryContext& context) { std::string sampling_rules_json; try { sampling_rules_json = Config::DataSource::read(proto_config.sampling_rule_manifest(), true, @@ -51,10 +50,7 @@ XRayTracerFactory::createHttpTracerTyped(const envoy::config::trace::v3::XRayCon XRayConfiguration xconfig{endpoint, proto_config.segment_name(), sampling_rules_json, origin, std::move(aws)}; - auto xray_driver = std::make_unique(xconfig, context); - - return std::make_shared(std::move(xray_driver), - context.serverFactoryContext().localInfo()); + return std::make_shared(xconfig, context); } /** diff --git a/source/extensions/tracers/xray/config.h b/source/extensions/tracers/xray/config.h index 69e1201de2f94..00c5b3d5fa8ef 100644 --- a/source/extensions/tracers/xray/config.h +++ b/source/extensions/tracers/xray/config.h @@ -20,9 +20,9 @@ class XRayTracerFactory : public Common::FactoryBase( + return std::make_shared( proto_config, context.serverFactoryContext().clusterManager(), context.serverFactoryContext().scope(), context.serverFactoryContext().threadLocal(), context.serverFactoryContext().runtime(), context.serverFactoryContext().localInfo(), context.serverFactoryContext().api().randomGenerator(), context.serverFactoryContext().timeSource()); - - return std::make_shared(std::move(zipkin_driver), - context.serverFactoryContext().localInfo()); } /** diff --git a/source/extensions/tracers/zipkin/config.h b/source/extensions/tracers/zipkin/config.h index b91ef7cb7f350..8c879e64f5243 100644 --- a/source/extensions/tracers/zipkin/config.h +++ b/source/extensions/tracers/zipkin/config.h @@ -19,9 +19,9 @@ class ZipkinTracerFactory : public Common::FactoryBase(); DriverPtr driver_ptr(driver_); tracer_ = std::make_shared(std::move(driver_ptr), local_info_); } Http::TestRequestHeaderMapImpl request_headers_{ {":path", "/"}, {":method", "GET"}, {"x-request-id", "foo"}, {":authority", "test"}}; - Http::TestResponseHeaderMapImpl response_headers; - Http::TestResponseTrailerMapImpl response_trailers; - StreamInfo::MockStreamInfo stream_info_; + Http::TestResponseHeaderMapImpl response_headers_{{":status", "200"}, + {"content-type", "application/grpc"}, + {"grpc-status", "7"}, + {"grpc-message", "permission denied"}}; + Http::TestResponseTrailerMapImpl response_trailers_; + NiceMock stream_info_; NiceMock local_info_; - MockConfig config_; - MockDriver* driver_; + NiceMock config_; + NiceMock* driver_; HttpTracerSharedPtr tracer_; }; @@ -764,6 +777,58 @@ TEST_F(HttpTracerImplTest, BasicFunctionalityNodeSet) { tracer_->startSpan(config_, request_headers_, stream_info_, {Reason::Sampling, true}); } +TEST_F(HttpTracerImplTest, ChildUpstreamSpanTest) { + EXPECT_CALL(stream_info_, startTime()); + EXPECT_CALL(local_info_, nodeName()); + EXPECT_CALL(config_, operationName()).Times(2).WillRepeatedly(Return(OperationName::Egress)); + + NiceMock* span = new NiceMock(); + const std::string operation_name = "egress test"; + EXPECT_CALL(*driver_, startSpan_(_, _, operation_name, stream_info_.start_time_, _)) + .WillOnce(Return(span)); + EXPECT_CALL(*span, setTag(_, _)).Times(testing::AnyNumber()); + EXPECT_CALL(*span, setTag(Eq(Tracing::Tags::get().NodeId), Eq("node_name"))); + + auto parent_span = + tracer_->startSpan(config_, request_headers_, stream_info_, {Reason::Sampling, true}); + + NiceMock* second_span = new NiceMock(); + + EXPECT_CALL(*span, spawnChild_(_, _, _)).WillOnce(Return(second_span)); + auto child_span = + parent_span->spawnChild(config_, "fake child of egress test", stream_info_.start_time_); + + const std::string expected_ip = "10.0.0.100"; + const auto remote_address = Network::Address::InstanceConstSharedPtr{ + new Network::Address::Ipv4Instance(expected_ip, 0, nullptr)}; + + absl::optional protocol = Http::Protocol::Http2; + absl::optional response_code(200); + const std::string cluster_name = "fake cluster"; + const std::string ob_cluster_name = "ob fake cluster"; + EXPECT_CALL(stream_info_, responseCode()).WillRepeatedly(ReturnPointee(&response_code)); + EXPECT_CALL(stream_info_, protocol()).WillRepeatedly(ReturnPointee(&protocol)); + EXPECT_CALL(*(stream_info_.host_), address()).WillOnce(Return(remote_address)); + EXPECT_CALL(stream_info_.host_->cluster_, name()).WillOnce(ReturnRef(cluster_name)); + EXPECT_CALL(stream_info_.host_->cluster_, observabilityName()) + .WillOnce(ReturnRef(ob_cluster_name)); + + EXPECT_CALL(*second_span, setTag(_, _)).Times(testing::AnyNumber()); + EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().HttpProtocol), Eq("HTTP/2"))); + EXPECT_CALL(*second_span, + setTag(Eq(Tracing::Tags::get().UpstreamAddress), Eq(expected_ip + ":0"))); + EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().UpstreamCluster), Eq("fake cluster"))); + EXPECT_CALL(*second_span, + setTag(Eq(Tracing::Tags::get().UpstreamClusterName), Eq("ob fake cluster"))); + EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().HttpStatusCode), Eq("200"))); + EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("7"))); + EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().GrpcMessage), Eq("permission denied"))); + EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); + + HttpTracerUtility::finalizeUpstreamSpan(*child_span, &response_headers_, &response_trailers_, + stream_info_, config_); +} + } // namespace } // namespace Tracing } // namespace Envoy diff --git a/test/common/tracing/http_tracer_manager_impl_test.cc b/test/common/tracing/http_tracer_manager_impl_test.cc index 67f802af9edc6..2b49433879b45 100644 --- a/test/common/tracing/http_tracer_manager_impl_test.cc +++ b/test/common/tracing/http_tracer_manager_impl_test.cc @@ -1,6 +1,6 @@ -#include "common/tracing/http_tracer_config_impl.h" #include "common/tracing/http_tracer_impl.h" #include "common/tracing/http_tracer_manager_impl.h" +#include "common/tracing/tracer_config_impl.h" #include "test/mocks/server/instance.h" #include "test/mocks/server/tracer_factory.h" @@ -20,9 +20,9 @@ namespace Envoy { namespace Tracing { namespace { -class SampleTracer : public HttpTracer { +class SampleDriver : public Driver { public: - SpanPtr startSpan(const Config&, Http::RequestHeaderMap&, const StreamInfo::StreamInfo&, + SpanPtr startSpan(const Config&, Http::RequestHeaderMap&, const std::string&, SystemTime, const Tracing::Decision) override { return nullptr; } @@ -30,10 +30,10 @@ class SampleTracer : public HttpTracer { class SampleTracerFactory : public Server::Configuration::TracerFactory { public: - Tracing::HttpTracerSharedPtr - createHttpTracer(const Protobuf::Message&, - Server::Configuration::TracerFactoryContext&) override { - return std::make_shared(); + Tracing::DriverSharedPtr + createTracerDriver(const Protobuf::Message&, + Server::Configuration::TracerFactoryContext&) override { + return std::make_shared(); } std::string name() const override { return "envoy.tracers.sample"; } @@ -69,8 +69,11 @@ TEST_F(HttpTracerManagerImplTest, ShouldUseProperTracerFactory) { auto http_tracer = http_tracer_manager_.getOrCreateHttpTracer(&tracing_config); + EXPECT_THAT(http_tracer.get(), WhenDynamicCastTo(NotNull())); + auto http_tracer_impl = dynamic_cast(http_tracer.get()); + // Should use proper TracerFactory. - EXPECT_THAT(http_tracer.get(), WhenDynamicCastTo(NotNull())); + EXPECT_THAT(http_tracer_impl->driverForTest().get(), WhenDynamicCastTo(NotNull())); } TEST_F(HttpTracerManagerImplTest, ShouldCacheAndReuseTracers) { @@ -162,23 +165,25 @@ class HttpTracerManagerImplCacheTest : public testing::Test { }; TEST_F(HttpTracerManagerImplCacheTest, ShouldCacheHttpTracersUsingWeakReferences) { - HttpTracer* expected_tracer = new NiceMock(); + Driver* expected_driver = new NiceMock(); // Expect HttpTracerManager to create a new HttpTracer. - EXPECT_CALL(tracer_factory_, createHttpTracer(_, _)) + EXPECT_CALL(tracer_factory_, createTracerDriver(_, _)) .WillOnce(InvokeWithoutArgs( - [expected_tracer] { return std::shared_ptr(expected_tracer); })); + [expected_driver] { return std::shared_ptr(expected_driver); })); auto actual_tracer_one = http_tracer_manager_.getOrCreateHttpTracer(&tracing_config_one_); - EXPECT_EQ(actual_tracer_one.get(), expected_tracer); + EXPECT_EQ(dynamic_cast(actual_tracer_one.get())->driverForTest().get(), + expected_driver); // Expect a new HttpTracer to be added to the cache. EXPECT_THAT(http_tracer_manager_.peekCachedTracersForTest(), SizeIs(1)); // Expect HttpTracerManager to re-use cached value. auto actual_tracer_two = http_tracer_manager_.getOrCreateHttpTracer(&tracing_config_one_); - EXPECT_EQ(actual_tracer_two.get(), expected_tracer); + EXPECT_EQ(dynamic_cast(actual_tracer_one.get())->driverForTest().get(), + expected_driver); // Expect no changes to the cache. EXPECT_THAT(http_tracer_manager_.peekCachedTracersForTest(), SizeIs(1)); @@ -194,25 +199,26 @@ TEST_F(HttpTracerManagerImplCacheTest, ShouldCacheHttpTracersUsingWeakReferences // Expect no more strong references to be left. EXPECT_EQ(weak_pointer.lock(), nullptr); - HttpTracer* expected_another_tracer = new NiceMock(); + Driver* expected_other_driver = new NiceMock(); // Expect HttpTracerManager to create a new HttpTracer once again. - EXPECT_CALL(tracer_factory_, createHttpTracer(_, _)) - .WillOnce(InvokeWithoutArgs([expected_another_tracer] { - return std::shared_ptr(expected_another_tracer); - })); + EXPECT_CALL(tracer_factory_, createTracerDriver(_, _)) + .WillOnce(InvokeWithoutArgs( + [expected_other_driver] { return std::shared_ptr(expected_other_driver); })); // Use a different config to guarantee that a new cache entry will be added anyway. auto actual_tracer_three = http_tracer_manager_.getOrCreateHttpTracer(&tracing_config_two_); - EXPECT_EQ(actual_tracer_three.get(), expected_another_tracer); + EXPECT_EQ(dynamic_cast(actual_tracer_three.get())->driverForTest().get(), + expected_other_driver); // Expect expired cache entries to be removed and a new HttpTracer to be added to the cache. EXPECT_THAT(http_tracer_manager_.peekCachedTracersForTest(), SizeIs(1)); // Expect HttpTracerManager to keep the right value in the cache. auto actual_tracer_four = http_tracer_manager_.getOrCreateHttpTracer(&tracing_config_two_); - EXPECT_EQ(actual_tracer_four.get(), expected_another_tracer); + EXPECT_EQ(dynamic_cast(actual_tracer_four.get())->driverForTest().get(), + expected_other_driver); // Expect no changes to the cache. EXPECT_THAT(http_tracer_manager_.peekCachedTracersForTest(), SizeIs(1)); } diff --git a/test/extensions/tracers/datadog/config_test.cc b/test/extensions/tracers/datadog/config_test.cc index 6171c0691e767..b591004204317 100644 --- a/test/extensions/tracers/datadog/config_test.cc +++ b/test/extensions/tracers/datadog/config_test.cc @@ -38,7 +38,7 @@ TEST(DatadogTracerConfigTest, DatadogHttpTracer) { DatadogTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr datadog_tracer = factory.createHttpTracer(*message, context); + auto datadog_tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, datadog_tracer); } diff --git a/test/extensions/tracers/dynamic_ot/config_test.cc b/test/extensions/tracers/dynamic_ot/config_test.cc index 951a71ba416e2..3ccabd8b3a3b7 100644 --- a/test/extensions/tracers/dynamic_ot/config_test.cc +++ b/test/extensions/tracers/dynamic_ot/config_test.cc @@ -49,7 +49,7 @@ TEST(DynamicOtTracerConfigTest, DynamicOpentracingHttpTracer) { DynamicOpenTracingTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - const Tracing::HttpTracerSharedPtr tracer = factory.createHttpTracer(*message, context); + auto tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, tracer); } diff --git a/test/extensions/tracers/lightstep/config_test.cc b/test/extensions/tracers/lightstep/config_test.cc index 60725e610fd26..a490959a6b050 100644 --- a/test/extensions/tracers/lightstep/config_test.cc +++ b/test/extensions/tracers/lightstep/config_test.cc @@ -41,7 +41,7 @@ TEST(LightstepTracerConfigTest, DEPRECATED_FEATURE_TEST(LightstepHttpTracer)) { LightstepTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr lightstep_tracer = factory.createHttpTracer(*message, context); + auto lightstep_tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, lightstep_tracer); } @@ -67,7 +67,7 @@ TEST(LightstepTracerConfigTest, LightstepHttpTracerAccessToken) { LightstepTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr lightstep_tracer = factory.createHttpTracer(*message, context); + auto lightstep_tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, lightstep_tracer); } diff --git a/test/extensions/tracers/opencensus/config_test.cc b/test/extensions/tracers/opencensus/config_test.cc index 0de2ac976df5c..ecb6b5d99c1c4 100644 --- a/test/extensions/tracers/opencensus/config_test.cc +++ b/test/extensions/tracers/opencensus/config_test.cc @@ -37,7 +37,7 @@ TEST(OpenCensusTracerConfigTest, InvalidStackdriverConfiguration) { auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - EXPECT_THROW_WITH_MESSAGE((factory.createHttpTracer(*message, context)), EnvoyException, + EXPECT_THROW_WITH_MESSAGE((factory.createTracerDriver(*message, context)), EnvoyException, "Opencensus stackdriver tracer only support GoogleGrpc."); } @@ -60,7 +60,7 @@ TEST(OpenCensusTracerConfigTest, InvalidOcagentConfiguration) { auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - EXPECT_THROW_WITH_MESSAGE((factory.createHttpTracer(*message, context)), EnvoyException, + EXPECT_THROW_WITH_MESSAGE((factory.createTracerDriver(*message, context)), EnvoyException, "Opencensus ocagent tracer only supports GoogleGrpc."); } @@ -77,7 +77,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracer) { OpenCensusTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr tracer = factory.createHttpTracer(*message, context); + auto tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, tracer); } @@ -113,7 +113,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerWithTypedConfig) { OpenCensusTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr tracer = factory.createHttpTracer(*message, context); + auto tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, tracer); // Reset TraceParams back to default. @@ -147,7 +147,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr tracer = factory.createHttpTracer(*message, context); + auto tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, tracer); // Reset TraceParams back to default. @@ -188,7 +188,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerGrpc) { auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); #ifdef ENVOY_GOOGLE_GRPC - Tracing::HttpTracerSharedPtr tracer = factory.createHttpTracer(*message, context); + auto tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, tracer); // Reset TraceParams back to default. @@ -196,7 +196,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerGrpc) { {32, 32, 128, 32, ::opencensus::trace::ProbabilitySampler(1e-4)}); #else EXPECT_THROW_WITH_MESSAGE( - (factory.createHttpTracer(*message, context)), EnvoyException, + (factory.createTracerDriver(*message, context)), EnvoyException, "Opencensus tracer: cannot handle ocagent google grpc service, google grpc is not built in."); #endif } @@ -219,12 +219,12 @@ TEST(OpenCensusTracerConfigTest, ShouldCreateAtMostOneOpenCensusTracer) { auto message_one = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr tracer_one = factory.createHttpTracer(*message_one, context); + auto tracer_one = factory.createTracerDriver(*message_one, context); EXPECT_NE(nullptr, tracer_one); auto message_two = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr tracer_two = factory.createHttpTracer(*message_two, context); + auto tracer_two = factory.createTracerDriver(*message_two, context); // Verify that no new tracer has been created. EXPECT_EQ(tracer_two, tracer_one); } @@ -242,13 +242,13 @@ TEST(OpenCensusTracerConfigTest, ShouldCacheFirstCreatedTracerUsingStrongReferen auto message_one = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - std::weak_ptr tracer_one = factory.createHttpTracer(*message_one, context); + std::weak_ptr tracer_one = factory.createTracerDriver(*message_one, context); // Verify that tracer factory keeps a strong reference. EXPECT_NE(nullptr, tracer_one.lock()); auto message_two = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr tracer_two = factory.createHttpTracer(*message_two, context); + auto tracer_two = factory.createTracerDriver(*message_two, context); EXPECT_NE(nullptr, tracer_two); // Verify that no new tracer has been created. EXPECT_EQ(tracer_two, tracer_one.lock()); @@ -273,7 +273,7 @@ TEST(OpenCensusTracerConfigTest, ShouldNotCacheInvalidConfiguration) { auto message_one = Config::Utility::translateToFactoryConfig( configuration_one.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - EXPECT_THROW_WITH_MESSAGE((factory.createHttpTracer(*message_one, context)), EnvoyException, + EXPECT_THROW_WITH_MESSAGE((factory.createTracerDriver(*message_one, context)), EnvoyException, "Opencensus ocagent tracer only supports GoogleGrpc."); const std::string yaml_two = R"EOF( @@ -293,12 +293,12 @@ TEST(OpenCensusTracerConfigTest, ShouldNotCacheInvalidConfiguration) { auto message_two = Config::Utility::translateToFactoryConfig( configuration_two.http(), ProtobufMessage::getStrictValidationVisitor(), factory); #ifdef ENVOY_GOOGLE_GRPC - Tracing::HttpTracerSharedPtr tracer_two = factory.createHttpTracer(*message_two, context); + auto tracer_two = factory.createTracerDriver(*message_two, context); // Verify that a new tracer has been created despite an earlier failed attempt. EXPECT_NE(nullptr, tracer_two); #else EXPECT_THROW_WITH_MESSAGE( - (factory.createHttpTracer(*message_two, context)), EnvoyException, + (factory.createTracerDriver(*message_two, context)), EnvoyException, "Opencensus tracer: cannot handle ocagent google grpc service, google grpc is not built in."); #endif } @@ -321,7 +321,7 @@ TEST(OpenCensusTracerConfigTest, ShouldRejectSubsequentCreateAttemptsWithDiffere auto message_one = Config::Utility::translateToFactoryConfig( configuration_one.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr tracer_one = factory.createHttpTracer(*message_one, context); + auto tracer_one = factory.createTracerDriver(*message_one, context); EXPECT_NE(nullptr, tracer_one); const std::string yaml_two = R"EOF( @@ -339,7 +339,7 @@ TEST(OpenCensusTracerConfigTest, ShouldRejectSubsequentCreateAttemptsWithDiffere auto message_two = Config::Utility::translateToFactoryConfig( configuration_two.http(), ProtobufMessage::getStrictValidationVisitor(), factory); // Verify that OpenCensus is only configured once in a lifetime. - EXPECT_THROW_WITH_MESSAGE((factory.createHttpTracer(*message_two, context)), EnvoyException, + EXPECT_THROW_WITH_MESSAGE((factory.createTracerDriver(*message_two, context)), EnvoyException, "Opencensus has already been configured with a different config."); } @@ -367,10 +367,10 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerStackdriverGrpc) { auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); #ifdef ENVOY_GOOGLE_GRPC - Tracing::HttpTracerSharedPtr tracer = factory.createHttpTracer(*message, context); + auto tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, tracer); #else - EXPECT_THROW_WITH_MESSAGE((factory.createHttpTracer(*message, context)), EnvoyException, + EXPECT_THROW_WITH_MESSAGE((factory.createTracerDriver(*message, context)), EnvoyException, "Opencensus tracer: cannot handle stackdriver google grpc service, " "google grpc is not built in."); #endif diff --git a/test/extensions/tracers/skywalking/config_test.cc b/test/extensions/tracers/skywalking/config_test.cc index 1f1082298f1fa..f865525e09a3b 100644 --- a/test/extensions/tracers/skywalking/config_test.cc +++ b/test/extensions/tracers/skywalking/config_test.cc @@ -45,7 +45,7 @@ TEST(SkyWalkingTracerConfigTest, SkyWalkingHttpTracer) { SkyWalkingTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr skywalking_tracer = factory.createHttpTracer(*message, context); + auto skywalking_tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, skywalking_tracer); } @@ -79,7 +79,7 @@ TEST(SkyWalkingTracerConfigTest, SkyWalkingHttpTracerWithClientConfig) { SkyWalkingTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr skywalking_tracer = factory.createHttpTracer(*message, context); + auto skywalking_tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, skywalking_tracer); } diff --git a/test/extensions/tracers/xray/config_test.cc b/test/extensions/tracers/xray/config_test.cc index 7008ef0a0c54e..26b613eaec3a9 100644 --- a/test/extensions/tracers/xray/config_test.cc +++ b/test/extensions/tracers/xray/config_test.cc @@ -43,7 +43,7 @@ TEST(XRayTracerConfigTest, XRayHttpTracerWithTypedConfig) { XRayTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr xray_tracer = factory.createHttpTracer(*message, context); + auto xray_tracer = factory.createTracerDriver(*message, context); ASSERT_NE(nullptr, xray_tracer); } @@ -78,7 +78,7 @@ TEST(XRayTracerConfigTest, XRayHttpTracerWithInvalidFileName) { auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr xray_tracer = factory.createHttpTracer(*message, context); + auto xray_tracer = factory.createTracerDriver(*message, context); ASSERT_NE(nullptr, xray_tracer); } @@ -104,7 +104,7 @@ TEST(XRayTracerConfigTest, ProtocolNotUDPThrows) { auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - ASSERT_THROW(factory.createHttpTracer(*message, context), EnvoyException); + ASSERT_THROW(factory.createTracerDriver(*message, context), EnvoyException); } TEST(XRayTracerConfigTest, UsingNamedPortThrows) { @@ -129,7 +129,7 @@ TEST(XRayTracerConfigTest, UsingNamedPortThrows) { auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - ASSERT_THROW(factory.createHttpTracer(*message, context), EnvoyException); + ASSERT_THROW(factory.createTracerDriver(*message, context), EnvoyException); } TEST(XRayTracerConfigTest, XRayHttpTracerWithSegmentFieldsTypedConfig) { @@ -162,7 +162,7 @@ TEST(XRayTracerConfigTest, XRayHttpTracerWithSegmentFieldsTypedConfig) { XRayTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr xray_tracer = factory.createHttpTracer(*message, context); + auto xray_tracer = factory.createTracerDriver(*message, context); ASSERT_NE(nullptr, xray_tracer); } diff --git a/test/extensions/tracers/zipkin/config_test.cc b/test/extensions/tracers/zipkin/config_test.cc index b0219b927665d..945a0e172d446 100644 --- a/test/extensions/tracers/zipkin/config_test.cc +++ b/test/extensions/tracers/zipkin/config_test.cc @@ -39,7 +39,7 @@ TEST(ZipkinTracerConfigTest, ZipkinHttpTracer) { ZipkinTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr zipkin_tracer = factory.createHttpTracer(*message, context); + auto zipkin_tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, zipkin_tracer); } @@ -63,7 +63,7 @@ TEST(ZipkinTracerConfigTest, ZipkinHttpTracerWithTypedConfig) { ZipkinTracerFactory factory; auto message = Config::Utility::translateToFactoryConfig( configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); - Tracing::HttpTracerSharedPtr zipkin_tracer = factory.createHttpTracer(*message, context); + auto zipkin_tracer = factory.createTracerDriver(*message, context); EXPECT_NE(nullptr, zipkin_tracer); } diff --git a/test/mocks/server/tracer_factory.h b/test/mocks/server/tracer_factory.h index e342116ee2d5a..c9d4fa73e41b8 100644 --- a/test/mocks/server/tracer_factory.h +++ b/test/mocks/server/tracer_factory.h @@ -16,7 +16,7 @@ class MockTracerFactory : public TracerFactory { std::string name() const override { return name_; } MOCK_METHOD(ProtobufTypes::MessagePtr, createEmptyConfigProto, ()); - MOCK_METHOD(Tracing::HttpTracerSharedPtr, createHttpTracer, + MOCK_METHOD(Tracing::DriverSharedPtr, createTracerDriver, (const Protobuf::Message& config, TracerFactoryContext& context)); private: From 80e1ca899e3170c8f87dfd300c9c5dacc76d100b Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Wed, 12 May 2021 16:58:11 -0500 Subject: [PATCH 197/209] aws_request_signing_filter hash payload by default (#15846) canonical must include the hashed payload for most services. The prior behavior of using UNSIGNED-PAYLOAD is an exception to the rule, which select services like s3 support, since hashing the payload may be impractical if the payload is very large. A new filter option is introduced, so that the filter may be explicitly configured to use the UNSIGNED-PAYLOAD string literal as specified in the S3 signing docs: https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html fixes #13904 Additional Description: The original implementation was seemingly very specific to S3 and was subsequently amended to extend the same niche singing behaviors for ES and Glacier. This changes the filter's default behavior to match the general SigV4 guidelines while providing a configuration option to enable the specialized UNSIGNED-PAYLOAD behavior. https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html Risk Level: Medium? Deployments using the filter will now buffer requests by default, which could result in 413 responses for requests with bodies exceeding the buffer limit. These users can mitigate buffering by enabling the `unsigned_payload` option. Testing: I tested locally with a filter config. I anticipate updating the automated tests based on feedback from maintainers. Docs Changes: Added Signed-off-by: Jonathan Stewmon --- .../v3/aws_request_signing.proto | 5 + .../aws_request_signing_filter.rst | 17 ++- docs/root/version_history/current.rst | 6 + .../v3/aws_request_signing.proto | 5 + source/extensions/common/aws/signer.h | 11 +- source/extensions/common/aws/signer_impl.cc | 20 +-- source/extensions/common/aws/signer_impl.h | 22 +-- .../http/aws_lambda/aws_lambda_filter.cc | 2 +- .../aws_request_signing_filter.cc | 58 +++++++- .../aws_request_signing_filter.h | 19 ++- .../http/aws_request_signing/config.cc | 5 +- test/extensions/common/aws/mocks.h | 3 +- .../extensions/common/aws/signer_impl_test.cc | 16 ++- .../http/aws_lambda/aws_lambda_filter_test.cc | 2 +- .../aws_request_signing_filter_test.cc | 129 ++++++++++++++++-- 15 files changed, 257 insertions(+), 63 deletions(-) diff --git a/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto b/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto index 215c7414c5a2d..ae46400130d52 100644 --- a/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto +++ b/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto @@ -43,4 +43,9 @@ message AwsRequestSigning { // value set here would be used for signing whereas the value set in the HCM would be used // for host header forwarding which is not the desired outcome. string host_rewrite = 3; + + // Instead of buffering the request to calculate the payload hash, use the literal string ``UNSIGNED-PAYLOAD`` + // to calculate the payload hash. Not all services support this option. See the `S3 + // `_ policy for details. + bool use_unsigned_payload = 4; } diff --git a/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst b/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst index 0280a012a05dc..4c31387c503a8 100644 --- a/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst +++ b/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst @@ -15,6 +15,16 @@ The HTTP AWS request signing filter is used to access authenticated AWS services existing AWS Credential Provider to get the secrets used for generating the required headers. + +The :ref:`use_unsigned_payload ` +option determines whether or not requests are buffered so the request body can be used to compute the payload hash. Some +services, such as S3, allow requests with unsigned payloads. Consult the AWS documentation and your service's resource +policies to determine if this option is appropriate. + +When :ref:`use_unsigned_payload ` +is false (the default), requests which exceed the configured buffer limit will receive a 413 response. See the +ref:`flow control docs ` for details. + Example configuration --------------------- @@ -27,6 +37,7 @@ Example filter configuration: "@type": type.googleapis.com/envoy.extensions.filters.http.aws_request_signing.v3.AwsRequestSigning service_name: s3 region: us-west-2 + use_unsigned_payload: true Statistics @@ -40,5 +51,7 @@ comes from the owning HTTP connection manager. :header: Name, Type, Description :widths: 1, 1, 2 - signing_added, Counter, Total authentication headers added to requests - signing_failed, Counter, Total requests for which a signature was not added + signing_added, Counter, Total requests for which signing succeeded (includes payload_signing_added) + signing_failed, Counter, Total requests for which signing failed (includes payload_signing_failed) + payload_signing_added, Counter, Total requests for which the payload was buffered signing succeeded + payload_signing_failed, Counter, Total requests for which the payload was buffered but signing failed diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 2630383773a96..bf3719e657612 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -14,6 +14,12 @@ Minor Behavior Changes *Changes that may cause incompatibilities for some users, but should not for most* * access_log: add new access_log command operator ``%REQUEST_TX_DURATION%``. +* aws_request_signing: requests are now buffered by default to compute signatures which include the + payload hash, making the filter compatible with most AWS services. Previously, requests were + never buffered, which only produced correct signatures for requests without a body, or for + requests to S3, ES or Glacier, which used the literal string ``UNSIGNED-PAYLOAD``. Buffering can + be now be disabled in favor of using unsigned payloads with compatible services via the new + `use_unsigned_payload` filter option (default false). * http: disable the integration between :ref:`ExtensionWithMatcher ` and HTTP filters by default to reflects its experimental status. This feature can be enabled by seting ``envoy.reloadable_features.experimental_matching_api`` to true. diff --git a/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto b/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto index 215c7414c5a2d..ae46400130d52 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto @@ -43,4 +43,9 @@ message AwsRequestSigning { // value set here would be used for signing whereas the value set in the HCM would be used // for host header forwarding which is not the desired outcome. string host_rewrite = 3; + + // Instead of buffering the request to calculate the payload hash, use the literal string ``UNSIGNED-PAYLOAD`` + // to calculate the payload hash. Not all services support this option. See the `S3 + // `_ policy for details. + bool use_unsigned_payload = 4; } diff --git a/source/extensions/common/aws/signer.h b/source/extensions/common/aws/signer.h index 34a892c9efb2f..3dd9040bc63fb 100644 --- a/source/extensions/common/aws/signer.h +++ b/source/extensions/common/aws/signer.h @@ -21,11 +21,18 @@ class Signer { virtual void sign(Http::RequestMessage& message, bool sign_body) PURE; /** - * Sign an AWS request. + * Sign an AWS request without a payload (empty string used as content hash). + * @param headers AWS API request headers. + * @throws EnvoyException if the request cannot be signed. + */ + virtual void signEmptyPayload(Http::RequestHeaderMap& headers) PURE; + + /** + * Sign an AWS request using the literal string UNSIGNED-PAYLOAD in the canonical request. * @param headers AWS API request headers. * @throws EnvoyException if the request cannot be signed. */ - virtual void sign(Http::RequestHeaderMap& headers) PURE; + virtual void signUnsignedPayload(Http::RequestHeaderMap& headers) PURE; /** * Sign an AWS request. diff --git a/source/extensions/common/aws/signer_impl.cc b/source/extensions/common/aws/signer_impl.cc index ec0ae8d4b2f95..a328610e28042 100644 --- a/source/extensions/common/aws/signer_impl.cc +++ b/source/extensions/common/aws/signer_impl.cc @@ -23,16 +23,16 @@ void SignerImpl::sign(Http::RequestMessage& message, bool sign_body) { sign(headers, content_hash); } -void SignerImpl::sign(Http::RequestHeaderMap& headers) { - if (require_content_hash_) { - headers.setReference(SignatureHeaders::get().ContentSha256, - SignatureConstants::get().UnsignedPayload); - sign(headers, SignatureConstants::get().UnsignedPayload); - } else { - headers.setReference(SignatureHeaders::get().ContentSha256, - SignatureConstants::get().HashedEmptyString); - sign(headers, SignatureConstants::get().HashedEmptyString); - } +void SignerImpl::signEmptyPayload(Http::RequestHeaderMap& headers) { + headers.setReference(SignatureHeaders::get().ContentSha256, + SignatureConstants::get().HashedEmptyString); + sign(headers, SignatureConstants::get().HashedEmptyString); +} + +void SignerImpl::signUnsignedPayload(Http::RequestHeaderMap& headers) { + headers.setReference(SignatureHeaders::get().ContentSha256, + SignatureConstants::get().UnsignedPayload); + sign(headers, SignatureConstants::get().UnsignedPayload); } void SignerImpl::sign(Http::RequestHeaderMap& headers, const std::string& content_hash) { diff --git a/source/extensions/common/aws/signer_impl.h b/source/extensions/common/aws/signer_impl.h index 78908874e042a..b1b5ad3c550d4 100644 --- a/source/extensions/common/aws/signer_impl.h +++ b/source/extensions/common/aws/signer_impl.h @@ -47,23 +47,14 @@ class SignerImpl : public Signer, public Logger::Loggable { public: SignerImpl(absl::string_view service_name, absl::string_view region, const CredentialsProviderSharedPtr& credentials_provider, TimeSource& time_source) - : service_name_(service_name), region_(region), - - // S3, Glacier, ES payloads require special treatment. - // S3: - // https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html. - // ES: - // https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-request-signing.html. - // Glacier: - // https://docs.aws.amazon.com/amazonglacier/latest/dev/amazon-glacier-signing-requests.html. - require_content_hash_{service_name_ == "s3" || service_name_ == "glacier" || - service_name_ == "es"}, - credentials_provider_(credentials_provider), time_source_(time_source), - long_date_formatter_(SignatureConstants::get().LongDateFormat), + : service_name_(service_name), region_(region), credentials_provider_(credentials_provider), + time_source_(time_source), long_date_formatter_(SignatureConstants::get().LongDateFormat), short_date_formatter_(SignatureConstants::get().ShortDateFormat) {} void sign(Http::RequestMessage& message, bool sign_body = false) override; - void sign(Http::RequestHeaderMap& headers) override; + void sign(Http::RequestHeaderMap& headers, const std::string& content_hash) override; + void signEmptyPayload(Http::RequestHeaderMap& headers) override; + void signUnsignedPayload(Http::RequestHeaderMap& headers) override; private: std::string createContentHash(Http::RequestMessage& message, bool sign_body) const; @@ -81,12 +72,9 @@ class SignerImpl : public Signer, public Logger::Loggable { const std::map& canonical_headers, absl::string_view signature) const; - void sign(Http::RequestHeaderMap& headers, const std::string& content_hash) override; - const std::string service_name_; const std::string region_; - const bool require_content_hash_; CredentialsProviderSharedPtr credentials_provider_; TimeSource& time_source_; DateFormatter long_date_formatter_; diff --git a/source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc b/source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc index b4a43217a978a..6a908ff117e49 100644 --- a/source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc +++ b/source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc @@ -165,7 +165,7 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, if (payload_passthrough_) { setLambdaHeaders(headers, arn_->functionName(), invocation_mode_); - sigv4_signer_->sign(headers); + sigv4_signer_->signEmptyPayload(headers); return Http::FilterHeadersStatus::Continue; } diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc index 5c4ff37272bf2..4a9fb083345dd 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc @@ -2,6 +2,9 @@ #include "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.pb.h" +#include "common/common/hex.h" +#include "common/crypto/utility.h" + namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -9,9 +12,9 @@ namespace AwsRequestSigningFilter { FilterConfigImpl::FilterConfigImpl(Extensions::Common::Aws::SignerPtr&& signer, const std::string& stats_prefix, Stats::Scope& scope, - const std::string& host_rewrite) + const std::string& host_rewrite, bool use_unsigned_payload) : signer_(std::move(signer)), stats_(Filter::generateStats(stats_prefix, scope)), - host_rewrite_(host_rewrite) {} + host_rewrite_(host_rewrite), use_unsigned_payload_{use_unsigned_payload} {} Filter::Filter(const std::shared_ptr& config) : config_(config) {} @@ -20,22 +23,37 @@ Extensions::Common::Aws::Signer& FilterConfigImpl::signer() { return *signer_; } FilterStats& FilterConfigImpl::stats() { return stats_; } const std::string& FilterConfigImpl::hostRewrite() const { return host_rewrite_; } +bool FilterConfigImpl::useUnsignedPayload() const { return use_unsigned_payload_; } FilterStats Filter::generateStats(const std::string& prefix, Stats::Scope& scope) { const std::string final_prefix = prefix + "aws_request_signing."; return {ALL_AWS_REQUEST_SIGNING_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))}; } -Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool) { +Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { const auto& host_rewrite = config_->hostRewrite(); + const bool use_unsigned_payload = config_->useUnsignedPayload(); + if (!host_rewrite.empty()) { headers.setHost(host_rewrite); } + if (!use_unsigned_payload && !end_stream) { + request_headers_ = &headers; + return Http::FilterHeadersStatus::StopIteration; + } + try { - config_->signer().sign(headers); + ENVOY_LOG(debug, "aws request signing from decodeHeaders use_unsigned_payload: {}", + use_unsigned_payload); + if (use_unsigned_payload) { + config_->signer().signUnsignedPayload(headers); + } else { + config_->signer().signEmptyPayload(headers); + } config_->stats().signing_added_.inc(); } catch (const EnvoyException& e) { + // TODO: sign should not throw to avoid exceptions in the request path ENVOY_LOG(debug, "signing failed: {}", e.what()); config_->stats().signing_failed_.inc(); } @@ -43,6 +61,38 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, return Http::FilterHeadersStatus::Continue; } +Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) { + if (config_->useUnsignedPayload()) { + return Http::FilterDataStatus::Continue; + } + + if (!end_stream) { + return Http::FilterDataStatus::StopIterationAndBuffer; + } + + decoder_callbacks_->addDecodedData(data, false); + + const Buffer::Instance& decoding_buffer = *decoder_callbacks_->decodingBuffer(); + + auto& hashing_util = Envoy::Common::Crypto::UtilitySingleton::get(); + const std::string hash = Hex::encode(hashing_util.getSha256Digest(decoding_buffer)); + + try { + ENVOY_LOG(debug, "aws request signing from decodeData"); + ASSERT(request_headers_ != nullptr); + config_->signer().sign(*request_headers_, hash); + config_->stats().signing_added_.inc(); + config_->stats().payload_signing_added_.inc(); + } catch (const EnvoyException& e) { + // TODO: sign should not throw to avoid exceptions in the request path + ENVOY_LOG(debug, "signing failed: {}", e.what()); + config_->stats().signing_failed_.inc(); + config_->stats().payload_signing_failed_.inc(); + } + + return Http::FilterDataStatus::Continue; +} + } // namespace AwsRequestSigningFilter } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h index ecd22a7b0b4da..2903ec4898530 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h @@ -17,9 +17,11 @@ namespace AwsRequestSigningFilter { * All stats for the AWS request signing filter. @see stats_macros.h */ // clang-format off -#define ALL_AWS_REQUEST_SIGNING_FILTER_STATS(COUNTER) \ - COUNTER(signing_added) \ - COUNTER(signing_failed) +#define ALL_AWS_REQUEST_SIGNING_FILTER_STATS(COUNTER) \ + COUNTER(signing_added) \ + COUNTER(signing_failed) \ + COUNTER(payload_signing_added) \ + COUNTER(payload_signing_failed) // clang-format on /** @@ -50,6 +52,11 @@ class FilterConfig { * @return the host rewrite value. */ virtual const std::string& hostRewrite() const PURE; + + /** + * @return whether or not to buffer and sign the payload. + */ + virtual bool useUnsignedPayload() const PURE; }; using FilterConfigSharedPtr = std::shared_ptr; @@ -60,16 +67,18 @@ using FilterConfigSharedPtr = std::shared_ptr; class FilterConfigImpl : public FilterConfig { public: FilterConfigImpl(Extensions::Common::Aws::SignerPtr&& signer, const std::string& stats_prefix, - Stats::Scope& scope, const std::string& host_rewrite); + Stats::Scope& scope, const std::string& host_rewrite, bool use_unsigned_payload); Extensions::Common::Aws::Signer& signer() override; FilterStats& stats() override; const std::string& hostRewrite() const override; + bool useUnsignedPayload() const override; private: Extensions::Common::Aws::SignerPtr signer_; FilterStats stats_; std::string host_rewrite_; + const bool use_unsigned_payload_; }; /** @@ -83,9 +92,11 @@ class Filter : public Http::PassThroughDecoderFilter, Logger::Loggable config_; + Http::RequestHeaderMap* request_headers_{}; }; } // namespace AwsRequestSigningFilter diff --git a/source/extensions/filters/http/aws_request_signing/config.cc b/source/extensions/filters/http/aws_request_signing/config.cc index b637f3bfd1781..11396f39e0523 100644 --- a/source/extensions/filters/http/aws_request_signing/config.cc +++ b/source/extensions/filters/http/aws_request_signing/config.cc @@ -25,8 +25,9 @@ Http::FilterFactoryCb AwsRequestSigningFilterFactory::createFilterFactoryFromPro config.service_name(), config.region(), credentials_provider, context.dispatcher().timeSource()); - auto filter_config = std::make_shared(std::move(signer), stats_prefix, - context.scope(), config.host_rewrite()); + auto filter_config = + std::make_shared(std::move(signer), stats_prefix, context.scope(), + config.host_rewrite(), config.use_unsigned_payload()); return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { auto filter = std::make_shared(filter_config); callbacks.addStreamDecoderFilter(filter); diff --git a/test/extensions/common/aws/mocks.h b/test/extensions/common/aws/mocks.h index d89e1934c6d9d..05f7562197107 100644 --- a/test/extensions/common/aws/mocks.h +++ b/test/extensions/common/aws/mocks.h @@ -24,8 +24,9 @@ class MockSigner : public Signer { ~MockSigner() override; MOCK_METHOD(void, sign, (Http::RequestMessage&, bool)); - MOCK_METHOD(void, sign, (Http::RequestHeaderMap&)); MOCK_METHOD(void, sign, (Http::RequestHeaderMap&, const std::string&)); + MOCK_METHOD(void, signEmptyPayload, (Http::RequestHeaderMap&)); + MOCK_METHOD(void, signUnsignedPayload, (Http::RequestHeaderMap&)); }; class MockMetadataFetcher { diff --git a/test/extensions/common/aws/signer_impl_test.cc b/test/extensions/common/aws/signer_impl_test.cc index cc4fb856b5de1..fb8625febbfc8 100644 --- a/test/extensions/common/aws/signer_impl_test.cc +++ b/test/extensions/common/aws/signer_impl_test.cc @@ -40,7 +40,7 @@ class SignerImplTest : public testing::Test { void setBody(const std::string& body) { message_->body().add(body); } void expectSignHeaders(absl::string_view service_name, absl::string_view signature, - absl::string_view payload) { + absl::string_view payload, bool use_unsigned_payload) { auto* credentials_provider = new NiceMock(); EXPECT_CALL(*credentials_provider, getCredentials()).WillOnce(Return(credentials_)); Http::TestRequestHeaderMapImpl headers{}; @@ -50,7 +50,11 @@ class SignerImplTest : public testing::Test { SignerImpl signer(service_name, "region", CredentialsProviderSharedPtr{credentials_provider}, time_system_); - signer.sign(headers); + if (use_unsigned_payload) { + signer.signUnsignedPayload(headers); + } else { + signer.signEmptyPayload(headers); + } EXPECT_EQ(fmt::format("AWS4-HMAC-SHA256 Credential=akid/20180102/region/{}/aws4_request, " "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " @@ -204,13 +208,13 @@ TEST_F(SignerImplTest, SignHostHeader) { // Verify signing headers for services. TEST_F(SignerImplTest, SignHeadersByService) { expectSignHeaders("s3", "d97cae067345792b78d2bad746f25c729b9eb4701127e13a7c80398f8216a167", - SignatureConstants::get().UnsignedPayload); + SignatureConstants::get().UnsignedPayload, true); expectSignHeaders("service", "d9fd9be575a254c924d843964b063d770181d938ae818f5b603ef0575a5ce2cd", - SignatureConstants::get().HashedEmptyString); + SignatureConstants::get().HashedEmptyString, false); expectSignHeaders("es", "0fd9c974bb2ad16c8d8a314dca4f6db151d32cbd04748d9c018afee2a685a02e", - SignatureConstants::get().UnsignedPayload); + SignatureConstants::get().UnsignedPayload, true); expectSignHeaders("glacier", "8d1f241d77c64cda57b042cd312180f16e98dbd7a96e5545681430f8dbde45a0", - SignatureConstants::get().UnsignedPayload); + SignatureConstants::get().UnsignedPayload, true); } } // namespace diff --git a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc index bcc46b3dc5e33..d8fb873cf2972 100644 --- a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc +++ b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc @@ -76,7 +76,7 @@ TEST_F(AwsLambdaFilterTest, DecodingHeaderStopIteration) { */ TEST_F(AwsLambdaFilterTest, HeaderOnlyShouldContinue) { setupFilter({arn_, InvocationMode::Synchronous, true /*passthrough*/}); - EXPECT_CALL(*signer_, sign(_)); + EXPECT_CALL(*signer_, signEmptyPayload(An())); Http::TestRequestHeaderMapImpl input_headers; const auto result = filter_->decodeHeaders(input_headers, true /*end_stream*/); EXPECT_EQ(Http::FilterHeadersStatus::Continue, result); diff --git a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc index 9fb6968faed78..2e730a8a70406 100644 --- a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc +++ b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc @@ -1,3 +1,5 @@ +#include "envoy/http/filter.h" + #include "extensions/common/aws/signer.h" #include "extensions/filters/http/aws_request_signing/aws_request_signing_filter.h" @@ -13,75 +15,176 @@ namespace HttpFilters { namespace AwsRequestSigningFilter { namespace { +using Common::Aws::MockSigner; +using ::testing::An; +using ::testing::InSequence; +using ::testing::NiceMock; +using ::testing::StrictMock; + class MockFilterConfig : public FilterConfig { public: - MockFilterConfig() { signer_ = std::make_shared(); } + MockFilterConfig() { signer_ = std::make_shared>(); } Common::Aws::Signer& signer() override { return *signer_; } FilterStats& stats() override { return stats_; } const std::string& hostRewrite() const override { return host_rewrite_; } + bool useUnsignedPayload() const override { return use_unsigned_payload_; } std::shared_ptr signer_; Stats::IsolatedStoreImpl stats_store_; FilterStats stats_{Filter::generateStats("test", stats_store_)}; std::string host_rewrite_; + bool use_unsigned_payload_; }; class AwsRequestSigningFilterTest : public testing::Test { public: void setup() { filter_config_ = std::make_shared(); + filter_config_->use_unsigned_payload_ = false; filter_ = std::make_unique(filter_config_); + filter_->setDecoderFilterCallbacks(decoder_callbacks_); } std::shared_ptr filter_config_; std::unique_ptr filter_; + NiceMock decoder_callbacks_; }; -// Verify filter functionality when signing works. +// Verify filter functionality when signing works for header only request. TEST_F(AwsRequestSigningFilterTest, SignSucceeds) { setup(); - EXPECT_CALL(*(filter_config_->signer_), sign(_)); + EXPECT_CALL(*(filter_config_->signer_), signEmptyPayload(An())); + + Http::TestRequestHeaderMapImpl headers; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); + EXPECT_EQ(1UL, filter_config_->stats_.signing_added_.value()); +} + +// Verify decodeHeaders signs when use_unsigned_payload is true and end_stream is false. +TEST_F(AwsRequestSigningFilterTest, DecodeHeadersSignsUnsignedPayload) { + setup(); + filter_config_->use_unsigned_payload_ = true; + EXPECT_CALL(*(filter_config_->signer_), signUnsignedPayload(An())); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); +} + +// Verify decodeHeaders signs when use_unsigned_payload is true and end_stream is true. +TEST_F(AwsRequestSigningFilterTest, DecodeHeadersSignsUnsignedPayloadHeaderOnly) { + setup(); + filter_config_->use_unsigned_payload_ = true; + EXPECT_CALL(*(filter_config_->signer_), signUnsignedPayload(An())); + + Http::TestRequestHeaderMapImpl headers; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); +} + +// Verify decodeHeaders does not sign when use_unsigned_payload is false and end_stream is false. +TEST_F(AwsRequestSigningFilterTest, DecodeHeadersStopsIterationWithoutSigning) { + setup(); + + Http::TestRequestHeaderMapImpl headers; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); +} + +// Verify decodeData does not sign when end_stream is false. +TEST_F(AwsRequestSigningFilterTest, DecodeDataStopsIterationWithoutSigning) { + setup(); + + Buffer::OwnedImpl buffer; + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(buffer, false)); +} + +// Verify decodeData signs when end_stream is true (empty payload). +TEST_F(AwsRequestSigningFilterTest, DecodeDataSignsEmptyPayloadAndContinues) { + InSequence seq; + setup(); + Http::TestRequestHeaderMapImpl headers; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); + + // sha256('') + const std::string hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + Buffer::OwnedImpl buffer; + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, false)); + EXPECT_CALL(decoder_callbacks_, decodingBuffer).WillOnce(Return(&buffer)); + EXPECT_CALL(*(filter_config_->signer_), sign(HeaderMapEqualRef(&headers), hash)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(buffer, true)); EXPECT_EQ(1UL, filter_config_->stats_.signing_added_.value()); + EXPECT_EQ(1UL, filter_config_->stats_.payload_signing_added_.value()); +} + +// Verify decodeData signs when end_stream is true (empty payload). +TEST_F(AwsRequestSigningFilterTest, DecodeDataSignsPayloadAndContinues) { + InSequence seq; + setup(); + Http::TestRequestHeaderMapImpl headers; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); + + // sha256('Action=SignThis') + const std::string hash = "1db26ef86fca9f7c54d2273d4673a4f2a614fadf3185d16288d454619f1cf491"; + Buffer::OwnedImpl buffer("Action=SignThis"); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, false)); + EXPECT_CALL(decoder_callbacks_, decodingBuffer).WillOnce(Return(&buffer)); + EXPECT_CALL(*(filter_config_->signer_), sign(HeaderMapEqualRef(&headers), hash)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(buffer, true)); } -// Verify filter functionality when a host rewrite happens. +// Verify filter functionality when a host rewrite happens for header only request. TEST_F(AwsRequestSigningFilterTest, SignWithHostRewrite) { setup(); filter_config_->host_rewrite_ = "foo"; - EXPECT_CALL(*(filter_config_->signer_), sign(_)); + EXPECT_CALL(*(filter_config_->signer_), signEmptyPayload(An())); Http::TestRequestHeaderMapImpl headers; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); EXPECT_EQ("foo", headers.getHostValue()); EXPECT_EQ(1UL, filter_config_->stats_.signing_added_.value()); } -// Verify filter functionality when signing fails. +// Verify filter functionality when signing fails in decodeHeaders. TEST_F(AwsRequestSigningFilterTest, SignFails) { setup(); - EXPECT_CALL(*(filter_config_->signer_), sign(_)).WillOnce(Invoke([](Http::HeaderMap&) -> void { - throw EnvoyException("failed"); - })); + EXPECT_CALL(*(filter_config_->signer_), signEmptyPayload(An())) + .WillOnce(Invoke([](Http::HeaderMap&) -> void { throw EnvoyException("failed"); })); Http::TestRequestHeaderMapImpl headers; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); + EXPECT_EQ(1UL, filter_config_->stats_.signing_failed_.value()); +} + +// Verify filter functionality when signing fails in decodeData. +TEST_F(AwsRequestSigningFilterTest, DecodeDataSignFails) { + setup(); + + Http::TestRequestHeaderMapImpl headers; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); + + Buffer::OwnedImpl buffer; + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, false)); + EXPECT_CALL(decoder_callbacks_, decodingBuffer).WillOnce(Return(&buffer)); + EXPECT_CALL(*(filter_config_->signer_), + sign(An(), An())) + .WillOnce(Invoke( + [](Http::HeaderMap&, const std::string&) -> void { throw EnvoyException("failed"); })); + + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(buffer, true)); EXPECT_EQ(1UL, filter_config_->stats_.signing_failed_.value()); + EXPECT_EQ(1UL, filter_config_->stats_.payload_signing_failed_.value()); } -// Verify FilterConfigImpl's getters. +// Verify FilterConfigImpl getters. TEST_F(AwsRequestSigningFilterTest, FilterConfigImplGetters) { Stats::IsolatedStoreImpl stats; auto signer = std::make_unique(); const auto* signer_ptr = signer.get(); - FilterConfigImpl config(std::move(signer), "prefix", stats, "foo"); + FilterConfigImpl config(std::move(signer), "prefix", stats, "foo", true); EXPECT_EQ(signer_ptr, &config.signer()); EXPECT_EQ(0UL, config.stats().signing_added_.value()); EXPECT_EQ("foo", config.hostRewrite()); + EXPECT_EQ(true, config.useUnsignedPayload()); } } // namespace From 3f5738131f6566dfa5aad462dd0990eb14670033 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 12 May 2021 15:01:42 -0700 Subject: [PATCH 198/209] wasm: fix V8 build with --config={docker,remote}-msan. (#16452) Signed-off-by: Piotr Sikora --- bazel/external/wee8.patch | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/bazel/external/wee8.patch b/bazel/external/wee8.patch index a7776c892483c..6a25320fa147b 100644 --- a/bazel/external/wee8.patch +++ b/bazel/external/wee8.patch @@ -1,6 +1,6 @@ # 1. Fix linking with unbundled toolchain on macOS. # 2. Increase VSZ limit to 64 TiB (allows us to start up to 6,553 VMs). -# 3. Fix MSAN linking. +# 3. Fix building and linking with MSAN. --- wee8/build/toolchain/gcc_toolchain.gni +++ wee8/build/toolchain/gcc_toolchain.gni @@ -348,6 +348,8 @@ template("gcc_toolchain") { @@ -53,3 +53,17 @@ if (use_libfuzzer && (is_linux || is_chromeos)) { if (is_asan) { +--- wee8/build/config/compiler/BUILD.gn ++++ wee8/build/config/compiler/BUILD.gn +@@ -736,11 +736,6 @@ config("compiler") { + cflags += [ "-fcomplete-member-pointers" ] + } + +- # TODO(crbug/1185183): Remove after next clang roll +- if (is_clang && !is_nacl && is_linux && is_msan) { +- cflags += [ "-flegacy-pass-manager" ] +- } +- + # Pass the same C/C++ flags to the objective C/C++ compiler. + cflags_objc += cflags_c + cflags_objcc += cflags_cc From 0d3bf7f06860bc75b888725c3ff782670c32e9d5 Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Wed, 12 May 2021 18:02:54 -0400 Subject: [PATCH 199/209] test: Removing orphan type_util_test file (#16464) Signed-off-by: Adi Suissa-Peleg --- test/common/protobuf/type_util_test.cc | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 test/common/protobuf/type_util_test.cc diff --git a/test/common/protobuf/type_util_test.cc b/test/common/protobuf/type_util_test.cc deleted file mode 100644 index 2732d8221fdb7..0000000000000 --- a/test/common/protobuf/type_util_test.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include "common/protobuf/type_util.h" - -#include "gtest/gtest.h" - -namespace Envoy { -namespace Config { -namespace { -TEST(TypeUtilTest, TypeUrlHelperFunction) { - EXPECT_EQ("envoy.config.filter.http.ip_tagging.v2.IPTagging", - TypeUtil::typeUrlToDescriptorFullName( - "type.googleapis.com/envoy.config.filter.http.ip_tagging.v2.IPTagging")); - EXPECT_EQ( - "type.googleapis.com/envoy.config.filter.http.ip_tagging.v2.IPTagging", - TypeUtil::descriptorFullNameToTypeUrl("envoy.config.filter.http.ip_tagging.v2.IPTagging")); -} -} // namespace -} // namespace Config -} // namespace Envoy From ff62ef19107974855488fb672ff698dd69d66b26 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 13 May 2021 07:50:47 +0100 Subject: [PATCH 200/209] dependabot: Updates (#16459) * build(deps): bump markupsafe in /source/extensions/filters/network/kafka Bumps [markupsafe](https://github.com/pallets/markupsafe) from 1.1.1 to 2.0.0. - [Release notes](https://github.com/pallets/markupsafe/releases) - [Changelog](https://github.com/pallets/markupsafe/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/markupsafe/compare/1.1.1...2.0.0) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey * build(deps): bump markupsafe from 1.1.1 to 2.0.0 in /tools/protodoc Bumps [markupsafe](https://github.com/pallets/markupsafe) from 1.1.1 to 2.0.0. - [Release notes](https://github.com/pallets/markupsafe/releases) - [Changelog](https://github.com/pallets/markupsafe/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/markupsafe/compare/1.1.1...2.0.0) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey * build(deps): bump jinja2 in /source/extensions/filters/network/kafka Bumps [jinja2](https://github.com/pallets/jinja) from 2.11.3 to 3.0.0. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/2.11.3...3.0.0) Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump jinja2 from 2.11.3 to 3.0.0 in /tools/protodoc Bumps [jinja2](https://github.com/pallets/jinja) from 2.11.3 to 3.0.0. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/2.11.3...3.0.0) Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump jinja2 from 2.11.3 to 3.0.0 in /docs Bumps [jinja2](https://github.com/pallets/jinja) from 2.11.3 to 3.0.0. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/2.11.3...3.0.0) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey * build(deps): bump markupsafe from 1.1.1 to 2.0.0 in /configs Bumps [markupsafe](https://github.com/pallets/markupsafe) from 1.1.1 to 2.0.0. - [Release notes](https://github.com/pallets/markupsafe/releases) - [Changelog](https://github.com/pallets/markupsafe/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/markupsafe/compare/1.1.1...2.0.0) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey * build(deps): bump jinja2 from 2.11.3 to 3.0.0 in /configs Bumps [jinja2](https://github.com/pallets/jinja) from 2.11.3 to 3.0.0. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/2.11.3...3.0.0) Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump markupsafe from 1.1.1 to 2.0.0 in /docs Bumps [markupsafe](https://github.com/pallets/markupsafe) from 1.1.1 to 2.0.0. - [Release notes](https://github.com/pallets/markupsafe/releases) - [Changelog](https://github.com/pallets/markupsafe/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/markupsafe/compare/1.1.1...2.0.0) Signed-off-by: dependabot[bot] Signed-off-by: Ryan Northey Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- configs/requirements.txt | 94 ++++++++----------- docs/requirements.txt | 75 +++++++-------- .../filters/network/kafka/requirements.txt | 94 ++++++++----------- tools/protodoc/requirements.txt | 94 ++++++++----------- 4 files changed, 152 insertions(+), 205 deletions(-) diff --git a/configs/requirements.txt b/configs/requirements.txt index 0e403da124c40..b425b09f84ced 100644 --- a/configs/requirements.txt +++ b/configs/requirements.txt @@ -1,56 +1,38 @@ -Jinja2==2.11.3 \ - --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ - --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 -MarkupSafe==1.1.1 \ - --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ - --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ - --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ - --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ - --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ - --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \ - --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \ - --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ - --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ - --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \ - --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \ - --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ - --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ - --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ - --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ - --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ - --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ - --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ - --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ - --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \ - --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \ - --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ - --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ - --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ - --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \ - --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \ - --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ - --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ - --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \ - --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ - --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ - --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ - --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \ - --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \ - --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ - --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ - --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ - --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \ - --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ - --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \ - --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ - --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \ - --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ - --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \ - --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ - --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ - --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \ - --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \ - --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \ - --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ - --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ - --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 +Jinja2==3.0.0 \ + --hash=sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6 \ + --hash=sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5 +MarkupSafe==2.0.0 \ + --hash=sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0 \ + --hash=sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c \ + --hash=sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1 \ + --hash=sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b \ + --hash=sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63 \ + --hash=sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf \ + --hash=sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715 \ + --hash=sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95 \ + --hash=sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b \ + --hash=sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b \ + --hash=sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901 \ + --hash=sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b \ + --hash=sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20 \ + --hash=sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730 \ + --hash=sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66 \ + --hash=sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd \ + --hash=sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b \ + --hash=sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6 \ + --hash=sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f \ + --hash=sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1 \ + --hash=sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf \ + --hash=sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96 \ + --hash=sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d \ + --hash=sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb \ + --hash=sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5 \ + --hash=sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348 \ + --hash=sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb \ + --hash=sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958 \ + --hash=sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc \ + --hash=sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75 \ + --hash=sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8 \ + --hash=sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05 \ + --hash=sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2 \ + --hash=sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527 diff --git a/docs/requirements.txt b/docs/requirements.txt index 9ae3006b4a475..b4a0924870692 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -57,46 +57,47 @@ imagesize==1.2.0 \ # via # -r docs/requirements.txt # sphinx -jinja2==2.11.3 \ - --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ - --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 +jinja2==3.0.0 \ + --hash=sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6 \ + --hash=sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5 # via # -r docs/requirements.txt # sphinx -markupsafe==1.1.1 \ - --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ - --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ - --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ - --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ - --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ - --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ - --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ - --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ - --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ - --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ - --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ - --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ - --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ - --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ - --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ - --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ - --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ - --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ - --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ - --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ - --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ - --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ - --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ - --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ - --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ - --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ - --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ - --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ - --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ - --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ - --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ - --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ - --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be +markupsafe==2.0.0 \ + --hash=sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0 \ + --hash=sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c \ + --hash=sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1 \ + --hash=sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b \ + --hash=sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63 \ + --hash=sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf \ + --hash=sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715 \ + --hash=sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95 \ + --hash=sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b \ + --hash=sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b \ + --hash=sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901 \ + --hash=sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b \ + --hash=sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20 \ + --hash=sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730 \ + --hash=sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66 \ + --hash=sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd \ + --hash=sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b \ + --hash=sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6 \ + --hash=sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f \ + --hash=sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1 \ + --hash=sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf \ + --hash=sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96 \ + --hash=sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d \ + --hash=sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb \ + --hash=sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5 \ + --hash=sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348 \ + --hash=sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb \ + --hash=sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958 \ + --hash=sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc \ + --hash=sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75 \ + --hash=sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8 \ + --hash=sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05 \ + --hash=sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2 \ + --hash=sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527 # via # -r docs/requirements.txt # jinja2 diff --git a/source/extensions/filters/network/kafka/requirements.txt b/source/extensions/filters/network/kafka/requirements.txt index 0e403da124c40..b425b09f84ced 100644 --- a/source/extensions/filters/network/kafka/requirements.txt +++ b/source/extensions/filters/network/kafka/requirements.txt @@ -1,56 +1,38 @@ -Jinja2==2.11.3 \ - --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ - --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 -MarkupSafe==1.1.1 \ - --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ - --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ - --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ - --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ - --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ - --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \ - --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \ - --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ - --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ - --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \ - --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \ - --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ - --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ - --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ - --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ - --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ - --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ - --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ - --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ - --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \ - --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \ - --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ - --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ - --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ - --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \ - --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \ - --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ - --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ - --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \ - --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ - --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ - --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ - --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \ - --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \ - --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ - --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ - --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ - --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \ - --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ - --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \ - --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ - --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \ - --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ - --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \ - --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ - --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ - --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \ - --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \ - --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \ - --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ - --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ - --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 +Jinja2==3.0.0 \ + --hash=sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6 \ + --hash=sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5 +MarkupSafe==2.0.0 \ + --hash=sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0 \ + --hash=sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c \ + --hash=sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1 \ + --hash=sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b \ + --hash=sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63 \ + --hash=sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf \ + --hash=sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715 \ + --hash=sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95 \ + --hash=sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b \ + --hash=sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b \ + --hash=sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901 \ + --hash=sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b \ + --hash=sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20 \ + --hash=sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730 \ + --hash=sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66 \ + --hash=sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd \ + --hash=sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b \ + --hash=sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6 \ + --hash=sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f \ + --hash=sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1 \ + --hash=sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf \ + --hash=sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96 \ + --hash=sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d \ + --hash=sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb \ + --hash=sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5 \ + --hash=sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348 \ + --hash=sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb \ + --hash=sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958 \ + --hash=sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc \ + --hash=sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75 \ + --hash=sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8 \ + --hash=sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05 \ + --hash=sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2 \ + --hash=sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527 diff --git a/tools/protodoc/requirements.txt b/tools/protodoc/requirements.txt index e94db0959e4eb..07944deec73a9 100644 --- a/tools/protodoc/requirements.txt +++ b/tools/protodoc/requirements.txt @@ -1,59 +1,6 @@ -Jinja2==2.11.3 \ - --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ - --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 -MarkupSafe==1.1.1 \ - --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ - --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ - --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ - --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ - --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ - --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \ - --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \ - --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ - --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ - --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \ - --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \ - --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ - --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ - --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ - --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ - --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ - --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ - --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ - --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ - --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \ - --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \ - --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ - --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ - --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ - --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \ - --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \ - --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ - --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ - --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \ - --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ - --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ - --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ - --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \ - --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \ - --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ - --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ - --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ - --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \ - --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ - --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \ - --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ - --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \ - --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ - --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \ - --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ - --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ - --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \ - --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \ - --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \ - --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ - --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ - --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 +Jinja2==3.0.0 \ + --hash=sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6 \ + --hash=sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5 PyYAML==5.4.1 \ --hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \ --hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \ @@ -84,3 +31,38 @@ PyYAML==5.4.1 \ --hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \ --hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \ --hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0 +MarkupSafe==2.0.0 \ + --hash=sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0 \ + --hash=sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c \ + --hash=sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1 \ + --hash=sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b \ + --hash=sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63 \ + --hash=sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf \ + --hash=sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715 \ + --hash=sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95 \ + --hash=sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b \ + --hash=sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b \ + --hash=sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901 \ + --hash=sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b \ + --hash=sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20 \ + --hash=sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730 \ + --hash=sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66 \ + --hash=sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd \ + --hash=sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b \ + --hash=sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6 \ + --hash=sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f \ + --hash=sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1 \ + --hash=sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf \ + --hash=sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96 \ + --hash=sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d \ + --hash=sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb \ + --hash=sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5 \ + --hash=sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348 \ + --hash=sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb \ + --hash=sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958 \ + --hash=sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc \ + --hash=sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75 \ + --hash=sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8 \ + --hash=sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05 \ + --hash=sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2 \ + --hash=sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527 From 2443032526cf6e50d63d35770df9473dd0460fc0 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 13 May 2021 16:51:42 +0100 Subject: [PATCH 201/209] Revert "dependabot: Updates (#16459)" (#16483) This reverts commit ff62ef19107974855488fb672ff698dd69d66b26. Signed-off-by: Ryan Northey --- configs/requirements.txt | 94 +++++++++++-------- docs/requirements.txt | 75 ++++++++------- .../filters/network/kafka/requirements.txt | 94 +++++++++++-------- tools/protodoc/requirements.txt | 94 +++++++++++-------- 4 files changed, 205 insertions(+), 152 deletions(-) diff --git a/configs/requirements.txt b/configs/requirements.txt index b425b09f84ced..0e403da124c40 100644 --- a/configs/requirements.txt +++ b/configs/requirements.txt @@ -1,38 +1,56 @@ -Jinja2==3.0.0 \ - --hash=sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6 \ - --hash=sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5 -MarkupSafe==2.0.0 \ - --hash=sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0 \ - --hash=sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c \ - --hash=sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1 \ - --hash=sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b \ - --hash=sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63 \ - --hash=sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf \ - --hash=sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715 \ - --hash=sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95 \ - --hash=sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b \ - --hash=sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b \ - --hash=sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901 \ - --hash=sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b \ - --hash=sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20 \ - --hash=sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730 \ - --hash=sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66 \ - --hash=sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd \ - --hash=sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b \ - --hash=sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6 \ - --hash=sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f \ - --hash=sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1 \ - --hash=sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf \ - --hash=sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96 \ - --hash=sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d \ - --hash=sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb \ - --hash=sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5 \ - --hash=sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348 \ - --hash=sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb \ - --hash=sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958 \ - --hash=sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc \ - --hash=sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75 \ - --hash=sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8 \ - --hash=sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05 \ - --hash=sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2 \ - --hash=sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527 +Jinja2==2.11.3 \ + --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ + --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 +MarkupSafe==1.1.1 \ + --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ + --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ + --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ + --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ + --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ + --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \ + --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \ + --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ + --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ + --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \ + --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \ + --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ + --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ + --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ + --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ + --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ + --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ + --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ + --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ + --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \ + --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \ + --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ + --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ + --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ + --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \ + --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \ + --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ + --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ + --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \ + --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ + --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ + --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ + --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \ + --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \ + --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ + --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ + --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ + --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \ + --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ + --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \ + --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ + --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \ + --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ + --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \ + --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ + --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ + --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \ + --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \ + --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \ + --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ + --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ + --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 diff --git a/docs/requirements.txt b/docs/requirements.txt index b4a0924870692..9ae3006b4a475 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -57,47 +57,46 @@ imagesize==1.2.0 \ # via # -r docs/requirements.txt # sphinx -jinja2==3.0.0 \ - --hash=sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6 \ - --hash=sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5 +jinja2==2.11.3 \ + --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ + --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 # via # -r docs/requirements.txt # sphinx -markupsafe==2.0.0 \ - --hash=sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0 \ - --hash=sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c \ - --hash=sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1 \ - --hash=sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b \ - --hash=sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63 \ - --hash=sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf \ - --hash=sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715 \ - --hash=sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95 \ - --hash=sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b \ - --hash=sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b \ - --hash=sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901 \ - --hash=sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b \ - --hash=sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20 \ - --hash=sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730 \ - --hash=sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66 \ - --hash=sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd \ - --hash=sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b \ - --hash=sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6 \ - --hash=sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f \ - --hash=sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1 \ - --hash=sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf \ - --hash=sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96 \ - --hash=sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d \ - --hash=sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb \ - --hash=sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5 \ - --hash=sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348 \ - --hash=sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb \ - --hash=sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958 \ - --hash=sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc \ - --hash=sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75 \ - --hash=sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8 \ - --hash=sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05 \ - --hash=sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2 \ - --hash=sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527 +markupsafe==1.1.1 \ + --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ + --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ + --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ + --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ + --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ + --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ + --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ + --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ + --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ + --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ + --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ + --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ + --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ + --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ + --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ + --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ + --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ + --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ + --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ + --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ + --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ + --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ + --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ + --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ + --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ + --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ + --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ + --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ + --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ + --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ + --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ + --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ + --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be # via # -r docs/requirements.txt # jinja2 diff --git a/source/extensions/filters/network/kafka/requirements.txt b/source/extensions/filters/network/kafka/requirements.txt index b425b09f84ced..0e403da124c40 100644 --- a/source/extensions/filters/network/kafka/requirements.txt +++ b/source/extensions/filters/network/kafka/requirements.txt @@ -1,38 +1,56 @@ -Jinja2==3.0.0 \ - --hash=sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6 \ - --hash=sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5 -MarkupSafe==2.0.0 \ - --hash=sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0 \ - --hash=sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c \ - --hash=sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1 \ - --hash=sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b \ - --hash=sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63 \ - --hash=sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf \ - --hash=sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715 \ - --hash=sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95 \ - --hash=sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b \ - --hash=sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b \ - --hash=sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901 \ - --hash=sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b \ - --hash=sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20 \ - --hash=sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730 \ - --hash=sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66 \ - --hash=sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd \ - --hash=sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b \ - --hash=sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6 \ - --hash=sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f \ - --hash=sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1 \ - --hash=sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf \ - --hash=sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96 \ - --hash=sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d \ - --hash=sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb \ - --hash=sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5 \ - --hash=sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348 \ - --hash=sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb \ - --hash=sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958 \ - --hash=sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc \ - --hash=sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75 \ - --hash=sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8 \ - --hash=sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05 \ - --hash=sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2 \ - --hash=sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527 +Jinja2==2.11.3 \ + --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ + --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 +MarkupSafe==1.1.1 \ + --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ + --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ + --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ + --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ + --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ + --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \ + --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \ + --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ + --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ + --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \ + --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \ + --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ + --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ + --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ + --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ + --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ + --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ + --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ + --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ + --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \ + --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \ + --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ + --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ + --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ + --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \ + --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \ + --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ + --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ + --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \ + --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ + --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ + --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ + --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \ + --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \ + --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ + --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ + --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ + --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \ + --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ + --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \ + --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ + --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \ + --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ + --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \ + --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ + --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ + --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \ + --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \ + --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \ + --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ + --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ + --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 diff --git a/tools/protodoc/requirements.txt b/tools/protodoc/requirements.txt index 07944deec73a9..e94db0959e4eb 100644 --- a/tools/protodoc/requirements.txt +++ b/tools/protodoc/requirements.txt @@ -1,6 +1,59 @@ -Jinja2==3.0.0 \ - --hash=sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6 \ - --hash=sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5 +Jinja2==2.11.3 \ + --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ + --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 +MarkupSafe==1.1.1 \ + --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ + --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ + --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ + --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ + --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ + --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \ + --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \ + --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ + --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ + --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \ + --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \ + --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ + --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ + --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ + --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ + --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ + --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ + --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ + --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ + --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \ + --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \ + --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ + --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ + --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ + --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \ + --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \ + --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ + --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ + --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \ + --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ + --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ + --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ + --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \ + --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \ + --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ + --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ + --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ + --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \ + --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ + --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \ + --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ + --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \ + --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ + --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \ + --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ + --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ + --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \ + --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \ + --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \ + --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ + --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ + --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 PyYAML==5.4.1 \ --hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \ --hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \ @@ -31,38 +84,3 @@ PyYAML==5.4.1 \ --hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \ --hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \ --hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0 -MarkupSafe==2.0.0 \ - --hash=sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0 \ - --hash=sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c \ - --hash=sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1 \ - --hash=sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b \ - --hash=sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63 \ - --hash=sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf \ - --hash=sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715 \ - --hash=sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95 \ - --hash=sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b \ - --hash=sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b \ - --hash=sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901 \ - --hash=sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b \ - --hash=sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20 \ - --hash=sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730 \ - --hash=sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66 \ - --hash=sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd \ - --hash=sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b \ - --hash=sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6 \ - --hash=sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f \ - --hash=sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1 \ - --hash=sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf \ - --hash=sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96 \ - --hash=sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d \ - --hash=sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb \ - --hash=sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5 \ - --hash=sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348 \ - --hash=sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb \ - --hash=sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958 \ - --hash=sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc \ - --hash=sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75 \ - --hash=sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8 \ - --hash=sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05 \ - --hash=sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2 \ - --hash=sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527 From 874fb03dbc662edcba5f64082c2996a2378ae7f6 Mon Sep 17 00:00:00 2001 From: Long Dai Date: Fri, 14 May 2021 09:32:48 +0800 Subject: [PATCH 202/209] transport_sockets: removed well_known_names.h file (#16164) Signed-off-by: Long Dai --- source/common/quic/BUILD | 4 ---- source/common/quic/envoy_quic_client_connection.cc | 2 -- source/common/quic/envoy_quic_proof_source.cc | 2 -- source/common/quic/envoy_quic_server_connection.cc | 2 -- source/common/quic/envoy_quic_utils.cc | 5 +---- source/common/quic/quic_transport_socket_factory.h | 5 +---- source/common/upstream/BUILD | 4 ---- source/common/upstream/health_discovery_service.h | 2 -- source/common/upstream/upstream_impl.cc | 6 ++---- source/extensions/filters/listener/http_inspector/BUILD | 1 - .../filters/listener/http_inspector/http_inspector.cc | 5 +---- source/extensions/filters/listener/tls_inspector/BUILD | 1 - .../filters/listener/tls_inspector/tls_inspector.cc | 5 +---- source/extensions/transport_sockets/alts/BUILD | 1 - source/extensions/transport_sockets/alts/config.h | 4 +--- source/extensions/transport_sockets/proxy_protocol/BUILD | 1 - .../extensions/transport_sockets/proxy_protocol/config.h | 4 +--- source/extensions/transport_sockets/raw_buffer/BUILD | 1 - source/extensions/transport_sockets/raw_buffer/config.h | 4 +--- source/extensions/transport_sockets/starttls/BUILD | 2 -- source/extensions/transport_sockets/starttls/config.h | 9 +++------ .../transport_sockets/starttls/starttls_socket.h | 4 +--- source/extensions/transport_sockets/tap/BUILD | 1 - source/extensions/transport_sockets/tap/config.h | 4 +--- source/extensions/transport_sockets/tls/BUILD | 1 - source/extensions/transport_sockets/tls/config.h | 4 +--- source/server/BUILD | 3 --- source/server/active_tcp_listener.cc | 5 +---- source/server/listener_impl.cc | 1 - source/server/listener_manager_impl.cc | 6 ++---- test/common/quic/envoy_quic_dispatcher_test.cc | 1 - test/common/quic/envoy_quic_proof_source_test.cc | 6 ++---- test/common/quic/envoy_quic_server_session_test.cc | 1 - .../transport_sockets/starttls/starttls_socket_test.cc | 4 ++-- test/server/filter_chain_benchmark_test.cc | 2 -- 35 files changed, 22 insertions(+), 91 deletions(-) diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index be256eccbf6b9..ba0c0401ea00c 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -79,7 +79,6 @@ envoy_cc_library( ":quic_io_handle_wrapper_lib", ":quic_transport_socket_factory_lib", "//include/envoy/ssl:tls_certificate_config_interface", - "//source/extensions/transport_sockets:well_known_names", "//source/server:connection_handler_lib", "@com_googlesource_quiche//:quic_core_crypto_certificate_view_lib", ], @@ -288,7 +287,6 @@ envoy_cc_library( ":quic_io_handle_wrapper_lib", ":quic_network_connection_lib", "//source/common/quic:envoy_quic_utils_lib", - "//source/extensions/transport_sockets:well_known_names", "//source/server:connection_handler_lib", "@com_googlesource_quiche//:quic_core_connection_lib", ], @@ -370,7 +368,6 @@ envoy_cc_library( "//source/common/network:listen_socket_lib", "//source/common/network:socket_option_factory_lib", "//source/common/quic:quic_io_handle_wrapper_lib", - "//source/extensions/transport_sockets:well_known_names", "@com_googlesource_quiche//:quic_core_config_lib", "@com_googlesource_quiche//:quic_core_http_header_list_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -388,7 +385,6 @@ envoy_cc_library( "//include/envoy/server:transport_socket_config_interface", "//include/envoy/ssl:context_config_interface", "//source/common/common:assert_lib", - "//source/extensions/transport_sockets:well_known_names", "//source/extensions/transport_sockets/tls:context_config_lib", "//source/extensions/transport_sockets/tls:ssl_socket_lib", "@envoy_api//envoy/extensions/transport_sockets/quic/v3:pkg_cc_proto", diff --git a/source/common/quic/envoy_quic_client_connection.cc b/source/common/quic/envoy_quic_client_connection.cc index 158fa4d2244b3..c9046d45b73a6 100644 --- a/source/common/quic/envoy_quic_client_connection.cc +++ b/source/common/quic/envoy_quic_client_connection.cc @@ -10,8 +10,6 @@ #include "common/quic/envoy_quic_packet_writer.h" #include "common/quic/envoy_quic_utils.h" -#include "extensions/transport_sockets/well_known_names.h" - namespace Envoy { namespace Quic { diff --git a/source/common/quic/envoy_quic_proof_source.cc b/source/common/quic/envoy_quic_proof_source.cc index fd9554f1a7a8b..e1b7673596d7d 100644 --- a/source/common/quic/envoy_quic_proof_source.cc +++ b/source/common/quic/envoy_quic_proof_source.cc @@ -7,8 +7,6 @@ #include "common/quic/envoy_quic_utils.h" #include "common/quic/quic_io_handle_wrapper.h" -#include "extensions/transport_sockets/well_known_names.h" - #include "openssl/bytestring.h" #include "quiche/quic/core/crypto/certificate_view.h" diff --git a/source/common/quic/envoy_quic_server_connection.cc b/source/common/quic/envoy_quic_server_connection.cc index b17a9a88663f3..f2bc90257dd85 100644 --- a/source/common/quic/envoy_quic_server_connection.cc +++ b/source/common/quic/envoy_quic_server_connection.cc @@ -4,8 +4,6 @@ #include "common/quic/envoy_quic_utils.h" #include "common/quic/quic_io_handle_wrapper.h" -#include "extensions/transport_sockets/well_known_names.h" - namespace Envoy { namespace Quic { diff --git a/source/common/quic/envoy_quic_utils.cc b/source/common/quic/envoy_quic_utils.cc index 969782322f098..2f9682b31a20f 100644 --- a/source/common/quic/envoy_quic_utils.cc +++ b/source/common/quic/envoy_quic_utils.cc @@ -9,8 +9,6 @@ #include "common/network/socket_option_factory.h" #include "common/network/utility.h" -#include "extensions/transport_sockets/well_known_names.h" - namespace Envoy { namespace Quic { @@ -237,8 +235,7 @@ createServerConnectionSocket(Network::IoHandle& io_handle, std::make_unique(io_handle), quicAddressToEnvoyAddressInstance(self_address), quicAddressToEnvoyAddressInstance(peer_address)); - connection_socket->setDetectedTransportProtocol( - Extensions::TransportSockets::TransportProtocolNames::get().Quic); + connection_socket->setDetectedTransportProtocol("quic"); connection_socket->setRequestedServerName(hostname); connection_socket->setRequestedApplicationProtocols({alpn}); return connection_socket; diff --git a/source/common/quic/quic_transport_socket_factory.h b/source/common/quic/quic_transport_socket_factory.h index cfad2cc329a6f..18fa00a7b0342 100644 --- a/source/common/quic/quic_transport_socket_factory.h +++ b/source/common/quic/quic_transport_socket_factory.h @@ -8,7 +8,6 @@ #include "common/common/assert.h" #include "extensions/transport_sockets/tls/ssl_socket.h" -#include "extensions/transport_sockets/well_known_names.h" namespace Envoy { namespace Quic { @@ -135,9 +134,7 @@ class QuicTransportSocketConfigFactory : public virtual Server::Configuration::TransportSocketConfigFactory { public: // Server::Configuration::TransportSocketConfigFactory - std::string name() const override { - return Extensions::TransportSockets::TransportSocketNames::get().Quic; - } + std::string name() const override { return "envoy.transport_sockets.quic"; } }; class QuicServerTransportSocketConfigFactory diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index efcb951029fcc..c8f8a219b17a8 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -258,7 +258,6 @@ envoy_cc_library( "//source/common/network:resolver_lib", "//source/common/protobuf:message_validator_lib", "//source/common/protobuf:utility_lib", - "//source/extensions/transport_sockets:well_known_names", "//source/server:transport_socket_config_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -482,7 +481,6 @@ envoy_cc_library( "//source/common/protobuf:utility_lib", "//source/common/runtime:runtime_lib", "//source/extensions/filters/network/common:utility_lib", - "//source/extensions/transport_sockets:well_known_names", "//source/server:transport_socket_config_lib", ], ) @@ -496,7 +494,6 @@ envoy_cc_library( "//source/common/config:utility_lib", "//source/common/protobuf", "//source/common/protobuf:utility_lib", - "//source/extensions/transport_sockets:well_known_names", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], @@ -593,7 +590,6 @@ envoy_cc_library( "//source/common/protobuf", "//source/common/protobuf:utility_lib", "//source/extensions/clusters:well_known_names", - "//source/extensions/transport_sockets:well_known_names", "//source/server:transport_socket_config_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", ], diff --git a/source/common/upstream/health_discovery_service.h b/source/common/upstream/health_discovery_service.h index 536b1d0b15f4f..e943efe63fe41 100644 --- a/source/common/upstream/health_discovery_service.h +++ b/source/common/upstream/health_discovery_service.h @@ -23,8 +23,6 @@ #include "server/transport_socket_config_impl.h" -#include "extensions/transport_sockets/well_known_names.h" - #include "absl/container/flat_hash_map.h" namespace Envoy { diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 07db4f18f2ff6..52affaa72b46c 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -52,7 +52,6 @@ #include "server/transport_socket_config_impl.h" #include "extensions/filters/network/common/utility.h" -#include "extensions/transport_sockets/well_known_names.h" #include "absl/container/node_hash_set.h" #include "absl/strings/str_cat.h" @@ -898,12 +897,11 @@ Network::TransportSocketFactoryPtr createTransportSocketFactory( auto transport_socket = config.transport_socket(); if (!config.has_transport_socket()) { if (config.has_hidden_envoy_deprecated_tls_context()) { - transport_socket.set_name(Extensions::TransportSockets::TransportSocketNames::get().Tls); + transport_socket.set_name("envoy.transport_sockets.tls"); transport_socket.mutable_typed_config()->PackFrom( config.hidden_envoy_deprecated_tls_context()); } else { - transport_socket.set_name( - Extensions::TransportSockets::TransportSocketNames::get().RawBuffer); + transport_socket.set_name("envoy.transport_sockets.raw_buffer"); } } diff --git a/source/extensions/filters/listener/http_inspector/BUILD b/source/extensions/filters/listener/http_inspector/BUILD index 5873f83eb830d..849277d618d12 100644 --- a/source/extensions/filters/listener/http_inspector/BUILD +++ b/source/extensions/filters/listener/http_inspector/BUILD @@ -25,7 +25,6 @@ envoy_cc_library( "//source/common/common:minimal_logger_lib", "//source/common/http:headers_lib", "//source/common/http:utility_lib", - "//source/extensions/transport_sockets:well_known_names", ], ) diff --git a/source/extensions/filters/listener/http_inspector/http_inspector.cc b/source/extensions/filters/listener/http_inspector/http_inspector.cc index 331277720e9e2..3f3c2c4d24264 100644 --- a/source/extensions/filters/listener/http_inspector/http_inspector.cc +++ b/source/extensions/filters/listener/http_inspector/http_inspector.cc @@ -10,8 +10,6 @@ #include "common/http/headers.h" #include "common/http/utility.h" -#include "extensions/transport_sockets/well_known_names.h" - #include "absl/strings/match.h" #include "absl/strings/str_split.h" @@ -40,8 +38,7 @@ Network::FilterStatus Filter::onAccept(Network::ListenerFilterCallbacks& cb) { const Network::ConnectionSocket& socket = cb.socket(); const absl::string_view transport_protocol = socket.detectedTransportProtocol(); - if (!transport_protocol.empty() && - transport_protocol != TransportSockets::TransportProtocolNames::get().RawBuffer) { + if (!transport_protocol.empty() && transport_protocol != "raw_buffer") { ENVOY_LOG(trace, "http inspector: cannot inspect http protocol with transport socket {}", transport_protocol); return Network::FilterStatus::Continue; diff --git a/source/extensions/filters/listener/tls_inspector/BUILD b/source/extensions/filters/listener/tls_inspector/BUILD index 9ee8da494d73a..3f6837524e2b2 100644 --- a/source/extensions/filters/listener/tls_inspector/BUILD +++ b/source/extensions/filters/listener/tls_inspector/BUILD @@ -29,7 +29,6 @@ envoy_cc_library( "//source/common/api:os_sys_calls_lib", "//source/common/common:assert_lib", "//source/common/common:minimal_logger_lib", - "//source/extensions/transport_sockets:well_known_names", ], ) diff --git a/source/extensions/filters/listener/tls_inspector/tls_inspector.cc b/source/extensions/filters/listener/tls_inspector/tls_inspector.cc index f773dade15d3c..f2adbaf36367e 100644 --- a/source/extensions/filters/listener/tls_inspector/tls_inspector.cc +++ b/source/extensions/filters/listener/tls_inspector/tls_inspector.cc @@ -13,8 +13,6 @@ #include "common/api/os_sys_calls_impl.h" #include "common/common/assert.h" -#include "extensions/transport_sockets/well_known_names.h" - #include "absl/strings/str_join.h" #include "openssl/ssl.h" @@ -227,8 +225,7 @@ ParseState Filter::parseClientHello(const void* data, size_t len) { } else { config_->stats().alpn_not_found_.inc(); } - cb_->socket().setDetectedTransportProtocol( - TransportSockets::TransportProtocolNames::get().Tls); + cb_->socket().setDetectedTransportProtocol("tls"); } else { config_->stats().tls_not_found_.inc(); } diff --git a/source/extensions/transport_sockets/alts/BUILD b/source/extensions/transport_sockets/alts/BUILD index 6e38ca522082e..587e9a2ecf29d 100644 --- a/source/extensions/transport_sockets/alts/BUILD +++ b/source/extensions/transport_sockets/alts/BUILD @@ -48,7 +48,6 @@ envoy_cc_extension( "//include/envoy/registry", "//include/envoy/server:transport_socket_config_interface", "//source/common/grpc:google_grpc_context_lib", - "//source/extensions/transport_sockets:well_known_names", "@envoy_api//envoy/extensions/transport_sockets/alts/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/transport_sockets/alts/config.h b/source/extensions/transport_sockets/alts/config.h index adec81d3faec5..2a1a9d40b22d1 100644 --- a/source/extensions/transport_sockets/alts/config.h +++ b/source/extensions/transport_sockets/alts/config.h @@ -2,8 +2,6 @@ #include "envoy/server/transport_socket_config.h" -#include "extensions/transport_sockets/well_known_names.h" - namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -14,7 +12,7 @@ class AltsTransportSocketConfigFactory : public virtual Server::Configuration::TransportSocketConfigFactory { public: ProtobufTypes::MessagePtr createEmptyConfigProto() override; - std::string name() const override { return TransportSocketNames::get().Alts; } + std::string name() const override { return "envoy.transport_sockets.alts"; } }; class UpstreamAltsTransportSocketConfigFactory diff --git a/source/extensions/transport_sockets/proxy_protocol/BUILD b/source/extensions/transport_sockets/proxy_protocol/BUILD index 105ac9b506d9f..e268b828524c3 100644 --- a/source/extensions/transport_sockets/proxy_protocol/BUILD +++ b/source/extensions/transport_sockets/proxy_protocol/BUILD @@ -23,7 +23,6 @@ envoy_cc_extension( "//include/envoy/registry", "//include/envoy/server:transport_socket_config_interface", "//source/common/config:utility_lib", - "//source/extensions/transport_sockets:well_known_names", "@envoy_api//envoy/extensions/transport_sockets/proxy_protocol/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/transport_sockets/proxy_protocol/config.h b/source/extensions/transport_sockets/proxy_protocol/config.h index 9ab05d870b401..b5b4bbe7bdb9e 100644 --- a/source/extensions/transport_sockets/proxy_protocol/config.h +++ b/source/extensions/transport_sockets/proxy_protocol/config.h @@ -2,8 +2,6 @@ #include "envoy/server/transport_socket_config.h" -#include "extensions/transport_sockets/well_known_names.h" - namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -16,7 +14,7 @@ namespace ProxyProtocol { class UpstreamProxyProtocolSocketConfigFactory : public Server::Configuration::UpstreamTransportSocketConfigFactory { public: - std::string name() const override { return TransportSocketNames::get().UpstreamProxyProtocol; } + std::string name() const override { return "envoy.transport_sockets.upstream_proxy_protocol"; } ProtobufTypes::MessagePtr createEmptyConfigProto() override; Network::TransportSocketFactoryPtr createTransportSocketFactory( const Protobuf::Message& config, diff --git a/source/extensions/transport_sockets/raw_buffer/BUILD b/source/extensions/transport_sockets/raw_buffer/BUILD index d09cdff468f47..94a2bee0a980a 100644 --- a/source/extensions/transport_sockets/raw_buffer/BUILD +++ b/source/extensions/transport_sockets/raw_buffer/BUILD @@ -26,7 +26,6 @@ envoy_cc_extension( "//include/envoy/registry", "//include/envoy/server:transport_socket_config_interface", "//source/common/network:raw_buffer_socket_lib", - "//source/extensions/transport_sockets:well_known_names", "@envoy_api//envoy/extensions/transport_sockets/raw_buffer/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/transport_sockets/raw_buffer/config.h b/source/extensions/transport_sockets/raw_buffer/config.h index b17f9836f5552..b1353b6c2d601 100644 --- a/source/extensions/transport_sockets/raw_buffer/config.h +++ b/source/extensions/transport_sockets/raw_buffer/config.h @@ -3,8 +3,6 @@ #include "envoy/registry/registry.h" #include "envoy/server/transport_socket_config.h" -#include "extensions/transport_sockets/well_known_names.h" - namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -16,7 +14,7 @@ namespace RawBuffer { */ class RawBufferSocketFactory : public virtual Server::Configuration::TransportSocketConfigFactory { public: - std::string name() const override { return TransportSocketNames::get().RawBuffer; } + std::string name() const override { return "envoy.transport_sockets.raw_buffer"; } ProtobufTypes::MessagePtr createEmptyConfigProto() override; }; diff --git a/source/extensions/transport_sockets/starttls/BUILD b/source/extensions/transport_sockets/starttls/BUILD index 8748f049b9b7b..f3414c9837e37 100644 --- a/source/extensions/transport_sockets/starttls/BUILD +++ b/source/extensions/transport_sockets/starttls/BUILD @@ -27,7 +27,6 @@ envoy_cc_extension( "//include/envoy/registry", "//include/envoy/server:transport_socket_config_interface", "//source/common/config:utility_lib", - "//source/extensions/transport_sockets:well_known_names", "@envoy_api//envoy/extensions/transport_sockets/starttls/v3:pkg_cc_proto", ], ) @@ -50,7 +49,6 @@ envoy_cc_library( "//source/common/common:empty_string", "//source/common/common:minimal_logger_lib", "//source/common/common:thread_annotations", - "//source/extensions/transport_sockets:well_known_names", "@envoy_api//envoy/extensions/transport_sockets/starttls/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/transport_sockets/starttls/config.h b/source/extensions/transport_sockets/starttls/config.h index 89765fc5f1cfa..2b5edbbef6d00 100644 --- a/source/extensions/transport_sockets/starttls/config.h +++ b/source/extensions/transport_sockets/starttls/config.h @@ -6,8 +6,6 @@ #include "common/config/utility.h" -#include "extensions/transport_sockets/well_known_names.h" - namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -16,7 +14,7 @@ namespace StartTls { template class BaseStartTlsSocketFactory : public ConfigFactory { public: - std::string name() const override { return TransportSocketNames::get().StartTls; } + std::string name() const override { return "envoy.transport_sockets.starttls"; } ProtobufTypes::MessagePtr createEmptyConfigProto() override { return std::make_unique(); @@ -25,12 +23,11 @@ class BaseStartTlsSocketFactory : public ConfigFactory { protected: ConfigFactory& rawSocketConfigFactory() { return Config::Utility::getAndCheckFactoryByName( - TransportSocketNames::get().RawBuffer); + "envoy.transport_sockets.raw_buffer"); } ConfigFactory& tlsSocketConfigFactory() { - return Config::Utility::getAndCheckFactoryByName( - TransportSocketNames::get().Tls); + return Config::Utility::getAndCheckFactoryByName("envoy.transport_sockets.tls"); } }; diff --git a/source/extensions/transport_sockets/starttls/starttls_socket.h b/source/extensions/transport_sockets/starttls/starttls_socket.h index dc84d776be0dc..616502b08bfa8 100644 --- a/source/extensions/transport_sockets/starttls/starttls_socket.h +++ b/source/extensions/transport_sockets/starttls/starttls_socket.h @@ -9,8 +9,6 @@ #include "common/buffer/buffer_impl.h" #include "common/common/logger.h" -#include "extensions/transport_sockets/well_known_names.h" - namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -28,7 +26,7 @@ class StartTlsSocket : public Network::TransportSocket, Logger::LoggablefailureReason(); } diff --git a/source/extensions/transport_sockets/tap/BUILD b/source/extensions/transport_sockets/tap/BUILD index 5461e5d2585c7..e97cb4f1255c3 100644 --- a/source/extensions/transport_sockets/tap/BUILD +++ b/source/extensions/transport_sockets/tap/BUILD @@ -70,7 +70,6 @@ envoy_cc_extension( "//include/envoy/server:transport_socket_config_interface", "//source/common/config:utility_lib", "//source/common/protobuf:utility_lib", - "//source/extensions/transport_sockets:well_known_names", "@envoy_api//envoy/config/tap/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/transport_sockets/tap/v3:pkg_cc_proto", ], diff --git a/source/extensions/transport_sockets/tap/config.h b/source/extensions/transport_sockets/tap/config.h index ac41dd19c9fc0..c641cf45484ba 100644 --- a/source/extensions/transport_sockets/tap/config.h +++ b/source/extensions/transport_sockets/tap/config.h @@ -2,8 +2,6 @@ #include "envoy/server/transport_socket_config.h" -#include "extensions/transport_sockets/well_known_names.h" - namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -15,7 +13,7 @@ namespace Tap { */ class TapSocketConfigFactory : public virtual Server::Configuration::TransportSocketConfigFactory { public: - std::string name() const override { return TransportSocketNames::get().Tap; } + std::string name() const override { return "envoy.transport_sockets.tap"; } ProtobufTypes::MessagePtr createEmptyConfigProto() override; }; diff --git a/source/extensions/transport_sockets/tls/BUILD b/source/extensions/transport_sockets/tls/BUILD index b05b7770a1310..29dfa2be2a9dc 100644 --- a/source/extensions/transport_sockets/tls/BUILD +++ b/source/extensions/transport_sockets/tls/BUILD @@ -27,7 +27,6 @@ envoy_cc_extension( "//include/envoy/network:transport_socket_interface", "//include/envoy/registry", "//include/envoy/server:transport_socket_config_interface", - "//source/extensions/transport_sockets:well_known_names", "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/transport_sockets/tls/config.h b/source/extensions/transport_sockets/tls/config.h index a10d597e09db2..f05066bd8141a 100644 --- a/source/extensions/transport_sockets/tls/config.h +++ b/source/extensions/transport_sockets/tls/config.h @@ -3,8 +3,6 @@ #include "envoy/registry/registry.h" #include "envoy/server/transport_socket_config.h" -#include "extensions/transport_sockets/well_known_names.h" - namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -17,7 +15,7 @@ namespace Tls { class SslSocketConfigFactory : public virtual Server::Configuration::TransportSocketConfigFactory { public: ~SslSocketConfigFactory() override = default; - std::string name() const override { return TransportSocketNames::get().Tls; } + std::string name() const override { return "envoy.transport_sockets.tls"; } }; class UpstreamSslSocketFactory : public Server::Configuration::UpstreamTransportSocketConfigFactory, diff --git a/source/server/BUILD b/source/server/BUILD index dd46e4e446c00..bd71a70146747 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -134,7 +134,6 @@ envoy_cc_library( "//source/common/network:connection_lib", "//source/common/stats:timespan_lib", "//source/common/stream_info:stream_info_lib", - "//source/extensions/transport_sockets:well_known_names", "//source/server:active_listener_base", ], ) @@ -153,7 +152,6 @@ envoy_cc_library( "//include/envoy/network:listen_socket_interface", "//include/envoy/network:listener_interface", "//include/envoy/server:listener_manager_interface", - "//source/extensions/transport_sockets:well_known_names", "//source/server:connection_handler_impl", ], ) @@ -412,7 +410,6 @@ envoy_cc_library( "//source/common/stream_info:stream_info_lib", "//source/extensions/filters/listener:well_known_names", "//source/extensions/filters/network/http_connection_manager:config", - "//source/extensions/transport_sockets:well_known_names", "//source/extensions/upstreams/http/generic:config", "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/source/server/active_tcp_listener.cc b/source/server/active_tcp_listener.cc index 4146ed86b39ac..bcf5cae34d836 100644 --- a/source/server/active_tcp_listener.cc +++ b/source/server/active_tcp_listener.cc @@ -12,8 +12,6 @@ #include "common/network/utility.h" #include "common/stats/timespan_impl.h" -#include "extensions/transport_sockets/well_known_names.h" - namespace Envoy { namespace Server { @@ -198,8 +196,7 @@ void ActiveTcpSocket::newConnection() { } else { // Set default transport protocol if none of the listener filters did it. if (socket_->detectedTransportProtocol().empty()) { - socket_->setDetectedTransportProtocol( - Extensions::TransportSockets::TransportProtocolNames::get().RawBuffer); + socket_->setDetectedTransportProtocol("raw_buffer"); } // TODO(lambdai): add integration test // TODO: Address issues in wider scope. See https://github.com/envoyproxy/envoy/issues/8925 diff --git a/source/server/listener_impl.cc b/source/server/listener_impl.cc index 4c1f5f19dfa66..405937f7cf7b1 100644 --- a/source/server/listener_impl.cc +++ b/source/server/listener_impl.cc @@ -32,7 +32,6 @@ #include "server/transport_socket_config_impl.h" #include "extensions/filters/listener/well_known_names.h" -#include "extensions/transport_sockets/well_known_names.h" #if defined(ENVOY_ENABLE_QUIC) #include "common/quic/active_quic_listener.h" diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index 5bf3709c41de6..c9fa407fd7912 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -35,7 +35,6 @@ #include "server/transport_socket_config_impl.h" #include "extensions/filters/listener/well_known_names.h" -#include "extensions/transport_sockets/well_known_names.h" namespace Envoy { namespace Server { @@ -944,12 +943,11 @@ Network::DrainableFilterChainSharedPtr ListenerFilterChainFactoryBuilder::buildF auto transport_socket = filter_chain.transport_socket(); if (!filter_chain.has_transport_socket()) { if (filter_chain.has_hidden_envoy_deprecated_tls_context()) { - transport_socket.set_name(Extensions::TransportSockets::TransportSocketNames::get().Tls); + transport_socket.set_name("envoy.transport_sockets.tls"); transport_socket.mutable_typed_config()->PackFrom( filter_chain.hidden_envoy_deprecated_tls_context()); } else { - transport_socket.set_name( - Extensions::TransportSockets::TransportSocketNames::get().RawBuffer); + transport_socket.set_name("envoy.transport_sockets.raw_buffer"); } } diff --git a/test/common/quic/envoy_quic_dispatcher_test.cc b/test/common/quic/envoy_quic_dispatcher_test.cc index 5b4f7b4b76b63..f46bac22788c2 100644 --- a/test/common/quic/envoy_quic_dispatcher_test.cc +++ b/test/common/quic/envoy_quic_dispatcher_test.cc @@ -33,7 +33,6 @@ #include "test/common/quic/test_utils.h" #include "common/quic/envoy_quic_alarm_factory.h" #include "common/quic/envoy_quic_utils.h" -#include "extensions/transport_sockets/well_known_names.h" #include "server/configuration_impl.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/common/quic/envoy_quic_proof_source_test.cc b/test/common/quic/envoy_quic_proof_source_test.cc index 7596d970757ae..960d385d2b2f7 100644 --- a/test/common/quic/envoy_quic_proof_source_test.cc +++ b/test/common/quic/envoy_quic_proof_source_test.cc @@ -154,8 +154,7 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { *connection_socket.addressProvider().localAddress()); EXPECT_EQ(*quicAddressToEnvoyAddressInstance(client_address_), *connection_socket.addressProvider().remoteAddress()); - EXPECT_EQ(Extensions::TransportSockets::TransportProtocolNames::get().Quic, - connection_socket.detectedTransportProtocol()); + EXPECT_EQ("quic", connection_socket.detectedTransportProtocol()); EXPECT_EQ("h3-29", connection_socket.requestedApplicationProtocols()[0]); return &filter_chain_; })); @@ -243,8 +242,7 @@ TEST_F(EnvoyQuicProofSourceTest, GetProofFailNoCertConfig) { *connection_socket.addressProvider().localAddress()); EXPECT_EQ(*quicAddressToEnvoyAddressInstance(client_address_), *connection_socket.addressProvider().remoteAddress()); - EXPECT_EQ(Extensions::TransportSockets::TransportProtocolNames::get().Quic, - connection_socket.detectedTransportProtocol()); + EXPECT_EQ("quic", connection_socket.detectedTransportProtocol()); EXPECT_EQ("h3-29", connection_socket.requestedApplicationProtocols()[0]); return &filter_chain_; })); diff --git a/test/common/quic/envoy_quic_server_session_test.cc b/test/common/quic/envoy_quic_server_session_test.cc index 415b5c609823d..6f13cf9359b0c 100644 --- a/test/common/quic/envoy_quic_server_session_test.cc +++ b/test/common/quic/envoy_quic_server_session_test.cc @@ -30,7 +30,6 @@ #include "common/quic/envoy_quic_utils.h" #include "test/common/quic/test_proof_source.h" #include "test/common/quic/test_utils.h" -#include "extensions/transport_sockets/well_known_names.h" #include "envoy/stats/stats_macros.h" #include "common/event/libevent_scheduler.h" diff --git a/test/extensions/transport_sockets/starttls/starttls_socket_test.cc b/test/extensions/transport_sockets/starttls/starttls_socket_test.cc index 23f0a19428124..28f51201395b0 100644 --- a/test/extensions/transport_sockets/starttls/starttls_socket_test.cc +++ b/test/extensions/transport_sockets/starttls/starttls_socket_test.cc @@ -38,7 +38,7 @@ TEST(StartTlsTest, BasicSwitch) { socket->setTransportSocketCallbacks(transport_callbacks); // StartTls socket is initial clear-text state. All calls should be forwarded to raw socket. - ASSERT_THAT(socket->protocol(), TransportProtocolNames::get().StartTls); + ASSERT_THAT(socket->protocol(), "starttls"); EXPECT_CALL(*raw_socket, onConnected()); EXPECT_CALL(*ssl_socket, onConnected()).Times(0); socket->onConnected(); @@ -81,7 +81,7 @@ TEST(StartTlsTest, BasicSwitch) { // Now calls to all methods should be forwarded to ssl_socket. // raw_socket has been destructed when switch to tls happened. - ASSERT_THAT(socket->protocol(), TransportProtocolNames::get().StartTls); + ASSERT_THAT(socket->protocol(), "starttls"); EXPECT_CALL(*ssl_socket, onConnected()); socket->onConnected(); diff --git a/test/server/filter_chain_benchmark_test.cc b/test/server/filter_chain_benchmark_test.cc index ef5403bdb471a..07f12b7812a82 100644 --- a/test/server/filter_chain_benchmark_test.cc +++ b/test/server/filter_chain_benchmark_test.cc @@ -11,8 +11,6 @@ #include "server/filter_chain_manager_impl.h" -#include "extensions/transport_sockets/well_known_names.h" - #include "test/benchmark/main.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/factory_context.h" From 8c59b6a27d9f803656c77f546082d4c483533683 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Fri, 14 May 2021 02:02:46 +0000 Subject: [PATCH 203/209] Workaround for CI gcc build error with long argument list (#16484) Signed-off-by: Yan Avlasov --- ci/do_ci.sh | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index e832dd61ff31e..89e949c18dcea 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -218,11 +218,20 @@ elif [[ "$CI_TARGET" == "bazel.sizeopt" ]]; then bazel_binary_build sizeopt exit 0 elif [[ "$CI_TARGET" == "bazel.gcc" ]]; then - BAZEL_BUILD_OPTIONS+=("--test_env=HEAPCHECK=") + # Temporariliy exclude some extensions from the envoy binary to address build failures + # due to long command line. Tests will still run. + BAZEL_BUILD_OPTIONS+=( + "--test_env=HEAPCHECK=" + "--//source/extensions/filters/network/rocketmq_proxy:enabled=False" + "--//source/extensions/filters/http/admission_control:enabled=False" + "--//source/extensions/filters/http/dynamo:enabled=False" + "--//source/extensions/filters/http/header_to_metadata:enabled=False" + "--//source/extensions/filters/http/on_demand:enabled=False") setup_gcc_toolchain - echo "Testing ${TEST_TARGETS[*]}" - bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" -c fastbuild "${TEST_TARGETS[@]}" + # Disable //test/config_test:example_configs_test so it does not fail because of excluded extensions above + echo "Testing ${TEST_TARGETS[*]} -//test/config_test:example_configs_test" + bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" -c fastbuild -- "${TEST_TARGETS[@]}" -//test/config_test:example_configs_test echo "bazel release build with gcc..." bazel_binary_build fastbuild From 55cfadd5d6207974d5897f52d0ed10773a09f786 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 14 May 2021 13:46:35 +0100 Subject: [PATCH 204/209] dependabot: Updates (#16485) Signed-off-by: Ryan Northey Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- configs/requirements.txt | 94 ++++++++----------- .../filters/network/kafka/requirements.txt | 94 ++++++++----------- tools/protodoc/requirements.txt | 94 ++++++++----------- 3 files changed, 114 insertions(+), 168 deletions(-) diff --git a/configs/requirements.txt b/configs/requirements.txt index 0e403da124c40..b425b09f84ced 100644 --- a/configs/requirements.txt +++ b/configs/requirements.txt @@ -1,56 +1,38 @@ -Jinja2==2.11.3 \ - --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ - --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 -MarkupSafe==1.1.1 \ - --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ - --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ - --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ - --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ - --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ - --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \ - --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \ - --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ - --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ - --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \ - --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \ - --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ - --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ - --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ - --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ - --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ - --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ - --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ - --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ - --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \ - --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \ - --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ - --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ - --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ - --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \ - --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \ - --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ - --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ - --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \ - --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ - --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ - --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ - --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \ - --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \ - --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ - --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ - --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ - --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \ - --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ - --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \ - --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ - --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \ - --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ - --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \ - --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ - --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ - --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \ - --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \ - --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \ - --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ - --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ - --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 +Jinja2==3.0.0 \ + --hash=sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6 \ + --hash=sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5 +MarkupSafe==2.0.0 \ + --hash=sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0 \ + --hash=sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c \ + --hash=sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1 \ + --hash=sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b \ + --hash=sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63 \ + --hash=sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf \ + --hash=sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715 \ + --hash=sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95 \ + --hash=sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b \ + --hash=sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b \ + --hash=sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901 \ + --hash=sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b \ + --hash=sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20 \ + --hash=sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730 \ + --hash=sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66 \ + --hash=sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd \ + --hash=sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b \ + --hash=sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6 \ + --hash=sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f \ + --hash=sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1 \ + --hash=sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf \ + --hash=sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96 \ + --hash=sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d \ + --hash=sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb \ + --hash=sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5 \ + --hash=sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348 \ + --hash=sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb \ + --hash=sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958 \ + --hash=sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc \ + --hash=sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75 \ + --hash=sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8 \ + --hash=sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05 \ + --hash=sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2 \ + --hash=sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527 diff --git a/source/extensions/filters/network/kafka/requirements.txt b/source/extensions/filters/network/kafka/requirements.txt index 0e403da124c40..b425b09f84ced 100644 --- a/source/extensions/filters/network/kafka/requirements.txt +++ b/source/extensions/filters/network/kafka/requirements.txt @@ -1,56 +1,38 @@ -Jinja2==2.11.3 \ - --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ - --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 -MarkupSafe==1.1.1 \ - --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ - --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ - --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ - --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ - --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ - --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \ - --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \ - --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ - --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ - --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \ - --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \ - --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ - --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ - --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ - --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ - --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ - --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ - --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ - --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ - --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \ - --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \ - --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ - --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ - --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ - --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \ - --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \ - --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ - --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ - --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \ - --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ - --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ - --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ - --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \ - --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \ - --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ - --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ - --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ - --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \ - --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ - --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \ - --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ - --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \ - --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ - --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \ - --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ - --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ - --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \ - --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \ - --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \ - --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ - --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ - --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 +Jinja2==3.0.0 \ + --hash=sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6 \ + --hash=sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5 +MarkupSafe==2.0.0 \ + --hash=sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0 \ + --hash=sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c \ + --hash=sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1 \ + --hash=sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b \ + --hash=sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63 \ + --hash=sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf \ + --hash=sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715 \ + --hash=sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95 \ + --hash=sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b \ + --hash=sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b \ + --hash=sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901 \ + --hash=sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b \ + --hash=sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20 \ + --hash=sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730 \ + --hash=sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66 \ + --hash=sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd \ + --hash=sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b \ + --hash=sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6 \ + --hash=sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f \ + --hash=sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1 \ + --hash=sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf \ + --hash=sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96 \ + --hash=sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d \ + --hash=sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb \ + --hash=sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5 \ + --hash=sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348 \ + --hash=sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb \ + --hash=sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958 \ + --hash=sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc \ + --hash=sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75 \ + --hash=sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8 \ + --hash=sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05 \ + --hash=sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2 \ + --hash=sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527 diff --git a/tools/protodoc/requirements.txt b/tools/protodoc/requirements.txt index e94db0959e4eb..07944deec73a9 100644 --- a/tools/protodoc/requirements.txt +++ b/tools/protodoc/requirements.txt @@ -1,59 +1,6 @@ -Jinja2==2.11.3 \ - --hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \ - --hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 -MarkupSafe==1.1.1 \ - --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ - --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ - --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ - --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ - --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ - --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \ - --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \ - --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ - --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ - --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \ - --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \ - --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ - --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ - --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ - --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ - --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ - --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ - --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ - --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ - --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \ - --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \ - --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ - --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ - --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ - --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \ - --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \ - --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ - --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ - --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \ - --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ - --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ - --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ - --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \ - --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \ - --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ - --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ - --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ - --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \ - --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ - --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \ - --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ - --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \ - --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ - --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \ - --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ - --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ - --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \ - --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \ - --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \ - --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ - --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ - --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 +Jinja2==3.0.0 \ + --hash=sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6 \ + --hash=sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5 PyYAML==5.4.1 \ --hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \ --hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \ @@ -84,3 +31,38 @@ PyYAML==5.4.1 \ --hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \ --hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \ --hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0 +MarkupSafe==2.0.0 \ + --hash=sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0 \ + --hash=sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c \ + --hash=sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1 \ + --hash=sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b \ + --hash=sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63 \ + --hash=sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf \ + --hash=sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715 \ + --hash=sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95 \ + --hash=sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b \ + --hash=sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b \ + --hash=sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901 \ + --hash=sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b \ + --hash=sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20 \ + --hash=sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730 \ + --hash=sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66 \ + --hash=sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd \ + --hash=sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b \ + --hash=sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6 \ + --hash=sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f \ + --hash=sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1 \ + --hash=sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf \ + --hash=sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96 \ + --hash=sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d \ + --hash=sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb \ + --hash=sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5 \ + --hash=sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348 \ + --hash=sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb \ + --hash=sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958 \ + --hash=sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc \ + --hash=sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75 \ + --hash=sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8 \ + --hash=sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05 \ + --hash=sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2 \ + --hash=sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527 From 1f4ca4c0d395360845d0453a949f6c53acb147cb Mon Sep 17 00:00:00 2001 From: Inode1 Date: Fri, 14 May 2021 18:28:59 +0300 Subject: [PATCH 205/209] oauth: fix sds update (#16253) The client_secret field was not updated on configuration changes. Signed-off-by: i.makarychev --- source/common/http/utility.cc | 79 ++++---- source/common/http/utility.h | 8 + .../extensions/filters/http/oauth2/filter.h | 29 ++- test/common/http/utility_test.cc | 37 ++++ test/extensions/filters/http/oauth2/BUILD | 3 + .../filters/http/oauth2/filter_test.cc | 76 ++++++++ .../http/oauth2/oauth_integration_test.cc | 176 +++++++++++++----- 7 files changed, 314 insertions(+), 94 deletions(-) diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index 4c5a237ef2ccc..8075a7ba6e602 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -254,6 +254,46 @@ bool maybeAdjustForIpv6(absl::string_view absolute_url, uint64_t& offset, uint64 return true; } +std::string parseCookie(const HeaderMap& headers, const std::string& key, + const std::string& cookie) { + + std::string ret; + + headers.iterateReverse([&key, &ret, &cookie](const HeaderEntry& header) -> HeaderMap::Iterate { + // Find the cookie headers in the request (typically, there's only one). + if (header.key() == cookie) { + + // Split the cookie header into individual cookies. + for (const auto& s : StringUtil::splitToken(header.value().getStringView(), ";")) { + // Find the key part of the cookie (i.e. the name of the cookie). + size_t first_non_space = s.find_first_not_of(' '); + size_t equals_index = s.find('='); + if (equals_index == absl::string_view::npos) { + // The cookie is malformed if it does not have an `=`. Continue + // checking other cookies in this header. + continue; + } + const absl::string_view k = s.substr(first_non_space, equals_index - first_non_space); + // If the key matches, parse the value from the rest of the cookie string. + if (k == key) { + absl::string_view v = s.substr(equals_index + 1, s.size() - 1); + + // Cookie values may be wrapped in double quotes. + // https://tools.ietf.org/html/rfc6265#section-4.1.1 + if (v.size() >= 2 && v.back() == '"' && v[0] == '"') { + v = v.substr(1, v.size() - 2); + } + ret = std::string{v}; + return HeaderMap::Iterate::Break; + } + } + } + return HeaderMap::Iterate::Continue; + }); + + return ret; +} + bool Utility::Url::initialize(absl::string_view absolute_url, bool is_connect) { struct http_parser_url u; http_parser_url_init(&u); @@ -382,42 +422,11 @@ absl::string_view Utility::findQueryStringStart(const HeaderString& path) { } std::string Utility::parseCookieValue(const HeaderMap& headers, const std::string& key) { + return parseCookie(headers, key, Http::Headers::get().Cookie.get()); +} - std::string ret; - - headers.iterateReverse([&key, &ret](const HeaderEntry& header) -> HeaderMap::Iterate { - // Find the cookie headers in the request (typically, there's only one). - if (header.key() == Http::Headers::get().Cookie.get()) { - - // Split the cookie header into individual cookies. - for (const auto& s : StringUtil::splitToken(header.value().getStringView(), ";")) { - // Find the key part of the cookie (i.e. the name of the cookie). - size_t first_non_space = s.find_first_not_of(" "); - size_t equals_index = s.find('='); - if (equals_index == absl::string_view::npos) { - // The cookie is malformed if it does not have an `=`. Continue - // checking other cookies in this header. - continue; - } - const absl::string_view k = s.substr(first_non_space, equals_index - first_non_space); - // If the key matches, parse the value from the rest of the cookie string. - if (k == key) { - absl::string_view v = s.substr(equals_index + 1, s.size() - 1); - - // Cookie values may be wrapped in double quotes. - // https://tools.ietf.org/html/rfc6265#section-4.1.1 - if (v.size() >= 2 && v.back() == '"' && v[0] == '"') { - v = v.substr(1, v.size() - 2); - } - ret = std::string{v}; - return HeaderMap::Iterate::Break; - } - } - } - return HeaderMap::Iterate::Continue; - }); - - return ret; +std::string Utility::parseSetCookieValue(const Http::HeaderMap& headers, const std::string& key) { + return parseCookie(headers, key, Http::Headers::get().SetCookie.get()); } std::string Utility::makeSetCookieValue(const std::string& key, const std::string& value, diff --git a/source/common/http/utility.h b/source/common/http/utility.h index 3cf328ea3b5ff..e01f65357feeb 100644 --- a/source/common/http/utility.h +++ b/source/common/http/utility.h @@ -248,6 +248,14 @@ absl::string_view findQueryStringStart(const HeaderString& path); **/ std::string parseCookieValue(const HeaderMap& headers, const std::string& key); +/** + * Parse a particular value out of a set-cookie + * @param headers supplies the headers to get the set-cookie from. + * @param key the key for the particular set-cookie value to return + * @return std::string the parsed set-cookie value, or "" if none exists + **/ +std::string parseSetCookieValue(const HeaderMap& headers, const std::string& key); + /** * Produce the value for a Set-Cookie header with the given parameters. * @param key is the name of the cookie that is being set. diff --git a/source/extensions/filters/http/oauth2/filter.h b/source/extensions/filters/http/oauth2/filter.h index b9a4cb9382117..bd70be95ed2d7 100644 --- a/source/extensions/filters/http/oauth2/filter.h +++ b/source/extensions/filters/http/oauth2/filter.h @@ -49,38 +49,35 @@ class SDSSecretReader : public SecretReader { public: SDSSecretReader(Secret::GenericSecretConfigProviderSharedPtr client_secret_provider, Secret::GenericSecretConfigProviderSharedPtr token_secret_provider, Api::Api& api) - : api_(api), client_secret_provider_(std::move(client_secret_provider)), - token_secret_provider_(std::move(token_secret_provider)) { - readAndWatchSecret(client_secret_, *client_secret_provider_); - readAndWatchSecret(token_secret_, *token_secret_provider_); - } + : update_callback_client_(readAndWatchSecret(client_secret_, client_secret_provider, api)), + update_callback_token_(readAndWatchSecret(token_secret_, token_secret_provider, api)) {} const std::string& clientSecret() const override { return client_secret_; } const std::string& tokenSecret() const override { return token_secret_; } private: - void readAndWatchSecret(std::string& value, - Secret::GenericSecretConfigProvider& secret_provider) { - const auto* secret = secret_provider.secret(); + Envoy::Common::CallbackHandlePtr + readAndWatchSecret(std::string& value, + Secret::GenericSecretConfigProviderSharedPtr& secret_provider, Api::Api& api) { + const auto* secret = secret_provider->secret(); if (secret != nullptr) { - value = Config::DataSource::read(secret->secret(), true, api_); + value = Config::DataSource::read(secret->secret(), true, api); } - update_callback_ = secret_provider.addUpdateCallback([&secret_provider, this, &value]() { - const auto* secret = secret_provider.secret(); + return secret_provider->addUpdateCallback([secret_provider, &api, &value]() { + const auto* secret = secret_provider->secret(); if (secret != nullptr) { - value = Config::DataSource::read(secret->secret(), true, api_); + value = Config::DataSource::read(secret->secret(), true, api); } }); } + std::string client_secret_; std::string token_secret_; - Api::Api& api_; - Envoy::Common::CallbackHandlePtr update_callback_; - Secret::GenericSecretConfigProviderSharedPtr client_secret_provider_; - Secret::GenericSecretConfigProviderSharedPtr token_secret_provider_; + Envoy::Common::CallbackHandlePtr update_callback_client_; + Envoy::Common::CallbackHandlePtr update_callback_token_; }; /** diff --git a/test/common/http/utility_test.cc b/test/common/http/utility_test.cc index 52a23bad8a5a4..fcf26b70e9c12 100644 --- a/test/common/http/utility_test.cc +++ b/test/common/http/utility_test.cc @@ -549,6 +549,18 @@ TEST(HttpUtility, TestParseCookie) { EXPECT_EQ(value, "abc123"); } +TEST(HttpUtility, TestParseSetCookie) { + TestRequestHeaderMapImpl headers{ + {"someheader", "10.0.0.1"}, + {"set-cookie", "somekey=somevalue; someotherkey=someothervalue"}, + {"set-cookie", "abc=def; token=abc123; Expires=Wed, 09 Jun 2021 10:18:14 GMT"}, + {"set-cookie", "key2=value2; key3=value3"}}; + + std::string key{"token"}; + std::string value = Utility::parseSetCookieValue(headers, key); + EXPECT_EQ(value, "abc123"); +} + TEST(HttpUtility, TestParseCookieBadValues) { TestRequestHeaderMapImpl headers{{"cookie", "token1=abc123; = "}, {"cookie", "token2=abc123; "}, @@ -561,6 +573,18 @@ TEST(HttpUtility, TestParseCookieBadValues) { EXPECT_EQ(Utility::parseCookieValue(headers, "token4"), "abc123"); } +TEST(HttpUtility, TestParseSetCookieBadValues) { + TestRequestHeaderMapImpl headers{{"set-cookie", "token1=abc123; = "}, + {"set-cookie", "token2=abc123; "}, + {"set-cookie", "; token3=abc123;"}, + {"set-cookie", "=; token4=\"abc123\""}}; + + EXPECT_EQ(Utility::parseSetCookieValue(headers, "token1"), "abc123"); + EXPECT_EQ(Utility::parseSetCookieValue(headers, "token2"), "abc123"); + EXPECT_EQ(Utility::parseSetCookieValue(headers, "token3"), "abc123"); + EXPECT_EQ(Utility::parseSetCookieValue(headers, "token4"), "abc123"); +} + TEST(HttpUtility, TestParseCookieWithQuotes) { TestRequestHeaderMapImpl headers{ {"someheader", "10.0.0.1"}, @@ -574,6 +598,19 @@ TEST(HttpUtility, TestParseCookieWithQuotes) { EXPECT_EQ(Utility::parseCookieValue(headers, "leadingdquote"), "\"foobar"); } +TEST(HttpUtility, TestParseSetCookieWithQuotes) { + TestRequestHeaderMapImpl headers{ + {"someheader", "10.0.0.1"}, + {"set-cookie", "dquote=\"; quoteddquote=\"\"\""}, + {"set-cookie", "leadingdquote=\"foobar;"}, + {"set-cookie", "abc=def; token=\"abc123\"; Expires=Wed, 09 Jun 2021 10:18:14 GMT"}}; + + EXPECT_EQ(Utility::parseSetCookieValue(headers, "token"), "abc123"); + EXPECT_EQ(Utility::parseSetCookieValue(headers, "dquote"), "\""); + EXPECT_EQ(Utility::parseSetCookieValue(headers, "quoteddquote"), "\""); + EXPECT_EQ(Utility::parseSetCookieValue(headers, "leadingdquote"), "\"foobar"); +} + TEST(HttpUtility, TestMakeSetCookieValue) { EXPECT_EQ("name=\"value\"; Max-Age=10", Utility::makeSetCookieValue("name", "value", "", std::chrono::seconds(10), false)); diff --git a/test/extensions/filters/http/oauth2/BUILD b/test/extensions/filters/http/oauth2/BUILD index 9e23aa35c1a27..01e40bcd70e65 100644 --- a/test/extensions/filters/http/oauth2/BUILD +++ b/test/extensions/filters/http/oauth2/BUILD @@ -27,7 +27,9 @@ envoy_extension_cc_test( srcs = ["oauth_integration_test.cc"], extension_name = "envoy.filters.http.oauth2", deps = [ + "//source/common/http:utility_lib", "//source/extensions/filters/http/oauth2:config", + "//source/extensions/filters/http/oauth2:oauth_lib", "//test/integration:http_integration_lib", "//test/integration:integration_lib", "//test/mocks/server:factory_context_mocks", @@ -39,6 +41,7 @@ envoy_extension_cc_test( srcs = ["filter_test.cc"], extension_name = "envoy.filters.http.oauth2", deps = [ + "//source/common/secret:secret_manager_impl_lib", "//source/extensions/filters/http/oauth2:config", "//source/extensions/filters/http/oauth2:oauth_callback_interface", "//source/extensions/filters/http/oauth2:oauth_client", diff --git a/test/extensions/filters/http/oauth2/filter_test.cc b/test/extensions/filters/http/oauth2/filter_test.cc index 60e852c3170e6..521d28dff3cb2 100644 --- a/test/extensions/filters/http/oauth2/filter_test.cc +++ b/test/extensions/filters/http/oauth2/filter_test.cc @@ -10,6 +10,7 @@ #include "common/http/message_impl.h" #include "common/protobuf/message_validator_impl.h" #include "common/protobuf/utility.h" +#include "common/secret/secret_manager_impl.h" #include "extensions/filters/http/oauth2/filter.h" @@ -155,6 +156,81 @@ class OAuth2Test : public testing::Test { Event::SimulatedTimeSystem test_time_; }; +// Verifies that the OAuth SDSSecretReader correctly updates dynamic generic secret. +TEST_F(OAuth2Test, SdsDynamicGenericSecret) { + NiceMock config_tracker; + Secret::SecretManagerImpl secret_manager{config_tracker}; + envoy::config::core::v3::ConfigSource config_source; + + NiceMock secret_context; + NiceMock local_info; + Api::ApiPtr api = Api::createApiForTest(); + Stats::IsolatedStoreImpl stats; + NiceMock init_manager; + Init::TargetHandlePtr init_handle; + NiceMock dispatcher; + EXPECT_CALL(secret_context, localInfo()).WillRepeatedly(ReturnRef(local_info)); + EXPECT_CALL(secret_context, api()).WillRepeatedly(ReturnRef(*api)); + EXPECT_CALL(secret_context, dispatcher()).WillRepeatedly(ReturnRef(dispatcher)); + EXPECT_CALL(secret_context, stats()).WillRepeatedly(ReturnRef(stats)); + EXPECT_CALL(secret_context, initManager()).WillRepeatedly(ReturnRef(init_manager)); + EXPECT_CALL(init_manager, add(_)) + .WillRepeatedly(Invoke([&init_handle](const Init::Target& target) { + init_handle = target.createHandle("test"); + })); + + auto client_secret_provider = + secret_manager.findOrCreateGenericSecretProvider(config_source, "client", secret_context); + auto client_callback = secret_context.cluster_manager_.subscription_factory_.callbacks_; + auto token_secret_provider = + secret_manager.findOrCreateGenericSecretProvider(config_source, "token", secret_context); + auto token_callback = secret_context.cluster_manager_.subscription_factory_.callbacks_; + + SDSSecretReader secret_reader(client_secret_provider, token_secret_provider, *api); + EXPECT_TRUE(secret_reader.clientSecret().empty()); + EXPECT_TRUE(secret_reader.tokenSecret().empty()); + + const std::string yaml_client = R"EOF( +name: client +generic_secret: + secret: + inline_string: "client_test" +)EOF"; + + envoy::extensions::transport_sockets::tls::v3::Secret typed_secret; + TestUtility::loadFromYaml(yaml_client, typed_secret); + const auto decoded_resources_client = TestUtility::decodeResources({typed_secret}); + + client_callback->onConfigUpdate(decoded_resources_client.refvec_, ""); + EXPECT_EQ(secret_reader.clientSecret(), "client_test"); + EXPECT_EQ(secret_reader.tokenSecret(), ""); + + const std::string yaml_token = R"EOF( +name: token +generic_secret: + secret: + inline_string: "token_test" +)EOF"; + TestUtility::loadFromYaml(yaml_token, typed_secret); + const auto decoded_resources_token = TestUtility::decodeResources({typed_secret}); + + token_callback->onConfigUpdate(decoded_resources_token.refvec_, ""); + EXPECT_EQ(secret_reader.clientSecret(), "client_test"); + EXPECT_EQ(secret_reader.tokenSecret(), "token_test"); + + const std::string yaml_client_recheck = R"EOF( +name: client +generic_secret: + secret: + inline_string: "client_test_recheck" +)EOF"; + TestUtility::loadFromYaml(yaml_client_recheck, typed_secret); + const auto decoded_resources_client_recheck = TestUtility::decodeResources({typed_secret}); + + client_callback->onConfigUpdate(decoded_resources_client_recheck.refvec_, ""); + EXPECT_EQ(secret_reader.clientSecret(), "client_test_recheck"); + EXPECT_EQ(secret_reader.tokenSecret(), "token_test"); +} // Verifies that we fail constructing the filter if the configured cluster doesn't exist. TEST_F(OAuth2Test, InvalidCluster) { ON_CALL(factory_context_.cluster_manager_, clusters()) diff --git a/test/extensions/filters/http/oauth2/oauth_integration_test.cc b/test/extensions/filters/http/oauth2/oauth_integration_test.cc index 95ce47c1b9bb6..e12091ef503ef 100644 --- a/test/extensions/filters/http/oauth2/oauth_integration_test.cc +++ b/test/extensions/filters/http/oauth2/oauth_integration_test.cc @@ -1,15 +1,20 @@ +#include "common/crypto/utility.h" +#include "common/http/utility.h" #include "common/protobuf/utility.h" #include "source/extensions/filters/http/oauth2/oauth_response.pb.h" +#include "extensions/filters/http/oauth2/filter.h" + #include "test/integration/http_integration.h" +#include "absl/strings/escaping.h" #include "gtest/gtest.h" namespace Envoy { namespace Extensions { namespace HttpFilters { -namespace Oauth { +namespace Oauth2 { namespace { class OauthIntegrationTest : public testing::Test, public HttpIntegrationTest { @@ -35,7 +40,43 @@ class OauthIntegrationTest : public testing::Test, public HttpIntegrationTest { void initialize() override { setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); - config_helper_.addFilter(R"EOF( + TestEnvironment::writeStringToFileForTest("token_secret.yaml", R"EOF( +resources: + - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" + name: token + generic_secret: + secret: + inline_string: "token_secret")EOF", + false); + + TestEnvironment::writeStringToFileForTest("token_secret_1.yaml", R"EOF( +resources: + - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" + name: token + generic_secret: + secret: + inline_string: "token_secret_1")EOF", + false); + + TestEnvironment::writeStringToFileForTest("hmac_secret.yaml", R"EOF( +resources: + - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" + name: hmac + generic_secret: + secret: + inline_string: "hmac_secret")EOF", + false); + + TestEnvironment::writeStringToFileForTest("hmac_secret_1.yaml", R"EOF( +resources: + - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" + name: hmac + generic_secret: + secret: + inline_string: "hmac_secret_1")EOF", + false); + + config_helper_.addFilter(TestEnvironment::substitute(R"EOF( name: oauth typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2 @@ -56,8 +97,14 @@ name: oauth client_id: foo token_secret: name: token + sds_config: + path: "{{ test_tmpdir }}/token_secret.yaml" + resource_api_version: V3 hmac_secret: name: hmac + sds_config: + path: "{{ test_tmpdir }}/hmac_secret.yaml" + resource_api_version: V3 auth_scopes: - user - openid @@ -66,26 +113,86 @@ name: oauth - oauth2-resource - http://example.com - https://example.com -)EOF"); +)EOF")); // Add the OAuth cluster. config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { *bootstrap.mutable_static_resources()->add_clusters() = config_helper_.buildStaticCluster("oauth", 0, "127.0.0.1"); - - auto* token_secret = bootstrap.mutable_static_resources()->add_secrets(); - token_secret->set_name("token"); - token_secret->mutable_generic_secret()->mutable_secret()->set_inline_bytes("token_secret"); - - auto* hmac_secret = bootstrap.mutable_static_resources()->add_secrets(); - hmac_secret->set_name("hmac"); - hmac_secret->mutable_generic_secret()->mutable_secret()->set_inline_bytes("hmac_secret"); }); setUpstreamCount(2); HttpIntegrationTest::initialize(); } + + bool validateHmac(const Http::ResponseHeaderMap& headers, absl::string_view host, + absl::string_view hmac_secret) { + std::string expires = Http::Utility::parseSetCookieValue(headers, "OauthExpires"); + std::string token = Http::Utility::parseSetCookieValue(headers, "BearerToken"); + std::string hmac = Http::Utility::parseSetCookieValue(headers, "OauthHMAC"); + + Http::TestRequestHeaderMapImpl validate_headers{{":authority", std::string(host)}}; + + validate_headers.addReferenceKey(Http::Headers::get().Cookie, absl::StrCat("OauthHMAC=", hmac)); + validate_headers.addReferenceKey(Http::Headers::get().Cookie, + absl::StrCat("OauthExpires=", expires)); + validate_headers.addReferenceKey(Http::Headers::get().Cookie, + absl::StrCat("BearerToken=", token)); + + OAuth2CookieValidator validator{api_->timeSource()}; + validator.setParams(validate_headers, std::string(hmac_secret)); + return validator.isValid(); + } + + void doAuthenticationFlow(absl::string_view token_secret, absl::string_view hmac_secret) { + codec_client_ = makeHttpConnection(lookupPort("http")); + + Http::TestRequestHeaderMapImpl headers{ + {":method", "GET"}, + {":path", "/callback?code=foo&state=http%3A%2F%2Ftraffic.example.com%2Fnot%2F_oauth"}, + {":scheme", "http"}, + {"x-forwarded-proto", "http"}, + {":authority", "authority"}, + {"authority", "Bearer token"}}; + + auto encoder_decoder = codec_client_->startRequest(headers); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + + waitForNextUpstreamRequest(1); + + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + + std::string request_body = upstream_request_->body().toString(); + const auto query_parameters = Http::Utility::parseFromBody(request_body); + auto it = query_parameters.find("client_secret"); + + ASSERT_TRUE(it != query_parameters.end()); + EXPECT_EQ(it->second, token_secret); + + upstream_request_->encodeHeaders( + Http::TestRequestHeaderMapImpl{{":status", "200"}, {"content-type", "application/json"}}, + false); + + envoy::extensions::http_filters::oauth2::OAuthResponse oauth_response; + oauth_response.mutable_access_token()->set_value("bar"); + oauth_response.mutable_expires_in()->set_value(DateUtil::nowToSeconds(api_->timeSource()) + 10); + + Buffer::OwnedImpl buffer(MessageUtil::getJsonStringFromMessageOrDie(oauth_response)); + upstream_request_->encodeData(buffer, true); + + // We should get an immediate redirect back. + response->waitForHeaders(); + + EXPECT_TRUE( + validateHmac(response->headers(), headers.Host()->value().getStringView(), hmac_secret)); + + EXPECT_EQ("302", response->headers().getStatusValue()); + + RELEASE_ASSERT(response->waitForEndStream(), "unexpected timeout"); + codec_client_->close(); + } }; // Regular request gets redirected to the login page. @@ -110,41 +217,24 @@ TEST_F(OauthIntegrationTest, UnauthenticatedFlow) { TEST_F(OauthIntegrationTest, AuthenticationFlow) { initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - Http::TestRequestHeaderMapImpl headers{ - {":method", "GET"}, - {":path", "/callback?code=foo&state=http%3A%2F%2Ftraffic.example.com%2Fnot%2F_oauth"}, - {":scheme", "http"}, - {"x-forwarded-proto", "http"}, - {":authority", "authority"}, - {"authority", "Bearer token"}}; - auto encoder_decoder = codec_client_->startRequest(headers); - request_encoder_ = &encoder_decoder.first; - auto response = std::move(encoder_decoder.second); - - waitForNextUpstreamRequest(1); - - ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); - - upstream_request_->encodeHeaders( - Http::TestRequestHeaderMapImpl{{":status", "200"}, {"content-type", "application/json"}}, - false); - - envoy::extensions::http_filters::oauth2::OAuthResponse oauth_response; - oauth_response.mutable_access_token()->set_value("bar"); - oauth_response.mutable_expires_in()->set_value(DateUtil::nowToSeconds(api_->timeSource()) + 10); - - Buffer::OwnedImpl buffer(MessageUtil::getJsonStringFromMessageOrDie(oauth_response)); - upstream_request_->encodeData(buffer, true); - - // We should get an immediate redirect back. - response->waitForHeaders(); - EXPECT_EQ("302", response->headers().getStatusValue()); + // 1. Do one authentication flow. + doAuthenticationFlow("token_secret", "hmac_secret"); + + // 2. Reload secrets. + EXPECT_EQ(test_server_->counter("sds.token.update_success")->value(), 1); + EXPECT_EQ(test_server_->counter("sds.hmac.update_success")->value(), 1); + TestEnvironment::renameFile(TestEnvironment::temporaryPath("token_secret_1.yaml"), + TestEnvironment::temporaryPath("token_secret.yaml")); + test_server_->waitForCounterEq("sds.token.update_success", 2, std::chrono::milliseconds(5000)); + TestEnvironment::renameFile(TestEnvironment::temporaryPath("hmac_secret_1.yaml"), + TestEnvironment::temporaryPath("hmac_secret.yaml")); + test_server_->waitForCounterEq("sds.hmac.update_success", 2, std::chrono::milliseconds(5000)); + // 3. Do another one authentication flow. + doAuthenticationFlow("token_secret_1", "hmac_secret_1"); } } // namespace -} // namespace Oauth +} // namespace Oauth2 } // namespace HttpFilters } // namespace Extensions } // namespace Envoy From ae780e21824bcb947299f5a048797fbf3cac21ba Mon Sep 17 00:00:00 2001 From: Christoph Pakulski Date: Fri, 14 May 2021 14:54:44 -0400 Subject: [PATCH 206/209] test: deflake upstream starttls integration test (#16436) Signed-off-by: Christoph Pakulski --- source/common/network/connection_impl.h | 1 + test/config/utility.cc | 43 -- .../transport_sockets/starttls/BUILD | 3 - .../upstream_starttls_integration_test.cc | 378 +++++++----------- test/integration/fake_upstream.h | 2 + 5 files changed, 137 insertions(+), 290 deletions(-) diff --git a/source/common/network/connection_impl.h b/source/common/network/connection_impl.h index 21aaa8e268c7e..bc80f1f92a0fa 100644 --- a/source/common/network/connection_impl.h +++ b/source/common/network/connection_impl.h @@ -122,6 +122,7 @@ class ConnectionImpl : public ConnectionImplBase, // Reconsider how to make fairness happen. void setTransportSocketIsReadable() override; void flushWriteBuffer() override; + TransportSocketPtr& transportSocket() { return transport_socket_; } // Obtain global next connection ID. This should only be used in tests. static uint64_t nextGlobalIdForTest() { return next_global_id_; } diff --git a/test/config/utility.cc b/test/config/utility.cc index 6a77b020e6909..b2a8e1600f9e1 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -146,49 +146,6 @@ std::string ConfigHelper::startTlsConfig() { TestEnvironment::runfilesPath("test/config/integration/certs/serverkey.pem"))); } -envoy::config::cluster::v3::Cluster ConfigHelper::buildStartTlsCluster(const std::string& address, - int port) { - API_NO_BOOST(envoy::config::cluster::v3::Cluster) cluster; - auto config_str = fmt::format( - R"EOF( - name: dummy_cluster - connect_timeout: 5s - type: STATIC - load_assignment: - cluster_name: dummy_cluster - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: {} - port_value: {} - transport_socket: - name: "starttls" - typed_config: - "@type": type.googleapis.com/envoy.extensions.transport_sockets.starttls.v3.UpstreamStartTlsConfig - cleartext_socket_config: - tls_socket_config: - common_tls_context: - tls_certificates: - certificate_chain: - filename: {} - private_key: - filename: {} - lb_policy: ROUND_ROBIN - typed_extension_protocol_options: - envoy.extensions.upstreams.http.v3.HttpProtocolOptions: - "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions - explicit_http_config: - http2_protocol_options: {{}} - )EOF", - address, port, TestEnvironment::runfilesPath("test/config/integration/certs/clientcert.pem"), - TestEnvironment::runfilesPath("test/config/integration/certs/clientkey.pem")); - - TestUtility::loadFromYaml(config_str, cluster); - return cluster; -} - std::string ConfigHelper::tlsInspectorFilter() { return R"EOF( name: "envoy.filters.listener.tls_inspector" diff --git a/test/extensions/transport_sockets/starttls/BUILD b/test/extensions/transport_sockets/starttls/BUILD index 64883b0c14201..966dd1e1b4ca8 100644 --- a/test/extensions/transport_sockets/starttls/BUILD +++ b/test/extensions/transport_sockets/starttls/BUILD @@ -62,11 +62,8 @@ envoy_extension_cc_test( extension_name = "envoy.transport_sockets.starttls", deps = [ ":starttls_integration_proto_cc_proto", - "//source/extensions/filters/network/tcp_proxy:config", - "//source/extensions/transport_sockets/raw_buffer:config", "//source/extensions/transport_sockets/starttls:config", "//test/integration:integration_lib", "//test/test_common:registry_lib", - "@envoy_api//envoy/extensions/transport_sockets/raw_buffer/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/transport_sockets/starttls/upstream_starttls_integration_test.cc b/test/extensions/transport_sockets/starttls/upstream_starttls_integration_test.cc index df86c4f17e4aa..d00af9e2389a0 100644 --- a/test/extensions/transport_sockets/starttls/upstream_starttls_integration_test.cc +++ b/test/extensions/transport_sockets/starttls/upstream_starttls_integration_test.cc @@ -1,13 +1,12 @@ -#include "envoy/extensions/transport_sockets/raw_buffer/v3/raw_buffer.pb.h" -#include "envoy/extensions/transport_sockets/raw_buffer/v3/raw_buffer.pb.validate.h" #include "envoy/network/filter.h" #include "envoy/server/filter_config.h" #include "common/network/connection_impl.h" #include "extensions/filters/network/common/factory_base.h" -#include "extensions/transport_sockets/raw_buffer/config.h" #include "extensions/transport_sockets/starttls/starttls_socket.h" +#include "extensions/transport_sockets/tls/context_config_impl.h" +#include "extensions/transport_sockets/tls/ssl_socket.h" #include "test/config/utility.h" #include "test/extensions/transport_sockets/starttls/starttls_integration_test.pb.h" @@ -20,52 +19,12 @@ namespace Envoy { -class TerminalServerTlsFilter : public Network::ReadFilter { -public: - // Network::ReadFilter - Network::FilterStatus onData(Buffer::Instance& buf, bool) override { - auto message = buf.toString(); - if (message != "usetls") { - // Just echo anything other than the 'usetls' command. - read_callbacks_->connection().write(buf, false); - } else { - read_callbacks_->connection().addBytesSentCallback([=](uint64_t bytes) -> bool { - // Wait until 6 bytes long "usetls" has been sent. - if (bytes >= 6) { - read_callbacks_->connection().startSecureTransport(); - // Unsubscribe the callback. - // Switch to tls has been completed. - return false; - } - return true; - }); - - buf.drain(buf.length()); - buf.add("switch"); - read_callbacks_->connection().write(buf, false); - } - - return Network::FilterStatus::StopIteration; - } - - Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; } - - void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override { - read_callbacks_ = &callbacks; - } - -private: - Network::ReadFilterCallbacks* read_callbacks_{}; -}; - // Simple filter for test purposes. This filter will be injected into the filter chain during -// tests. The filter reacts only to few keywords. If received payload does not contain -// allowed keyword, filter will stop iteration. -// The filter will be configured to sit on top of tcp_proxy and use start-tls transport socket. -// If it receives a data which is not known keyword it means that transport socket has not been -// successfully converted to use TLS and filter receives either encrypted data or TLS handshake -// messages. -class StartTlsSwitchFilter : public Network::ReadFilter { +// tests. +// The filter reacts only to "switch" keyword to switch upstream startTls transport socket to secure +// mode. All other payloads are forwarded either downstream or upstream respectively. +// Filter will be instantiated as terminal filter in order to have access to upstream connection. +class StartTlsSwitchFilter : public Network::Filter { public: ~StartTlsSwitchFilter() override { if (upstream_connection_) { @@ -73,15 +32,22 @@ class StartTlsSwitchFilter : public Network::ReadFilter { } } - void upstreamWrite(Buffer::Instance& data, bool end_stream); + void onCommand(Buffer::Instance& data); // Network::ReadFilter Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override; - Network::FilterStatus onNewConnection() override; void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override { read_callbacks_ = &callbacks; } + // Network::WriteFilter + Network::FilterStatus onWrite(Buffer::Instance& data, bool end_stream) override; + void initializeWriteFilterCallbacks(Network::WriteFilterCallbacks& callbacks) override { + write_callbacks_ = &callbacks; + } + + Network::FilterStatus onNewConnection() override; + static std::shared_ptr newInstance(Upstream::ClusterManager& cluster_manager) { auto p = std::shared_ptr(new StartTlsSwitchFilter(cluster_manager)); @@ -89,14 +55,14 @@ class StartTlsSwitchFilter : public Network::ReadFilter { return p; } + // Helper filter to catch onData coming on upstream connection. struct UpstreamReadFilter : public Network::ReadFilter { - UpstreamReadFilter(std::weak_ptr parent) : parent_(parent) {} Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override { if (auto parent = parent_.lock()) { - parent->upstreamWrite(data, end_stream); - return Network::FilterStatus::Continue; + parent->onCommand(data); + return parent->onWrite(data, end_stream); } else { read_callbacks_->connection().close(Network::ConnectionCloseType::FlushWrite); return Network::FilterStatus::StopIteration; @@ -119,12 +85,13 @@ class StartTlsSwitchFilter : public Network::ReadFilter { std::weak_ptr self_{}; Network::ReadFilterCallbacks* read_callbacks_{}; + Network::WriteFilterCallbacks* write_callbacks_{}; Network::ClientConnectionPtr upstream_connection_{}; Upstream::ClusterManager& cluster_manager_; }; Network::FilterStatus StartTlsSwitchFilter::onNewConnection() { - auto c = cluster_manager_.getThreadLocalCluster("dummy_cluster"); + auto c = cluster_manager_.getThreadLocalCluster("cluster_0"); auto h = c->loadBalancer().chooseHost(nullptr); upstream_connection_ = h->createConnection(read_callbacks_->connection().dispatcher(), nullptr, nullptr).connection_; @@ -139,35 +106,33 @@ Network::FilterStatus StartTlsSwitchFilter::onData(Buffer::Instance& data, bool return Network::FilterStatus::StopIteration; } + onCommand(data); upstream_connection_->write(data, end_stream); return Network::FilterStatus::Continue; } -void StartTlsSwitchFilter::upstreamWrite(Buffer::Instance& buf, bool end_stream) { +Network::FilterStatus StartTlsSwitchFilter::onWrite(Buffer::Instance& buf, bool end_stream) { + read_callbacks_->connection().write(buf, end_stream); + return Network::FilterStatus::Continue; +} + +void StartTlsSwitchFilter::onCommand(Buffer::Instance& buf) { const std::string message = buf.toString(); if (message == "switch") { // Start the upstream secure transport immediately since we clearly have all the bytes ASSERT_TRUE(upstream_connection_->startSecureTransport()); - read_callbacks_->connection().addBytesSentCallback([=](uint64_t bytes) -> bool { - // Wait until 6 bytes long "switch" has been sent. - if (bytes >= 6) { - read_callbacks_->connection().startSecureTransport(); - // Unsubscribe the callback. - // Switch to tls has been completed. - return false; - } - return true; - }); } - // Finally just forward the data downstream. - read_callbacks_->connection().write(buf, end_stream); } -// Config factory for StartTlsSwitchFilter. +// Config factory for StartTlsSwitchFilter terminal filter. class StartTlsSwitchFilterConfigFactory : public Extensions::NetworkFilters::Common::FactoryBase< test::integration::starttls::StartTlsFilterConfig> { public: explicit StartTlsSwitchFilterConfigFactory(const std::string& name) : FactoryBase(name) {} + bool isTerminalFilterByProtoTyped(const test::integration::starttls::StartTlsFilterConfig&, + Server::Configuration::FactoryContext&) override { + return true; + } Network::FilterFactoryCb createFilterFactoryFromProtoTyped(const test::integration::starttls::StartTlsFilterConfig&, @@ -183,225 +148,150 @@ class StartTlsSwitchFilterConfigFactory : public Extensions::NetworkFilters::Com const std::string name_; }; -// ClientTestConnection is used for simulating a client -// which initiates a connection to Envoy in clear-text and then switches to TLS -// without closing the socket. -class ClientTestConnection : public Network::ClientConnectionImpl { -public: - ClientTestConnection(Event::Dispatcher& dispatcher, - const Network::Address::InstanceConstSharedPtr& remote_address, - const Network::Address::InstanceConstSharedPtr& source_address, - Network::TransportSocketPtr&& transport_socket, - const Network::ConnectionSocket::OptionsSharedPtr& options) - : ClientConnectionImpl(dispatcher, remote_address, source_address, - std::move(transport_socket), options) {} - - void setTransportSocket(Network::TransportSocketPtr&& transport_socket) { - transport_socket_ = std::move(transport_socket); - transport_socket_->setTransportSocketCallbacks(*this); - - // Reset connection's state machine. - connecting_ = true; - - // Issue event which will trigger TLS handshake. - ioHandle().activateFileEvents(Event::FileReadyType::Write); - } -}; - // Fixture class for integration tests. class StartTlsIntegrationTest : public testing::TestWithParam, public BaseIntegrationTest { public: StartTlsIntegrationTest() - : BaseIntegrationTest(GetParam(), ConfigHelper::startTlsConfig()), + : BaseIntegrationTest(GetParam(), ConfigHelper::baseConfig()), stream_info_(timeSystem(), nullptr) {} void initialize() override; - void addStartTlsSwitchFilter(ConfigHelper& config_helper); - - // Contexts needed by raw buffer and tls transport sockets. - std::unique_ptr tls_context_manager_; - Network::TransportSocketFactoryPtr tls_context_; - Network::TransportSocketFactoryPtr cleartext_context_; - - MockWatermarkBuffer* client_write_buffer_{nullptr}; - ConnectionStatusCallbacks connect_callbacks_; // Config factory for StartTlsSwitchFilter. StartTlsSwitchFilterConfigFactory config_factory_{"startTls"}; Registry::InjectFactory registered_config_factory_{config_factory_}; - std::unique_ptr conn_; - std::shared_ptr payload_reader_; - Network::ListenerPtr listener_; - Network::MockTcpListenerCallbacks listener_callbacks_; - Network::ServerConnectionPtr server_connection_; + std::unique_ptr tls_context_manager_; + Network::TransportSocketFactoryPtr tls_context_; // Technically unused. StreamInfo::StreamInfoImpl stream_info_; }; void StartTlsIntegrationTest::initialize() { - EXPECT_CALL(*mock_buffer_factory_, create_(_, _, _)) - // Connection constructor will first create write buffer. - // Test tracks how many bytes are sent. - .WillOnce(Invoke([&](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - client_write_buffer_ = - new NiceMock(below_low, above_high, above_overflow); - ON_CALL(*client_write_buffer_, move(_)) - .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); - ON_CALL(*client_write_buffer_, drain(_)) - .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); - return client_write_buffer_; - })) - // Connection constructor will also create read buffer, but the test does - // not track received bytes. - .WillRepeatedly(Invoke([&](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); - })); - - config_helper_.renameListener("tcp_proxy"); - addStartTlsSwitchFilter(config_helper_); - - // Setup factories and contexts for upstream clear-text raw buffer transport socket. - auto config = std::make_unique(); - - auto factory = - std::make_unique(); - cleartext_context_ = Network::TransportSocketFactoryPtr{ - factory->createTransportSocketFactory(*config, factory_context_)}; - - // Setup factories and contexts for tls transport socket. - tls_context_manager_ = - std::make_unique(timeSystem()); - tls_context_ = Ssl::createClientSslTransportSocketFactory({}, *tls_context_manager_, *api_); - payload_reader_ = std::make_shared(*dispatcher_); - - auto socket = std::make_shared( - Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); - - // Prepare for the server side listener - EXPECT_CALL(listener_callbacks_, onAccept_(_)) - .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket) -> void { - auto server_tls_context_ = Ssl::createUpstreamSslContext(*tls_context_manager_, *api_); - - auto startTlsTransportSocket = - Extensions::TransportSockets::StartTls::StartTlsSocketFactory(move(cleartext_context_), - move(server_tls_context_)) - .createTransportSocket(std::make_shared( - absl::string_view(""), std::vector(), std::vector())); - - server_connection_ = dispatcher_->createServerConnection( - std::move(socket), move(startTlsTransportSocket), stream_info_); - server_connection_->addReadFilter(std::make_shared()); - })); - - listener_ = - dispatcher_->createListener(socket, listener_callbacks_, true, ENVOY_TCP_BACKLOG_SIZE); - - // Add a start_tls cluster - config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { - *bootstrap.mutable_static_resources()->add_clusters() = ConfigHelper::buildStartTlsCluster( - socket->addressProvider().localAddress()->ip()->addressAsString(), - socket->addressProvider().localAddress()->ip()->port()); + config_helper_.renameListener("starttls_test"); + + // Modifications to ConfigHelper::baseConfig. + // Add starttls transport socket to cluster_0 + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* cluster = bootstrap.mutable_static_resources()->mutable_clusters(0); + envoy::extensions::transport_sockets::starttls::v3::UpstreamStartTlsConfig starttls_config; + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext* tls_context = + starttls_config.mutable_tls_socket_config(); + auto* tls_certificate = tls_context->mutable_common_tls_context()->add_tls_certificates(); + tls_certificate->mutable_certificate_chain()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/clientcert.pem")); + tls_certificate->mutable_private_key()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/clientkey.pem")); + cluster->mutable_transport_socket()->set_name("envoy.transport_sockets.starttls"); + cluster->mutable_transport_socket()->mutable_typed_config()->PackFrom(starttls_config); }); - BaseIntegrationTest::initialize(); - - Network::Address::InstanceConstSharedPtr address = - Ssl::getSslAddress(version_, lookupPort("tcp_proxy")); - - conn_ = std::make_unique( - *dispatcher_, address, Network::Address::InstanceConstSharedPtr(), - cleartext_context_->createTransportSocket( - std::make_shared( - absl::string_view(""), std::vector(), std::vector())), - nullptr); - - conn_->enableHalfClose(true); - conn_->addConnectionCallbacks(connect_callbacks_); - conn_->addReadFilter(payload_reader_); -} - -// Method adds StartTlsSwitchFilter into the filter chain. -// The filter is required to instruct StartTls transport -// socket to start using Tls. -void StartTlsIntegrationTest::addStartTlsSwitchFilter(ConfigHelper& config_helper) { - config_helper.addNetworkFilter(R"EOF( + // Modifications to ConfigHelper::baseConfig. + // Insert StartTlsSwitchFilter into filter chain. + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + bootstrap.mutable_static_resources()->mutable_listeners(0)->add_filter_chains(); + config_helper_.addNetworkFilter(R"EOF( name: startTls typed_config: "@type": type.googleapis.com/test.integration.starttls.StartTlsFilterConfig )EOF"); - // double-check the filter was actually added - config_helper.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { - ASSERT_EQ("startTls", - bootstrap.static_resources().listeners(0).filter_chains(0).filters(0).name()); }); + + // Setup factory and context for tls transport socket. + // The tls transport socket will be inserted into fake_upstream when + // upstream starttls transport socket is converted to secure mode. + tls_context_manager_ = + std::make_unique(timeSystem()); + + envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext downstream_tls_context; + + std::string yaml_plain = R"EOF( + common_tls_context: + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/config/integration/certs/cacert.pem" + tls_certificates: + certificate_chain: + filename: "{{ test_rundir }}/test/config/integration/certs/clientcert.pem" + private_key: + filename: "{{ test_rundir }}/test/config/integration/certs/clientkey.pem" +)EOF"; + + TestUtility::loadFromYaml(TestEnvironment::substitute(yaml_plain), downstream_tls_context); + + NiceMock mock_factory_ctx; + ON_CALL(mock_factory_ctx, api()).WillByDefault(testing::ReturnRef(*api_)); + auto cfg = std::make_unique( + downstream_tls_context, mock_factory_ctx); + static auto* client_stats_store = new Stats::TestIsolatedStoreImpl(); + tls_context_ = Network::TransportSocketFactoryPtr{ + new Extensions::TransportSockets::Tls::ServerSslSocketFactory( + std::move(cfg), *tls_context_manager_, *client_stats_store, {})}; + + BaseIntegrationTest::initialize(); } -// Test creates a clear-text connection from a client to Envoy and sends several messages. -// Then a special message is sent, which causes StartTlsSwitchFilter to -// instruct StartTls transport socket to start using tls for both the upstream and -// downstream. Connections. The Client connection starts using tls, performs tls handshake -// and a message is sent over tls. start-tls transport socket de-crypts the messages and -// forwards them upstream over yet another start-tls transport. +// Test creates a client connection to Envoy and passes some date to fake_upstream. +// After that fake_upstream is converted to use TLS transport socket. +// The client sends a special message to Envoy which causes upstream starttls +// transport socket to start using secure mode. +// The client sends a message to fake_upstream to test encrypted connection between +// Envoy and fake_upstream. +// Finally fake_upstream sends a message through encrypted connection to Envoy +// which is resent to the client in clear-text. TEST_P(StartTlsIntegrationTest, SwitchToTlsFromClient) { initialize(); // Open clear-text connection. - conn_->connect(); + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("starttls_test")); FakeRawConnectionPtr fake_upstream_connection; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); - ASSERT_THAT(test_server_->server().listenerManager().numConnections(), 1); - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - - // Send a message to switch to tls on the receiver side. - // StartTlsSwitchFilter will switch transport socket on the - // upstream side upon receiving "switch" message and forward - // back the message "switch" to this client. - payload_reader_->set_data_to_wait_for("switch"); - Buffer::OwnedImpl buffer; - buffer.add("usetls"); - conn_->write(buffer, false); - while (client_write_buffer_->bytesDrained() != 6) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - - ASSERT_TRUE(payload_reader_->waitForLength(6, std::chrono::milliseconds(100000))); + // Send a message to fake_upstream via Envoy. + ASSERT_TRUE(tcp_client->write("hello")); + // Make sure the data makes it upstream. + ASSERT_TRUE(fake_upstream_connection->waitForData(5)); - // Make sure we received the 'switch' command from the upstream. - ASSERT_EQ("switch", payload_reader_->data()); - payload_reader_->clearData(); - - // Without closing the connection, switch to tls. - conn_->setTransportSocket( + // Create TLS transport socket and install it in fake_upstream. + Network::TransportSocketPtr ts = tls_context_->createTransportSocket(std::make_shared( - absl::string_view(""), std::vector(), std::vector()))); - connect_callbacks_.reset(); + absl::string_view(""), std::vector(), + std::vector{"envoyalpn"})); + + // Synchronization object used to suspend execution + // until dispatcher completes transport socket conversion. + absl::Notification notification; + + // Execute transport socket conversion to TLS on the same thread where received data + // is dispatched. Otherwise conversion may collide with data processing. + fake_upstreams_[0]->dispatcher()->post([&]() { + auto connection = + dynamic_cast(&fake_upstream_connection->connection()); + connection->transportSocket() = std::move(ts); + connection->transportSocket()->setTransportSocketCallbacks(*connection); + notification.Notify(); + }); - while (!connect_callbacks_.connected() && !connect_callbacks_.closed()) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } + // Wait until the transport socket conversion completes. + notification.WaitForNotification(); - // // Send few messages over the encrypted connection. - buffer.add("hola"); - conn_->write(buffer, false); - while (client_write_buffer_->bytesDrained() != 10) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } + // Send message which will trigger upstream starttls to use secure mode. + ASSERT_TRUE(tcp_client->write("switch")); + // Make sure the data makes it upstream. + ASSERT_TRUE(fake_upstream_connection->waitForData(11)); - // Make sure we get our echo back - ASSERT_TRUE(payload_reader_->waitForLength(4, std::chrono::milliseconds(100000))); - ASSERT_EQ("hola", payload_reader_->data()); - payload_reader_->clearData(); + // Send a message from upstream down through Envoy to tcp_client. + ASSERT_TRUE(fake_upstream_connection->write("upstream")); + tcp_client->waitForData("upstream"); - conn_->close(Network::ConnectionCloseType::FlushWrite); - server_connection_->close(Network::ConnectionCloseType::FlushWrite); + // Cleanup. + tcp_client->close(); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + test_server_.reset(); } INSTANTIATE_TEST_SUITE_P(StartTlsIntegrationTestSuite, StartTlsIntegrationTest, diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index a2d170eb110e2..d10c7ce4d8f51 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -663,6 +663,8 @@ class FakeUpstream : Logger::Loggable, const envoy::config::core::v3::Http2ProtocolOptions& http2Options() { return http2_options_; } const envoy::config::core::v3::Http3ProtocolOptions& http3Options() { return http3_options_; } + Event::DispatcherPtr& dispatcher() { return dispatcher_; } + protected: Stats::IsolatedStoreImpl stats_store_; const FakeHttpConnection::Type http_type_; From 6be425fe0f1c440e9135052d1d744a5b8466c480 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Sun, 16 May 2021 13:39:25 -0500 Subject: [PATCH 207/209] deps: update protobuf to 3.16.0 (#16390) Signed-off-by: Benjamin Peterson --- bazel/protobuf.patch | 2 +- bazel/repository_locations.bzl | 12 ++--- source/common/protobuf/utility.cc | 2 +- source/common/protobuf/utility.h | 2 +- .../json_transcoder_filter.cc | 35 ++++++------- test/common/protobuf/utility_test.cc | 8 +-- .../grpc_json_transcoder_integration_test.cc | 52 +++++++++---------- .../json_transcoder_filter_test.cc | 10 ++-- 8 files changed, 59 insertions(+), 64 deletions(-) diff --git a/bazel/protobuf.patch b/bazel/protobuf.patch index fd6325838ba4a..e786c7ebe1469 100644 --- a/bazel/protobuf.patch +++ b/bazel/protobuf.patch @@ -28,7 +28,7 @@ index 97ac28028..8b7585d9d 100644 @@ -31,3 +31,9 @@ # Copyright 2007 Google Inc. All Rights Reserved. - __version__ = '3.14.0' + __version__ = '3.16.0' + +if __name__ != '__main__': + try: diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 1e981bb96c883..bd88f5b04890d 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -598,25 +598,25 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Protocol Buffers", project_desc = "Language-neutral, platform-neutral extensible mechanism for serializing structured data", project_url = "https://developers.google.com/protocol-buffers", - version = "3.14.0", - sha256 = "6dd0f6b20094910fbb7f1f7908688df01af2d4f6c5c21331b9f636048674aebf", + version = "3.16.0", + sha256 = "d7371dc2d46fddac1af8cb27c0394554b068768fc79ecaf5be1a1863e8ff3392", strip_prefix = "protobuf-{version}", urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v{version}/protobuf-all-{version}.tar.gz"], use_category = ["dataplane_core", "controlplane"], - release_date = "2020-11-13", + release_date = "2021-05-06", cpe = "cpe:2.3:a:google:protobuf:*", ), grpc_httpjson_transcoding = dict( project_name = "grpc-httpjson-transcoding", project_desc = "Library that supports transcoding so that HTTP/JSON can be converted to gRPC", project_url = "https://github.com/grpc-ecosystem/grpc-httpjson-transcoding", - version = "909368951e9a447098f87e41bc49e37810af99f5", - sha256 = "4be3d92add3e3fad56d09a68b6f3f18367b086a3442275e046e84b3c2bb94d43", + version = "f1591a41318104b7e27a26be12f502b106a16256", + sha256 = "440baf465096ce1a7152c6d1090a70e871e5ca93b23c6cf9f8cd79f028bf5bb8", strip_prefix = "grpc-httpjson-transcoding-{version}", urls = ["https://github.com/grpc-ecosystem/grpc-httpjson-transcoding/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.filters.http.grpc_json_transcoder"], - release_date = "2021-02-24", + release_date = "2021-05-08", cpe = "N/A", ), io_bazel_rules_go = dict( diff --git a/source/common/protobuf/utility.cc b/source/common/protobuf/utility.cc index a194d72e2f146..1dfed5a161719 100644 --- a/source/common/protobuf/utility.cc +++ b/source/common/protobuf/utility.cc @@ -741,7 +741,7 @@ ProtobufWkt::Struct MessageUtil::keyValueStruct(const std::map; @@ -259,7 +259,7 @@ Status JsonTranscoderConfig::resolveField(const Protobuf::Descriptor* descriptor const ProtobufWkt::Type* message_type = type_helper_->Info()->GetTypeByTypeUrl(Grpc::Common::typeUrl(descriptor->full_name())); if (message_type == nullptr) { - return ProtobufUtil::Status(Code::NOT_FOUND, + return ProtobufUtil::Status(StatusCode::kNotFound, "Could not resolve type: " + descriptor->full_name()); } @@ -277,7 +277,7 @@ Status JsonTranscoderConfig::resolveField(const Protobuf::Descriptor* descriptor *is_http_body = body_type != nullptr && body_type->name() == google::api::HttpBody::descriptor()->full_name(); } - return Status::OK; + return Status(); } Status JsonTranscoderConfig::createMethodInfo(const Protobuf::MethodDescriptor* descriptor, @@ -302,12 +302,12 @@ Status JsonTranscoderConfig::createMethodInfo(const Protobuf::MethodDescriptor* if (!method_info->response_body_field_path.empty() && !method_info->response_type_is_http_body_) { // TODO(euroelessar): Implement https://github.com/envoyproxy/envoy/issues/11136. - return Status(Code::UNIMPLEMENTED, + return Status(StatusCode::kUnimplemented, "Setting \"response_body\" is not supported yet for non-HttpBody fields: " + descriptor->full_name()); } - return Status::OK; + return Status(); } bool JsonTranscoderConfig::matchIncomingRequestInfo() const { @@ -337,7 +337,8 @@ ProtobufUtil::Status JsonTranscoderConfig::createTranscoder( method_info = path_matcher_->Lookup(method, path, args, &variable_bindings, &request_info.body_field_path); if (!method_info) { - return ProtobufUtil::Status(Code::NOT_FOUND, "Could not resolve " + path + " to a method."); + return ProtobufUtil::Status(StatusCode::kNotFound, + "Could not resolve " + path + " to a method."); } auto status = methodToRequestInfo(method_info, &request_info); @@ -400,7 +401,7 @@ JsonTranscoderConfig::methodToRequestInfo(const MethodInfoSharedPtr& method_info info->message_type = type_helper_->Info()->GetTypeByTypeUrl(request_type_url); if (info->message_type == nullptr) { ENVOY_LOG(debug, "Cannot resolve input-type: {}", request_type_full_name); - return ProtobufUtil::Status(Code::NOT_FOUND, + return ProtobufUtil::Status(StatusCode::kNotFound, "Could not resolve type: " + request_type_full_name); } @@ -446,16 +447,16 @@ Http::FilterHeadersStatus JsonTranscoderFilter::decodeHeaders(Http::RequestHeade const auto status = per_route_config_->createTranscoder(headers, request_in_, response_in_, transcoder_, method_); if (!status.ok()) { - ENVOY_LOG(debug, "Failed to transcode request headers: {}", status.error_message()); + ENVOY_LOG(debug, "Failed to transcode request headers: {}", status.message()); - if (status.code() == Code::NOT_FOUND && + if (status.code() == StatusCode::kNotFound && !config_.request_validation_options_.reject_unknown_method()) { ENVOY_LOG(debug, "Request is passed through without transcoding because it cannot be mapped " "to a gRPC method."); return Http::FilterHeadersStatus::Continue; } - if (status.code() == Code::INVALID_ARGUMENT && + if (status.code() == StatusCode::kInvalidArgument && !config_.request_validation_options_.reject_unknown_query_parameters()) { ENVOY_LOG(debug, "Request is passed through without transcoding because it contains unknown " "query parameters."); @@ -469,11 +470,10 @@ Http::FilterHeadersStatus JsonTranscoderFilter::decodeHeaders(Http::RequestHeade ENVOY_LOG(debug, "Request is rejected due to strict rejection policy."); error_ = true; - decoder_callbacks_->sendLocalReply(static_cast(http_code), - status.error_message().ToString(), nullptr, absl::nullopt, - absl::StrCat(RcDetails::get().GrpcTranscodeFailedEarly, "{", - MessageUtil::CodeEnumToString(status.code()), - "}")); + decoder_callbacks_->sendLocalReply( + static_cast(http_code), status.message().ToString(), nullptr, absl::nullopt, + absl::StrCat(RcDetails::get().GrpcTranscodeFailedEarly, "{", + MessageUtil::codeEnumToString(status.code()), "}")); return Http::FilterHeadersStatus::StopIteration; } @@ -751,10 +751,9 @@ bool JsonTranscoderFilter::checkIfTranscoderFailed(const std::string& details) { error_ = true; decoder_callbacks_->sendLocalReply( Http::Code::BadRequest, - absl::string_view(request_status.error_message().data(), - request_status.error_message().size()), + absl::string_view(request_status.message().data(), request_status.message().size()), nullptr, absl::nullopt, - absl::StrCat(details, "{", MessageUtil::CodeEnumToString(request_status.code()), "}")); + absl::StrCat(details, "{", MessageUtil::codeEnumToString(request_status.code()), "}")); return true; } diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index 52071d16a7bf9..97b25a67bcade 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -2000,13 +2000,13 @@ INSTANTIATE_TEST_SUITE_P(TimestampUtilTestAcrossRange, TimestampUtilTest, )); TEST(StatusCode, Strings) { - int last_code = static_cast(ProtobufUtil::error::UNAUTHENTICATED); + int last_code = static_cast(ProtobufUtil::StatusCode::kUnauthenticated); for (int i = 0; i < last_code; ++i) { - EXPECT_NE(MessageUtil::CodeEnumToString(static_cast(i)), ""); + EXPECT_NE(MessageUtil::codeEnumToString(static_cast(i)), ""); } ASSERT_EQ("UNKNOWN", - MessageUtil::CodeEnumToString(static_cast(last_code + 1))); - ASSERT_EQ("OK", MessageUtil::CodeEnumToString(ProtobufUtil::error::OK)); + MessageUtil::codeEnumToString(static_cast(last_code + 1))); + ASSERT_EQ("OK", MessageUtil::codeEnumToString(ProtobufUtil::StatusCode::kOk)); } TEST(TypeUtilTest, TypeUrlHelperFunction) { diff --git a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc index 851129b539c72..15e669eb33671 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc +++ b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc @@ -17,7 +17,7 @@ using Envoy::Protobuf::TextFormat; using Envoy::ProtobufUtil::Status; -using Envoy::ProtobufUtil::error::Code; +using Envoy::ProtobufUtil::StatusCode; using Envoy::ProtobufWkt::Empty; namespace Envoy { @@ -112,9 +112,8 @@ class GrpcJsonTranscoderIntegrationTest response_headers.setStatus(200); response_headers.setContentType("application/grpc"); if (grpc_response_messages.empty() && !always_send_trailers) { - response_headers.setGrpcStatus(static_cast(grpc_status.error_code())); - response_headers.setGrpcMessage(absl::string_view(grpc_status.error_message().data(), - grpc_status.error_message().size())); + response_headers.setGrpcStatus(static_cast(grpc_status.code())); + response_headers.setGrpcMessage(grpc_status.message().as_string()); upstream_request_->encodeHeaders(response_headers, true); } else { response_headers.addCopy(Http::LowerCaseString("trailer"), "Grpc-Status"); @@ -127,9 +126,8 @@ class GrpcJsonTranscoderIntegrationTest upstream_request_->encodeData(*buffer, false); } Http::TestResponseTrailerMapImpl response_trailers; - response_trailers.setGrpcStatus(static_cast(grpc_status.error_code())); - response_trailers.setGrpcMessage(absl::string_view(grpc_status.error_message().data(), - grpc_status.error_message().size())); + response_trailers.setGrpcStatus(static_cast(grpc_status.code())); + response_trailers.setGrpcMessage(grpc_status.message().as_string()); upstream_request_->encodeTrailers(response_trailers); } EXPECT_TRUE(upstream_request_->complete()); @@ -382,7 +380,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, StreamGetHttpBody) { testTranscoding( Http::TestRequestHeaderMapImpl{ {":method", "GET"}, {":path", "/indexStream"}, {":authority", "host"}}, - "", {""}, {}, Status(Code::NOT_FOUND, "Not Found"), + "", {""}, {}, Status(StatusCode::kNotFound, "Not Found"), Http::TestResponseHeaderMapImpl{{":status", "404"}, {"content-type", "application/json"}}, ""); } @@ -421,9 +419,8 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, StreamGetHttpBodyMultipleFramesInData) // Trailers Http::TestResponseTrailerMapImpl response_trailers; auto grpc_status = Status(); - response_trailers.setGrpcStatus(static_cast(grpc_status.error_code())); - response_trailers.setGrpcMessage( - absl::string_view(grpc_status.error_message().data(), grpc_status.error_message().size())); + response_trailers.setGrpcStatus(static_cast(grpc_status.code())); + response_trailers.setGrpcMessage(grpc_status.message().as_string()); upstream_request_->encodeTrailers(response_trailers); EXPECT_TRUE(upstream_request_->complete()); @@ -464,9 +461,8 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, StreamGetHttpBodyFragmented) { // Trailers Http::TestResponseTrailerMapImpl response_trailers; auto grpc_status = Status(); - response_trailers.setGrpcStatus(static_cast(grpc_status.error_code())); - response_trailers.setGrpcMessage( - absl::string_view(grpc_status.error_message().data(), grpc_status.error_message().size())); + response_trailers.setGrpcStatus(static_cast(grpc_status.code())); + response_trailers.setGrpcMessage(grpc_status.message().as_string()); upstream_request_->encodeTrailers(response_trailers); EXPECT_TRUE(upstream_request_->complete()); @@ -501,7 +497,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, UnaryGetError) { testTranscoding( Http::TestRequestHeaderMapImpl{ {":method", "GET"}, {":path", "/shelves/100?"}, {":authority", "host"}}, - "", {"shelf: 100"}, {}, Status(Code::NOT_FOUND, "Shelf 100 Not Found"), + "", {"shelf: 100"}, {}, Status(StatusCode::kNotFound, "Shelf 100 Not Found"), Http::TestResponseHeaderMapImpl{ {":status", "404"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 100 Not Found"}}, ""); @@ -524,7 +520,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, UnaryGetError1) { Http::TestRequestHeaderMapImpl{{":method", "GET"}, {":path", "/shelves/100?unknown=1&shelf=9999"}, {":authority", "host"}}, - "", {"shelf: 9999"}, {}, Status(Code::NOT_FOUND, "Shelf 9999 Not Found"), + "", {"shelf: 9999"}, {}, Status(StatusCode::kNotFound, "Shelf 9999 Not Found"), Http::TestResponseHeaderMapImpl{ {":status", "404"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}}, ""); @@ -547,7 +543,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, UnaryErrorConvertedToJson) { testTranscoding( Http::TestRequestHeaderMapImpl{ {":method", "GET"}, {":path", "/shelves/100"}, {":authority", "host"}}, - "", {"shelf: 100"}, {}, Status(Code::NOT_FOUND, "Shelf 100 Not Found"), + "", {"shelf: 100"}, {}, Status(StatusCode::kNotFound, "Shelf 100 Not Found"), Http::TestResponseHeaderMapImpl{{":status", "404"}, {"content-type", "application/json"}, {"grpc-status", UnexpectedHeaderValue}, @@ -572,7 +568,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, UnaryErrorInTrailerConvertedToJson) { testTranscoding( Http::TestRequestHeaderMapImpl{ {":method", "GET"}, {":path", "/shelves/100"}, {":authority", "host"}}, - "", {"shelf: 100"}, {}, Status(Code::NOT_FOUND, "Shelf 100 Not Found"), + "", {"shelf: 100"}, {}, Status(StatusCode::kNotFound, "Shelf 100 Not Found"), Http::TestResponseHeaderMapImpl{{":status", "404"}, {"content-type", "application/json"}, {"grpc-status", UnexpectedHeaderValue}, @@ -597,7 +593,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, StreamingErrorConvertedToJson) { testTranscoding( Http::TestRequestHeaderMapImpl{ {":method", "GET"}, {":path", "/shelves/37/books"}, {":authority", "host"}}, - "", {"shelf: 37"}, {}, Status(Code::NOT_FOUND, "Shelf 37 Not Found"), + "", {"shelf: 37"}, {}, Status(StatusCode::kNotFound, "Shelf 37 Not Found"), Http::TestResponseHeaderMapImpl{{":status", "404"}, {"content-type", "application/json"}, {"grpc-status", UnexpectedHeaderValue}, @@ -687,7 +683,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, ServerStreamingGet) { testTranscoding( Http::TestRequestHeaderMapImpl{ {":method", "GET"}, {":path", "/shelves/37/books"}, {":authority", "host"}}, - "", {"shelf: 37"}, {}, Status(Code::NOT_FOUND, "Shelf 37 not found"), + "", {"shelf: 37"}, {}, Status(StatusCode::kNotFound, "Shelf 37 not found"), Http::TestResponseHeaderMapImpl{{":status", "404"}, {"content-type", "application/json"}}, "[]"); } @@ -942,7 +938,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidation) { {":path", "/shelves/100"}, {":authority", "host"}, {"content-type", "application/grpc"}}, - R"({ "theme" : "Children")", {}, {}, Status(Code::NOT_FOUND, "Shelf 9999 Not Found"), + R"({ "theme" : "Children")", {}, {}, Status(StatusCode::kNotFound, "Shelf 9999 Not Found"), Http::TestResponseHeaderMapImpl{ {":status", "200"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}}, "", true, false, R"({ "theme" : "Children")"); @@ -955,7 +951,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidation) { {":path", "/unknown/path"}, {":authority", "host"}, {"content-type", "application/json"}}, - R"({ "theme" : "Children")", {}, {}, Status(Code::NOT_FOUND, "Shelf 9999 Not Found"), + R"({ "theme" : "Children")", {}, {}, Status(StatusCode::kNotFound, "Shelf 9999 Not Found"), Http::TestResponseHeaderMapImpl{ {":status", "200"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}}, "", true, false, R"({ "theme" : "Children")"); @@ -968,7 +964,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidation) { {":path", "/shelves/100?unknown=1"}, {":authority", "host"}, {"content-type", "application/json"}}, - R"({ "theme" : "Children")", {}, {}, Status(Code::NOT_FOUND, "Shelf 9999 Not Found"), + R"({ "theme" : "Children")", {}, {}, Status(StatusCode::kNotFound, "Shelf 9999 Not Found"), Http::TestResponseHeaderMapImpl{ {":status", "200"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}}, "", true, false, R"({ "theme" : "Children")"); @@ -998,7 +994,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, RejectUnknownMethod) { {":path", "/unknown/path"}, {":authority", "host"}, {"content-type", "application/grpc"}}, - R"({ "theme" : "Children")", {}, {}, Status(Code::NOT_FOUND, "Shelf 9999 Not Found"), + R"({ "theme" : "Children")", {}, {}, Status(StatusCode::kNotFound, "Shelf 9999 Not Found"), Http::TestResponseHeaderMapImpl{ {":status", "200"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}}, "", true, false, R"({ "theme" : "Children")"); @@ -1021,7 +1017,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, RejectUnknownMethod) { {":path", "/shelves/100?unknown=1"}, {":authority", "host"}, {"content-type", "application/json"}}, - R"({ "theme" : "Children")", {}, {}, Status(Code::NOT_FOUND, "Shelf 9999 Not Found"), + R"({ "theme" : "Children")", {}, {}, Status(StatusCode::kNotFound, "Shelf 9999 Not Found"), Http::TestResponseHeaderMapImpl{ {":status", "200"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}}, "", true, false, R"({ "theme" : "Children")"); @@ -1051,7 +1047,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, RejectUnknownQueryParam) { {":path", "/shelves/100?unknown=1"}, {":authority", "host"}, {"content-type", "application/grpc"}}, - R"({ "theme" : "Children")", {}, {}, Status(Code::NOT_FOUND, "Shelf 9999 Not Found"), + R"({ "theme" : "Children")", {}, {}, Status(StatusCode::kNotFound, "Shelf 9999 Not Found"), Http::TestResponseHeaderMapImpl{ {":status", "200"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}}, "", true, false, R"({ "theme" : "Children")"); @@ -1064,7 +1060,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, RejectUnknownQueryParam) { {":path", "/unknown/path"}, {":authority", "host"}, {"content-type", "application/json"}}, - R"({ "theme" : "Children")", {}, {}, Status(Code::NOT_FOUND, "Shelf 9999 Not Found"), + R"({ "theme" : "Children")", {}, {}, Status(StatusCode::kNotFound, "Shelf 9999 Not Found"), Http::TestResponseHeaderMapImpl{ {":status", "200"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}}, "", true, false, R"({ "theme" : "Children")"); @@ -1103,7 +1099,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, EnableRequestValidationIgnoreQueryPara testTranscoding( Http::TestRequestHeaderMapImpl{ {":method", "GET"}, {":path", "/shelves/9999?unknown=1"}, {":authority", "host"}}, - "", {"shelf: 9999"}, {}, Status(Code::NOT_FOUND, "Shelf 9999 Not Found"), + "", {"shelf: 9999"}, {}, Status(StatusCode::kNotFound, "Shelf 9999 Not Found"), Http::TestResponseHeaderMapImpl{ {":status", "404"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}}, ""); diff --git a/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc b/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc index e5acc3214fe67..1de0a48874d59 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc +++ b/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc @@ -30,7 +30,7 @@ using testing::Return; using Envoy::Protobuf::FileDescriptorProto; using Envoy::Protobuf::FileDescriptorSet; using Envoy::Protobuf::util::MessageDifferencer; -using Envoy::ProtobufUtil::error::Code; +using Envoy::ProtobufUtil::StatusCode; using google::api::HttpRule; using google::grpc::transcoding::Transcoder; using TranscoderPtr = std::unique_ptr; @@ -270,9 +270,9 @@ TEST_F(GrpcJsonTranscoderConfigTest, InvalidQueryParameter) { const auto status = config.createTranscoder(headers, request_in, response_in, transcoder, method_info); - EXPECT_EQ(Code::INVALID_ARGUMENT, status.error_code()); + EXPECT_EQ(StatusCode::kInvalidArgument, status.code()); EXPECT_EQ("Could not find field \"foo\" in the type \"google.protobuf.Empty\".", - status.error_message()); + status.message()); EXPECT_FALSE(transcoder); } @@ -331,9 +331,9 @@ TEST_F(GrpcJsonTranscoderConfigTest, InvalidVariableBinding) { const auto status = config.createTranscoder(headers, request_in, response_in, transcoder, method_info); - EXPECT_EQ(Code::INVALID_ARGUMENT, status.error_code()); + EXPECT_EQ(StatusCode::kInvalidArgument, status.code()); EXPECT_EQ("Could not find field \"b\" in the type \"bookstore.GetBookRequest\".", - status.error_message()); + status.message()); EXPECT_FALSE(transcoder); } From beac1ece7512e6e39b4f1c29490e247996a0f51c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Guti=C3=A9rrez=20Segal=C3=A9s?= Date: Sun, 16 May 2021 17:00:40 -0400 Subject: [PATCH 208/209] HCM: add support for IP detection extensions (#14855) This is a follow-up to: #14432 (comment) After that PR, it's no longer possible (unless you do a dynamic_cast) to set the remote address from a filter. This is something that we need to do because we have specialized logic for this (XFF doesn't work for us). So this adds an extension point which will allow us to push that logic down to ConnectionManagerUtility::mutateRequestHeaders() where it belongs. Signed-off-by: Raul Gutierrez Segales --- CODEOWNERS | 3 + api/BUILD | 2 + .../network/http_connection_manager/v3/BUILD | 1 + .../v3/http_connection_manager.proto | 34 +++++- .../v4alpha/http_connection_manager.proto | 24 ++-- .../custom_header/v3/BUILD | 12 ++ .../custom_header/v3/custom_header.proto | 43 +++++++ .../http/original_ip_detection/xff/v3/BUILD | 9 ++ .../original_ip_detection/xff/v3/xff.proto | 25 ++++ api/versioning/BUILD | 2 + bazel/envoy_library.bzl | 1 + docs/root/api-v3/config/config.rst | 1 + .../config/http/original_ip_detection.rst | 8 ++ .../http/http_conn_man/headers.rst | 11 +- .../http_conn_man/response_code_details.rst | 1 + .../http/http_conn_man/stats.rst | 1 + .../other_features/ip_transparency.rst | 8 ++ docs/root/version_history/current.rst | 5 + generated_api_shadow/BUILD | 2 + .../v3/http_connection_manager.proto | 33 +++++- .../http_connection_manager/v4alpha/BUILD | 1 + .../v4alpha/http_connection_manager.proto | 34 +++++- .../custom_header/v3/BUILD | 12 ++ .../custom_header/v3/custom_header.proto | 43 +++++++ .../http/original_ip_detection/xff/v3/BUILD | 9 ++ .../original_ip_detection/xff/v3/xff.proto | 25 ++++ include/envoy/http/BUILD | 12 ++ include/envoy/http/original_ip_detection.h | 90 +++++++++++++++ include/envoy/stream_info/stream_info.h | 4 +- source/common/http/BUILD | 1 + source/common/http/conn_manager_config.h | 8 ++ source/common/http/conn_manager_impl.cc | 17 ++- source/common/http/conn_manager_utility.cc | 87 ++++++++------ source/common/http/conn_manager_utility.h | 26 ++++- source/common/http/utility.h | 4 +- source/extensions/all_extensions.bzl | 1 + source/extensions/extensions_build_config.bzl | 8 ++ .../network/http_connection_manager/BUILD | 3 + .../network/http_connection_manager/config.cc | 31 ++++- .../network/http_connection_manager/config.h | 6 + .../original_ip_detection/custom_header/BUILD | 40 +++++++ .../custom_header/config.cc | 35 ++++++ .../custom_header/config.h | 36 ++++++ .../custom_header/custom_header.cc | 47 ++++++++ .../custom_header/custom_header.h | 47 ++++++++ .../http/original_ip_detection/xff/BUILD | 40 +++++++ .../http/original_ip_detection/xff/config.cc | 35 ++++++ .../http/original_ip_detection/xff/config.h | 35 ++++++ .../http/original_ip_detection/xff/xff.cc | 29 +++++ .../http/original_ip_detection/xff/xff.h | 31 +++++ source/server/admin/admin.h | 5 + test/common/http/BUILD | 12 ++ .../http/conn_manager_impl_fuzz_test.cc | 5 + test/common/http/conn_manager_impl_test_2.cc | 24 ++++ .../http/conn_manager_impl_test_base.cc | 4 + .../common/http/conn_manager_impl_test_base.h | 6 + test/common/http/conn_manager_utility_test.cc | 51 ++++++++- test/common/http/ip_detection_extensions.cc | 25 ++++ test/common/http/ip_detection_extensions.h | 14 +++ test/common/http/utility_test.cc | 30 ++--- .../network/http_connection_manager/BUILD | 1 + .../http_connection_manager/config_test.cc | 83 ++++++++++++++ .../original_ip_detection/custom_header/BUILD | 38 +++++++ .../custom_header/config_test.cc | 59 ++++++++++ .../custom_header/custom_header_test.cc | 107 ++++++++++++++++++ .../http/original_ip_detection/xff/BUILD | 38 +++++++ .../original_ip_detection/xff/config_test.cc | 39 +++++++ .../original_ip_detection/xff/xff_test.cc | 52 +++++++++ test/integration/BUILD | 14 +++ test/integration/header_integration_test.cc | 6 +- .../original_ip_detection_integration_test.cc | 55 +++++++++ test/mocks/http/mocks.h | 2 + 72 files changed, 1611 insertions(+), 82 deletions(-) create mode 100644 api/envoy/extensions/http/original_ip_detection/custom_header/v3/BUILD create mode 100644 api/envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.proto create mode 100644 api/envoy/extensions/http/original_ip_detection/xff/v3/BUILD create mode 100644 api/envoy/extensions/http/original_ip_detection/xff/v3/xff.proto create mode 100644 docs/root/api-v3/config/http/original_ip_detection.rst create mode 100644 generated_api_shadow/envoy/extensions/http/original_ip_detection/custom_header/v3/BUILD create mode 100644 generated_api_shadow/envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.proto create mode 100644 generated_api_shadow/envoy/extensions/http/original_ip_detection/xff/v3/BUILD create mode 100644 generated_api_shadow/envoy/extensions/http/original_ip_detection/xff/v3/xff.proto create mode 100644 include/envoy/http/original_ip_detection.h create mode 100644 source/extensions/http/original_ip_detection/custom_header/BUILD create mode 100644 source/extensions/http/original_ip_detection/custom_header/config.cc create mode 100644 source/extensions/http/original_ip_detection/custom_header/config.h create mode 100644 source/extensions/http/original_ip_detection/custom_header/custom_header.cc create mode 100644 source/extensions/http/original_ip_detection/custom_header/custom_header.h create mode 100644 source/extensions/http/original_ip_detection/xff/BUILD create mode 100644 source/extensions/http/original_ip_detection/xff/config.cc create mode 100644 source/extensions/http/original_ip_detection/xff/config.h create mode 100644 source/extensions/http/original_ip_detection/xff/xff.cc create mode 100644 source/extensions/http/original_ip_detection/xff/xff.h create mode 100644 test/common/http/ip_detection_extensions.cc create mode 100644 test/common/http/ip_detection_extensions.h create mode 100644 test/extensions/http/original_ip_detection/custom_header/BUILD create mode 100644 test/extensions/http/original_ip_detection/custom_header/config_test.cc create mode 100644 test/extensions/http/original_ip_detection/custom_header/custom_header_test.cc create mode 100644 test/extensions/http/original_ip_detection/xff/BUILD create mode 100644 test/extensions/http/original_ip_detection/xff/config_test.cc create mode 100644 test/extensions/http/original_ip_detection/xff/xff_test.cc create mode 100644 test/integration/original_ip_detection_integration_test.cc diff --git a/CODEOWNERS b/CODEOWNERS index d199614f25348..90b5d3a35482c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -183,3 +183,6 @@ extensions/filters/http/oauth2 @rgs1 @derekargueta @snowp /*/extensions/filters/common/ext_authz @esmet @gsagula @dio /*/extensions/filters/http/ext_authz @esmet @gsagula @dio /*/extensions/filters/network/ext_authz @esmet @gsagula @dio +# Original IP detection +/*/extensions/http/original_ip_detection/custom_header @rgs1 @alyssawilk @antoniovicente +/*/extensions/http/original_ip_detection/xff @rgs1 @alyssawilk @antoniovicente diff --git a/api/BUILD b/api/BUILD index 7a6671dd681f9..04b94ff211fd4 100644 --- a/api/BUILD +++ b/api/BUILD @@ -245,6 +245,8 @@ proto_library( "//envoy/extensions/filters/udp/udp_proxy/v3:pkg", "//envoy/extensions/health_checkers/redis/v3:pkg", "//envoy/extensions/http/header_formatters/preserve_case/v3:pkg", + "//envoy/extensions/http/original_ip_detection/custom_header/v3:pkg", + "//envoy/extensions/http/original_ip_detection/xff/v3:pkg", "//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg", "//envoy/extensions/internal_redirect/previous_routes/v3:pkg", "//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg", diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/BUILD b/api/envoy/extensions/filters/network/http_connection_manager/v3/BUILD index 55b63248136ce..456f4e9e61702 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/BUILD +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/BUILD @@ -6,6 +6,7 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/annotations:pkg", "//envoy/config/accesslog/v3:pkg", "//envoy/config/core/v3:pkg", "//envoy/config/filter/network/http_connection_manager/v2:pkg", diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index e46032daa4269..90f4b276dc3cd 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -19,6 +19,7 @@ import "google/protobuf/any.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; +import "envoy/annotations/deprecation.proto"; import "udpa/annotations/migrate.proto"; import "udpa/annotations/security.proto"; import "udpa/annotations/status.proto"; @@ -34,7 +35,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 46] +// [#next-free-field: 47] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -495,7 +496,36 @@ message HttpConnectionManager { // determining the origin client's IP address. The default is zero if this option // is not specified. See the documentation for // :ref:`config_http_conn_man_headers_x-forwarded-for` for more information. - uint32 xff_num_trusted_hops = 19; + // + // .. note:: + // This field is deprecated and instead :ref:`original_ip_detection_extensions + // ` + // should be used to configure the :ref:`xff extension ` + // to configure IP detection using the :ref:`config_http_conn_man_headers_x-forwarded-for` header. To replace + // this field use a config like the following: + // + // .. code-block:: yaml + // + // original_ip_detection_extensions: + // typed_config: + // "@type": type.googleapis.com/envoy.extensions.http.original_ip_detection.xff.v3.XffConfig + // xff_num_trusted_hops: 1 + // + uint32 xff_num_trusted_hops = 19 + [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; + + // The configuration for the original IP detection extensions. + // + // When configured the extensions will be called along with the request headers + // and information about the downstream connection, such as the directly connected address. + // Each extension will then use these parameters to decide the request's effective remote address. + // If an extension fails to detect the original IP address and isn't configured to reject + // the request, the HCM will try the remaining extensions until one succeeds or rejects + // the request. If the request isn't rejected nor any extension succeeds, the HCM will + // fallback to using the remote address. + // + // [#extension-category: envoy.http.original_ip_detection] + repeated config.core.v3.TypedExtensionConfig original_ip_detection_extensions = 46; // Configures what network addresses are considered internal for stats and header sanitation // purposes. If unspecified, only RFC1918 IP addresses will be considered internal. diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index 59d0d5c74984a..4f61ffb8ccca3 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -33,7 +33,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 46] +// [#next-free-field: 47] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"; @@ -304,9 +304,9 @@ message HttpConnectionManager { type.http.v3.PathTransformation http_filter_transformation = 2; } - reserved 27, 11; + reserved 27, 11, 19; - reserved "idle_timeout"; + reserved "idle_timeout", "xff_num_trusted_hops"; // Supplies the type of codec that the connection manager should use. CodecType codec_type = 1 [(validate.rules).enum = {defined_only: true}]; @@ -493,12 +493,18 @@ message HttpConnectionManager { google.protobuf.BoolValue use_remote_address = 14 [(udpa.annotations.security).configure_for_untrusted_downstream = true]; - // The number of additional ingress proxy hops from the right side of the - // :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header to trust when - // determining the origin client's IP address. The default is zero if this option - // is not specified. See the documentation for - // :ref:`config_http_conn_man_headers_x-forwarded-for` for more information. - uint32 xff_num_trusted_hops = 19; + // The configuration for the original IP detection extensions. + // + // When configured the extensions will be called along with the request headers + // and information about the downstream connection, such as the directly connected address. + // Each extension will then use these parameters to decide the request's effective remote address. + // If an extension fails to detect the original IP address and isn't configured to reject + // the request, the HCM will try the remaining extensions until one succeeds or rejects + // the request. If the request isn't rejected nor any extension succeeds, the HCM will + // fallback to using the remote address. + // + // [#extension-category: envoy.http.original_ip_detection] + repeated config.core.v4alpha.TypedExtensionConfig original_ip_detection_extensions = 46; // Configures what network addresses are considered internal for stats and header sanitation // purposes. If unspecified, only RFC1918 IP addresses will be considered internal. diff --git a/api/envoy/extensions/http/original_ip_detection/custom_header/v3/BUILD b/api/envoy/extensions/http/original_ip_detection/custom_header/v3/BUILD new file mode 100644 index 0000000000000..9a76b7e148e03 --- /dev/null +++ b/api/envoy/extensions/http/original_ip_detection/custom_header/v3/BUILD @@ -0,0 +1,12 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/type/v3:pkg", + "@com_github_cncf_udpa//udpa/annotations:pkg", + ], +) diff --git a/api/envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.proto b/api/envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.proto new file mode 100644 index 0000000000000..5ea93d7548438 --- /dev/null +++ b/api/envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +package envoy.extensions.http.original_ip_detection.custom_header.v3; + +import "envoy/type/v3/http_status.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.http.original_ip_detection.custom_header.v3"; +option java_outer_classname = "CustomHeaderProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Custom header original IP detection extension] + +// This extension allows for the original downstream remote IP to be detected +// by reading the value from a configured header name. If the value is successfully parsed +// as an IP, it'll be treated as the effective downstream remote address and seen as such +// by all filters. See :ref:`original_ip_detection_extensions +// ` +// for an overview of how extensions operate and what happens when an extension fails +// to detect the remote IP. +// +// [#extension: envoy.http.original_ip_detection.custom_header] +message CustomHeaderConfig { + // The header name containing the original downstream remote address, if present. + // + // Note: in the case of a multi-valued header, only the first value is tried and the rest are ignored. + string header_name = 1 + [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_NAME strict: true}]; + + // If set to true, the extension could decide that the detected address should be treated as + // trusted by the HCM. If the address is considered :ref:`trusted`, + // it might be used as input to determine if the request is internal (among other things). + bool allow_extension_to_set_address_as_trusted = 2; + + // If this is set, the request will be rejected when detection fails using it as the HTTP response status. + // + // .. note:: + // If this is set to < 400 or > 511, the default status 403 will be used instead. + type.v3.HttpStatus reject_with_status = 3; +} diff --git a/api/envoy/extensions/http/original_ip_detection/xff/v3/BUILD b/api/envoy/extensions/http/original_ip_detection/xff/v3/BUILD new file mode 100644 index 0000000000000..ee92fb652582e --- /dev/null +++ b/api/envoy/extensions/http/original_ip_detection/xff/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/envoy/extensions/http/original_ip_detection/xff/v3/xff.proto b/api/envoy/extensions/http/original_ip_detection/xff/v3/xff.proto new file mode 100644 index 0000000000000..6864788f9f185 --- /dev/null +++ b/api/envoy/extensions/http/original_ip_detection/xff/v3/xff.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package envoy.extensions.http.original_ip_detection.xff.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.http.original_ip_detection.xff.v3"; +option java_outer_classname = "XffProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: XFF original IP detection extension] + +// This extension allows for the original downstream remote IP to be detected +// by reading the :ref:`config_http_conn_man_headers_x-forwarded-for` header. +// +// [#extension: envoy.http.original_ip_detection.xff] +message XffConfig { + // The number of additional ingress proxy hops from the right side of the + // :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header to trust when + // determining the origin client's IP address. The default is zero if this option + // is not specified. See the documentation for + // :ref:`config_http_conn_man_headers_x-forwarded-for` for more information. + uint32 xff_num_trusted_hops = 1; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 338a8cdb80f2a..eac32a5cdfd38 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -128,6 +128,8 @@ proto_library( "//envoy/extensions/filters/udp/udp_proxy/v3:pkg", "//envoy/extensions/health_checkers/redis/v3:pkg", "//envoy/extensions/http/header_formatters/preserve_case/v3:pkg", + "//envoy/extensions/http/original_ip_detection/custom_header/v3:pkg", + "//envoy/extensions/http/original_ip_detection/xff/v3:pkg", "//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg", "//envoy/extensions/internal_redirect/previous_routes/v3:pkg", "//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg", diff --git a/bazel/envoy_library.bzl b/bazel/envoy_library.bzl index 7f9b745a504bc..0c4ae3a53a361 100644 --- a/bazel/envoy_library.bzl +++ b/bazel/envoy_library.bzl @@ -84,6 +84,7 @@ EXTENSION_CATEGORIES = [ "envoy.http.stateful_header_formatters", "envoy.internal_redirect_predicates", "envoy.io_socket", + "envoy.http.original_ip_detection", "envoy.matching.common_inputs", "envoy.matching.input_matchers", "envoy.rate_limit_descriptors", diff --git a/docs/root/api-v3/config/config.rst b/docs/root/api-v3/config/config.rst index 84bbc17d48a33..712ed03fa278d 100644 --- a/docs/root/api-v3/config/config.rst +++ b/docs/root/api-v3/config/config.rst @@ -27,3 +27,4 @@ Extensions descriptors/descriptors request_id/request_id http/header_formatters + http/original_ip_detection diff --git a/docs/root/api-v3/config/http/original_ip_detection.rst b/docs/root/api-v3/config/http/original_ip_detection.rst new file mode 100644 index 0000000000000..1d333c2f53c90 --- /dev/null +++ b/docs/root/api-v3/config/http/original_ip_detection.rst @@ -0,0 +1,8 @@ +Original IP Detection +===================== + +.. toctree:: + :glob: + :maxdepth: 2 + + ../../extensions/http/original_ip_detection/*/v3/* diff --git a/docs/root/configuration/http/http_conn_man/headers.rst b/docs/root/configuration/http/http_conn_man/headers.rst index d3a9d2aa96d62..57c34ec2e7f6e 100644 --- a/docs/root/configuration/http/http_conn_man/headers.rst +++ b/docs/root/configuration/http/http_conn_man/headers.rst @@ -189,7 +189,9 @@ Given an HTTP request that has traveled through a series of zero or more proxies Envoy, the trusted client address is the earliest source IP address that is known to be accurate. The source IP address of the immediate downstream node's connection to Envoy is trusted. XFF *sometimes* can be trusted. Malicious clients can forge XFF, but the last -address in XFF can be trusted if it was put there by a trusted proxy. +address in XFF can be trusted if it was put there by a trusted proxy. Alternatively, Envoy +supports :ref:`extensions ` +for determining the *trusted client address* or original IP address. Envoy's default rules for determining the trusted client address (*before* appending anything to XFF) are: @@ -200,8 +202,11 @@ to XFF) are: node's connection to Envoy. In an environment where there are one or more trusted proxies in front of an edge -Envoy instance, the *xff_num_trusted_hops* configuration option can be used to trust -additional addresses from XFF: +Envoy instance, the :ref:`XFF extension ` +can be configured via the :ref:`original_ip_detection_extensions field +` +to set the *xff_num_trusted_hops* option which controls the number of additional +addresses that are to be trusted: * If *use_remote_address* is false and *xff_num_trusted_hops* is set to a value *N* that is greater than zero, the trusted client address is the (N+1)th address from the right end diff --git a/docs/root/configuration/http/http_conn_man/response_code_details.rst b/docs/root/configuration/http/http_conn_man/response_code_details.rst index 24d94164ca78f..def0da6cbba23 100644 --- a/docs/root/configuration/http/http_conn_man/response_code_details.rst +++ b/docs/root/configuration/http/http_conn_man/response_code_details.rst @@ -35,6 +35,7 @@ Below are the list of reasons the HttpConnectionManager or Router filter may sen missing_path_rejected, The request was rejected due to a missing Path or :path header field. no_healthy_upstream, The request was rejected by the router filter because there was no healthy upstream found. overload, The request was rejected due to the Overload Manager reaching configured resource limits. + original_ip_detection_failed, The request was rejected because the original IP couldn't be detected. path_normalization_failed, "The request was rejected because path normalization was configured on and failed, probably due to an invalid path." request_headers_failed_strict_check, The request was rejected due to x-envoy-* headers failing strict header validation. request_overall_timeout, The per-stream total request timeout was exceeded. diff --git a/docs/root/configuration/http/http_conn_man/stats.rst b/docs/root/configuration/http/http_conn_man/stats.rst index b8d81e7ce7a89..7fd66553222f9 100644 --- a/docs/root/configuration/http/http_conn_man/stats.rst +++ b/docs/root/configuration/http/http_conn_man/stats.rst @@ -45,6 +45,7 @@ statistics: downstream_rq_http2_total, Counter, Total HTTP/2 requests downstream_rq_http3_total, Counter, Total HTTP/3 requests downstream_rq_active, Gauge, Total active requests + downstream_rq_rejected_via_ip_detection, Counter, Total requests rejected because the original IP detection failed downstream_rq_response_before_rq_complete, Counter, Total responses sent before the request was complete downstream_rq_rx_reset, Counter, Total request resets received downstream_rq_tx_reset, Counter, Total request resets sent diff --git a/docs/root/intro/arch_overview/other_features/ip_transparency.rst b/docs/root/intro/arch_overview/other_features/ip_transparency.rst index 2bb277205adf2..92b519421c3f1 100644 --- a/docs/root/intro/arch_overview/other_features/ip_transparency.rst +++ b/docs/root/intro/arch_overview/other_features/ip_transparency.rst @@ -20,6 +20,14 @@ called the *downstream remote address*, for many reasons. Some examples include: Envoy supports multiple methods for providing the downstream remote address to the upstream host. These techniques vary in complexity and applicability. +Envoy also supports +:ref:`extensions ` +for detecting the original IP address. This might be useful if none of the techniques below is +applicable to your setup. Two available extensions are the :ref:`custom header +` +extension and the :ref:`xff ` +extension. + HTTP Headers ------------ diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index bf3719e657612..b00d52cbde733 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -61,6 +61,9 @@ Removed Config or Runtime New Features ------------ +* http: added support for :ref:`original IP detection extensions`. + Two initial extensions were added, the :ref:`custom header ` extension and the + :ref:`xff ` extension. * http: added the ability to :ref:`unescape slash sequences` in the path. Requests with unescaped slashes can be proxied, rejected or redirected to the new unescaped path. By default this feature is disabled. The default behavior can be overridden through :ref:`http_connection_manager.path_with_escaped_slashes_action` runtime variable. This action can be selectively enabled for a portion of requests by setting the :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime variable. * http: added upstream and downstream alpha HTTP/3 support! See :ref:`quic_options ` for downstream and the new http3_protocol_options in :ref:`http_protocol_options ` for upstream HTTP/3. * listener: added ability to change an existing listener's address. @@ -69,3 +72,5 @@ New Features Deprecated ---------- + +* http: :ref:`xff_num_trusted_hops ` is deprecated in favor of :ref:`original IP detection extensions`. diff --git a/generated_api_shadow/BUILD b/generated_api_shadow/BUILD index 7a6671dd681f9..04b94ff211fd4 100644 --- a/generated_api_shadow/BUILD +++ b/generated_api_shadow/BUILD @@ -245,6 +245,8 @@ proto_library( "//envoy/extensions/filters/udp/udp_proxy/v3:pkg", "//envoy/extensions/health_checkers/redis/v3:pkg", "//envoy/extensions/http/header_formatters/preserve_case/v3:pkg", + "//envoy/extensions/http/original_ip_detection/custom_header/v3:pkg", + "//envoy/extensions/http/original_ip_detection/xff/v3:pkg", "//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg", "//envoy/extensions/internal_redirect/previous_routes/v3:pkg", "//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg", diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 3714c7cca8aa5..1ac96a5a292cc 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -36,7 +36,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 46] +// [#next-free-field: 47] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -501,7 +501,36 @@ message HttpConnectionManager { // determining the origin client's IP address. The default is zero if this option // is not specified. See the documentation for // :ref:`config_http_conn_man_headers_x-forwarded-for` for more information. - uint32 xff_num_trusted_hops = 19; + // + // .. note:: + // This field is deprecated and instead :ref:`original_ip_detection_extensions + // ` + // should be used to configure the :ref:`xff extension ` + // to configure IP detection using the :ref:`config_http_conn_man_headers_x-forwarded-for` header. To replace + // this field use a config like the following: + // + // .. code-block:: yaml + // + // original_ip_detection_extensions: + // typed_config: + // "@type": type.googleapis.com/envoy.extensions.http.original_ip_detection.xff.v3.XffConfig + // xff_num_trusted_hops: 1 + // + uint32 xff_num_trusted_hops = 19 + [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; + + // The configuration for the original IP detection extensions. + // + // When configured the extensions will be called along with the request headers + // and information about the downstream connection, such as the directly connected address. + // Each extension will then use these parameters to decide the request's effective remote address. + // If an extension fails to detect the original IP address and isn't configured to reject + // the request, the HCM will try the remaining extensions until one succeeds or rejects + // the request. If the request isn't rejected nor any extension succeeds, the HCM will + // fallback to using the remote address. + // + // [#extension-category: envoy.http.original_ip_detection] + repeated config.core.v3.TypedExtensionConfig original_ip_detection_extensions = 46; // Configures what network addresses are considered internal for stats and header sanitation // purposes. If unspecified, only RFC1918 IP addresses will be considered internal. diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/BUILD b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/BUILD index 64536cdef30b9..37cbc68f19156 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/BUILD +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/BUILD @@ -6,6 +6,7 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/annotations:pkg", "//envoy/config/accesslog/v4alpha:pkg", "//envoy/config/core/v4alpha:pkg", "//envoy/config/route/v4alpha:pkg", diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index 59d0d5c74984a..7582fc2611ee3 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -19,6 +19,7 @@ import "google/protobuf/any.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; +import "envoy/annotations/deprecation.proto"; import "udpa/annotations/security.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -33,7 +34,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 46] +// [#next-free-field: 47] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"; @@ -498,7 +499,36 @@ message HttpConnectionManager { // determining the origin client's IP address. The default is zero if this option // is not specified. See the documentation for // :ref:`config_http_conn_man_headers_x-forwarded-for` for more information. - uint32 xff_num_trusted_hops = 19; + // + // .. note:: + // This field is deprecated and instead :ref:`original_ip_detection_extensions + // ` + // should be used to configure the :ref:`xff extension ` + // to configure IP detection using the :ref:`config_http_conn_man_headers_x-forwarded-for` header. To replace + // this field use a config like the following: + // + // .. code-block:: yaml + // + // original_ip_detection_extensions: + // typed_config: + // "@type": type.googleapis.com/envoy.extensions.http.original_ip_detection.xff.v3.XffConfig + // xff_num_trusted_hops: 1 + // + uint32 hidden_envoy_deprecated_xff_num_trusted_hops = 19 + [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; + + // The configuration for the original IP detection extensions. + // + // When configured the extensions will be called along with the request headers + // and information about the downstream connection, such as the directly connected address. + // Each extension will then use these parameters to decide the request's effective remote address. + // If an extension fails to detect the original IP address and isn't configured to reject + // the request, the HCM will try the remaining extensions until one succeeds or rejects + // the request. If the request isn't rejected nor any extension succeeds, the HCM will + // fallback to using the remote address. + // + // [#extension-category: envoy.http.original_ip_detection] + repeated config.core.v4alpha.TypedExtensionConfig original_ip_detection_extensions = 46; // Configures what network addresses are considered internal for stats and header sanitation // purposes. If unspecified, only RFC1918 IP addresses will be considered internal. diff --git a/generated_api_shadow/envoy/extensions/http/original_ip_detection/custom_header/v3/BUILD b/generated_api_shadow/envoy/extensions/http/original_ip_detection/custom_header/v3/BUILD new file mode 100644 index 0000000000000..9a76b7e148e03 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/http/original_ip_detection/custom_header/v3/BUILD @@ -0,0 +1,12 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/type/v3:pkg", + "@com_github_cncf_udpa//udpa/annotations:pkg", + ], +) diff --git a/generated_api_shadow/envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.proto b/generated_api_shadow/envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.proto new file mode 100644 index 0000000000000..5ea93d7548438 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +package envoy.extensions.http.original_ip_detection.custom_header.v3; + +import "envoy/type/v3/http_status.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.http.original_ip_detection.custom_header.v3"; +option java_outer_classname = "CustomHeaderProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Custom header original IP detection extension] + +// This extension allows for the original downstream remote IP to be detected +// by reading the value from a configured header name. If the value is successfully parsed +// as an IP, it'll be treated as the effective downstream remote address and seen as such +// by all filters. See :ref:`original_ip_detection_extensions +// ` +// for an overview of how extensions operate and what happens when an extension fails +// to detect the remote IP. +// +// [#extension: envoy.http.original_ip_detection.custom_header] +message CustomHeaderConfig { + // The header name containing the original downstream remote address, if present. + // + // Note: in the case of a multi-valued header, only the first value is tried and the rest are ignored. + string header_name = 1 + [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_NAME strict: true}]; + + // If set to true, the extension could decide that the detected address should be treated as + // trusted by the HCM. If the address is considered :ref:`trusted`, + // it might be used as input to determine if the request is internal (among other things). + bool allow_extension_to_set_address_as_trusted = 2; + + // If this is set, the request will be rejected when detection fails using it as the HTTP response status. + // + // .. note:: + // If this is set to < 400 or > 511, the default status 403 will be used instead. + type.v3.HttpStatus reject_with_status = 3; +} diff --git a/generated_api_shadow/envoy/extensions/http/original_ip_detection/xff/v3/BUILD b/generated_api_shadow/envoy/extensions/http/original_ip_detection/xff/v3/BUILD new file mode 100644 index 0000000000000..ee92fb652582e --- /dev/null +++ b/generated_api_shadow/envoy/extensions/http/original_ip_detection/xff/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/generated_api_shadow/envoy/extensions/http/original_ip_detection/xff/v3/xff.proto b/generated_api_shadow/envoy/extensions/http/original_ip_detection/xff/v3/xff.proto new file mode 100644 index 0000000000000..6864788f9f185 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/http/original_ip_detection/xff/v3/xff.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package envoy.extensions.http.original_ip_detection.xff.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.http.original_ip_detection.xff.v3"; +option java_outer_classname = "XffProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: XFF original IP detection extension] + +// This extension allows for the original downstream remote IP to be detected +// by reading the :ref:`config_http_conn_man_headers_x-forwarded-for` header. +// +// [#extension: envoy.http.original_ip_detection.xff] +message XffConfig { + // The number of additional ingress proxy hops from the right side of the + // :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header to trust when + // determining the origin client's IP address. The default is zero if this option + // is not specified. See the documentation for + // :ref:`config_http_conn_man_headers_x-forwarded-for` for more information. + uint32 xff_num_trusted_hops = 1; +} diff --git a/include/envoy/http/BUILD b/include/envoy/http/BUILD index 7b97f7089cc8a..749355e04c5dc 100644 --- a/include/envoy/http/BUILD +++ b/include/envoy/http/BUILD @@ -157,3 +157,15 @@ envoy_cc_library( "//include/envoy/config:typed_config_interface", ], ) + +envoy_cc_library( + name = "original_ip_detection_interface", + hdrs = ["original_ip_detection.h"], + deps = [ + ":codes_interface", + ":header_map_interface", + "//include/envoy/config:typed_config_interface", + "//include/envoy/network:address_interface", + "//include/envoy/server:factory_context_interface", + ], +) diff --git a/include/envoy/http/original_ip_detection.h b/include/envoy/http/original_ip_detection.h new file mode 100644 index 0000000000000..27d52d59e2f26 --- /dev/null +++ b/include/envoy/http/original_ip_detection.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include + +#include "envoy/common/pure.h" +#include "envoy/config/typed_config.h" +#include "envoy/http/codes.h" +#include "envoy/http/header_map.h" +#include "envoy/network/address.h" +#include "envoy/server/factory_context.h" + +namespace Envoy { +namespace Http { + +struct OriginalIPDetectionParams { + // The request headers from downstream. + // + // Note that while extensions can modify the headers, they will undergo standard Envoy + // sanitation after the detect() call so additions made here may be removed before + // filters have access to headers. + Http::RequestHeaderMap& request_headers; + // The downstream directly connected address. + const Network::Address::InstanceConstSharedPtr& downstream_remote_address; +}; + +// Parameters to be used for sending a local reply when detection fails. +struct OriginalIPRejectRequestOptions { + Code response_code; + std::string body; +}; + +struct OriginalIPDetectionResult { + // An address that represents the detected address or nullptr if detection failed. + Network::Address::InstanceConstSharedPtr detected_remote_address; + // Is the detected address trusted (e.g.: can it be used to determine if this is an internal + // request). + bool allow_trusted_address_checks; + // If set, these parameters will be used to signal that detection failed and the request should + // be rejected. + absl::optional reject_options; +}; + +/** + * Interface class for original IP detection extensions. + */ +class OriginalIPDetection { +public: + virtual ~OriginalIPDetection() = default; + + /** + * Detect the final remote address. + * + * If the call to this method succeeds in detecting the remote IP address or + * fails and is configured to reject the request in that case, no other + * configured extensions will be called (if any). + * + * @param param supplies the OriginalIPDetectionParams params for detection. + * @return OriginalIPDetectionResult the result of the extension's attempt to detect + * the final remote address. + */ + virtual OriginalIPDetectionResult detect(OriginalIPDetectionParams& params) PURE; +}; + +using OriginalIPDetectionSharedPtr = std::shared_ptr; + +/* + * A factory for creating original IP detection extensions. + */ +class OriginalIPDetectionFactory : public Envoy::Config::TypedFactory { +public: + ~OriginalIPDetectionFactory() override = default; + + /** + * Creates a particular extension implementation. + * + * @param config supplies the configuration for the original IP detection extension. + * @return OriginalIPDetectionSharedPtr the extension instance. + */ + virtual OriginalIPDetectionSharedPtr + createExtension(const Protobuf::Message& config, + Server::Configuration::FactoryContext& context) PURE; + + std::string category() const override { return "envoy.http.original_ip_detection"; } +}; + +using OriginalIPDetectionFactoryPtr = std::unique_ptr; + +} // namespace Http +} // namespace Envoy diff --git a/include/envoy/stream_info/stream_info.h b/include/envoy/stream_info/stream_info.h index 71d4fdbb2886b..acf84800d7303 100644 --- a/include/envoy/stream_info/stream_info.h +++ b/include/envoy/stream_info/stream_info.h @@ -181,8 +181,10 @@ struct ResponseCodeDetailValues { const std::string InternalRedirect = "internal_redirect"; // The request was rejected because configured filters erroneously removed required headers. const std::string FilterRemovedRequiredHeaders = "filter_removed_required_headers"; + // The request was rejected because the original IP couldn't be detected. + const std::string OriginalIPDetectionFailed = "rejecting because detection failed"; // Changes or additions to details should be reflected in - // docs/root/configuration/http/http_conn_man/response_code_details_details.rst + // docs/root/configuration/http/http_conn_man/response_code_details.rst }; using ResponseCodeDetails = ConstSingleton; diff --git a/source/common/http/BUILD b/source/common/http/BUILD index f6e23941dfbc4..2dfa803c7d94e 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -188,6 +188,7 @@ envoy_cc_library( ":date_provider_lib", "//include/envoy/config:config_provider_interface", "//include/envoy/http:filter_interface", + "//include/envoy/http:original_ip_detection_interface", "//include/envoy/http:request_id_extension_interface", "//include/envoy/router:rds_interface", "//source/common/local_reply:local_reply_lib", diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index 2033da5c321c9..8b05b4bbff49b 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -3,6 +3,7 @@ #include "envoy/config/config_provider.h" #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" #include "envoy/http/filter.h" +#include "envoy/http/original_ip_detection.h" #include "envoy/http/request_id_extension.h" #include "envoy/router/rds.h" #include "envoy/stats/scope.h" @@ -57,6 +58,7 @@ namespace Http { COUNTER(downstream_rq_non_relative_path) \ COUNTER(downstream_rq_overload_close) \ COUNTER(downstream_rq_redirected_with_normalized_path) \ + COUNTER(downstream_rq_rejected_via_ip_detection) \ COUNTER(downstream_rq_response_before_rq_complete) \ COUNTER(downstream_rq_rx_reset) \ COUNTER(downstream_rq_timeout) \ @@ -476,6 +478,12 @@ class ConnectionManagerConfig { virtual envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: PathWithEscapedSlashesAction pathWithEscapedSlashesAction() const PURE; + + /** + * @return vector of OriginalIPDetectionSharedPtr original IP detection extensions. + */ + virtual const std::vector& + originalIpDetectionExtensions() const PURE; }; } // namespace Http } // namespace Envoy diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index a9e7205651b6e..285a4f3627738 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1002,9 +1002,22 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapPtr&& he if (!state_.is_internally_created_) { // Only sanitize headers on first pass. // Modify the downstream remote address depending on configuration and headers. - filter_manager_.setDownstreamRemoteAddress(ConnectionManagerUtility::mutateRequestHeaders( + const auto mutate_result = ConnectionManagerUtility::mutateRequestHeaders( *request_headers_, connection_manager_.read_callbacks_->connection(), - connection_manager_.config_, *snapped_route_config_, connection_manager_.local_info_)); + connection_manager_.config_, *snapped_route_config_, connection_manager_.local_info_); + + // IP detection failed, reject the request. + if (mutate_result.reject_request.has_value()) { + const auto& reject_request_params = mutate_result.reject_request.value(); + connection_manager_.stats_.named_.downstream_rq_rejected_via_ip_detection_.inc(); + sendLocalReply(Grpc::Common::isGrpcRequestHeaders(*request_headers_), + reject_request_params.response_code, reject_request_params.body, nullptr, + absl::nullopt, + StreamInfo::ResponseCodeDetails::get().OriginalIPDetectionFailed); + return; + } + + filter_manager_.setDownstreamRemoteAddress(mutate_result.final_remote_address); } ASSERT(filter_manager_.streamInfo().downstreamAddressProvider().remoteAddress() != nullptr); diff --git a/source/common/http/conn_manager_utility.cc b/source/common/http/conn_manager_utility.cc index 38747b89f63bd..1131f40113bb6 100644 --- a/source/common/http/conn_manager_utility.cc +++ b/source/common/http/conn_manager_utility.cc @@ -72,7 +72,7 @@ ServerConnectionPtr ConnectionManagerUtility::autoCreateCodec( } } -Network::Address::InstanceConstSharedPtr ConnectionManagerUtility::mutateRequestHeaders( +ConnectionManagerUtility::MutateRequestHeadersResult ConnectionManagerUtility::mutateRequestHeaders( RequestHeaderMap& request_headers, Network::Connection& connection, ConnectionManagerConfig& config, const Router::Config& route_config, const LocalInfo::LocalInfo& local_info) { @@ -104,11 +104,11 @@ Network::Address::InstanceConstSharedPtr ConnectionManagerUtility::mutateRequest // peer. Cases where we don't "use remote address" include trusted double proxy where we expect // our peer to have already properly set XFF, etc. Network::Address::InstanceConstSharedPtr final_remote_address; - bool single_xff_address; + bool allow_trusted_address_checks = false; const uint32_t xff_num_trusted_hops = config.xffNumTrustedHops(); if (config.useRemoteAddress()) { - single_xff_address = request_headers.ForwardedFor() == nullptr; + allow_trusted_address_checks = request_headers.ForwardedFor() == nullptr; // If there are any trusted proxies in front of this Envoy instance (as indicated by // the xff_num_trusted_hops configuration option), get the trusted client address // from the XFF before we append to XFF. @@ -136,12 +136,27 @@ Network::Address::InstanceConstSharedPtr ConnectionManagerUtility::mutateRequest connection.ssl() ? Headers::get().SchemeValues.Https : Headers::get().SchemeValues.Http); } } else { - // If we are not using remote address, attempt to pull a valid IPv4 or IPv6 address out of XFF. + // If we are not using remote address, attempt to pull a valid IPv4 or IPv6 address out of XFF + // or through an extension. An extension might be needed when XFF doesn't work (e.g. an + // irregular network). + // // If we find one, it will be used as the downstream address for logging. It may or may not be // used for determining internal/external status (see below). - auto ret = Utility::getLastAddressFromXFF(request_headers, xff_num_trusted_hops); - final_remote_address = ret.address_; - single_xff_address = ret.single_address_; + OriginalIPDetectionParams params = {request_headers, + connection.addressProvider().remoteAddress()}; + for (const auto& detection_extension : config.originalIpDetectionExtensions()) { + const auto result = detection_extension->detect(params); + + if (result.reject_options.has_value()) { + return {nullptr, result.reject_options}; + } + + if (result.detected_remote_address) { + final_remote_address = result.detected_remote_address; + allow_trusted_address_checks = result.allow_trusted_address_checks; + break; + } + } } // If the x-forwarded-proto header is not set, set it here, since Envoy uses it for determining @@ -169,7 +184,7 @@ Network::Address::InstanceConstSharedPtr ConnectionManagerUtility::mutateRequest // we can't change it at this point. In the future we will likely need to add // additional inference modes and make this mode legacy. const bool internal_request = - single_xff_address && final_remote_address != nullptr && + allow_trusted_address_checks && final_remote_address != nullptr && config.internalAddressConfig().isInternalAddress(*final_remote_address); // After determining internal request status, if there is no final remote address, due to no XFF, @@ -187,30 +202,7 @@ Network::Address::InstanceConstSharedPtr ConnectionManagerUtility::mutateRequest request_headers.setReferenceEnvoyInternalRequest( Headers::get().EnvoyInternalRequestValues.True); } else { - if (edge_request) { - request_headers.removeEnvoyDecoratorOperation(); - request_headers.removeEnvoyDownstreamServiceCluster(); - request_headers.removeEnvoyDownstreamServiceNode(); - } - - request_headers.removeEnvoyRetriableStatusCodes(); - request_headers.removeEnvoyRetriableHeaderNames(); - request_headers.removeEnvoyRetryOn(); - request_headers.removeEnvoyRetryGrpcOn(); - request_headers.removeEnvoyMaxRetries(); - request_headers.removeEnvoyUpstreamAltStatName(); - request_headers.removeEnvoyUpstreamRequestTimeoutMs(); - request_headers.removeEnvoyUpstreamRequestPerTryTimeoutMs(); - request_headers.removeEnvoyUpstreamRequestTimeoutAltResponse(); - request_headers.removeEnvoyExpectedRequestTimeoutMs(); - request_headers.removeEnvoyForceTrace(); - request_headers.removeEnvoyIpTags(); - request_headers.removeEnvoyOriginalUrl(); - request_headers.removeEnvoyHedgeOnPerTryTimeout(); - - for (const LowerCaseString& header : route_config.internalOnlyHeaders()) { - request_headers.remove(header); - } + cleanInternalHeaders(request_headers, edge_request, route_config.internalOnlyHeaders()); } if (config.userAgent()) { @@ -250,7 +242,36 @@ Network::Address::InstanceConstSharedPtr ConnectionManagerUtility::mutateRequest mutateXfccRequestHeader(request_headers, connection, config); - return final_remote_address; + return {final_remote_address, absl::nullopt}; +} + +void ConnectionManagerUtility::cleanInternalHeaders( + RequestHeaderMap& request_headers, bool edge_request, + const std::list& internal_only_headers) { + if (edge_request) { + request_headers.removeEnvoyDecoratorOperation(); + request_headers.removeEnvoyDownstreamServiceCluster(); + request_headers.removeEnvoyDownstreamServiceNode(); + } + + request_headers.removeEnvoyRetriableStatusCodes(); + request_headers.removeEnvoyRetriableHeaderNames(); + request_headers.removeEnvoyRetryOn(); + request_headers.removeEnvoyRetryGrpcOn(); + request_headers.removeEnvoyMaxRetries(); + request_headers.removeEnvoyUpstreamAltStatName(); + request_headers.removeEnvoyUpstreamRequestTimeoutMs(); + request_headers.removeEnvoyUpstreamRequestPerTryTimeoutMs(); + request_headers.removeEnvoyUpstreamRequestTimeoutAltResponse(); + request_headers.removeEnvoyExpectedRequestTimeoutMs(); + request_headers.removeEnvoyForceTrace(); + request_headers.removeEnvoyIpTags(); + request_headers.removeEnvoyOriginalUrl(); + request_headers.removeEnvoyHedgeOnPerTryTimeout(); + + for (const LowerCaseString& header : internal_only_headers) { + request_headers.remove(header); + } } Tracing::Reason ConnectionManagerUtility::mutateTracingRequestHeader( diff --git a/source/common/http/conn_manager_utility.h b/source/common/http/conn_manager_utility.h index d85af74ce570e..f5b15eebdfebf 100644 --- a/source/common/http/conn_manager_utility.h +++ b/source/common/http/conn_manager_utility.h @@ -47,6 +47,15 @@ class ConnectionManagerUtility { envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction headers_with_underscores_action); + /* The result after calling mutateRequestHeaders(), containing the final remote address. Note that + * an extension used for detecting the original IP of the request might decide it should be + * rejected if the detection failed. In this case, the reject_request optional will be set. + */ + struct MutateRequestHeadersResult { + Network::Address::InstanceConstSharedPtr final_remote_address; + absl::optional reject_request; + }; + /** * Mutates request headers in various ways. This functionality is broken out because of its * complexity for ease of testing. See the method itself for detailed comments on what @@ -55,13 +64,16 @@ class ConnectionManagerUtility { * Note this function may be called twice on the response path if there are * 100-Continue headers. * - * @return the final trusted remote address. This depends on various settings and the - * existence of the x-forwarded-for header. Again see the method for more details. + * @return MutateRequestHeadersResult containing the final trusted remote address if detected. + * This depends on various settings and the existence of the x-forwarded-for header. + * Note that an extension might also be used. If detection fails, the result may contain + * options for rejecting the request. */ - static Network::Address::InstanceConstSharedPtr - mutateRequestHeaders(RequestHeaderMap& request_headers, Network::Connection& connection, - ConnectionManagerConfig& config, const Router::Config& route_config, - const LocalInfo::LocalInfo& local_info); + static MutateRequestHeadersResult mutateRequestHeaders(RequestHeaderMap& request_headers, + Network::Connection& connection, + ConnectionManagerConfig& config, + const Router::Config& route_config, + const LocalInfo::LocalInfo& local_info); static void mutateResponseHeaders(ResponseHeaderMap& response_headers, const RequestHeaderMap* request_headers, @@ -95,6 +107,8 @@ class ConnectionManagerUtility { static void mutateXfccRequestHeader(RequestHeaderMap& request_headers, Network::Connection& connection, ConnectionManagerConfig& config); + static void cleanInternalHeaders(RequestHeaderMap& request_headers, bool edge_request, + const std::list& internal_only_headers); }; } // namespace Http diff --git a/source/common/http/utility.h b/source/common/http/utility.h index e01f65357feeb..b05ff1b0b5106 100644 --- a/source/common/http/utility.h +++ b/source/common/http/utility.h @@ -357,8 +357,8 @@ void sendLocalReply(const bool& is_reset, const EncodeFunctions& encode_function struct GetLastAddressFromXffInfo { // Last valid address pulled from the XFF header. Network::Address::InstanceConstSharedPtr address_; - // Whether this is the only address in the XFF header. - bool single_address_; + // Whether this address can be used to determine if it's an internal request. + bool allow_trusted_address_checks_; }; /** diff --git a/source/extensions/all_extensions.bzl b/source/extensions/all_extensions.bzl index f1f2f9901850c..761c3c75e0bcc 100644 --- a/source/extensions/all_extensions.bzl +++ b/source/extensions/all_extensions.bzl @@ -5,6 +5,7 @@ load("@envoy_build_config//:extensions_build_config.bzl", "EXTENSIONS") # The map may be overridden by extensions specified in envoy_build_config. _required_extensions = { "envoy.common.crypto.utility_lib": "//source/extensions/common/crypto:utility_lib", + "envoy.http.original_ip_detection.xff": "//source/extensions/http/original_ip_detection/xff:config", "envoy.request_id.uuid": "//source/extensions/request_id/uuid:config", "envoy.transport_sockets.tls": "//source/extensions/transport_sockets/tls:config", } diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 616d7714ef79a..ee8ab2a61b716 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -269,6 +269,14 @@ EXTENSIONS = { # "envoy.http.stateful_header_formatters.preserve_case": "//source/extensions/http/header_formatters/preserve_case:preserve_case_formatter", + + # + # Original IP detection + # + + "envoy.http.original_ip_detection.custom_header": "//source/extensions/http/original_ip_detection/custom_header:config", + "envoy.http.original_ip_detection.xff": "//source/extensions/http/original_ip_detection/xff:config", + } # These can be changed to ["//visibility:public"], for downstream builds which diff --git a/source/extensions/filters/network/http_connection_manager/BUILD b/source/extensions/filters/network/http_connection_manager/BUILD index 60c8ce180e04e..c2c6a19c2e949 100644 --- a/source/extensions/filters/network/http_connection_manager/BUILD +++ b/source/extensions/filters/network/http_connection_manager/BUILD @@ -28,6 +28,7 @@ envoy_cc_extension( "//include/envoy/filesystem:filesystem_interface", "//include/envoy/http:codec_interface", "//include/envoy/http:filter_interface", + "//include/envoy/http:original_ip_detection_interface", "//include/envoy/http:request_id_extension_interface", "//include/envoy/registry", "//include/envoy/router:route_config_provider_manager_interface", @@ -58,9 +59,11 @@ envoy_cc_extension( "//source/extensions/filters/http/common:pass_through_filter_lib", "//source/extensions/filters/network:well_known_names", "//source/extensions/filters/network/common:factory_base_lib", + "//source/extensions/http/original_ip_detection/xff:config", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/request_id/uuid/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/http/original_ip_detection/xff/v3:pkg_cc_proto", "@envoy_api//envoy/type/tracing/v3:pkg_cc_proto", "@envoy_api//envoy/type/v3:pkg_cc_proto", ] + envoy_select_enable_http3([ diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 03669895e655f..684fae1fca17c 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -8,6 +8,7 @@ #include "envoy/config/core/v3/base.pb.h" #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.validate.h" +#include "envoy/extensions/http/original_ip_detection/xff/v3/xff.pb.h" #include "envoy/extensions/request_id/uuid/v3/uuid.pb.h" #include "envoy/filesystem/filesystem.h" #include "envoy/registry/registry.h" @@ -344,6 +345,34 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( } request_id_extension_ = Http::RequestIDExtensionFactory::fromProto(final_rid_config, context_); + // Check if IP detection extensions were configured, otherwise fall back to XFF. + auto ip_detection_extensions = config.original_ip_detection_extensions(); + if (ip_detection_extensions.empty()) { + envoy::extensions::http::original_ip_detection::xff::v3::XffConfig xff_config; + xff_config.set_xff_num_trusted_hops(xff_num_trusted_hops_); + + auto* extension = ip_detection_extensions.Add(); + extension->set_name("envoy.http.original_ip_detection.xff"); + extension->mutable_typed_config()->PackFrom(xff_config); + } + + original_ip_detection_extensions_.reserve(ip_detection_extensions.size()); + for (const auto& extension_config : ip_detection_extensions) { + auto* factory = + Envoy::Config::Utility::getFactory(extension_config); + if (!factory) { + throw EnvoyException( + fmt::format("Original IP detection extension not found: '{}'", extension_config.name())); + } + + auto extension = factory->createExtension(extension_config.typed_config(), context_); + if (!extension) { + throw EnvoyException(fmt::format("Original IP detection extension could not be created: '{}'", + extension_config.name())); + } + original_ip_detection_extensions_.push_back(extension); + } + // If scoped RDS is enabled, avoid creating a route config provider. Route config providers will // be managed by the scoped routing logic instead. switch (config.route_specifier_case()) { @@ -740,7 +769,7 @@ const envoy::config::trace::v3::Tracing_Http* HttpConnectionManagerConfig::getPe if (config.tracing().has_provider()) { return &config.tracing().provider(); } - // Otherwise, for the sake of backwards compatibility, fallback to using tracing provider + // Otherwise, for the sake of backwards compatibility, fall back to using tracing provider // configuration defined in the bootstrap config. if (context_.httpContext().defaultTracingConfig().has_http()) { return &context_.httpContext().defaultTracingConfig().http(); diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index 6b023bafb6fe9..b98629ee8ecc2 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -13,6 +13,7 @@ #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.validate.h" #include "envoy/filter/http/filter_config_provider.h" #include "envoy/http/filter.h" +#include "envoy/http/original_ip_detection.h" #include "envoy/http/request_id_extension.h" #include "envoy/router/route_config_provider_manager.h" #include "envoy/tracing/http_tracer_manager.h" @@ -186,6 +187,10 @@ class HttpConnectionManagerConfig : Logger::Loggable, pathWithEscapedSlashesAction() const override { return path_with_escaped_slashes_action_; } + const std::vector& + originalIpDetectionExtensions() const override { + return original_ip_detection_extensions_; + } private: enum class CodecType { HTTP1, HTTP2, HTTP3, AUTO }; @@ -268,6 +273,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, const envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction headers_with_underscores_action_; const LocalReply::LocalReplyPtr local_reply_; + std::vector original_ip_detection_extensions_{}; // Default idle timeout is 5 minutes if nothing is specified in the HCM config. static const uint64_t StreamIdleTimeoutMs = 5 * 60 * 1000; diff --git a/source/extensions/http/original_ip_detection/custom_header/BUILD b/source/extensions/http/original_ip_detection/custom_header/BUILD new file mode 100644 index 0000000000000..13f1a20009999 --- /dev/null +++ b/source/extensions/http/original_ip_detection/custom_header/BUILD @@ -0,0 +1,40 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "custom_header_lib", + srcs = ["custom_header.cc"], + hdrs = ["custom_header.h"], + # This extension is used from core tests. + visibility = ["//visibility:public"], + deps = [ + "//include/envoy/http:original_ip_detection_interface", + "//source/common/network:utility_lib", + "@envoy_api//envoy/extensions/http/original_ip_detection/custom_header/v3:pkg_cc_proto", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + category = "envoy.http.original_ip_detection", + security_posture = "robust_to_untrusted_downstream", + # This extension is used from core tests. + visibility = ["//visibility:public"], + deps = [ + ":custom_header_lib", + "//include/envoy/http:original_ip_detection_interface", + "//include/envoy/registry", + "//source/common/config:utility_lib", + "@envoy_api//envoy/extensions/http/original_ip_detection/custom_header/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/http/original_ip_detection/custom_header/config.cc b/source/extensions/http/original_ip_detection/custom_header/config.cc new file mode 100644 index 0000000000000..65defdccb738d --- /dev/null +++ b/source/extensions/http/original_ip_detection/custom_header/config.cc @@ -0,0 +1,35 @@ +#include "extensions/http/original_ip_detection/custom_header/config.h" + +#include "envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.pb.h" +#include "envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.pb.validate.h" +#include "envoy/http/original_ip_detection.h" +#include "envoy/registry/registry.h" + +#include "common/config/utility.h" + +#include "extensions/http/original_ip_detection/custom_header/custom_header.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace OriginalIPDetection { +namespace CustomHeader { + +Envoy::Http::OriginalIPDetectionSharedPtr +CustomHeaderIPDetectionFactory::createExtension(const Protobuf::Message& message, + Server::Configuration::FactoryContext& context) { + auto mptr = Envoy::Config::Utility::translateAnyToFactoryConfig( + dynamic_cast(message), context.messageValidationVisitor(), *this); + const auto& proto_config = MessageUtil::downcastAndValidate< + const envoy::extensions::http::original_ip_detection::custom_header::v3::CustomHeaderConfig&>( + *mptr, context.messageValidationVisitor()); + return std::make_shared(proto_config); +} + +REGISTER_FACTORY(CustomHeaderIPDetectionFactory, Envoy::Http::OriginalIPDetectionFactory); + +} // namespace CustomHeader +} // namespace OriginalIPDetection +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/http/original_ip_detection/custom_header/config.h b/source/extensions/http/original_ip_detection/custom_header/config.h new file mode 100644 index 0000000000000..05e28c07b30e6 --- /dev/null +++ b/source/extensions/http/original_ip_detection/custom_header/config.h @@ -0,0 +1,36 @@ +#pragma once + +#include "envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.pb.h" +#include "envoy/http/original_ip_detection.h" + +#include "common/protobuf/protobuf.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace OriginalIPDetection { +namespace CustomHeader { + +/** + * Config registration for the custom header IP detection extension. + * @see OriginalIPDetectionFactory. + */ +class CustomHeaderIPDetectionFactory : public Envoy::Http::OriginalIPDetectionFactory { +public: + Envoy::Http::OriginalIPDetectionSharedPtr + createExtension(const Protobuf::Message& message, + Server::Configuration::FactoryContext& context) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique< + envoy::extensions::http::original_ip_detection::custom_header::v3::CustomHeaderConfig>(); + } + + std::string name() const override { return "envoy.http.original_ip_detection.custom_header"; } +}; + +} // namespace CustomHeader +} // namespace OriginalIPDetection +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/http/original_ip_detection/custom_header/custom_header.cc b/source/extensions/http/original_ip_detection/custom_header/custom_header.cc new file mode 100644 index 0000000000000..9e7d9d7e8bcb8 --- /dev/null +++ b/source/extensions/http/original_ip_detection/custom_header/custom_header.cc @@ -0,0 +1,47 @@ +#include "extensions/http/original_ip_detection/custom_header/custom_header.h" + +#include "common/network/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace OriginalIPDetection { +namespace CustomHeader { + +CustomHeaderIPDetection::CustomHeaderIPDetection( + const envoy::extensions::http::original_ip_detection::custom_header::v3::CustomHeaderConfig& + config) + : header_name_(config.header_name()), + allow_trusted_address_checks_(config.allow_extension_to_set_address_as_trusted()) { + if (config.has_reject_with_status()) { + const auto reject_code = toErrorCode(config.reject_with_status().code()); + reject_options_ = {reject_code, ""}; + } +} + +CustomHeaderIPDetection::CustomHeaderIPDetection( + const std::string& header_name, + absl::optional reject_options) + : header_name_(header_name), reject_options_(reject_options) {} + +Envoy::Http::OriginalIPDetectionResult +CustomHeaderIPDetection::detect(Envoy::Http::OriginalIPDetectionParams& params) { + auto hdr = params.request_headers.get(header_name_); + if (hdr.empty()) { + return {nullptr, false, reject_options_}; + } + + auto header_value = hdr[0]->value().getStringView(); + auto addr = Network::Utility::parseInternetAddressNoThrow(std::string(header_value)); + if (addr) { + return {addr, allow_trusted_address_checks_, absl::nullopt}; + } + + return {nullptr, false, reject_options_}; +} + +} // namespace CustomHeader +} // namespace OriginalIPDetection +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/http/original_ip_detection/custom_header/custom_header.h b/source/extensions/http/original_ip_detection/custom_header/custom_header.h new file mode 100644 index 0000000000000..4a820b2c5bebf --- /dev/null +++ b/source/extensions/http/original_ip_detection/custom_header/custom_header.h @@ -0,0 +1,47 @@ +#pragma once + +#include "envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.pb.h" +#include "envoy/http/codes.h" +#include "envoy/http/original_ip_detection.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace OriginalIPDetection { +namespace CustomHeader { + +/** + * Custom header IP detection extension. + */ +class CustomHeaderIPDetection : public Envoy::Http::OriginalIPDetection { +public: + CustomHeaderIPDetection( + const envoy::extensions::http::original_ip_detection::custom_header::v3::CustomHeaderConfig& + config); + CustomHeaderIPDetection( + const std::string& header_name, + absl::optional reject_options = absl::nullopt); + + Envoy::Http::OriginalIPDetectionResult + detect(Envoy::Http::OriginalIPDetectionParams& params) override; + +private: + static Envoy::Http::Code toErrorCode(uint64_t status) { + const auto code = static_cast(status); + if (code >= Envoy::Http::Code::BadRequest && + code <= Envoy::Http::Code::NetworkAuthenticationRequired) { + return code; + } + return Envoy::Http::Code::Forbidden; + } + + Envoy::Http::LowerCaseString header_name_; + bool allow_trusted_address_checks_{false}; + absl::optional reject_options_{absl::nullopt}; +}; + +} // namespace CustomHeader +} // namespace OriginalIPDetection +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/http/original_ip_detection/xff/BUILD b/source/extensions/http/original_ip_detection/xff/BUILD new file mode 100644 index 0000000000000..a247f485a1f56 --- /dev/null +++ b/source/extensions/http/original_ip_detection/xff/BUILD @@ -0,0 +1,40 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "xff_lib", + srcs = ["xff.cc"], + hdrs = ["xff.h"], + # This extension is core code. + visibility = ["//visibility:public"], + deps = [ + "//include/envoy/http:original_ip_detection_interface", + "//source/common/http:utility_lib", + "@envoy_api//envoy/extensions/http/original_ip_detection/xff/v3:pkg_cc_proto", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + category = "envoy.http.original_ip_detection", + security_posture = "robust_to_untrusted_downstream", + # This extension is core code. + visibility = ["//visibility:public"], + deps = [ + ":xff_lib", + "//include/envoy/http:original_ip_detection_interface", + "//include/envoy/registry", + "//source/common/config:utility_lib", + "@envoy_api//envoy/extensions/http/original_ip_detection/xff/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/http/original_ip_detection/xff/config.cc b/source/extensions/http/original_ip_detection/xff/config.cc new file mode 100644 index 0000000000000..57179a6a077f0 --- /dev/null +++ b/source/extensions/http/original_ip_detection/xff/config.cc @@ -0,0 +1,35 @@ +#include "extensions/http/original_ip_detection/xff/config.h" + +#include "envoy/extensions/http/original_ip_detection/xff/v3/xff.pb.h" +#include "envoy/extensions/http/original_ip_detection/xff/v3/xff.pb.validate.h" +#include "envoy/http/original_ip_detection.h" +#include "envoy/registry/registry.h" + +#include "common/config/utility.h" + +#include "extensions/http/original_ip_detection/xff/xff.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace OriginalIPDetection { +namespace Xff { + +Envoy::Http::OriginalIPDetectionSharedPtr +XffIPDetectionFactory::createExtension(const Protobuf::Message& message, + Server::Configuration::FactoryContext& context) { + auto mptr = Envoy::Config::Utility::translateAnyToFactoryConfig( + dynamic_cast(message), context.messageValidationVisitor(), *this); + const auto& proto_config = MessageUtil::downcastAndValidate< + const envoy::extensions::http::original_ip_detection::xff::v3::XffConfig&>( + *mptr, context.messageValidationVisitor()); + return std::make_shared(proto_config); +} + +REGISTER_FACTORY(XffIPDetectionFactory, Envoy::Http::OriginalIPDetectionFactory); + +} // namespace Xff +} // namespace OriginalIPDetection +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/http/original_ip_detection/xff/config.h b/source/extensions/http/original_ip_detection/xff/config.h new file mode 100644 index 0000000000000..df3872adbab01 --- /dev/null +++ b/source/extensions/http/original_ip_detection/xff/config.h @@ -0,0 +1,35 @@ +#pragma once + +#include "envoy/extensions/http/original_ip_detection/xff/v3/xff.pb.h" +#include "envoy/http/original_ip_detection.h" + +#include "common/protobuf/protobuf.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace OriginalIPDetection { +namespace Xff { + +/** + * Config registration for the x-forwarded-for IP detection extension. + * @see OriginalIPDetectionFactory. + */ +class XffIPDetectionFactory : public Envoy::Http::OriginalIPDetectionFactory { +public: + Envoy::Http::OriginalIPDetectionSharedPtr + createExtension(const Protobuf::Message& message, + Server::Configuration::FactoryContext& context) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() const override { return "envoy.http.original_ip_detection.xff"; } +}; + +} // namespace Xff +} // namespace OriginalIPDetection +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/http/original_ip_detection/xff/xff.cc b/source/extensions/http/original_ip_detection/xff/xff.cc new file mode 100644 index 0000000000000..70f142bb52dfc --- /dev/null +++ b/source/extensions/http/original_ip_detection/xff/xff.cc @@ -0,0 +1,29 @@ +#include "extensions/http/original_ip_detection/xff/xff.h" + +#include "common/http/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace OriginalIPDetection { +namespace Xff { + +XffIPDetection::XffIPDetection( + const envoy::extensions::http::original_ip_detection::xff::v3::XffConfig& config) + : xff_num_trusted_hops_(config.xff_num_trusted_hops()) {} + +XffIPDetection::XffIPDetection(uint32_t xff_num_trusted_hops) + : xff_num_trusted_hops_(xff_num_trusted_hops) {} + +Envoy::Http::OriginalIPDetectionResult +XffIPDetection::detect(Envoy::Http::OriginalIPDetectionParams& params) { + auto ret = + Envoy::Http::Utility::getLastAddressFromXFF(params.request_headers, xff_num_trusted_hops_); + return {ret.address_, ret.allow_trusted_address_checks_, absl::nullopt}; +} + +} // namespace Xff +} // namespace OriginalIPDetection +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/http/original_ip_detection/xff/xff.h b/source/extensions/http/original_ip_detection/xff/xff.h new file mode 100644 index 0000000000000..82d58a1aeaa88 --- /dev/null +++ b/source/extensions/http/original_ip_detection/xff/xff.h @@ -0,0 +1,31 @@ +#pragma once + +#include "envoy/extensions/http/original_ip_detection/xff/v3/xff.pb.h" +#include "envoy/http/original_ip_detection.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace OriginalIPDetection { +namespace Xff { + +/** + * XFF (x-forwarded-for) IP detection extension. + */ +class XffIPDetection : public Envoy::Http::OriginalIPDetection { +public: + XffIPDetection(const envoy::extensions::http::original_ip_detection::xff::v3::XffConfig& config); + XffIPDetection(uint32_t xff_num_trusted_hops); + + Envoy::Http::OriginalIPDetectionResult + detect(Envoy::Http::OriginalIPDetectionParams& params) override; + +private: + const uint32_t xff_num_trusted_hops_; +}; + +} // namespace Xff +} // namespace OriginalIPDetection +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index c9339f6925e9b..84d5236e212bc 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -189,6 +189,10 @@ class AdminImpl : public Admin, return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: KEEP_UNCHANGED; } + const std::vector& + originalIpDetectionExtensions() const override { + return detection_extensions_; + } Http::Code request(absl::string_view path_and_query, absl::string_view method, Http::ResponseHeaderMap& response_headers, std::string& body) override; void closeSocket(); @@ -447,6 +451,7 @@ class AdminImpl : public Admin, AdminListenerPtr listener_; const AdminInternalAddressConfig internal_address_config_; const LocalReply::LocalReplyPtr local_reply_; + const std::vector detection_extensions_{}; }; } // namespace Server diff --git a/test/common/http/BUILD b/test/common/http/BUILD index 6a4aae6d35d62..0063b157ced8f 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -214,6 +214,7 @@ envoy_cc_test( ], shard_count = 3, deps = [ + ":ip_detection_extensions_lib", "//source/common/http:conn_manager_lib", "//source/common/http:context_lib", "//source/extensions/access_loggers/common:file_access_log_lib", @@ -238,6 +239,7 @@ envoy_cc_test( name = "conn_manager_utility_test", srcs = ["conn_manager_utility_test.cc"], deps = [ + ":ip_detection_extensions_lib", "//source/common/common:random_generator_lib", "//source/common/event:dispatcher_lib", "//source/common/http:conn_manager_lib", @@ -480,3 +482,13 @@ envoy_cc_fuzz_test( "//test/fuzz:utility_lib", ], ) + +envoy_cc_test_library( + name = "ip_detection_extensions_lib", + srcs = ["ip_detection_extensions.cc"], + hdrs = ["ip_detection_extensions.h"], + deps = [ + "//source/extensions/http/original_ip_detection/custom_header:custom_header_lib", + "//source/extensions/http/original_ip_detection/xff:xff_lib", + ], +) diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index 56873a2b14a07..a62a208315ae4 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -210,6 +210,10 @@ class FuzzConfig : public ConnectionManagerConfig { return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: KEEP_UNCHANGED; } + const std::vector& + originalIpDetectionExtensions() const override { + return ip_detection_extensions_; + } const envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager config_; @@ -255,6 +259,7 @@ class FuzzConfig : public ConnectionManagerConfig { Http::DefaultInternalAddressConfig internal_address_config_; bool normalize_path_{true}; LocalReply::LocalReplyPtr local_reply_; + std::vector ip_detection_extensions_{}; }; // Internal representation of stream state. Encapsulates the stream state, mocks diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index 96adb828b74a6..f660286015139 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -1,4 +1,5 @@ #include "test/common/http/conn_manager_impl_test_base.h" +#include "test/common/http/ip_detection_extensions.h" #include "test/test_common/logging.h" #include "test/test_common/test_runtime.h" @@ -3014,5 +3015,28 @@ TEST_F(HttpConnectionManagerImplDeathTest, InvalidConnectionManagerConfig) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } +TEST_F(HttpConnectionManagerImplTest, RequestRejectedViaIPDetection) { + OriginalIPRejectRequestOptions reject_options = {Http::Code::Forbidden, "ip detection failed"}; + auto extension = getCustomHeaderExtension("x-ip", reject_options); + ip_detection_extensions_.push_back(extension); + + use_remote_address_ = false; + + setup(false, ""); + + // 403 direct response when IP detection fails. + EXPECT_CALL(response_encoder_, encodeHeaders(_, false)) + .WillOnce(Invoke([](const ResponseHeaderMap& headers, bool) -> void { + EXPECT_EQ("403", headers.getStatusValue()); + })); + std::string response_body; + EXPECT_CALL(response_encoder_, encodeData(_, true)).WillOnce(AddBufferToString(&response_body)); + + startRequest(); + + EXPECT_EQ("ip detection failed", response_body); + EXPECT_EQ(1U, stats_.named_.downstream_rq_rejected_via_ip_detection_.value()); +} + } // namespace Http } // namespace Envoy diff --git a/test/common/http/conn_manager_impl_test_base.cc b/test/common/http/conn_manager_impl_test_base.cc index a38d613068583..cd9fa25b98e67 100644 --- a/test/common/http/conn_manager_impl_test_base.cc +++ b/test/common/http/conn_manager_impl_test_base.cc @@ -2,6 +2,8 @@ #include "extensions/request_id/uuid/config.h" +#include "test/common/http/ip_detection_extensions.h" + using testing::AtLeast; using testing::InSequence; using testing::InvokeWithoutArgs; @@ -33,6 +35,8 @@ HttpConnectionManagerImplTest::HttpConnectionManagerImplTest() // response_encoder_ is not a NiceMock on purpose. This prevents complaining about this // method only. EXPECT_CALL(response_encoder_, getStream()).Times(AtLeast(0)); + + ip_detection_extensions_.push_back(getXFFExtension(0)); } HttpConnectionManagerImplTest::~HttpConnectionManagerImplTest() { diff --git a/test/common/http/conn_manager_impl_test_base.h b/test/common/http/conn_manager_impl_test_base.h index 7029b8414be1f..c8e174853fb4e 100644 --- a/test/common/http/conn_manager_impl_test_base.h +++ b/test/common/http/conn_manager_impl_test_base.h @@ -148,6 +148,10 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan pathWithEscapedSlashesAction() const override { return path_with_escaped_slashes_action_; } + const std::vector& + originalIpDetectionExtensions() const override { + return ip_detection_extensions_; + } Envoy::Event::SimulatedTimeSystem test_time_; NiceMock route_config_provider_; @@ -213,6 +217,8 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan NiceMock upstream_conn_; // for websocket tests NiceMock conn_pool_; // for websocket tests RequestIDExtensionSharedPtr request_id_extension_; + std::vector ip_detection_extensions_{}; + const LocalReply::LocalReplyPtr local_reply_; // TODO(mattklein123): Not all tests have been converted over to better setup. Convert the rest. diff --git a/test/common/http/conn_manager_utility_test.cc b/test/common/http/conn_manager_utility_test.cc index 7226c2a00d7ff..7dc2a957ebad9 100644 --- a/test/common/http/conn_manager_utility_test.cc +++ b/test/common/http/conn_manager_utility_test.cc @@ -14,6 +14,7 @@ #include "extensions/request_id/uuid/config.h" +#include "test/common/http/ip_detection_extensions.h" #include "test/mocks/http/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" @@ -108,9 +109,18 @@ class ConnectionManagerUtilityTest : public testing::Test { ON_CALL(config_, pathWithEscapedSlashesAction()) .WillByDefault(Return(envoy::extensions::filters::network::http_connection_manager::v3:: HttpConnectionManager::KEEP_UNCHANGED)); + + detection_extensions_.push_back(getXFFExtension(0)); + ON_CALL(config_, originalIpDetectionExtensions()) + .WillByDefault(ReturnRef(detection_extensions_)); } struct MutateRequestRet { + MutateRequestRet() = default; + MutateRequestRet(const std::string& downstream_address, bool internal, + Tracing::Reason trace_reason) + : downstream_address_(downstream_address), internal_(internal), + trace_reason_(trace_reason) {} bool operator==(const MutateRequestRet& rhs) const { return downstream_address_ == rhs.downstream_address_ && internal_ == rhs.internal_ && trace_reason_ == rhs.trace_reason_; @@ -119,6 +129,7 @@ class ConnectionManagerUtilityTest : public testing::Test { std::string downstream_address_; bool internal_; Tracing::Reason trace_reason_; + absl::optional reject_request_{absl::nullopt}; }; // This is a convenience method used to call mutateRequestHeaders(). It is done in this @@ -126,9 +137,10 @@ class ConnectionManagerUtilityTest : public testing::Test { // the request is internal/external, given the importance of these two pieces of data. MutateRequestRet callMutateRequestHeaders(RequestHeaderMap& headers, Protocol) { MutateRequestRet ret; - ret.downstream_address_ = ConnectionManagerUtility::mutateRequestHeaders( - headers, connection_, config_, route_config_, local_info_) - ->asString(); + const auto result = ConnectionManagerUtility::mutateRequestHeaders( + headers, connection_, config_, route_config_, local_info_); + ret.downstream_address_ = result.final_remote_address->asString(); + ret.reject_request_ = result.reject_request; ret.trace_reason_ = ConnectionManagerUtility::mutateTracingRequestHeader(headers, runtime_, config_, &route_); ret.internal_ = HeaderUtility::isEnvoyInternalRequest(headers); @@ -139,6 +151,7 @@ class ConnectionManagerUtilityTest : public testing::Test { NiceMock random_; const std::shared_ptr request_id_extension_; const std::shared_ptr request_id_extension_to_return_; + std::vector detection_extensions_{}; NiceMock config_; NiceMock route_config_; NiceMock route_; @@ -367,6 +380,11 @@ TEST_F(ConnectionManagerUtilityTest, UseRemoteAddressWithXFFTrustedHops) { // Verify that xff_num_trusted_hops works when not using remote address. TEST_F(ConnectionManagerUtilityTest, UseXFFTrustedHopsWithoutRemoteAddress) { + // Reconfigure XFF detection. + detection_extensions_.clear(); + detection_extensions_.push_back(getXFFExtension(1)); + ON_CALL(config_, originalIpDetectionExtensions()).WillByDefault(ReturnRef(detection_extensions_)); + connection_.stream_info_.downstream_address_provider_->setRemoteAddress( std::make_shared("127.0.0.1")); ON_CALL(config_, useRemoteAddress()).WillByDefault(Return(false)); @@ -1758,5 +1776,32 @@ TEST_F(ConnectionManagerUtilityTest, NoPreserveExternalRequestIdNoEdgeRequest) { EXPECT_EQ("my-request-id", headers.get_(Headers::get().RequestId)); } } + +// Test detecting the original IP via a header (no rejection if it fails). +TEST_F(ConnectionManagerUtilityTest, OriginalIPDetectionExtension) { + const std::string header_name = "x-cdn-detected-ip"; + auto detection_extension = getCustomHeaderExtension(header_name); + const std::vector extensions = {detection_extension}; + + ON_CALL(config_, originalIpDetectionExtensions()).WillByDefault(ReturnRef(extensions)); + ON_CALL(config_, useRemoteAddress()).WillByDefault(Return(false)); + + // Header is present. + { + TestRequestHeaderMapImpl headers{{header_name, "2.1.3.4"}}; + auto ret = callMutateRequestHeaders(headers, Protocol::Http11); + EXPECT_EQ(ret.downstream_address_, "2.1.3.4:0"); + EXPECT_EQ(ret.reject_request_, absl::nullopt); + } + + // Header missing -- fallbacks to default behavior. + { + TestRequestHeaderMapImpl headers; + auto ret = callMutateRequestHeaders(headers, Protocol::Http11); + EXPECT_EQ(ret.downstream_address_, "10.0.0.3:50000"); + EXPECT_EQ(ret.reject_request_, absl::nullopt); + } +} + } // namespace Http } // namespace Envoy diff --git a/test/common/http/ip_detection_extensions.cc b/test/common/http/ip_detection_extensions.cc new file mode 100644 index 0000000000000..95c2abec84888 --- /dev/null +++ b/test/common/http/ip_detection_extensions.cc @@ -0,0 +1,25 @@ +#include "ip_detection_extensions.h" + +#include "extensions/http/original_ip_detection/custom_header/custom_header.h" +#include "extensions/http/original_ip_detection/xff/xff.h" + +namespace Envoy { + +Http::OriginalIPDetectionSharedPtr getXFFExtension(uint32_t hops) { + return std::make_shared(hops); +} + +Http::OriginalIPDetectionSharedPtr getCustomHeaderExtension(const std::string& header_name) { + return std::make_shared< + Extensions::Http::OriginalIPDetection::CustomHeader::CustomHeaderIPDetection>(header_name); +} + +Http::OriginalIPDetectionSharedPtr +getCustomHeaderExtension(const std::string& header_name, + Http::OriginalIPRejectRequestOptions reject_options) { + return std::make_shared< + Extensions::Http::OriginalIPDetection::CustomHeader::CustomHeaderIPDetection>(header_name, + reject_options); +} + +} // namespace Envoy diff --git a/test/common/http/ip_detection_extensions.h b/test/common/http/ip_detection_extensions.h new file mode 100644 index 0000000000000..32863f20a1a10 --- /dev/null +++ b/test/common/http/ip_detection_extensions.h @@ -0,0 +1,14 @@ +#pragma once + +#include "envoy/http/original_ip_detection.h" + +// This helper is used to escape namespace pollution issues. +namespace Envoy { + +Http::OriginalIPDetectionSharedPtr getXFFExtension(uint32_t hops); +Http::OriginalIPDetectionSharedPtr getCustomHeaderExtension(const std::string& header_name); +Http::OriginalIPDetectionSharedPtr +getCustomHeaderExtension(const std::string& header_name, + Http::OriginalIPRejectRequestOptions reject_options); + +} // namespace Envoy diff --git a/test/common/http/utility_test.cc b/test/common/http/utility_test.cc index fcf26b70e9c12..c8c9d311af5fc 100644 --- a/test/common/http/utility_test.cc +++ b/test/common/http/utility_test.cc @@ -454,16 +454,16 @@ TEST(HttpUtility, getLastAddressFromXFF) { {"x-forwarded-for", "192.0.2.10, 192.0.2.1, 10.0.0.1"}}; auto ret = Utility::getLastAddressFromXFF(request_headers); EXPECT_EQ(third_address, ret.address_->ip()->addressAsString()); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); ret = Utility::getLastAddressFromXFF(request_headers, 1); EXPECT_EQ(second_address, ret.address_->ip()->addressAsString()); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); ret = Utility::getLastAddressFromXFF(request_headers, 2); EXPECT_EQ(first_address, ret.address_->ip()->addressAsString()); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); ret = Utility::getLastAddressFromXFF(request_headers, 3); EXPECT_EQ(nullptr, ret.address_); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); } { const std::string first_address = "192.0.2.10"; @@ -476,64 +476,64 @@ TEST(HttpUtility, getLastAddressFromXFF) { // No space on the left. auto ret = Utility::getLastAddressFromXFF(request_headers); EXPECT_EQ(fourth_address, ret.address_->ip()->addressAsString()); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); // No space on either side. ret = Utility::getLastAddressFromXFF(request_headers, 1); EXPECT_EQ(third_address, ret.address_->ip()->addressAsString()); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); // Exercise rtrim() and ltrim(). ret = Utility::getLastAddressFromXFF(request_headers, 2); EXPECT_EQ(second_address, ret.address_->ip()->addressAsString()); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); // No space trimming. ret = Utility::getLastAddressFromXFF(request_headers, 3); EXPECT_EQ(first_address, ret.address_->ip()->addressAsString()); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); // No address found. ret = Utility::getLastAddressFromXFF(request_headers, 4); EXPECT_EQ(nullptr, ret.address_); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); } { TestRequestHeaderMapImpl request_headers{{"x-forwarded-for", ""}}; auto ret = Utility::getLastAddressFromXFF(request_headers); EXPECT_EQ(nullptr, ret.address_); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); } { TestRequestHeaderMapImpl request_headers{{"x-forwarded-for", ","}}; auto ret = Utility::getLastAddressFromXFF(request_headers); EXPECT_EQ(nullptr, ret.address_); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); } { TestRequestHeaderMapImpl request_headers{{"x-forwarded-for", ", "}}; auto ret = Utility::getLastAddressFromXFF(request_headers); EXPECT_EQ(nullptr, ret.address_); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); } { TestRequestHeaderMapImpl request_headers{{"x-forwarded-for", ", bad"}}; auto ret = Utility::getLastAddressFromXFF(request_headers); EXPECT_EQ(nullptr, ret.address_); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); } { TestRequestHeaderMapImpl request_headers; auto ret = Utility::getLastAddressFromXFF(request_headers); EXPECT_EQ(nullptr, ret.address_); - EXPECT_FALSE(ret.single_address_); + EXPECT_FALSE(ret.allow_trusted_address_checks_); } { const std::string first_address = "34.0.0.1"; TestRequestHeaderMapImpl request_headers{{"x-forwarded-for", first_address}}; auto ret = Utility::getLastAddressFromXFF(request_headers); EXPECT_EQ(first_address, ret.address_->ip()->addressAsString()); - EXPECT_TRUE(ret.single_address_); + EXPECT_TRUE(ret.allow_trusted_address_checks_); } } diff --git a/test/extensions/filters/network/http_connection_manager/BUILD b/test/extensions/filters/network/http_connection_manager/BUILD index 5124dc2fa9bbc..aa3feb45998fe 100644 --- a/test/extensions/filters/network/http_connection_manager/BUILD +++ b/test/extensions/filters/network/http_connection_manager/BUILD @@ -50,6 +50,7 @@ envoy_extension_cc_test( "//source/common/buffer:buffer_lib", "//source/extensions/access_loggers/file:config", "//source/extensions/filters/network/http_connection_manager:config", + "//source/extensions/http/original_ip_detection/custom_header:config", "//test/integration/filters:encoder_decoder_buffer_filter_lib", "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", diff --git a/test/extensions/filters/network/http_connection_manager/config_test.cc b/test/extensions/filters/network/http_connection_manager/config_test.cc index 90bc34105da58..7f4b8daec07e4 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_test.cc @@ -1782,6 +1782,89 @@ TEST_F(HttpConnectionManagerConfigTest, DefaultRequestIDExtensionWithParams) { EXPECT_FALSE(request_id_extension->packTraceReason()); } +TEST_F(HttpConnectionManagerConfigTest, UnknownOriginalIPDetectionExtension) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + route_config: + name: local_route + original_ip_detection_extensions: + - name: envoy.http.original_ip_detection.UnknownOriginalIPDetectionExtension + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + http_filters: + - name: envoy.filters.http.router + )EOF"; + + EXPECT_THROW_WITH_REGEX(createHttpConnectionManagerConfig(yaml_string), EnvoyException, + "Original IP detection extension not found: " + "'envoy.http.original_ip_detection.UnknownOriginalIPDetectionExtension'"); +} + +namespace { + +class OriginalIPDetectionExtensionNotCreatedFactory : public Http::OriginalIPDetectionFactory { +public: + Http::OriginalIPDetectionSharedPtr + createExtension(const Protobuf::Message&, Server::Configuration::FactoryContext&) override { + return nullptr; + } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() const override { + return "envoy.http.original_ip_detection.OriginalIPDetectionExtensionNotCreated"; + } +}; + +} // namespace + +TEST_F(HttpConnectionManagerConfigTest, OriginalIPDetectionExtensionNotCreated) { + OriginalIPDetectionExtensionNotCreatedFactory factory; + Registry::InjectFactory registration(factory); + + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + route_config: + name: local_route + original_ip_detection_extensions: + - name: envoy.http.original_ip_detection.OriginalIPDetectionExtensionNotCreated + typed_config: + "@type": type.googleapis.com/google.protobuf.UInt32Value + http_filters: + - name: envoy.filters.http.router + )EOF"; + + EXPECT_THROW_WITH_REGEX( + createHttpConnectionManagerConfig(yaml_string), EnvoyException, + "Original IP detection extension could not be created: " + "'envoy.http.original_ip_detection.OriginalIPDetectionExtensionNotCreated'"); +} + +TEST_F(HttpConnectionManagerConfigTest, OriginalIPDetectionExtension) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + route_config: + name: local_route + original_ip_detection_extensions: + - name: envoy.http.original_ip_detection.custom_header + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.original_ip_detection.custom_header.v3.CustomHeaderConfig + header_name: x-ip-header + http_filters: + - name: envoy.filters.http.router + )EOF"; + + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, http_tracer_manager_, + filter_config_provider_manager_); + + const auto& original_ip_detection_extensions = config.originalIpDetectionExtensions(); + EXPECT_EQ(1, original_ip_detection_extensions.size()); +} + TEST_F(HttpConnectionManagerConfigTest, DynamicFilterWarmingNoDefault) { const std::string yaml_string = R"EOF( codec_type: http1 diff --git a/test/extensions/http/original_ip_detection/custom_header/BUILD b/test/extensions/http/original_ip_detection/custom_header/BUILD new file mode 100644 index 0000000000000..7a02e5ec778f1 --- /dev/null +++ b/test/extensions/http/original_ip_detection/custom_header/BUILD @@ -0,0 +1,38 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "custom_header_detection_test", + srcs = ["custom_header_test.cc"], + extension_name = "envoy.http.original_ip_detection.custom_header", + deps = [ + "//source/common/network:utility_lib", + "//source/extensions/http/original_ip_detection/custom_header:custom_header_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/http/original_ip_detection/custom_header/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_name = "envoy.http.original_ip_detection.custom_header", + deps = [ + "//include/envoy/registry", + "//source/extensions/http/original_ip_detection/custom_header:config", + "//source/extensions/http/original_ip_detection/custom_header:custom_header_lib", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/http/original_ip_detection/custom_header/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/http/original_ip_detection/custom_header/config_test.cc b/test/extensions/http/original_ip_detection/custom_header/config_test.cc new file mode 100644 index 0000000000000..54e42644c053b --- /dev/null +++ b/test/extensions/http/original_ip_detection/custom_header/config_test.cc @@ -0,0 +1,59 @@ +#include "envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.pb.h" +#include "envoy/registry/registry.h" + +#include "extensions/http/original_ip_detection/custom_header/config.h" + +#include "test/mocks/server/factory_context.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace OriginalIPDetection { +namespace CustomHeader { + +TEST(CustomHeaderFactoryTest, Basic) { + auto* factory = Registry::FactoryRegistry::getFactory( + "envoy.http.original_ip_detection.custom_header"); + ASSERT_NE(factory, nullptr); + + envoy::config::core::v3::TypedExtensionConfig typed_config; + const std::string yaml = R"EOF( + name: envoy.formatter.TestFormatter + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.original_ip_detection.custom_header.v3.CustomHeaderConfig + header_name: x-real-ip +)EOF"; + TestUtility::loadFromYaml(yaml, typed_config); + + NiceMock context; + EXPECT_NE(factory->createExtension(typed_config.typed_config(), context), nullptr); +} + +TEST(CustomHeaderFactoryTest, InvalidHeaderName) { + auto* factory = Registry::FactoryRegistry::getFactory( + "envoy.http.original_ip_detection.custom_header"); + ASSERT_NE(factory, nullptr); + + envoy::config::core::v3::TypedExtensionConfig typed_config; + const std::string yaml = R"EOF( + name: envoy.formatter.TestFormatter + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.original_ip_detection.custom_header.v3.CustomHeaderConfig + header_name: " " +)EOF"; + TestUtility::loadFromYaml(yaml, typed_config); + + NiceMock context; + EXPECT_THROW_WITH_REGEX(factory->createExtension(typed_config.typed_config(), context), + EnvoyException, + "Proto constraint validation failed.*does not match regex pattern.*"); +} + +} // namespace CustomHeader +} // namespace OriginalIPDetection +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/http/original_ip_detection/custom_header/custom_header_test.cc b/test/extensions/http/original_ip_detection/custom_header/custom_header_test.cc new file mode 100644 index 0000000000000..e1a14c133d73d --- /dev/null +++ b/test/extensions/http/original_ip_detection/custom_header/custom_header_test.cc @@ -0,0 +1,107 @@ +#include "envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.pb.h" + +#include "common/network/utility.h" + +#include "extensions/http/original_ip_detection/custom_header/custom_header.h" + +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace OriginalIPDetection { +namespace CustomHeader { + +class CustomHeaderTest : public testing::Test { +protected: + CustomHeaderTest() { configure(); } + + void configure(envoy::type::v3::StatusCode code = envoy::type::v3::StatusCode::Unauthorized) { + envoy::extensions::http::original_ip_detection::custom_header::v3::CustomHeaderConfig config; + config.set_header_name("x-real-ip"); + config.set_allow_extension_to_set_address_as_trusted(true); + auto* reject_with_status = config.mutable_reject_with_status(); + reject_with_status->set_code(code); + custom_header_extension_ = std::make_shared(config); + } + + std::shared_ptr custom_header_extension_; +}; + +TEST_F(CustomHeaderTest, Detection) { + // Header missing. + { + Envoy::Http::TestRequestHeaderMapImpl headers{{"x-other", "abc"}}; + Envoy::Http::OriginalIPDetectionParams params = {headers, nullptr}; + auto result = custom_header_extension_->detect(params); + + EXPECT_EQ(nullptr, result.detected_remote_address); + EXPECT_FALSE(result.allow_trusted_address_checks); + EXPECT_TRUE(result.reject_options.has_value()); + + const auto& reject_options = result.reject_options.value(); + EXPECT_EQ(reject_options.response_code, Envoy::Http::Code::Unauthorized); + EXPECT_EQ(reject_options.body, ""); + } + + // Bad IP in the header. + { + Envoy::Http::TestRequestHeaderMapImpl headers{{"x-real-ip", "not-a-real-ip"}}; + Envoy::Http::OriginalIPDetectionParams params = {headers, nullptr}; + auto result = custom_header_extension_->detect(params); + + EXPECT_EQ(nullptr, result.detected_remote_address); + EXPECT_FALSE(result.allow_trusted_address_checks); + EXPECT_TRUE(result.reject_options.has_value()); + + const auto& reject_options = result.reject_options.value(); + EXPECT_EQ(reject_options.response_code, Envoy::Http::Code::Unauthorized); + EXPECT_EQ(reject_options.body, ""); + } + + // Good IPv4. + { + Envoy::Http::TestRequestHeaderMapImpl headers{{"x-real-ip", "1.2.3.4"}}; + Envoy::Http::OriginalIPDetectionParams params = {headers, nullptr}; + auto result = custom_header_extension_->detect(params); + + EXPECT_EQ("1.2.3.4:0", result.detected_remote_address->asString()); + EXPECT_TRUE(result.allow_trusted_address_checks); + EXPECT_FALSE(result.reject_options.has_value()); + } + + // Good IPv6. + { + Envoy::Http::TestRequestHeaderMapImpl headers{{"x-real-ip", "fc00::1"}}; + Envoy::Http::OriginalIPDetectionParams params = {headers, nullptr}; + auto result = custom_header_extension_->detect(params); + + EXPECT_EQ("[fc00::1]:0", result.detected_remote_address->asString()); + EXPECT_TRUE(result.allow_trusted_address_checks); + EXPECT_FALSE(result.reject_options.has_value()); + } +} + +TEST_F(CustomHeaderTest, FallbacksToDefaultResponseCode) { + configure(envoy::type::v3::StatusCode::OK); + + Envoy::Http::TestRequestHeaderMapImpl headers{{"x-other", "abc"}}; + Envoy::Http::OriginalIPDetectionParams params = {headers, nullptr}; + auto result = custom_header_extension_->detect(params); + + EXPECT_EQ(nullptr, result.detected_remote_address); + EXPECT_FALSE(result.allow_trusted_address_checks); + EXPECT_TRUE(result.reject_options.has_value()); + + const auto& reject_options = result.reject_options.value(); + EXPECT_EQ(reject_options.response_code, Envoy::Http::Code::Forbidden); + EXPECT_EQ(reject_options.body, ""); +} + +} // namespace CustomHeader +} // namespace OriginalIPDetection +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/http/original_ip_detection/xff/BUILD b/test/extensions/http/original_ip_detection/xff/BUILD new file mode 100644 index 0000000000000..14d79ac5cb50d --- /dev/null +++ b/test/extensions/http/original_ip_detection/xff/BUILD @@ -0,0 +1,38 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "xff_detection_test", + srcs = ["xff_test.cc"], + extension_name = "envoy.http.original_ip_detection.xff", + deps = [ + "//source/common/http:utility_lib", + "//source/extensions/http/original_ip_detection/xff:xff_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/http/original_ip_detection/xff/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_name = "envoy.http.original_ip_detection.xff", + deps = [ + "//include/envoy/registry", + "//source/extensions/http/original_ip_detection/xff:config", + "//source/extensions/http/original_ip_detection/xff:xff_lib", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/http/original_ip_detection/xff/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/http/original_ip_detection/xff/config_test.cc b/test/extensions/http/original_ip_detection/xff/config_test.cc new file mode 100644 index 0000000000000..0f7f1ace9632d --- /dev/null +++ b/test/extensions/http/original_ip_detection/xff/config_test.cc @@ -0,0 +1,39 @@ +#include "envoy/extensions/http/original_ip_detection/xff/v3/xff.pb.h" +#include "envoy/registry/registry.h" + +#include "extensions/http/original_ip_detection/xff/config.h" + +#include "test/mocks/server/factory_context.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace OriginalIPDetection { +namespace Xff { + +TEST(CustomHeaderFactoryTest, Basic) { + auto* factory = Registry::FactoryRegistry::getFactory( + "envoy.http.original_ip_detection.xff"); + ASSERT_NE(factory, nullptr); + + envoy::config::core::v3::TypedExtensionConfig typed_config; + const std::string yaml = R"EOF( + name: envoy.formatter.TestFormatter + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.original_ip_detection.xff.v3.XffConfig + xff_num_trusted_hops: 1 +)EOF"; + TestUtility::loadFromYaml(yaml, typed_config); + + NiceMock context; + EXPECT_NE(factory->createExtension(typed_config.typed_config(), context), nullptr); +} + +} // namespace Xff +} // namespace OriginalIPDetection +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/http/original_ip_detection/xff/xff_test.cc b/test/extensions/http/original_ip_detection/xff/xff_test.cc new file mode 100644 index 0000000000000..f03ac1a307ca7 --- /dev/null +++ b/test/extensions/http/original_ip_detection/xff/xff_test.cc @@ -0,0 +1,52 @@ +#include "envoy/extensions/http/original_ip_detection/xff/v3/xff.pb.h" + +#include "extensions/http/original_ip_detection/xff/xff.h" + +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace OriginalIPDetection { +namespace Xff { + +class XffTest : public testing::Test { +protected: + XffTest() { + envoy::extensions::http::original_ip_detection::xff::v3::XffConfig config; + config.set_xff_num_trusted_hops(1); + xff_extension_ = std::make_shared(config); + } + + std::shared_ptr xff_extension_; +}; + +TEST_F(XffTest, Detection) { + // Header missing. + { + Envoy::Http::TestRequestHeaderMapImpl headers{{"x-other", "abc"}}; + Envoy::Http::OriginalIPDetectionParams params = {headers, nullptr}; + auto result = xff_extension_->detect(params); + + EXPECT_EQ(nullptr, result.detected_remote_address); + EXPECT_FALSE(result.allow_trusted_address_checks); + } + + // Good request. + { + Envoy::Http::TestRequestHeaderMapImpl headers{{"x-forwarded-for", "1.2.3.4,2.2.2.2"}}; + Envoy::Http::OriginalIPDetectionParams params = {headers, nullptr}; + auto result = xff_extension_->detect(params); + + EXPECT_EQ("1.2.3.4:0", result.detected_remote_address->asString()); + EXPECT_FALSE(result.allow_trusted_address_checks); + } +} + +} // namespace Xff +} // namespace OriginalIPDetection +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/test/integration/BUILD b/test/integration/BUILD index 014e08b26d0ef..3648cea48ffaa 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -299,6 +299,7 @@ envoy_cc_test( ":http_integration_lib", "//source/common/config:api_version_lib", "//source/common/protobuf", + "//source/extensions/http/original_ip_detection/xff:config", "//test/test_common:utility_lib", "@envoy_api//envoy/api/v2:pkg_cc_proto", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", @@ -1730,3 +1731,16 @@ envoy_cc_test( "@com_googlesource_quiche//:quic_test_tools_session_peer_lib", ]), ) + +envoy_cc_test( + name = "original_ip_detection_integration_test", + srcs = [ + "original_ip_detection_integration_test.cc", + ], + deps = [ + ":http_integration_lib", + "//source/extensions/http/original_ip_detection/custom_header:config", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/http/original_ip_detection/custom_header/v3:pkg_cc_proto", + ], +) diff --git a/test/integration/header_integration_test.cc b/test/integration/header_integration_test.cc index 0045426314907..073c05bf097b2 100644 --- a/test/integration/header_integration_test.cc +++ b/test/integration/header_integration_test.cc @@ -42,7 +42,11 @@ const std::string http_connection_mgr_config = R"EOF( - name: envoy.filters.http.router codec_type: HTTP1 use_remote_address: false -xff_num_trusted_hops: 1 +original_ip_detection_extensions: +- name: envoy.http.original_ip_detection.xff + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.original_ip_detection.xff.v3.XffConfig + xff_num_trusted_hops: 1 stat_prefix: header_test route_config: virtual_hosts: diff --git a/test/integration/original_ip_detection_integration_test.cc b/test/integration/original_ip_detection_integration_test.cc new file mode 100644 index 0000000000000..9fa25edce17e2 --- /dev/null +++ b/test/integration/original_ip_detection_integration_test.cc @@ -0,0 +1,55 @@ +#include "envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.pb.h" + +#include "test/integration/http_integration.h" +#include "test/test_common/registry.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +using testing::HasSubstr; + +namespace Envoy { +namespace Formatter { + +class OriginalIPDetectionIntegrationTest + : public testing::TestWithParam, + public HttpIntegrationTest { +public: + OriginalIPDetectionIntegrationTest() + : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, GetParam()) {} + + void runTest(const std::string& ip) { + useAccessLog("%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"); + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { + envoy::extensions::http::original_ip_detection::custom_header::v3::CustomHeaderConfig + config; + config.set_header_name("x-cdn-detected-ip"); + + auto* extension = hcm.add_original_ip_detection_extensions(); + extension->set_name("envoy.http.original_ip_detection.custom_header"); + extension->mutable_typed_config()->PackFrom(config); + + hcm.mutable_use_remote_address()->set_value(false); + }); + initialize(); + auto raw_http = + fmt::format("GET / HTTP/1.1\r\nHost: host\r\nx-cdn-detected-ip: {}\r\n\r\n", ip); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), raw_http.c_str(), &response, true); + std::string log = waitForAccessLog(access_log_name_); + EXPECT_THAT(log, HasSubstr(ip)); + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, OriginalIPDetectionIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(OriginalIPDetectionIntegrationTest, HeaderBasedDetectionIPv4) { runTest("9.9.9.9"); } + +TEST_P(OriginalIPDetectionIntegrationTest, HeaderBasedDetectionIPv6) { runTest("fc00::1"); } + +} // namespace Formatter +} // namespace Envoy diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index 31a40da3a3540..da5cb0b8a4979 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -589,6 +589,8 @@ class MockConnectionManagerConfig : public ConnectionManagerConfig { MOCK_METHOD(envoy::extensions::filters::network::http_connection_manager::v3:: HttpConnectionManager::PathWithEscapedSlashesAction, pathWithEscapedSlashesAction, (), (const)); + MOCK_METHOD(const std::vector&, originalIpDetectionExtensions, + (), (const)); std::unique_ptr internal_address_config_ = std::make_unique(); From 41c1da44008ea31969dc604c17ab6efd3b9e70a6 Mon Sep 17 00:00:00 2001 From: Long Dai Date: Mon, 17 May 2021 14:25:44 +0800 Subject: [PATCH 209/209] docs: comment config extension (#16406) * docs: comment config extension Signed-off-by: Long Dai --- docs/root/start/quick-start/run-envoy.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/root/start/quick-start/run-envoy.rst b/docs/root/start/quick-start/run-envoy.rst index 3d6c7b8c103b8..dcdba9bcd5b46 100644 --- a/docs/root/start/quick-start/run-envoy.rst +++ b/docs/root/start/quick-start/run-envoy.rst @@ -82,6 +82,9 @@ Run Envoy with the demo configuration The ``-c`` or ``--config-path`` flag tells Envoy the path to its initial configuration. +Envoy will parse the config file according to the file extension, please see the +:option:`config path command line option <-c>` for further information. + .. tabs:: .. tab:: System