diff --git a/source/common/http/http2/BUILD b/source/common/http/http2/BUILD index da5201ffc2b23..2e22a964d674d 100644 --- a/source/common/http/http2/BUILD +++ b/source/common/http/http2/BUILD @@ -24,12 +24,21 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "keepalive_observer_lib", + hdrs = ["keepalive_observer.h"], + deps = [ + "//envoy/stream_info:stream_info_interface", + ], +) + envoy_cc_library( name = "codec_lib", srcs = ["codec_impl.cc"], hdrs = ["codec_impl.h"], deps = [ ":codec_stats_lib", + ":keepalive_observer_lib", ":metadata_decoder_lib", ":metadata_encoder_lib", ":protocol_constraints_lib", diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index 3752eaf92f4d0..501f60965b038 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -1036,6 +1036,9 @@ void ConnectionImpl::sendKeepalive() { // Intended to check through coverage that this error case is tested return; } + if (const auto* observer = keepaliveObserver(); observer != nullptr) { + observer->onKeepalivePingSent(connection_.streamInfo(), ms_since_epoch); + } keepalive_timeout_timer_->enableTimer(keepalive_timeout_); } @@ -1062,6 +1065,14 @@ void ConnectionImpl::onKeepaliveResponseTimeout() { StreamInfo::LocalCloseReasons::get().Http2PingTimeout); } +const KeepaliveObserver* ConnectionImpl::keepaliveObserver() const { + if (const auto& filter_state = connection_.streamInfo().filterState(); filter_state != nullptr) { + return filter_state->getDataReadOnly(kKeepaliveObserverFilterStateKey); + } + + return nullptr; +} + bool ConnectionImpl::slowContainsStreamId(int32_t stream_id) const { for (const auto& stream : active_streams_) { if (stream->stream_id_ == stream_id) { @@ -1228,6 +1239,9 @@ Status ConnectionImpl::onPing(uint64_t opaque_data, bool is_ack) { if (is_ack) { ENVOY_CONN_LOG(trace, "recv PING ACK {}", connection_, opaque_data); + if (const auto* observer = keepaliveObserver(); observer != nullptr) { + observer->onKeepalivePingAck(connection_.streamInfo(), opaque_data); + } onKeepaliveResponse(); } diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index 20a620efdc2d9..a49fcda608708 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -24,6 +24,7 @@ #include "source/common/http/codec_helper.h" #include "source/common/http/header_map_impl.h" #include "source/common/http/http2/codec_stats.h" +#include "source/common/http/http2/keepalive_observer.h" #include "source/common/http/http2/metadata_decoder.h" #include "source/common/http/http2/metadata_encoder.h" #include "source/common/http/http2/protocol_constraints.h" @@ -798,6 +799,7 @@ class ConnectionImpl : public virtual Connection, uint32_t padding_length); void onKeepaliveResponse(); void onKeepaliveResponseTimeout(); + const KeepaliveObserver* keepaliveObserver() const; bool slowContainsStreamId(int32_t stream_id) const; virtual StreamResetReason getMessagingErrorResetReason() const PURE; diff --git a/source/common/http/http2/keepalive_observer.h b/source/common/http/http2/keepalive_observer.h new file mode 100644 index 0000000000000..521c4481c7f12 --- /dev/null +++ b/source/common/http/http2/keepalive_observer.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "envoy/common/pure.h" +#include "envoy/stream_info/filter_state.h" +#include "envoy/stream_info/stream_info.h" + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +namespace Envoy { +namespace Http { +namespace Http2 { + +inline constexpr absl::string_view kKeepaliveObserverFilterStateKey = + "envoy.http2.keepalive_observer"; + +class KeepaliveObserver : public StreamInfo::FilterState::Object { +public: + ~KeepaliveObserver() override = default; + + virtual void onKeepalivePingSent(StreamInfo::StreamInfo& stream_info, + uint64_t opaque_data) const PURE; + virtual void onKeepalivePingAck(StreamInfo::StreamInfo& stream_info, + uint64_t opaque_data) const PURE; + virtual absl::optional pendingKeepalivePingId() const PURE; +}; + +} // namespace Http2 +} // namespace Http +} // namespace Envoy diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index 5b139938a6feb..013ae051a39b8 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -111,6 +111,13 @@ class MockRequestDecoderShimWithUhv : public Http::MockRequestDecoder { Http::ServerHeaderValidator* header_validator_{nullptr}; Http::ResponseEncoder* response_encoder_{nullptr}; }; + +class MockKeepaliveObserver : public KeepaliveObserver { +public: + MOCK_METHOD(void, onKeepalivePingSent, (StreamInfo::StreamInfo&, uint64_t), (const, override)); + MOCK_METHOD(void, onKeepalivePingAck, (StreamInfo::StreamInfo&, uint64_t), (const, override)); + MOCK_METHOD(absl::optional, pendingKeepalivePingId, (), (const, override)); +}; } // namespace enum class Http2Impl { @@ -1376,6 +1383,37 @@ TEST_P(Http2CodecImplTest, ConnectionKeepalive) { timeout_timer->invokeCallback(); } +TEST_P(Http2CodecImplTest, ConnectionKeepaliveObserver) { + constexpr uint32_t interval_ms = 100; + constexpr uint32_t timeout_ms = 200; + client_http2_options_.mutable_connection_keepalive()->mutable_interval()->set_nanos(interval_ms * + 1000 * 1000); + client_http2_options_.mutable_connection_keepalive()->mutable_timeout()->set_nanos(timeout_ms * + 1000 * 1000); + client_http2_options_.mutable_connection_keepalive()->mutable_interval_jitter()->set_value(0); + + auto observer = std::make_shared>(); + ON_CALL(*observer, pendingKeepalivePingId()).WillByDefault(Return(absl::nullopt)); + client_connection_.streamInfo().filterState()->setData( + kKeepaliveObserverFilterStateKey, observer, StreamInfo::FilterState::StateType::ReadOnly, + StreamInfo::FilterState::LifeSpan::Connection); + + auto timeout_timer = new NiceMock(&client_connection_.dispatcher_); + auto send_timer = new NiceMock(&client_connection_.dispatcher_); + EXPECT_CALL(*timeout_timer, disableTimer()); + EXPECT_CALL(*send_timer, enableTimer(std::chrono::milliseconds(interval_ms), _)); + initialize(); + + testing::InSequence sequence; + EXPECT_CALL(*observer, onKeepalivePingSent(_, _)); + EXPECT_CALL(*timeout_timer, enableTimer(std::chrono::milliseconds(timeout_ms), _)); + EXPECT_CALL(*observer, onKeepalivePingAck(_, _)); + EXPECT_CALL(*timeout_timer, disableTimer()); + EXPECT_CALL(*send_timer, enableTimer(std::chrono::milliseconds(interval_ms), _)); + send_timer->invokeCallback(); + driveToCompletion(); +} + // Verify that extending the timeout is performed when a frame is received. TEST_P(Http2CodecImplTest, KeepaliveTimeoutDelay) { constexpr uint32_t interval_ms = 100;