Skip to content

Commit ca4ca98

Browse files
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 <[email protected]>
1 parent d20dbc4 commit ca4ca98

File tree

5 files changed

+261
-0
lines changed

5 files changed

+261
-0
lines changed

source/common/http/BUILD

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,15 @@ envoy_cc_library(
149149
],
150150
)
151151

152+
envoy_cc_library(
153+
name = "alternate_protocols",
154+
srcs = ["alternate_protocols.cc"],
155+
hdrs = ["alternate_protocols.h"],
156+
deps = [
157+
"//include/envoy/common:time_interface",
158+
],
159+
)
160+
152161
envoy_cc_library(
153162
name = "conn_pool_grid",
154163
srcs = ["conn_pool_grid.cc"],
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include "common/http/alternate_protocols.h"
2+
3+
namespace Envoy {
4+
namespace Http {
5+
6+
AlternateProtocols::AlternateProtocol::AlternateProtocol(absl::string_view alpn,
7+
absl::string_view hostname, int port)
8+
: alpn_(alpn), hostname_(hostname), port_(port) {}
9+
10+
AlternateProtocols::Origin::Origin(absl::string_view scheme, absl::string_view hostname, int port)
11+
: scheme_(scheme), hostname_(hostname), port_(port) {}
12+
13+
AlternateProtocols::AlternateProtocols(TimeSource& time_source) : time_source_(time_source) {}
14+
15+
void AlternateProtocols::setAlternatives(const Origin& origin,
16+
const std::vector<AlternateProtocol>& protocols,
17+
const MonotonicTime& expiration) {
18+
Entry& entry = protocols_[origin];
19+
if (entry.protocols_ != protocols) {
20+
entry.protocols_ = protocols;
21+
}
22+
if (entry.expiration_ != expiration) {
23+
entry.expiration_ = expiration;
24+
}
25+
}
26+
27+
OptRef<const std::vector<AlternateProtocols::AlternateProtocol>>
28+
AlternateProtocols::findAlternatives(const Origin& origin) {
29+
auto entry_it = protocols_.find(origin);
30+
if (entry_it == protocols_.end()) {
31+
return makeOptRefFromPtr<const std::vector<AlternateProtocols::AlternateProtocol>>(nullptr);
32+
}
33+
34+
const Entry& entry = entry_it->second;
35+
if (time_source_.monotonicTime() > entry.expiration_) {
36+
// Expire the entry.
37+
protocols_.erase(entry_it);
38+
return makeOptRefFromPtr<const std::vector<AlternateProtocols::AlternateProtocol>>(nullptr);
39+
}
40+
return makeOptRef(entry.protocols_);
41+
}
42+
43+
size_t AlternateProtocols::size() const { return protocols_.size(); }
44+
45+
} // namespace Http
46+
} // namespace Envoy
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#pragma once
2+
3+
#include <map>
4+
#include <string>
5+
#include <tuple>
6+
#include <vector>
7+
8+
#include "envoy/common/optref.h"
9+
#include "envoy/common/time.h"
10+
11+
#include "absl/strings/string_view.h"
12+
13+
namespace Envoy {
14+
namespace Http {
15+
16+
// Tracks alternate protocols that can be used to make an HTTP connection to an origin server.
17+
// See https://tools.ietf.org/html/rfc7838 for HTTP Alternate Services and
18+
// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04 for the
19+
// "HTTPS" DNS resource record.
20+
class AlternateProtocols {
21+
public:
22+
// Represents an HTTP origin to be connected too.
23+
struct Origin {
24+
public:
25+
Origin(absl::string_view scheme, absl::string_view hostname, int port);
26+
27+
bool operator==(const Origin& other) const {
28+
return std::tie(scheme_, hostname_, port_) ==
29+
std::tie(other.scheme_, other.hostname_, other.port_);
30+
}
31+
32+
bool operator!=(const Origin& other) const { return !this->operator==(other); }
33+
34+
bool operator<(const Origin& other) const {
35+
return std::tie(scheme_, hostname_, port_) <
36+
std::tie(other.scheme_, other.hostname_, other.port_);
37+
}
38+
39+
bool operator>(const Origin& other) const {
40+
return std::tie(scheme_, hostname_, port_) >
41+
std::tie(other.scheme_, other.hostname_, other.port_);
42+
}
43+
44+
bool operator<=(const Origin& other) const {
45+
return std::tie(scheme_, hostname_, port_) <=
46+
std::tie(other.scheme_, other.hostname_, other.port_);
47+
}
48+
49+
bool operator>=(const Origin& other) const {
50+
return std::tie(scheme_, hostname_, port_) >=
51+
std::tie(other.scheme_, other.hostname_, other.port_);
52+
}
53+
54+
std::string scheme_;
55+
std::string hostname_;
56+
int port_{};
57+
};
58+
59+
// Represents an alternative protocol that can be used to connect to an origin.
60+
struct AlternateProtocol {
61+
public:
62+
AlternateProtocol(absl::string_view alpn, absl::string_view hostname, int port);
63+
64+
bool operator==(const AlternateProtocol& other) const {
65+
return std::tie(alpn_, hostname_, port_) ==
66+
std::tie(other.alpn_, other.hostname_, other.port_);
67+
}
68+
69+
bool operator!=(const AlternateProtocol& other) const { return !this->operator==(other); }
70+
71+
std::string alpn_;
72+
std::string hostname_;
73+
int port_;
74+
};
75+
76+
explicit AlternateProtocols(TimeSource& time_source);
77+
78+
// Sets the possible alternative protocols which can be used to connect to the
79+
// specified origin. Expires after the specified expiration time.
80+
void setAlternatives(const Origin& origin, const std::vector<AlternateProtocol>& protocols,
81+
const MonotonicTime& expiration);
82+
83+
// Returns the possible alternative protocols which can be used to connect to the
84+
// specified origin, or nullptr if not alternatives are found. The returned pointer
85+
// is owned by the AlternateProtocols and is valid until the next operation on
86+
// AlternateProtocols.
87+
OptRef<const std::vector<AlternateProtocol>> findAlternatives(const Origin& origin);
88+
89+
// Returns the number of entries in the map.
90+
size_t size() const;
91+
92+
private:
93+
struct Entry {
94+
std::vector<AlternateProtocol> protocols_;
95+
MonotonicTime expiration_;
96+
};
97+
98+
// Time source used to check expiration of entries.
99+
TimeSource& time_source_;
100+
101+
// Map from hostname to list of alternate protocols.
102+
// TODO(RyanTheOptimist): Add a limit to the size of this map and evict based on usage.
103+
std::map<Origin, Entry> protocols_;
104+
};
105+
106+
} // namespace Http
107+
} // namespace Envoy

test/common/http/BUILD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,17 @@ envoy_cc_test(
440440
]),
441441
)
442442

