Automatically send PING acks by default in OgHttp2Session.
This CL allows OgHttp2Session to automatically enqueue PING ack frames in
response to receiving non-ack PINGs from its peer. Note that OgHttp2Session
already automatically enqueues SETTINGS ack frames:
http://google3/third_party/http2/adapter/oghttp2_session.cc;l=843-845;rcl=403185538.
This PING ack addition aligns OgHttp2Session with default nghttp2 behavior.
This CL also adds an option to OgHttp2Session::Options to disable automatic
PING acks, analogous to nghttp2_option_set_no_auto_ping_ack().
The default automatic PING acks are helpful in Envoy's frame flood detection
tests. With this CL, the following four tests now pass with oghttp2 migration
cl/392724171, as hypothesized/hoped for in http://b/201799377#comment2:
- Http2FloodMitigationTest.UpstreamFloodDetectionIsOnByDefault
http://sponge2/c75a3080-9839-4d4c-8ed6-4ad33a1d4aa5
- Http2FloodMitigationTest.UpstreamPingFlood
http://sponge2/64cb3358-31f5-4b6e-b94c-bd7082c49fd4
- Http2FloodMitigationTest.UpstreamRstStreamOnStreamIdleTimeout
http://sponge2/bb3585d3-ed50-4b2b-8d87-9123b6baa9ba
- Http2FloodMitigationTest.UpstreamRstStreamOnDownstreamRemoteClose
http://sponge2/34466067-2445-45ae-8cf8-61924a0f8d85
PiperOrigin-RevId: 405915725
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index 8e3aa6d..03e2629 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -6,6 +6,7 @@
#include "http2/adapter/oghttp2_util.h"
#include "http2/adapter/test_frame_sequence.h"
#include "http2/adapter/test_utils.h"
+#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
#include "common/platform/api/quiche_test.h"
namespace http2 {
@@ -2691,6 +2692,76 @@
EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY}));
}
+TEST(NgHttp2AdapterTest, AutomaticSettingsAndPingAcks) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+ const std::string frames =
+ TestFrameSequence().ClientPreface().Ping(42).Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // PING
+ EXPECT_CALL(visitor, OnFrameHeader(0, _, PING, 0));
+ EXPECT_CALL(visitor, OnPing(42, false));
+
+ const int64_t read_result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(static_cast<size_t>(read_result), frames.size());
+
+ EXPECT_TRUE(adapter->want_write());
+
+ // Server preface does not appear to include the mandatory SETTINGS frame.
+ // SETTINGS ack
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ // PING ack
+ EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x1, 0));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::PING}));
+}
+
+TEST(NgHttp2AdapterTest, AutomaticPingAcksDisabled) {
+ DataSavingVisitor visitor;
+ nghttp2_option* options;
+ nghttp2_option_new(&options);
+ nghttp2_option_set_no_auto_ping_ack(options, 1);
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor, options);
+ nghttp2_option_del(options);
+
+ const std::string frames =
+ TestFrameSequence().ClientPreface().Ping(42).Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // PING
+ EXPECT_CALL(visitor, OnFrameHeader(0, _, PING, 0));
+ EXPECT_CALL(visitor, OnPing(42, false));
+
+ const int64_t read_result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(static_cast<size_t>(read_result), frames.size());
+
+ EXPECT_TRUE(adapter->want_write());
+
+ // Server preface does not appear to include the mandatory SETTINGS frame.
+ // SETTINGS ack
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ // No PING ack expected because automatic PING acks are disabled.
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+}
+
} // namespace
} // namespace test
} // namespace adapter
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index 4b19874..2fe13fd 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -120,6 +120,79 @@
}
}
+TEST_F(OgHttp2AdapterTest, AutomaticSettingsAndPingAcks) {
+ const std::string frames =
+ TestFrameSequence().ClientPreface().Ping(42).Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(http2_visitor_, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(http2_visitor_, OnSettingsStart());
+ EXPECT_CALL(http2_visitor_, OnSettingsEnd());
+ // PING
+ EXPECT_CALL(http2_visitor_, OnFrameHeader(0, _, PING, 0));
+ EXPECT_CALL(http2_visitor_, OnPing(42, false));
+
+ const int64_t read_result = adapter_->ProcessBytes(frames);
+ EXPECT_EQ(static_cast<size_t>(read_result), frames.size());
+
+ EXPECT_TRUE(adapter_->want_write());
+
+ // Server preface (SETTINGS)
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ // SETTINGS ack
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ // PING ack
+ EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(PING, 0, _, 0x1));
+ EXPECT_CALL(http2_visitor_, OnFrameSent(PING, 0, _, 0x1, 0));
+
+ int send_result = adapter_->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(
+ http2_visitor_.data(),
+ EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::PING}));
+}
+
+TEST_F(OgHttp2AdapterTest, AutomaticPingAcksDisabled) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kServer,
+ .auto_ping_ack = false};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ const std::string frames =
+ TestFrameSequence().ClientPreface().Ping(42).Serialize();
+ testing::InSequence s;
+
+ // Client preface (empty SETTINGS)
+ EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0));
+ EXPECT_CALL(visitor, OnSettingsStart());
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ // PING
+ EXPECT_CALL(visitor, OnFrameHeader(0, _, PING, 0));
+ EXPECT_CALL(visitor, OnPing(42, false));
+
+ const int64_t read_result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(static_cast<size_t>(read_result), frames.size());
+
+ EXPECT_TRUE(adapter->want_write());
+
+ // Server preface (SETTINGS)
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ // SETTINGS ack
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ // No PING ack expected because automatic PING acks are disabled.
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::SETTINGS}));
+}
+
TEST(OgHttp2AdapterClientTest, ClientHandlesTrailers) {
DataSavingVisitor visitor;
OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index dd1a987..fe84230 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -851,6 +851,11 @@
void OgHttp2Session::OnPing(spdy::SpdyPingId unique_id, bool is_ack) {
visitor_.OnPing(unique_id, is_ack);
+ if (options_.auto_ping_ack && !is_ack) {
+ auto ping = absl::make_unique<spdy::SpdyPingIR>(unique_id);
+ ping->set_is_ack(true);
+ EnqueueFrame(std::move(ping));
+ }
}
void OgHttp2Session::OnGoAway(spdy::SpdyStreamId last_accepted_stream_id,
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index 40a142b..3baf7dd 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -32,6 +32,8 @@
public:
struct QUICHE_EXPORT_PRIVATE Options {
Perspective perspective = Perspective::kClient;
+ // Whether to automatically send PING acks when receiving a PING.
+ bool auto_ping_ack = true;
};
OgHttp2Session(Http2VisitorInterface& visitor, Options options);
diff --git a/http2/adapter/oghttp2_session_test.cc b/http2/adapter/oghttp2_session_test.cc
index 8951273..b4b464b 100644
--- a/http2/adapter/oghttp2_session_test.cc
+++ b/http2/adapter/oghttp2_session_test.cc
@@ -611,14 +611,19 @@
EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x1, 0));
// Some bytes should have been serialized.
int send_result = session.Send();
EXPECT_EQ(0, send_result);
- // Initial SETTINGS, SETTINGS ack.
- // TODO(birenroy): automatically queue PING acks.
- EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
- spdy::SpdyFrameType::SETTINGS}));
+ // Initial SETTINGS, SETTINGS ack, and PING acks (for PING IDs 42 and 47).
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames(
+ {spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::PING, spdy::SpdyFrameType::PING}));
}
// Verifies that a server session enqueues initial SETTINGS before whatever