Line data Source code
1 : #include "source/common/http/http3/conn_pool.h"
2 :
3 : #include <cstdint>
4 : #include <memory>
5 :
6 : #include "envoy/event/dispatcher.h"
7 :
8 : #include "source/common/config/utility.h"
9 : #include "source/common/http/utility.h"
10 : #include "source/common/network/address_impl.h"
11 : #include "source/common/network/utility.h"
12 : #include "source/common/runtime/runtime_features.h"
13 : #include "source/common/upstream/upstream_impl.h"
14 :
15 : namespace Envoy {
16 : namespace Http {
17 : namespace Http3 {
18 : namespace {
19 :
20 0 : uint32_t getMaxStreams(const Upstream::ClusterInfo& cluster) {
21 0 : return PROTOBUF_GET_WRAPPED_OR_DEFAULT(cluster.http3Options().quic_protocol_options(),
22 0 : max_concurrent_streams, 100);
23 0 : }
24 :
25 : const Envoy::Ssl::ClientContextConfig&
26 0 : getConfig(Network::UpstreamTransportSocketFactory& transport_socket_factory) {
27 0 : ASSERT(transport_socket_factory.clientContextConfig().has_value());
28 0 : return transport_socket_factory.clientContextConfig().value();
29 0 : }
30 :
31 : std::string sni(const Network::TransportSocketOptionsConstSharedPtr& options,
32 0 : Upstream::HostConstSharedPtr host) {
33 0 : return options && options->serverNameOverride().has_value()
34 0 : ? options->serverNameOverride().value()
35 0 : : getConfig(host->transportSocketFactory()).serverNameIndication();
36 0 : }
37 :
38 : } // namespace
39 :
40 : ActiveClient::ActiveClient(Envoy::Http::HttpConnPoolImplBase& parent,
41 : Upstream::Host::CreateConnectionData& data)
42 : : MultiplexedActiveClientBase(
43 : parent, getMaxStreams(parent.host()->cluster()), getMaxStreams(parent.host()->cluster()),
44 : parent.host()->cluster().trafficStats()->upstream_cx_http3_total_, data),
45 0 : async_connect_callback_(parent_.dispatcher().createSchedulableCallback([this]() {
46 0 : if (state() != Envoy::ConnectionPool::ActiveClient::State::Connecting) {
47 0 : return;
48 0 : }
49 0 : codec_client_->connect();
50 0 : if (readyForStream()) {
51 : // This client can send early data, so check if there are any pending streams can be sent
52 : // as early data.
53 0 : parent_.onUpstreamReadyForEarlyData(*this);
54 0 : }
55 0 : })) {
56 0 : ASSERT(codec_client_);
57 0 : if (dynamic_cast<CodecClientProd*>(codec_client_.get()) == nullptr) {
58 : // Hasn't called connect() yet, schedule one for the next event loop.
59 0 : async_connect_callback_->scheduleCallbackNextIteration();
60 0 : }
61 0 : }
62 :
63 0 : void ActiveClient::onMaxStreamsChanged(uint32_t num_streams) {
64 0 : updateCapacity(num_streams);
65 0 : if (state() == ActiveClient::State::Busy && currentUnusedCapacity() != 0) {
66 0 : ENVOY_BUG(hasHandshakeCompleted(), "Received MAX_STREAM frame before handshake completed.");
67 0 : parent_.transitionActiveClientState(*this, ActiveClient::State::Ready);
68 : // If there's waiting streams, make sure the pool will now serve them.
69 0 : parent_.onUpstreamReady();
70 0 : } else if (currentUnusedCapacity() == 0 && state() == ActiveClient::State::ReadyForEarlyData) {
71 : // With HTTP/3 this can only happen during a rejected 0-RTT handshake.
72 0 : parent_.transitionActiveClientState(*this, ActiveClient::State::Busy);
73 0 : }
74 0 : }
75 :
76 : ConnectionPool::Cancellable* Http3ConnPoolImpl::newStream(Http::ResponseDecoder& response_decoder,
77 : ConnectionPool::Callbacks& callbacks,
78 0 : const Instance::StreamOptions& options) {
79 0 : ENVOY_BUG(options.can_use_http3_,
80 0 : "Trying to send request over h3 while alternate protocols is disabled.");
81 0 : return FixedHttpConnPoolImpl::newStream(response_decoder, callbacks, options);
82 0 : }
83 :
84 : Http3ConnPoolImpl::Http3ConnPoolImpl(
85 : Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority,
86 : Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options,
87 : const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options,
88 : Random::RandomGenerator& random_generator, Upstream::ClusterConnectivityState& state,
89 : CreateClientFn client_fn, CreateCodecFn codec_fn, std::vector<Http::Protocol> protocol,
90 : OptRef<PoolConnectResultCallback> connect_callback, Http::PersistentQuicInfo& quic_info)
91 : : FixedHttpConnPoolImpl(host, priority, dispatcher, options, transport_socket_options,
92 : random_generator, state, client_fn, codec_fn, protocol, {}, nullptr),
93 : quic_info_(dynamic_cast<Quic::PersistentQuicInfoImpl&>(quic_info)),
94 : server_id_(sni(transport_socket_options, host),
95 : static_cast<uint16_t>(host_->address()->ip()->port()), false),
96 0 : connect_callback_(connect_callback) {}
97 :
98 0 : void Http3ConnPoolImpl::onConnected(Envoy::ConnectionPool::ActiveClient&) {
99 0 : if (connect_callback_ != absl::nullopt) {
100 0 : connect_callback_->onHandshakeComplete();
101 0 : }
102 0 : }
103 :
104 0 : void Http3ConnPoolImpl::onConnectFailed(Envoy::ConnectionPool::ActiveClient& client) {
105 0 : ASSERT(client.numActiveStreams() == 0);
106 0 : if (static_cast<ActiveClient&>(client).hasCreatedStream() && connect_callback_ != absl::nullopt) {
107 0 : connect_callback_->onZeroRttHandshakeFailed();
108 0 : }
109 0 : }
110 :
111 : // Make sure all connections are torn down before quic_info_ is deleted.
112 0 : Http3ConnPoolImpl::~Http3ConnPoolImpl() { destructAllConnections(); }
113 :
114 : std::unique_ptr<Network::ClientConnection>
115 : Http3ConnPoolImpl::createClientConnection(Quic::QuicStatNames& quic_stat_names,
116 : OptRef<Http::HttpServerPropertiesCache> rtt_cache,
117 0 : Stats::Scope& scope) {
118 0 : std::shared_ptr<quic::QuicCryptoClientConfig> crypto_config =
119 0 : host_->transportSocketFactory().getCryptoConfig();
120 0 : if (crypto_config == nullptr) {
121 0 : return nullptr; // no secrets available yet.
122 0 : }
123 :
124 0 : auto upstream_local_address_selector = host()->cluster().getUpstreamLocalAddressSelector();
125 0 : auto upstream_local_address =
126 0 : upstream_local_address_selector->getUpstreamLocalAddress(host()->address(), socketOptions());
127 0 : auto source_address = upstream_local_address.address_;
128 :
129 0 : if (source_address == nullptr) {
130 0 : auto host_address = host()->address();
131 0 : source_address = Network::Utility::getLocalAddress(host_address->ip()->version());
132 0 : }
133 :
134 0 : return Quic::createQuicNetworkConnection(
135 0 : quic_info_, std::move(crypto_config), server_id_, dispatcher(), host()->address(),
136 0 : source_address, quic_stat_names, rtt_cache, scope, upstream_local_address.socket_options_,
137 0 : transportSocketOptions(), connection_id_generator_, host_->transportSocketFactory());
138 0 : }
139 :
140 : std::unique_ptr<Http3ConnPoolImpl>
141 : allocateConnPool(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator,
142 : Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority,
143 : const Network::ConnectionSocket::OptionsSharedPtr& options,
144 : const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options,
145 : Upstream::ClusterConnectivityState& state, Quic::QuicStatNames& quic_stat_names,
146 : OptRef<Http::HttpServerPropertiesCache> rtt_cache, Stats::Scope& scope,
147 : OptRef<PoolConnectResultCallback> connect_callback,
148 0 : Http::PersistentQuicInfo& quic_info) {
149 0 : return std::make_unique<Http3ConnPoolImpl>(
150 0 : host, priority, dispatcher, options, transport_socket_options, random_generator, state,
151 0 : [&quic_stat_names, rtt_cache,
152 0 : &scope](HttpConnPoolImplBase* pool) -> ::Envoy::ConnectionPool::ActiveClientPtr {
153 0 : ENVOY_LOG_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::pool), debug,
154 0 : "Creating Http/3 client");
155 : // If there's no ssl context, the secrets are not loaded. Fast-fail by returning null.
156 0 : auto factory = &pool->host()->transportSocketFactory();
157 0 : if (factory->sslCtx() == nullptr) {
158 0 : ENVOY_LOG_EVERY_POW_2_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::pool),
159 0 : warn,
160 0 : "Failed to create Http/3 client. Transport socket "
161 0 : "factory is not configured correctly.");
162 0 : return nullptr;
163 0 : }
164 0 : Http3ConnPoolImpl* h3_pool = reinterpret_cast<Http3ConnPoolImpl*>(pool);
165 0 : Upstream::Host::CreateConnectionData data{};
166 0 : data.host_description_ = pool->host();
167 0 : data.connection_ = h3_pool->createClientConnection(quic_stat_names, rtt_cache, scope);
168 0 : if (data.connection_ == nullptr) {
169 0 : ENVOY_LOG_EVERY_POW_2_TO_LOGGER(
170 0 : Envoy::Logger::Registry::getLog(Envoy::Logger::Id::pool), warn,
171 0 : "Failed to create Http/3 client. Failed to create quic network connection.");
172 0 : return nullptr;
173 0 : }
174 0 : auto client = std::make_unique<ActiveClient>(*pool, data);
175 0 : return client;
176 0 : },
177 0 : [](Upstream::Host::CreateConnectionData& data, HttpConnPoolImplBase* pool) {
178 : // Because HTTP/3 codec client connect() can close connection inline and can raise 0-RTT
179 : // event inline, and both cases need to have network callbacks and http callbacks wired up
180 : // to propagate the event, so do not call connect() during codec client construction.
181 0 : CodecClientPtr codec = std::make_unique<NoConnectCodecClientProd>(
182 0 : CodecType::HTTP3, std::move(data.connection_), data.host_description_,
183 0 : pool->dispatcher(), pool->randomGenerator(), pool->transportSocketOptions());
184 0 : return codec;
185 0 : },
186 0 : std::vector<Protocol>{Protocol::Http3}, connect_callback, quic_info);
187 0 : }
188 :
189 : } // namespace Http3
190 : } // namespace Http
191 : } // namespace Envoy
|