Line data Source code
1 : #pragma once 2 : 3 : #include <cstdint> 4 : #include <memory> 5 : 6 : #include "envoy/common/optref.h" 7 : #include "envoy/http/persistent_quic_info.h" 8 : #include "envoy/upstream/upstream.h" 9 : 10 : #include "source/common/http/codec_client.h" 11 : #include "source/common/http/conn_pool_base.h" 12 : 13 : #ifdef ENVOY_ENABLE_QUIC 14 : #include "source/common/quic/client_connection_factory_impl.h" 15 : #include "source/common/quic/envoy_quic_utils.h" 16 : #include "source/common/quic/quic_transport_socket_factory.h" 17 : #include "quiche/quic/core/deterministic_connection_id_generator.h" 18 : #else 19 : #error "http3 conn pool should not be built with QUIC disabled" 20 : #endif 21 : 22 : namespace Envoy { 23 : namespace Http { 24 : namespace Http3 { 25 : 26 : class ActiveClient : public MultiplexedActiveClientBase { 27 : public: 28 : ActiveClient(Envoy::Http::HttpConnPoolImplBase& parent, 29 : Upstream::Host::CreateConnectionData& data); 30 : 31 0 : ~ActiveClient() override { 32 0 : if (async_connect_callback_ != nullptr && async_connect_callback_->enabled()) { 33 0 : async_connect_callback_->cancel(); 34 0 : } 35 0 : } 36 : // Http::ConnectionCallbacks 37 : void onMaxStreamsChanged(uint32_t num_streams) override; 38 : 39 0 : RequestEncoder& newStreamEncoder(ResponseDecoder& response_decoder) override { 40 0 : ASSERT(quiche_capacity_ != 0); 41 0 : has_created_stream_ = true; 42 : // Each time a quic stream is allocated the quic capacity needs to get 43 : // decremented. See comments by quiche_capacity_. 44 0 : updateCapacity(quiche_capacity_ - 1); 45 0 : return MultiplexedActiveClientBase::newStreamEncoder(response_decoder); 46 0 : } 47 : 48 0 : uint32_t effectiveConcurrentStreamLimit() const override { 49 0 : return std::min<int64_t>(MultiplexedActiveClientBase::effectiveConcurrentStreamLimit(), 50 0 : quiche_capacity_); 51 0 : } 52 : 53 : // Overload the default capacity calculations to return the quic capacity 54 : // (modified by any stream limits in Envoy config) 55 0 : int64_t currentUnusedCapacity() const override { 56 0 : return std::min<int64_t>(quiche_capacity_, effectiveConcurrentStreamLimit()); 57 0 : } 58 : 59 : // Overridden to return true as long as the client is doing handshake even when it is ready for 60 : // early data streams. 61 0 : bool hasHandshakeCompleted() const override { return has_handshake_completed_; } 62 : 63 : // Overridden to include ReadyForEarlyData state. 64 0 : bool readyForStream() const override { 65 0 : return state() == State::Ready || state() == State::ReadyForEarlyData; 66 0 : } 67 : 68 0 : void updateCapacity(uint64_t new_quiche_capacity) { 69 : // Each time we update the capacity make sure to reflect the update in the 70 : // connection pool. 71 : // 72 : // Due to interplay between the max number of concurrent streams Envoy will 73 : // allow and the max number of streams per connection this is not as simple 74 : // as just updating based on the delta between quiche_capacity_ and 75 : // new_quiche_capacity, so we use the delta between the actual calculated 76 : // capacity before and after the update. 77 0 : uint64_t old_capacity = currentUnusedCapacity(); 78 0 : quiche_capacity_ = new_quiche_capacity; 79 0 : uint64_t new_capacity = currentUnusedCapacity(); 80 : 81 0 : if (new_capacity < old_capacity) { 82 0 : parent_.decrConnectingAndConnectedStreamCapacity(old_capacity - new_capacity, *this); 83 0 : } else if (old_capacity < new_capacity) { 84 0 : parent_.incrConnectingAndConnectedStreamCapacity(new_capacity - old_capacity, *this); 85 0 : } 86 0 : } 87 : 88 0 : bool hasCreatedStream() const { return has_created_stream_; } 89 : 90 : protected: 91 0 : bool supportsEarlyData() const override { return true; } 92 : 93 : private: 94 : // Unlike HTTP/2 and HTTP/1, rather than having a cap on the number of active 95 : // streams, QUIC has a fixed number of streams available which is updated via 96 : // the MAX_STREAMS frame. 97 : // 98 : // As such each time we create a new stream for QUIC, the capacity goes down 99 : // by one, but unlike the other two codecs it is _not_ restored on stream 100 : // closure. 101 : // 102 : // We track the QUIC capacity here, and overload currentUnusedCapacity so the 103 : // connection pool can accurately keep track of when it is safe to create new 104 : // streams. 105 : // 106 : // Though HTTP/3 should arguably start out with 0 stream capacity until the 107 : // initial handshake is complete and MAX_STREAMS frame has been received, 108 : // assume optimistically it will get ~100 streams, so that the connection pool 109 : // won't fetch a connection for each incoming stream but will assume that the 110 : // first connection will likely be able to serve 100. 111 : // This number will be updated to the correct value before the connection is 112 : // deemed connected, at which point further connections will be established if 113 : // necessary. 114 : uint64_t quiche_capacity_ = 100; 115 : // Used to schedule a deferred connect() call. Because HTTP/3 codec client can 116 : // do 0-RTT during connect(), deferring it to avoid handling network events during CodecClient 117 : // construction. 118 : Event::SchedulableCallbackPtr async_connect_callback_; 119 : // True if newStream() is ever called. 120 : bool has_created_stream_{false}; 121 : }; 122 : 123 : // An interface to propagate H3 handshake result. 124 : // TODO(danzh) add an API to propagate 0-RTT handshake failure. 125 : class PoolConnectResultCallback { 126 : public: 127 0 : virtual ~PoolConnectResultCallback() = default; 128 : 129 : // Called when the mandatory handshake is complete. This is when a HTTP/3 connection is regarded 130 : // as connected and is able to send requests. 131 : virtual void onHandshakeComplete() PURE; 132 : // Called upon connection close event from a client who hasn't finish handshake but already sent 133 : // early data. 134 : // TODO(danzh) actually call it from h3 pool. 135 : virtual void onZeroRttHandshakeFailed() PURE; 136 : }; 137 : 138 : // Http3 subclass of FixedHttpConnPoolImpl which exists to store quic data. 139 : class Http3ConnPoolImpl : public FixedHttpConnPoolImpl { 140 : public: 141 : Http3ConnPoolImpl(Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, 142 : Event::Dispatcher& dispatcher, 143 : const Network::ConnectionSocket::OptionsSharedPtr& options, 144 : const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, 145 : Random::RandomGenerator& random_generator, 146 : Upstream::ClusterConnectivityState& state, CreateClientFn client_fn, 147 : CreateCodecFn codec_fn, std::vector<Http::Protocol> protocol, 148 : OptRef<PoolConnectResultCallback> connect_callback, 149 : Http::PersistentQuicInfo& quic_info); 150 : 151 : ~Http3ConnPoolImpl() override; 152 : ConnectionPool::Cancellable* newStream(Http::ResponseDecoder& response_decoder, 153 : ConnectionPool::Callbacks& callbacks, 154 : const Instance::StreamOptions& options) override; 155 : 156 : // For HTTP/3 the base connection pool does not track stream capacity, rather 157 : // the HTTP3 active client does. 158 0 : bool trackStreamCapacity() override { return false; } 159 : 160 : std::unique_ptr<Network::ClientConnection> 161 : createClientConnection(Quic::QuicStatNames& quic_stat_names, 162 : OptRef<Http::HttpServerPropertiesCache> rtt_cache, Stats::Scope& scope); 163 : 164 : protected: 165 : void onConnected(Envoy::ConnectionPool::ActiveClient&) override; 166 : void onConnectFailed(Envoy::ConnectionPool::ActiveClient&) override; 167 : 168 : private: 169 : friend class Http3ConnPoolImplPeer; 170 : 171 : // Latches Quic helpers shared across the cluster 172 : Quic::PersistentQuicInfoImpl& quic_info_; 173 : // server-id can change over the lifetime of Envoy but will be consistent for a 174 : // given connection pool. 175 : quic::QuicServerId server_id_; 176 : // If not nullopt, called when the handshake state changes. 177 : OptRef<PoolConnectResultCallback> connect_callback_; 178 : 179 : quic::DeterministicConnectionIdGenerator connection_id_generator_{ 180 : quic::kQuicDefaultConnectionIdLength}; 181 : }; 182 : 183 : std::unique_ptr<Http3ConnPoolImpl> 184 : allocateConnPool(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, 185 : Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, 186 : const Network::ConnectionSocket::OptionsSharedPtr& options, 187 : const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, 188 : Upstream::ClusterConnectivityState& state, Quic::QuicStatNames& quic_stat_names, 189 : OptRef<Http::HttpServerPropertiesCache> rtt_cache, Stats::Scope& scope, 190 : OptRef<PoolConnectResultCallback> connect_callback, 191 : Http::PersistentQuicInfo& quic_info); 192 : 193 : } // namespace Http3 194 : } // namespace Http 195 : } // namespace Envoy