Skip to content

Commit

Permalink
[test] Http2Frame support for client and general stream ids (envoypro…
Browse files Browse the repository at this point in the history
…xy#13310)

* [test] Http2Frame support both client and non-client stream ids in Http2Frame

Signed-off-by: Adi Suissa-Peleg <[email protected]>
  • Loading branch information
adisuissa authored Sep 30, 2020
1 parent aa68fac commit 52161ce
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 49 deletions.
2 changes: 1 addition & 1 deletion test/common/http/http2/codec_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ class Http2CodecImplTest : public ::testing::TestWithParam<Http2SettingsTestPara

// HTTP/2 codec does not send empty DATA frames with no END_STREAM flag.
// To make this work, send raw bytes representing empty DATA frames bypassing client codec.
Http2Frame emptyDataFrame = Http2Frame::makeEmptyDataFrame(0);
Http2Frame emptyDataFrame = Http2Frame::makeEmptyDataFrame(Http2Frame::makeClientStreamId(0));
constexpr uint32_t max_allowed =
CommonUtility::OptionsLimits::DEFAULT_MAX_CONSECUTIVE_INBOUND_FRAMES_WITH_EMPTY_PAYLOAD;
for (uint32_t i = 0; i < max_allowed + 1; ++i) {
Expand Down
39 changes: 21 additions & 18 deletions test/common/http/http2/http2_frame.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

namespace {

// Make request stream ID in the network byte order
uint32_t makeRequestStreamId(uint32_t stream_id) { return htonl((stream_id << 1) | 1); }
// Converts stream ID to the network byte order. Supports all values in the range [0, 2^30).
uint32_t makeNetworkOrderStreamId(uint32_t stream_id) { return htonl(stream_id); }

// All this templatized stuff is for the typesafe constexpr bitwise ORing of the "enum class" values
template <typename First, typename... Rest> struct FirstArgType {
Expand Down Expand Up @@ -147,28 +147,30 @@ Http2Frame Http2Frame::makeEmptySettingsFrame(SettingsFlags flags) {
Http2Frame Http2Frame::makeEmptyHeadersFrame(uint32_t stream_index, HeadersFlags flags) {
Http2Frame frame;
frame.buildHeader(Type::Headers, 0, static_cast<uint8_t>(flags),
makeRequestStreamId(stream_index));
makeNetworkOrderStreamId(stream_index));
return frame;
}

Http2Frame Http2Frame::makeEmptyContinuationFrame(uint32_t stream_index, HeadersFlags flags) {
Http2Frame frame;
frame.buildHeader(Type::Continuation, 0, static_cast<uint8_t>(flags),
makeRequestStreamId(stream_index));
makeNetworkOrderStreamId(stream_index));
return frame;
}

Http2Frame Http2Frame::makeEmptyDataFrame(uint32_t stream_index, DataFlags flags) {
Http2Frame frame;
frame.buildHeader(Type::Data, 0, static_cast<uint8_t>(flags), makeRequestStreamId(stream_index));
frame.buildHeader(Type::Data, 0, static_cast<uint8_t>(flags),
makeNetworkOrderStreamId(stream_index));
return frame;
}

Http2Frame Http2Frame::makePriorityFrame(uint32_t stream_index, uint32_t dependent_index) {
static constexpr size_t kPriorityPayloadSize = 5;
Http2Frame frame;
frame.buildHeader(Type::Priority, kPriorityPayloadSize, 0, makeRequestStreamId(stream_index));
const uint32_t dependent_net = makeRequestStreamId(dependent_index);
frame.buildHeader(Type::Priority, kPriorityPayloadSize, 0,
makeNetworkOrderStreamId(stream_index));
const uint32_t dependent_net = makeNetworkOrderStreamId(dependent_index);
ASSERT(frame.data_.capacity() >= HeaderSize + sizeof(uint32_t));
memcpy(&frame.data_[HeaderSize], reinterpret_cast<const void*>(&dependent_net), sizeof(uint32_t));
return frame;
Expand All @@ -180,8 +182,8 @@ Http2Frame Http2Frame::makeEmptyPushPromiseFrame(uint32_t stream_index,
static constexpr size_t kEmptyPushPromisePayloadSize = 4;
Http2Frame frame;
frame.buildHeader(Type::PushPromise, kEmptyPushPromisePayloadSize, static_cast<uint8_t>(flags),
makeRequestStreamId(stream_index));
const uint32_t promised_stream_id = makeRequestStreamId(promised_stream_index);
makeNetworkOrderStreamId(stream_index));
const uint32_t promised_stream_id = makeNetworkOrderStreamId(promised_stream_index);
ASSERT(frame.data_.capacity() >= HeaderSize + sizeof(uint32_t));
memcpy(&frame.data_[HeaderSize], reinterpret_cast<const void*>(&promised_stream_id),
sizeof(uint32_t));
Expand All @@ -191,7 +193,8 @@ Http2Frame Http2Frame::makeEmptyPushPromiseFrame(uint32_t stream_index,
Http2Frame Http2Frame::makeResetStreamFrame(uint32_t stream_index, ErrorCode error_code) {
static constexpr size_t kResetStreamPayloadSize = 4;
Http2Frame frame;
frame.buildHeader(Type::RstStream, kResetStreamPayloadSize, 0, makeRequestStreamId(stream_index));
frame.buildHeader(Type::RstStream, kResetStreamPayloadSize, 0,
makeNetworkOrderStreamId(stream_index));
const uint32_t error = static_cast<uint32_t>(error_code);
ASSERT(frame.data_.capacity() >= HeaderSize + sizeof(uint32_t));
memcpy(&frame.data_[HeaderSize], reinterpret_cast<const void*>(&error), sizeof(uint32_t));
Expand All @@ -201,8 +204,8 @@ Http2Frame Http2Frame::makeResetStreamFrame(uint32_t stream_index, ErrorCode err
Http2Frame Http2Frame::makeEmptyGoAwayFrame(uint32_t last_stream_index, ErrorCode error_code) {
static constexpr size_t kEmptyGoAwayPayloadSize = 8;
Http2Frame frame;
frame.buildHeader(Type::GoAway, kEmptyGoAwayPayloadSize, 0, makeRequestStreamId(0));
const uint32_t last_stream_id = makeRequestStreamId(last_stream_index);
frame.buildHeader(Type::GoAway, kEmptyGoAwayPayloadSize, 0);
const uint32_t last_stream_id = makeNetworkOrderStreamId(last_stream_index);
ASSERT(frame.data_.capacity() >= HeaderSize + 4 + sizeof(uint32_t));
memcpy(&frame.data_[HeaderSize], reinterpret_cast<const void*>(&last_stream_id),
sizeof(uint32_t));
Expand All @@ -215,7 +218,7 @@ Http2Frame Http2Frame::makeWindowUpdateFrame(uint32_t stream_index, uint32_t inc
static constexpr size_t kWindowUpdatePayloadSize = 4;
Http2Frame frame;
frame.buildHeader(Type::WindowUpdate, kWindowUpdatePayloadSize, 0,
makeRequestStreamId(stream_index));
makeNetworkOrderStreamId(stream_index));
const uint32_t increment_net = htonl(increment);
ASSERT(frame.data_.capacity() >= HeaderSize + sizeof(uint32_t));
memcpy(&frame.data_[HeaderSize], reinterpret_cast<const void*>(&increment_net), sizeof(uint32_t));
Expand Down Expand Up @@ -251,7 +254,7 @@ Http2Frame Http2Frame::makeMetadataFrameFromMetadataMap(uint32_t stream_index,

Http2Frame frame;
frame.buildHeader(Type::Metadata, numberOfBytesInMetadataPayload, static_cast<uint8_t>(flags),
makeRequestStreamId(stream_index));
makeNetworkOrderStreamId(stream_index));
std::vector<uint8_t> bufferVector(buffer, buffer + numberOfBytesInMetadataPayload);
frame.appendDataAfterHeaders(bufferVector);
delete[] buffer;
Expand All @@ -262,7 +265,7 @@ Http2Frame Http2Frame::makeMetadataFrameFromMetadataMap(uint32_t stream_index,
Http2Frame Http2Frame::makeMalformedRequest(uint32_t stream_index) {
Http2Frame frame;
frame.buildHeader(Type::Headers, 0, orFlags(HeadersFlags::EndStream, HeadersFlags::EndHeaders),
makeRequestStreamId(stream_index));
makeNetworkOrderStreamId(stream_index));
frame.appendStaticHeader(
StaticHeaderIndex::Status200); // send :status as request header, which is invalid
frame.adjustPayloadSize();
Expand All @@ -274,7 +277,7 @@ Http2Frame Http2Frame::makeMalformedRequestWithZerolenHeader(uint32_t stream_ind
absl::string_view path) {
Http2Frame frame;
frame.buildHeader(Type::Headers, 0, orFlags(HeadersFlags::EndStream, HeadersFlags::EndHeaders),
makeRequestStreamId(stream_index));
makeNetworkOrderStreamId(stream_index));
frame.appendStaticHeader(StaticHeaderIndex::MethodGet);
frame.appendStaticHeader(StaticHeaderIndex::SchemeHttps);
frame.appendHeaderWithoutIndexing(StaticHeaderIndex::Path, path);
Expand All @@ -288,7 +291,7 @@ Http2Frame Http2Frame::makeRequest(uint32_t stream_index, absl::string_view host
absl::string_view path) {
Http2Frame frame;
frame.buildHeader(Type::Headers, 0, orFlags(HeadersFlags::EndStream, HeadersFlags::EndHeaders),
makeRequestStreamId(stream_index));
makeNetworkOrderStreamId(stream_index));
frame.appendStaticHeader(StaticHeaderIndex::MethodGet);
frame.appendStaticHeader(StaticHeaderIndex::SchemeHttps);
frame.appendHeaderWithoutIndexing(StaticHeaderIndex::Path, path);
Expand All @@ -312,7 +315,7 @@ Http2Frame Http2Frame::makePostRequest(uint32_t stream_index, absl::string_view
absl::string_view path) {
Http2Frame frame;
frame.buildHeader(Type::Headers, 0, orFlags(HeadersFlags::EndHeaders),
makeRequestStreamId(stream_index));
makeNetworkOrderStreamId(stream_index));
frame.appendStaticHeader(StaticHeaderIndex::MethodPost);
frame.appendStaticHeader(StaticHeaderIndex::SchemeHttps);
frame.appendHeaderWithoutIndexing(StaticHeaderIndex::Path, path);
Expand Down
9 changes: 9 additions & 0 deletions test/common/http/http2/http2_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ class Http2Frame {
std::string value_;
};

/**
* Make client stream ID out of the given ID in the host byte order, ensuring that the stream id
* is odd as required by https://tools.ietf.org/html/rfc7540#section-5.1.1
* Use this function to create client stream ids for methods creating HTTP/2 frames.
* @param stream_id some stream id that will be used to create the client stream id.
* @return an odd number client stream id.
*/
static uint32_t makeClientStreamId(uint32_t stream_id) { return (stream_id << 1) | 1; }

// Methods for creating HTTP2 frames
static Http2Frame makePingFrame(absl::string_view data = {});
static Http2Frame makeEmptySettingsFrame(SettingsFlags flags = SettingsFlags::None);
Expand Down
4 changes: 2 additions & 2 deletions test/common/http/http2/http2_frame_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ TEST(EqualityMetadataFrame, Http2FrameTest) {
ASSERT_EQ(static_cast<int>(http2FrameFromUtility.type()), 0x4D); // type
ASSERT_EQ(payloadFromHttp2Frame[4], 4); // flags
ASSERT_EQ(std::to_string(payloadFromHttp2Frame[8]),
std::to_string(3)); // stream_id (extra bit at the end)
std::to_string(1)); // stream_id
}
} // namespace Http2
} // namespace Http
} // namespace Envoy
} // namespace Envoy
60 changes: 32 additions & 28 deletions test/integration/http2_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1702,14 +1702,14 @@ void Http2FloodMitigationTest::floodServer(absl::string_view host, absl::string_
Http2Frame::ResponseStatus expected_http_status,
const std::string& flood_stat, uint32_t num_frames) {
uint32_t request_idx = 0;
auto request = Http2Frame::makeRequest(request_idx, host, path);
auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(request_idx), host, path);
sendFrame(request);
auto frame = readFrame();
EXPECT_EQ(Http2Frame::Type::Headers, frame.type());
EXPECT_EQ(expected_http_status, frame.responseStatus());
writev_matcher_->setWritevReturnsEgain();
for (uint32_t frame = 0; frame < num_frames; ++frame) {
request = Http2Frame::makeRequest(++request_idx, host, path);
request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(++request_idx), host, path);
sendFrame(request);
}
tcp_client_->waitForDisconnect();
Expand Down Expand Up @@ -1767,8 +1767,8 @@ TEST_P(Http2FloodMitigationTest, Data) {
// to accumulate in the transport socket buffer.
writev_matcher_->setWritevReturnsEgain();

auto request = Http2Frame::makeRequest(0, "host", "/test/long/url",
{Http2Frame::Header("response_data_blocks", "1000")});
const auto request = Http2Frame::makeRequest(
1, "host", "/test/long/url", {Http2Frame::Header("response_data_blocks", "1000")});
sendFrame(request);

// Wait for 19077 bytes to arrive from upstream (1K DATA frames of size 10 + HEADERS frame)
Expand Down Expand Up @@ -1805,7 +1805,8 @@ TEST_P(Http2FloodMitigationTest, RST_STREAM) {
beginSession();

uint32_t stream_index = 0;
auto request = Http::Http2::Http2Frame::makeMalformedRequest(stream_index);
auto request =
Http::Http2::Http2Frame::makeMalformedRequest(Http2Frame::makeClientStreamId(stream_index));
sendFrame(request);
auto response = readFrame();
// Make sure we've got RST_STREAM from the server
Expand All @@ -1816,7 +1817,8 @@ TEST_P(Http2FloodMitigationTest, RST_STREAM) {
writev_matcher_->setWritevReturnsEgain();

for (++stream_index; stream_index < ControlFrameFloodLimit + 2; ++stream_index) {
request = Http::Http2::Http2Frame::makeMalformedRequest(stream_index);
request =
Http::Http2::Http2Frame::makeMalformedRequest(Http2Frame::makeClientStreamId(stream_index));
sendFrame(request);
}
tcp_client_->waitForDisconnect();
Expand Down Expand Up @@ -1855,8 +1857,7 @@ TEST_P(Http2FloodMitigationTest, EmptyHeaders) {
});
beginSession();

uint32_t request_idx = 0;
auto request = Http2Frame::makeEmptyHeadersFrame(request_idx);
const auto request = Http2Frame::makeEmptyHeadersFrame(Http2Frame::makeClientStreamId(0));
sendFrame(request);

tcp_client_->waitForDisconnect();
Expand All @@ -1870,12 +1871,12 @@ TEST_P(Http2FloodMitigationTest, EmptyHeadersContinuation) {
useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS%");
beginSession();

uint32_t request_idx = 0;
auto request = Http2Frame::makeEmptyHeadersFrame(request_idx);
const uint32_t request_stream_id = Http2Frame::makeClientStreamId(0);
auto request = Http2Frame::makeEmptyHeadersFrame(request_stream_id);
sendFrame(request);

for (int i = 0; i < 2; i++) {
request = Http2Frame::makeEmptyContinuationFrame(request_idx);
request = Http2Frame::makeEmptyContinuationFrame(request_stream_id);
sendFrame(request);
}

Expand All @@ -1891,12 +1892,12 @@ TEST_P(Http2FloodMitigationTest, EmptyData) {
useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS%");
beginSession();

uint32_t request_idx = 0;
auto request = Http2Frame::makePostRequest(request_idx, "host", "/");
const uint32_t request_stream_id = Http2Frame::makeClientStreamId(0);
auto request = Http2Frame::makePostRequest(request_stream_id, "host", "/");
sendFrame(request);

for (int i = 0; i < 2; i++) {
request = Http2Frame::makeEmptyDataFrame(request_idx);
request = Http2Frame::makeEmptyDataFrame(request_stream_id);
sendFrame(request);
}

Expand All @@ -1911,19 +1912,21 @@ TEST_P(Http2FloodMitigationTest, EmptyData) {
TEST_P(Http2FloodMitigationTest, PriorityIdleStream) {
beginSession();

floodServer(Http2Frame::makePriorityFrame(0, 1), "http2.inbound_priority_frames_flood",
floodServer(Http2Frame::makePriorityFrame(Http2Frame::makeClientStreamId(0),
Http2Frame::makeClientStreamId(1)),
"http2.inbound_priority_frames_flood",
Http2::Utility::OptionsLimits::DEFAULT_MAX_INBOUND_PRIORITY_FRAMES_PER_STREAM + 1);
}

TEST_P(Http2FloodMitigationTest, PriorityOpenStream) {
beginSession();

// Open stream.
uint32_t request_idx = 0;
auto request = Http2Frame::makeRequest(request_idx, "host", "/");
const uint32_t request_stream_id = Http2Frame::makeClientStreamId(0);
const auto request = Http2Frame::makeRequest(request_stream_id, "host", "/");
sendFrame(request);

floodServer(Http2Frame::makePriorityFrame(request_idx, request_idx + 1),
floodServer(Http2Frame::makePriorityFrame(request_stream_id, Http2Frame::makeClientStreamId(1)),
"http2.inbound_priority_frames_flood",
Http2::Utility::OptionsLimits::DEFAULT_MAX_INBOUND_PRIORITY_FRAMES_PER_STREAM * 2 +
1);
Expand All @@ -1934,14 +1937,14 @@ TEST_P(Http2FloodMitigationTest, PriorityClosedStream) {
beginSession();

// Open stream.
uint32_t request_idx = 0;
auto request = Http2Frame::makeRequest(request_idx, "host", "/");
const uint32_t request_stream_id = Http2Frame::makeClientStreamId(0);
const auto request = Http2Frame::makeRequest(request_stream_id, "host", "/");
sendFrame(request);
// Reading response marks this stream as closed in nghttp2.
auto frame = readFrame();
EXPECT_EQ(Http2Frame::Type::Headers, frame.type());

floodServer(Http2Frame::makePriorityFrame(request_idx, request_idx + 1),
floodServer(Http2Frame::makePriorityFrame(request_stream_id, Http2Frame::makeClientStreamId(1)),
"http2.inbound_priority_frames_flood",
Http2::Utility::OptionsLimits::DEFAULT_MAX_INBOUND_PRIORITY_FRAMES_PER_STREAM * 2 +
1);
Expand All @@ -1951,13 +1954,13 @@ TEST_P(Http2FloodMitigationTest, WindowUpdate) {
beginSession();

// Open stream.
uint32_t request_idx = 0;
auto request = Http2Frame::makeRequest(request_idx, "host", "/");
const uint32_t request_stream_id = Http2Frame::makeClientStreamId(0);
const auto request = Http2Frame::makeRequest(request_stream_id, "host", "/");
sendFrame(request);

// Since we do not send any DATA frames, only 4 sequential WINDOW_UPDATE frames should
// trigger flood protection.
floodServer(Http2Frame::makeWindowUpdateFrame(request_idx, 1),
floodServer(Http2Frame::makeWindowUpdateFrame(request_stream_id, 1),
"http2.inbound_window_update_frames_flood", 4);
}

Expand All @@ -1967,8 +1970,8 @@ TEST_P(Http2FloodMitigationTest, ZerolenHeader) {
beginSession();

// Send invalid request.
uint32_t request_idx = 0;
auto request = Http2Frame::makeMalformedRequestWithZerolenHeader(request_idx, "host", "/");
const auto request = Http2Frame::makeMalformedRequestWithZerolenHeader(
Http2Frame::makeClientStreamId(0), "host", "/");
sendFrame(request);

tcp_client_->waitForDisconnect();
Expand Down Expand Up @@ -1996,15 +1999,16 @@ TEST_P(Http2FloodMitigationTest, ZerolenHeaderAllowed) {

// Send invalid request.
uint32_t request_idx = 0;
auto request = Http2Frame::makeMalformedRequestWithZerolenHeader(request_idx, "host", "/");
auto request = Http2Frame::makeMalformedRequestWithZerolenHeader(
Http2Frame::makeClientStreamId(request_idx), "host", "/");
sendFrame(request);
// Make sure we've got RST_STREAM from the server.
auto response = readFrame();
EXPECT_EQ(Http2Frame::Type::RstStream, response.type());

// Send valid request using the same connection.
request_idx++;
request = Http2Frame::makeRequest(request_idx, "host", "/");
request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(request_idx), "host", "/");
sendFrame(request);
response = readFrame();
EXPECT_EQ(Http2Frame::Type::Headers, response.type());
Expand Down

0 comments on commit 52161ce

Please sign in to comment.