Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions source/common/http/http2/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 14 additions & 0 deletions source/common/http/http2/codec_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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_);
}

Expand All @@ -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<KeepaliveObserver>(kKeepaliveObserverFilterStateKey);
}

return nullptr;
}

bool ConnectionImpl::slowContainsStreamId(int32_t stream_id) const {
for (const auto& stream : active_streams_) {
if (stream->stream_id_ == stream_id) {
Expand Down Expand Up @@ -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();
}
Expand Down
2 changes: 2 additions & 0 deletions source/common/http/http2/codec_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;

Expand Down
32 changes: 32 additions & 0 deletions source/common/http/http2/keepalive_observer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <cstdint>

#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<uint64_t> pendingKeepalivePingId() const PURE;
};

} // namespace Http2
} // namespace Http
} // namespace Envoy
38 changes: 38 additions & 0 deletions test/common/http/http2/codec_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint64_t>, pendingKeepalivePingId, (), (const, override));
};
} // namespace

enum class Http2Impl {
Expand Down Expand Up @@ -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<NiceMock<MockKeepaliveObserver>>();
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<Event::MockTimer>(&client_connection_.dispatcher_);
auto send_timer = new NiceMock<Event::MockTimer>(&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;
Expand Down
Loading