443+
envoy_cc_test(
444+
name = "alternate_protocols_test",
445+
srcs = ["alternate_protocols_test.cc"],
446+
deps = [
447+
":common_lib",
448+
"//source/common/http:alternate_protocols",
449+
"//test/mocks:common_lib",
450+
"//test/test_common:simulated_time_system_lib",
451+
],
452+
)
453+
443454
envoy_proto_library(
444455
name = "path_utility_fuzz_proto",
445456
srcs = ["path_utility_fuzz.proto"],
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#include "common/http/alternate_protocols.h"
2+
3+
#include "test/test_common/simulated_time_system.h"
4+
5+
#include "gtest/gtest.h"
6+
7+
namespace Envoy {
8+
namespace Http {
9+
10+
namespace {
11+
class AlternateProtocolsTest : public testing::Test, public Event::TestUsingSimulatedTime {
12+
public:
13+
AlternateProtocolsTest() : protocols_(simTime()) {}
14+
15+
AlternateProtocols protocols_;
16+
const std::string hostname1_ = "hostname1";
17+
const std::string hostname2_ = "hostname2";
18+
const int port1_ = 1;
19+
const int port2_ = 2;
20+
const std::string https_ = "https";
21+
const std::string http_ = "http";
22+
23+
const std::string alpn1_ = "alpn1";
24+
const std::string alpn2_ = "alpn2";
25+
26+
const AlternateProtocols::Origin origin1_ = {https_, hostname1_, port1_};
27+
const AlternateProtocols::Origin origin2_ = {https_, hostname2_, port2_};
28+
29+
const AlternateProtocols::AlternateProtocol protocol1_ = {alpn1_, hostname1_, port1_};
30+
const AlternateProtocols::AlternateProtocol protocol2_ = {alpn2_, hostname2_, port2_};
31+
32+
const std::vector<AlternateProtocols::AlternateProtocol> protocols1_ = {protocol1_};
33+
const std::vector<AlternateProtocols::AlternateProtocol> protocols2_ = {protocol2_};
34+
35+
const MonotonicTime expiration1_ = simTime().monotonicTime() + Seconds(5);
36+
const MonotonicTime expiration2_ = simTime().monotonicTime() + Seconds(10);
37+
};
38+
39+
TEST_F(AlternateProtocolsTest, Init) { EXPECT_EQ(0, protocols_.size()); }
40+
41+
TEST_F(AlternateProtocolsTest, SetAlternatives) {
42+
EXPECT_EQ(0, protocols_.size());
43+
protocols_.setAlternatives(origin1_, protocols1_, expiration1_);
44+
EXPECT_EQ(1, protocols_.size());
45+
}
46+
47+
TEST_F(AlternateProtocolsTest, FindAlternatives) {
48+
protocols_.setAlternatives(origin1_, protocols1_, expiration1_);
49+
OptRef<const std::vector<AlternateProtocols::AlternateProtocol>> protocols =
50+
protocols_.findAlternatives(origin1_);
51+
ASSERT_TRUE(protocols.has_value());
52+
EXPECT_EQ(protocols1_, protocols.ref());
53+
}
54+
55+
TEST_F(AlternateProtocolsTest, FindAlternativesAfterReplacement) {
56+
protocols_.setAlternatives(origin1_, protocols1_, expiration1_);
57+
protocols_.setAlternatives(origin1_, protocols2_, expiration2_);
58+
OptRef<const std::vector<AlternateProtocols::AlternateProtocol>> protocols =
59+
protocols_.findAlternatives(origin1_);
60+
ASSERT_TRUE(protocols.has_value());
61+
EXPECT_EQ(protocols2_, protocols.ref());
62+
EXPECT_NE(protocols1_, protocols.ref());
63+
}
64+
65+
TEST_F(AlternateProtocolsTest, FindAlternativesForMultipleOrigins) {
66+
protocols_.setAlternatives(origin1_, protocols1_, expiration1_);
67+
protocols_.setAlternatives(origin2_, protocols2_, expiration2_);
68+
OptRef<const std::vector<AlternateProtocols::AlternateProtocol>> protocols =
69+
protocols_.findAlternatives(origin1_);
70+
ASSERT_TRUE(protocols.has_value());
71+
EXPECT_EQ(protocols1_, protocols.ref());
72+
73+
protocols = protocols_.findAlternatives(origin2_);
74+
EXPECT_EQ(protocols2_, protocols.ref());
75+
}
76+
77+
TEST_F(AlternateProtocolsTest, FindAlternativesAfterExpiration) {
78+
protocols_.setAlternatives(origin1_, protocols1_, expiration1_);
79+
simTime().setMonotonicTime(expiration1_ + Seconds(1));
80+
OptRef<const std::vector<AlternateProtocols::AlternateProtocol>> protocols =
81+
protocols_.findAlternatives(origin1_);
82+
ASSERT_FALSE(protocols.has_value());
83+
EXPECT_EQ(0, protocols_.size());
84+
}
85+
86+
} // namespace
87+
} // namespace Http
88+
} // namespace Envoy

0 commit comments

Comments
 (0)